radix_leptos_progress/
progress.rs

1use std::fmt::{Display, Formatter};
2
3use leptos::{html::AnyElement, *};
4use radix_leptos_primitive::Primitive;
5
6const DEFAULT_MAX: f64 = 100.0;
7
8#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
9pub enum ProgressState {
10    Indeterminate,
11    Complete,
12    Loading,
13}
14
15impl Display for ProgressState {
16    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
17        write!(
18            f,
19            "{}",
20            match self {
21                ProgressState::Indeterminate => "indeterminate",
22                ProgressState::Complete => "complete",
23                ProgressState::Loading => "loading",
24            }
25        )
26    }
27}
28
29impl IntoAttribute for ProgressState {
30    fn into_attribute(self) -> Attribute {
31        Attribute::String(self.to_string().into())
32    }
33
34    fn into_attribute_boxed(self: Box<Self>) -> Attribute {
35        self.into_attribute()
36    }
37}
38
39#[derive(Clone, Debug)]
40struct ProgressContextValue {
41    value: Signal<Option<f64>>,
42    max: Signal<f64>,
43}
44
45#[component]
46pub fn Progress(
47    #[prop(into, optional)] value: MaybeProp<f64>,
48    #[prop(into, optional)] max: MaybeProp<f64>,
49    #[prop(into, optional)] get_value_label: Option<Box<dyn Fn(f64, f64) -> String>>,
50    #[prop(into, optional)] as_child: MaybeProp<bool>,
51    #[prop(optional)] node_ref: NodeRef<AnyElement>,
52    #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
53    children: ChildrenFn,
54) -> impl IntoView {
55    let get_value_label = get_value_label.unwrap_or(Box::new(default_get_value_label));
56    let max = Signal::derive(move || {
57        max.get()
58            .and_then(|max| match max == 0.0 {
59                true => None,
60                false => Some(max),
61            })
62            .unwrap_or(DEFAULT_MAX)
63    });
64    let value = Signal::derive(move || value.get().map(|value| value.min(max.get()).max(0.0)));
65
66    let value_label =
67        Signal::derive(move || value.get().map(|value| get_value_label(value, max.get())));
68
69    let context_value = ProgressContextValue { value, max };
70
71    let mut attrs = attrs.clone();
72    attrs.extend([
73        ("aria-valuemax", max.into_attribute()),
74        ("aria-valuemin", "0".into_attribute()),
75        ("aria-valuenow", value.into_attribute()),
76        ("aria-valuetext", value_label.into_attribute()),
77        ("role", "progressbar".into_attribute()),
78        (
79            "data-state",
80            (move || get_progress_state(value.get(), max.get())).into_attribute(),
81        ),
82        ("data-value", value.into_attribute()),
83        ("data-max", max.into_attribute()),
84    ]);
85
86    view! {
87        <Provider value=context_value>
88             <Primitive
89                element=html::div
90                as_child=as_child
91                node_ref=node_ref
92                attrs=attrs
93            >
94                {children()}
95            </Primitive>
96        </Provider>
97    }
98}
99
100#[component]
101pub fn ProgressIndicator(
102    #[prop(into, optional)] as_child: MaybeProp<bool>,
103    #[prop(optional)] node_ref: NodeRef<AnyElement>,
104    #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
105    #[prop(optional)] children: Option<ChildrenFn>,
106) -> impl IntoView {
107    let children = StoredValue::new(children);
108
109    let context = expect_context::<ProgressContextValue>();
110
111    let mut attrs = attrs.clone();
112    attrs.extend([
113        (
114            "data-state",
115            (move || get_progress_state(context.value.get(), context.max.get())).into_attribute(),
116        ),
117        ("data-value", context.value.into_attribute()),
118        ("data-max", context.max.into_attribute()),
119    ]);
120    let attrs = StoredValue::new(attrs);
121
122    view! {
123        <Primitive
124            element=html::div
125            as_child=as_child
126            node_ref=node_ref
127            attrs=attrs.get_value()
128        >
129            {children.with_value(|children| children.as_ref().map(|children| children()))}
130        </Primitive>
131    }
132}
133
134fn default_get_value_label(value: f64, max: f64) -> String {
135    format!("{}%", (value / max).round() * 100.0)
136}
137
138fn get_progress_state(value: Option<f64>, max_value: f64) -> ProgressState {
139    match value {
140        Some(value) => match value == max_value {
141            true => ProgressState::Complete,
142            false => ProgressState::Loading,
143        },
144        None => ProgressState::Indeterminate,
145    }
146}