radix_leptos_progress/
progress.rs1use 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}