1use dioxus::prelude::*;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum AvatarShape {
6 Circle,
7 Square,
8}
9
10impl AvatarShape {
11 fn as_class(&self) -> &'static str {
12 match self {
13 AvatarShape::Circle => "adui-avatar-circle",
14 AvatarShape::Square => "adui-avatar-square",
15 }
16 }
17}
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum AvatarSize {
22 Small,
23 Default,
24 Large,
25}
26
27impl AvatarSize {
28 fn as_class(&self) -> &'static str {
29 match self {
30 AvatarSize::Small => "adui-avatar-sm",
31 AvatarSize::Default => "adui-avatar-md",
32 AvatarSize::Large => "adui-avatar-lg",
33 }
34 }
35}
36
37#[derive(Props, Clone, PartialEq)]
39pub struct AvatarProps {
40 #[props(optional)]
43 pub src: Option<String>,
44 #[props(optional)]
46 pub alt: Option<String>,
47 #[props(optional)]
49 pub shape: Option<AvatarShape>,
50 #[props(optional)]
52 pub size: Option<AvatarSize>,
53 pub icon: Option<Element>,
55 #[props(optional)]
57 pub class: Option<String>,
58 #[props(optional)]
60 pub style: Option<String>,
61 pub children: Option<Element>,
64}
65
66#[component]
68pub fn Avatar(props: AvatarProps) -> Element {
69 let AvatarProps {
70 src,
71 alt,
72 shape,
73 size,
74 icon,
75 class,
76 style,
77 children,
78 } = props;
79
80 let shape_cls = shape.unwrap_or(AvatarShape::Circle).as_class();
81 let size_cls = size.unwrap_or(AvatarSize::Default).as_class();
82
83 let mut class_list = vec![
84 "adui-avatar".to_string(),
85 shape_cls.to_string(),
86 size_cls.to_string(),
87 ];
88 if let Some(extra) = class {
89 class_list.push(extra);
90 }
91 let class_attr = class_list.join(" ");
92 let style_attr = style.unwrap_or_default();
93
94 rsx! {
99 span { class: "{class_attr}", style: "{style_attr}",
100 if let Some(url) = src {
101 img {
102 class: "adui-avatar-img",
103 src: "{url}",
104 alt: "{alt.clone().unwrap_or_default()}",
105 }
106 } else if let Some(node) = icon {
107 span { class: "adui-avatar-icon", {node} }
108 } else if let Some(node) = children {
109 span { class: "adui-avatar-text", {node} }
110 }
111 }
112 }
113}
114
115#[derive(Props, Clone, PartialEq)]
117pub struct AvatarGroupProps {
118 #[props(optional)]
120 pub class: Option<String>,
121 #[props(optional)]
123 pub style: Option<String>,
124 pub children: Element,
126}
127
128#[component]
130pub fn AvatarGroup(props: AvatarGroupProps) -> Element {
131 let AvatarGroupProps {
132 class,
133 style,
134 children,
135 } = props;
136
137 let mut class_list = vec!["adui-avatar-group".to_string()];
138 if let Some(extra) = class {
139 class_list.push(extra);
140 }
141 let class_attr = class_list.join(" ");
142 let style_attr = style.unwrap_or_default();
143
144 rsx! {
145 div { class: "{class_attr}", style: "{style_attr}",
146 {children}
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn avatar_shape_and_size_class_mapping_is_stable() {
157 assert_eq!(AvatarShape::Circle.as_class(), "adui-avatar-circle");
158 assert_eq!(AvatarShape::Square.as_class(), "adui-avatar-square");
159
160 assert_eq!(AvatarSize::Small.as_class(), "adui-avatar-sm");
161 assert_eq!(AvatarSize::Default.as_class(), "adui-avatar-md");
162 assert_eq!(AvatarSize::Large.as_class(), "adui-avatar-lg");
163 }
164
165 #[test]
166 fn avatar_shape_all_variants() {
167 let variants = [AvatarShape::Circle, AvatarShape::Square];
168 for variant in variants.iter() {
169 let class = variant.as_class();
170 assert!(!class.is_empty());
171 assert!(class.starts_with("adui-avatar-"));
172 }
173 }
174
175 #[test]
176 fn avatar_size_all_variants() {
177 let variants = [AvatarSize::Small, AvatarSize::Default, AvatarSize::Large];
178 for variant in variants.iter() {
179 let class = variant.as_class();
180 assert!(!class.is_empty());
181 assert!(class.starts_with("adui-avatar-"));
182 }
183 }
184
185 #[test]
186 fn avatar_shape_equality() {
187 assert_eq!(AvatarShape::Circle, AvatarShape::Circle);
188 assert_eq!(AvatarShape::Square, AvatarShape::Square);
189 assert_ne!(AvatarShape::Circle, AvatarShape::Square);
190 }
191
192 #[test]
193 fn avatar_size_equality() {
194 assert_eq!(AvatarSize::Small, AvatarSize::Small);
195 assert_eq!(AvatarSize::Default, AvatarSize::Default);
196 assert_eq!(AvatarSize::Large, AvatarSize::Large);
197 assert_ne!(AvatarSize::Small, AvatarSize::Large);
198 }
199
200 #[test]
201 fn avatar_shape_clone() {
202 let original = AvatarShape::Circle;
203 let cloned = original;
204 assert_eq!(original, cloned);
205 assert_eq!(original.as_class(), cloned.as_class());
206 }
207
208 #[test]
209 fn avatar_size_clone() {
210 let original = AvatarSize::Large;
211 let cloned = original;
212 assert_eq!(original, cloned);
213 assert_eq!(original.as_class(), cloned.as_class());
214 }
215
216 #[test]
217 fn avatar_props_defaults() {
218 }
223
224 #[test]
225 fn avatar_group_props_defaults() {
226 }
229
230 #[test]
231 fn avatar_shape_debug() {
232 let shape = AvatarShape::Square;
233 let debug_str = format!("{:?}", shape);
234 assert!(debug_str.contains("Square") || debug_str.contains("Circle"));
235 }
236
237 #[test]
238 fn avatar_size_debug() {
239 let size = AvatarSize::Small;
240 let debug_str = format!("{:?}", size);
241 assert!(
242 debug_str.contains("Small")
243 || debug_str.contains("Default")
244 || debug_str.contains("Large")
245 );
246 }
247
248 #[test]
249 fn avatar_shape_class_prefix() {
250 assert!(AvatarShape::Circle.as_class().starts_with("adui-avatar-"));
252 assert!(AvatarShape::Square.as_class().starts_with("adui-avatar-"));
253 }
254
255 #[test]
256 fn avatar_size_class_prefix() {
257 assert!(AvatarSize::Small.as_class().starts_with("adui-avatar-"));
259 assert!(AvatarSize::Default.as_class().starts_with("adui-avatar-"));
260 assert!(AvatarSize::Large.as_class().starts_with("adui-avatar-"));
261 }
262
263 #[test]
264 fn avatar_shape_unique_classes() {
265 assert_ne!(
267 AvatarShape::Circle.as_class(),
268 AvatarShape::Square.as_class()
269 );
270 }
271
272 #[test]
273 fn avatar_size_unique_classes() {
274 let small = AvatarSize::Small.as_class();
276 let default = AvatarSize::Default.as_class();
277 let large = AvatarSize::Large.as_class();
278 assert_ne!(small, default);
279 assert_ne!(default, large);
280 assert_ne!(small, large);
281 }
282
283 #[test]
284 fn avatar_shape_copy_semantics() {
285 let shape = AvatarShape::Circle;
287 let class1 = shape.as_class();
288 let class2 = shape.as_class();
289 assert_eq!(class1, class2);
290 }
291
292 #[test]
293 fn avatar_size_copy_semantics() {
294 let size = AvatarSize::Large;
296 let class1 = size.as_class();
297 let class2 = size.as_class();
298 assert_eq!(class1, class2);
299 }
300
301 #[test]
302 fn avatar_shape_all_variants_equality() {
303 let shapes = [AvatarShape::Circle, AvatarShape::Square];
304 for (i, shape1) in shapes.iter().enumerate() {
305 for (j, shape2) in shapes.iter().enumerate() {
306 if i == j {
307 assert_eq!(shape1, shape2);
308 } else {
309 assert_ne!(shape1, shape2);
310 }
311 }
312 }
313 }
314
315 #[test]
316 fn avatar_size_all_variants_equality() {
317 let sizes = [AvatarSize::Small, AvatarSize::Default, AvatarSize::Large];
318 for (i, size1) in sizes.iter().enumerate() {
319 for (j, size2) in sizes.iter().enumerate() {
320 if i == j {
321 assert_eq!(size1, size2);
322 } else {
323 assert_ne!(size1, size2);
324 }
325 }
326 }
327 }
328}