@ceed/ads 1.25.1-next.3 → 1.26.0

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.
Files changed (60) hide show
  1. package/dist/chunks/rehype-accent-FZRUD7VI.js +39 -0
  2. package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
  3. package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
  4. package/dist/components/DataTable/components.d.ts +2 -1
  5. package/dist/components/DataTable/styled.d.ts +3 -1
  6. package/dist/components/DataTable/types.d.ts +1 -0
  7. package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
  8. package/dist/components/data-display/Badge.md +71 -39
  9. package/dist/components/data-display/DataTable.md +77 -1
  10. package/dist/components/data-display/InfoSign.md +74 -98
  11. package/dist/components/data-display/Typography.md +363 -97
  12. package/dist/components/feedback/CircularProgress.md +257 -0
  13. package/dist/components/feedback/Dialog.md +76 -62
  14. package/dist/components/feedback/Modal.md +259 -44
  15. package/dist/components/feedback/Skeleton.md +280 -0
  16. package/dist/components/feedback/llms.txt +2 -0
  17. package/dist/components/inputs/Autocomplete.md +356 -107
  18. package/dist/components/inputs/ButtonGroup.md +115 -106
  19. package/dist/components/inputs/Calendar.md +98 -459
  20. package/dist/components/inputs/CurrencyInput.md +183 -5
  21. package/dist/components/inputs/DatePicker.md +108 -431
  22. package/dist/components/inputs/DateRangePicker.md +131 -492
  23. package/dist/components/inputs/FilterMenu.md +169 -19
  24. package/dist/components/inputs/FilterableCheckboxGroup.md +123 -23
  25. package/dist/components/inputs/FormControl.md +361 -0
  26. package/dist/components/inputs/IconButton.md +137 -88
  27. package/dist/components/inputs/Input.md +5 -0
  28. package/dist/components/inputs/MonthPicker.md +95 -422
  29. package/dist/components/inputs/MonthRangePicker.md +89 -466
  30. package/dist/components/inputs/PercentageInput.md +185 -16
  31. package/dist/components/inputs/RadioButton.md +163 -35
  32. package/dist/components/inputs/RadioList.md +241 -0
  33. package/dist/components/inputs/RadioTileGroup.md +150 -61
  34. package/dist/components/inputs/Select.md +222 -326
  35. package/dist/components/inputs/Slider.md +334 -0
  36. package/dist/components/inputs/Switch.md +136 -376
  37. package/dist/components/inputs/Textarea.md +213 -10
  38. package/dist/components/inputs/Uploader/Uploader.md +145 -66
  39. package/dist/components/inputs/llms.txt +3 -0
  40. package/dist/components/navigation/Breadcrumbs.md +80 -322
  41. package/dist/components/navigation/Dropdown.md +92 -221
  42. package/dist/components/navigation/IconMenuButton.md +40 -502
  43. package/dist/components/navigation/InsetDrawer.md +68 -738
  44. package/dist/components/navigation/Link.md +39 -298
  45. package/dist/components/navigation/Menu.md +92 -285
  46. package/dist/components/navigation/MenuButton.md +55 -448
  47. package/dist/components/navigation/Pagination.md +47 -338
  48. package/dist/components/navigation/ProfileMenu.md +45 -268
  49. package/dist/components/navigation/Stepper.md +160 -28
  50. package/dist/components/navigation/Tabs.md +57 -316
  51. package/dist/components/surfaces/Sheet.md +151 -334
  52. package/dist/guides/ThemeProvider.md +116 -0
  53. package/dist/guides/llms.txt +9 -0
  54. package/dist/index.browser.js +16 -18
  55. package/dist/index.browser.js.map +4 -4
  56. package/dist/index.cjs +303 -209
  57. package/dist/index.js +381 -343
  58. package/dist/llms.txt +8 -0
  59. package/framer/index.js +1 -166
  60. package/package.json +15 -16
@@ -41,15 +41,7 @@ The Modal component is a dialog overlay that appears on top of the main content,
41
41
  ## Usage
42
42
 
