dioxus_material/
tab_row.rs

1use crate::{use_theme, Ripple};
2use dioxus::prelude::*;
3use dioxus_resize_observer::{use_resize, Rect};
4use dioxus_spring::{use_animated, use_spring};
5use dioxus_use_mounted::use_mounted;
6use std::{collections::HashMap, time::Duration};
7
8#[component]
9pub fn TabRow(tabs: Vec<Element>, selected: usize, onselect: EventHandler<usize>) -> Element {
10    let sizes = use_signal(HashMap::new);
11
12    let width = sizes
13        .read()
14        .get(&selected)
15        .map(|rect: &Rect| rect.width() as f32)
16        .unwrap_or_default();
17    let left: f32 = (0..selected)
18        .map(|idx| {
19            sizes
20                .read()
21                .get(&idx)
22                .map(|rect: &Rect| rect.width() as f32)
23                .unwrap_or_default()
24        })
25        .sum();
26
27    let value_ref = use_spring([width, left], Duration::from_millis(200));
28    let animated_ref = use_mounted();
29
30    let theme = use_theme();
31    let primary_color = theme.primary_color.clone();
32
33    use_animated(animated_ref, value_ref, move |[width, left]| {
34        format!(
35            r"
36            position: absolute;
37            bottom: 0;
38            left: {left}px;
39            width: {width}px;
40            height: 4px;
41            background: {primary_color};
42            "
43        )
44    });
45
46    rsx!(
47        div { position: "relative",
48            ul {
49                display: "flex",
50                flex_direction: "row",
51                justify_content: "space-evenly",
52                list_style: "none",
53                margin: 0,
54                padding: 0,
55                {tabs.iter().enumerate().map(|(idx, tab)| rsx!(TabRowItem { index: idx, sizes: sizes, onselect: move |idx| onselect.call(idx), {tab} }))}
56            }
57            div { onmounted: move |event| animated_ref.onmounted(event) }
58        }
59    )
60}
61
62#[component]
63fn TabRowItem(
64    children: Element,
65    index: usize,
66    sizes: Signal<HashMap<usize, Rect>>,
67    onselect: EventHandler<usize>,
68) -> Element {
69    let mounted = use_mounted();
70    let resize = use_resize(mounted);
71
72    use_effect(move || {
73        if let Some(content_rect) = &*resize.read() {
74            sizes
75                .write()
76                .entry(index)
77                .and_modify(|rect| *rect = content_rect.clone())
78                .or_insert(content_rect.clone());
79        }
80    });
81
82    rsx!(
83        li {
84            display: "flex",
85            flex_direction: "row",
86            flex: 1,
87            margin: 0,
88            padding: 0,
89            onmounted: move |event| mounted.onmounted(event),
90            Ripple { onclick: move |_| onselect.call(index), children }
91        }
92    )
93}