dioxus_ui_system/molecules/
scroll_area.rs1use crate::styles::Style;
8use crate::theme::use_style;
9use dioxus::prelude::*;
10
11#[derive(Default, Clone, PartialEq)]
13pub enum ScrollOrientation {
14 #[default]
16 Vertical,
17 Horizontal,
19 Both,
21}
22
23#[derive(Props, Clone, PartialEq)]
25pub struct ScrollAreaProps {
26 pub children: Element,
28 #[props(default)]
30 pub orientation: ScrollOrientation,
31 #[props(default)]
33 pub class: Option<String>,
34 #[props(default)]
36 pub style: Option<String>,
37 #[props(default)]
39 pub scrollbar_size: Option<String>,
40 #[props(default)]
42 pub max_height: Option<String>,
43 #[props(default)]
45 pub max_width: Option<String>,
46 #[props(default)]
48 pub auto_hide: bool,
49}
50
51#[component]
73pub fn ScrollArea(props: ScrollAreaProps) -> Element {
74 let orientation = props.orientation.clone();
75 let auto_hide = props.auto_hide;
76 let scrollbar_size_val = props
77 .scrollbar_size
78 .clone()
79 .unwrap_or_else(|| "8px".to_string());
80 let max_height = props.max_height.clone();
81 let max_width = props.max_width.clone();
82
83 let scrollbar_colors = use_style(|t| {
85 let thumb_color = t.colors.border.darken(0.2);
86 let thumb_hover_color = t.colors.muted_foreground.clone();
87 let track_color = t.colors.muted.lighten(0.5);
88 let corner_color = t.colors.background.clone();
89
90 (thumb_color, thumb_hover_color, track_color, corner_color)
91 });
92
93 let container_style = use_style(move |t| {
95 let (thumb_color, _thumb_hover, track_color, _corner_color) = scrollbar_colors().clone();
96
97 let (overflow_x, overflow_y) = match orientation {
99 ScrollOrientation::Vertical => ("hidden", "auto"),
100 ScrollOrientation::Horizontal => ("auto", "hidden"),
101 ScrollOrientation::Both => ("auto", "auto"),
102 };
103
104 let mut style = Style::new()
105 .w_full()
106 .h_full()
107 .overflow_hidden() .rounded(&t.radius, "md")
109 .bg(&t.colors.background);
110
111 if let Some(ref max_h) = max_height {
113 style = Style {
114 max_height: Some(max_h.clone()),
115 ..style
116 };
117 }
118
119 if let Some(ref max_w) = max_width {
120 style = Style {
121 max_width: Some(max_w.clone()),
122 ..style
123 };
124 }
125
126 let base_style = style.build();
128
129 format!(
131 "{} --scrollbar-thumb: {}; --scrollbar-track: {}; --scrollbar-size: {}; overflow-x: {}; overflow-y: {}; scrollbar-width: thin; scrollbar-color: {} {};",
132 base_style,
133 thumb_color.to_rgba(),
134 track_color.to_rgba(),
135 scrollbar_size_val,
136 overflow_x,
137 overflow_y,
138 thumb_color.to_rgba(),
139 track_color.to_rgba()
140 )
141 });
142
143 let webkit_styles = use_style(move |t| {
145 let (thumb_color, thumb_hover_color, track_color, corner_color) =
146 scrollbar_colors().clone();
147 let size = props
148 .scrollbar_size
149 .clone()
150 .unwrap_or_else(|| "8px".to_string());
151
152 let hover_opacity = if auto_hide { "0" } else { "1" };
153 let hover_transition = if auto_hide {
154 "transition: opacity 0.2s ease;"
155 } else {
156 ""
157 };
158
159 format!(
160 r#"
161 .scroll-area::-webkit-scrollbar {{
162 width: {size};
163 height: {size};
164 {hover_transition}
165 opacity: {hover_opacity};
166 }}
167 .scroll-area::-webkit-scrollbar-track {{
168 background: {track};
169 border-radius: {radius}px;
170 }}
171 .scroll-area::-webkit-scrollbar-thumb {{
172 background: {thumb};
173 border-radius: {radius}px;
174 border: 2px solid transparent;
175 background-clip: content-box;
176 transition: background-color 0.2s ease;
177 }}
178 .scroll-area::-webkit-scrollbar-thumb:hover {{
179 background: {thumb_hover};
180 border: 2px solid transparent;
181 background-clip: content-box;
182 }}
183 .scroll-area::-webkit-scrollbar-corner {{
184 background: {corner};
185 }}
186 .scroll-area:hover::-webkit-scrollbar {{
187 opacity: 1;
188 }}
189 "#,
190 size = size,
191 track = track_color.to_rgba(),
192 thumb = thumb_color.to_rgba(),
193 thumb_hover = thumb_hover_color.to_rgba(),
194 corner = corner_color.to_rgba(),
195 radius = t.radius.md,
196 hover_transition = hover_transition,
197 hover_opacity = hover_opacity,
198 )
199 });
200
201 let custom_class = props.class.clone().unwrap_or_default();
202 let custom_style = props.style.clone().unwrap_or_default();
203
204 rsx! {
205 style { "{webkit_styles}" }
207
208 div {
209 class: "scroll-area {custom_class}",
210 style: "{container_style} {custom_style}",
211 {props.children}
212 }
213 }
214}
215
216#[derive(Props, Clone, PartialEq)]
221pub struct ScrollViewportProps {
222 pub children: Element,
224 #[props(default)]
226 pub class: Option<String>,
227 #[props(default)]
229 pub style: Option<String>,
230}
231
232#[component]
237pub fn ScrollViewport(props: ScrollViewportProps) -> Element {
238 let viewport_style = use_style(|t| {
239 Style::new()
240 .w_full()
241 .min_h_full()
242 .p(&t.spacing, "md")
243 .build()
244 });
245
246 let custom_class = props.class.clone().unwrap_or_default();
247 let custom_style = props.style.clone().unwrap_or_default();
248
249 rsx! {
250 div {
251 class: "scroll-viewport {custom_class}",
252 style: "{viewport_style} {custom_style}",
253 {props.children}
254 }
255 }
256}
257
258#[derive(Props, Clone, PartialEq)]
263pub struct HorizontalScrollProps {
264 pub children: Element,
266 #[props(default)]
268 pub class: Option<String>,
269 #[props(default)]
271 pub style: Option<String>,
272 #[props(default)]
274 pub height: Option<String>,
275 #[props(default = true)]
277 pub show_scrollbar: bool,
278}
279
280#[component]
285pub fn HorizontalScroll(props: HorizontalScrollProps) -> Element {
286 rsx! {
287 ScrollArea {
288 orientation: ScrollOrientation::Horizontal,
289 class: props.class.clone(),
290 style: props.style.clone(),
291 scrollbar_size: if props.show_scrollbar { None } else { Some("0px".to_string()) },
292 max_height: props.height.clone(),
293 {props.children}
294 }
295 }
296}
297
298#[derive(Props, Clone, PartialEq)]
303pub struct VerticalScrollProps {
304 pub children: Element,
306 #[props(default)]
308 pub class: Option<String>,
309 #[props(default)]
311 pub style: Option<String>,
312 #[props(default)]
314 pub max_height: Option<String>,
315 #[props(default)]
317 pub auto_hide: bool,
318}
319
320#[component]
325pub fn VerticalScroll(props: VerticalScrollProps) -> Element {
326 rsx! {
327 ScrollArea {
328 orientation: ScrollOrientation::Vertical,
329 class: props.class.clone(),
330 style: props.style.clone(),
331 max_height: props.max_height.clone(),
332 auto_hide: props.auto_hide,
333 {props.children}
334 }
335 }
336}