43
43
  ```tsx
44
- import {
45
- Modal,
46
- ModalDialog,
47
- ModalClose,
48
- DialogTitle,
49
- DialogContent,
50
- DialogActions,
51
- Button,
52
- } from '@ceed/ads';
44
+ import { Modal, ModalDialog, ModalClose, DialogTitle, DialogContent, DialogActions, Button } from '@ceed/ads';
53
45
 
54
46
  function MyComponent() {
55
47
  const [open, setOpen] = useState(false);
@@ -61,9 +53,7 @@ function MyComponent() {
61
53
  <ModalDialog>
62
54
  <ModalClose />
63
55
  <DialogTitle>Modal Title</DialogTitle>
64
- <DialogContent>
65
- Place your content here.
66
- </DialogContent>
56
+ <DialogContent>Place your content here.</DialogContent>
67
57
  <DialogActions>
68
58
  <Button onClick={() => setOpen(false)}>Close</Button>
69
59
  </DialogActions>
@@ -74,6 +64,28 @@ function MyComponent() {
74
64
  }
75
65
  ```
76
66
 
67
+ ### ModalFrame Usage
68
+
69
+ `ModalFrame` is a convenience component that combines `ModalDialog` + `ModalClose` + `DialogTitle` + `DialogContent` into a single composable unit.
70
+ It provides a concise way to build modals with a title, close button, and content area.
71
+
72
+ ```tsx
73
+ import { Modal, ModalFrame } from '@ceed/ads';
74
+
75
+ function DetailModal({ open, onClose }) {
76
+ return (
77
+ <Modal open={open} onClose={onClose}>
78
+ <ModalFrame title="Detail" onClose={onClose}>
79
+ Content goes here.
80
+ </ModalFrame>
81
+ </Modal>
82
+ );
83
+ }
84
+ ```
85
+
86
+ > **Note**: Connect the same handler to both `Modal`'s `onClose` and `ModalFrame`'s `onClose`.
87
+ > `Modal` handles backdrop click and ESC key, while `ModalFrame` handles the X button click.
88
+
77
89
  ## Examples
78
90
 
79
91
  ### Basic Modal
@@ -292,6 +304,166 @@ Modals can be stacked on top of each other when necessary.
292
304
  </>
