Skip to main content

dioxus_element_plug/components/
slider.rs

1use dioxus::prelude::*;
2
3/// Slider props
4#[derive(Props, Clone, PartialEq)]
5pub struct SliderProps {
6    /// Current value
7    #[props(default = 0.0)]
8    pub model_value: f64,
9
10    /// Minimum value
11    #[props(default = 0.0)]
12    pub min: f64,
13
14    /// Maximum value
15    #[props(default = 100.0)]
16    pub max: f64,
17
18    /// Step size
19    #[props(default = 1.0)]
20    pub step: f64,
21
22    /// Whether the slider is disabled
23    #[props(default = false)]
24    pub disabled: bool,
25
26    /// Whether to show input
27    #[props(default = false)]
28    pub show_input: bool,
29
30    /// Whether to show stops
31    #[props(default = false)]
32    pub show_stops: bool,
33
34    /// Whether to show tooltip
35    #[props(default = true)]
36    pub show_tooltip: bool,
37
38    /// Slider orientation
39    #[props(default = "horizontal".to_string())]
40    pub direction: String,
41
42    /// Whether to allow range selection
43    #[props(default = false)]
44    pub range: bool,
45
46    /// Format tooltip text
47    #[props(default)]
48    pub format_tooltip: Option<String>,
49
50    /// Change event handler
51    #[props(default)]
52    pub on_change: Option<EventHandler<f64>>,
53
54    /// Input event handler
55    #[props(default)]
56    pub on_input: Option<EventHandler<f64>>,
57
58    /// Additional CSS classes
59    #[props(default)]
60    pub class: Option<String>,
61
62    /// Inline styles
63    #[props(default)]
64    pub style: Option<String>,
65}
66
67/// Slider component for selecting a value from a range
68///
69/// ## Example
70///
71/// ```rust,ignore
72/// rsx! {
73///     Slider {
74///         model_value: 50.0,
75///         min: 0.0,
76///         max: 100.0,
77///         on_change: move |v| println!("Value: {}", v),
78///     }
79/// }
80/// ```
81#[component]
82pub fn Slider(props: SliderProps) -> Element {
83    let mut class_names = vec!["el-slider".to_string()];
84
85    class_names.push(format!("is-{}", props.direction));
86
87    if props.disabled {
88        class_names.push("is-disabled".to_string());
89    }
90
91    if props.show_input {
92        class_names.push("el-slider--with-input".to_string());
93    }
94
95    if let Some(ref custom_class) = props.class {
96        class_names.push(custom_class.clone());
97    }
98
99    let class_string = class_names.join(" ");
100    let style_string = props.style.clone().unwrap_or_default();
101
102    let percentage = if props.max > props.min {
103        ((props.model_value - props.min) / (props.max - props.min) * 100.0).clamp(0.0, 100.0)
104    } else {
105        0.0
106    };
107
108    let bar_style = format!("width: {}%;", percentage);
109    let button_style = format!("left: {}%;", percentage);
110
111    let stops = if props.show_stops && props.step > 0.0 {
112        let mut stops_vec = Vec::new();
113        let mut val = props.min;
114        while val < props.max {
115            let stop_pct = ((val - props.min) / (props.max - props.min) * 100.0).clamp(0.0, 100.0);
116            stops_vec.push(stop_pct);
117            val += props.step;
118        }
119        stops_vec
120    } else {
121        vec![]
122    };
123
124    rsx! {
125        div {
126            class: "{class_string}",
127            style: "{style_string}",
128            role: "slider",
129            aria_valuemin: "{props.min}",
130            aria_valuemax: "{props.max}",
131            aria_valuenow: "{props.model_value}",
132            aria_disabled: "{props.disabled}",
133            div {
134                class: "el-slider__runway",
135                div {
136                    class: "el-slider__bar",
137                    style: "{bar_style}",
138                }
139                for stop_pct in stops.iter() {
140                    div {
141                        class: "el-slider__stop",
142                        style: "left: {stop_pct}%;",
143                    }
144                }
145                div {
146                    class: "el-slider__button-wrapper",
147                    style: "{button_style}",
148                    div {
149                        class: "el-slider__button",
150                    }
151                    if props.show_tooltip {
152                        div {
153                            class: "el-slider__tooltip",
154                            "{props.model_value}"
155                        }
156                    }
157                }
158            }
159            if props.show_input {
160                div {
161                    class: "el-slider__input",
162                    input {
163                        r#type: "number",
164                        value: "{props.model_value}",
165                        min: "{props.min}",
166                        max: "{props.max}",
167                        step: "{props.step}",
168                        disabled: props.disabled,
169                        onchange: move |e| {
170                            if let Some(handler) = props.on_change {
171                                if let Ok(val) = e.value().parse::<f64>() {
172                                    handler.call(val);
173                                }
174                            }
175                        },
176                    }
177                }
178            }
179        }
180    }
181}