1use dioxus::prelude::*;
2
3#[derive(Props, Clone, PartialEq)]
5pub struct SkeletonProps {
6 #[props(optional)]
8 pub loading: Option<bool>,
9 #[props(default)]
11 pub active: bool,
12 #[props(default = true)]
14 pub title: bool,
15 #[props(optional)]
17 pub paragraph_rows: Option<u8>,
18 #[props(optional)]
20 pub class: Option<String>,
21 #[props(optional)]
23 pub style: Option<String>,
24 #[props(optional)]
27 pub content: Option<Element>,
28}
29
30#[component]
32pub fn Skeleton(props: SkeletonProps) -> Element {
33 let SkeletonProps {
34 loading,
35 active,
36 title,
37 paragraph_rows,
38 class,
39 style,
40 content,
41 } = props;
42
43 let is_loading = loading.unwrap_or(true);
44
45 if !is_loading {
46 if let Some(node) = content {
47 return node;
48 }
49 return rsx! {};
50 }
51
52 let mut classes = vec!["adui-skeleton".to_string()];
53 if active {
54 classes.push("adui-skeleton-active".into());
55 }
56 if let Some(extra) = class {
57 classes.push(extra);
58 }
59 let class_attr = classes.join(" ");
60 let style_attr = style.unwrap_or_default();
61
62 let rows = paragraph_rows.unwrap_or(3).max(1);
63
64 rsx! {
65 div { class: "{class_attr}", style: "{style_attr}",
66 if title {
67 div { class: "adui-skeleton-title" }
68 }
69 div { class: "adui-skeleton-paragraph",
70 {(0..rows).map(|idx| {
71 let mut line_class = "adui-skeleton-paragraph-line".to_string();
72 if idx == rows - 1 {
73 line_class.push_str(" adui-skeleton-paragraph-line-last");
74 }
75 rsx! {
76 div { class: "{line_class}" }
77 }
78 })}
79 }
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn skeleton_props_defaults() {
90 let props = SkeletonProps {
91 loading: None,
92 active: false,
93 title: true,
94 paragraph_rows: None,
95 class: None,
96 style: None,
97 content: None,
98 };
99 assert!(props.loading.is_none());
100 assert_eq!(props.active, false);
101 assert_eq!(props.title, true);
102 }
103
104 #[test]
105 fn skeleton_props_loading() {
106 let props = SkeletonProps {
107 loading: Some(true),
108 active: false,
109 title: true,
110 paragraph_rows: None,
111 class: None,
112 style: None,
113 content: None,
114 };
115 assert_eq!(props.loading, Some(true));
116 }
117
118 #[test]
119 fn skeleton_props_active() {
120 let props = SkeletonProps {
121 loading: None,
122 active: true,
123 title: true,
124 paragraph_rows: None,
125 class: None,
126 style: None,
127 content: None,
128 };
129 assert_eq!(props.active, true);
130 }
131
132 #[test]
133 fn skeleton_props_title() {
134 let props = SkeletonProps {
135 loading: None,
136 active: false,
137 title: false,
138 paragraph_rows: None,
139 class: None,
140 style: None,
141 content: None,
142 };
143 assert_eq!(props.title, false);
144 }
145
146 #[test]
147 fn skeleton_props_paragraph_rows() {
148 let props = SkeletonProps {
149 loading: None,
150 active: false,
151 title: true,
152 paragraph_rows: Some(5),
153 class: None,
154 style: None,
155 content: None,
156 };
157 assert_eq!(props.paragraph_rows, Some(5));
158 }
159
160 #[test]
161 fn skeleton_paragraph_rows_minimum() {
162 let rows = 0u8;
164 let min_rows = rows.max(1);
165 assert_eq!(min_rows, 1);
166
167 let rows2 = 3u8;
168 let min_rows2 = rows2.max(1);
169 assert_eq!(min_rows2, 3);
170 }
171
172 #[test]
173 fn skeleton_paragraph_rows_boundary_values() {
174 assert_eq!(0u8.max(1), 1);
176 assert_eq!(1u8.max(1), 1);
177 assert_eq!(2u8.max(1), 2);
178 assert_eq!(255u8.max(1), 255);
179 }
180
181 #[test]
182 fn skeleton_props_loading_false() {
183 let props = SkeletonProps {
184 loading: Some(false),
185 active: false,
186 title: true,
187 paragraph_rows: None,
188 class: None,
189 style: None,
190 content: None,
191 };
192 assert_eq!(props.loading, Some(false));
193 }
194
195 #[test]
196 fn skeleton_props_all_combinations() {
197 let props = SkeletonProps {
199 loading: None,
200 active: true,
201 title: true,
202 paragraph_rows: Some(5),
203 class: None,
204 style: None,
205 content: None,
206 };
207 assert_eq!(props.active, true);
208 assert_eq!(props.title, true);
209 assert_eq!(props.paragraph_rows, Some(5));
210
211 let props = SkeletonProps {
213 loading: Some(true),
214 active: true,
215 title: false,
216 paragraph_rows: None,
217 class: None,
218 style: None,
219 content: None,
220 };
221 assert_eq!(props.loading, Some(true));
222 assert_eq!(props.active, true);
223 }
224
225 #[test]
226 fn skeleton_props_with_class_and_style() {
227 let props = SkeletonProps {
228 loading: None,
229 active: false,
230 title: true,
231 paragraph_rows: None,
232 class: Some("custom-class".into()),
233 style: Some("color: red;".into()),
234 content: None,
235 };
236 assert_eq!(props.class, Some("custom-class".into()));
237 assert_eq!(props.style, Some("color: red;".into()));
238 }
239
240 #[test]
241 fn skeleton_props_clone() {
242 let props = SkeletonProps {
243 loading: Some(true),
244 active: true,
245 title: false,
246 paragraph_rows: Some(10),
247 class: Some("test".into()),
248 style: Some("test-style".into()),
249 content: None,
250 };
251 let cloned = props.clone();
252 assert_eq!(props.loading, cloned.loading);
253 assert_eq!(props.active, cloned.active);
254 assert_eq!(props.title, cloned.title);
255 assert_eq!(props.paragraph_rows, cloned.paragraph_rows);
256 assert_eq!(props.class, cloned.class);
257 assert_eq!(props.style, cloned.style);
258 }
259
260 #[test]
261 fn skeleton_props_paragraph_rows_edge_cases() {
262 let props = SkeletonProps {
264 loading: None,
265 active: false,
266 title: true,
267 paragraph_rows: Some(1),
268 class: None,
269 style: None,
270 content: None,
271 };
272 assert_eq!(props.paragraph_rows, Some(1));
273
274 let props = SkeletonProps {
276 loading: None,
277 active: false,
278 title: true,
279 paragraph_rows: Some(255),
280 class: None,
281 style: None,
282 content: None,
283 };
284 assert_eq!(props.paragraph_rows, Some(255));
285 }
286
287 #[test]
288 fn skeleton_props_minimal() {
289 let props = SkeletonProps {
290 loading: None,
291 active: false,
292 title: true,
293 paragraph_rows: None,
294 class: None,
295 style: None,
296 content: None,
297 };
298 assert!(props.loading.is_none());
300 assert_eq!(props.active, false);
301 assert_eq!(props.title, true);
302 assert!(props.paragraph_rows.is_none());
303 }
304
305 #[test]
306 fn skeleton_props_loading_none_means_true() {
307 let props = SkeletonProps {
310 loading: None,
311 active: false,
312 title: true,
313 paragraph_rows: None,
314 class: None,
315 style: None,
316 content: None,
317 };
318 assert!(props.loading.is_none());
319 }
320}