293
305
  ```
294
306
 
307
+ ### ModalFrame
308
+
309
+ ModalFrame is a convenience component that automatically provides a title, close button, and content area.
310
+
311
+ #### ModalFrame Playground
312
+
313
+ ```tsx
314
+ <>
315
+ <Button onClick={() => setOpen(true)}>Open ModalFrame</Button>
316
+ <Modal open={open} onClose={() => setOpen(false)}>
317
+ <ModalFrame title="ModalFrame Title" onClose={() => setOpen(false)}>
318
+ <Typography>
319
+ ModalFrame automatically composes ModalDialog, ModalClose, DialogTitle, and DialogContent. You only need
320
+ to provide a title, onClose handler, and children.
321
+ </Typography>
322
+ </ModalFrame>
323
+ </Modal>
324
+ </>
325
+ ```
326
+
327
+ #### titleStartDecorator
328
+
329
+ Display an icon or decorative element before the title.
330
+
331
+ ```tsx
332
+ <>
333
+ <Button onClick={() => setOpen(true)}>With Decorator</Button>
334
+ <Modal open={open} onClose={() => setOpen(false)}>
335
+ <ModalFrame title="Details" titleStartDecorator={<InfoOutlinedIcon />} onClose={() => setOpen(false)}>
336
+ <Typography>
337
+ Use the <code>titleStartDecorator</code> prop to display an icon or element before the title.
338
+ </Typography>
339
+ </ModalFrame>
340
+ </Modal>
341
+ </>
342
+ ```
343
+
344
+ #### Form Content
345
+
346
+ An inline form pattern where the submit button lives inside the content area.
347
+
348
+ ```tsx
349
+ <>
350
+ <Button onClick={() => setOpen(true)}>Form in ModalFrame</Button>
351
+ <Modal open={open} onClose={() => setOpen(false)}>
352
+ <ModalFrame title="Create Project" onClose={() => setOpen(false)}>
353
+ <form onSubmit={(event: React.FormEvent<HTMLFormElement>) => {
354
+ event.preventDefault();
355
+ setOpen(false);
356
+ }}>
357
+ <Stack spacing={2}>
358
+ <FormControl>
359
+ <FormLabel>Name</FormLabel>
360
+ <Input required />
361
+ </FormControl>
362
+ <FormControl>
363
+ <FormLabel>Description</FormLabel>
364
+ <Input required />
365
+ </FormControl>
366
+ <Button type="submit">Submit</Button>
367
+ </Stack>
368
+ </form>
369
+ </ModalFrame>
370
+ </Modal>
371
+ </>
372
+ ```
373
+
374
+ #### Sizes
375
+
376
+ Compare sm / md / lg sizes side by side.
377
+
378
+ ```tsx
379
+ <Stack direction="row" spacing={2}>
380
+ <Button size="sm" onClick={() => setOpenSm(true)}>
381
+ Small
382
+ </Button>
383
+ <Button size="md" onClick={() => setOpenMd(true)}>
384
+ Medium
385
+ </Button>
386
+ <Button size="lg" onClick={() => setOpenLg(true)}>
387
+ Large
388
+ </Button>
389
+ <Modal open={openSm} onClose={() => setOpenSm(false)}>
390
+ <ModalFrame title="Small ModalFrame" size="sm" onClose={() => setOpenSm(false)}>
391
+ <Typography>This is a small ModalFrame.</Typography>
392
+ </ModalFrame>
393
+ </Modal>
394
+ <Modal open={openMd} onClose={() => setOpenMd(false)}>
395
+ <ModalFrame title="Medium ModalFrame" size="md" onClose={() => setOpenMd(false)}>
396
+ <Typography>This is a medium ModalFrame.</Typography>
397
+ </ModalFrame>
398
+ </Modal>
399
+ <Modal open={openLg} onClose={() => setOpenLg(false)}>
400
+ <ModalFrame title="Large ModalFrame" size="lg" onClose={() => setOpenLg(false)}>
401
+ <Typography>This is a large ModalFrame.</Typography>
402
+ </ModalFrame>
403
+ </Modal>
404
+ </Stack>
405
+ ```
406
+
407
+ #### Custom Content
408
+
409
+ A layout example suited for displaying detailed information.
410
+
411
+ ```tsx
412
+ <>
413
+ <Button onClick={() => setOpen(true)}>Custom Content</Button>
414
+ <Modal open={open} onClose={() => setOpen(false)}>
415
+ <ModalFrame title="Order Details" onClose={() => setOpen(false)}>
416
+ <Stack spacing={2}>
417
+ <Box>
418
+ <Typography level="title-sm">Order ID</Typography>
419
+ <Typography level="body-sm">ORD-2024-00123</Typography>
420
+ </Box>
421
+ <Divider />
422
+ <Box>
423
+ <Typography level="title-sm">Customer</Typography>
424
+ <Typography level="body-sm">John Doe</Typography>
425
+ </Box>
426
+ <Divider />
427
+ <Box>
428
+ <Typography level="title-sm">Status</Typography>
429
+ <Typography level="body-sm" color="success">
430
+ Completed
431
+ </Typography>
432
+ </Box>
433
+ </Stack>
434
+ </ModalFrame>
435
+ </Modal>
436
+ </>
437
+ ```
438
+
439
+ #### Standalone
440
+
441
+ ModalFrame can be used without a Modal wrapper for embedding dialog-style layouts directly within a page.
442
+
443
+ > ⚠️ **Important** ⚠️
444
+ >
445
+ > When using ModalFrame without Modal, the parent container **must** meet the following requirements:
446
+ >
447
+ > - `position: 'relative'` — ModalFrame uses absolute positioning internally
448
+ > - Explicit `width` and `height` — `height` must be an absolute value (e.g., `300`, `'400px'`), not a relative value like `%` or `auto`
449
+ >
450
+ > ModalFrame inherits its dimensions from `ModalDialog`, which normally receives sizing from the Modal overlay. Without these constraints, the component will not render with correct dimensions.
451
+
452
+ ```tsx
453
+ <Box sx={{
454
+ position: 'relative',
455
+ width: 480,
456
+ height: 300
457
+ }}>
458
+ <ModalFrame title="Standalone ModalFrame" onClose={() => console.log('close')}>
459
+ <Typography>
460
+ ModalFrame used without Modal. The parent container must provide
461
+ explicit width and height.
462
+ </Typography>
463
+ </ModalFrame>
464
+ </Box>
465
+ ```
466
+
295
467
  ## When to Use
296
468
 
297
469
  ### ✅ Good Use Cases
@@ -327,8 +499,7 @@ function DeleteConfirmation({ item, onDelete, onCancel }) {
327
499
  </DialogTitle>
328
500
  <Divider />
329
501
  <DialogContent>
330
- This action cannot be undone. All data associated with this item
331
- will be permanently removed.
502
+ This action cannot be undone. All data associated with this item will be permanently removed.
332
503
  </DialogContent>
333
504
  <DialogActions>
334
505
  <Button variant="solid" color="danger" onClick={onDelete}>
@@ -404,8 +575,7 @@ function TermsModal({ open, onAccept, onDecline }) {
404
575
  <DialogTitle>Terms of Service</DialogTitle>
405
576
  <DialogContent>
406
577
  <Typography level="body-sm">
407
- Please read and accept the following terms and conditions before
408
- proceeding...
578
+ Please read and accept the following terms and conditions before proceeding...
409
579
  </Typography>
410
580
  {/* Terms content */}
411
581
  </DialogContent>
@@ -433,11 +603,7 @@ function ImagePreviewModal({ image, open, onClose }) {
433
603
  <Modal open={open} onClose={onClose}>
434
604
  <ModalDialog layout="center" sx={{ p: 0, overflow: 'hidden' }}>
435
605
  <ModalClose sx={{ top: 8, right: 8, zIndex: 1 }} />
436
- <img
437
- src={image.src}
438
- alt={image.alt}
439
- style={{ maxWidth: '90vw', maxHeight: '90vh', objectFit: 'contain' }}
440
- />
606
+ <img src={image.src} alt={image.alt} style={{ maxWidth: '90vw', maxHeight: '90vh', objectFit: 'contain' }} />
441
607
  </ModalDialog>
442
608
  </Modal>
443
609
  );
@@ -468,22 +634,69 @@ function LoadingModal({ open, message }) {
468
634
  Modal uses a composition pattern with multiple sub-components:
469
635
 
470
636
  ```tsx
