@pnx-mixtape/mxds 0.1.0-rc.1 → 0.1.0-rc.3
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/.storybook/decorators.ts +15 -0
- package/.storybook/{preview.ts → preview.tsx} +8 -1
- package/.storybook/src/docs.tsx +29 -0
- package/.storybook/themes/campus-vibrant.css +181 -0
- package/.storybook/themes/civic-formal.css +246 -0
- package/.storybook/{theme-demo.css → themes/demo.css} +14 -7
- package/.storybook/themes/health-services.css +255 -0
- package/.storybook/themes/research-faculty.css +169 -0
- package/dist/build/accordion.css +1 -1
- package/dist/build/accordion.entry.js +1 -1
- package/dist/build/autocomplete.css +1 -1
- package/dist/build/autocomplete.entry.js +1 -1
- package/dist/build/base.css +1 -1
- package/dist/build/breadcrumb.css +1 -1
- package/dist/build/button.css +1 -1
- package/dist/build/card.css +1 -1
- package/dist/build/chunks/{Accordion-z-dH6nFH.js → Accordion-GN0sME4W.js} +1 -1
- package/dist/build/chunks/{AccordionMobile-BYPPNyf5.js → AccordionMobile-DG5jSe8Q.js} +1 -1
- package/dist/build/chunks/{Autocomplete-2L6tLLuh.js → Autocomplete-B4RtkAyh.js} +1 -1
- package/dist/build/chunks/{ClosableAlert-DdXbF1J2.js → ClosableAlert-DEhj8Nxm.js} +1 -1
- package/dist/build/chunks/{Dialog-Ds8gzhri.js → Dialog-Haahn-Yn.js} +1 -1
- package/dist/build/chunks/DropMenu-1baQc8Yc.js +1 -0
- package/dist/build/chunks/Gallery-D8NGXy2Q.js +1 -0
- package/dist/build/chunks/GlobalToggle-B52fdBQu.js +1 -0
- package/dist/build/chunks/InPageNavigation-Dyn2TR-N.js +1 -0
- package/dist/build/chunks/Navigation-hOrfLq7h.js +1 -0
- package/dist/build/chunks/Popover-Cxg-s5Jn.js +1 -0
- package/dist/build/chunks/Sticky-BGCuuEJK.js +1 -0
- package/dist/build/chunks/Tabs-DYy1YTdU.js +15 -0
- package/dist/build/chunks/enums-DLV_1NAf.js +1 -0
- package/dist/build/chunks/polyfills-C7pRt54M.js +1 -0
- package/dist/build/chunks/utilities-_Bq3TxAQ.js +1 -0
- package/dist/build/constants.css +1 -1
- package/dist/build/cookie-consent.css +1 -1
- package/dist/build/dialog.entry.js +1 -1
- package/dist/build/drop-menu.entry.js +1 -1
- package/dist/build/elements.js +1 -1
- package/dist/build/filters.css +1 -1
- package/dist/build/filters.entry.js +1 -1
- package/dist/build/form.css +1 -1
- package/dist/build/gallery.entry.js +1 -1
- package/dist/build/global-alert.entry.js +1 -1
- package/dist/build/grid.css +1 -1
- package/dist/build/header.entry.js +1 -1
- package/dist/build/icon.css +1 -1
- package/dist/build/in-page-alert.css +1 -1
- package/dist/build/in-page-navigation.entry.js +1 -1
- package/dist/build/list-item.css +1 -1
- package/dist/build/navigation.css +1 -1
- package/dist/build/navigation.entry.js +1 -1
- package/dist/build/one-time-code.css +1 -0
- package/dist/build/popover.css +1 -1
- package/dist/build/popover.entry.js +1 -1
- package/dist/build/print.css +1 -1
- package/dist/build/progress-indicator.css +1 -0
- package/dist/build/react.js +1 -1
- package/dist/build/sidebar.css +1 -1
- package/dist/build/steps.css +1 -1
- package/dist/build/sticky.entry.js +1 -1
- package/dist/build/tabs.css +1 -1
- package/dist/build/tabs.entry.js +1 -1
- package/dist/build/tag.css +1 -1
- package/dist/build/utilities.css +1 -1
- package/dist/types/Atom/DefinitionList/DefinitionList.d.ts +3 -2
- package/dist/types/Atom/DefinitionList/DefinitionList.d.ts.map +1 -1
- package/dist/types/Atom/Heading/Heading.d.ts.map +1 -1
- package/dist/types/Atom/Link/Link.d.ts.map +1 -1
- package/dist/types/Atom/List/List.d.ts +10 -0
- package/dist/types/Atom/List/List.d.ts.map +1 -0
- package/dist/types/Component/Accordion/Accordion.d.ts.map +1 -1
- package/dist/types/Component/Dialog/Dialog.d.ts.map +1 -1
- package/dist/types/Component/DropMenu/Elements/DropMenu.d.ts.map +1 -1
- package/dist/types/Component/Filters/Elements/Filters.d.ts +20 -1
- package/dist/types/Component/Filters/Elements/Filters.d.ts.map +1 -1
- package/dist/types/Component/Filters/Filters.d.ts +7 -2
- package/dist/types/Component/Filters/Filters.d.ts.map +1 -1
- package/dist/types/Component/Gallery/Gallery.d.ts.map +1 -1
- package/dist/types/Component/InPageNavigation/Elements/InPageNavigation.d.ts +2 -2
- package/dist/types/Component/InPageNavigation/Elements/InPageNavigation.d.ts.map +1 -1
- package/dist/types/Component/InPageNavigation/Hooks/useInPageNavigation.d.ts.map +1 -1
- package/dist/types/Component/Pagination/Components/PaginationContext.d.ts.map +1 -1
- package/dist/types/Component/ProgressIndicator/ProgressIndicator.d.ts +9 -0
- package/dist/types/Component/ProgressIndicator/ProgressIndicator.d.ts.map +1 -0
- package/dist/types/Component/Tabs/Elements/Tabs.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Tabs.d.ts.map +1 -1
- package/dist/types/Component/Tag/Tag.d.ts +4 -2
- package/dist/types/Component/Tag/Tag.d.ts.map +1 -1
- package/dist/types/Form/Autocomplete/Autocomplete.d.ts +2 -2
- package/dist/types/Form/Autocomplete/Autocomplete.d.ts.map +1 -1
- package/dist/types/Form/Autocomplete/Elements/Autocomplete.d.ts +10 -0
- package/dist/types/Form/Autocomplete/Elements/Autocomplete.d.ts.map +1 -1
- package/dist/types/Form/Checkbox/FormCheckbox.d.ts.map +1 -1
- package/dist/types/Form/CheckboxCard/CheckboxCard.d.ts +23 -0
- package/dist/types/Form/CheckboxCard/CheckboxCard.d.ts.map +1 -0
- package/dist/types/Form/OneTimeCode/OneTimeCode.d.ts +12 -0
- package/dist/types/Form/OneTimeCode/OneTimeCode.d.ts.map +1 -0
- package/dist/types/Form/Radio/FormRadio.d.ts.map +1 -1
- package/dist/types/Form/RadioCard/RadioCard.d.ts +16 -0
- package/dist/types/Form/RadioCard/RadioCard.d.ts.map +1 -0
- package/dist/types/Form/Select/FormSelect.d.ts.map +1 -1
- package/dist/types/Form/TextInput/FormText.d.ts.map +1 -1
- package/dist/types/Form/Textarea/FormTextarea.d.ts.map +1 -1
- package/dist/types/Utility/Hooks/useIsomorphicLayoutEffect.d.ts +4 -0
- package/dist/types/Utility/Hooks/useIsomorphicLayoutEffect.d.ts.map +1 -0
- package/dist/types/enums.d.ts +19 -0
- package/dist/types/enums.d.ts.map +1 -1
- package/dist/types/react.d.ts +5 -0
- package/dist/types/react.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/Atom/Background/_background.css +2 -2
- package/src/Atom/Blockquote/_blockquote.css +9 -2
- package/src/Atom/Button/README.md +1 -0
- package/src/Atom/Button/__snapshots__/Button.stories.ts.snap +2 -3
- package/src/Atom/Button/_buttons-styles.css +6 -6
- package/src/Atom/Button/_buttons.css +21 -3
- package/src/Atom/Button/button.twig +4 -1
- package/src/Atom/DefinitionList/DefinitionList.stories.ts +42 -2
- package/src/Atom/DefinitionList/DefinitionList.stories.tsx +36 -0
- package/src/Atom/DefinitionList/DefinitionList.tsx +4 -1
- package/src/Atom/DefinitionList/DefinitionListItem.stories.ts +1 -1
- package/src/Atom/DefinitionList/README.md +43 -0
- package/src/Atom/DefinitionList/__snapshots__/DefinitionList.stories.ts.snap +37 -3
- package/src/Atom/DefinitionList/__snapshots__/DefinitionList.stories.tsx.snap +2 -0
- package/src/Atom/DefinitionList/__snapshots__/DefinitionListItem.stories.ts.snap +3 -3
- package/src/Atom/DefinitionList/definition-list-item.twig +1 -0
- package/src/Atom/DefinitionList/definition-list.twig +6 -1
- package/src/Atom/Heading/Heading.tsx +6 -0
- package/src/Atom/Heading/_headings.css +2 -1
- package/src/Atom/Icon/_extended-set.css +30 -30
- package/src/Atom/Icon/_icon.css +39 -36
- package/src/Atom/Image/README.md +2 -2
- package/src/Atom/Image/_image.css +12 -6
- package/src/Atom/Link/Link.tsx +16 -3
- package/src/Atom/Link/__snapshots__/Link.stories.ts.snap +7 -12
- package/src/Atom/Link/__snapshots__/Link.stories.tsx.snap +1 -1
- package/src/Atom/Link/_links.css +9 -0
- package/src/Atom/Link/link.twig +8 -2
- package/src/Atom/LinkedImage/linked-image.twig +1 -2
- package/src/Atom/List/List.stories.ts +102 -0
- package/src/Atom/List/List.stories.tsx +103 -0
- package/src/Atom/List/List.tsx +22 -0
- package/src/Atom/List/README.md +150 -0
- package/src/Atom/List/__snapshots__/List.stories.ts.snap +43 -0
- package/src/Atom/List/__snapshots__/List.stories.tsx.snap +13 -0
- package/src/Atom/List/_list.css +123 -0
- package/src/Atom/List/list.twig +5 -0
- package/src/Atom/Media/Media.stories.ts +1 -1
- package/src/Atom/Media/__snapshots__/Media.stories.ts.snap +2 -2
- package/src/Atom/Spacing/_spacing.css +24 -24
- package/src/Atom/Table/__snapshots__/Table.stories.ts.snap +24 -4
- package/src/Atom/Table/_table.css +15 -3
- package/src/Atom/Table/table.twig +6 -1
- package/src/Atom/Text/__snapshots__/Text.stories.ts.snap +3 -2
- package/src/Atom/Text/_text-styles.css +21 -1
- package/src/Atom/Text/twig/text-styles-example.twig +3 -2
- package/src/Atom/_base.css +6 -0
- package/src/Atom/_flow.css +1 -1
- package/src/Atom/_generic.css +18 -11
- package/src/Component/Accordion/Accordion.stories.ts +31 -0
- package/src/Component/Accordion/Accordion.tsx +3 -2
- package/src/Component/Accordion/AccordionItem.stories.ts +27 -0
- package/src/Component/Accordion/__snapshots__/Accordion.stories.ts.snap +36 -0
- package/src/Component/Accordion/__snapshots__/AccordionItem.stories.ts.snap +16 -0
- package/src/Component/Accordion/accordion.css +2 -0
- package/src/Component/Breadcrumb/__snapshots__/Breadcrumb.stories.ts.snap +3 -6
- package/src/Component/Breadcrumb/breadcrumb.css +4 -0
- package/src/Component/Card/__snapshots__/Card.stories.ts.snap +13 -24
- package/src/Component/Card/card.css +2 -2
- package/src/Component/Card/card.twig +1 -1
- package/src/Component/Carousel/Carousel.stories.ts +101 -23
- package/src/Component/Carousel/Carousel.stories.tsx +74 -0
- package/src/Component/Carousel/__snapshots__/Carousel.stories.ts.snap +273 -88
- package/src/Component/Carousel/__snapshots__/Carousel.stories.tsx.snap +5 -3
- package/src/Component/ContentBlock/__snapshots__/ContentBlock.stories.ts.snap +12 -24
- package/src/Component/CookieConsent/cookie-consent.css +3 -1
- package/src/Component/Dialog/Dialog.stories.ts +33 -0
- package/src/Component/Dialog/Dialog.stories.tsx +25 -0
- package/src/Component/Dialog/Dialog.tsx +2 -2
- package/src/Component/Dialog/__snapshots__/Dialog.stories.ts.snap +33 -2
- package/src/Component/Dialog/__snapshots__/Dialog.stories.tsx.snap +2 -0
- package/src/Component/DropMenu/DropMenu.stories.ts +39 -1
- package/src/Component/DropMenu/DropMenu.stories.tsx +29 -0
- package/src/Component/DropMenu/DropMenu.tsx +1 -1
- package/src/Component/DropMenu/Elements/DropMenu.ts +10 -1
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.ts.snap +14 -0
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.tsx.snap +3 -1
- package/src/Component/Filters/Elements/Filters.ts +71 -1
- package/src/Component/Filters/FilterItem.stories.ts +2 -1
- package/src/Component/Filters/FilterItem.tsx +1 -1
- package/src/Component/Filters/Filters.figma.ts +1 -1
- package/src/Component/Filters/Filters.stories.ts +95 -3
- package/src/Component/Filters/Filters.stories.tsx +246 -17
- package/src/Component/Filters/Filters.tsx +145 -29
- package/src/Component/Filters/README.md +102 -37
- package/src/Component/Filters/__snapshots__/FilterItem.stories.tsx.snap +3 -3
- package/src/Component/Filters/__snapshots__/Filters.stories.ts.snap +82 -4
- package/src/Component/Filters/__snapshots__/Filters.stories.tsx.snap +9 -3
- package/src/Component/Filters/filters.css +11 -0
- package/src/Component/Gallery/Gallery.tsx +2 -2
- package/src/Component/HeroBanner/HeroBanner.stories.ts +1 -1
- package/src/Component/HeroBanner/__snapshots__/HeroBanner.stories.ts.snap +4 -8
- package/src/Component/HeroSearch/__snapshots__/HeroSearch.stories.ts.snap +5 -10
- package/src/Component/InPageAlert/__snapshots__/InPageAlert.stories.ts.snap +6 -12
- package/src/Component/InPageAlert/in-page-alert.css +2 -1
- package/src/Component/InPageNavigation/Elements/InPageNavigation.ts +10 -5
- package/src/Component/InPageNavigation/Hooks/useInPageNavigation.ts +10 -5
- package/src/Component/InPageNavigation/InPageNavigation.stories.ts +83 -0
- package/src/Component/InPageNavigation/InPageNavigation.stories.tsx +130 -0
- package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.ts.snap +61 -1
- package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.tsx.snap +4 -0
- package/src/Component/InPageNavigation/twig/content-example-duplicate-headings.twig +20 -0
- package/src/Component/InPageNavigation/twig/in-page-navigation-story-duplicate-headings.twig +2 -0
- package/src/Component/LinkList/__snapshots__/LinkList.stories.ts.snap +6 -12
- package/src/Component/ListItem/__snapshots__/ListItem.stories.ts.snap +11 -18
- package/src/Component/ListItem/list-item.css +1 -1
- package/src/Component/ListItem/list-item.twig +2 -2
- package/src/Component/Navigation/Dropdown.stories.tsx +46 -0
- package/src/Component/Navigation/Navigation.stories.ts +56 -0
- package/src/Component/Navigation/__snapshots__/Dropdown.stories.tsx.snap +2 -0
- package/src/Component/Navigation/__snapshots__/Navigation.stories.ts.snap +57 -0
- package/src/Component/Navigation/_navigation.css +1 -1
- package/src/Component/Pagination/Components/PaginationContext.tsx +2 -1
- package/src/Component/Pagination/Components/PaginationItem.tsx +1 -1
- package/src/Component/Pagination/Pagination.stories.ts +44 -4
- package/src/Component/Pagination/Pagination.stories.tsx +31 -0
- package/src/Component/Pagination/__snapshots__/Pagination.stories.ts.snap +69 -12
- package/src/Component/Pagination/__snapshots__/Pagination.stories.tsx.snap +3 -1
- package/src/Component/Pagination/pagination-item.twig +1 -1
- package/src/Component/Pagination/pagination.css +2 -2
- package/src/Component/Popover/__snapshots__/Popover.stories.ts.snap +13 -26
- package/src/Component/Popover/popover.css +1 -1
- package/src/Component/ProgressIndicator/ProgressIndicator.stories.ts +36 -0
- package/src/Component/ProgressIndicator/ProgressIndicator.stories.tsx +30 -0
- package/src/Component/ProgressIndicator/ProgressIndicator.tsx +60 -0
- package/src/Component/ProgressIndicator/README.md +111 -0
- package/src/Component/ProgressIndicator/__snapshots__/ProgressIndicator.stories.ts.snap +67 -0
- package/src/Component/ProgressIndicator/__snapshots__/ProgressIndicator.stories.tsx.snap +7 -0
- package/src/Component/ProgressIndicator/progress-indicator.css +59 -0
- package/src/Component/ProgressIndicator/progress-indicator.twig +22 -0
- package/src/Component/SideNavigation/__snapshots__/SideNavigation.stories.ts.snap +1 -2
- package/src/Component/SocialLinks/__snapshots__/SocialLinks.stories.ts.snap +6 -9
- package/src/Component/Steps/Steps.stories.ts +1 -1
- package/src/Component/Steps/steps.css +13 -0
- package/src/Component/Sticky/Elements/Sticky.ts +1 -1
- package/src/Component/Sticky/sticky.css +3 -3
- package/src/Component/Tabs/Elements/Tabs.ts +3 -1
- package/src/Component/Tabs/Tabs.stories.ts +77 -21
- package/src/Component/Tabs/Tabs.stories.tsx +24 -0
- package/src/Component/Tabs/Tabs.tsx +2 -2
- package/src/Component/Tabs/__snapshots__/Tabs.stories.ts.snap +20 -2
- package/src/Component/Tabs/__snapshots__/Tabs.stories.tsx.snap +3 -1
- package/src/Component/Tabs/tabs.css +2 -0
- package/src/Component/Tag/README.md +52 -26
- package/src/Component/Tag/Tag.stories.tsx +24 -0
- package/src/Component/Tag/Tag.tsx +12 -2
- package/src/Component/Tag/TagItem.stories.ts +25 -0
- package/src/Component/Tag/__snapshots__/Tag.stories.ts.snap +2 -2
- package/src/Component/Tag/__snapshots__/Tag.stories.tsx.snap +2 -0
- package/src/Component/Tag/tag-item.twig +2 -2
- package/src/Component/Tag/tag.css +76 -2
- package/src/Form/Autocomplete/Autocomplete.stories.ts +89 -7
- package/src/Form/Autocomplete/Autocomplete.stories.tsx +85 -1
- package/src/Form/Autocomplete/Autocomplete.tsx +20 -10
- package/src/Form/Autocomplete/Elements/Autocomplete.ts +24 -1
- package/src/Form/Autocomplete/README.md +26 -26
- package/src/Form/Autocomplete/__snapshots__/Autocomplete.stories.ts.snap +39 -5
- package/src/Form/Autocomplete/__snapshots__/Autocomplete.stories.tsx.snap +8 -4
- package/src/Form/Autocomplete/autocomplete.css +12 -1
- package/src/Form/Autocomplete/autocomplete.twig +1 -3
- package/src/Form/Checkbox/FormCheckbox.tsx +2 -8
- package/src/Form/Checkbox/README.md +23 -18
- package/src/Form/Checkbox/input-checkbox.twig +0 -2
- package/src/Form/CheckboxCard/CheckboxCard.stories.ts +90 -0
- package/src/Form/CheckboxCard/CheckboxCard.stories.tsx +43 -0
- package/src/Form/CheckboxCard/CheckboxCard.tsx +102 -0
- package/src/Form/CheckboxCard/README.md +163 -0
- package/src/Form/CheckboxCard/__snapshots__/CheckboxCard.stories.ts.snap +63 -0
- package/src/Form/CheckboxCard/__snapshots__/CheckboxCard.stories.tsx.snap +7 -0
- package/src/Form/CheckboxCard/checkbox-card.twig +29 -0
- package/src/Form/FormItem/__snapshots__/FormItem.stories.ts.snap +2 -2
- package/src/Form/OneTimeCode/OneTimeCode.stories.ts +20 -0
- package/src/Form/OneTimeCode/OneTimeCode.stories.tsx +18 -0
- package/src/Form/OneTimeCode/OneTimeCode.tsx +56 -0
- package/src/Form/OneTimeCode/README.md +83 -0
- package/src/Form/OneTimeCode/__snapshots__/OneTimeCode.stories.ts.snap +7 -0
- package/src/Form/OneTimeCode/__snapshots__/OneTimeCode.stories.tsx.snap +3 -0
- package/src/Form/OneTimeCode/one-time-code.css +13 -0
- package/src/Form/OneTimeCode/one-time-code.twig +31 -0
- package/src/Form/README.md +65 -13
- package/src/Form/Radio/FormRadio.tsx +1 -4
- package/src/Form/Radio/input-radio.twig +0 -2
- package/src/Form/RadioCard/README.md +146 -0
- package/src/Form/RadioCard/RadioCard.stories.ts +95 -0
- package/src/Form/RadioCard/RadioCard.stories.tsx +26 -0
- package/src/Form/RadioCard/RadioCard.tsx +59 -0
- package/src/Form/RadioCard/__snapshots__/RadioCard.stories.ts.snap +69 -0
- package/src/Form/RadioCard/__snapshots__/RadioCard.stories.tsx.snap +3 -0
- package/src/Form/RadioCard/radio-card.twig +20 -0
- package/src/Form/Select/FormSelect.tsx +1 -4
- package/src/Form/Select/input-select.twig +0 -2
- package/src/Form/TextInput/FormText.stories.tsx +14 -0
- package/src/Form/TextInput/FormText.tsx +1 -4
- package/src/Form/TextInput/__snapshots__/FormText.stories.tsx.snap +2 -0
- package/src/Form/TextInput/input-text.twig +0 -2
- package/src/Form/Textarea/FormTextarea.tsx +1 -4
- package/src/Form/Textarea/input-textarea.twig +0 -2
- package/src/Form/Validation.stories.ts +153 -0
- package/src/Form/__snapshots__/Validation.stories.ts.snap +130 -0
- package/src/Form/form.css +240 -55
- package/src/Layout/Footer/__snapshots__/Footer.stories.ts.snap +18 -33
- package/src/Layout/Grid/grid.css +4 -4
- package/src/Layout/Header/Header.stories.ts +33 -0
- package/src/Layout/Header/Header.stories.tsx +31 -0
- package/src/Layout/Header/__snapshots__/Header.stories.ts.snap +50 -0
- package/src/Layout/Header/__snapshots__/Header.stories.tsx.snap +2 -0
- package/src/Layout/Masthead/__snapshots__/Masthead.stories.ts.snap +8 -16
- package/src/Layout/Section/__snapshots__/Background.stories.ts.snap +2 -4
- package/src/Layout/Section/__snapshots__/Section.stories.ts.snap +4 -8
- package/src/Layout/Sidebar/__snapshots__/Sidebar.stories.ts.snap +5 -5
- package/src/Layout/Sidebar/sidebar.css +1 -0
- package/src/Layout/Sidebar/sidebar.twig +1 -1
- package/src/Utility/Hooks/useIsomorphicLayoutEffect.ts +8 -0
- package/src/Utility/_layout-utils.css +8 -0
- package/src/Utility/_mixins.css +24 -0
- package/src/Utility/print.css +51 -29
- package/src/Utility/utilities.css +22 -2
- package/src/_custom-media.css +1 -0
- package/src/constants.css +12 -2
- package/src/enums.ts +22 -0
- package/src/react.ts +5 -0
- package/tools/lib/extract-classnames.mjs +0 -0
- package/tools/lint-classnames.mjs +1 -1
- package/tools/test-utils/screen-reader.ts +33 -0
- package/dist/build/chunks/DropMenu-Co9jZZ4S.js +0 -1
- package/dist/build/chunks/Gallery-BT3cxS5m.js +0 -1
- package/dist/build/chunks/GlobalToggle-DQHho97V.js +0 -1
- package/dist/build/chunks/InPageNavigation-CzR_KZaz.js +0 -1
- package/dist/build/chunks/Navigation-B9Wug-ok.js +0 -1
- package/dist/build/chunks/Popover-De_CQqcd.js +0 -1
- package/dist/build/chunks/Sticky-Kyjykb36.js +0 -1
- package/dist/build/chunks/Tabs-Dm8g301e.js +0 -15
- package/dist/build/chunks/disclosure-widget-BETzOHLO.js +0 -1
- package/dist/build/chunks/polyfills-Dokbkxxk.js +0 -1
- package/dist/build/chunks/utilities-C5dAO2YA.js +0 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createContext, JSX, PropsWithChildren, use, useId, useRef } from "react"
|
|
2
|
+
import {
|
|
3
|
+
AriaCheckboxGroupItemProps,
|
|
4
|
+
AriaCheckboxProps,
|
|
5
|
+
useCheckbox,
|
|
6
|
+
useCheckboxGroup,
|
|
7
|
+
useCheckboxGroupItem,
|
|
8
|
+
} from "react-aria"
|
|
9
|
+
import {
|
|
10
|
+
CheckboxGroupProps,
|
|
11
|
+
CheckboxGroupState,
|
|
12
|
+
useCheckboxGroupState,
|
|
13
|
+
useToggleState,
|
|
14
|
+
} from "react-stately"
|
|
15
|
+
import classNames from "classnames"
|
|
16
|
+
import FormItem from "../FormItem/FormItem"
|
|
17
|
+
import { FormStatus } from "../../enums"
|
|
18
|
+
|
|
19
|
+
const CheckboxCardContext = createContext<CheckboxGroupState>(null)
|
|
20
|
+
|
|
21
|
+
type FormCheckboxCardProps = CheckboxGroupProps &
|
|
22
|
+
PropsWithChildren & {
|
|
23
|
+
label?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const FormCheckboxCard = ({ label, children, ...props }: FormCheckboxCardProps): JSX.Element => {
|
|
27
|
+
const state = useCheckboxGroupState(props)
|
|
28
|
+
const { groupProps } = useCheckboxGroup(props, state)
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<FormItem fieldset {...groupProps} legend={label}>
|
|
32
|
+
<CheckboxCardContext value={state}>{children}</CheckboxCardContext>
|
|
33
|
+
</FormItem>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type CheckboxCardProps = AriaCheckboxGroupItemProps &
|
|
38
|
+
PropsWithChildren & {
|
|
39
|
+
hint?: string
|
|
40
|
+
status?: FormStatus
|
|
41
|
+
className?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const CheckboxCard = ({
|
|
45
|
+
children,
|
|
46
|
+
hint = null,
|
|
47
|
+
status = FormStatus.DEFAULT,
|
|
48
|
+
className = "",
|
|
49
|
+
...props
|
|
50
|
+
}: CheckboxCardProps): JSX.Element => {
|
|
51
|
+
const id = useId()
|
|
52
|
+
const ref = useRef<HTMLInputElement>(null)
|
|
53
|
+
const state = use(CheckboxCardContext)
|
|
54
|
+
const { inputProps } = useCheckboxGroupItem(props, state, ref)
|
|
55
|
+
|
|
56
|
+
const classes = classNames("mx-checkbox", "mx-choice-card", className)
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className={classes}>
|
|
60
|
+
<input id={id} ref={ref} aria-invalid={status === FormStatus.INVALID} {...inputProps} />
|
|
61
|
+
<label htmlFor={id}>
|
|
62
|
+
{children}
|
|
63
|
+
{hint && <span className="mx-choice-card__hint">{hint}</span>}
|
|
64
|
+
</label>
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type SingleCheckboxCardProps = AriaCheckboxProps &
|
|
70
|
+
PropsWithChildren & {
|
|
71
|
+
hint?: string
|
|
72
|
+
status?: FormStatus
|
|
73
|
+
className?: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const SingleCheckboxCard = ({
|
|
77
|
+
children,
|
|
78
|
+
hint = null,
|
|
79
|
+
status = FormStatus.DEFAULT,
|
|
80
|
+
className = "",
|
|
81
|
+
...props
|
|
82
|
+
}: SingleCheckboxCardProps): JSX.Element => {
|
|
83
|
+
const id = useId()
|
|
84
|
+
const ref = useRef<HTMLInputElement>(null)
|
|
85
|
+
const state = useToggleState(props)
|
|
86
|
+
const { inputProps } = useCheckbox(props, state, ref)
|
|
87
|
+
|
|
88
|
+
const classes = classNames("mx-checkbox", "mx-choice-card", className)
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className={classes}>
|
|
92
|
+
<input id={id} ref={ref} aria-invalid={status === FormStatus.INVALID} {...inputProps} />
|
|
93
|
+
<label htmlFor={id}>
|
|
94
|
+
{children}
|
|
95
|
+
{hint && <span className="mx-choice-card__hint">{hint}</span>}
|
|
96
|
+
</label>
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default CheckboxCard
|
|
102
|
+
export { FormCheckboxCard, SingleCheckboxCard }
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# CheckboxCard
|
|
2
|
+
|
|
3
|
+
A card-style checkbox — a full padded, bordered, selectable card instead of a small inline control. Use for a small set of options that benefit from a larger tap target and optional secondary hint text (e.g. a high-stakes form question), rather than a dense list of ordinary checkboxes.
|
|
4
|
+
|
|
5
|
+
> **HTML/Twig users:** use native `<input type="checkbox">` with both the `.mx-checkbox` and `.mx-choice-card` wrapper classes. The react-aria wiring is React-only.
|
|
6
|
+
|
|
7
|
+
`.mx-choice-card` layers on top of `.mx-checkbox` rather than replacing it — the base class still supplies input reset/sizing, disabled state, and the spacing between stacked cards in a group; `.mx-choice-card`'s higher cascade layer (`design-system.components` vs. `.mx-checkbox`'s `design-system.atoms`) overrides only what the card treatment needs to change (position, padding, border).
|
|
8
|
+
|
|
9
|
+
`.mx-choice-card` is shared with `RadioCard` — it lives in `src/Form/form.css` alongside the base `.mx-checkbox`/`.mx-radio` rules, not in a per-component CSS file, since the two components' card treatments were identical enough to not warrant separate copies.
|
|
10
|
+
|
|
11
|
+
## CSS Import
|
|
12
|
+
|
|
13
|
+
```css
|
|
14
|
+
@import "@pnx-mixtape/mxds/src/Form/form.css";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Twig
|
|
20
|
+
|
|
21
|
+
```twig
|
|
22
|
+
{# Single card #}
|
|
23
|
+
{% include '@mixtape/Form/CheckboxCard/checkbox-card.twig' with {
|
|
24
|
+
id: 'pay-carer',
|
|
25
|
+
name: 'payments',
|
|
26
|
+
value: 'Carer Payment or Allowance',
|
|
27
|
+
label: 'Carer Payment or Carer Allowance',
|
|
28
|
+
} %}
|
|
29
|
+
|
|
30
|
+
{# With a secondary hint line under the label #}
|
|
31
|
+
{% include '@mixtape/Form/CheckboxCard/checkbox-card.twig' with {
|
|
32
|
+
id: 'pay-other',
|
|
33
|
+
name: 'payments',
|
|
34
|
+
value: 'Another income support payment',
|
|
35
|
+
label: 'Another income support payment',
|
|
36
|
+
hint: 'You will be asked which one before you submit',
|
|
37
|
+
} %}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**checkbox-card.twig props:**
|
|
41
|
+
|
|
42
|
+
| Prop | Type | Required | Description |
|
|
43
|
+
| ---------------- | ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
44
|
+
| `id` | `string` | Yes | Input id — also used to derive the `aria-describedby` ids (`help-{id}`, `status-{id}`) |
|
|
45
|
+
| `name` | `string` | Yes | `name` attribute on the input |
|
|
46
|
+
| `value` | `string` | Yes | `value` attribute on the input |
|
|
47
|
+
| `label` | `string` | Yes | Label text rendered inside `<label for="{id}">` |
|
|
48
|
+
| `hint` | `string` | No | Secondary line rendered under the label, inside the card |
|
|
49
|
+
| `checked` | `boolean` | No | Adds `checked="checked"` to the input |
|
|
50
|
+
| `required` | `boolean` | No | Adds the `required` attribute |
|
|
51
|
+
| `status` | `FormStatusTypes` | No | `invalid` adds the `error` class and `aria-invalid="true"`; `valid` adds the `success` class. Defaults to `default` |
|
|
52
|
+
| `hasDescription` | `boolean` | No | When `true`, `aria-describedby` is wired up to `help-{id}` (and `status-{id}` when `status` is set) |
|
|
53
|
+
| `attributes` | `Attribute` | No | Optional Drupal-style attributes object — `mx-checkbox mx-choice-card` (and the status class) is always merged on top |
|
|
54
|
+
|
|
55
|
+
For a group of cards, compose several `checkbox-card.twig` includes inside a plain `<fieldset>`, same as `Checkbox` — see HTML below.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## HTML
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<fieldset class="mx-form-item mx-fieldset mx-fieldset--plain">
|
|
63
|
+
<legend class="mx-legend">Which payments do you currently receive?</legend>
|
|
64
|
+
|
|
65
|
+
<div class="mx-checkbox mx-choice-card">
|
|
66
|
+
<input type="checkbox" id="pay-age" name="payments" value="Age Pension" />
|
|
67
|
+
<label for="pay-age">Age Pension</label>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="mx-checkbox mx-choice-card">
|
|
71
|
+
<input type="checkbox" id="pay-other" name="payments" value="Another income support payment" />
|
|
72
|
+
<label for="pay-other">
|
|
73
|
+
Another income support payment
|
|
74
|
+
<span class="mx-choice-card__hint">You will be asked which one before you submit</span>
|
|
75
|
+
</label>
|
|
76
|
+
</div>
|
|
77
|
+
</fieldset>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### "Or" divider between two option groups
|
|
81
|
+
|
|
82
|
+
`.mx-choice-divider` renders a horizontal rule with centred text — useful for splitting a set of card choices into "pick one of these — or — pick this other option" (see the source mockup's card question). It's plain markup, not tied to `CheckboxCard`/`RadioCard` specifically:
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
<div class="mx-choice-divider">or</div>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## React
|
|
91
|
+
|
|
92
|
+
### Standalone card
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { SingleCheckboxCard } from "@pnx-mixtape/mxds/react"
|
|
96
|
+
|
|
97
|
+
;<SingleCheckboxCard value="carer">Carer Payment or Carer Allowance</SingleCheckboxCard>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Card group
|
|
101
|
+
|
|
102
|
+
Use `FormCheckboxCard` as the group wrapper and `CheckboxCard` for each option — same pattern as `FormCheckbox`/`Checkbox`.
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { FormCheckboxCard, CheckboxCard } from "@pnx-mixtape/mxds/react"
|
|
106
|
+
|
|
107
|
+
;<FormCheckboxCard label="Which payments do you currently receive?" onChange={setSelected}>
|
|
108
|
+
<CheckboxCard value="age">Age Pension</CheckboxCard>
|
|
109
|
+
<CheckboxCard value="dsp">Disability Support Pension</CheckboxCard>
|
|
110
|
+
<CheckboxCard value="other" hint="You will be asked which one before you submit">
|
|
111
|
+
Another income support payment
|
|
112
|
+
</CheckboxCard>
|
|
113
|
+
</FormCheckboxCard>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**`FormCheckboxCard` props:** all `CheckboxGroupProps` from react-stately + `label: string`
|
|
117
|
+
|
|
118
|
+
**`CheckboxCard` props:** all `AriaCheckboxGroupItemProps` + `hint?: string`, `status?: FormStatus`, `className?: string`
|
|
119
|
+
|
|
120
|
+
**`SingleCheckboxCard` props:** all `AriaCheckboxProps` + `hint?: string`, `status?: FormStatus`, `className?: string`
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### React ⇄ Twig parity
|
|
125
|
+
|
|
126
|
+
The prop shapes intentionally diverge, same as `Checkbox`/`Radio`:
|
|
127
|
+
|
|
128
|
+
- Twig's `label` becomes React `children` — react-aria composes the accessible name from rendered content, not a string prop.
|
|
129
|
+
- Twig's `id`/`name`/`checked`/`required`/`value` are handled by the react-aria hooks (`useCheckboxGroupItem`/`useCheckbox`) via prop spread — they aren't declared as explicit props because react-aria owns generating/wiring them (e.g. `id` comes from `useId()`).
|
|
130
|
+
- Twig's `hasDescription` manually wires `aria-describedby` to a `help-{id}` node; React doesn't need it because react-aria's hooks generate and wire that association themselves when a description is passed through their own props.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Modifiers
|
|
135
|
+
|
|
136
|
+
No BEM modifier classes — `error`/`success` are applied as plain (unprefixed) status classes, matching `Checkbox`'s convention, driven by the `status` prop/Twig var.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Accessibility
|
|
141
|
+
|
|
142
|
+
- The input stays a real, focusable `<input type="checkbox">` — only its default appearance is replaced (`appearance: none` plus a custom checked-state image), so it keeps native checkbox semantics, keyboard support (<kbd>Space</kbd> to toggle), and form submission behaviour.
|
|
143
|
+
- The whole card is clickable because the `<label>` fills it — the input itself only needs to be as large as `--control-size` dictates, not the full card.
|
|
144
|
+
- The focus ring (`:focus-visible`) renders on the input itself, inside the card, rather than around the whole card — keeps the indicator anchored to the actual interactive control.
|
|
145
|
+
- `hint` text is inside the same `<label>` as the primary text, so both are announced together when the control receives focus — don't rely on `hint` to convey anything the primary `label` text doesn't already establish.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Customisation
|
|
150
|
+
|
|
151
|
+
| Custom property | Applies to | Default |
|
|
152
|
+
| ------------------------- | ----------------------------------------------------------------- | --------------------------------------------------- |
|
|
153
|
+
| `--choice-padding-block` | `.mx-choice-card` | `var(--spacing-s)` |
|
|
154
|
+
| `--choice-padding-inline` | `.mx-choice-card` | `var(--spacing-m)` |
|
|
155
|
+
| `--choice-colour-active` | `.mx-choice-card` | `var(--colour-primary)` |
|
|
156
|
+
| `--control-size` | `.mx-choice-card input`, and `.mx-checkbox`'s own input styling | `var(--spacing-m)` (shared with `Checkbox`/`Radio`) |
|
|
157
|
+
| `--line-width` | `.mx-choice-card`'s border, and `.mx-checkbox`'s own input border | `2px` |
|
|
158
|
+
|
|
159
|
+
```css
|
|
160
|
+
.theme--health-services .mx-choice-card {
|
|
161
|
+
--choice-colour-active: var(--colour-primary);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`Checkbox Card 1`] = `
|
|
4
|
+
"
|
|
5
|
+
<div class="mx-checkbox mx-choice-card">
|
|
6
|
+
<input id="pay-carer" type="checkbox" name="payments" value="Carer Payment or Allowance">
|
|
7
|
+
<label for="pay-carer">
|
|
8
|
+
Carer Payment or Carer Allowance
|
|
9
|
+
</label>
|
|
10
|
+
</div>
|
|
11
|
+
"
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
exports[`Checked 1`] = `
|
|
15
|
+
"
|
|
16
|
+
<div class="mx-checkbox mx-choice-card">
|
|
17
|
+
<input id="pay-carer" type="checkbox" name="payments" value="Carer Payment or Allowance" checked="checked">
|
|
18
|
+
<label for="pay-carer">
|
|
19
|
+
Carer Payment or Carer Allowance
|
|
20
|
+
</label>
|
|
21
|
+
</div>
|
|
22
|
+
"
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
exports[`Divider 1`] = `
|
|
26
|
+
"
|
|
27
|
+
<div class="mx-checkbox mx-choice-card">
|
|
28
|
+
<input id="card-yes" type="checkbox" name="card" value="Yes, I hold a card">
|
|
29
|
+
<label for="card-yes">
|
|
30
|
+
Yes, I hold a card
|
|
31
|
+
<span class="mx-choice-card__hint">Pensioner Concession, Health Care or DVA card</span> </label>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="mx-choice-divider">or</div>
|
|
34
|
+
<div class="mx-checkbox mx-choice-card">
|
|
35
|
+
<input id="card-unsure" type="checkbox" name="card" value="Not sure">
|
|
36
|
+
<label for="card-unsure">
|
|
37
|
+
I am not sure
|
|
38
|
+
<span class="mx-choice-card__hint">We will ask a few more questions to work it out</span> </label>
|
|
39
|
+
</div>
|
|
40
|
+
"
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
exports[`Invalid 1`] = `
|
|
44
|
+
"
|
|
45
|
+
<div class="mx-checkbox mx-choice-card">
|
|
46
|
+
<input id="pay-carer" type="checkbox" name="unique-0" value="Carer Payment or Allowance" aria-describedby="unique-0" aria-invalid="true">
|
|
47
|
+
<label for="pay-carer">
|
|
48
|
+
Carer Payment or Carer Allowance
|
|
49
|
+
</label>
|
|
50
|
+
</div>
|
|
51
|
+
"
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
exports[`With Hint 1`] = `
|
|
55
|
+
"
|
|
56
|
+
<div class="mx-checkbox mx-choice-card">
|
|
57
|
+
<input id="pay-other" type="checkbox" name="payments" value="Another income support payment">
|
|
58
|
+
<label for="pay-other">
|
|
59
|
+
Another income support payment
|
|
60
|
+
<span class="mx-choice-card__hint">You will be asked which one before you submit</span> </label>
|
|
61
|
+
</div>
|
|
62
|
+
"
|
|
63
|
+
`;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`Group 1`] = `"<fieldset class="mx-form-item mx-fieldset" role="group" id="react-aria-_r_6_"><legend class="mx-legend">Which payments do you currently receive?</legend><div class="mx-checkbox mx-choice-card"><input id="_r_b_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="age"><label for="_r_b_">Age Pension</label></div><div class="mx-checkbox mx-choice-card"><input id="_r_e_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="dsp"><label for="_r_e_">Disability Support Pension</label></div><div class="mx-checkbox mx-choice-card"><input id="_r_h_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="carer"><label for="_r_h_">Carer Payment or Carer Allowance</label></div><div class="mx-checkbox mx-choice-card"><input id="_r_k_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="other"><label for="_r_k_">Another income support payment<span class="mx-choice-card__hint">You will be asked which one before you submit</span></label></div></fieldset>"`;
|
|
4
|
+
|
|
5
|
+
exports[`Single 1`] = `"<div class="mx-checkbox mx-choice-card"><input id="_r_0_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="carer"><label for="_r_0_">Carer Payment or Carer Allowance</label></div>"`;
|
|
6
|
+
|
|
7
|
+
exports[`With Hint 1`] = `"<div class="mx-checkbox mx-choice-card"><input id="_r_3_" data-react-aria-pressable="true" tabindex="0" type="checkbox" value="other"><label for="_r_3_">Another income support payment<span class="mx-choice-card__hint">You will be asked which one before you submit</span></label></div>"`;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{% set classes = [
|
|
2
|
+
'mx-checkbox',
|
|
3
|
+
'mx-choice-card',
|
|
4
|
+
] %}
|
|
5
|
+
{% set attributes = (attributes ?? create_attribute()).addClass(classes) %}
|
|
6
|
+
{% set status = status|default("default") %}
|
|
7
|
+
{% if hasDescription or status != "default" %}
|
|
8
|
+
{% set helpers = [
|
|
9
|
+
hasDescription ? "help-"~ id : null,
|
|
10
|
+
status != "default" ? "status-"~ id : null,
|
|
11
|
+
]|join(" ")|trim() %}
|
|
12
|
+
{% endif %}
|
|
13
|
+
|
|
14
|
+
<div{{ attributes }}>
|
|
15
|
+
<input
|
|
16
|
+
id="{{ id }}"
|
|
17
|
+
type="checkbox"
|
|
18
|
+
name="{{ name }}"
|
|
19
|
+
value="{{ value }}"
|
|
20
|
+
{% if checked %} checked="checked"{% endif %}
|
|
21
|
+
{% if helpers is not empty %} aria-describedby="{{ helpers }}"{% endif %}
|
|
22
|
+
{% if status == "invalid" %} aria-invalid="true"{% endif %}
|
|
23
|
+
{% if required %} required{% endif %}
|
|
24
|
+
>
|
|
25
|
+
<label for="{{ id }}">
|
|
26
|
+
{{ label }}
|
|
27
|
+
{% if hint %}<span class="mx-choice-card__hint">{{ hint }}</span>{% endif %}
|
|
28
|
+
</label>
|
|
29
|
+
</div>
|
|
@@ -31,7 +31,7 @@ exports[`Error 1`] = `
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
<input class="mx-input__text
|
|
34
|
+
<input class="mx-input__text" id="form-item" name="unique-0" type="text" value="Musicals" placeholder="eg. Funk, Soul, Blues.." aria-describedby="unique-0" aria-invalid="true">
|
|
35
35
|
|
|
36
36
|
<div class="mx-description" id="help-form-item">
|
|
37
37
|
Help text that provides additional information about the field.
|
|
@@ -118,7 +118,7 @@ exports[`Success 1`] = `
|
|
|
118
118
|
|
|
119
119
|
|
|
120
120
|
|
|
121
|
-
<input class="mx-input__text
|
|
121
|
+
<input class="mx-input__text" id="form-item" name="unique-0" type="text" value="Jazz" placeholder="eg. Funk, Soul, Blues.." aria-describedby="unique-0">
|
|
122
122
|
|
|
123
123
|
<div class="mx-description" id="help-form-item">
|
|
124
124
|
Help text that provides additional information about the field.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/html-vite"
|
|
2
|
+
import Component from "./one-time-code.twig"
|
|
3
|
+
import "../form.css"
|
|
4
|
+
import "./one-time-code.css"
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Component> = {
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
component: Component,
|
|
9
|
+
args: {
|
|
10
|
+
id: "otp",
|
|
11
|
+
name: "otp",
|
|
12
|
+
label: "One-time code",
|
|
13
|
+
length: 6,
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default meta
|
|
18
|
+
type Story = StoryObj<typeof meta>
|
|
19
|
+
|
|
20
|
+
export const OneTimeCode: Story = {}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react-vite"
|
|
2
|
+
import Component from "./OneTimeCode"
|
|
3
|
+
import "./one-time-code.css"
|
|
4
|
+
import { Page } from "../../../.storybook-react/decorators"
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Component> = {
|
|
7
|
+
decorators: [Page],
|
|
8
|
+
component: Component,
|
|
9
|
+
args: {
|
|
10
|
+
id: "otp",
|
|
11
|
+
name: "otp",
|
|
12
|
+
"aria-label": "One-time code",
|
|
13
|
+
length: 6,
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default meta
|
|
18
|
+
export const OneTimeCode: StoryObj<typeof Component> = {}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { JSX, ComponentPropsWithoutRef, useState, useMemo, ChangeEventHandler } from "react"
|
|
2
|
+
import classNames from "classnames"
|
|
3
|
+
import { FormStatus } from "../../enums"
|
|
4
|
+
|
|
5
|
+
type OneTimeCodeProps = ComponentPropsWithoutRef<"input"> & {
|
|
6
|
+
value?: string
|
|
7
|
+
className?: string
|
|
8
|
+
status?: FormStatus
|
|
9
|
+
/** Number of digits the code contains. Drives `maxLength` and the field's width. */
|
|
10
|
+
length?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const OneTimeCode = ({
|
|
14
|
+
id,
|
|
15
|
+
required = false,
|
|
16
|
+
value = "",
|
|
17
|
+
status = FormStatus.DEFAULT,
|
|
18
|
+
name = null,
|
|
19
|
+
className = "",
|
|
20
|
+
length = 6,
|
|
21
|
+
style,
|
|
22
|
+
...props
|
|
23
|
+
}: OneTimeCodeProps): JSX.Element => {
|
|
24
|
+
const [inputValue, setInputValue] = useState<string>(value)
|
|
25
|
+
const handleChange: ChangeEventHandler<HTMLInputElement> = ({ currentTarget }) =>
|
|
26
|
+
setInputValue(currentTarget.value.replace(/\D/g, ""))
|
|
27
|
+
|
|
28
|
+
const classes = classNames("mx-input__text", "mx-one-time-code", "mx-validate", className)
|
|
29
|
+
const otpStyle = useMemo(
|
|
30
|
+
() => ({ ...style, "--one-time-code-length": length }) as never,
|
|
31
|
+
[style, length],
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<input
|
|
36
|
+
className={classes}
|
|
37
|
+
style={otpStyle}
|
|
38
|
+
onChange={handleChange}
|
|
39
|
+
id={id}
|
|
40
|
+
type="text"
|
|
41
|
+
inputMode="numeric"
|
|
42
|
+
autoComplete="one-time-code"
|
|
43
|
+
pattern="[0-9]*"
|
|
44
|
+
minLength={length}
|
|
45
|
+
maxLength={length}
|
|
46
|
+
name={name}
|
|
47
|
+
value={inputValue}
|
|
48
|
+
required={required}
|
|
49
|
+
aria-describedby={`help-${id}`}
|
|
50
|
+
aria-invalid={status === FormStatus.INVALID}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default OneTimeCode
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# OneTimeCode
|
|
2
|
+
|
|
3
|
+
A single `<input>` for one-time-code / 2FA entry — `autocomplete="one-time-code"` gets native SMS autofill and password-manager support, without splitting the field into six separate inputs. Centred, letter-spaced digits stand in for the usual per-box divider look. See [Form/README.md](../README.md#the-composition-pattern) for the surrounding `FormItem`/`FormLabel`/`FormDescription` pattern.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## CSS Import
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
@import "@pnx-mixtape/mxds/src/Form/OneTimeCode/one-time-code.css";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Twig
|
|
16
|
+
|
|
17
|
+
```twig
|
|
18
|
+
{% include '@mixtape/Form/OneTimeCode/one-time-code.twig' with {
|
|
19
|
+
id: 'otp',
|
|
20
|
+
name: 'otp',
|
|
21
|
+
label: 'One-time code',
|
|
22
|
+
length: 6,
|
|
23
|
+
} %}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## HTML
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<input
|
|
32
|
+
class="mx-input__text mx-one-time-code mx-validate"
|
|
33
|
+
id="otp"
|
|
34
|
+
name="otp"
|
|
35
|
+
type="text"
|
|
36
|
+
inputmode="numeric"
|
|
37
|
+
autocomplete="one-time-code"
|
|
38
|
+
pattern="[0-9]*"
|
|
39
|
+
minlength="6"
|
|
40
|
+
maxlength="6"
|
|
41
|
+
aria-label="One-time code"
|
|
42
|
+
/>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## React
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { OneTimeCode } from "@pnx-mixtape/mxds/react"
|
|
51
|
+
|
|
52
|
+
;<OneTimeCode id="otp" name="otp" aria-label="One-time code" length={6} />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**`OneTimeCode` props:** all native `<input>` attributes + `status?: FormStatus` (default: `DEFAULT`) + `length?: number` (default: `6`, sets `minLength`/`maxLength` and the field's width — see Customisation)
|
|
56
|
+
|
|
57
|
+
Renders `mx-input__text mx-one-time-code mx-validate`. Sets `aria-describedby` and `aria-invalid` automatically, same as `FormText`. Strips non-digit characters on change.
|
|
58
|
+
|
|
59
|
+
### React ⇄ Twig parity
|
|
60
|
+
|
|
61
|
+
- `id`/`name`/`required` flow through React via `ComponentPropsWithoutRef<"input">` (spread onto the `<input>`, not named in `OneTimeCodeProps`) rather than explicit props — same shape as `FormText`. Twig takes them as named template variables. Functionally identical, not a real divergence.
|
|
62
|
+
- `label`/`hasDescription` are Twig-only helpers that build the `aria-label`/`aria-describedby` wiring from a single include call, matching `TextInput`'s Twig template. React expects those same ARIA attributes passed directly (`aria-label`, `aria-describedby`) since JSX has no template-include layer to generate them from.
|
|
63
|
+
- `length` sizes the field via the `--one-time-code-length` custom property (React sets it through `style`, Twig through a `style` attribute on the `<input>`) — both stacks resolve `length` the same way. CSS can't read the `maxlength` attribute's value into a `calc()`, so this is the one place the component reaches for an inline style rather than a class.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Modifiers
|
|
68
|
+
|
|
69
|
+
This component has no BEM modifier classes — it composes `mx-input__text`'s shared border/background/status styling from `Form/form.css` (see [Form/README.md](../README.md#input-modifier-classes)) and adds `mx-one-time-code` for the centred, letter-spaced digit layout. Unlike `FormText`, `mx-validate` is always applied (not opt-in) — a code field's native constraints (`pattern`, `minlength`/`maxlength`, `required`) are worth surfacing live as the user types, and there's no plausible case for wanting a OneTimeCode field that never colours on completion.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Accessibility
|
|
74
|
+
|
|
75
|
+
- `autocomplete="one-time-code"` is what lets browsers/OSes autofill an SMS-delivered code into this single field — the reason to prefer one input over six. See [WHATWG HTML: autofill field `one-time-code`](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill-field-one-time-code).
|
|
76
|
+
- `inputmode="numeric"` brings up a numeric keypad on mobile without changing the semantic `type="text"` (using `type="number"` would strip leading zeros and add spinner controls).
|
|
77
|
+
- Always pair with a visible or `aria-label`led label; centred digits alone don't announce "one-time code" to screen readers.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Customisation
|
|
82
|
+
|
|
83
|
+
`--one-time-code-length` (default `6`) sizes the field and is set per-instance via the `length` prop/variable, not something you override directly in CSS — see React ⇄ Twig parity, above. Letter-spacing and `tabular-nums` digit width can be tuned by overriding `letter-spacing`/`font-variant-numeric` on `.mx-one-time-code` as with any other input.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`One Time Code 1`] = `
|
|
4
|
+
"
|
|
5
|
+
<input class="mx-input__text mx-one-time-code mx-validate" id="otp" name="otp" type="text" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]*" minlength="6" maxlength="6" style="--one-time-code-length: 6;" value="" aria-label="One-time code">
|
|
6
|
+
"
|
|
7
|
+
`;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`One Time Code 1`] = `"<input class="mx-input__text mx-one-time-code mx-validate" id="otp" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]*" minlength="6" maxlength="6" aria-describedby="help-otp" aria-invalid="false" aria-label="One-time code" type="text" value="" name="otp" style="--one-time-code-length: 6;">"`;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OneTimeCode
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
@layer design-system.components {
|
|
6
|
+
/* @public --one-time-code-length */
|
|
7
|
+
.mx-one-time-code {
|
|
8
|
+
inline-size: calc((var(--one-time-code-length, 6) * 2.5ch) + 1ch);
|
|
9
|
+
text-align: center;
|
|
10
|
+
letter-spacing: 0.5ch;
|
|
11
|
+
font-variant-numeric: tabular-nums;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{% set classes = [
|
|
2
|
+
'mx-input__text',
|
|
3
|
+
'mx-one-time-code',
|
|
4
|
+
'mx-validate',
|
|
5
|
+
] %}
|
|
6
|
+
{% set attributes = (attributes ?? create_attribute()).addClass(classes) %}
|
|
7
|
+
{% set status = status|default("default") %}
|
|
8
|
+
{% set length = length|default(6) %}
|
|
9
|
+
{% if hasDescription or status != "default" %}
|
|
10
|
+
{% set helpers = [
|
|
11
|
+
hasDescription ? "help-"~ id : null,
|
|
12
|
+
status != "default" ? "status-"~ id : null,
|
|
13
|
+
]|join(" ")|trim() %}
|
|
14
|
+
{% endif %}
|
|
15
|
+
|
|
16
|
+
<input{{ attributes }}
|
|
17
|
+
id="{{ id }}"
|
|
18
|
+
name="{{ name }}"
|
|
19
|
+
type="text"
|
|
20
|
+
inputmode="numeric"
|
|
21
|
+
autocomplete="one-time-code"
|
|
22
|
+
pattern="[0-9]*"
|
|
23
|
+
minlength="{{ length }}"
|
|
24
|
+
maxlength="{{ length }}"
|
|
25
|
+
style="--one-time-code-length: {{ length }};"
|
|
26
|
+
value="{{ value }}"
|
|
27
|
+
{% if label %} aria-label="{{ label }}"{% endif %}
|
|
28
|
+
{% if helpers is not empty %} aria-describedby="{{ helpers }}"{% endif %}
|
|
29
|
+
{% if status == "invalid" %} aria-invalid="true"{% endif %}
|
|
30
|
+
{% if required %} required{% endif %}
|
|
31
|
+
/>
|