1use dioxus::prelude::*;
2
3#[derive(Props, Clone, PartialEq)]
5pub struct StatisticProps {
6 #[props(optional)]
8 pub title: Option<Element>,
9 #[props(optional)]
11 pub value: Option<f64>,
12 #[props(optional)]
15 pub value_text: Option<String>,
16 #[props(optional)]
18 pub precision: Option<u8>,
19 #[props(optional)]
21 pub prefix: Option<Element>,
22 #[props(optional)]
24 pub suffix: Option<Element>,
25 #[props(optional)]
27 pub value_style: Option<String>,
28 #[props(optional)]
30 pub class: Option<String>,
31 #[props(optional)]
33 pub style: Option<String>,
34}
35
36fn format_value(props: &StatisticProps) -> String {
37 if let Some(text) = &props.value_text {
38 return text.clone();
39 }
40 let value = props.value.unwrap_or(0.0);
41 if let Some(precision) = props.precision {
42 let p: usize = precision as usize;
43 format!("{value:.p$}")
44 } else {
45 let s = format!("{value}");
47 if s.ends_with(".0") {
48 s.trim_end_matches(".0").to_string()
49 } else {
50 s
51 }
52 }
53}
54
55#[component]
57pub fn Statistic(props: StatisticProps) -> Element {
58 let display_text = format_value(&props);
59
60 let mut class_list = vec!["adui-statistic".to_string()];
61 if let Some(extra) = props.class.clone() {
62 class_list.push(extra);
63 }
64 let class_attr = class_list.join(" ");
65 let style_attr = props.style.clone().unwrap_or_default();
66 let value_style_attr = props.value_style.clone().unwrap_or_default();
67
68 rsx! {
69 div { class: "{class_attr}", style: "{style_attr}",
70 if let Some(title) = props.title.clone() {
71 div { class: "adui-statistic-title", {title} }
72 }
73 div { class: "adui-statistic-content",
74 if let Some(prefix) = props.prefix.clone() {
75 span { class: "adui-statistic-prefix", {prefix} }
76 }
77 span { class: "adui-statistic-value", style: "{value_style_attr}", "{display_text}" }
78 if let Some(suffix) = props.suffix.clone() {
79 span { class: "adui-statistic-suffix", {suffix} }
80 }
81 }
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn format_value_uses_value_text_first() {
92 let props = StatisticProps {
93 title: None,
94 value: Some(123.456),
95 value_text: Some("custom".into()),
96 precision: None,
97 prefix: None,
98 suffix: None,
99 value_style: None,
100 class: None,
101 style: None,
102 };
103 assert_eq!(format_value(&props), "custom");
104 }
105
106 #[test]
107 fn format_value_applies_precision() {
108 let props = StatisticProps {
109 title: None,
110 value: Some(std::f64::consts::PI),
111 value_text: None,
112 precision: Some(2),
113 prefix: None,
114 suffix: None,
115 value_style: None,
116 class: None,
117 style: None,
118 };
119 assert_eq!(format_value(&props), "3.14");
120 }
121
122 #[test]
123 fn format_value_trims_trailing_point_zero() {
124 let props = StatisticProps {
125 title: None,
126 value: Some(10.0),
127 value_text: None,
128 precision: None,
129 prefix: None,
130 suffix: None,
131 value_style: None,
132 class: None,
133 style: None,
134 };
135 assert_eq!(format_value(&props), "10");
136 }
137
138 #[test]
139 fn format_value_handles_negative_numbers() {
140 let props = StatisticProps {
141 title: None,
142 value: Some(-123.456),
143 value_text: None,
144 precision: None,
145 prefix: None,
146 suffix: None,
147 value_style: None,
148 class: None,
149 style: None,
150 };
151 assert_eq!(format_value(&props), "-123.456");
152 }
153
154 #[test]
155 fn format_value_handles_negative_integers() {
156 let props = StatisticProps {
157 title: None,
158 value: Some(-100.0),
159 value_text: None,
160 precision: None,
161 prefix: None,
162 suffix: None,
163 value_style: None,
164 class: None,
165 style: None,
166 };
167 assert_eq!(format_value(&props), "-100");
168 }
169
170 #[test]
171 fn format_value_handles_large_numbers() {
172 let props = StatisticProps {
173 title: None,
174 value: Some(1_000_000.0),
175 value_text: None,
176 precision: None,
177 prefix: None,
178 suffix: None,
179 value_style: None,
180 class: None,
181 style: None,
182 };
183 assert_eq!(format_value(&props), "1000000");
184 }
185
186 #[test]
187 fn format_value_handles_very_large_numbers() {
188 let props = StatisticProps {
189 title: None,
190 value: Some(1e15),
191 value_text: None,
192 precision: None,
193 prefix: None,
194 suffix: None,
195 value_style: None,
196 class: None,
197 style: None,
198 };
199 let result = format_value(&props);
200 assert!(!result.is_empty());
201 }
202
203 #[test]
204 fn format_value_precision_zero() {
205 let props = StatisticProps {
206 title: None,
207 value: Some(123.456),
208 value_text: None,
209 precision: Some(0),
210 prefix: None,
211 suffix: None,
212 value_style: None,
213 class: None,
214 style: None,
215 };
216 assert_eq!(format_value(&props), "123");
217 }
218
219 #[test]
220 fn format_value_precision_high() {
221 let props = StatisticProps {
222 title: None,
223 value: Some(123.456789),
224 value_text: None,
225 precision: Some(6),
226 prefix: None,
227 suffix: None,
228 value_style: None,
229 class: None,
230 style: None,
231 };
232 assert_eq!(format_value(&props), "123.456789");
233 }
234
235 #[test]
236 fn format_value_precision_with_negative() {
237 let props = StatisticProps {
238 title: None,
239 value: Some(-123.456),
240 value_text: None,
241 precision: Some(1),
242 prefix: None,
243 suffix: None,
244 value_style: None,
245 class: None,
246 style: None,
247 };
248 assert_eq!(format_value(&props), "-123.5");
249 }
250
251 #[test]
252 fn format_value_defaults_to_zero() {
253 let props = StatisticProps {
254 title: None,
255 value: None,
256 value_text: None,
257 precision: None,
258 prefix: None,
259 suffix: None,
260 value_style: None,
261 class: None,
262 style: None,
263 };
264 assert_eq!(format_value(&props), "0");
265 }
266
267 #[test]
268 fn format_value_defaults_to_zero_with_precision() {
269 let props = StatisticProps {
270 title: None,
271 value: None,
272 value_text: None,
273 precision: Some(2),
274 prefix: None,
275 suffix: None,
276 value_style: None,
277 class: None,
278 style: None,
279 };
280 assert_eq!(format_value(&props), "0.00");
281 }
282
283 #[test]
284 fn format_value_handles_small_decimals() {
285 let props = StatisticProps {
286 title: None,
287 value: Some(0.001),
288 value_text: None,
289 precision: None,
290 prefix: None,
291 suffix: None,
292 value_style: None,
293 class: None,
294 style: None,
295 };
296 assert_eq!(format_value(&props), "0.001");
297 }
298
299 #[test]
300 fn format_value_handles_zero() {
301 let props = StatisticProps {
302 title: None,
303 value: Some(0.0),
304 value_text: None,
305 precision: None,
306 prefix: None,
307 suffix: None,
308 value_style: None,
309 class: None,
310 style: None,
311 };
312 assert_eq!(format_value(&props), "0");
313 }
314
315 #[test]
316 fn format_value_handles_zero_with_precision() {
317 let props = StatisticProps {
318 title: None,
319 value: Some(0.0),
320 value_text: None,
321 precision: Some(2),
322 prefix: None,
323 suffix: None,
324 value_style: None,
325 class: None,
326 style: None,
327 };
328 assert_eq!(format_value(&props), "0.00");
329 }
330
331 #[test]
332 fn format_value_handles_nan() {
333 let props = StatisticProps {
334 title: None,
335 value: Some(f64::NAN),
336 value_text: None,
337 precision: None,
338 prefix: None,
339 suffix: None,
340 value_style: None,
341 class: None,
342 style: None,
343 };
344 let result = format_value(&props);
345 assert!(!result.is_empty());
347 }
348
349 #[test]
350 fn format_value_handles_infinity() {
351 let props = StatisticProps {
352 title: None,
353 value: Some(f64::INFINITY),
354 value_text: None,
355 precision: None,
356 prefix: None,
357 suffix: None,
358 value_style: None,
359 class: None,
360 style: None,
361 };
362 let result = format_value(&props);
363 assert!(!result.is_empty());
365 }
366
367 #[test]
368 fn format_value_value_text_overrides_everything() {
369 let props = StatisticProps {
370 title: None,
371 value: Some(999.999),
372 value_text: Some("Custom Text".into()),
373 precision: Some(5),
374 prefix: None,
375 suffix: None,
376 value_style: None,
377 class: None,
378 style: None,
379 };
380 assert_eq!(format_value(&props), "Custom Text");
381 }
382
383 #[test]
384 fn format_value_decimal_that_ends_with_zero() {
385 let props = StatisticProps {
386 title: None,
387 value: Some(123.450),
388 value_text: None,
389 precision: None,
390 prefix: None,
391 suffix: None,
392 value_style: None,
393 class: None,
394 style: None,
395 };
396 assert_eq!(format_value(&props), "123.45");
398 }
399
400 #[test]
401 fn statistic_props_defaults() {
402 }
405}