471
- <Modal> {/* Overlay and backdrop */}
472
- <ModalDialog> {/* Dialog container */}
473
- <ModalClose /> {/* Close button (optional) */}
474
- <DialogTitle> {/* Header */}
637
+ <Modal>
638
+ {/* Overlay and backdrop */}
639
+ <ModalDialog>
640
+ {/* Dialog container */}
641
+ <ModalClose /> {/* Close button (optional) */}
642
+ <DialogTitle>
643
+ {/* Header */}
475
644
  Title
476
645
  </DialogTitle>
477
- <DialogContent> {/* Body */}
646
+ <DialogContent>
647
+ {/* Body */}
478
648
  Content goes here
479
649
  </DialogContent>
480
- <DialogActions> {/* Footer */}
650
+ <DialogActions>
651
+ {/* Footer */}
481
652
  <Button>Action</Button>
482
653
  </DialogActions>
483
654
  </ModalDialog>
484
655
  </Modal>
485
656
  ```
486
657
 
658
+ ## Component Roles
659
+
660
+ | Component | Role | When to Use |
661
+ | ----------------- | --------------------------------------------------------------- | ---------------------------------------------------------------- |
662
+ | **Modal** | Overlay backdrop, open/close state management | Always required as the outermost wrapper |
663
+ | **ModalDialog** | Dialog container (variant/size/layout) | When you need direct control over layout |
664
+ | **ModalClose** | Close (X) button in the top-right corner | When users should be able to close via a button |
665
+ | **ModalOverflow** | Scrollable area | When content exceeds the viewport |
666
+ | **ModalFrame** | Combines ModalDialog + ModalClose + DialogTitle + DialogContent | When you only need a title + close + content (no action buttons) |
667
+ | **DialogTitle** | Header area (styled padding) | When composing manually |
668
+ | **DialogContent** | Body area (styled padding) | When composing manually |
669
+ | **DialogActions** | Footer action button area | When confirm/cancel buttons are needed |
670
+
671
+ ## Choosing the Right Component
672
+
673
+ ### ModalFrame vs DialogFrame
674
+
675
+ | | ModalFrame | DialogFrame |
676
+ | ------------------ | ----------------------------------------------- | --------------------------------------- |
677
+ | Close (X) button | Built-in | None |
678
+ | Title decorator | `titleStartDecorator` | None |
679
+ | Action button area | None | `actions` prop (required) |
680
+ | Fullscreen | `layout="fullscreen"` | `fullscreen` prop |
681
+ | Best for | Information display, detail views, inline forms | Confirm/cancel dialogs, decision-making |
682
+
683
+ ### Use ModalFrame when
684
+
685
+ - You need an informational modal with a close button (detail views, previews)
686
+ - The form's submit button lives inside the content area
687
+ - You need an icon next to the title
688
+
689
+ ### Use DialogFrame when
690
+
691
+ - Explicit action buttons (confirm/cancel) must be pinned to the bottom
692
+ - User decisions are required (delete confirmation, save confirmation)
693
+ - Only explicit choices should be allowed without a close (X) button
694
+
695
+ ### Use manual composition when
696
+
697
+ - You need a custom layout that doesn't fit the ModalFrame/DialogFrame pattern
698
+ - You want to use both ModalClose and DialogActions together
699
+
487
700
  ## Props and Customization
488
701
 
489
702
  ### Modal Props
@@ -505,6 +718,17 @@ Modal uses a composition pattern with multiple sub-components:
505
718
  | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Dialog size |
506
719
  | `layout` | `'center' \| 'fullscreen'` | `'center'` | Layout mode |
507
720
 
721
+ ### ModalFrame Props
722
+
723
+ | Prop | Type | Default | Description |
724
+ | --------------------- | ------------ | ------- | ------------------------------------------ |
725
+ | `title` | `ReactNode` | - | Title displayed in the header |
726
+ | `children` | `ReactNode` | - | Body content |
727
+ | `titleStartDecorator` | `ReactNode` | - | Icon or element displayed before the title |
728
+ | `onClose` | `() => void` | - | Callback when the close button is clicked |
729
+
730
+ ModalFrame accepts all ModalDialog props (`variant`, `color`, `size`, `layout`, `sx`, etc.).
731
+
508
732
  ### Custom Styling
509
733
 
510
734
  ```tsx
