@tpzdsp/next-toolkit 2.5.1 → 3.0.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 +32 -0
- package/src/components/ErrorModal/ErrorModal.stories.tsx +101 -0
- package/src/components/ErrorModal/ErrorModal.tsx +120 -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 +4 -2
- package/src/components/Footer/Footer.stories.tsx +24 -0
- package/src/components/Footer/Footer.tsx +41 -0
- package/src/components/Footer/Licence.tsx +18 -0
- package/src/components/{layout/footer → Footer}/MetaLinks.tsx +8 -3
- package/src/components/Footer/Small/SmallFooter.tsx +43 -42
- 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 +303 -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 +114 -0
- package/src/components/Modal/Modal.stories.tsx +41 -160
- package/src/components/Modal/Modal.tsx +132 -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 +194 -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 +291 -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 +342 -61
- package/src/errors/errorDisplay.ts +70 -0
- package/src/errors/index.ts +1 -0
- package/src/http/constants.ts +1 -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/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,120 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
import { LuCircleAlert } from 'react-icons/lu';
|
|
6
|
+
|
|
7
|
+
import { formatErrorForClipboard, getErrorDisplayInfo } from '../../errors/errorDisplay';
|
|
8
|
+
import { Button } from '../Button/Button';
|
|
9
|
+
import { CopyButton } from '../CopyButton/CopyButton';
|
|
10
|
+
import { Details, DetailsContent, DetailsTrigger } from '../Details/Details';
|
|
11
|
+
import { HintText } from '../HintText/HintText';
|
|
12
|
+
import { Stack } from '../Layout/Stack/Stack';
|
|
13
|
+
import {
|
|
14
|
+
Modal,
|
|
15
|
+
ModalContent,
|
|
16
|
+
ModalDescription,
|
|
17
|
+
ModalFooter,
|
|
18
|
+
ModalHeader,
|
|
19
|
+
ModalTitle,
|
|
20
|
+
} from '../Modal/Modal';
|
|
21
|
+
import {
|
|
22
|
+
SummaryList,
|
|
23
|
+
SummaryListKey,
|
|
24
|
+
SummaryListRow,
|
|
25
|
+
SummaryListValue,
|
|
26
|
+
} from '../SummaryList/SummaryList';
|
|
27
|
+
|
|
28
|
+
export type ErrorModalProps = Omit<ComponentProps<typeof Modal>, 'children'> & {
|
|
29
|
+
/** The error that occurred. When set, an expandable "Technical information"
|
|
30
|
+
* section shows its message/details/digest, with a button to copy all of
|
|
31
|
+
* it (including the stack trace, when available) to the clipboard. */
|
|
32
|
+
error?: unknown;
|
|
33
|
+
title?: ReactNode;
|
|
34
|
+
children?: ReactNode;
|
|
35
|
+
closeLabel?: ReactNode;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const ErrorModal = ({
|
|
39
|
+
error,
|
|
40
|
+
title = 'An error occurred',
|
|
41
|
+
children,
|
|
42
|
+
closeLabel = 'Close',
|
|
43
|
+
onOpenChange,
|
|
44
|
+
...props
|
|
45
|
+
}: ErrorModalProps) => {
|
|
46
|
+
const info = error !== undefined ? getErrorDisplayInfo(error) : null;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Modal onOpenChange={onOpenChange} {...props}>
|
|
50
|
+
<ModalContent className="cn-error-modal">
|
|
51
|
+
<div className="cn-error-modal-icon">
|
|
52
|
+
<LuCircleAlert aria-hidden className="size-8" />
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div className="cn-error-modal-body">
|
|
56
|
+
<ModalHeader>
|
|
57
|
+
<ModalTitle>{title}</ModalTitle>
|
|
58
|
+
</ModalHeader>
|
|
59
|
+
|
|
60
|
+
<ModalDescription>
|
|
61
|
+
<Stack gap="sm">
|
|
62
|
+
{children}
|
|
63
|
+
|
|
64
|
+
{info ? (
|
|
65
|
+
<Details>
|
|
66
|
+
<DetailsTrigger>Technical information</DetailsTrigger>
|
|
67
|
+
|
|
68
|
+
<DetailsContent>
|
|
69
|
+
<Stack gap="sm">
|
|
70
|
+
<div className="cn-error-modal-details">
|
|
71
|
+
<SummaryList>
|
|
72
|
+
<SummaryListRow>
|
|
73
|
+
<SummaryListKey>Reason</SummaryListKey>
|
|
74
|
+
|
|
75
|
+
<SummaryListValue>{info.message}</SummaryListValue>
|
|
76
|
+
</SummaryListRow>
|
|
77
|
+
|
|
78
|
+
{info.details ? (
|
|
79
|
+
<SummaryListRow>
|
|
80
|
+
<SummaryListKey>Details</SummaryListKey>
|
|
81
|
+
|
|
82
|
+
<SummaryListValue>{info.details}</SummaryListValue>
|
|
83
|
+
</SummaryListRow>
|
|
84
|
+
) : null}
|
|
85
|
+
|
|
86
|
+
{info.digest ? (
|
|
87
|
+
<SummaryListRow>
|
|
88
|
+
<SummaryListKey>Digest</SummaryListKey>
|
|
89
|
+
|
|
90
|
+
<SummaryListValue>{info.digest}</SummaryListValue>
|
|
91
|
+
</SummaryListRow>
|
|
92
|
+
) : null}
|
|
93
|
+
</SummaryList>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div className="flex flex-col gap-gds-1">
|
|
97
|
+
<CopyButton
|
|
98
|
+
appearance="secondary"
|
|
99
|
+
content={formatErrorForClipboard(info)}
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
<HintText className="text-sm">
|
|
103
|
+
Copy to share error details with the app administrator.
|
|
104
|
+
</HintText>
|
|
105
|
+
</div>
|
|
106
|
+
</Stack>
|
|
107
|
+
</DetailsContent>
|
|
108
|
+
</Details>
|
|
109
|
+
) : null}
|
|
110
|
+
</Stack>
|
|
111
|
+
</ModalDescription>
|
|
112
|
+
|
|
113
|
+
<ModalFooter className="sm:justify-start">
|
|
114
|
+
<Button onClick={() => onOpenChange?.(false)}>{closeLabel}</Button>
|
|
115
|
+
</ModalFooter>
|
|
116
|
+
</div>
|
|
117
|
+
</ModalContent>
|
|
118
|
+
</Modal>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* Meant to stay mounted at all times (even with no error, as empty
|
|
3
|
+
children), rather than being conditionally rendered, so it's a stable
|
|
4
|
+
role="alert" live region screen readers can reliably announce content
|
|
5
|
+
changes on. A live region needs to already exist in the DOM before its
|
|
6
|
+
content changes for that announcement to be reliable, inserting a
|
|
7
|
+
brand new role="alert" node after the fact is a recognised trigger too,
|
|
8
|
+
but is markedly less consistent across AT/browser combinations.
|
|
9
|
+
Consumers that always render a fixed, non-empty message (e.g.
|
|
10
|
+
ErrorFallback) are unaffected by the rule below, it only ever matches
|
|
11
|
+
when there's genuinely no content. */
|
|
12
|
+
.cn-error-text {
|
|
13
|
+
@apply text-error font-bold;
|
|
14
|
+
|
|
15
|
+
/* Cancels out the gap-gds-1 that FormGroup/FieldSet's flex layout would
|
|
16
|
+
otherwise still apply around this element even while it's empty,
|
|
17
|
+
leaving no permanent dead space when there's no error to show. */
|
|
18
|
+
&:empty {
|
|
19
|
+
margin-top: calc(var(--spacing-gds-1) * -1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -1,27 +1,30 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
1
3
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
4
|
|
|
3
5
|
import { ErrorText } from './ErrorText';
|
|
4
|
-
import {
|
|
6
|
+
import { Button } from '../Button/Button';
|
|
7
|
+
import { FormGroup } from '../Form/FormGroup';
|
|
8
|
+
import { Input } from '../Input/Input';
|
|
9
|
+
import { Label } from '../Label/Label';
|
|
5
10
|
|
|
6
11
|
const meta: Meta<typeof ErrorText> = {
|
|
7
12
|
title: 'Components/ErrorText',
|
|
8
13
|
component: ErrorText,
|
|
14
|
+
tags: ['autodocs'],
|
|
9
15
|
parameters: {
|
|
10
|
-
|
|
16
|
+
docs: {
|
|
17
|
+
description: {
|
|
18
|
+
component:
|
|
19
|
+
'Show an error message next to the field and in the Error summary component when there is a validation error. Renders role="alert" (plus explicit aria-live/aria-atomic). Always render ErrorText, even with no error (empty children), rather than conditionally mounting it: a live region needs to already exist in the DOM before its content changes for screen readers to reliably announce that change, inserting a brand new role="alert" node after the fact is a recognised trigger too, but is markedly less consistent. The empty state collapses to zero height and cancels out FormGroup/FieldSet\'s own gap, so it never leaves a permanent gap when there\'s nothing to show.',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
11
22
|
},
|
|
12
|
-
tags: ['autodocs'],
|
|
13
23
|
argTypes: {
|
|
14
|
-
children: {
|
|
15
|
-
description: 'The content to display for the error',
|
|
16
|
-
control: false,
|
|
17
|
-
},
|
|
18
|
-
className: {
|
|
19
|
-
description: 'Additional TailwindCSS classes to apply',
|
|
20
|
-
control: 'text',
|
|
21
|
-
},
|
|
24
|
+
children: { control: 'text' },
|
|
22
25
|
},
|
|
23
26
|
args: {
|
|
24
|
-
children: '
|
|
27
|
+
children: 'The date your passport was issued must be in the past',
|
|
25
28
|
},
|
|
26
29
|
};
|
|
27
30
|
|
|
@@ -31,22 +34,34 @@ type Story = StoryObj<typeof ErrorText>;
|
|
|
31
34
|
|
|
32
35
|
export const Default: Story = {};
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const ToggleableExample = () => {
|
|
38
|
+
const [hasError, setHasError] = useState(false);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="flex flex-col gap-gds-4">
|
|
42
|
+
<FormGroup invalid={hasError}>
|
|
43
|
+
<Label htmlFor="full-name">Full name</Label>
|
|
44
|
+
|
|
45
|
+
<Input id="full-name" aria-invalid={hasError} aria-describedby="full-name-error" />
|
|
46
|
+
|
|
47
|
+
<ErrorText id="full-name-error">{hasError ? 'Enter your full name' : ''}</ErrorText>
|
|
48
|
+
</FormGroup>
|
|
49
|
+
|
|
50
|
+
<Button onClick={() => setHasError((current) => !current)} className="self-start">
|
|
51
|
+
Toggle error
|
|
52
|
+
</Button>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
39
55
|
};
|
|
40
56
|
|
|
41
|
-
export const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
</div>
|
|
50
|
-
),
|
|
57
|
+
export const AlwaysMounted: Story = {
|
|
58
|
+
parameters: {
|
|
59
|
+
docs: {
|
|
60
|
+
description: {
|
|
61
|
+
story:
|
|
62
|
+
"ErrorText stays mounted the whole time here, only its children toggle between empty and the message, click the button to see there's no gap left behind when it's empty.",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
51
65
|
},
|
|
66
|
+
render: () => <ToggleableExample />,
|
|
52
67
|
};
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { cn } from '../../utils';
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import { cn } from '../../utils/components';
|
|
4
|
+
import { Paragraph } from '../Paragraph/Paragraph';
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
const Component = typeof children === 'string' ? 'p' : 'div';
|
|
6
|
+
export type ErrorTextProps = ComponentProps<typeof Paragraph>;
|
|
8
7
|
|
|
8
|
+
export const ErrorText = ({ className, children, ...props }: ErrorTextProps) => {
|
|
9
9
|
return (
|
|
10
|
-
<
|
|
10
|
+
<Paragraph
|
|
11
11
|
role="alert"
|
|
12
|
-
|
|
12
|
+
aria-live="assertive"
|
|
13
|
+
aria-atomic="true"
|
|
14
|
+
className={cn('cn-error-text', className)}
|
|
13
15
|
{...props}
|
|
14
16
|
>
|
|
15
17
|
{children}
|
|
16
|
-
</
|
|
18
|
+
</Paragraph>
|
|
17
19
|
);
|
|
18
20
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { ExternalLink } from './ExternalLink';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof ExternalLink> = {
|
|
6
|
+
title: 'Components/ExternalLink',
|
|
7
|
+
component: ExternalLink,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'Use ExternalLink for navigation that should open in a new tab. Generally avoid opening links in a new tab or window, however, if needed, aim to include the words `opens in new tab` in the link. This is the default variant of this component.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
argTypes: {
|
|
18
|
+
variant: {
|
|
19
|
+
control: { type: 'radio' },
|
|
20
|
+
options: ['default', 'icon', 'text'],
|
|
21
|
+
},
|
|
22
|
+
href: { control: 'text' },
|
|
23
|
+
children: { control: 'text' },
|
|
24
|
+
},
|
|
25
|
+
args: {
|
|
26
|
+
href: '#',
|
|
27
|
+
children: 'Story link',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default meta;
|
|
32
|
+
|
|
33
|
+
type Story = StoryObj<typeof ExternalLink>;
|
|
34
|
+
|
|
35
|
+
export const Default: Story = {};
|
|
36
|
+
|
|
37
|
+
export const WithIcon: Story = {
|
|
38
|
+
args: { variant: 'icon' },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const WithNothing: Story = {
|
|
42
|
+
args: { variant: 'plain' },
|
|
43
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { LuExternalLink } from 'react-icons/lu';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../utils/components';
|
|
6
|
+
import { Link } from '../Link/Link';
|
|
7
|
+
|
|
8
|
+
export type ExternalLinkVariants = 'plain' | 'icon' | 'text';
|
|
9
|
+
|
|
10
|
+
export type ExternalLinkProps = Omit<ComponentProps<typeof Link>, 'target' | 'rel' | 'variant'> & {
|
|
11
|
+
variant?: ExternalLinkVariants;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const ExternalLink = ({
|
|
15
|
+
children,
|
|
16
|
+
variant = 'text',
|
|
17
|
+
className,
|
|
18
|
+
...props
|
|
19
|
+
}: ExternalLinkProps) => {
|
|
20
|
+
const content = {
|
|
21
|
+
plain: null,
|
|
22
|
+
icon: (
|
|
23
|
+
<span className="cn-external-link-icon-wrapper">
|
|
24
|
+
<span className="sr-only">(opens in new tab)</span>
|
|
25
|
+
|
|
26
|
+
<LuExternalLink aria-hidden className="cn-external-link-icon" />
|
|
27
|
+
</span>
|
|
28
|
+
),
|
|
29
|
+
text: <span> (opens in new tab)</span>,
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Link
|
|
34
|
+
{...props}
|
|
35
|
+
className={cn('cn-external-link', className)}
|
|
36
|
+
rel="noopener noreferrer"
|
|
37
|
+
target="_blank"
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
{content[variant]}
|
|
41
|
+
</Link>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: Fieldset
|
|
3
|
+
Reset browser fieldset defaults. Browsers apply borders,
|
|
4
|
+
padding, and an unusual min-width that breaks flex layouts. */
|
|
5
|
+
.cn-field-set {
|
|
6
|
+
@apply min-w-0 m-0 p-0 border-0 flex flex-col gap-gds-2;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* MARK: Legend
|
|
10
|
+
Plain body text by default, matching GDS's own unmodified
|
|
11
|
+
govuk-fieldset__legend (and govuk-label): no bold, same size as a
|
|
12
|
+
label (govuk-fieldset__legend and govuk-label both use govuk-font(19)
|
|
13
|
+
in real GDS, this project's text-lg), not automatically a heading. For
|
|
14
|
+
a heading-like look, nest a real Heading component inside FieldLegend
|
|
15
|
+
instead, mirroring GDS's own documented pattern of nesting an <h1>
|
|
16
|
+
inside <legend> on question-per-page forms, rather than baking
|
|
17
|
+
heading-mimicking size variants into the legend itself. */
|
|
18
|
+
/* margin-bottom (not the fieldset's own gap-gds-2): a <legend> is exempt
|
|
19
|
+
from its fieldset's flex gap. Per spec, a flex/grid fieldset lays out
|
|
20
|
+
its OTHER children inside an anonymous wrapper box, legend sits
|
|
21
|
+
outside it as its own block box, so gap, which only bridges children
|
|
22
|
+
within the same flex/grid formatting context, never reaches the
|
|
23
|
+
space between legend and whatever follows it, unlike FormGroup's
|
|
24
|
+
Label, an ordinary element with no such carve-out, which does get its
|
|
25
|
+
gap-gds-1 for free. Matches gap-gds-2 so a legend reads the same as
|
|
26
|
+
any other item-to-item gap in the set. */
|
|
27
|
+
.cn-field-legend {
|
|
28
|
+
@apply block box-border max-w-full p-0 text-lg text-body whitespace-normal;
|
|
29
|
+
margin-bottom: var(--spacing-gds-2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* GDS tightens the gap specifically when a group-level hint sits directly
|
|
33
|
+
under a plain legend (margin-top: govuk-spacing(-1) on the hint). This
|
|
34
|
+
stacks with cn-field-legend's own margin-bottom above (flex item
|
|
35
|
+
margins never collapse), landing on a net gds-1 (5px) rather than the
|
|
36
|
+
usual gds-2 (10px) between legend and controls. */
|
|
37
|
+
.cn-field-legend + .cn-hint-text {
|
|
38
|
+
margin-top: calc(var(--spacing-gds-1) * -1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* MARK: Divider
|
|
42
|
+
Appears between groups of controls, typically contains "or".
|
|
43
|
+
Width matches GDS (40px). Sits naturally in the item flex-col gap. */
|
|
44
|
+
.cn-field-divider {
|
|
45
|
+
@apply w-10 text-center text-base font-normal;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* MARK: Item
|
|
49
|
+
Mirrors GDS's govuk-radios__item/govuk-checkboxes__item exactly: flex
|
|
50
|
+
with wrap, not grid. Control and label sit side by side on the first
|
|
51
|
+
line; the hint (if any) is forced onto its own wrapped line by w-full,
|
|
52
|
+
a flex item at 100% width can't share a line with anything else. Unlike
|
|
53
|
+
grid, flex has no shared column to auto-align the hint into under the
|
|
54
|
+
label, so its start padding has to repeat the control's width plus the
|
|
55
|
+
item's own gap-x by hand, same as GDS hardcodes touch-target + label
|
|
56
|
+
padding rather than measuring anything. margin-top is negative for the
|
|
57
|
+
same reason GDS's own hint margin-top is: it sits closer to the label
|
|
58
|
+
above it than a hint normally would on its own.
|
|
59
|
+
|
|
60
|
+
No align-items override here, same as GDS's own item wrapper: the
|
|
61
|
+
control (Radio/Checkbox) has an explicit, non-auto height, so the
|
|
62
|
+
default stretch alignment has no size to stretch it into and falls
|
|
63
|
+
back to flex-start, top-aligning it for free. The label gets its own
|
|
64
|
+
align-self: center instead (see below), matching GDS's own
|
|
65
|
+
govuk-radios__label/govuk-checkboxes__label. Centering only the label,
|
|
66
|
+
not the row, is what keeps both cases correct at once: a short label
|
|
67
|
+
centers against the control's full height same as before, and a
|
|
68
|
+
wrapping label, being the row's own tallest item once it wraps,
|
|
69
|
+
already fills the full row height, so centering it is a no-op and it
|
|
70
|
+
stays flush with the control's top instead of floating down between
|
|
71
|
+
the lines the way row-level centering would. */
|
|
72
|
+
.cn-field-item {
|
|
73
|
+
@apply flex flex-wrap gap-x-gds-2;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* min-w-0 flex-1: without this, a long label wraps as a whole flex item
|
|
77
|
+
instead of wrapping its own text, a flex item's default min-width is
|
|
78
|
+
auto, not 0, so it won't shrink below its label text's full unwrapped
|
|
79
|
+
width, and once that doesn't fit next to the control on the row,
|
|
80
|
+
flex-wrap moves the entire label below the control rather than
|
|
81
|
+
letting the label's own text wrap while staying beside it. flex-1
|
|
82
|
+
(not just min-w-0) is what makes the label actually claim the row's
|
|
83
|
+
remaining width to wrap within, rather than shrinking to its own
|
|
84
|
+
content and only wrapping once genuinely out of room.
|
|
85
|
+
|
|
86
|
+
self-center: see the item comment above. */
|
|
87
|
+
.cn-field-item > .cn-label {
|
|
88
|
+
@apply min-w-0 flex-1 self-center;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.cn-field-item > .cn-hint-text {
|
|
92
|
+
@apply w-full;
|
|
93
|
+
margin-top: calc(var(--spacing-gds-1) * -1);
|
|
94
|
+
padding-inline-start: calc(2.5rem + var(--spacing-gds-2));
|
|
95
|
+
padding-inline-end: var(--spacing-gds-3);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Small Radio/Checkbox (cn-radio-size-sm/cn-checkbox-size-sm) are 1.5rem,
|
|
99
|
+
not the base 2.5rem, so the hint's offset needs to match. */
|
|
100
|
+
.cn-field-item:has(.cn-radio-size-sm, .cn-checkbox-size-sm) > .cn-hint-text {
|
|
101
|
+
padding-inline-start: calc(1.5rem + var(--spacing-gds-2));
|
|
102
|
+
}
|
|
103
|
+
}
|