1use dioxus::prelude::*;
2
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
5pub enum SpinSize {
6 Small,
7 #[default]
8 Default,
9 Large,
10}
11
12#[derive(Props, Clone, PartialEq)]
14pub struct SpinProps {
15 #[props(optional)]
17 pub spinning: Option<bool>,
18 #[props(optional)]
20 pub size: Option<SpinSize>,
21 #[props(optional)]
23 pub tip: Option<String>,
24 #[props(optional)]
26 pub class: Option<String>,
27 #[props(optional)]
29 pub style: Option<String>,
30 #[props(default)]
33 pub fullscreen: bool,
34 pub children: Element,
38}
39
40#[component]
42pub fn Spin(props: SpinProps) -> Element {
43 let SpinProps {
44 spinning,
45 size,
46 tip,
47 class,
48 style,
49 fullscreen,
50 children,
51 } = props;
52
53 let is_spinning = spinning.unwrap_or(true);
54 let size = size.unwrap_or_default();
55
56 let mut classes = vec!["adui-spin".to_string(), "adui-spin-nested".to_string()];
58 match size {
59 SpinSize::Small => classes.push("adui-spin-sm".into()),
60 SpinSize::Large => classes.push("adui-spin-lg".into()),
61 SpinSize::Default => {}
62 }
63 if fullscreen {
64 classes.push("adui-spin-fullscreen".into());
65 }
66 if let Some(extra) = class {
67 classes.push(extra);
68 }
69 let class_attr = classes.join(" ");
70 let style_attr = style.unwrap_or_default();
71
72 let tip_text = tip.unwrap_or_default();
73
74 if !is_spinning {
76 return rsx! {
77 div { class: "{class_attr}", style: "{style_attr}",
78 div { class: "adui-spin-nested-container", {children} }
79 }
80 };
81 }
82
83 rsx! {
85 div { class: "{class_attr}", style: "{style_attr}",
86 div { class: "adui-spin-nested-container", {children} }
87 div { class: "adui-spin-nested-mask",
88 div { class: "adui-spin-indicator",
89 span { class: "adui-spin-dot" }
90 }
91 if !tip_text.is_empty() {
92 div { class: "adui-spin-text", "{tip_text}" }
93 }
94 }
95 }
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn spin_size_default() {
105 assert_eq!(SpinSize::default(), SpinSize::Default);
106 }
107
108 #[test]
109 fn spin_size_variants() {
110 assert_ne!(SpinSize::Small, SpinSize::Default);
111 assert_ne!(SpinSize::Default, SpinSize::Large);
112 assert_ne!(SpinSize::Small, SpinSize::Large);
113 }
114
115 #[test]
116 fn spin_size_equality() {
117 assert_eq!(SpinSize::Small, SpinSize::Small);
118 assert_eq!(SpinSize::Default, SpinSize::Default);
119 assert_eq!(SpinSize::Large, SpinSize::Large);
120 }
121
122 #[test]
123 fn spin_size_clone() {
124 let original = SpinSize::Small;
125 let cloned = original;
126 assert_eq!(original, cloned);
127 }
128
129 #[test]
130 fn spin_props_defaults() {
131 let props = SpinProps {
132 spinning: None,
133 size: None,
134 tip: None,
135 class: None,
136 style: None,
137 fullscreen: false,
138 children: rsx!(div {}),
139 };
140 assert!(props.spinning.is_none());
141 assert!(props.size.is_none());
142 assert!(props.tip.is_none());
143 assert_eq!(props.fullscreen, false);
144 }
145
146 #[test]
147 fn spin_size_all_variants() {
148 let small = SpinSize::Small;
150 let default = SpinSize::Default;
151 let large = SpinSize::Large;
152
153 assert_ne!(small, default);
154 assert_ne!(default, large);
155 assert_ne!(small, large);
156 }
157
158 #[test]
159 fn spin_size_debug() {
160 let small = SpinSize::Small;
161 let default = SpinSize::Default;
162 let large = SpinSize::Large;
163
164 let small_str = format!("{:?}", small);
165 let default_str = format!("{:?}", default);
166 let large_str = format!("{:?}", large);
167
168 assert!(small_str.contains("Small"));
169 assert!(default_str.contains("Default"));
170 assert!(large_str.contains("Large"));
171 }
172
173 #[test]
174 fn spin_props_with_all_fields() {
175 let props = SpinProps {
176 spinning: Some(true),
177 size: Some(SpinSize::Large),
178 tip: Some("Loading...".into()),
179 class: Some("custom-class".into()),
180 style: Some("color: red;".into()),
181 fullscreen: true,
182 children: rsx!(div {}),
183 };
184 assert_eq!(props.spinning, Some(true));
185 assert_eq!(props.size, Some(SpinSize::Large));
186 assert_eq!(props.tip, Some("Loading...".into()));
187 assert_eq!(props.fullscreen, true);
188 }
189
190 #[test]
191 fn spin_props_spinning_false() {
192 let props = SpinProps {
193 spinning: Some(false),
194 size: None,
195 tip: None,
196 class: None,
197 style: None,
198 fullscreen: false,
199 children: rsx!(div {}),
200 };
201 assert_eq!(props.spinning, Some(false));
202 }
203
204 #[test]
205 fn spin_props_size_small() {
206 let props = SpinProps {
207 spinning: None,
208 size: Some(SpinSize::Small),
209 tip: None,
210 class: None,
211 style: None,
212 fullscreen: false,
213 children: rsx!(div {}),
214 };
215 assert_eq!(props.size, Some(SpinSize::Small));
216 }
217
218 #[test]
219 fn spin_props_size_default() {
220 let props = SpinProps {
221 spinning: None,
222 size: Some(SpinSize::Default),
223 tip: None,
224 class: None,
225 style: None,
226 fullscreen: false,
227 children: rsx!(div {}),
228 };
229 assert_eq!(props.size, Some(SpinSize::Default));
230 }
231
232 #[test]
233 fn spin_props_size_large() {
234 let props = SpinProps {
235 spinning: None,
236 size: Some(SpinSize::Large),
237 tip: None,
238 class: None,
239 style: None,
240 fullscreen: false,
241 children: rsx!(div {}),
242 };
243 assert_eq!(props.size, Some(SpinSize::Large));
244 }
245
246 #[test]
247 fn spin_props_with_tip() {
248 let props = SpinProps {
249 spinning: None,
250 size: None,
251 tip: Some("Please wait...".into()),
252 class: None,
253 style: None,
254 fullscreen: false,
255 children: rsx!(div {}),
256 };
257 assert_eq!(props.tip, Some("Please wait...".into()));
258 }
259
260 #[test]
261 fn spin_props_fullscreen() {
262 let props = SpinProps {
263 spinning: None,
264 size: None,
265 tip: None,
266 class: None,
267 style: None,
268 fullscreen: true,
269 children: rsx!(div {}),
270 };
271 assert_eq!(props.fullscreen, true);
272 }
273
274 #[test]
275 fn spin_props_all_combinations() {
276 let props = SpinProps {
278 spinning: Some(true),
279 size: Some(SpinSize::Small),
280 tip: Some("Loading".into()),
281 class: None,
282 style: None,
283 fullscreen: false,
284 children: rsx!(div {}),
285 };
286 assert_eq!(props.spinning, Some(true));
287 assert_eq!(props.size, Some(SpinSize::Small));
288 assert_eq!(props.tip, Some("Loading".into()));
289
290 let props = SpinProps {
292 spinning: Some(true),
293 size: Some(SpinSize::Large),
294 tip: None,
295 class: None,
296 style: None,
297 fullscreen: true,
298 children: rsx!(div {}),
299 };
300 assert_eq!(props.fullscreen, true);
301 assert_eq!(props.size, Some(SpinSize::Large));
302 }
303
304 #[test]
305 fn spin_props_clone() {
306 let props = SpinProps {
307 spinning: Some(true),
308 size: Some(SpinSize::Large),
309 tip: Some("Test".into()),
310 class: Some("test-class".into()),
311 style: Some("test-style".into()),
312 fullscreen: true,
313 children: rsx!(div {}),
314 };
315 let cloned = props.clone();
316 assert_eq!(props.spinning, cloned.spinning);
317 assert_eq!(props.size, cloned.size);
318 assert_eq!(props.tip, cloned.tip);
319 assert_eq!(props.fullscreen, cloned.fullscreen);
320 }
321
322 #[test]
323 fn spin_props_minimal() {
324 let props = SpinProps {
325 spinning: None,
326 size: None,
327 tip: None,
328 class: None,
329 style: None,
330 fullscreen: false,
331 children: rsx!(div {}),
332 };
333 assert!(props.spinning.is_none());
335 assert!(props.size.is_none());
336 assert!(props.tip.is_none());
337 assert_eq!(props.fullscreen, false);
338 }
339}