radix_leptos_primitives/components/
progress.rs1use leptos::children::Children;
2use leptos::prelude::*;
3use crate::utils::{merge_optional_classes, generate_id};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum ProgressVariant {
8 Default,
9 Destructive,
10 Success,
11 Warning,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum ProgressSize {
16 Default,
17 Sm,
18 Lg,
19}
20
21impl ProgressVariant {
22 pub fn as_str(&self) -> &'static str {
23 match self {
24 ProgressVariant::Default => "default",
25 ProgressVariant::Destructive => "destructive",
26 ProgressVariant::Success => "success",
27 ProgressVariant::Warning => "warning",
28 }
29 }
30}
31
32impl ProgressSize {
33 pub fn as_str(&self) -> &'static str {
34 match self {
35 ProgressSize::Default => "default",
36 ProgressSize::Sm => "sm",
37 ProgressSize::Lg => "lg",
38 }
39 }
40}
41
42
43#[component]
45pub fn Progress(
46 #[prop(optional, default = 0.0)]
48 value: f64,
49 #[prop(optional, default = 100.0)]
51 max: f64,
52 #[prop(optional, default = false)]
54 indeterminate: bool,
55 #[prop(optional, default = ProgressVariant::Default)]
57 variant: ProgressVariant,
58 #[prop(optional, default = ProgressSize::Default)]
60 size: ProgressSize,
61 #[prop(optional)]
63 class: Option<String>,
64 #[prop(optional)]
66 style: Option<String>,
67 children: Children,
69) -> impl IntoView {
70 let __progress_id = generate_id("progress");
71
72 let data_variant = variant.as_str();
74 let data_size = size.as_str();
75
76 let base_classes = "radix-progress";
78 let combined_class = merge_optional_classes(Some(base_classes), class.as_deref())
79 .unwrap_or_else(|| base_classes.to_string());
80
81 let percentage = if max > 0.0 && !indeterminate {
83 (value / max * 100.0).clamp(0.0, 100.0)
84 } else {
85 0.0
86 };
87
88 view! {
89 <div
90 class=combined_class
91 style=style
92 data-variant=data_variant
93 data-size=data_size
94 data-value=value
95 data-max=max
96 data-indeterminate=indeterminate
97 data-percentage=percentage
98 role="progressbar"
99 aria-valuemin=0.0
100 aria-valuemax=max
101 >
102 </div>
103 }
104}
105
106#[component]
108pub fn ProgressTrack(
109 #[prop(optional)]
111 class: Option<String>,
112 #[prop(optional)]
114 style: Option<String>,
115) -> impl IntoView {
116 let base_classes = "radix-progress-track";
117 let combined_class = merge_optional_classes(Some(base_classes), class.as_deref())
118 .unwrap_or_else(|| base_classes.to_string());
119
120 view! {
121 <div
122 class=combined_class
123 style=style
124 >
125 </div>
126 }
127}
128
129#[component]
131pub fn ProgressIndicator(
132 #[prop(optional)]
134 class: Option<String>,
135 #[prop(optional)]
137 style: Option<String>,
138) -> impl IntoView {
139 let base_classes = "radix-progress-indicator";
140 let combined_class = merge_optional_classes(Some(base_classes), class.as_deref())
141 .unwrap_or_else(|| base_classes.to_string());
142
143 view! {
144 <div
145 class=combined_class
146 style=style
147 >
148 </div>
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use crate::{ProgressSize, ProgressVariant};
155
156 use proptest::prelude::*;
157use crate::utils::{merge_optional_classes, generate_id};
158
159 #[test]
161 fn test_progress_variants() {
162 run_test(|| {
163 let variants = [
164 ProgressVariant::Default,
165 ProgressVariant::Destructive,
166 ProgressVariant::Success,
167 ProgressVariant::Warning,
168 ];
169
170 for variant in variants {
171 assert!(!variant.as_str().is_empty());
172 }
173 });
174 }
175
176 #[test]
177 fn test_progress_sizes() {
178 run_test(|| {
179 let sizes = [ProgressSize::Default, ProgressSize::Sm, ProgressSize::Lg];
180
181 for size in sizes {
182 assert!(!size.as_str().is_empty());
183 }
184 });
185 }
186
187 #[test]
189 fn test_progress_default_values() {
190 run_test(|| {
191 let value = 0.0;
192 let _max = 100.0;
193 let indeterminate = false;
194 let variant = ProgressVariant::Default;
195 let size = ProgressSize::Default;
196
197 assert_eq!(value, 0.0);
198 assert_eq!(_max, 100.0);
199 assert!(!indeterminate);
200 assert_eq!(variant, ProgressVariant::Default);
201 assert_eq!(size, ProgressSize::Default);
202 });
203 }
204
205 #[test]
206 fn test_progress_custom_values() {
207 run_test(|| {
208 let value = 50.0;
209 let _max = 200.0;
210 let indeterminate = false;
211 let variant = ProgressVariant::Success;
212 let size = ProgressSize::Lg;
213
214 assert_eq!(value, 50.0);
215 assert_eq!(_max, 200.0);
216 assert!(!indeterminate);
217 assert_eq!(variant, ProgressVariant::Success);
218 assert_eq!(size, ProgressSize::Lg);
219 });
220 }
221
222 #[test]
223 fn test_progressindeterminate_state() {
224 run_test(|| {
225 let value = 0.0;
226 let _max = 100.0;
227 let indeterminate = true;
228 let variant = ProgressVariant::Warning;
229 let size = ProgressSize::Sm;
230
231 assert_eq!(value, 0.0);
232 assert_eq!(_max, 100.0);
233 assert!(indeterminate);
234 assert_eq!(variant, ProgressVariant::Warning);
235 assert_eq!(size, ProgressSize::Sm);
236 });
237 }
238
239 #[test]
241 fn test_progress_value_calculation() {
242 run_test(|| {
243 let value = 50.0;
244 let _max = 100.0;
245 let indeterminate = false;
246
247 let percentage = if _max > 0.0 && !indeterminate {
249 (value / _max * 100.0f64).clamp(0.0f64, 100.0f64)
250 } else {
251 0.0
252 };
253
254 assert_eq!(percentage, 50.0);
255 });
256 }
257
258 #[test]
259 fn test_progress_value_bounds() {
260 run_test(|| {
261 let _max = 100.0;
262 let indeterminate = false;
263
264 let value_below_min = -10.0;
266 let value_above_max = 150.0;
267 let value_in_range = 50.0;
268
269 let percentage_below = if _max > 0.0 && !indeterminate {
270 (value_below_min / _max * 100.0f64).clamp(0.0f64, 100.0f64)
271 } else {
272 0.0
273 };
274
275 let percentage_above = if _max > 0.0 && !indeterminate {
276 (value_above_max / _max * 100.0f64).clamp(0.0f64, 100.0f64)
277 } else {
278 0.0
279 };
280
281 let percentage_in_range = if _max > 0.0 && !indeterminate {
282 (value_in_range / _max * 100.0f64).clamp(0.0f64, 100.0f64)
283 } else {
284 0.0
285 };
286
287 assert_eq!(percentage_below, 0.0);
288 assert_eq!(percentage_above, 100.0);
289 assert_eq!(percentage_in_range, 50.0);
290 });
291 }
292
293 #[test]
295 fn test_progressindeterminate_calculation() {
296 run_test(|| {
297 let value = 50.0;
298 let _max = 100.0;
299 let indeterminate = true;
300
301 let percentage = if _max > 0.0 && !indeterminate {
303 (value / _max * 100.0f64).clamp(0.0f64, 100.0f64)
304 } else {
305 0.0
306 };
307
308 assert_eq!(percentage, 0.0);
309 });
310 }
311
312 #[test]
313 fn test_progressindeterminate_aria() {
314 run_test(|| {
315 let value = 50.0;
316 let _max = 100.0;
317 let indeterminate = true;
318
319 assert!(indeterminate);
321 });
322 }
323
324 #[test]
326 fn test_progress_accessibility() {
327 run_test(|| {
328 let role = "progressbar";
329 let aria_valuemin = 0.0;
330 let aria_valuemax = 100.0;
331 let aria_valuenow = Some(50.0);
332 let aria_label = "Progress";
333
334 assert_eq!(role, "progressbar");
335 assert_eq!(aria_valuemin, 0.0);
336 assert_eq!(aria_valuemax, 100.0);
337 assert_eq!(aria_valuenow, Some(50.0));
338 assert_eq!(aria_label, "Progress");
339 });
340 }
341
342 #[test]
344 fn test_progress_edge_cases() {
345 run_test(|| {
346 let value = 50.0;
348 let _max = 0.0;
349 let indeterminate = false;
350
351 let percentage = if _max > 0.0 && !indeterminate {
352 (value / _max * 100.0f64).clamp(0.0f64, 100.0f64)
353 } else {
354 0.0
355 };
356
357 assert_eq!(percentage, 0.0);
358 });
359 }
360
361 #[test]
362 fn test_progress_negative_values() {
363 run_test(|| {
364 let value = -25.0;
365 let _max = 100.0;
366 let indeterminate = false;
367
368 let percentage = if _max > 0.0 && !indeterminate {
369 (value / _max * 100.0f64).clamp(0.0f64, 100.0f64)
370 } else {
371 0.0
372 };
373
374 assert_eq!(percentage, 0.0);
375 });
376 }
377
378 #[test]
379 fn test_progress_completion_state() {
380 run_test(|| {
381 let value = 100.0;
382 let _max = 100.0;
383 let indeterminate = false;
384
385 let percentage = if _max > 0.0 && !indeterminate {
386 (value / _max * 100.0f64).clamp(0.0f64, 100.0f64)
387 } else {
388 0.0
389 };
390
391 assert_eq!(percentage, 100.0);
392 });
393 }
394
395 proptest! {
397 #[test]
398 fn test_progress_properties(
399 variant in prop::sample::select(&[
400 ProgressVariant::Default,
401 ProgressVariant::Destructive,
402 ProgressVariant::Success,
403 ProgressVariant::Warning,
404 ]),
405 size in prop::sample::select(&[
406 ProgressSize::Default,
407 ProgressSize::Sm,
408 ProgressSize::Lg,
409 ]),
410 value in -100.0..1000.0f64,
411 __max in 0.1..1000.0f64,
412 indeterminate in prop::bool::ANY
413 ) {
414 assert!(!variant.as_str().is_empty());
415 assert!(!size.as_str().is_empty());
416
417 assert!(matches!(indeterminate, true | false));
419 assert!(__max > 0.0);
420
421 let percentage = if __max > 0.0 && !indeterminate {
423 (value / __max * 100.0f64).clamp(0.0f64, 100.0f64)
424 } else {
425 0.0
426 };
427
428 assert!((0.0..=100.0).contains(&percentage));
429
430 }
433 }
434
435 fn run_test<F>(f: F)
437 where
438 F: FnOnce(),
439 {
440 f();
441 }
442}