@fpkit/acss 0.5.12 → 0.6.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.
- package/README.md +89 -0
- package/libs/chunk-2NRIP6RB.cjs +17 -0
- package/libs/chunk-2NRIP6RB.cjs.map +1 -0
- package/libs/chunk-33PNJ4LO.cjs +15 -0
- package/libs/chunk-33PNJ4LO.cjs.map +1 -0
- package/libs/chunk-4BZKFPEC.cjs +17 -0
- package/libs/chunk-4BZKFPEC.cjs.map +1 -0
- package/libs/{chunk-O6QZBB6G.js → chunk-5QD3DWFI.js} +5 -5
- package/libs/chunk-5QD3DWFI.js.map +1 -0
- package/libs/chunk-6SAHIYCZ.js +7 -0
- package/libs/chunk-6SAHIYCZ.js.map +1 -0
- package/libs/{chunk-KKLTUJFB.cjs → chunk-6WTC4JXH.cjs} +5 -5
- package/libs/chunk-6WTC4JXH.cjs.map +1 -0
- package/libs/chunk-75QHTLFO.js +7 -0
- package/libs/chunk-75QHTLFO.js.map +1 -0
- package/libs/{chunk-YWOYVRFT.js → chunk-7XPFW7CB.js} +3 -3
- package/libs/chunk-BFK62VX5.js +5 -0
- package/libs/chunk-BFK62VX5.js.map +1 -0
- package/libs/{chunk-ICCKQ2GC.cjs → chunk-DKTHCQ5P.cjs} +4 -4
- package/libs/{chunk-6TE5QEVE.cjs → chunk-E2AJURUW.cjs} +3 -3
- package/libs/chunk-E2AJURUW.cjs.map +1 -0
- package/libs/chunk-ENTCUJ3A.cjs +13 -0
- package/libs/chunk-ENTCUJ3A.cjs.map +1 -0
- package/libs/chunk-F5EYMVQM.js +10 -0
- package/libs/chunk-F5EYMVQM.js.map +1 -0
- package/libs/chunk-FVROL3V5.js +9 -0
- package/libs/chunk-FVROL3V5.js.map +1 -0
- package/libs/chunk-GT77BX4L.cjs +17 -0
- package/libs/chunk-GT77BX4L.cjs.map +1 -0
- package/libs/chunk-GUJSMQ3V.cjs +16 -0
- package/libs/chunk-GUJSMQ3V.cjs.map +1 -0
- package/libs/chunk-HHLNOC5T.js +7 -0
- package/libs/chunk-HHLNOC5T.js.map +1 -0
- package/libs/chunk-HRRHPLER.js +8 -0
- package/libs/chunk-HRRHPLER.js.map +1 -0
- package/libs/chunk-IEB64SWY.js +8 -0
- package/libs/chunk-IEB64SWY.js.map +1 -0
- package/libs/{chunk-LIQJ7ZZR.js → chunk-IQ76HGVP.js} +2 -2
- package/libs/chunk-IRLFZ3OL.js +9 -0
- package/libs/chunk-IRLFZ3OL.js.map +1 -0
- package/libs/chunk-KK47SYZI.js +8 -0
- package/libs/chunk-KK47SYZI.js.map +1 -0
- package/libs/chunk-O3JIHC5M.cjs +15 -0
- package/libs/chunk-O3JIHC5M.cjs.map +1 -0
- package/libs/chunk-O5XAJ7BY.cjs +18 -0
- package/libs/chunk-O5XAJ7BY.cjs.map +1 -0
- package/libs/chunk-OVWLQYMK.js +10 -0
- package/libs/chunk-OVWLQYMK.js.map +1 -0
- package/libs/chunk-PNWIRCG3.cjs +7 -0
- package/libs/chunk-PNWIRCG3.cjs.map +1 -0
- package/libs/chunk-QVW6W76L.cjs +18 -0
- package/libs/chunk-QVW6W76L.cjs.map +1 -0
- package/libs/chunk-T4T6GWYQ.cjs +17 -0
- package/libs/chunk-T4T6GWYQ.cjs.map +1 -0
- package/libs/chunk-TON2YGMD.cjs +9 -0
- package/libs/chunk-TON2YGMD.cjs.map +1 -0
- package/libs/chunk-UEPAWMDF.js +8 -0
- package/libs/chunk-UEPAWMDF.js.map +1 -0
- package/libs/{chunk-LT5KZ2QW.cjs → chunk-US2I5GI7.cjs} +3 -3
- package/libs/{chunk-E3XP6BEX.cjs → chunk-W2UIN7EV.cjs} +3 -3
- package/libs/chunk-W5TKWBFC.cjs +18 -0
- package/libs/chunk-W5TKWBFC.cjs.map +1 -0
- package/libs/chunk-WXBFBWYF.cjs +16 -0
- package/libs/chunk-WXBFBWYF.cjs.map +1 -0
- package/libs/chunk-X3JCTEPD.js +11 -0
- package/libs/chunk-X3JCTEPD.js.map +1 -0
- package/libs/chunk-X5LGFCWG.js +9 -0
- package/libs/chunk-X5LGFCWG.js.map +1 -0
- package/libs/{chunk-5M57K4SW.js → chunk-Y2PFDELK.js} +2 -2
- package/libs/chunk-ZFJ4U45S.js +10 -0
- package/libs/chunk-ZFJ4U45S.js.map +1 -0
- package/libs/{component-props-a8a2f97e.d.ts → component-props-67d978a2.d.ts} +4 -4
- 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 +275 -36
- package/libs/components/breadcrumbs/breadcrumb.d.ts +275 -36
- package/libs/components/breadcrumbs/breadcrumb.js +3 -3
- package/libs/components/button.cjs +6 -4
- package/libs/components/button.d.cts +97 -4
- package/libs/components/button.d.ts +97 -4
- package/libs/components/button.js +4 -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 +278 -34
- package/libs/components/card.d.ts +278 -34
- 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 +9 -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 +7 -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 +16 -7
- package/libs/components/form/fields.d.ts +16 -7
- package/libs/components/form/fields.js +2 -2
- package/libs/components/form/inputs.cjs +6 -4
- package/libs/components/form/inputs.d.cts +50 -2
- package/libs/components/form/inputs.d.ts +50 -2
- package/libs/components/form/inputs.js +4 -2
- package/libs/components/form/textarea.cjs +5 -4
- package/libs/components/form/textarea.d.cts +32 -23
- package/libs/components/form/textarea.d.ts +32 -23
- package/libs/components/form/textarea.js +3 -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 +183 -39
- package/libs/components/icons/icon.d.ts +183 -39
- 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.css +1 -1
- package/libs/components/link/link.css.map +1 -1
- package/libs/components/link/link.d.cts +3 -19
- package/libs/components/link/link.d.ts +3 -19
- package/libs/components/link/link.js +2 -2
- package/libs/components/link/link.min.css +2 -2
- package/libs/components/list/list.cjs +5 -5
- package/libs/components/list/list.css +1 -0
- package/libs/components/list/list.css.map +1 -0
- package/libs/components/list/list.d.cts +120 -33
- package/libs/components/list/list.d.ts +120 -33
- package/libs/components/list/list.js +2 -2
- package/libs/components/list/list.min.css +3 -0
- package/libs/components/modal.cjs +6 -4
- package/libs/components/modal.d.cts +8 -8
- package/libs/components/modal.d.ts +8 -8
- package/libs/components/modal.js +5 -3
- package/libs/components/nav/nav.cjs +7 -7
- package/libs/components/nav/nav.css +1 -1
- package/libs/components/nav/nav.css.map +1 -1
- package/libs/components/nav/nav.d.cts +550 -34
- package/libs/components/nav/nav.d.ts +550 -34
- package/libs/components/nav/nav.js +3 -3
- package/libs/components/nav/nav.min.css +2 -2
- package/libs/components/popover/popover.d.cts +5 -5
- package/libs/components/popover/popover.d.ts +5 -5
- package/libs/components/tables/table.cjs +5 -5
- package/libs/components/tables/table.d.cts +8 -8
- package/libs/components/tables/table.d.ts +8 -8
- package/libs/components/tables/table.js +2 -2
- package/libs/components/tag/tag.css +1 -1
- package/libs/components/tag/tag.css.map +1 -1
- package/libs/components/tag/tag.min.css +2 -2
- package/libs/components/text/text.cjs +5 -5
- package/libs/components/text/text.d.cts +5 -5
- package/libs/components/text/text.d.ts +5 -5
- package/libs/components/text/text.js +2 -2
- package/libs/form.types-d25ebfac.d.ts +233 -0
- package/libs/heading-7446cb46.d.ts +250 -0
- package/libs/hooks.cjs +12 -0
- package/libs/hooks.d.cts +140 -1
- package/libs/hooks.d.ts +140 -1
- package/libs/hooks.js +4 -0
- package/libs/icons.cjs +3 -3
- package/libs/icons.d.cts +2 -2
- package/libs/icons.d.ts +2 -2
- package/libs/icons.js +2 -2
- package/libs/index.cjs +117 -94
- 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 +834 -61
- package/libs/index.d.ts +834 -61
- package/libs/index.js +36 -22
- package/libs/index.js.map +1 -1
- package/libs/link-5192f411.d.ts +323 -0
- package/libs/list.types-d26de310.d.ts +245 -0
- package/libs/ui-d01b50d4.d.ts +289 -0
- package/package.json +4 -87
- 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 +110 -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 +430 -170
- package/src/components/buttons/README.mdx +102 -1
- package/src/components/buttons/button.scss +34 -31
- package/src/components/buttons/button.stories.tsx +141 -0
- package/src/components/buttons/button.tsx +82 -52
- 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-a11y-review.md +653 -0
- 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/form/README.mdx +725 -43
- package/src/components/form/WCAG-REVIEW.md +654 -0
- package/src/components/form/fields.tsx +10 -1
- package/src/components/form/form.stories.tsx +604 -23
- package/src/components/form/form.tsx +204 -63
- package/src/components/form/form.types.ts +378 -0
- package/src/components/form/input.stories.tsx +71 -3
- package/src/components/form/inputs.tsx +159 -67
- package/src/components/form/select.tsx +122 -66
- package/src/components/form/textarea.tsx +120 -73
- package/src/components/fp.tsx +86 -11
- 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/link/README.mdx +923 -0
- package/src/components/link/link.scss +79 -26
- package/src/components/link/link.stories.tsx +383 -30
- package/src/components/link/link.test.tsx +677 -0
- package/src/components/link/link.tsx +163 -57
- package/src/components/link/link.types.ts +261 -0
- package/src/components/list/README.mdx +764 -0
- package/src/components/list/list.scss +285 -0
- package/src/components/list/list.stories.tsx +514 -27
- package/src/components/list/list.test.tsx +554 -0
- package/src/components/list/list.tsx +153 -51
- package/src/components/list/list.types.ts +255 -0
- package/src/components/nav/ACCESSIBILITY.md +649 -0
- package/src/components/nav/README.mdx +782 -0
- package/src/components/nav/nav.scss +32 -1
- package/src/components/nav/nav.stories.tsx +44 -6
- package/src/components/nav/nav.tsx +302 -51
- package/src/components/nav/nav.types.ts +308 -0
- package/src/components/tag/README.mdx +426 -0
- package/src/components/tag/tag.scss +101 -27
- package/src/components/tag/tag.stories.tsx +384 -10
- package/src/components/tag/tag.test.tsx +210 -0
- package/src/components/tag/tag.tsx +106 -9
- package/src/components/tag/tag.types.ts +107 -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 +274 -18
- package/src/components/word-count/README.md +240 -0
- package/src/hooks/use-disabled-state.test.tsx +536 -0
- package/src/hooks/use-disabled-state.ts +246 -0
- package/src/hooks/useDisabledState.md +393 -0
- package/src/hooks.ts +7 -0
- package/src/index.scss +2 -0
- package/src/index.ts +12 -3
- package/src/sass/_globals.scss +2 -7
- package/src/sass/_properties.scss +1 -0
- package/src/styles/alert/alert.css +92 -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 +691 -128
- package/src/styles/index.css.map +1 -1
- package/src/styles/link/link.css +45 -28
- package/src/styles/link/link.css.map +1 -1
- package/src/styles/list/list.css +214 -0
- package/src/styles/list/list.css.map +1 -0
- package/src/styles/nav/nav.css +21 -1
- package/src/styles/nav/nav.css.map +1 -1
- package/src/styles/tag/tag.css +113 -35
- package/src/styles/tag/tag.css.map +1 -1
- package/src/styles/utilities/_disabled.scss +58 -0
- package/src/test/setup.d.ts +9 -0
- package/src/test/setup.ts +53 -1
- package/src/types/shared.ts +43 -6
- package/src/utils/accessibility.ts +109 -0
- package/libs/chunk-5ZM4XL44.js +0 -8
- package/libs/chunk-5ZM4XL44.js.map +0 -1
- package/libs/chunk-6BVXFW7U.cjs +0 -15
- package/libs/chunk-6BVXFW7U.cjs.map +0 -1
- 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-BHRQBJRY.js +0 -8
- package/libs/chunk-BHRQBJRY.js.map +0 -1
- package/libs/chunk-BIP2NY53.js +0 -8
- package/libs/chunk-BIP2NY53.js.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-DV56L5YX.cjs +0 -18
- package/libs/chunk-DV56L5YX.cjs.map +0 -1
- package/libs/chunk-ECLD37WN.cjs +0 -16
- package/libs/chunk-ECLD37WN.cjs.map +0 -1
- package/libs/chunk-EQ67LF46.js +0 -9
- package/libs/chunk-EQ67LF46.js.map +0 -1
- package/libs/chunk-HYBZBN4G.js +0 -8
- package/libs/chunk-HYBZBN4G.js.map +0 -1
- package/libs/chunk-IYUN2EW3.cjs +0 -15
- package/libs/chunk-IYUN2EW3.cjs.map +0 -1
- package/libs/chunk-KKLTUJFB.cjs.map +0 -1
- package/libs/chunk-LHVJKDMA.cjs +0 -15
- package/libs/chunk-LHVJKDMA.cjs.map +0 -1
- package/libs/chunk-LL7HTLMS.cjs +0 -15
- package/libs/chunk-LL7HTLMS.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-NHYXGV3L.js +0 -8
- package/libs/chunk-NHYXGV3L.js.map +0 -1
- package/libs/chunk-O6QZBB6G.js.map +0 -1
- package/libs/chunk-P7TTEYCD.js +0 -7
- package/libs/chunk-P7TTEYCD.js.map +0 -1
- package/libs/chunk-PPOOBUOS.js +0 -8
- package/libs/chunk-PPOOBUOS.js.map +0 -1
- package/libs/chunk-QCMV4VQZ.js +0 -8
- package/libs/chunk-QCMV4VQZ.js.map +0 -1
- package/libs/chunk-QVV34QEH.cjs +0 -32
- package/libs/chunk-QVV34QEH.cjs.map +0 -1
- package/libs/chunk-S7BABR7Z.cjs +0 -13
- package/libs/chunk-S7BABR7Z.cjs.map +0 -1
- package/libs/chunk-SXVZSWX6.js +0 -11
- package/libs/chunk-SXVZSWX6.js.map +0 -1
- package/libs/chunk-X3EVB7VS.cjs +0 -15
- package/libs/chunk-X3EVB7VS.cjs.map +0 -1
- package/libs/inputs-f3a216db.d.ts +0 -45
- 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-YWOYVRFT.js.map → chunk-7XPFW7CB.js.map} +0 -0
- /package/libs/{chunk-ICCKQ2GC.cjs.map → chunk-DKTHCQ5P.cjs.map} +0 -0
- /package/libs/{chunk-LIQJ7ZZR.js.map → chunk-IQ76HGVP.js.map} +0 -0
- /package/libs/{chunk-LT5KZ2QW.cjs.map → chunk-US2I5GI7.cjs.map} +0 -0
- /package/libs/{chunk-E3XP6BEX.cjs.map → chunk-W2UIN7EV.cjs.map} +0 -0
- /package/libs/{chunk-5M57K4SW.js.map → chunk-Y2PFDELK.js.map} +0 -0
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import React, { useRef, useEffect } from "react";
|
|
3
|
+
import UI from "./ui";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The UI component is a polymorphic React primitive that can render as any HTML element
|
|
7
|
+
* while maintaining full TypeScript type safety. It serves as the foundation for 25+
|
|
8
|
+
* components across the fpkit library.
|
|
9
|
+
*
|
|
10
|
+
* ## Key Features
|
|
11
|
+
* - Polymorphic rendering with the `as` prop
|
|
12
|
+
* - Full TypeScript type safety for element-specific props
|
|
13
|
+
* - Style merging with `defaultStyles` and `styles`
|
|
14
|
+
* - Proper ref forwarding with typed refs
|
|
15
|
+
* - Zero runtime overhead
|
|
16
|
+
*/
|
|
17
|
+
const meta = {
|
|
18
|
+
title: "FP.UI",
|
|
19
|
+
component: UI,
|
|
20
|
+
tags: ["autodocs", "primitive"],
|
|
21
|
+
parameters: {
|
|
22
|
+
docs: {
|
|
23
|
+
description: {
|
|
24
|
+
component:
|
|
25
|
+
"A foundational polymorphic component that can render as any HTML element with complete type safety.",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
argTypes: {
|
|
30
|
+
as: {
|
|
31
|
+
control: "select",
|
|
32
|
+
options: [
|
|
33
|
+
"div",
|
|
34
|
+
"span",
|
|
35
|
+
"button",
|
|
36
|
+
"a",
|
|
37
|
+
"section",
|
|
38
|
+
"article",
|
|
39
|
+
"nav",
|
|
40
|
+
"main",
|
|
41
|
+
"header",
|
|
42
|
+
"footer",
|
|
43
|
+
],
|
|
44
|
+
description: "The HTML element type to render",
|
|
45
|
+
table: {
|
|
46
|
+
type: { summary: "React.ElementType" },
|
|
47
|
+
defaultValue: { summary: "div" },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
styles: {
|
|
51
|
+
control: "object",
|
|
52
|
+
description: "Inline styles to apply (overrides defaultStyles)",
|
|
53
|
+
table: {
|
|
54
|
+
type: { summary: "React.CSSProperties" },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
classes: {
|
|
58
|
+
control: "text",
|
|
59
|
+
description: "CSS class names to apply",
|
|
60
|
+
table: {
|
|
61
|
+
type: { summary: "string" },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
children: {
|
|
65
|
+
control: "text",
|
|
66
|
+
description: "Content to render inside the component",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
} satisfies Meta<typeof UI>;
|
|
70
|
+
|
|
71
|
+
export default meta;
|
|
72
|
+
type Story = StoryObj<typeof meta>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Default story showing the UI component rendering as a div with basic styling.
|
|
76
|
+
*/
|
|
77
|
+
export const Default: Story = {
|
|
78
|
+
args: {
|
|
79
|
+
children: "Default UI Component (renders as div)",
|
|
80
|
+
styles: {
|
|
81
|
+
padding: "1rem",
|
|
82
|
+
backgroundColor: "#f0f0f0",
|
|
83
|
+
borderRadius: "0.25rem",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Demonstrates the UI component rendering as a button with button-specific props.
|
|
90
|
+
*/
|
|
91
|
+
export const AsButton: Story = {
|
|
92
|
+
args: {
|
|
93
|
+
as: "button",
|
|
94
|
+
children: "Click Me",
|
|
95
|
+
styles: {
|
|
96
|
+
padding: "0.75rem 1.5rem",
|
|
97
|
+
backgroundColor: "#007bff",
|
|
98
|
+
color: "white",
|
|
99
|
+
border: "none",
|
|
100
|
+
borderRadius: "0.25rem",
|
|
101
|
+
cursor: "pointer",
|
|
102
|
+
fontSize: "1rem",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Demonstrates the UI component rendering as a span element.
|
|
109
|
+
*/
|
|
110
|
+
export const AsSpan: Story = {
|
|
111
|
+
args: {
|
|
112
|
+
as: "span",
|
|
113
|
+
children: "Inline Span Element",
|
|
114
|
+
styles: {
|
|
115
|
+
fontWeight: "bold",
|
|
116
|
+
color: "#28a745",
|
|
117
|
+
padding: "0.25rem 0.5rem",
|
|
118
|
+
backgroundColor: "#d4edda",
|
|
119
|
+
borderRadius: "0.25rem",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Demonstrates the UI component rendering as an anchor link with href.
|
|
126
|
+
*/
|
|
127
|
+
export const AsAnchor: Story = {
|
|
128
|
+
args: {
|
|
129
|
+
as: "a",
|
|
130
|
+
href: "https://example.com",
|
|
131
|
+
target: "_blank",
|
|
132
|
+
rel: "noopener noreferrer",
|
|
133
|
+
children: "External Link",
|
|
134
|
+
styles: {
|
|
135
|
+
color: "#007bff",
|
|
136
|
+
textDecoration: "underline",
|
|
137
|
+
padding: "0.5rem",
|
|
138
|
+
display: "inline-block",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Demonstrates the UI component rendering as a semantic section element.
|
|
145
|
+
*/
|
|
146
|
+
export const AsSection: Story = {
|
|
147
|
+
args: {
|
|
148
|
+
as: "section",
|
|
149
|
+
children: (
|
|
150
|
+
<>
|
|
151
|
+
<h2 style={{ marginTop: 0 }}>Section Title</h2>
|
|
152
|
+
<p>
|
|
153
|
+
This demonstrates the UI component rendering as a semantic section
|
|
154
|
+
element.
|
|
155
|
+
</p>
|
|
156
|
+
</>
|
|
157
|
+
),
|
|
158
|
+
styles: {
|
|
159
|
+
padding: "1.5rem",
|
|
160
|
+
backgroundColor: "#fff3cd",
|
|
161
|
+
border: "1px solid #ffc107",
|
|
162
|
+
borderRadius: "0.25rem",
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Shows how the `styles` prop applies inline styles.
|
|
169
|
+
*/
|
|
170
|
+
export const WithStyles: Story = {
|
|
171
|
+
args: {
|
|
172
|
+
children: "Styled with inline CSS",
|
|
173
|
+
styles: {
|
|
174
|
+
padding: "1rem 2rem",
|
|
175
|
+
backgroundColor: "#6f42c1",
|
|
176
|
+
color: "white",
|
|
177
|
+
borderRadius: "0.5rem",
|
|
178
|
+
fontWeight: "bold",
|
|
179
|
+
textAlign: "center",
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Shows how the `classes` prop applies CSS class names.
|
|
186
|
+
*/
|
|
187
|
+
export const WithClasses: Story = {
|
|
188
|
+
args: {
|
|
189
|
+
children: "Element with CSS classes",
|
|
190
|
+
classes: "custom-class another-class",
|
|
191
|
+
styles: {
|
|
192
|
+
padding: "1rem",
|
|
193
|
+
border: "2px dashed #17a2b8",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Demonstrates how `styles` overrides `defaultStyles`.
|
|
200
|
+
*/
|
|
201
|
+
export const StyleMerging: Story = {
|
|
202
|
+
args: {
|
|
203
|
+
children: "Style Merging Example",
|
|
204
|
+
defaultStyles: {
|
|
205
|
+
padding: "1rem",
|
|
206
|
+
backgroundColor: "lightblue",
|
|
207
|
+
color: "blue",
|
|
208
|
+
fontSize: "1rem",
|
|
209
|
+
borderRadius: "0.25rem",
|
|
210
|
+
},
|
|
211
|
+
styles: {
|
|
212
|
+
color: "red", // This overrides the blue color
|
|
213
|
+
fontWeight: "bold", // This is added
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
parameters: {
|
|
217
|
+
docs: {
|
|
218
|
+
description: {
|
|
219
|
+
story:
|
|
220
|
+
"The `defaultStyles` provide base styling (blue text, light blue background), while `styles` overrides specific properties (text becomes red and bold).",
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Demonstrates using CSS custom properties for theming.
|
|
228
|
+
*/
|
|
229
|
+
export const CSSCustomProperties: Story = {
|
|
230
|
+
args: {
|
|
231
|
+
children: "Themed with CSS Variables",
|
|
232
|
+
styles: {
|
|
233
|
+
"--primary-color": "#28a745",
|
|
234
|
+
"--secondary-color": "#ffffff",
|
|
235
|
+
padding: "1rem 1.5rem",
|
|
236
|
+
backgroundColor: "var(--primary-color)",
|
|
237
|
+
color: "var(--secondary-color)",
|
|
238
|
+
borderRadius: "0.25rem",
|
|
239
|
+
} as React.CSSProperties,
|
|
240
|
+
},
|
|
241
|
+
parameters: {
|
|
242
|
+
docs: {
|
|
243
|
+
description: {
|
|
244
|
+
story:
|
|
245
|
+
"CSS custom properties (variables) can be set dynamically through the styles prop for theming.",
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Demonstrates ref forwarding with proper typing.
|
|
253
|
+
*/
|
|
254
|
+
export const RefForwarding: Story = {
|
|
255
|
+
render: () => {
|
|
256
|
+
const RefExample = () => {
|
|
257
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
258
|
+
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
// Focus the button on mount
|
|
261
|
+
if (buttonRef.current) {
|
|
262
|
+
buttonRef.current.focus();
|
|
263
|
+
}
|
|
264
|
+
}, []);
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<UI
|
|
268
|
+
as="button"
|
|
269
|
+
ref={buttonRef}
|
|
270
|
+
styles={{
|
|
271
|
+
padding: "0.75rem 1.5rem",
|
|
272
|
+
backgroundColor: "#dc3545",
|
|
273
|
+
color: "white",
|
|
274
|
+
border: "none",
|
|
275
|
+
borderRadius: "0.25rem",
|
|
276
|
+
cursor: "pointer",
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
Auto-focused Button
|
|
280
|
+
</UI>
|
|
281
|
+
);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
return <RefExample />;
|
|
285
|
+
},
|
|
286
|
+
parameters: {
|
|
287
|
+
docs: {
|
|
288
|
+
description: {
|
|
289
|
+
story:
|
|
290
|
+
"The UI component forwards refs with proper typing. This button is automatically focused when mounted.",
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Example of building a Button component using UI as a primitive.
|
|
298
|
+
*/
|
|
299
|
+
export const ButtonPattern: Story = {
|
|
300
|
+
render: () => {
|
|
301
|
+
interface ButtonProps {
|
|
302
|
+
variant?: "primary" | "secondary" | "danger";
|
|
303
|
+
children: React.ReactNode;
|
|
304
|
+
onClick?: () => void;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const Button = ({
|
|
308
|
+
variant = "primary",
|
|
309
|
+
children,
|
|
310
|
+
...props
|
|
311
|
+
}: ButtonProps) => {
|
|
312
|
+
const variantStyles = {
|
|
313
|
+
primary: {
|
|
314
|
+
backgroundColor: "#007bff",
|
|
315
|
+
color: "white",
|
|
316
|
+
},
|
|
317
|
+
secondary: {
|
|
318
|
+
backgroundColor: "#6c757d",
|
|
319
|
+
color: "white",
|
|
320
|
+
},
|
|
321
|
+
danger: {
|
|
322
|
+
backgroundColor: "#dc3545",
|
|
323
|
+
color: "white",
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<UI
|
|
329
|
+
as="button"
|
|
330
|
+
defaultStyles={{
|
|
331
|
+
padding: "0.5rem 1rem",
|
|
332
|
+
border: "none",
|
|
333
|
+
borderRadius: "0.25rem",
|
|
334
|
+
cursor: "pointer",
|
|
335
|
+
fontSize: "1rem",
|
|
336
|
+
...variantStyles[variant],
|
|
337
|
+
}}
|
|
338
|
+
{...props}
|
|
339
|
+
>
|
|
340
|
+
{children}
|
|
341
|
+
</UI>
|
|
342
|
+
);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
<div style={{ display: "flex", gap: "1rem" }}>
|
|
347
|
+
<Button variant="primary">Primary</Button>
|
|
348
|
+
<Button variant="secondary">Secondary</Button>
|
|
349
|
+
<Button variant="danger">Danger</Button>
|
|
350
|
+
</div>
|
|
351
|
+
);
|
|
352
|
+
},
|
|
353
|
+
parameters: {
|
|
354
|
+
docs: {
|
|
355
|
+
description: {
|
|
356
|
+
story:
|
|
357
|
+
"This shows how to build a Button component with variants using UI as the primitive.",
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Example of building a Badge component using UI as a primitive.
|
|
365
|
+
*/
|
|
366
|
+
export const BadgePattern: Story = {
|
|
367
|
+
render: () => {
|
|
368
|
+
interface BadgeProps {
|
|
369
|
+
variant?: "info" | "success" | "warning" | "error";
|
|
370
|
+
children: React.ReactNode;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const Badge = ({ variant = "info", children, ...props }: BadgeProps) => {
|
|
374
|
+
const variantStyles = {
|
|
375
|
+
info: {
|
|
376
|
+
backgroundColor: "#d1ecf1",
|
|
377
|
+
color: "#0c5460",
|
|
378
|
+
},
|
|
379
|
+
success: {
|
|
380
|
+
backgroundColor: "#d4edda",
|
|
381
|
+
color: "#155724",
|
|
382
|
+
},
|
|
383
|
+
warning: {
|
|
384
|
+
backgroundColor: "#fff3cd",
|
|
385
|
+
color: "#856404",
|
|
386
|
+
},
|
|
387
|
+
error: {
|
|
388
|
+
backgroundColor: "#f8d7da",
|
|
389
|
+
color: "#721c24",
|
|
390
|
+
},
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<UI
|
|
395
|
+
as="span"
|
|
396
|
+
defaultStyles={{
|
|
397
|
+
display: "inline-block",
|
|
398
|
+
padding: "0.25rem 0.5rem",
|
|
399
|
+
fontSize: "0.75rem",
|
|
400
|
+
fontWeight: "bold",
|
|
401
|
+
borderRadius: "0.25rem",
|
|
402
|
+
...variantStyles[variant],
|
|
403
|
+
}}
|
|
404
|
+
{...props}
|
|
405
|
+
>
|
|
406
|
+
{children}
|
|
407
|
+
</UI>
|
|
408
|
+
);
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
return (
|
|
412
|
+
<div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
|
|
413
|
+
<Badge variant="info">Info</Badge>
|
|
414
|
+
<Badge variant="success">Success</Badge>
|
|
415
|
+
<Badge variant="warning">Warning</Badge>
|
|
416
|
+
<Badge variant="error">Error</Badge>
|
|
417
|
+
</div>
|
|
418
|
+
);
|
|
419
|
+
},
|
|
420
|
+
parameters: {
|
|
421
|
+
docs: {
|
|
422
|
+
description: {
|
|
423
|
+
story:
|
|
424
|
+
"This shows how to build a Badge component with variants using UI as the primitive.",
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Demonstrates TypeScript type safety - element-specific props are correctly typed.
|
|
432
|
+
*/
|
|
433
|
+
export const TypeSafeProps: Story = {
|
|
434
|
+
render: () => {
|
|
435
|
+
return (
|
|
436
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
|
|
437
|
+
{/* Button with disabled prop (only valid for buttons) */}
|
|
438
|
+
<UI
|
|
439
|
+
as="button"
|
|
440
|
+
disabled
|
|
441
|
+
styles={{
|
|
442
|
+
padding: "0.5rem 1rem",
|
|
443
|
+
backgroundColor: "#6c757d",
|
|
444
|
+
color: "white",
|
|
445
|
+
border: "none",
|
|
446
|
+
borderRadius: "0.25rem",
|
|
447
|
+
cursor: "not-allowed",
|
|
448
|
+
opacity: 0.6,
|
|
449
|
+
}}
|
|
450
|
+
>
|
|
451
|
+
Disabled Button
|
|
452
|
+
</UI>
|
|
453
|
+
|
|
454
|
+
{/* Anchor with href and target (only valid for anchors) */}
|
|
455
|
+
<UI
|
|
456
|
+
as="a"
|
|
457
|
+
href="https://github.com"
|
|
458
|
+
target="_blank"
|
|
459
|
+
rel="noopener noreferrer"
|
|
460
|
+
styles={{
|
|
461
|
+
color: "#007bff",
|
|
462
|
+
textDecoration: "none",
|
|
463
|
+
padding: "0.5rem",
|
|
464
|
+
}}
|
|
465
|
+
>
|
|
466
|
+
GitHub Link
|
|
467
|
+
</UI>
|
|
468
|
+
|
|
469
|
+
{/* Form with onSubmit (only valid for forms) */}
|
|
470
|
+
<UI
|
|
471
|
+
as="form"
|
|
472
|
+
onSubmit={(e: React.FormEvent) => {
|
|
473
|
+
e.preventDefault();
|
|
474
|
+
alert("Form submitted!");
|
|
475
|
+
}}
|
|
476
|
+
styles={{
|
|
477
|
+
padding: "1rem",
|
|
478
|
+
border: "1px solid #dee2e6",
|
|
479
|
+
borderRadius: "0.25rem",
|
|
480
|
+
}}
|
|
481
|
+
>
|
|
482
|
+
<button type="submit">Submit Form</button>
|
|
483
|
+
</UI>
|
|
484
|
+
</div>
|
|
485
|
+
);
|
|
486
|
+
},
|
|
487
|
+
parameters: {
|
|
488
|
+
docs: {
|
|
489
|
+
description: {
|
|
490
|
+
story:
|
|
491
|
+
"TypeScript ensures that only valid props for each element type are accepted. Try changing the `as` prop to see IntelliSense update!",
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Demonstrates accessible interactive elements with proper ARIA attributes.
|
|
499
|
+
* All examples pass WCAG 2.1 AA accessibility checks.
|
|
500
|
+
*/
|
|
501
|
+
export const AccessibleInteractiveElements: Story = {
|
|
502
|
+
render: function AccessibleInteractiveElementsStory() {
|
|
503
|
+
const [isExpanded, setIsExpanded] = React.useState(false);
|
|
504
|
+
|
|
505
|
+
return (
|
|
506
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
|
|
507
|
+
{/* Accessible button with aria-label for icon-only button */}
|
|
508
|
+
<div>
|
|
509
|
+
<h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
|
|
510
|
+
Icon Button with aria-label
|
|
511
|
+
</h4>
|
|
512
|
+
<UI
|
|
513
|
+
as="button"
|
|
514
|
+
aria-label="Close dialog"
|
|
515
|
+
onClick={() => alert("Dialog closed")}
|
|
516
|
+
styles={{
|
|
517
|
+
padding: "0.5rem",
|
|
518
|
+
backgroundColor: "#dc3545",
|
|
519
|
+
color: "white",
|
|
520
|
+
border: "none",
|
|
521
|
+
borderRadius: "0.25rem",
|
|
522
|
+
cursor: "pointer",
|
|
523
|
+
fontSize: "1.25rem",
|
|
524
|
+
lineHeight: 1,
|
|
525
|
+
}}
|
|
526
|
+
>
|
|
527
|
+
×
|
|
528
|
+
</UI>
|
|
529
|
+
</div>
|
|
530
|
+
|
|
531
|
+
{/* Accessible link with descriptive text */}
|
|
532
|
+
<div>
|
|
533
|
+
<h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
|
|
534
|
+
Accessible Link
|
|
535
|
+
</h4>
|
|
536
|
+
<UI
|
|
537
|
+
as="a"
|
|
538
|
+
href="/products"
|
|
539
|
+
styles={{
|
|
540
|
+
color: "#007bff",
|
|
541
|
+
textDecoration: "underline",
|
|
542
|
+
padding: "0.5rem",
|
|
543
|
+
display: "inline-block",
|
|
544
|
+
}}
|
|
545
|
+
>
|
|
546
|
+
View all products
|
|
547
|
+
</UI>
|
|
548
|
+
</div>
|
|
549
|
+
|
|
550
|
+
{/* Toggle button with aria-expanded */}
|
|
551
|
+
<div>
|
|
552
|
+
<h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
|
|
553
|
+
Expandable Section
|
|
554
|
+
</h4>
|
|
555
|
+
<UI
|
|
556
|
+
as="button"
|
|
557
|
+
aria-expanded={isExpanded}
|
|
558
|
+
aria-controls="expandable-content"
|
|
559
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
560
|
+
styles={{
|
|
561
|
+
padding: "0.75rem 1rem",
|
|
562
|
+
backgroundColor: "#007bff",
|
|
563
|
+
color: "white",
|
|
564
|
+
border: "none",
|
|
565
|
+
borderRadius: "0.25rem",
|
|
566
|
+
cursor: "pointer",
|
|
567
|
+
width: "100%",
|
|
568
|
+
textAlign: "left",
|
|
569
|
+
}}
|
|
570
|
+
>
|
|
571
|
+
{isExpanded ? "▼" : "▶"} Toggle Content
|
|
572
|
+
</UI>
|
|
573
|
+
{isExpanded && (
|
|
574
|
+
<UI
|
|
575
|
+
id="expandable-content"
|
|
576
|
+
styles={{
|
|
577
|
+
padding: "1rem",
|
|
578
|
+
backgroundColor: "#f8f9fa",
|
|
579
|
+
marginTop: "0.5rem",
|
|
580
|
+
borderRadius: "0.25rem",
|
|
581
|
+
}}
|
|
582
|
+
>
|
|
583
|
+
This content is now visible and announced to screen readers.
|
|
584
|
+
</UI>
|
|
585
|
+
)}
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
{/* Custom interactive element with proper role and keyboard support */}
|
|
589
|
+
<div>
|
|
590
|
+
<h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
|
|
591
|
+
Custom Interactive (div with role="button")
|
|
592
|
+
</h4>
|
|
593
|
+
<UI
|
|
594
|
+
as="div"
|
|
595
|
+
role="button"
|
|
596
|
+
tabIndex={0}
|
|
597
|
+
aria-label="Custom toggle"
|
|
598
|
+
onClick={() => alert("Clicked!")}
|
|
599
|
+
onKeyDown={(e: React.KeyboardEvent) => {
|
|
600
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
601
|
+
e.preventDefault();
|
|
602
|
+
alert("Activated via keyboard!");
|
|
603
|
+
}
|
|
604
|
+
}}
|
|
605
|
+
styles={{
|
|
606
|
+
padding: "0.75rem 1rem",
|
|
607
|
+
backgroundColor: "#28a745",
|
|
608
|
+
color: "white",
|
|
609
|
+
borderRadius: "0.25rem",
|
|
610
|
+
cursor: "pointer",
|
|
611
|
+
userSelect: "none",
|
|
612
|
+
display: "inline-block",
|
|
613
|
+
}}
|
|
614
|
+
>
|
|
615
|
+
Press Enter or Space
|
|
616
|
+
</UI>
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
);
|
|
620
|
+
},
|
|
621
|
+
parameters: {
|
|
622
|
+
docs: {
|
|
623
|
+
description: {
|
|
624
|
+
story:
|
|
625
|
+
"Examples of accessible interactive elements using proper ARIA attributes, semantic HTML, and keyboard support. Run the Storybook a11y addon to verify these pass accessibility checks.",
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
tags: ["a11y"],
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Demonstrates accessibility patterns including focus management and ARIA attributes.
|
|
634
|
+
*/
|
|
635
|
+
export const AccessibilityPatterns: Story = {
|
|
636
|
+
render: function AccessibilityPatternsStory() {
|
|
637
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
638
|
+
const [count, setCount] = React.useState(0);
|
|
639
|
+
|
|
640
|
+
useEffect(() => {
|
|
641
|
+
// Auto-focus on mount for keyboard navigation
|
|
642
|
+
buttonRef.current?.focus();
|
|
643
|
+
}, []);
|
|
644
|
+
|
|
645
|
+
return (
|
|
646
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
|
|
647
|
+
{/* Focus management example */}
|
|
648
|
+
<div>
|
|
649
|
+
<h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
|
|
650
|
+
Auto-focused Button (Focus Management)
|
|
651
|
+
</h4>
|
|
652
|
+
<UI
|
|
653
|
+
as="button"
|
|
654
|
+
ref={buttonRef}
|
|
655
|
+
onClick={() => alert("Focused button clicked")}
|
|
656
|
+
styles={{
|
|
657
|
+
padding: "0.75rem 1.5rem",
|
|
658
|
+
backgroundColor: "#007bff",
|
|
659
|
+
color: "white",
|
|
660
|
+
border: "none",
|
|
661
|
+
borderRadius: "0.25rem",
|
|
662
|
+
cursor: "pointer",
|
|
663
|
+
outline: "2px solid transparent",
|
|
664
|
+
outlineOffset: "2px",
|
|
665
|
+
}}
|
|
666
|
+
// Custom focus indicator with WCAG 2.4.7 compliant contrast
|
|
667
|
+
onFocus={(e: React.FocusEvent<HTMLButtonElement>) => {
|
|
668
|
+
e.currentTarget.style.outline = "2px solid #0056b3";
|
|
669
|
+
}}
|
|
670
|
+
onBlur={(e: React.FocusEvent<HTMLButtonElement>) => {
|
|
671
|
+
e.currentTarget.style.outline = "2px solid transparent";
|
|
672
|
+
}}
|
|
673
|
+
>
|
|
674
|
+
This button auto-focused on mount
|
|
675
|
+
</UI>
|
|
676
|
+
</div>
|
|
677
|
+
|
|
678
|
+
{/* ARIA live region for dynamic content */}
|
|
679
|
+
<div>
|
|
680
|
+
<h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
|
|
681
|
+
ARIA Live Region (Dynamic Updates)
|
|
682
|
+
</h4>
|
|
683
|
+
<UI
|
|
684
|
+
as="button"
|
|
685
|
+
onClick={() => setCount((c) => c + 1)}
|
|
686
|
+
aria-describedby="counter-description"
|
|
687
|
+
styles={{
|
|
688
|
+
padding: "0.75rem 1.5rem",
|
|
689
|
+
backgroundColor: "#28a745",
|
|
690
|
+
color: "white",
|
|
691
|
+
border: "none",
|
|
692
|
+
borderRadius: "0.25rem",
|
|
693
|
+
cursor: "pointer",
|
|
694
|
+
}}
|
|
695
|
+
>
|
|
696
|
+
Increment Counter
|
|
697
|
+
</UI>
|
|
698
|
+
<UI
|
|
699
|
+
id="counter-description"
|
|
700
|
+
role="status"
|
|
701
|
+
aria-live="polite"
|
|
702
|
+
aria-atomic="true"
|
|
703
|
+
styles={{
|
|
704
|
+
marginTop: "0.5rem",
|
|
705
|
+
padding: "0.75rem",
|
|
706
|
+
backgroundColor: "#d4edda",
|
|
707
|
+
borderRadius: "0.25rem",
|
|
708
|
+
}}
|
|
709
|
+
>
|
|
710
|
+
Current count: {count}
|
|
711
|
+
</UI>
|
|
712
|
+
</div>
|
|
713
|
+
|
|
714
|
+
{/* Semantic vs generic elements */}
|
|
715
|
+
<div>
|
|
716
|
+
<h4 style={{ marginTop: 0, marginBottom: "0.5rem" }}>
|
|
717
|
+
Semantic HTML Choice
|
|
718
|
+
</h4>
|
|
719
|
+
<div style={{ display: "flex", gap: "1rem" }}>
|
|
720
|
+
<UI
|
|
721
|
+
as="button"
|
|
722
|
+
type="button"
|
|
723
|
+
styles={{
|
|
724
|
+
padding: "0.5rem 1rem",
|
|
725
|
+
backgroundColor: "#007bff",
|
|
726
|
+
color: "white",
|
|
727
|
+
border: "none",
|
|
728
|
+
borderRadius: "0.25rem",
|
|
729
|
+
cursor: "pointer",
|
|
730
|
+
}}
|
|
731
|
+
>
|
|
732
|
+
✅ Semantic <button>
|
|
733
|
+
</UI>
|
|
734
|
+
<UI
|
|
735
|
+
as="nav"
|
|
736
|
+
aria-label="Secondary navigation"
|
|
737
|
+
styles={{
|
|
738
|
+
padding: "0.5rem 1rem",
|
|
739
|
+
backgroundColor: "#6f42c1",
|
|
740
|
+
color: "white",
|
|
741
|
+
borderRadius: "0.25rem",
|
|
742
|
+
}}
|
|
743
|
+
>
|
|
744
|
+
✅ Semantic <nav>
|
|
745
|
+
</UI>
|
|
746
|
+
</div>
|
|
747
|
+
</div>
|
|
748
|
+
</div>
|
|
749
|
+
);
|
|
750
|
+
},
|
|
751
|
+
parameters: {
|
|
752
|
+
docs: {
|
|
753
|
+
description: {
|
|
754
|
+
story:
|
|
755
|
+
"Demonstrates accessibility patterns including focus management, ARIA live regions for dynamic content, custom focus indicators, and semantic HTML usage.",
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
},
|
|
759
|
+
tags: ["a11y"],
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* ⚠️ Shows common accessibility mistakes to avoid.
|
|
764
|
+
* These examples intentionally violate accessibility guidelines to demonstrate what NOT to do.
|
|
765
|
+
*/
|
|
766
|
+
export const CommonAccessibilityMistakes: Story = {
|
|
767
|
+
render: () => {
|
|
768
|
+
return (
|
|
769
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
|
|
770
|
+
<div
|
|
771
|
+
style={{
|
|
772
|
+
padding: "1rem",
|
|
773
|
+
backgroundColor: "#fff3cd",
|
|
774
|
+
borderLeft: "4px solid #ffc107",
|
|
775
|
+
marginBottom: "1rem",
|
|
776
|
+
}}
|
|
777
|
+
>
|
|
778
|
+
<strong>⚠️ Warning:</strong> These examples show common accessibility
|
|
779
|
+
violations. Do NOT copy these patterns. They are shown for educational
|
|
780
|
+
purposes only.
|
|
781
|
+
</div>
|
|
782
|
+
|
|
783
|
+
{/* Missing accessible name */}
|
|
784
|
+
<div>
|
|
785
|
+
<h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
|
|
786
|
+
❌ BAD: Icon button without accessible name
|
|
787
|
+
</h4>
|
|
788
|
+
<UI
|
|
789
|
+
as="button"
|
|
790
|
+
onClick={() => {}}
|
|
791
|
+
styles={{
|
|
792
|
+
padding: "0.5rem",
|
|
793
|
+
backgroundColor: "#dc3545",
|
|
794
|
+
color: "white",
|
|
795
|
+
border: "none",
|
|
796
|
+
borderRadius: "0.25rem",
|
|
797
|
+
cursor: "pointer",
|
|
798
|
+
fontSize: "1.25rem",
|
|
799
|
+
}}
|
|
800
|
+
>
|
|
801
|
+
×
|
|
802
|
+
</UI>
|
|
803
|
+
<p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
|
|
804
|
+
<strong>Problem:</strong> Screen readers cannot identify this button's
|
|
805
|
+
purpose. <strong>Fix:</strong> Add <code>aria-label="Close"</code>
|
|
806
|
+
</p>
|
|
807
|
+
</div>
|
|
808
|
+
|
|
809
|
+
{/* Non-semantic clickable div */}
|
|
810
|
+
<div>
|
|
811
|
+
<h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
|
|
812
|
+
❌ BAD: Clickable div without keyboard support
|
|
813
|
+
</h4>
|
|
814
|
+
<UI
|
|
815
|
+
as="div"
|
|
816
|
+
onClick={() => alert("This is not keyboard accessible!")}
|
|
817
|
+
styles={{
|
|
818
|
+
padding: "0.75rem 1rem",
|
|
819
|
+
backgroundColor: "#6c757d",
|
|
820
|
+
color: "white",
|
|
821
|
+
borderRadius: "0.25rem",
|
|
822
|
+
cursor: "pointer",
|
|
823
|
+
display: "inline-block",
|
|
824
|
+
}}
|
|
825
|
+
>
|
|
826
|
+
Click me (but you can't use keyboard!)
|
|
827
|
+
</UI>
|
|
828
|
+
<p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
|
|
829
|
+
<strong>Problem:</strong> Not keyboard accessible or announced to screen
|
|
830
|
+
readers. <strong>Fix:</strong> Use <code>as="button"</code> or add{" "}
|
|
831
|
+
<code>role="button"</code>, <code>tabIndex=0</code>, and keyboard handlers.
|
|
832
|
+
</p>
|
|
833
|
+
</div>
|
|
834
|
+
|
|
835
|
+
{/* Poor contrast focus indicator */}
|
|
836
|
+
<div>
|
|
837
|
+
<h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
|
|
838
|
+
❌ BAD: Insufficient focus indicator contrast
|
|
839
|
+
</h4>
|
|
840
|
+
<UI
|
|
841
|
+
as="button"
|
|
842
|
+
styles={{
|
|
843
|
+
padding: "0.75rem 1.5rem",
|
|
844
|
+
backgroundColor: "#007bff",
|
|
845
|
+
color: "white",
|
|
846
|
+
border: "none",
|
|
847
|
+
borderRadius: "0.25rem",
|
|
848
|
+
cursor: "pointer",
|
|
849
|
+
outline: "1px solid #4da3ff",
|
|
850
|
+
}}
|
|
851
|
+
>
|
|
852
|
+
Low contrast focus
|
|
853
|
+
</UI>
|
|
854
|
+
<p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
|
|
855
|
+
<strong>Problem:</strong> Focus indicator contrast ratio is less than 3:1
|
|
856
|
+
(WCAG 2.4.7). <strong>Fix:</strong> Use a contrasting color like dark blue
|
|
857
|
+
on light blue background.
|
|
858
|
+
</p>
|
|
859
|
+
</div>
|
|
860
|
+
|
|
861
|
+
{/* Vague link text */}
|
|
862
|
+
<div>
|
|
863
|
+
<h4 style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}>
|
|
864
|
+
❌ BAD: Non-descriptive link text
|
|
865
|
+
</h4>
|
|
866
|
+
<UI
|
|
867
|
+
as="a"
|
|
868
|
+
href="#"
|
|
869
|
+
styles={{
|
|
870
|
+
color: "#007bff",
|
|
871
|
+
textDecoration: "underline",
|
|
872
|
+
}}
|
|
873
|
+
>
|
|
874
|
+
Click here
|
|
875
|
+
</UI>
|
|
876
|
+
<p style={{ fontSize: "0.875rem", color: "#721c24", marginTop: "0.5rem" }}>
|
|
877
|
+
<strong>Problem:</strong> "Click here" doesn't describe the link's
|
|
878
|
+
destination. <strong>Fix:</strong> Use descriptive text like "View product
|
|
879
|
+
documentation".
|
|
880
|
+
</p>
|
|
881
|
+
</div>
|
|
882
|
+
</div>
|
|
883
|
+
);
|
|
884
|
+
},
|
|
885
|
+
parameters: {
|
|
886
|
+
docs: {
|
|
887
|
+
description: {
|
|
888
|
+
story:
|
|
889
|
+
"⚠️ Educational examples showing common accessibility violations. These patterns should be avoided. Each example includes an explanation of the problem and how to fix it. Run the Storybook a11y addon to see these violations detected automatically.",
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
tags: ["a11y"],
|
|
894
|
+
};
|