@tpzdsp/next-toolkit 2.5.1 → 3.1.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/package.json +21 -12
- package/src/assets/styles/globals.css +75 -95
- package/src/assets/styles/ol.css +57 -16
- package/src/assets/styles/utils.css +80 -0
- package/src/components/Accordion/Accordion.css +85 -0
- package/src/components/Accordion/Accordion.stories.tsx +264 -0
- package/src/components/Accordion/Accordion.tsx +98 -0
- package/src/components/Alert/Alert.css +129 -0
- package/src/components/Alert/Alert.stories.tsx +291 -0
- package/src/components/Alert/Alert.tsx +76 -0
- package/src/components/BackLink/BackLink.css +12 -0
- package/src/components/BackLink/BackLink.stories.tsx +52 -0
- package/src/components/BackLink/BackLink.tsx +44 -0
- package/src/components/BackToTop/BackToTop.css +23 -0
- package/src/components/BackToTop/BackToTop.stories.tsx +41 -0
- package/src/components/BackToTop/BackToTop.tsx +48 -0
- package/src/components/Breadcrumb/Breadcrumb.css +42 -0
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +94 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +135 -0
- package/src/components/Button/Button.css +229 -0
- package/src/components/Button/Button.stories.tsx +140 -20
- package/src/components/Button/Button.tsx +53 -28
- package/src/components/Button/ButtonSkeleton.stories.tsx +50 -0
- package/src/components/Button/ButtonSkeleton.tsx +14 -0
- package/src/components/ButtonGroup/ButtonGroup.css +52 -0
- package/src/components/ButtonGroup/ButtonGroup.stories.tsx +156 -0
- package/src/components/ButtonGroup/ButtonGroup.tsx +50 -0
- package/src/components/ButtonLink/ButtonLink.css +114 -0
- package/src/components/ButtonLink/ButtonLink.stories.tsx +38 -51
- package/src/components/ButtonLink/ButtonLink.tsx +54 -23
- package/src/components/Card/Card.css +13 -0
- package/src/components/Card/Card.stories.tsx +98 -57
- package/src/components/Card/Card.tsx +4 -13
- package/src/components/CardGroup/CardGroup.css +10 -0
- package/src/components/CardGroup/CardGroup.stories.tsx +81 -0
- package/src/components/CardGroup/CardGroup.tsx +9 -0
- package/src/components/Checkbox/Checkbox.css +49 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +86 -0
- package/src/components/Checkbox/Checkbox.tsx +32 -0
- package/src/components/Chip/Chip.css +54 -0
- package/src/components/Chip/Chip.stories.tsx +94 -0
- package/src/components/Chip/Chip.tsx +34 -0
- package/src/components/CookieBanner/CookieBanner.css +19 -0
- package/src/components/CookieBanner/CookieBanner.stories.tsx +52 -0
- package/src/components/CookieBanner/CookieBanner.tsx +55 -0
- package/src/components/CopyButton/CopyButton.stories.tsx +28 -0
- package/src/components/CopyButton/CopyButton.tsx +65 -0
- package/src/components/DateInput/DateInput.css +13 -0
- package/src/components/DateInput/DateInput.stories.tsx +131 -0
- package/src/components/DateInput/DateInput.test.tsx +21 -0
- package/src/components/DateInput/DateInput.tsx +98 -0
- package/src/components/Details/Details.css +28 -0
- package/src/components/Details/Details.stories.tsx +48 -0
- package/src/components/Details/Details.tsx +41 -0
- package/src/components/Dropdown/Dropdown.css +81 -0
- package/src/components/Dropdown/Dropdown.stories.tsx +131 -0
- package/src/components/Dropdown/Dropdown.tsx +69 -0
- package/src/components/ErrorBoundary/ErrorBoundary.stories.tsx +4 -39
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +7 -4
- package/src/components/ErrorBoundary/ErrorFallback.tsx +32 -66
- package/src/components/ErrorModal/ErrorModal.css +34 -0
- package/src/components/ErrorModal/ErrorModal.stories.tsx +100 -0
- package/src/components/ErrorModal/ErrorModal.tsx +123 -0
- package/src/components/ErrorText/ErrorText.css +22 -0
- package/src/components/ErrorText/ErrorText.stories.tsx +42 -27
- package/src/components/ErrorText/ErrorText.tsx +10 -8
- package/src/components/ExternalLink/ExternalLink.css +12 -0
- package/src/components/ExternalLink/ExternalLink.stories.tsx +43 -0
- package/src/components/ExternalLink/ExternalLink.tsx +43 -0
- package/src/components/Fieldset/Fieldset.css +103 -0
- package/src/components/Fieldset/Fieldset.stories.tsx +304 -0
- package/src/components/Fieldset/Fieldset.tsx +38 -0
- package/src/components/FileUpload/FileUpload.css +85 -0
- package/src/components/FileUpload/FileUpload.stories.tsx +106 -0
- package/src/components/FileUpload/FileUpload.tsx +99 -0
- package/src/components/{layout/footer → Footer}/Copyright.tsx +3 -2
- package/src/components/Footer/Footer.stories.tsx +27 -0
- package/src/components/Footer/Footer.tsx +46 -0
- package/src/components/Footer/Licence.tsx +18 -0
- package/src/components/Footer/MetaLinks.tsx +47 -0
- package/src/components/Footer/Small/SmallFooter.stories.tsx +3 -0
- package/src/components/Footer/Small/SmallFooter.tsx +53 -41
- package/src/components/Form/FormGroup.css +18 -0
- package/src/components/Form/FormGroup.stories.tsx +87 -0
- package/src/components/Form/FormGroup.tsx +16 -0
- package/src/components/{layout/header → Header}/Header.stories.tsx +26 -12
- package/src/components/Header/Header.test.tsx +87 -0
- package/src/components/Header/Header.tsx +304 -0
- package/src/components/Header/HeaderAppContent.tsx +120 -0
- package/src/components/Heading/Heading.css +40 -0
- package/src/components/Heading/Heading.stories.tsx +15 -5
- package/src/components/Heading/Heading.tsx +21 -44
- package/src/components/Heading/HeadingSkeleton.stories.tsx +30 -0
- package/src/components/Heading/HeadingSkeleton.tsx +14 -0
- package/src/components/Hero/Hero.css +19 -0
- package/src/components/Hero/Hero.stories.tsx +41 -0
- package/src/components/Hero/Hero.tsx +14 -0
- package/src/components/HintText/HintText.css +5 -0
- package/src/components/HintText/HintText.stories.tsx +29 -0
- package/src/components/HintText/HintText.tsx +13 -0
- package/src/components/InfoBox/InfoBox.css +38 -0
- package/src/components/InfoBox/InfoBox.stories.tsx +51 -403
- package/src/components/InfoBox/InfoBox.tsx +31 -158
- package/src/components/Input/Input.css +77 -0
- package/src/components/Input/Input.stories.tsx +123 -0
- package/src/components/Input/Input.tsx +74 -0
- package/src/components/InputGroup/InputGroup.css +140 -0
- package/src/components/InputGroup/InputGroup.stories.tsx +125 -0
- package/src/components/InputGroup/InputGroup.tsx +47 -0
- package/src/components/InsetText/InsetText.css +5 -0
- package/src/components/InsetText/InsetText.stories.tsx +30 -0
- package/src/components/InsetText/InsetText.tsx +9 -0
- package/src/components/Keyboard/Keyboard.css +9 -0
- package/src/components/Keyboard/Keyboard.stories.tsx +63 -0
- package/src/components/Keyboard/Keyboard.tsx +30 -0
- package/src/components/Label/Label.css +27 -0
- package/src/components/Label/Label.stories.tsx +40 -0
- package/src/components/Label/Label.tsx +17 -0
- package/src/components/LayerSwitcher/LayerSwitcher.css +59 -0
- package/src/components/LayerSwitcher/LayerSwitcher.stories.tsx +112 -0
- package/src/components/LayerSwitcher/LayerSwitcher.tsx +61 -0
- package/src/components/Layout/401/401.stories.tsx +23 -0
- package/src/components/Layout/401/401.tsx +1 -0
- package/src/components/Layout/403/403.stories.tsx +23 -0
- package/src/components/Layout/403/403.tsx +21 -0
- package/src/components/Layout/404/404.stories.tsx +23 -0
- package/src/components/Layout/404/404.tsx +28 -0
- package/src/components/Layout/500/500.stories.tsx +56 -0
- package/src/components/Layout/500/500.tsx +63 -0
- package/src/components/Layout/ButtonGroup/ButtonGroup.css +52 -0
- package/src/components/Layout/ButtonGroup/ButtonGroup.stories.tsx +156 -0
- package/src/components/Layout/ButtonGroup/ButtonGroup.tsx +50 -0
- package/src/components/Layout/HeadingGroup/HeadingGroup.css +21 -0
- package/src/components/Layout/HeadingGroup/HeadingGroup.stories.tsx +84 -0
- package/src/components/Layout/HeadingGroup/HeadingGroup.tsx +9 -0
- package/src/components/Layout/MainWrapper/MainWrapper.css +12 -0
- package/src/components/Layout/MainWrapper/MainWrapper.stories.tsx +36 -0
- package/src/components/Layout/MainWrapper/MainWrapper.tsx +14 -0
- package/src/components/Layout/PageShell/PageShell.css +42 -0
- package/src/components/Layout/PageShell/PageShell.stories.tsx +108 -0
- package/src/components/Layout/PageShell/PageShell.tsx +31 -0
- package/src/components/Layout/ParagraphGroup/ParagraphGroup.css +5 -0
- package/src/components/Layout/ParagraphGroup/ParagraphGroup.stories.tsx +34 -0
- package/src/components/Layout/ParagraphGroup/ParagraphGroup.tsx +9 -0
- package/src/components/Layout/Stack/Stack.css +29 -0
- package/src/components/Layout/Stack/Stack.stories.tsx +83 -0
- package/src/components/Layout/Stack/Stack.tsx +19 -0
- package/src/components/Layout/WidthContainer/WidthContainer.css +17 -0
- package/src/components/Layout/WidthContainer/WidthContainer.stories.tsx +59 -0
- package/src/components/Layout/WidthContainer/WidthContainer.tsx +13 -0
- package/src/components/Legend/Legend.css +95 -0
- package/src/components/Legend/Legend.stories.tsx +81 -0
- package/src/components/Legend/Legend.tsx +32 -0
- package/src/components/Legend/LegendDot.tsx +15 -0
- package/src/components/Legend/LegendPanel.stories.tsx +101 -0
- package/src/components/Legend/LegendPanel.tsx +68 -0
- package/src/components/Legend/LegendSkeleton.stories.tsx +87 -0
- package/src/components/Legend/LegendSkeleton.tsx +73 -0
- package/src/components/LegendSelect/LegendSelect.css +8 -0
- package/src/components/LegendSelect/LegendSelect.stories.tsx +80 -0
- package/src/components/LegendSelect/LegendSelect.tsx +26 -0
- package/src/components/Link/Link.css +98 -0
- package/src/components/Link/Link.stories.tsx +70 -0
- package/src/components/Link/Link.tsx +52 -0
- package/src/components/LinkButton/LinkButton.css +67 -0
- package/src/components/LinkButton/LinkButton.stories.tsx +90 -42
- package/src/components/LinkButton/LinkButton.tsx +50 -63
- package/src/components/LinkList/LinkList.css +28 -0
- package/src/components/LinkList/LinkList.stories.tsx +46 -0
- package/src/components/LinkList/LinkList.tsx +23 -0
- package/src/components/List/Li.tsx +9 -0
- package/src/components/List/List.css +9 -0
- package/src/components/List/OrderedList.stories.tsx +36 -0
- package/src/components/List/OrderedList.tsx +9 -0
- package/src/components/List/UnorderedList.stories.tsx +36 -0
- package/src/components/List/UnorderedList.tsx +9 -0
- package/src/components/LoadingBox/LoadingBox.css +27 -0
- package/src/components/LoadingBox/LoadingBox.stories.tsx +66 -0
- package/src/components/LoadingBox/LoadingBox.tsx +40 -0
- package/src/components/Modal/Modal.css +111 -0
- package/src/components/Modal/Modal.stories.tsx +94 -155
- package/src/components/Modal/Modal.tsx +141 -75
- package/src/components/NoScriptBanner/NoScriptBanner.css +17 -0
- package/src/components/NoScriptBanner/NoScriptBanner.stories.tsx +46 -0
- package/src/components/NoScriptBanner/NoScriptBanner.tsx +26 -0
- package/src/components/Paragraph/Paragraph.css +22 -0
- package/src/components/Paragraph/Paragraph.stories.tsx +1 -42
- package/src/components/Paragraph/Paragraph.tsx +4 -3
- package/src/components/Paragraph/ParagraphSkeleton.stories.tsx +44 -0
- package/src/components/Paragraph/ParagraphSkeleton.tsx +33 -0
- package/src/components/PasswordInput/PasswordInput.css +3 -0
- package/src/components/PasswordInput/PasswordInput.stories.tsx +47 -0
- package/src/components/PasswordInput/PasswordInput.tsx +45 -0
- package/src/components/PhaseBanner/PhaseBanner.css +12 -0
- package/src/components/PhaseBanner/PhaseBanner.stories.tsx +61 -0
- package/src/components/PhaseBanner/PhaseBanner.tsx +17 -0
- package/src/components/Popover/Popover.css +43 -0
- package/src/components/Popover/Popover.stories.tsx +102 -0
- package/src/components/Popover/Popover.tsx +40 -0
- package/src/components/Radio/Radio.css +69 -0
- package/src/components/Radio/Radio.stories.tsx +169 -0
- package/src/components/Radio/Radio.tsx +56 -0
- package/src/components/Resizable/Resizable.css +86 -0
- package/src/components/Resizable/Resizable.stories.tsx +123 -0
- package/src/components/Resizable/Resizable.tsx +86 -0
- package/src/components/ScrollArea/ScrollArea.css +9 -0
- package/src/components/ScrollArea/ScrollArea.stories.tsx +42 -0
- package/src/components/ScrollArea/ScrollArea.tsx +11 -0
- package/src/components/SearchBox/SearchBox.stories.tsx +88 -0
- package/src/components/SearchBox/SearchBox.tsx +36 -0
- package/src/components/Select/AsyncSelect.stories.tsx +84 -0
- package/src/components/Select/AsyncSelect.tsx +41 -0
- package/src/components/Select/Select.css +202 -0
- package/src/components/Select/Select.stories.tsx +162 -0
- package/src/components/Select/Select.test.tsx +36 -0
- package/src/components/Select/Select.tsx +296 -0
- package/src/components/Select/SelectSkeleton.stories.tsx +51 -0
- package/src/components/Select/SelectSkeleton.tsx +23 -0
- package/src/components/Separator/Separator.css +41 -0
- package/src/components/Separator/Separator.stories.tsx +63 -0
- package/src/components/Separator/Separator.tsx +42 -0
- package/src/components/Sidebar/ResizableSidebar.tsx +118 -0
- package/src/components/Sidebar/Sidebar.css +203 -0
- package/src/components/Sidebar/Sidebar.stories.tsx +438 -0
- package/src/components/Sidebar/Sidebar.tsx +225 -0
- package/src/components/Skeleton/Skeleton.css +16 -0
- package/src/components/Skeleton/Skeleton.stories.tsx +39 -0
- package/src/components/Skeleton/Skeleton.tsx +12 -0
- package/src/components/SkipLink/SkipLink.css +16 -0
- package/src/components/SkipLink/SkipLink.stories.tsx +40 -0
- package/src/components/SkipLink/SkipLink.tsx +15 -0
- package/src/components/Spinner/Spinner.css +30 -0
- package/src/components/Spinner/Spinner.stories.tsx +36 -0
- package/src/components/Spinner/Spinner.tsx +45 -0
- package/src/components/StartButton/StartButton.css +13 -0
- package/src/components/StartButton/StartButton.stories.tsx +68 -0
- package/src/components/StartButton/StartButton.tsx +50 -0
- package/src/components/SubmitButton/SubmitButton.stories.tsx +31 -0
- package/src/components/SubmitButton/SubmitButton.tsx +14 -0
- package/src/components/SummaryList/SummaryList.css +71 -0
- package/src/components/SummaryList/SummaryList.stories.tsx +211 -0
- package/src/components/SummaryList/SummaryList.tsx +41 -0
- package/src/components/SummaryList/SummaryListSkeleton.stories.tsx +67 -0
- package/src/components/SummaryList/SummaryListSkeleton.tsx +40 -0
- package/src/components/Tabs/Tabs.css +84 -0
- package/src/components/Tabs/Tabs.stories.tsx +133 -0
- package/src/components/Tabs/Tabs.tsx +66 -0
- package/src/components/Tag/Tag.css +8 -0
- package/src/components/Tag/Tag.stories.tsx +96 -0
- package/src/components/Tag/Tag.tsx +34 -0
- package/src/components/Textarea/Textarea.css +38 -0
- package/src/components/Textarea/Textarea.stories.tsx +131 -0
- package/src/components/Textarea/Textarea.tsx +50 -0
- package/src/components/TextareaCount/TextareaCount.css +17 -0
- package/src/components/TextareaCount/TextareaCount.stories.tsx +131 -0
- package/src/components/TextareaCount/TextareaCount.tsx +108 -0
- package/src/components/Tooltip/Tooltip.css +43 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +110 -0
- package/src/components/Tooltip/Tooltip.tsx +53 -0
- package/src/components/WarningText/WarningText.css +20 -0
- package/src/components/WarningText/WarningText.stories.tsx +29 -0
- package/src/components/WarningText/WarningText.tsx +17 -0
- package/src/components/index.ts +343 -61
- package/src/errors/errorDisplay.ts +70 -0
- package/src/errors/index.ts +1 -0
- package/src/http/constants.ts +2 -0
- package/src/http/fetch.ts +7 -3
- package/src/map/LayerSwitcherPanel.tsx +2 -173
- package/src/map/LegendShim.tsx +2 -0
- package/src/map/MapComponent.tsx +7 -17
- package/src/map/MapControlsOverlay.tsx +68 -175
- package/src/map/Popup.tsx +71 -24
- package/src/map/geocoder/Geocoder.test.tsx +46 -3
- package/src/map/geocoder/Geocoder.tsx +155 -278
- package/src/map/index.ts +1 -1
- package/src/types/utils.ts +0 -10
- package/src/utils/components.ts +96 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/utils.ts +0 -12
- package/src/assets/images/defra-logo.svg +0 -51
- package/src/assets/images/ea-logo.svg +0 -58
- package/src/assets/images/ogl.svg +0 -1
- package/src/components/Button/Button.test.tsx +0 -53
- package/src/components/ButtonLink/ButtonLink.test.tsx +0 -154
- package/src/components/Card/Card.test.tsx +0 -51
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +0 -75
- package/src/components/ErrorBoundary/ErrorFallback.test.tsx +0 -107
- package/src/components/ErrorText/ErrorText.test.tsx +0 -33
- package/src/components/Heading/Heading.test.tsx +0 -22
- package/src/components/Hint/Hint.stories.tsx +0 -58
- package/src/components/Hint/Hint.test.tsx +0 -33
- package/src/components/Hint/Hint.tsx +0 -12
- package/src/components/InfoBox/InfoBox.test.tsx +0 -330
- package/src/components/InfoBox/types.ts +0 -6
- package/src/components/LinkButton/LinkButton.test.tsx +0 -177
- package/src/components/Modal/Modal.test.tsx +0 -246
- package/src/components/NotificationBanner/NotificationBanner.stories.tsx +0 -45
- package/src/components/NotificationBanner/NotificationBanner.test.tsx +0 -58
- package/src/components/NotificationBanner/NotificationBanner.tsx +0 -45
- package/src/components/Paragraph/Paragraph.test.tsx +0 -10
- package/src/components/SlidingPanel/SlidingPanel.stories.tsx +0 -129
- package/src/components/SlidingPanel/SlidingPanel.test.tsx +0 -83
- package/src/components/SlidingPanel/SlidingPanel.tsx +0 -258
- package/src/components/accordion/Accordion.stories.tsx +0 -234
- package/src/components/accordion/Accordion.test.tsx +0 -192
- package/src/components/accordion/Accordion.tsx +0 -73
- package/src/components/backToTop/BackToTop.stories.tsx +0 -407
- package/src/components/backToTop/BackToTop.test.tsx +0 -57
- package/src/components/backToTop/BackToTop.tsx +0 -134
- package/src/components/chip/Chip.stories.tsx +0 -61
- package/src/components/chip/Chip.test.tsx +0 -31
- package/src/components/chip/Chip.tsx +0 -23
- package/src/components/container/Container.tsx +0 -42
- package/src/components/cookieBanner/CookieBanner.stories.tsx +0 -257
- package/src/components/cookieBanner/CookieBanner.test.tsx +0 -68
- package/src/components/cookieBanner/CookieBanner.tsx +0 -73
- package/src/components/divider/RuleDivider.stories.tsx +0 -254
- package/src/components/divider/RuleDivider.test.tsx +0 -164
- package/src/components/divider/RuleDivider.tsx +0 -23
- package/src/components/dropdown/DropdownMenu.test.tsx +0 -211
- package/src/components/dropdown/DropdownMenu.tsx +0 -108
- package/src/components/dropdown/useDropdownMenu.ts +0 -249
- package/src/components/form/Input.stories.tsx +0 -435
- package/src/components/form/Input.test.tsx +0 -206
- package/src/components/form/Input.tsx +0 -23
- package/src/components/form/TextArea.stories.tsx +0 -464
- package/src/components/form/TextArea.test.tsx +0 -232
- package/src/components/form/TextArea.tsx +0 -23
- package/src/components/layout/footer/Footer.tsx +0 -26
- package/src/components/layout/footer/Licence.tsx +0 -19
- package/src/components/layout/footer/MetaLinks.tsx +0 -36
- package/src/components/layout/header/Header.test.tsx +0 -36
- package/src/components/layout/header/Header.tsx +0 -90
- package/src/components/layout/header/HeaderAuthClient.test.tsx +0 -45
- package/src/components/layout/header/HeaderAuthClient.tsx +0 -40
- package/src/components/layout/header/HeaderNavClient.test.tsx +0 -44
- package/src/components/layout/header/HeaderNavClient.tsx +0 -67
- package/src/components/link/ExternalLink.test.tsx +0 -102
- package/src/components/link/ExternalLink.tsx +0 -29
- package/src/components/link/Link.tsx +0 -26
- package/src/components/select/Select.stories.tsx +0 -336
- package/src/components/select/Select.test.tsx +0 -474
- package/src/components/select/Select.tsx +0 -125
- package/src/components/select/SelectSkeleton.stories.tsx +0 -194
- package/src/components/select/SelectSkeleton.test.tsx +0 -104
- package/src/components/select/SelectSkeleton.tsx +0 -23
- package/src/components/select/common.ts +0 -3
- package/src/components/select/index.ts +0 -18
- package/src/components/skipLink/SkipLink.stories.tsx +0 -346
- package/src/components/skipLink/SkipLink.test.tsx +0 -22
- package/src/components/skipLink/SkipLink.tsx +0 -49
- package/src/map/LegendPanel.tsx +0 -85
- /package/src/{components → assets}/images/DefraLogo.tsx +0 -0
- /package/src/{components → assets}/images/EaLogo.tsx +0 -0
- /package/src/{components → assets}/images/OglLogo.tsx +0 -0
- /package/src/components/{googleAnalytics → GoogleAnalytics}/GlobalVars.tsx +0 -0
- /package/src/components/{googleAnalytics → GoogleAnalytics}/GoogleAnalytics.tsx +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { Chip } from './Chip';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Chip> = {
|
|
6
|
+
title: 'Components/Chip',
|
|
7
|
+
component: Chip,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component: 'Displays a chip. Use the Tag component if it has semantic importance.',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
argTypes: {
|
|
17
|
+
appearance: {
|
|
18
|
+
control: { type: 'radio' },
|
|
19
|
+
options: ['neutral', 'success', 'warning', 'error', 'info'],
|
|
20
|
+
},
|
|
21
|
+
size: {
|
|
22
|
+
control: { type: 'radio' },
|
|
23
|
+
options: ['sm', 'base', 'lg'],
|
|
24
|
+
},
|
|
25
|
+
children: { control: 'text' },
|
|
26
|
+
},
|
|
27
|
+
args: {
|
|
28
|
+
children: 'Soil',
|
|
29
|
+
size: 'base',
|
|
30
|
+
appearance: 'info',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
|
|
36
|
+
type Story = StoryObj<typeof Chip>;
|
|
37
|
+
|
|
38
|
+
export const Appearances: Story = {
|
|
39
|
+
parameters: {
|
|
40
|
+
docs: {
|
|
41
|
+
description: {
|
|
42
|
+
story:
|
|
43
|
+
'Subtle tinted backgrounds are used for all semantic appearances to avoid looking interactive. `primary` uses the solid brand colour and is suitable for selected state or active filters.',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
render: (args) => (
|
|
48
|
+
<div className="flex flex-wrap gap-gds-2">
|
|
49
|
+
<Chip {...args} appearance="neutral">
|
|
50
|
+
Neutral
|
|
51
|
+
</Chip>
|
|
52
|
+
|
|
53
|
+
<Chip {...args} appearance="success">
|
|
54
|
+
Success
|
|
55
|
+
</Chip>
|
|
56
|
+
|
|
57
|
+
<Chip {...args} appearance="warning">
|
|
58
|
+
Warning
|
|
59
|
+
</Chip>
|
|
60
|
+
|
|
61
|
+
<Chip {...args} appearance="error">
|
|
62
|
+
Error
|
|
63
|
+
</Chip>
|
|
64
|
+
|
|
65
|
+
<Chip {...args} appearance="info">
|
|
66
|
+
Info
|
|
67
|
+
</Chip>
|
|
68
|
+
</div>
|
|
69
|
+
),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const Small: Story = {
|
|
73
|
+
args: { size: 'sm' },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const Large: Story = {
|
|
77
|
+
args: { size: 'lg' },
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Inline: Story = {
|
|
81
|
+
parameters: {
|
|
82
|
+
docs: {
|
|
83
|
+
description: {
|
|
84
|
+
story:
|
|
85
|
+
'All chips use negative margins so they sit inline within body text without inflating line height.',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
render: (args) => (
|
|
90
|
+
<p>
|
|
91
|
+
Use a keyword like <Chip {...args} /> to narrow your search.
|
|
92
|
+
</p>
|
|
93
|
+
),
|
|
94
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils/components';
|
|
4
|
+
|
|
5
|
+
const CHIP_APPEARANCES = {
|
|
6
|
+
neutral: 'cn-chip-appearance-neutral',
|
|
7
|
+
success: 'cn-chip-appearance-success',
|
|
8
|
+
warning: 'cn-chip-appearance-warning',
|
|
9
|
+
error: 'cn-chip-appearance-error',
|
|
10
|
+
info: 'cn-chip-appearance-info',
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
const CHIP_SIZES = {
|
|
14
|
+
sm: 'cn-chip-size-sm',
|
|
15
|
+
base: 'cn-chip-size-base',
|
|
16
|
+
lg: 'cn-chip-size-lg',
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export type ChipSize = keyof typeof CHIP_SIZES;
|
|
20
|
+
export type ChipAppearances = keyof typeof CHIP_APPEARANCES;
|
|
21
|
+
|
|
22
|
+
export type ChipProps = ComponentProps<'span'> & {
|
|
23
|
+
size?: ChipSize;
|
|
24
|
+
appearance: ChipAppearances;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const Chip = ({ className, size = 'base', appearance, ...props }: ChipProps) => (
|
|
28
|
+
<span
|
|
29
|
+
data-slot="chip"
|
|
30
|
+
data-size={size}
|
|
31
|
+
className={cn('cn-chip', CHIP_SIZES[size], CHIP_APPEARANCES[appearance], className)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: Cookie banner
|
|
3
|
+
Full-width bar at the top of the page. Background and bottom border
|
|
4
|
+
match the GDS govuk-cookie-banner pattern. */
|
|
5
|
+
.cn-cookie-banner {
|
|
6
|
+
@apply bg-surface border-b border-divider py-gds-4;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* MARK: Inner wrapper
|
|
10
|
+
Centres and constrains content. Flex column with gap replaces browser
|
|
11
|
+
default heading/paragraph margins, which vary and break the GDS spacing. */
|
|
12
|
+
.cn-cookie-banner-inner {
|
|
13
|
+
@apply mx-auto max-w-lg px-gds-4 flex flex-col gap-gds-3;
|
|
14
|
+
|
|
15
|
+
& :is(h1, h2, h3, h4, h5, h6, p) {
|
|
16
|
+
@apply m-0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { CookieBanner } from './CookieBanner';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof CookieBanner> = {
|
|
6
|
+
title: 'Components/CookieBanner',
|
|
7
|
+
component: CookieBanner,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component: `
|
|
13
|
+
GDS-style cookie consent banner. Starts hidden (\`display: none\`) and is shown by the external cookie-banner script when appropriate.
|
|
14
|
+
|
|
15
|
+
The \`id="cookie-banner"\` and \`id="accept-all-cookies"\` attributes are required by the script to attach its event handlers.
|
|
16
|
+
|
|
17
|
+
**Props:**
|
|
18
|
+
- \`cookiePolicyUrl\`: URL for your cookie policy page, used in the body text link and the reject button.
|
|
19
|
+
- \`scriptSrc\`: URL of the cookie banner JS. Defaults to the EA shared script.
|
|
20
|
+
`.trim(),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
type Story = StoryObj<typeof CookieBanner>;
|
|
28
|
+
|
|
29
|
+
export const Default: Story = {
|
|
30
|
+
parameters: {
|
|
31
|
+
docs: {
|
|
32
|
+
description: {
|
|
33
|
+
story:
|
|
34
|
+
'Rendered without the external script so the banner is visible. In production it starts hidden and is shown by the cookie-banner script.',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
args: {
|
|
39
|
+
serviceName: 'My service',
|
|
40
|
+
cookiePolicyUrl: 'https://environment.data.gov.uk/help/cookies',
|
|
41
|
+
style: { display: 'block' },
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const CustomBackgroundColor: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
className: 'bg-wash',
|
|
48
|
+
serviceName: 'My service',
|
|
49
|
+
cookiePolicyUrl: 'https://environment.data.gov.uk/help/cookies',
|
|
50
|
+
style: { display: 'block' },
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import Script from 'next/script';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../utils/components';
|
|
6
|
+
import { Button } from '../Button/Button';
|
|
7
|
+
import { ExternalLink } from '../ExternalLink/ExternalLink';
|
|
8
|
+
import { Heading } from '../Heading/Heading';
|
|
9
|
+
import { ButtonGroup } from '../Layout/ButtonGroup/ButtonGroup';
|
|
10
|
+
import { Paragraph } from '../Paragraph/Paragraph';
|
|
11
|
+
|
|
12
|
+
export type CookieBannerProps = ComponentProps<'div'> & {
|
|
13
|
+
serviceName: string;
|
|
14
|
+
cookiePolicyUrl?: string;
|
|
15
|
+
scriptSrc?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const CookieBanner = ({
|
|
19
|
+
serviceName,
|
|
20
|
+
cookiePolicyUrl = 'https://environment.data.gov.uk/help/cookies',
|
|
21
|
+
scriptSrc = 'https://environment.data.gov.uk/shared/cookie-banner.js',
|
|
22
|
+
className,
|
|
23
|
+
style,
|
|
24
|
+
...props
|
|
25
|
+
}: CookieBannerProps) => (
|
|
26
|
+
<div
|
|
27
|
+
id="cookie-banner"
|
|
28
|
+
role="region"
|
|
29
|
+
aria-label="Cookie banner"
|
|
30
|
+
className={cn('cn-cookie-banner', className)}
|
|
31
|
+
style={{ display: 'none', ...style }}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
<Script src={scriptSrc} />
|
|
35
|
+
|
|
36
|
+
<div className="cn-cookie-banner-inner">
|
|
37
|
+
<Heading level="h2">Cookies on {serviceName}</Heading>
|
|
38
|
+
|
|
39
|
+
<Paragraph>We use some essential cookies to make this service work.</Paragraph>
|
|
40
|
+
|
|
41
|
+
<Paragraph>
|
|
42
|
+
We'd also like to use analytics cookies so we can understand how you use the service
|
|
43
|
+
and make improvements.
|
|
44
|
+
</Paragraph>
|
|
45
|
+
|
|
46
|
+
<ButtonGroup>
|
|
47
|
+
<Button id="accept-all-cookies">Accept analytics cookies</Button>
|
|
48
|
+
|
|
49
|
+
<Button id="reject-all-cookies">Reject analytics cookies</Button>
|
|
50
|
+
|
|
51
|
+
<ExternalLink href={cookiePolicyUrl}>View cookies</ExternalLink>
|
|
52
|
+
</ButtonGroup>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { CopyButton } from './CopyButton';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof CopyButton> = {
|
|
6
|
+
title: 'Components/CopyButton',
|
|
7
|
+
component: CopyButton,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component: 'Use a CopyButton to copy some text or other data to the clipboard.',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
argTypes: {
|
|
17
|
+
content: { control: 'text' },
|
|
18
|
+
},
|
|
19
|
+
args: {
|
|
20
|
+
content: 'Copy me',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof CopyButton>;
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState, type ComponentProps } from 'react';
|
|
4
|
+
|
|
5
|
+
import { LuCheck, LuCopy } from 'react-icons/lu';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../../utils/components';
|
|
8
|
+
import { Button } from '../Button/Button';
|
|
9
|
+
|
|
10
|
+
export type CopyButtonProps = Omit<
|
|
11
|
+
ComponentProps<typeof Button>,
|
|
12
|
+
'type' | 'variant' | 'children' | 'onClick' | 'aria-label'
|
|
13
|
+
> & {
|
|
14
|
+
content?: string | ClipboardItem[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const CopyButton = ({ className, content, ...props }: CopyButtonProps) => {
|
|
18
|
+
const [copied, setCopied] = useState<number | null>(null);
|
|
19
|
+
|
|
20
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!copied) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const clear = () => {
|
|
28
|
+
if (timeoutRef.current) {
|
|
29
|
+
clearTimeout(timeoutRef.current);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
clear();
|
|
34
|
+
|
|
35
|
+
timeoutRef.current = setTimeout(() => setCopied(null), copied);
|
|
36
|
+
|
|
37
|
+
return clear;
|
|
38
|
+
}, [copied]);
|
|
39
|
+
|
|
40
|
+
const handleCopy = async () => {
|
|
41
|
+
if (!content) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof content === 'string') {
|
|
46
|
+
await navigator.clipboard.writeText(content);
|
|
47
|
+
} else {
|
|
48
|
+
await navigator.clipboard.write(content);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setCopied(2000);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Button
|
|
56
|
+
variant="icon"
|
|
57
|
+
className={cn(className, 'cn-copy-button')}
|
|
58
|
+
onClick={handleCopy}
|
|
59
|
+
aria-label={copied ? 'Content copied to clipboard' : 'Copy content'}
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
{copied ? <LuCheck aria-hidden /> : <LuCopy aria-hidden />}
|
|
63
|
+
</Button>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: Date input
|
|
3
|
+
Three side-by-side fields following the GDS date input pattern.
|
|
4
|
+
Each field is a column of label + input, the inputs are max-width
|
|
5
|
+
constrained via cn-input-size-date-part and cn-input-size-year. */
|
|
6
|
+
.cn-date-input {
|
|
7
|
+
@apply flex gap-gds-3;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.cn-date-input-field {
|
|
11
|
+
@apply flex flex-col gap-gds-1;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { DateInput } from './DateInput';
|
|
4
|
+
import { ErrorText } from '../ErrorText/ErrorText';
|
|
5
|
+
import { FormGroup } from '../Form/FormGroup';
|
|
6
|
+
import { HintText } from '../HintText/HintText';
|
|
7
|
+
import { Label } from '../Label/Label';
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof DateInput> = {
|
|
10
|
+
title: 'Components/DateInput',
|
|
11
|
+
component: DateInput,
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
parameters: {
|
|
14
|
+
docs: {
|
|
15
|
+
description: {
|
|
16
|
+
component:
|
|
17
|
+
'Three separate text inputs for day, month, and year, following the GDS date input pattern. Uses `type="text"` with `inputMode="numeric"` rather than `type="number"` to avoid browser spinners and allow leading zeros. Each field ID is derived from the `id` prop with a `-day`, `-month`, or `-year` suffix, use these when wiring up `aria-describedby` on error messages. Pass `invalid={true}` to mark all fields, or an object like `{ day: true }` to mark specific ones.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
args: {
|
|
22
|
+
id: 'date',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
|
|
28
|
+
type Story = StoryObj<typeof DateInput>;
|
|
29
|
+
|
|
30
|
+
export const Default: Story = {
|
|
31
|
+
render: (args) => (
|
|
32
|
+
<div className="flex flex-col gap-gds-1">
|
|
33
|
+
<Label htmlFor={`${args.id}-day`}>Date of birth</Label>
|
|
34
|
+
|
|
35
|
+
<DateInput {...args} />
|
|
36
|
+
</div>
|
|
37
|
+
),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const WithDefault: Story = {
|
|
41
|
+
parameters: {
|
|
42
|
+
docs: {
|
|
43
|
+
description: {
|
|
44
|
+
story:
|
|
45
|
+
'Pre-populated using `defaultValue`. The component manages state internally from this point.',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
args: {
|
|
50
|
+
defaultValue: { day: '5', month: '3', year: '1990' },
|
|
51
|
+
},
|
|
52
|
+
render: (args) => (
|
|
53
|
+
<div className="flex flex-col gap-gds-1">
|
|
54
|
+
<Label htmlFor={`${args.id}-day`}>Date of birth</Label>
|
|
55
|
+
|
|
56
|
+
<HintText>For example, 27 3 2007</HintText>
|
|
57
|
+
|
|
58
|
+
<DateInput {...args} />
|
|
59
|
+
</div>
|
|
60
|
+
),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const AllFieldsInvalid: Story = {
|
|
64
|
+
parameters: {
|
|
65
|
+
docs: {
|
|
66
|
+
description: {
|
|
67
|
+
story:
|
|
68
|
+
'Pass `invalid={true}` to mark all three fields with an error border. Used when the date as a whole is invalid (e.g. a date that does not exist).',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
render: (args) => (
|
|
73
|
+
<FormGroup invalid>
|
|
74
|
+
<Label htmlFor={`${args.id}-day`}>Date of birth</Label>
|
|
75
|
+
|
|
76
|
+
<HintText>For example, 27 3 2007</HintText>
|
|
77
|
+
|
|
78
|
+
<ErrorText id="date-error-all">Enter a valid date of birth</ErrorText>
|
|
79
|
+
|
|
80
|
+
<DateInput
|
|
81
|
+
{...args}
|
|
82
|
+
invalid
|
|
83
|
+
defaultValue={{ day: '31', month: '2', year: '1990' }}
|
|
84
|
+
aria-describedby="date-error-all"
|
|
85
|
+
/>
|
|
86
|
+
</FormGroup>
|
|
87
|
+
),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const PartialInvalid: Story = {
|
|
91
|
+
parameters: {
|
|
92
|
+
docs: {
|
|
93
|
+
description: {
|
|
94
|
+
story:
|
|
95
|
+
'Pass an object to `invalid` to highlight only the specific fields that are wrong. Useful when part of the date is missing or implausible.',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
render: (args) => (
|
|
100
|
+
<FormGroup invalid>
|
|
101
|
+
<Label htmlFor={`${args.id}-day`}>Date of birth</Label>
|
|
102
|
+
|
|
103
|
+
<HintText>For example, 27 3 2007</HintText>
|
|
104
|
+
|
|
105
|
+
<ErrorText id="date-error-partial">Date of birth must include a year</ErrorText>
|
|
106
|
+
|
|
107
|
+
<DateInput
|
|
108
|
+
{...args}
|
|
109
|
+
invalid={{ year: true }}
|
|
110
|
+
defaultValue={{ day: '15', month: '6', year: '' }}
|
|
111
|
+
aria-describedby="date-error-partial"
|
|
112
|
+
/>
|
|
113
|
+
</FormGroup>
|
|
114
|
+
),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const Disabled: Story = {
|
|
118
|
+
args: {
|
|
119
|
+
defaultValue: { day: '1', month: '1', year: '2000' },
|
|
120
|
+
disabled: true,
|
|
121
|
+
},
|
|
122
|
+
render: (args) => (
|
|
123
|
+
<div className="flex flex-col gap-gds-1">
|
|
124
|
+
<Label htmlFor={`${args.id}-day`}>Date of birth</Label>
|
|
125
|
+
|
|
126
|
+
<HintText>For example, 27 3 2007</HintText>
|
|
127
|
+
|
|
128
|
+
<DateInput {...args} />
|
|
129
|
+
</div>
|
|
130
|
+
),
|
|
131
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DateInput } from './DateInput';
|
|
2
|
+
import { render, screen } from '../../test/renderers';
|
|
3
|
+
|
|
4
|
+
const ERROR_ID = 'dob-error';
|
|
5
|
+
const ARIA_DESCRIBEDBY = 'aria-describedby';
|
|
6
|
+
|
|
7
|
+
describe('DateInput', () => {
|
|
8
|
+
it('applies aria-describedby to all three day/month/year inputs', () => {
|
|
9
|
+
render(<DateInput id="dob" aria-describedby={ERROR_ID} />);
|
|
10
|
+
|
|
11
|
+
expect(screen.getByLabelText('Day')).toHaveAttribute(ARIA_DESCRIBEDBY, ERROR_ID);
|
|
12
|
+
expect(screen.getByLabelText('Month')).toHaveAttribute(ARIA_DESCRIBEDBY, ERROR_ID);
|
|
13
|
+
expect(screen.getByLabelText('Year')).toHaveAttribute(ARIA_DESCRIBEDBY, ERROR_ID);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('omits aria-describedby when not provided', () => {
|
|
17
|
+
render(<DateInput id="dob" />);
|
|
18
|
+
|
|
19
|
+
expect(screen.getByLabelText('Day')).not.toHaveAttribute(ARIA_DESCRIBEDBY);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, type ChangeEvent } from 'react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../utils/components';
|
|
6
|
+
import { Input } from '../Input/Input';
|
|
7
|
+
import { Label } from '../Label/Label';
|
|
8
|
+
|
|
9
|
+
export type DateValue = {
|
|
10
|
+
day: string;
|
|
11
|
+
month: string;
|
|
12
|
+
year: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type DateInputInvalid = boolean | { day?: boolean; month?: boolean; year?: boolean };
|
|
16
|
+
|
|
17
|
+
export type DateInputProps = {
|
|
18
|
+
id: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
value?: DateValue;
|
|
21
|
+
defaultValue?: DateValue;
|
|
22
|
+
onChange?: (value: DateValue) => void;
|
|
23
|
+
invalid?: DateInputInvalid;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
className?: string;
|
|
26
|
+
/** Applied to all three day/month/year inputs, matching GDS's own date
|
|
27
|
+
* input pattern: one shared error message for the whole date, not one per
|
|
28
|
+
* field, so every field's aria-describedby points at the same id. */
|
|
29
|
+
'aria-describedby'?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const EMPTY_DATE: DateValue = { day: '', month: '', year: '' };
|
|
33
|
+
|
|
34
|
+
const FIELDS = [
|
|
35
|
+
{ key: 'day' as const, label: 'Day', size: 'date-part' as const, maxLength: 2 },
|
|
36
|
+
{ key: 'month' as const, label: 'Month', size: 'date-part' as const, maxLength: 2 },
|
|
37
|
+
{ key: 'year' as const, label: 'Year', size: 'year' as const, maxLength: 4 },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const resolveFieldInvalid = (invalid: DateInputInvalid, field: keyof DateValue): boolean => {
|
|
41
|
+
if (typeof invalid === 'boolean') {
|
|
42
|
+
return invalid;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return invalid[field] ?? false;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const DateInput = ({
|
|
49
|
+
id,
|
|
50
|
+
name,
|
|
51
|
+
value,
|
|
52
|
+
defaultValue,
|
|
53
|
+
onChange,
|
|
54
|
+
invalid = false,
|
|
55
|
+
disabled,
|
|
56
|
+
className,
|
|
57
|
+
'aria-describedby': ariaDescribedBy,
|
|
58
|
+
}: DateInputProps) => {
|
|
59
|
+
const [internalValue, setInternalValue] = useState<DateValue>(defaultValue ?? EMPTY_DATE);
|
|
60
|
+
|
|
61
|
+
const isControlled = value !== undefined;
|
|
62
|
+
const currentValue = isControlled ? value : internalValue;
|
|
63
|
+
|
|
64
|
+
const handleChange = (field: keyof DateValue) => (event: ChangeEvent<HTMLInputElement>) => {
|
|
65
|
+
const newValue = { ...currentValue, [field]: event.target.value };
|
|
66
|
+
|
|
67
|
+
if (!isControlled) {
|
|
68
|
+
setInternalValue(newValue);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onChange?.(newValue);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div data-slot="date-input" className={cn('cn-date-input', className)}>
|
|
76
|
+
{FIELDS.map(({ key, label, size, maxLength }) => (
|
|
77
|
+
<div key={key} className="cn-date-input-field">
|
|
78
|
+
<Label htmlFor={`${id}-${key}`}>{label}</Label>
|
|
79
|
+
|
|
80
|
+
<Input
|
|
81
|
+
type="text"
|
|
82
|
+
inputMode="numeric"
|
|
83
|
+
pattern="[0-9]*"
|
|
84
|
+
id={`${id}-${key}`}
|
|
85
|
+
name={name ? `${name}-${key}` : undefined}
|
|
86
|
+
size={size}
|
|
87
|
+
maxLength={maxLength}
|
|
88
|
+
value={currentValue[key]}
|
|
89
|
+
onChange={handleChange(key)}
|
|
90
|
+
aria-invalid={resolveFieldInvalid(invalid, key) ? 'true' : undefined}
|
|
91
|
+
aria-describedby={ariaDescribedBy}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: Details */
|
|
3
|
+
.cn-details-trigger {
|
|
4
|
+
@apply cn-link-decoration cn-link-appearance-primary text-base gap-gds-1 cursor-pointer flex
|
|
5
|
+
items-center;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* GDS yellow focus box: matches the pattern used by .cn-link */
|
|
9
|
+
.cn-details-trigger:focus {
|
|
10
|
+
@apply text-on-focus bg-focus no-underline outline-focus outline-transparent;
|
|
11
|
+
box-shadow:
|
|
12
|
+
0 -2px var(--color-focus),
|
|
13
|
+
0 4px var(--color-on-focus);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.cn-details-trigger[data-state='open'] .cn-details-trigger-icon {
|
|
17
|
+
@apply rotate-90;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.cn-details-trigger-icon {
|
|
21
|
+
@apply size-[1em] stroke-3;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* MARK: Content */
|
|
25
|
+
.cn-details-content {
|
|
26
|
+
@apply border-l-standard border-divider mt-1.5 py-gds-3 pl-gds-4;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { Details, DetailsContent, DetailsTrigger } from './Details';
|
|
4
|
+
import { Paragraph } from '../Paragraph/Paragraph';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Details> = {
|
|
7
|
+
title: 'Components/Details',
|
|
8
|
+
component: Details,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component:
|
|
14
|
+
'Make a page easier to scan by letting users reveal more detailed information only if they need it.',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
argTypes: {
|
|
19
|
+
children: { control: 'text' },
|
|
20
|
+
},
|
|
21
|
+
args: {
|
|
22
|
+
children: 'Help with nationality',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
|
|
28
|
+
type Story = StoryObj<typeof Details>;
|
|
29
|
+
|
|
30
|
+
export const Default: Story = {
|
|
31
|
+
render: (args) => (
|
|
32
|
+
<Details>
|
|
33
|
+
<DetailsTrigger>{args.children}</DetailsTrigger>
|
|
34
|
+
|
|
35
|
+
<DetailsContent>
|
|
36
|
+
<Paragraph>
|
|
37
|
+
We need to know your nationality so we can work out which elections you’re entitled to
|
|
38
|
+
vote in. If you cannot provide your nationality, you’ll have to send copies of identity
|
|
39
|
+
documents through the post.
|
|
40
|
+
</Paragraph>
|
|
41
|
+
</DetailsContent>
|
|
42
|
+
</Details>
|
|
43
|
+
),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// export const Disabled: Story = {
|
|
47
|
+
// args:{disabled: true}
|
|
48
|
+
// };
|