@@ -543,17 +767,10 @@ Modal components include comprehensive accessibility features:
543
767
  - `aria-describedby` connects to DialogContent
544
768
 
545
769
  ```tsx
546
- <Modal
547
- open={open}
548
- onClose={onClose}
549
- aria-labelledby="modal-title"
550
- aria-describedby="modal-description"
551
- >
770
+ <Modal open={open} onClose={onClose} aria-labelledby="modal-title" aria-describedby="modal-description">
552
771
  <ModalDialog>
553
772
  <DialogTitle id="modal-title">Accessible Title</DialogTitle>
554
- <DialogContent id="modal-description">
555
- This content is read by screen readers.
556
- </DialogContent>
773
+ <DialogContent id="modal-description">This content is read by screen readers.</DialogContent>
557
774
  </ModalDialog>
558
775
  </Modal>
559
776
  ```
@@ -582,7 +799,9 @@ Modal components include comprehensive accessibility features:
582
799
  ```tsx
583
800
  // ✅ Good: Clear action buttons
584
801
  <DialogActions>
585
- <Button variant="solid" color="danger">Delete</Button>
802
+ <Button variant="solid" color="danger">
803
+ Delete
804
+ </Button>
586
805
  <Button variant="plain">Cancel</Button>
587
806
  </DialogActions>
588
807
  ```
@@ -657,15 +876,11 @@ Use `keepMounted` only when the modal needs to preserve state between openings:
657
876
  Memoize modal content when it depends on complex data:
658
877
 
659
878
  ```tsx
660
- const modalContent = useMemo(() => (
661
- <ComplexContent data={data} />
662
- ), [data]);
879
+ const modalContent = useMemo(() => <ComplexContent data={data} />, [data]);
663
880
 
664
881
  <Modal open={open} onClose={onClose}>
665
- <ModalDialog>
666
- {modalContent}
667
- </ModalDialog>
668
- </Modal>
882
+ <ModalDialog>{modalContent}</ModalDialog>
883
+ </Modal>;
669
884
  ```
670
885
 
671
886
  Modal is a powerful component for focused user interactions. Use it thoughtfully to maintain a smooth user experience while capturing important decisions and inputs.
@@ -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/ads';
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.
@@ -3,8 +3,10 @@
3
3
  ## Documentation
4
4
 
5
5
  - [Alert](./Alert.md)
6
+ - [CircularProgress](./CircularProgress.md)
6
7
  - [DialogFrame](./Dialog.md)
7
8
  - [Modal](./Modal.md)
9
+ - [Skeleton](./Skeleton.md)
8
10
 
9
11
  ## Parent
10
12