adui_dioxus/components/
progress.rs1use dioxus::prelude::*;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum ProgressStatus {
6 Normal,
7 Success,
8 Exception,
9 Active,
10}
11
12impl ProgressStatus {
13 fn as_class(&self) -> &'static str {
14 match self {
15 ProgressStatus::Normal => "adui-progress-status-normal",
16 ProgressStatus::Success => "adui-progress-status-success",
17 ProgressStatus::Exception => "adui-progress-status-exception",
18 ProgressStatus::Active => "adui-progress-status-active",
19 }
20 }
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum ProgressType {
26 Line,
27 Circle,
28}
29
30#[derive(Props, Clone, PartialEq)]
32pub struct ProgressProps {
33 #[props(default = 0.0)]
36 pub percent: f32,
37 #[props(optional)]
40 pub status: Option<ProgressStatus>,
41 #[props(default = true)]
43 pub show_info: bool,
44 #[props(default = ProgressType::Line)]
46 pub r#type: ProgressType,
47 #[props(optional)]
49 pub stroke_width: Option<f32>,
50 #[props(optional)]
52 pub class: Option<String>,
53 #[props(optional)]
55 pub style: Option<String>,
56}
57
58fn clamp_percent(value: f32) -> f32 {
59 if value.is_nan() {
60 0.0
61 } else {
62 value.clamp(0.0, 100.0)
63 }
64}
65
66fn resolve_status(percent: f32, status: Option<ProgressStatus>) -> ProgressStatus {
67 if let Some(s) = status {
68 s
69 } else if percent >= 100.0 {
70 ProgressStatus::Success
71 } else {
72 ProgressStatus::Normal
73 }
74}
75
76#[component]
78pub fn Progress(props: ProgressProps) -> Element {
79 let ProgressProps {
80 percent,
81 status,
82 show_info,
83 r#type,
84 stroke_width,
85 class,
86 style,
87 } = props;
88
89 let percent = clamp_percent(percent);
90 let status_value = resolve_status(percent, status);
91
92 let mut class_list = vec![
93 "adui-progress".to_string(),
94 status_value.as_class().to_string(),
95 ];
96 match r#type {
97 ProgressType::Line => class_list.push("adui-progress-line".into()),
98 ProgressType::Circle => class_list.push("adui-progress-circle".into()),
99 }
100 if let Some(extra) = class {
101 class_list.push(extra);
102 }
103 let class_attr = class_list.join(" ");
104 let style_attr = style.unwrap_or_default();
105
106 let display_text = format!("{}%", percent.round() as i32);
107
108 match r#type {
109 ProgressType::Line => {
110 let height = stroke_width.unwrap_or(6.0);
111 rsx! {
112 div { class: "{class_attr}", style: "{style_attr}",
113 div { class: "adui-progress-outer",
114 div { class: "adui-progress-inner",
115 div {
116 class: "adui-progress-bg",
117 style: "width:{percent}%;height:{height}px;",
118 }
119 }
120 }
121 if show_info {
122 span { class: "adui-progress-text", "{display_text}" }
123 }
124 }
125 }
126 }
127 ProgressType::Circle => {
128 let size = 80.0f32;
129 let border = stroke_width.unwrap_or(6.0);
130 let circle_style = format!(
131 "width:{size}px;height:{size}px;border-width:{border}px;background:conic-gradient(currentColor {percent}%, rgba(0,0,0,0.06) 0);",
132 );
133
134 rsx! {
135 div { class: "{class_attr}", style: "{style_attr}",
136 div { class: "adui-progress-circle-inner", style: "{circle_style}", }
137 if show_info {
138 div { class: "adui-progress-text", "{display_text}" }
139 }
140 }
141 }
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn clamp_percent_bounds_values() {
152 assert_eq!(clamp_percent(-10.0), 0.0);
153 assert_eq!(clamp_percent(0.0), 0.0);
154 assert_eq!(clamp_percent(50.0), 50.0);
155 assert_eq!(clamp_percent(120.0), 100.0);
156 }
157
158 #[test]
159 fn resolve_status_defaults_to_success_when_full() {
160 assert_eq!(resolve_status(100.0, None), ProgressStatus::Success);
161 assert_eq!(resolve_status(50.0, None), ProgressStatus::Normal);
162 assert_eq!(
163 resolve_status(80.0, Some(ProgressStatus::Exception)),
164 ProgressStatus::Exception
165 );
166 }
167}