@fpkit/acss 0.5.12 → 0.5.13
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/README.md +89 -0
- package/libs/{chunk-DV56L5YX.cjs → chunk-2LTJ7HHX.cjs} +4 -4
- package/libs/{chunk-EQ67LF46.js → chunk-2Y7W75TT.js} +3 -3
- package/libs/{chunk-KKLTUJFB.cjs → chunk-3MKLDCKQ.cjs} +5 -5
- package/libs/chunk-3MKLDCKQ.cjs.map +1 -0
- package/libs/{chunk-X3EVB7VS.cjs → chunk-5S4ORA4C.cjs} +3 -3
- package/libs/{chunk-O6QZBB6G.js → chunk-772NRB75.js} +5 -5
- package/libs/chunk-772NRB75.js.map +1 -0
- package/libs/{chunk-6BVXFW7U.cjs → chunk-AHDJGCG5.cjs} +3 -3
- package/libs/{chunk-E3XP6BEX.cjs → chunk-B7F5FS6D.cjs} +3 -3
- package/libs/chunk-D4YLRWAO.cjs +18 -0
- package/libs/chunk-D4YLRWAO.cjs.map +1 -0
- package/libs/chunk-ETFLFC2S.js +10 -0
- package/libs/chunk-ETFLFC2S.js.map +1 -0
- package/libs/chunk-GZ4QFPRY.js +9 -0
- package/libs/chunk-GZ4QFPRY.js.map +1 -0
- package/libs/{chunk-LHVJKDMA.cjs → chunk-J32EZPYD.cjs} +3 -3
- package/libs/chunk-JJ43O4Y5.js +8 -0
- package/libs/chunk-JJ43O4Y5.js.map +1 -0
- package/libs/chunk-KUKIVRC2.js +7 -0
- package/libs/chunk-KUKIVRC2.js.map +1 -0
- package/libs/chunk-L75OQKEI.cjs +13 -0
- package/libs/chunk-L75OQKEI.cjs.map +1 -0
- package/libs/{chunk-LL7HTLMS.cjs → chunk-M5RRNTVX.cjs} +3 -3
- package/libs/{chunk-LIQJ7ZZR.js → chunk-NGTJDDFO.js} +2 -2
- package/libs/chunk-OK5QEIMD.cjs +17 -0
- package/libs/chunk-OK5QEIMD.cjs.map +1 -0
- package/libs/chunk-P2DC76ZZ.cjs +18 -0
- package/libs/chunk-P2DC76ZZ.cjs.map +1 -0
- package/libs/chunk-PQ2K3BM6.cjs +17 -0
- package/libs/chunk-PQ2K3BM6.cjs.map +1 -0
- package/libs/{chunk-QCMV4VQZ.js → chunk-QLZWHAMK.js} +2 -2
- package/libs/{chunk-BIP2NY53.js → chunk-RIVUMPOG.js} +2 -2
- package/libs/{chunk-ICCKQ2GC.cjs → chunk-ROZI23GS.cjs} +4 -4
- package/libs/{chunk-NHYXGV3L.js → chunk-SMYRLO3E.js} +2 -2
- package/libs/{chunk-5ZM4XL44.js → chunk-TYRCEX2L.js} +2 -2
- package/libs/chunk-VUH3FXGJ.js +11 -0
- package/libs/chunk-VUH3FXGJ.js.map +1 -0
- package/libs/{chunk-PPOOBUOS.js → chunk-XBA562WW.js} +2 -2
- package/libs/{chunk-QVV34QEH.cjs → chunk-XTQKWY7W.cjs} +3 -3
- package/libs/{chunk-YWOYVRFT.js → chunk-ZANSFMTD.js} +3 -3
- package/libs/components/alert/alert.css +1 -1
- package/libs/components/alert/alert.css.map +1 -1
- package/libs/components/alert/alert.min.css +2 -2
- package/libs/components/badge/badge.css +1 -1
- package/libs/components/badge/badge.css.map +1 -1
- package/libs/components/badge/badge.min.css +2 -2
- package/libs/components/breadcrumbs/breadcrumb.cjs +9 -5
- package/libs/components/breadcrumbs/breadcrumb.d.cts +271 -32
- package/libs/components/breadcrumbs/breadcrumb.d.ts +271 -32
- package/libs/components/breadcrumbs/breadcrumb.js +3 -3
- package/libs/components/button.cjs +4 -4
- package/libs/components/button.d.cts +2 -2
- package/libs/components/button.d.ts +2 -2
- package/libs/components/button.js +2 -2
- package/libs/components/buttons/button.css +1 -1
- package/libs/components/buttons/button.css.map +1 -1
- package/libs/components/buttons/button.min.css +2 -2
- package/libs/components/card.cjs +7 -7
- package/libs/components/card.d.cts +277 -33
- package/libs/components/card.d.ts +277 -33
- package/libs/components/card.js +2 -2
- package/libs/components/cards/card.css +1 -1
- package/libs/components/cards/card.css.map +1 -1
- package/libs/components/cards/card.min.css +2 -2
- package/libs/components/details/details.css +1 -1
- package/libs/components/details/details.css.map +1 -1
- package/libs/components/details/details.min.css +2 -2
- package/libs/components/dialog/dialog.cjs +7 -7
- package/libs/components/dialog/dialog.css +1 -1
- package/libs/components/dialog/dialog.css.map +1 -1
- package/libs/components/dialog/dialog.d.cts +88 -34
- package/libs/components/dialog/dialog.d.ts +88 -34
- package/libs/components/dialog/dialog.js +5 -5
- package/libs/components/dialog/dialog.min.css +2 -2
- package/libs/components/form/fields.cjs +4 -4
- package/libs/components/form/fields.d.cts +2 -2
- package/libs/components/form/fields.d.ts +2 -2
- package/libs/components/form/fields.js +2 -2
- package/libs/components/form/textarea.cjs +4 -4
- package/libs/components/form/textarea.d.cts +2 -2
- package/libs/components/form/textarea.d.ts +2 -2
- package/libs/components/form/textarea.js +2 -2
- package/libs/components/heading/heading.cjs +3 -3
- package/libs/components/heading/heading.d.cts +3 -14
- package/libs/components/heading/heading.d.ts +3 -14
- package/libs/components/heading/heading.js +2 -2
- package/libs/components/icons/icon.cjs +4 -4
- package/libs/components/icons/icon.d.cts +148 -4
- package/libs/components/icons/icon.d.ts +148 -4
- package/libs/components/icons/icon.js +2 -2
- package/libs/components/images/img.css +1 -1
- package/libs/components/images/img.css.map +1 -1
- package/libs/components/images/img.min.css +2 -2
- package/libs/components/link/link.cjs +4 -4
- package/libs/components/link/link.d.cts +2 -2
- package/libs/components/link/link.d.ts +2 -2
- package/libs/components/link/link.js +2 -2
- package/libs/components/list/list.cjs +5 -5
- package/libs/components/list/list.d.cts +3 -3
- package/libs/components/list/list.d.ts +3 -3
- package/libs/components/list/list.js +2 -2
- package/libs/components/modal.cjs +4 -4
- package/libs/components/modal.js +3 -3
- package/libs/components/nav/nav.cjs +7 -7
- package/libs/components/nav/nav.d.cts +2 -2
- package/libs/components/nav/nav.d.ts +2 -2
- package/libs/components/nav/nav.js +3 -3
- package/libs/components/text/text.cjs +5 -5
- package/libs/components/text/text.d.cts +2 -2
- package/libs/components/text/text.d.ts +2 -2
- package/libs/components/text/text.js +2 -2
- package/libs/heading-3648c538.d.ts +250 -0
- package/libs/hooks.cjs +7 -0
- package/libs/hooks.d.cts +5 -0
- package/libs/hooks.d.ts +5 -0
- package/libs/hooks.js +3 -0
- package/libs/icons.cjs +3 -3
- package/libs/icons.d.cts +1 -1
- package/libs/icons.d.ts +1 -1
- package/libs/icons.js +2 -2
- package/libs/index.cjs +112 -91
- package/libs/index.cjs.map +1 -1
- package/libs/index.css +1 -1
- package/libs/index.css.map +1 -1
- package/libs/index.d.cts +515 -31
- package/libs/index.d.ts +515 -31
- package/libs/index.js +31 -19
- package/libs/index.js.map +1 -1
- package/libs/ui-645f95b5.d.ts +285 -0
- package/package.json +2 -83
- package/src/components/README-UI.mdx +416 -0
- package/src/components/alert/ACCESSIBILITY.md +319 -0
- package/src/components/alert/README.mdx +475 -19
- package/src/components/alert/alert.scss +113 -6
- package/src/components/alert/alert.stories.tsx +372 -0
- package/src/components/alert/alert.test.tsx +762 -0
- package/src/components/alert/alert.tsx +331 -66
- package/src/components/alert/views/alert-actions.tsx +13 -0
- package/src/components/alert/views/alert-content.tsx +17 -0
- package/src/components/alert/views/alert-icon.tsx +53 -0
- package/src/components/alert/views/alert-screen-reader-text.tsx +30 -0
- package/src/components/alert/views/alert-title.tsx +23 -0
- package/src/components/alert/views/alert-view.tsx +158 -0
- package/src/components/alert/views/index.ts +12 -0
- package/src/components/badge/badge.mdx +186 -49
- package/src/components/badge/badge.scss +20 -2
- package/src/components/badge/badge.stories.tsx +160 -14
- package/src/components/badge/badge.test.tsx +179 -0
- package/src/components/badge/badge.tsx +97 -4
- package/src/components/breadcrumbs/README.mdx +364 -45
- package/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap +152 -0
- package/src/components/breadcrumbs/breadcrumb.stories.tsx +7 -3
- package/src/components/breadcrumbs/breadcrumb.test.tsx +490 -0
- package/src/components/breadcrumbs/breadcrumb.tsx +427 -170
- package/src/components/buttons/button.scss +34 -31
- package/src/components/buttons/button.stories.tsx +35 -0
- package/src/components/cards/README.mdx +657 -0
- package/src/components/cards/card.scss +22 -0
- package/src/components/cards/card.stories.tsx +167 -5
- package/src/components/cards/card.test.tsx +360 -20
- package/src/components/cards/card.tsx +200 -79
- package/src/components/cards/card.types.ts +135 -0
- package/src/components/cards/card.utils.ts +79 -0
- package/src/components/details/ACCESSIBILITY-REVIEW-LIVE.md +1050 -0
- package/src/components/details/ACCESSIBILITY-REVIEW.md +502 -0
- package/src/components/details/README.mdx +437 -69
- package/src/components/details/details.scss +16 -7
- package/src/components/details/details.test.tsx +385 -0
- package/src/components/details/details.tsx +101 -69
- package/src/components/details/details.types.ts +76 -0
- package/src/components/dialog/README.mdx +513 -110
- package/src/components/dialog/dialog-modal.tsx +79 -56
- package/src/components/dialog/dialog.scss +53 -3
- package/src/components/dialog/dialog.stories.tsx +10 -7
- package/src/components/dialog/dialog.test.tsx +450 -0
- package/src/components/dialog/dialog.tsx +69 -59
- package/src/components/dialog/dialog.types.ts +133 -0
- package/src/components/dialog/views/dialog-footer.tsx +54 -11
- package/src/components/dialog/views/dialog-header.tsx +20 -15
- package/src/components/heading/heading.stories.tsx +44 -4
- package/src/components/heading/heading.tsx +89 -23
- package/src/components/icons/README.mdx +332 -0
- package/src/components/icons/icon.stories.tsx +74 -1
- package/src/components/icons/icon.tsx +89 -1
- package/src/components/icons/types.ts +47 -0
- package/src/components/images/README.mdx +340 -24
- package/src/components/images/img.scss +19 -3
- package/src/components/images/img.stories.tsx +424 -15
- package/src/components/images/img.test.tsx +354 -25
- package/src/components/images/img.tsx +186 -63
- package/src/components/images/img.types.ts +211 -0
- package/src/components/title/MIGRATION.md +199 -0
- package/src/components/title/README.md +326 -0
- package/src/components/title/README.mdx +452 -0
- package/src/components/title/title.stories.tsx +393 -0
- package/src/components/title/title.test.tsx +251 -0
- package/src/components/title/title.tsx +219 -0
- package/src/components/ui.stories.tsx +894 -0
- package/src/components/ui.test.tsx +559 -0
- package/src/components/ui.tsx +266 -15
- package/src/components/word-count/README.md +240 -0
- package/src/hooks.ts +1 -0
- package/src/index.ts +10 -2
- package/src/sass/_properties.scss +1 -0
- package/src/styles/alert/alert.css +94 -4
- package/src/styles/alert/alert.css.map +1 -1
- package/src/styles/badge/badge.css +20 -2
- package/src/styles/badge/badge.css.map +1 -1
- package/src/styles/buttons/button.css +31 -31
- package/src/styles/buttons/button.css.map +1 -1
- package/src/styles/cards/card.css +16 -0
- package/src/styles/cards/card.css.map +1 -1
- package/src/styles/details/details.css +19 -8
- package/src/styles/details/details.css.map +1 -1
- package/src/styles/dialog/dialog.css +43 -2
- package/src/styles/dialog/dialog.css.map +1 -1
- package/src/styles/images/img.css +15 -3
- package/src/styles/images/img.css.map +1 -1
- package/src/styles/index.css +240 -51
- package/src/styles/index.css.map +1 -1
- package/src/test/setup.d.ts +9 -0
- package/src/test/setup.ts +53 -1
- package/libs/chunk-6TE5QEVE.cjs +0 -13
- package/libs/chunk-6TE5QEVE.cjs.map +0 -1
- package/libs/chunk-7K76RW2A.cjs +0 -18
- package/libs/chunk-7K76RW2A.cjs.map +0 -1
- package/libs/chunk-BSPKFLO4.js +0 -8
- package/libs/chunk-BSPKFLO4.js.map +0 -1
- package/libs/chunk-BV5CLH44.cjs +0 -18
- package/libs/chunk-BV5CLH44.cjs.map +0 -1
- package/libs/chunk-DKGJHKGW.js +0 -9
- package/libs/chunk-DKGJHKGW.js.map +0 -1
- package/libs/chunk-ECLD37WN.cjs +0 -16
- package/libs/chunk-ECLD37WN.cjs.map +0 -1
- package/libs/chunk-HYBZBN4G.js +0 -8
- package/libs/chunk-HYBZBN4G.js.map +0 -1
- package/libs/chunk-KKLTUJFB.cjs.map +0 -1
- package/libs/chunk-M5QL5TAE.cjs +0 -14
- package/libs/chunk-M5QL5TAE.cjs.map +0 -1
- package/libs/chunk-NE6YXTMC.js +0 -7
- package/libs/chunk-NE6YXTMC.js.map +0 -1
- package/libs/chunk-O6QZBB6G.js.map +0 -1
- package/libs/chunk-SXVZSWX6.js +0 -11
- package/libs/chunk-SXVZSWX6.js.map +0 -1
- package/libs/ui-9a6f9f8d.d.ts +0 -24
- package/src/components/cards/README.md +0 -80
- package/src/components/dialog/hooks/useClickOutside.ts +0 -33
- /package/libs/{chunk-DV56L5YX.cjs.map → chunk-2LTJ7HHX.cjs.map} +0 -0
- /package/libs/{chunk-EQ67LF46.js.map → chunk-2Y7W75TT.js.map} +0 -0
- /package/libs/{chunk-X3EVB7VS.cjs.map → chunk-5S4ORA4C.cjs.map} +0 -0
- /package/libs/{chunk-6BVXFW7U.cjs.map → chunk-AHDJGCG5.cjs.map} +0 -0
- /package/libs/{chunk-E3XP6BEX.cjs.map → chunk-B7F5FS6D.cjs.map} +0 -0
- /package/libs/{chunk-LHVJKDMA.cjs.map → chunk-J32EZPYD.cjs.map} +0 -0
- /package/libs/{chunk-LL7HTLMS.cjs.map → chunk-M5RRNTVX.cjs.map} +0 -0
- /package/libs/{chunk-LIQJ7ZZR.js.map → chunk-NGTJDDFO.js.map} +0 -0
- /package/libs/{chunk-QCMV4VQZ.js.map → chunk-QLZWHAMK.js.map} +0 -0
- /package/libs/{chunk-BIP2NY53.js.map → chunk-RIVUMPOG.js.map} +0 -0
- /package/libs/{chunk-ICCKQ2GC.cjs.map → chunk-ROZI23GS.cjs.map} +0 -0
- /package/libs/{chunk-NHYXGV3L.js.map → chunk-SMYRLO3E.js.map} +0 -0
- /package/libs/{chunk-5ZM4XL44.js.map → chunk-TYRCEX2L.js.map} +0 -0
- /package/libs/{chunk-PPOOBUOS.js.map → chunk-XBA562WW.js.map} +0 -0
- /package/libs/{chunk-QVV34QEH.cjs.map → chunk-XTQKWY7W.cjs.map} +0 -0
- /package/libs/{chunk-YWOYVRFT.js.map → chunk-ZANSFMTD.js.map} +0 -0
|
@@ -4,184 +4,587 @@ import { Meta } from "@storybook/addon-docs/blocks";
|
|
|
4
4
|
|
|
5
5
|
# Dialog Components
|
|
6
6
|
|
|
7
|
-
A
|
|
8
|
-
React applications. The component supports:
|
|
7
|
+
A modern, accessible dialog component system built with React and TypeScript, leveraging the native HTML `<dialog>` element for optimal performance and built-in accessibility features.
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
- Full ARIA accessibility support out of the box
|
|
12
|
-
- Customizable headers and content layout
|
|
13
|
-
- Multiple variants including standard dialogs and alert dialogs
|
|
14
|
-
- Event handling for open, close, and cancel actions
|
|
15
|
-
- Responsive design with inline rendering option
|
|
16
|
-
- Modal and non-modal dialog variants via `isAlertDialog`
|
|
17
|
-
- Built-in header with close functionality
|
|
18
|
-
- Configurable footer with confirm/cancel actions
|
|
19
|
-
- Click-outside-to-close behavior
|
|
20
|
-
- Focus management and keyboard navigation
|
|
9
|
+
## Features
|
|
21
10
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
✅ **Controlled Component Pattern** - Full React state control with `isOpen`/`onOpenChange`
|
|
12
|
+
✅ **WCAG 2.1 AA Compliant** - Proper ARIA attributes, focus management, keyboard navigation
|
|
13
|
+
✅ **Native Dialog API** - Leverages browser's built-in focus trap and escape key handling
|
|
14
|
+
✅ **TypeScript Support** - Comprehensive type definitions with full IntelliSense
|
|
15
|
+
✅ **Two Modes** - Modal dialogs (overlay) and inline alert dialogs
|
|
16
|
+
✅ **Focus Management** - Automatic focus restoration to trigger element
|
|
17
|
+
✅ **Flexible API** - Controlled `Dialog` or uncontrolled `DialogModal` wrapper
|
|
18
|
+
✅ **Customizable** - CSS custom properties for theming
|
|
19
|
+
✅ **Keyboard Accessible** - `:focus-visible` styles, Escape key support
|
|
20
|
+
✅ **High Contrast Mode** - Supports `prefers-contrast: high` media query
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
---
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
### Dialog Component
|
|
24
|
+
## Component Architecture
|
|
31
25
|
|
|
32
26
|
The dialog system consists of:
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
| Component | Purpose | Usage |
|
|
29
|
+
|-----------|---------|-------|
|
|
30
|
+
| **`Dialog`** | Controlled modal/alert dialog | When you need full state control |
|
|
31
|
+
| **`DialogModal`** | Uncontrolled wrapper with trigger button | For simple use cases |
|
|
32
|
+
| **`DialogHeader`** | Header with title and close button | Internal (auto-rendered) |
|
|
33
|
+
| **`DialogFooter`** | Footer with confirm/cancel actions | Internal (auto-rendered) |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### Option 1: Controlled Dialog (Recommended)
|
|
40
|
+
|
|
41
|
+
Use this when you need to control the dialog state from your component.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { Dialog } from "@fpkit/acss";
|
|
45
|
+
import { useState } from "react";
|
|
46
|
+
|
|
47
|
+
function MyComponent() {
|
|
48
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
<button onClick={() => setIsOpen(true)}>Open Dialog</button>
|
|
53
|
+
|
|
54
|
+
<Dialog
|
|
55
|
+
isOpen={isOpen}
|
|
56
|
+
onOpenChange={setIsOpen}
|
|
57
|
+
dialogTitle="Confirm Action"
|
|
58
|
+
>
|
|
59
|
+
<p>Are you sure you want to proceed?</p>
|
|
60
|
+
</Dialog>
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Option 2: Uncontrolled DialogModal (Simple)
|
|
67
|
+
|
|
68
|
+
Use this when you want a dialog with a built-in trigger button and automatic state management.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { DialogModal } from "@fpkit/acss";
|
|
72
|
+
|
|
73
|
+
function MyComponent() {
|
|
74
|
+
return (
|
|
75
|
+
<DialogModal
|
|
76
|
+
dialogTitle="Delete Item"
|
|
77
|
+
btnLabel="Delete"
|
|
78
|
+
btnSize="md"
|
|
79
|
+
onConfirm={async () => {
|
|
80
|
+
await deleteItem();
|
|
81
|
+
}}
|
|
82
|
+
confirmLabel="Delete"
|
|
83
|
+
cancelLabel="Cancel"
|
|
84
|
+
>
|
|
85
|
+
<p>This action cannot be undone. Are you sure?</p>
|
|
86
|
+
</DialogModal>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
37
90
|
|
|
38
|
-
|
|
91
|
+
---
|
|
39
92
|
|
|
40
|
-
|
|
41
|
-
|
|
93
|
+
## API Reference
|
|
94
|
+
|
|
95
|
+
### Dialog Props (Controlled)
|
|
42
96
|
|
|
43
97
|
#### Required Props
|
|
44
98
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
99
|
+
| Prop | Type | Description |
|
|
100
|
+
|------|------|-------------|
|
|
101
|
+
| `isOpen` | `boolean` | Controls whether the dialog is currently open |
|
|
102
|
+
| `onOpenChange` | `(open: boolean) => void` | Callback fired when dialog open state changes |
|
|
103
|
+
| `dialogTitle` | `string` | Title displayed in the dialog header |
|
|
104
|
+
| `children` | `ReactNode` | Content to display inside the dialog body |
|
|
48
105
|
|
|
49
106
|
#### Optional Props
|
|
50
107
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
108
|
+
| Prop | Type | Default | Description |
|
|
109
|
+
|------|------|---------|-------------|
|
|
110
|
+
| `isAlertDialog` | `boolean` | `false` | If true, renders as non-modal inline alert using `dialog.show()` |
|
|
111
|
+
| `onClose` | `() => void` | - | **Deprecated:** Use `onOpenChange`. Called when dialog closes. |
|
|
112
|
+
| `onConfirm` | `() => void \| Promise<void>` | - | Callback fired when confirm button is clicked |
|
|
113
|
+
| `confirmLabel` | `string` | `"Confirm"` | Text label for the confirm button |
|
|
114
|
+
| `cancelLabel` | `string` | `"Cancel"` | Text label for the cancel button |
|
|
115
|
+
| `hideFooter` | `boolean` | `false` | If true, hides the footer with action buttons |
|
|
116
|
+
| `className` | `string` | `""` | Additional CSS classes to apply to the dialog |
|
|
117
|
+
| `dialogLabel` | `string` | - | Optional `aria-label` for the dialog |
|
|
118
|
+
| `styles` | `CSSProperties` | - | Inline styles to apply to the dialog element |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### DialogModal Props (Uncontrolled)
|
|
123
|
+
|
|
124
|
+
All `Dialog` props plus:
|
|
58
125
|
|
|
59
|
-
|
|
126
|
+
| Prop | Type | Default | Description |
|
|
127
|
+
|------|------|---------|-------------|
|
|
128
|
+
| `btnLabel` | `string` | `"Open Dialog"` | Text label for the trigger button |
|
|
129
|
+
| `btnSize` | `"sm" \| "md" \| "lg"` | `"sm"` | Size variant for the trigger button |
|
|
130
|
+
| `btnOnClick` | `() => void` | - | Callback fired when trigger button is clicked (before opening) |
|
|
131
|
+
| `btnProps` | `Record<string, unknown>` | - | Additional props to pass to the trigger button |
|
|
60
132
|
|
|
61
|
-
|
|
62
|
-
- `React.ComponentProps<dialog>`
|
|
133
|
+
**Note:** `DialogModal` manages its own state internally, so you don't need `isOpen`/`onOpenChange`.
|
|
63
134
|
|
|
64
|
-
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Usage Examples
|
|
138
|
+
|
|
139
|
+
### Basic Modal Dialog
|
|
65
140
|
|
|
66
141
|
```tsx
|
|
67
|
-
|
|
68
|
-
import {
|
|
142
|
+
import { Dialog } from "@fpkit/acss";
|
|
143
|
+
import { useState } from "react";
|
|
69
144
|
|
|
70
|
-
function
|
|
71
|
-
const [
|
|
145
|
+
function BasicExample() {
|
|
146
|
+
const [open, setOpen] = useState(false);
|
|
72
147
|
|
|
73
148
|
return (
|
|
74
149
|
<>
|
|
75
|
-
<button onClick={() =>
|
|
150
|
+
<button onClick={() => setOpen(true)}>Show Info</button>
|
|
151
|
+
|
|
76
152
|
<Dialog
|
|
77
|
-
isOpen={
|
|
78
|
-
|
|
79
|
-
dialogTitle="
|
|
80
|
-
|
|
153
|
+
isOpen={open}
|
|
154
|
+
onOpenChange={setOpen}
|
|
155
|
+
dialogTitle="Information"
|
|
156
|
+
hideFooter
|
|
81
157
|
>
|
|
82
|
-
<
|
|
158
|
+
<p>This is a simple informational dialog.</p>
|
|
159
|
+
<p>Click the X or press Escape to close.</p>
|
|
83
160
|
</Dialog>
|
|
84
161
|
</>
|
|
85
162
|
);
|
|
86
163
|
}
|
|
87
164
|
```
|
|
88
165
|
|
|
89
|
-
|
|
166
|
+
### Confirmation Dialog with Actions
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
import { Dialog } from "@fpkit/acss";
|
|
170
|
+
import { useState } from "react";
|
|
171
|
+
|
|
172
|
+
function ConfirmationExample() {
|
|
173
|
+
const [open, setOpen] = useState(false);
|
|
90
174
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
175
|
+
const handleDelete = async () => {
|
|
176
|
+
await deleteItem();
|
|
177
|
+
setOpen(false);
|
|
178
|
+
};
|
|
94
179
|
|
|
95
|
-
|
|
180
|
+
return (
|
|
181
|
+
<>
|
|
182
|
+
<button onClick={() => setOpen(true)}>Delete Item</button>
|
|
96
183
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
184
|
+
<Dialog
|
|
185
|
+
isOpen={open}
|
|
186
|
+
onOpenChange={setOpen}
|
|
187
|
+
dialogTitle="Confirm Deletion"
|
|
188
|
+
onConfirm={handleDelete}
|
|
189
|
+
confirmLabel="Delete"
|
|
190
|
+
cancelLabel="Keep"
|
|
191
|
+
>
|
|
192
|
+
<p>Are you sure you want to delete this item?</p>
|
|
193
|
+
<p>This action cannot be undone.</p>
|
|
194
|
+
</Dialog>
|
|
195
|
+
</>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
```
|
|
101
199
|
|
|
102
|
-
###
|
|
200
|
+
### Inline Alert Dialog (Non-Modal)
|
|
103
201
|
|
|
104
202
|
```tsx
|
|
203
|
+
import { Dialog } from "@fpkit/acss";
|
|
204
|
+
import { useState } from "react";
|
|
205
|
+
|
|
105
206
|
function AlertExample() {
|
|
207
|
+
const [showAlert, setShowAlert] = useState(false);
|
|
208
|
+
|
|
106
209
|
return (
|
|
107
|
-
<
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
210
|
+
<div>
|
|
211
|
+
<button onClick={() => setShowAlert(true)}>Show Alert</button>
|
|
212
|
+
|
|
213
|
+
{/* Non-modal: page remains interactive */}
|
|
214
|
+
<Dialog
|
|
215
|
+
isOpen={showAlert}
|
|
216
|
+
onOpenChange={setShowAlert}
|
|
217
|
+
dialogTitle="Warning"
|
|
218
|
+
isAlertDialog={true}
|
|
219
|
+
hideFooter
|
|
220
|
+
>
|
|
221
|
+
<p>⚠️ Your session will expire in 5 minutes.</p>
|
|
222
|
+
</Dialog>
|
|
223
|
+
|
|
224
|
+
<p>This content remains interactive even when alert is shown.</p>
|
|
225
|
+
</div>
|
|
111
226
|
);
|
|
112
227
|
}
|
|
113
228
|
```
|
|
114
229
|
|
|
115
|
-
###
|
|
230
|
+
### DialogModal with Simple API
|
|
116
231
|
|
|
117
232
|
```tsx
|
|
118
|
-
|
|
119
|
-
|
|
233
|
+
import { DialogModal } from "@fpkit/acss";
|
|
234
|
+
|
|
235
|
+
function SimpleExample() {
|
|
120
236
|
return (
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
237
|
+
<DialogModal
|
|
238
|
+
dialogTitle="Subscribe to Newsletter"
|
|
239
|
+
btnLabel="Subscribe"
|
|
240
|
+
btnSize="lg"
|
|
241
|
+
onConfirm={async () => {
|
|
242
|
+
await subscribe();
|
|
243
|
+
}}
|
|
244
|
+
confirmLabel="Sign Up"
|
|
245
|
+
cancelLabel="Maybe Later"
|
|
246
|
+
>
|
|
247
|
+
<p>Get weekly updates and exclusive content!</p>
|
|
248
|
+
<input type="email" placeholder="your@email.com" />
|
|
249
|
+
</DialogModal>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Custom Styling
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
import { Dialog } from "@fpkit/acss";
|
|
258
|
+
import { useState } from "react";
|
|
259
|
+
|
|
260
|
+
function StyledExample() {
|
|
261
|
+
const [open, setOpen] = useState(false);
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<Dialog
|
|
265
|
+
isOpen={open}
|
|
266
|
+
onOpenChange={setOpen}
|
|
267
|
+
dialogTitle="Custom Styled Dialog"
|
|
268
|
+
className="my-custom-dialog"
|
|
269
|
+
styles={{ maxWidth: "600px", borderRadius: "1rem" }}
|
|
270
|
+
>
|
|
271
|
+
<p>This dialog has custom styles applied.</p>
|
|
124
272
|
</Dialog>
|
|
125
273
|
);
|
|
126
274
|
}
|
|
127
275
|
```
|
|
128
276
|
|
|
129
|
-
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Modal vs Non-Modal Behavior
|
|
280
|
+
|
|
281
|
+
### Modal Dialog (Default)
|
|
282
|
+
|
|
283
|
+
Uses `dialog.showModal()` which provides:
|
|
284
|
+
- ✅ Full-page overlay with backdrop
|
|
285
|
+
- ✅ Automatic focus trap (Tab cycles within dialog)
|
|
286
|
+
- ✅ Escape key closes dialog (native behavior)
|
|
287
|
+
- ✅ Backdrop click closes dialog
|
|
288
|
+
- ✅ Background becomes inert (non-interactive)
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
<Dialog
|
|
292
|
+
isOpen={open}
|
|
293
|
+
onOpenChange={setOpen}
|
|
294
|
+
dialogTitle="Modal Dialog"
|
|
295
|
+
>
|
|
296
|
+
Content here - page is blocked
|
|
297
|
+
</Dialog>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Inline Alert Dialog (`isAlertDialog={true}`)
|
|
301
|
+
|
|
302
|
+
Uses `dialog.show()` for non-modal inline alerts:
|
|
303
|
+
- ✅ Positioned inline in page flow
|
|
304
|
+
- ❌ No focus trap (page remains interactive)
|
|
305
|
+
- ❌ No escape key behavior
|
|
306
|
+
- ❌ No backdrop
|
|
307
|
+
- ✅ User can interact with page content
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
<Dialog
|
|
311
|
+
isOpen={showAlert}
|
|
312
|
+
onOpenChange={setShowAlert}
|
|
313
|
+
dialogTitle="Alert"
|
|
314
|
+
isAlertDialog={true}
|
|
315
|
+
>
|
|
316
|
+
Warning message - page stays interactive
|
|
317
|
+
</Dialog>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Accessibility Features
|
|
323
|
+
|
|
324
|
+
### WCAG 2.1 AA Compliance
|
|
325
|
+
|
|
326
|
+
#### ARIA Attributes
|
|
327
|
+
- `aria-labelledby` - Links dialog to title using unique ID
|
|
328
|
+
- `aria-describedby` - Links dialog to content description
|
|
329
|
+
- `aria-modal="true"` - Indicates modal dialogs
|
|
330
|
+
- `aria-label` - Custom accessible label support
|
|
331
|
+
- `role="dialog"` or `role="alertdialog"` - Semantic roles
|
|
332
|
+
|
|
333
|
+
#### Keyboard Navigation
|
|
334
|
+
- **Escape key** - Closes modal dialogs (native `<dialog>` behavior)
|
|
335
|
+
- **Tab** - Cycles through focusable elements within dialog (focus trap)
|
|
336
|
+
- **Enter/Space** - Activates buttons
|
|
337
|
+
- `:focus-visible` styles for keyboard users
|
|
338
|
+
|
|
339
|
+
#### Focus Management
|
|
340
|
+
- **Auto-focus** - Dialog close button receives focus when opened
|
|
341
|
+
- **Focus restoration** - Focus returns to trigger button when closed
|
|
342
|
+
- **Focus trap** - Tab navigation stays within modal dialog
|
|
343
|
+
|
|
344
|
+
#### Screen Reader Support
|
|
345
|
+
- Semantic HTML structure
|
|
346
|
+
- Proper heading hierarchy
|
|
347
|
+
- Clear button labels
|
|
348
|
+
- Accessible close button ("Close dialog")
|
|
349
|
+
|
|
350
|
+
### High Contrast Mode
|
|
351
|
+
|
|
352
|
+
```scss
|
|
353
|
+
@media (prefers-contrast: high) {
|
|
354
|
+
dialog {
|
|
355
|
+
--dialog-border-color: currentColor;
|
|
356
|
+
--dialog-border-width: 0.125rem;
|
|
357
|
+
--dialog-focus-width: 0.1875rem;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Styling & Customization
|
|
365
|
+
|
|
366
|
+
### CSS Custom Properties
|
|
367
|
+
|
|
368
|
+
Override these variables to customize the dialog appearance:
|
|
369
|
+
|
|
370
|
+
```css
|
|
371
|
+
:root {
|
|
372
|
+
/* Dimensions */
|
|
373
|
+
--dialog-min-w: max(20rem, 80%);
|
|
374
|
+
--dialog-gap: 0.625rem;
|
|
375
|
+
--dialog-padding: 1.5rem;
|
|
376
|
+
--dialog-padding-inline: 1rem;
|
|
377
|
+
|
|
378
|
+
/* Borders */
|
|
379
|
+
--dialog-border-color: lightgray;
|
|
380
|
+
--dialog-border-width: thin;
|
|
381
|
+
--dialog-border-radius: var(--border-radius);
|
|
382
|
+
|
|
383
|
+
/* Buttons */
|
|
384
|
+
--dialog-button-bg: transparent;
|
|
385
|
+
--dialog-button-hover-bg: whitesmoke;
|
|
386
|
+
--dialog-close-color: gray;
|
|
387
|
+
|
|
388
|
+
/* Focus (Accessibility) */
|
|
389
|
+
--dialog-focus-color: #0066cc;
|
|
390
|
+
--dialog-focus-width: 0.125rem;
|
|
391
|
+
--dialog-focus-offset: 0.125rem;
|
|
392
|
+
|
|
393
|
+
/* Layout */
|
|
394
|
+
--dialog-display: flex;
|
|
395
|
+
--dialog-flex-direction: column;
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Example: Custom Theme
|
|
400
|
+
|
|
401
|
+
```scss
|
|
402
|
+
// Dark theme dialog
|
|
403
|
+
.dark-theme-dialog {
|
|
404
|
+
--dialog-border-color: #444;
|
|
405
|
+
--dialog-button-hover-bg: #333;
|
|
406
|
+
--dialog-close-color: #ccc;
|
|
407
|
+
background: #1a1a1a;
|
|
408
|
+
color: #fff;
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## TypeScript Support
|
|
415
|
+
|
|
416
|
+
### Import Types
|
|
417
|
+
|
|
418
|
+
```tsx
|
|
419
|
+
import type {
|
|
420
|
+
DialogProps,
|
|
421
|
+
DialogModalProps,
|
|
422
|
+
DialogHeaderProps,
|
|
423
|
+
DialogFooterProps,
|
|
424
|
+
} from "@fpkit/acss";
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Type Definitions
|
|
428
|
+
|
|
429
|
+
All components are fully typed with comprehensive JSDoc comments:
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
interface DialogProps extends BaseDialogProps {
|
|
433
|
+
isOpen: boolean;
|
|
434
|
+
onOpenChange: (open: boolean) => void;
|
|
435
|
+
isAlertDialog?: boolean;
|
|
436
|
+
onClose?: () => void; // Deprecated
|
|
437
|
+
onConfirm?: () => void | Promise<void>;
|
|
438
|
+
confirmLabel?: string;
|
|
439
|
+
cancelLabel?: string;
|
|
440
|
+
hideFooter?: boolean;
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Migration Guide
|
|
130
447
|
|
|
131
|
-
|
|
132
|
-
customizable properties using CSS variables.
|
|
448
|
+
### Upgrading from Old API
|
|
133
449
|
|
|
134
|
-
|
|
450
|
+
**Before (Old API):**
|
|
451
|
+
```tsx
|
|
452
|
+
<Dialog
|
|
453
|
+
showDialog={isOpen}
|
|
454
|
+
onClose={() => setIsOpen(false)}
|
|
455
|
+
dialogTitle="Old API"
|
|
456
|
+
>
|
|
457
|
+
Content
|
|
458
|
+
</Dialog>
|
|
459
|
+
```
|
|
135
460
|
|
|
136
|
-
|
|
461
|
+
**After (New API):**
|
|
462
|
+
```tsx
|
|
463
|
+
<Dialog
|
|
464
|
+
isOpen={isOpen}
|
|
465
|
+
onOpenChange={setIsOpen}
|
|
466
|
+
dialogTitle="New API"
|
|
467
|
+
>
|
|
468
|
+
Content
|
|
469
|
+
</Dialog>
|
|
470
|
+
```
|
|
137
471
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
472
|
+
**Backward Compatible (Temporary):**
|
|
473
|
+
```tsx
|
|
474
|
+
<Dialog
|
|
475
|
+
isOpen={isOpen}
|
|
476
|
+
onOpenChange={setIsOpen}
|
|
477
|
+
onClose={() => console.log('Still works!')} // Deprecated but functional
|
|
478
|
+
dialogTitle="Hybrid"
|
|
479
|
+
>
|
|
480
|
+
Content
|
|
481
|
+
</Dialog>
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Breaking Changes
|
|
142
485
|
|
|
143
|
-
|
|
486
|
+
| Old Prop | New Prop | Migration |
|
|
487
|
+
|----------|----------|-----------|
|
|
488
|
+
| `showDialog` | `isOpen` | Rename prop |
|
|
489
|
+
| `onClose` | `onOpenChange` | Change signature from `() => void` to `(open: boolean) => void` |
|
|
490
|
+
| `dialogId` | *(removed)* | Use auto-generated IDs via `useId()` |
|
|
144
491
|
|
|
145
|
-
|
|
146
|
-
- `--dialog-border-width`: Width of dialog border (thin)
|
|
147
|
-
- `--dialog-border-style`: Style of dialog border (solid)
|
|
148
|
-
- `--dialog-border-radius`: Border radius of dialog corners (0.5rem)
|
|
492
|
+
---
|
|
149
493
|
|
|
150
|
-
|
|
494
|
+
## Best Practices
|
|
151
495
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
496
|
+
### ✅ Do
|
|
497
|
+
|
|
498
|
+
```tsx
|
|
499
|
+
// Use controlled pattern for complex flows
|
|
500
|
+
const [step, setStep] = useState(1);
|
|
501
|
+
<Dialog isOpen={step === 2} onOpenChange={(open) => !open && setStep(1)}>
|
|
502
|
+
Multi-step content
|
|
503
|
+
</Dialog>
|
|
158
504
|
|
|
159
|
-
|
|
505
|
+
// Provide clear button labels
|
|
506
|
+
<Dialog confirmLabel="Delete Forever" cancelLabel="Keep Item">
|
|
160
507
|
|
|
161
|
-
|
|
162
|
-
|
|
508
|
+
// Use DialogModal for simple cases
|
|
509
|
+
<DialogModal dialogTitle="Quick Action" btnLabel="Go">
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### ❌ Don't
|
|
513
|
+
|
|
514
|
+
```tsx
|
|
515
|
+
// Don't manage state twice
|
|
516
|
+
const [open, setOpen] = useState(false);
|
|
517
|
+
<Dialog isOpen={open} onOpenChange={setOpen} onClose={() => setOpen(false)}>
|
|
518
|
+
// onClose is redundant with onOpenChange
|
|
519
|
+
|
|
520
|
+
// Don't use inline alerts for critical actions
|
|
521
|
+
<Dialog isAlertDialog={true} onConfirm={deleteAllData}>
|
|
522
|
+
// Use modal for destructive actions!
|
|
523
|
+
|
|
524
|
+
// Don't skip ARIA labels for custom content
|
|
525
|
+
<Dialog hideFooter>
|
|
526
|
+
<button>Close</button> {/* Missing aria-label */}
|
|
527
|
+
</Dialog>
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Performance Considerations
|
|
533
|
+
|
|
534
|
+
The dialog component is optimized with:
|
|
535
|
+
- ✅ `React.memo` on subcomponents (DialogHeader, DialogFooter)
|
|
536
|
+
- ✅ `useCallback` for event handlers
|
|
537
|
+
- ✅ Minimal re-renders via proper dependency arrays
|
|
538
|
+
- ✅ Native `<dialog>` API (no JavaScript focus trap)
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Browser Support
|
|
543
|
+
|
|
544
|
+
Requires browsers with native `<dialog>` element support:
|
|
545
|
+
- ✅ Chrome 37+
|
|
546
|
+
- ✅ Edge 79+
|
|
547
|
+
- ✅ Firefox 98+
|
|
548
|
+
- ✅ Safari 15.4+
|
|
549
|
+
|
|
550
|
+
For older browsers, consider using a polyfill like [`dialog-polyfill`](https://github.com/GoogleChrome/dialog-polyfill).
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## Testing
|
|
555
|
+
|
|
556
|
+
The dialog component has comprehensive test coverage:
|
|
557
|
+
- ✓ 24 tests covering all scenarios
|
|
558
|
+
- ✓ Controlled vs uncontrolled behavior
|
|
559
|
+
- ✓ Modal vs non-modal rendering
|
|
560
|
+
- ✓ ARIA attributes validation
|
|
561
|
+
- ✓ Focus restoration
|
|
562
|
+
- ✓ Keyboard interactions
|
|
563
|
+
|
|
564
|
+
Run tests:
|
|
565
|
+
```bash
|
|
566
|
+
npm test -- dialog.test.tsx
|
|
567
|
+
```
|
|
163
568
|
|
|
164
|
-
|
|
569
|
+
---
|
|
165
570
|
|
|
166
|
-
|
|
571
|
+
## Related Components
|
|
167
572
|
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
573
|
+
- **DialogHeader** - Header with title and close button (internal)
|
|
574
|
+
- **DialogFooter** - Footer with confirm/cancel buttons (internal)
|
|
575
|
+
- **Button** - Used for trigger and action buttons
|
|
576
|
+
- **Icon** - Used for close button icon
|
|
171
577
|
|
|
172
|
-
|
|
578
|
+
---
|
|
173
579
|
|
|
174
|
-
|
|
175
|
-
- Contains title (h3) and close button
|
|
176
|
-
- Custom button styling with hover states
|
|
580
|
+
## Resources
|
|
177
581
|
|
|
178
|
-
|
|
582
|
+
- [MDN: `<dialog>` Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog)
|
|
583
|
+
- [ARIA: Dialog Role](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)
|
|
584
|
+
- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
|
|
179
585
|
|
|
180
|
-
|
|
181
|
-
- Uses flex layout with left alignment
|
|
182
|
-
- Includes gap spacing between buttons
|
|
586
|
+
---
|
|
183
587
|
|
|
184
|
-
##
|
|
588
|
+
## License
|
|
185
589
|
|
|
186
|
-
|
|
187
|
-
stylesheet.
|
|
590
|
+
Part of the `@fpkit/acss` component library.
|