radix_leptos_primitives/components/
avatar.rs1use crate::utils::{merge_classes, generate_id};
2use leptos::callback::Callback;
3use leptos::children::Children;
4use leptos::prelude::*;
5
6#[component]
8pub fn Avatar(
9 #[prop(optional)] class: Option<String>,
10 #[prop(optional)] style: Option<String>,
11 #[prop(optional)] children: Option<Children>,
12 #[prop(optional)] src: Option<String>,
13 #[prop(optional)] alt: Option<String>,
14 #[prop(optional)] fallback: Option<String>,
15 #[prop(optional)] size: Option<AvatarSize>,
16 #[prop(optional)] shape: Option<AvatarShape>,
17 #[prop(optional)] loading: Option<AvatarLoading>,
18 #[prop(optional)] on_load: Option<Callback<()>>,
19 #[prop(optional)] on_error: Option<Callback<()>>,
20) -> impl IntoView {
21 let src = src.unwrap_or_default();
22 let alt = alt.unwrap_or_else(|| "Avatar".to_string());
23 let fallback = fallback.unwrap_or_else(|| "?".to_string());
24 let size = size.unwrap_or_default();
25 let shape = shape.unwrap_or_default();
26 let loading = loading.unwrap_or_default();
27
28 let class = merge_classes(vec![
29 "avatar",
30 &size.to_class(),
31 &shape.to_class(),
32 &loading.to_class(),
33 class.as_deref().unwrap_or(""),
34 ]);
35
36 view! {
37 <div
38 class=class
39 style=style
40 role="img"
41 aria-label=alt
42 data-src=src
43 data-fallback=fallback
44 data-size=size.to_string()
45 data-shape=shape.to_string()
46 data-loading=loading.to_string()
47 >
48 {children.map(|c| c())}
49 </div>
50 }
51}
52
53#[component]
55pub fn AvatarImage(
56 #[prop(optional)] class: Option<String>,
57 #[prop(optional)] style: Option<String>,
58 #[prop(optional)] src: Option<String>,
59 #[prop(optional)] alt: Option<String>,
60 #[prop(optional)] on_load: Option<Callback<()>>,
61 #[prop(optional)] on_error: Option<Callback<()>>,
62) -> impl IntoView {
63 let src = src.unwrap_or_default();
64 let alt = alt.unwrap_or_else(|| "Avatar image".to_string());
65
66 let class = merge_classes(vec!["avatar-image", class.as_deref().unwrap_or("")]);
67
68 let handle_load = move |_| {
69 if let Some(callback) = on_load {
70 callback.run(());
71 }
72 };
73
74 let handle_error = move |_| {
75 if let Some(callback) = on_error {
76 callback.run(());
77 }
78 };
79
80 view! {
81 <img
82 class=class
83 style=style
84 src=src
85 alt=alt
86 on:load=handle_load
87 on:error=handle_error
88 />
89 }
90}
91
92#[component]
94pub fn AvatarFallback(
95 #[prop(optional)] class: Option<String>,
96 #[prop(optional)] style: Option<String>,
97 #[prop(optional)] children: Option<Children>,
98 #[prop(optional)] text: Option<String>,
99) -> impl IntoView {
100 let text = text.unwrap_or_else(|| "?".to_string());
101
102 let class = merge_classes(vec!["avatar-fallback", class.as_deref().unwrap_or("")]);
103
104 view! {
105 <div
106 class=class
107 style=style
108 role="img"
109 aria-label="Avatar fallback"
110 >
111 {children.map(|c| c())}
112 </div>
113 }
114}
115
116#[component]
118pub fn AvatarGroup(
119 #[prop(optional)] class: Option<String>,
120 #[prop(optional)] style: Option<String>,
121 #[prop(optional)] children: Option<Children>,
122 #[prop(optional)] maxvisible: Option<usize>,
123 #[prop(optional)] spacing: Option<AvatarSpacing>,
124) -> impl IntoView {
125 let maxvisible = maxvisible.unwrap_or(5);
126 let spacing = spacing.unwrap_or_default();
127
128 let class = merge_classes(vec![
129 "avatar-group",
130 &spacing.to_class(),
131 class.as_deref().unwrap_or(""),
132 ]);
133
134 view! {
135 <div
136 class=class
137 style=style
138 role="group"
139 aria-label="Avatar group"
140 data-max-visible=maxvisible
141 data-spacing=spacing.to_string()
142 >
143 {children.map(|c| c())}
144 </div>
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Default)]
150pub enum AvatarSize {
151 #[default]
152 Small,
153 Medium,
154 Large,
155 ExtraLarge,
156 Custom(f64),
157}
158
159impl AvatarSize {
160 pub fn to_class(&self) -> &'static str {
161 match self {
162 AvatarSize::Small => "size-small",
163 AvatarSize::Medium => "size-medium",
164 AvatarSize::Large => "size-large",
165 AvatarSize::ExtraLarge => "size-extra-large",
166 AvatarSize::Custom(_) => "size-custom",
167 }
168 }
169
170 pub fn to_string(&self) -> String {
171 match self {
172 AvatarSize::Small => "small".to_string(),
173 AvatarSize::Medium => "medium".to_string(),
174 AvatarSize::Large => "large".to_string(),
175 AvatarSize::ExtraLarge => "extra-large".to_string(),
176 AvatarSize::Custom(size) => format!("custom-{}", size),
177 }
178 }
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
183pub enum AvatarShape {
184 #[default]
185 Circle,
186 Square,
187 Rounded,
188}
189
190impl AvatarShape {
191 pub fn to_class(&self) -> &'static str {
192 match self {
193 AvatarShape::Circle => "shape-circle",
194 AvatarShape::Square => "shape-square",
195 AvatarShape::Rounded => "shape-rounded",
196 }
197 }
198
199 pub fn to_string(&self) -> &'static str {
200 match self {
201 AvatarShape::Circle => "circle",
202 AvatarShape::Square => "square",
203 AvatarShape::Rounded => "rounded",
204 }
205 }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
210pub enum AvatarLoading {
211 #[default]
212 Eager,
213 Lazy,
214}
215
216impl AvatarLoading {
217 pub fn to_class(&self) -> &'static str {
218 match self {
219 AvatarLoading::Eager => "loading-eager",
220 AvatarLoading::Lazy => "loading-lazy",
221 }
222 }
223
224 pub fn to_string(&self) -> &'static str {
225 match self {
226 AvatarLoading::Eager => "eager",
227 AvatarLoading::Lazy => "lazy",
228 }
229 }
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
234pub enum AvatarSpacing {
235 #[default]
236 Tight,
237 Normal,
238 Loose,
239}
240
241impl AvatarSpacing {
242 pub fn to_class(&self) -> &'static str {
243 match self {
244 AvatarSpacing::Tight => "spacing-tight",
245 AvatarSpacing::Normal => "spacing-normal",
246 AvatarSpacing::Loose => "spacing-loose",
247 }
248 }
249
250 pub fn to_string(&self) -> &'static str {
251 match self {
252 AvatarSpacing::Tight => "tight",
253 AvatarSpacing::Normal => "normal",
254 AvatarSpacing::Loose => "loose",
255 }
256 }
257}
258
259#[cfg(test)]
262mod tests {
263 use proptest::prelude::*;
264 use wasm_bindgen_test::*;
265
266 wasm_bindgen_test_configure!(run_in_browser);
267
268 #[test]
270 fn test_avatar_creation() {}
271 #[test]
272 fn test_avatar_with_class() {}
273 #[test]
274 fn test_avatar_with_style() {}
275 #[test]
276 fn test_avatar_with_src() {}
277 #[test]
278 fn test_avatar_with_alt() {}
279 #[test]
280 fn test_avatar_with_fallback() {}
281 #[test]
282 fn test_avatar_with_size() {}
283 #[test]
284 fn test_avatar_with_shape() {}
285 #[test]
286 fn test_avatar_withloading() {}
287 #[test]
288 fn test_avatar_on_load() {}
289 #[test]
290 fn test_avatar_on_error() {}
291
292 #[test]
294 fn test_avatar_image_creation() {}
295 #[test]
296 fn test_avatar_image_with_class() {}
297 #[test]
298 fn test_avatar_image_with_src() {}
299 #[test]
300 fn test_avatar_image_with_alt() {}
301 #[test]
302 fn test_avatar_image_on_load() {}
303 #[test]
304 fn test_avatar_image_on_error() {}
305
306 #[test]
308 fn test_avatar_fallback_creation() {}
309 #[test]
310 fn test_avatar_fallback_with_class() {}
311 #[test]
312 fn test_avatar_fallback_with_text() {}
313
314 #[test]
316 fn test_avatar_group_creation() {}
317 #[test]
318 fn test_avatar_group_with_class() {}
319 #[test]
320 fn test_avatar_group_maxvisible() {}
321 #[test]
322 fn test_avatar_group_spacing() {}
323
324 #[test]
326 fn test_avatar_size_default() {}
327 #[test]
328 fn test_avatar_size_small() {}
329 #[test]
330 fn test_avatar_size_medium() {}
331 #[test]
332 fn test_avatar_size_large() {}
333 #[test]
334 fn test_avatar_size_extra_large() {}
335 #[test]
336 fn test_avatar_size_custom() {}
337
338 #[test]
340 fn test_avatar_shape_default() {}
341 #[test]
342 fn test_avatar_shape_circle() {}
343 #[test]
344 fn test_avatar_shape_square() {}
345 #[test]
346 fn test_avatar_shape_rounded() {}
347
348 #[test]
350 fn test_avatarloading_default() {}
351 #[test]
352 fn test_avatarloading_eager() {}
353 #[test]
354 fn test_avatarloading_lazy() {}
355
356 #[test]
358 fn test_avatar_spacing_default() {}
359 #[test]
360 fn test_avatar_spacing_tight() {}
361 #[test]
362 fn test_avatar_spacing_normal() {}
363 #[test]
364 fn test_avatar_spacing_loose() {}
365
366 #[test]
368 fn test_merge_classes_empty() {}
369 #[test]
370 fn test_merge_classes_single() {}
371 #[test]
372 fn test_merge_classes_multiple() {}
373 #[test]
374 fn test_merge_classes_with_empty() {}
375
376 #[test]
378 fn test_avatar_property_based() {
379 proptest!(|(____class in ".*", __style in ".*")| {
380
381 });
382 }
383
384 #[test]
385 fn test_avatar_size_validation() {
386 proptest!(|(____size in 10.0..200.0f64)| {
387
388 });
389 }
390
391 #[test]
392 fn test_avatar_group_validation() {
393 proptest!(|(____maxvisible in 1..20usize)| {
394
395 });
396 }
397
398 #[test]
400 fn test_avatar_imageloading() {}
401 #[test]
402 fn test_avatar_fallback_display() {}
403 #[test]
404 fn test_avatar_group_overflow() {}
405 #[test]
406 fn test_avatar_accessibility() {}
407 #[test]
408 fn test_avatar_responsive_behavior() {}
409
410 #[test]
412 fn test_avatar_large_groups() {}
413 #[test]
414 fn test_avatar_render_performance() {}
415 #[test]
416 fn test_avatar_memory_usage() {}
417 #[test]
418 fn test_avatar_imageloading_performance() {}
419}