yew_scroll_area/
lib.rs

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/// Props for ScrollArea.
20#[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/// ScrollArea component.
44#[function_component(ScrollArea)]
45pub fn scroll_area(props: &Props) -> Html {
46    // props
47    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    // Node Refs
57    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    // Hide State
63    let hide_state = use_hide_state(outer_ref.clone(), hide_time);
64
65    // Scroll States
66    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    // Animation Frame
80    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}