1#![doc = include_str!("../README.md")]
2
3use gloo::render::request_animation_frame;
4use yew::prelude::*;
5use yew_style_in_rs::*;
6
7mod color;
8mod default_thumb;
9mod hooks;
10mod scroll_option;
11mod utils;
12
13use default_thumb::*;
14use hooks::*;
15
16pub use color::Color;
17pub use scroll_option::ScrollOption;
18
19#[derive(PartialEq, Properties)]
21pub struct Props {
22 #[prop_or_default]
23 pub horizontal: bool,
24 #[prop_or_default]
25 pub vertical: bool,
26 #[prop_or_default]
27 pub color: Color,
28 #[prop_or(4.0)]
29 pub width: f64,
30 #[prop_or(10.0)]
31 pub draggable_width: f64,
32 #[prop_or(true)]
33 pub round: bool,
34 #[prop_or(Some(1.5))]
35 pub hide_time: Option<f64>,
36 #[prop_or_default]
37 pub scroll_option: ScrollOption,
38 pub custom_vertical_thumb: Option<Html>,
39 pub custom_horizontal_thumb: Option<Html>,
40 pub children: Children,
41}
42
43#[function_component(ScrollArea)]
45pub fn scroll_area(props: &Props) -> Html {
46 let horizontal = props.horizontal;
48 let vertical = props.vertical;
49 let color = props.color;
50 let width = props.width;
51 let draggable_width = props.draggable_width;
52 let round = props.round;
53 let hide_time = props.hide_time;
54 let scroll_option = props.scroll_option;
55
56 let outer_ref = use_node_ref();
58 let inner_ref = use_node_ref();
59 let horizontal_thumb_ref = use_node_ref();
60 let vertical_thumb_ref = use_node_ref();
61
62 let hide_state = use_hide_state(outer_ref.clone(), hide_time);
64
65 let horizontal_state = use_horizontal_state(
67 outer_ref.clone(),
68 inner_ref.clone(),
69 horizontal_thumb_ref.clone(),
70 scroll_option,
71 );
72 let vertical_state = use_vertical_state(
73 outer_ref.clone(),
74 inner_ref.clone(),
75 vertical_thumb_ref.clone(),
76 scroll_option,
77 );
78
79 let animation_holder = use_mut_ref(|| None);
81 let animation = request_animation_frame({
82 let horizontal_state = horizontal_state.clone();
83 let vertical_state = vertical_state.clone();
84 move |timestamp| {
85 horizontal_state.update(timestamp);
86 vertical_state.update(timestamp);
87 }
88 });
89 *animation_holder.borrow_mut() = Some(animation);
90
91 let hide_class = hide_state.hide_class();
92
93 let horizontal_position = horizontal_state.scroll_position();
94 let horizontal_thumb_position = horizontal_state.thumb_position();
95 let horizontal_thumb_size = horizontal_state.thumb_size();
96
97 let vertical_position = vertical_state.scroll_position();
98 let vertical_thumb_position = vertical_state.thumb_position();
99 let vertical_thumb_size = vertical_state.thumb_size();
100
101 style! {
102 let css = css! {r#"
103 width: 100%;
104 height: 100%;
105 overflow: hidden;
106 position: relative;
107 touch-action: none;
108
109 & > div:first-of-type {
110 min-width: 100%;
111 min-height: 100%;
112 position: absolute;
113 top: 0;
114 left: 0;
115 }
116
117 & > .horizontal-thumb {
118 width: 100%;
119 background: transparent;
120 position: absolute;
121 bottom: 0;
122 left: 0;
123 opacity: 1;
124 transition: opacity 0.2s;
125 cursor: pointer;
126 }
127
128 & > .vertical-thumb {
129 height: 100%;
130 background: transparent;
131 position: absolute;
132 top: 0;
133 right: 0;
134 opacity: 1;
135 transition: opacity 0.2s;
136 cursor: pointer;
137 }
138
139 &.hide > .horizontal-thumb {
140 opacity: 0;
141 }
142 &.hide > .vertical-thumb {
143 opacity: 0;
144 }
145 "#};
146 let dyn_css = dyn css! {r#"
147 & > div:first-of-type {
148 transform: translateY(${vertical_position}px) translateX(${horizontal_position}px);
149 }
150 & > .horizontal-thumb {
151 transform: translateX(${horizontal_thumb_position}px);
152 width: ${horizontal_thumb_size}px;
153 height: ${draggable_width}px;
154 }
155 & > .vertical-thumb {
156 transform: translateY(${vertical_thumb_position}px);
157 width: ${draggable_width}px;
158 height: ${vertical_thumb_size}px;
159 }
160 "#};
161 }
162 html! {
163 <div ref={outer_ref} class={classes!(css, dyn_css, hide_class)}>
164 <div ref={inner_ref}>
165 {props.children.clone()}
166 </div>
167 if horizontal {
168 <div ref={horizontal_thumb_ref} class="horizontal-thumb">
169 if let Some(custom_horizontal_thumb) = props.custom_horizontal_thumb.clone() {
170 {custom_horizontal_thumb}
171 } else {
172 <DefaultHorizontalThumb {color} {width} {round}/>
173 }
174 </div>
175 }
176 if vertical {
177 <div ref={vertical_thumb_ref} class="vertical-thumb">
178 if let Some(custom_vertical_thumb) = props.custom_vertical_thumb.clone() {
179 {custom_vertical_thumb}
180 } else {
181 <DefaultVerticalThumb {color} {width} {round}/>
182 }
183 </div>
184 }
185 </div>
186 }
187}