1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use *;
use ;
use component_doc;
use inject_style;
use skeleton_item_styles;
/// `Skeleton` preserves layout while async data loads — stack [`SkeletonItem`] blocks to mirror text lines, avatars, or media rectangles.
///
/// Set defaults on `Skeleton` and override per item with `size`, `shape`, or explicit `width`/`height`. Remove placeholders as soon as real content is ready.
///
/// # When to use
///
/// - List rows, cards, or feeds where layout should not shift when data arrives
/// - Content areas that stay interactive while loading (unlike a blocking overlay)
///
/// Prefer [`Spinner`](crate::Spinner) for indeterminate action waits with no layout to preserve.
///
/// # Usage
///
/// 1. Wrap placeholder blocks in [`Skeleton`].
/// 2. Set default `size` and `shape` on the container; child [`SkeletonItem`] elements inherit when omitted.
/// 3. Override per item with explicit dimensions to match final content geometry.
/// 4. Swap the skeleton subtree for real content when the fetch completes.
///
/// # Examples
///
/// ## Text block skeleton
/// Skeleton placeholders mirror the shape of loading text so layout does not shift when real content arrives. Stack lines at approximate widths of the final copy.
/// <!-- preview -->
/// ```rust
/// use crate::{Skeleton, SkeletonItem, SkeletonItemSize};
/// view! {
/// <div data-testid="skeleton-preview" style="width: 100%; min-width: 240px;">
/// <Skeleton>
/// <SkeletonItem size=Signal::from(SkeletonItemSize::S16) />
/// <div style="width: 80%; margin-top: 8px;">
/// <SkeletonItem size=Signal::from(SkeletonItemSize::S16) />
/// </div>
/// </Skeleton>
/// </div>
/// }
/// ```
///
/// ## Avatar row
/// Row layout mimics a list item with avatar and text lines while content loads.
/// <!-- preview -->
/// ```rust
/// use crate::{Skeleton, SkeletonItem, SkeletonItemShape, SkeletonItemSize};
/// view! {
/// <Skeleton>
/// <div data-testid="skeleton-row" style="display: flex; gap: 12px; align-items: center;">
/// <SkeletonItem
/// shape=Signal::from(SkeletonItemShape::Circle)
/// size=Signal::from(SkeletonItemSize::S40)
/// />
/// <div style="flex: 1;">
/// <SkeletonItem size=Signal::from(SkeletonItemSize::S14) />
/// <div style="width: 40%; margin-top: 8px;">
/// <SkeletonItem size=Signal::from(SkeletonItemSize::S14) />
/// </div>
/// </div>
/// </div>
/// </Skeleton>
/// }
/// ```
///
/// ## Card placeholder
/// Card skeleton combines a media block and title line for feed or grid placeholders.
/// <!-- preview -->
/// ```rust
/// use crate::{Skeleton, SkeletonItem, SkeletonItemSize};
/// view! {
/// <div data-testid="skeleton-card" style="width: 240px;">
/// <Skeleton>
/// <SkeletonItem size=Signal::from(SkeletonItemSize::S120) />
/// <div style="width: 70%; margin-top: 12px;">
/// <SkeletonItem size=Signal::from(SkeletonItemSize::S16) />
/// </div>
/// </Skeleton>
/// </div>
/// }
/// ```