@ceed/cds 1.22.3 → 1.22.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/data-display/InfoSign.md +74 -91
- package/dist/components/data-display/Typography.md +363 -63
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/inputs/ButtonGroup.md +115 -104
- package/dist/components/inputs/CurrencyInput.md +181 -8
- package/dist/components/inputs/DatePicker.md +108 -436
- package/dist/components/inputs/DateRangePicker.md +130 -496
- package/dist/components/inputs/FilterableCheckboxGroup.md +141 -20
- package/dist/components/inputs/FormControl.md +361 -0
- package/dist/components/inputs/IconButton.md +137 -88
- package/dist/components/inputs/Input.md +203 -77
- package/dist/components/inputs/MonthPicker.md +95 -427
- package/dist/components/inputs/MonthRangePicker.md +89 -471
- package/dist/components/inputs/PercentageInput.md +183 -19
- package/dist/components/inputs/RadioButton.md +163 -35
- package/dist/components/inputs/RadioList.md +241 -0
- package/dist/components/inputs/RadioTileGroup.md +146 -62
- package/dist/components/inputs/Select.md +219 -328
- package/dist/components/inputs/Slider.md +334 -0
- package/dist/components/inputs/Switch.md +143 -376
- package/dist/components/inputs/Textarea.md +209 -11
- package/dist/components/inputs/Uploader/Uploader.md +145 -66
- package/dist/components/inputs/llms.txt +3 -0
- package/dist/components/navigation/Breadcrumbs.md +57 -308
- package/dist/components/navigation/Drawer.md +180 -0
- package/dist/components/navigation/Dropdown.md +98 -215
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +281 -650
- package/dist/components/navigation/Link.md +31 -348
- package/dist/components/navigation/Menu.md +92 -285
- package/dist/components/navigation/MenuButton.md +55 -448
- package/dist/components/navigation/Pagination.md +47 -338
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Accordions.md +49 -804
- package/dist/components/surfaces/Card.md +97 -157
- package/dist/components/surfaces/Divider.md +83 -234
- package/dist/components/surfaces/Sheet.md +152 -327
- package/dist/guides/ThemeProvider.md +89 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/llms.txt +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# CircularProgress
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
The CircularProgress component displays a circular loading indicator. It is based on Joy UI's CircularProgress and supports both indeterminate (spinning) and determinate (progress percentage) modes. Use it to provide visual feedback during asynchronous operations like data fetching, file uploads, or form submissions.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<CircularProgress />
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
| Field | Description | Default |
|
|
12
|
+
| ----------- | ----------- | ------- |
|
|
13
|
+
| size | — | — |
|
|
14
|
+
| color | — | — |
|
|
15
|
+
| variant | — | — |
|
|
16
|
+
| value | — | — |
|
|
17
|
+
| determinate | — | — |
|
|
18
|
+
| thickness | — | — |
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { CircularProgress } from '@ceed/cds';
|
|
24
|
+
|
|
25
|
+
function MyComponent() {
|
|
26
|
+
return <CircularProgress />;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Sizes
|
|
31
|
+
|
|
32
|
+
CircularProgress supports three sizes: `sm`, `md` (default), and `lg`.
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
<>
|
|
36
|
+
<CircularProgress size="sm" />
|
|
37
|
+
<CircularProgress size="md" />
|
|
38
|
+
<CircularProgress size="lg" />
|
|
39
|
+
</>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<CircularProgress size="sm" />
|
|
44
|
+
<CircularProgress size="md" />
|
|
45
|
+
<CircularProgress size="lg" />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Colors
|
|
49
|
+
|
|
50
|
+
Five semantic colors are available via the `color` prop. The default is `primary`.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<>
|
|
54
|
+
<CircularProgress color="primary" />
|
|
55
|
+
<CircularProgress color="neutral" />
|
|
56
|
+
<CircularProgress color="danger" />
|
|
57
|
+
<CircularProgress color="success" />
|
|
58
|
+
<CircularProgress color="warning" />
|
|
59
|
+
</>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<CircularProgress color="primary" />
|
|
64
|
+
<CircularProgress color="neutral" />
|
|
65
|
+
<CircularProgress color="danger" />
|
|
66
|
+
<CircularProgress color="success" />
|
|
67
|
+
<CircularProgress color="warning" />
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Variants
|
|
71
|
+
|
|
72
|
+
Four visual variants are supported: `solid`, `soft` (default), `outlined`, and `plain`.
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
<>
|
|
76
|
+
<CircularProgress variant="solid" />
|
|
77
|
+
<CircularProgress variant="soft" />
|
|
78
|
+
<CircularProgress variant="outlined" />
|
|
79
|
+
<CircularProgress variant="plain" />
|
|
80
|
+
</>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<CircularProgress variant="solid" />
|
|
85
|
+
<CircularProgress variant="soft" />
|
|
86
|
+
<CircularProgress variant="outlined" />
|
|
87
|
+
<CircularProgress variant="plain" />
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Determinate Mode
|
|
91
|
+
|
|
92
|
+
Set `determinate` to `true` and provide a `value` (0–100) to display specific progress.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<>
|
|
96
|
+
<CircularProgress determinate value={25} />
|
|
97
|
+
<CircularProgress determinate value={50} />
|
|
98
|
+
<CircularProgress determinate value={75} />
|
|
99
|
+
<CircularProgress determinate value={100} />
|
|
100
|
+
</>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<CircularProgress determinate value={25} />
|
|
105
|
+
<CircularProgress determinate value={50} />
|
|
106
|
+
<CircularProgress determinate value={75} />
|
|
107
|
+
<CircularProgress determinate value={100} />
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## With Label
|
|
111
|
+
|
|
112
|
+
Pass children to display a label inside the progress ring. This is useful for showing the current percentage.
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
<CircularProgress determinate value={66}>
|
|
116
|
+
<Typography level="body-xs">66%</Typography>
|
|
117
|
+
</CircularProgress>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
<CircularProgress determinate value={66}>
|
|
122
|
+
<Typography level="body-xs">66%</Typography>
|
|
123
|
+
</CircularProgress>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## In Button
|
|
127
|
+
|
|
128
|
+
Use CircularProgress inside a Button to indicate a loading state.
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
<>
|
|
132
|
+
<Button startDecorator={<CircularProgress variant="solid" size="sm" />}>
|
|
133
|
+
Loading…
|
|
134
|
+
</Button>
|
|
135
|
+
<Button disabled>
|
|
136
|
+
<CircularProgress variant="soft" size="sm" />
|
|
137
|
+
</Button>
|
|
138
|
+
</>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
<Button startDecorator={<CircularProgress variant="solid" size="sm" />}>
|
|
143
|
+
Loading…
|
|
144
|
+
</Button>
|
|
145
|
+
|
|
146
|
+
<Button disabled>
|
|
147
|
+
<CircularProgress variant="soft" size="sm" />
|
|
148
|
+
</Button>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Thickness
|
|
152
|
+
|
|
153
|
+
Control the stroke thickness of the progress ring with the `thickness` prop.
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<>
|
|
157
|
+
<CircularProgress thickness={2} />
|
|
158
|
+
<CircularProgress thickness={4} />
|
|
159
|
+
<CircularProgress thickness={8} />
|
|
160
|
+
</>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
<CircularProgress thickness={2} />
|
|
165
|
+
<CircularProgress thickness={4} />
|
|
166
|
+
<CircularProgress thickness={8} />
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Common Use Cases
|
|
170
|
+
|
|
171
|
+
### Data Fetching Indicator
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
function DataLoader() {
|
|
175
|
+
const [loading, setLoading] = React.useState(true);
|
|
176
|
+
const [data, setData] = React.useState(null);
|
|
177
|
+
|
|
178
|
+
React.useEffect(() => {
|
|
179
|
+
fetchData().then((result) => {
|
|
180
|
+
setData(result);
|
|
181
|
+
setLoading(false);
|
|
182
|
+
});
|
|
183
|
+
}, []);
|
|
184
|
+
|
|
185
|
+
if (loading) {
|
|
186
|
+
return (
|
|
187
|
+
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
|
188
|
+
<CircularProgress />
|
|
189
|
+
</Box>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return <DataView data={data} />;
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### File Upload Progress
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
function FileUpload({ progress }: { progress: number }) {
|
|
201
|
+
return (
|
|
202
|
+
<Stack direction="row" alignItems="center" spacing={2}>
|
|
203
|
+
<CircularProgress determinate value={progress} size="lg">
|
|
204
|
+
<Typography level="body-xs">{progress}%</Typography>
|
|
205
|
+
</CircularProgress>
|
|
206
|
+
<Typography level="body-md">Uploading file…</Typography>
|
|
207
|
+
</Stack>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Inline Loading Button
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
function SubmitButton({ isSubmitting }: { isSubmitting: boolean }) {
|
|
216
|
+
return (
|
|
217
|
+
<Button
|
|
218
|
+
disabled={isSubmitting}
|
|
219
|
+
startDecorator={
|
|
220
|
+
isSubmitting ? <CircularProgress variant="solid" size="sm" /> : null
|
|
221
|
+
}
|
|
222
|
+
>
|
|
223
|
+
{isSubmitting ? 'Submitting…' : 'Submit'}
|
|
224
|
+
</Button>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Best Practices
|
|
230
|
+
|
|
231
|
+
1. **Use indeterminate for unknown durations**: When you cannot estimate progress, use the default spinning mode. Switch to determinate only when you can track actual progress.
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
// ✅ Unknown duration — indeterminate
|
|
235
|
+
<CircularProgress />
|
|
236
|
+
|
|
237
|
+
// ✅ Known progress — determinate
|
|
238
|
+
<CircularProgress determinate value={uploadProgress} />
|
|
239
|
+
|
|
240
|
+
// ❌ Don't use determinate with a static value for loading
|
|
241
|
+
<CircularProgress determinate value={50} />
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
2. **Size appropriately**: Use `sm` for inline contexts (buttons, table cells), `md` for general use, and `lg` for prominent page-level loading states.
|
|
245
|
+
|
|
246
|
+
3. **Match variant with context**: Use `solid` variant inside solid buttons, and `soft` (default) for standalone usage to maintain visual consistency.
|
|
247
|
+
|
|
248
|
+
4. **Add descriptive text**: Always pair the indicator with text or an `aria-label` so users understand what is loading.
|
|
249
|
+
|
|
250
|
+
5. **Avoid multiple spinners**: Show one loading indicator per distinct loading region. Multiple spinners on a page create visual clutter.
|
|
251
|
+
|
|
252
|
+
## Accessibility
|
|
253
|
+
|
|
254
|
+
- CircularProgress has `role="progressbar"` by default.
|
|
255
|
+
- In determinate mode, `aria-valuenow`, `aria-valuemin`, and `aria-valuemax` are set automatically.
|
|
256
|
+
- Add `aria-label` or nearby descriptive text to explain what is loading (e.g., `aria-label="Loading user data"`).
|
|
257
|
+
- Ensure sufficient color contrast between the progress ring and its background, especially with the `plain` variant.
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Skeleton
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
The Skeleton component provides placeholder previews of content before data is loaded. It is based on Joy UI's Skeleton and helps reduce perceived loading time by showing an approximation of the page layout. Skeletons improve the user experience by preventing layout shifts and giving users a visual cue that content is on its way.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Skeleton
|
|
9
|
+
variant="rectangular"
|
|
10
|
+
width={200}
|
|
11
|
+
height={24}
|
|
12
|
+
/>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Field | Description | Default |
|
|
16
|
+
| ---------------------------- | ----------- | ------- |
|
|
17
|
+
| Controls resolved at runtime | — | — |
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { Skeleton } from '@ceed/cds';
|
|
23
|
+
|
|
24
|
+
function MyComponent() {
|
|
25
|
+
return <Skeleton variant="rectangular" width={200} height={24} />;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Variants
|
|
30
|
+
|
|
31
|
+
Skeleton supports three variants: `rectangular`, `circular`, and `text`.
|
|
32
|
+
|
|
33
|
+
- **rectangular**: Block-shaped placeholder for images, cards, and content areas.
|
|
34
|
+
- **circular**: Round placeholder for avatars and icons.
|
|
35
|
+
- **text**: Matches the height and spacing of text content at a given `level`.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<>
|
|
39
|
+
<Skeleton variant="rectangular" width={200} height={24} />
|
|
40
|
+
<Skeleton variant="circular" width={48} height={48} />
|
|
41
|
+
<Skeleton variant="text" width={200} />
|
|
42
|
+
</>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<Skeleton variant="rectangular" width={200} height={24} />
|
|
47
|
+
<Skeleton variant="circular" width={48} height={48} />
|
|
48
|
+
<Skeleton variant="text" width={200} />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Animations
|
|
52
|
+
|
|
53
|
+
Skeleton supports `wave` (default) and `pulse` animations. Set `animation={false}` to disable animation entirely.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<Stack gap={3}>
|
|
57
|
+
<Box>
|
|
58
|
+
<Typography level="body-sm" sx={{
|
|
59
|
+
mb: 1
|
|
60
|
+
}}>
|
|
61
|
+
Wave (default)
|
|
62
|
+
</Typography>
|
|
63
|
+
<Skeleton animation="wave" variant="rectangular" width={200} height={24} />
|
|
64
|
+
</Box>
|
|
65
|
+
<Box>
|
|
66
|
+
<Typography level="body-sm" sx={{
|
|
67
|
+
mb: 1
|
|
68
|
+
}}>
|
|
69
|
+
Pulse
|
|
70
|
+
</Typography>
|
|
71
|
+
<Skeleton animation="pulse" variant="rectangular" width={200} height={24} />
|
|
72
|
+
</Box>
|
|
73
|
+
<Box>
|
|
74
|
+
<Typography level="body-sm" sx={{
|
|
75
|
+
mb: 1
|
|
76
|
+
}}>
|
|
77
|
+
No animation (false)
|
|
78
|
+
</Typography>
|
|
79
|
+
<Skeleton animation={false} variant="rectangular" width={200} height={24} />
|
|
80
|
+
</Box>
|
|
81
|
+
</Stack>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
<Skeleton animation="wave" variant="rectangular" width={200} height={24} />
|
|
86
|
+
<Skeleton animation="pulse" variant="rectangular" width={200} height={24} />
|
|
87
|
+
<Skeleton animation={false} variant="rectangular" width={200} height={24} />
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Text Skeleton
|
|
91
|
+
|
|
92
|
+
Use `variant="text"` with the `level` prop to match Typography sizing. This is useful for creating text content placeholders that accurately reflect the final layout.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<Stack gap={1} sx={{
|
|
96
|
+
width: 300
|
|
97
|
+
}}>
|
|
98
|
+
<Skeleton variant="text" level="h3" />
|
|
99
|
+
<Skeleton variant="text" level="body-md" />
|
|
100
|
+
<Skeleton variant="text" level="body-md" />
|
|
101
|
+
<Skeleton variant="text" level="body-md" width="80%" />
|
|
102
|
+
</Stack>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<Skeleton variant="text" level="h3" />
|
|
107
|
+
<Skeleton variant="text" level="body-md" />
|
|
108
|
+
<Skeleton variant="text" level="body-md" />
|
|
109
|
+
<Skeleton variant="text" level="body-md" width="80%" />
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Card Skeleton
|
|
113
|
+
|
|
114
|
+
Compose multiple Skeleton elements to create placeholder layouts for complex components like cards.
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
<Box sx={{
|
|
118
|
+
width: 300,
|
|
119
|
+
p: 2,
|
|
120
|
+
border: '1px solid',
|
|
121
|
+
borderColor: 'divider',
|
|
122
|
+
borderRadius: 'sm'
|
|
123
|
+
}}>
|
|
124
|
+
<Skeleton variant="rectangular" width="100%" height={140} sx={{
|
|
125
|
+
borderRadius: 'sm',
|
|
126
|
+
mb: 2
|
|
127
|
+
}} />
|
|
128
|
+
<Skeleton variant="text" level="title-md" sx={{
|
|
129
|
+
mb: 1
|
|
130
|
+
}} />
|
|
131
|
+
<Skeleton variant="text" level="body-sm" />
|
|
132
|
+
<Skeleton variant="text" level="body-sm" width="60%" />
|
|
133
|
+
<Stack direction="row" gap={1} sx={{
|
|
134
|
+
mt: 2
|
|
135
|
+
}}>
|
|
136
|
+
<Skeleton variant="rectangular" width={80} height={32} sx={{
|
|
137
|
+
borderRadius: 'sm'
|
|
138
|
+
}} />
|
|
139
|
+
<Skeleton variant="rectangular" width={80} height={32} sx={{
|
|
140
|
+
borderRadius: 'sm'
|
|
141
|
+
}} />
|
|
142
|
+
</Stack>
|
|
143
|
+
</Box>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Data Loading List
|
|
147
|
+
|
|
148
|
+
Combine circular and text skeletons to represent list items during loading.
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
<Stack gap={2} sx={{
|
|
152
|
+
width: 400
|
|
153
|
+
}}>
|
|
154
|
+
{[1, 2, 3].map(i => <Stack key={i} direction="row" gap={2} alignItems="center">
|
|
155
|
+
<Skeleton variant="circular" width={40} height={40} />
|
|
156
|
+
<Box sx={{
|
|
157
|
+
flex: 1
|
|
158
|
+
}}>
|
|
159
|
+
<Skeleton variant="text" level="title-sm" width="60%" />
|
|
160
|
+
<Skeleton variant="text" level="body-xs" width="40%" />
|
|
161
|
+
</Box>
|
|
162
|
+
</Stack>)}
|
|
163
|
+
</Stack>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Inline Wrapping
|
|
167
|
+
|
|
168
|
+
Wrap existing content with Skeleton to overlay it while loading. Set the `loading` prop to control visibility.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
<Typography level="body-md">
|
|
172
|
+
<Skeleton loading>
|
|
173
|
+
This text will be hidden behind a skeleton while loading.
|
|
174
|
+
</Skeleton>
|
|
175
|
+
</Typography>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
<Typography level="body-md">
|
|
180
|
+
<Skeleton loading>
|
|
181
|
+
This text will be hidden behind a skeleton while loading.
|
|
182
|
+
</Skeleton>
|
|
183
|
+
</Typography>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Common Use Cases
|
|
187
|
+
|
|
188
|
+
### Page Content Loading
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
function PageSkeleton() {
|
|
192
|
+
return (
|
|
193
|
+
<Stack gap={3}>
|
|
194
|
+
<Skeleton variant="text" level="h1" width="50%" />
|
|
195
|
+
<Skeleton variant="text" level="body-md" />
|
|
196
|
+
<Skeleton variant="text" level="body-md" />
|
|
197
|
+
<Skeleton variant="text" level="body-md" width="75%" />
|
|
198
|
+
|
|
199
|
+
<Skeleton variant="rectangular" width="100%" height={200} sx={{ borderRadius: 'sm' }} />
|
|
200
|
+
|
|
201
|
+
<Skeleton variant="text" level="body-md" />
|
|
202
|
+
<Skeleton variant="text" level="body-md" />
|
|
203
|
+
</Stack>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### User List Loading
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
function UserListSkeleton({ count = 5 }: { count?: number }) {
|
|
212
|
+
return (
|
|
213
|
+
<Stack gap={2}>
|
|
214
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
215
|
+
<Stack key={i} direction="row" gap={2} alignItems="center">
|
|
216
|
+
<Skeleton variant="circular" width={40} height={40} />
|
|
217
|
+
<Box sx={{ flex: 1 }}>
|
|
218
|
+
<Skeleton variant="text" level="title-sm" width="40%" />
|
|
219
|
+
<Skeleton variant="text" level="body-xs" width="25%" />
|
|
220
|
+
</Box>
|
|
221
|
+
</Stack>
|
|
222
|
+
))}
|
|
223
|
+
</Stack>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Conditional Rendering
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
function UserProfile({ loading, user }: { loading: boolean; user?: User }) {
|
|
232
|
+
return (
|
|
233
|
+
<Stack direction="row" gap={2} alignItems="center">
|
|
234
|
+
{loading ? (
|
|
235
|
+
<Skeleton variant="circular" width={48} height={48} />
|
|
236
|
+
) : (
|
|
237
|
+
<Avatar src={user?.avatar} />
|
|
238
|
+
)}
|
|
239
|
+
<Box>
|
|
240
|
+
<Typography level="title-md">
|
|
241
|
+
<Skeleton loading={loading}>{user?.name || 'Placeholder Name'}</Skeleton>
|
|
242
|
+
</Typography>
|
|
243
|
+
<Typography level="body-sm">
|
|
244
|
+
<Skeleton loading={loading}>{user?.email || 'email@example.com'}</Skeleton>
|
|
245
|
+
</Typography>
|
|
246
|
+
</Box>
|
|
247
|
+
</Stack>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Best Practices
|
|
253
|
+
|
|
254
|
+
1. **Match the final layout**: Skeleton placeholders should closely approximate the size and position of the real content to prevent layout shifts.
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
// ✅ Matches the actual content structure
|
|
258
|
+
<Stack gap={1}>
|
|
259
|
+
<Skeleton variant="text" level="title-md" width="60%" />
|
|
260
|
+
<Skeleton variant="text" level="body-sm" />
|
|
261
|
+
</Stack>
|
|
262
|
+
|
|
263
|
+
// ❌ Generic rectangle that doesn't match
|
|
264
|
+
<Skeleton variant="rectangular" width={300} height={100} />
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
2. **Use `variant="text"` with `level`**: When replacing Typography, use the text variant with the matching level to get accurate line heights.
|
|
268
|
+
|
|
269
|
+
3. **Avoid over-skeletonizing**: Only skeleton the main content areas. Don't add skeletons for static elements like navigation or headers that are always present.
|
|
270
|
+
|
|
271
|
+
4. **Use consistent animation**: Keep the same animation type (`wave` or `pulse`) across the entire application for a cohesive loading experience.
|
|
272
|
+
|
|
273
|
+
5. **Set appropriate widths**: Vary skeleton widths (e.g., 60%, 80%, 100%) to mimic natural text line lengths rather than using uniform widths.
|
|
274
|
+
|
|
275
|
+
## Accessibility
|
|
276
|
+
|
|
277
|
+
- Skeleton elements are purely decorative. Screen readers should focus on the loading state announcement, not individual skeleton elements.
|
|
278
|
+
- Use `aria-busy="true"` on the container element while content is loading.
|
|
279
|
+
- Provide an `aria-label` or visually hidden text that describes the loading state (e.g., "Loading user profile").
|
|
280
|
+
- Ensure the animation respects `prefers-reduced-motion` — Joy UI handles this automatically.
|