@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,40 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils/components';
|
|
4
|
+
import { Spinner } from '../Spinner/Spinner';
|
|
5
|
+
|
|
6
|
+
export type LoadingBoxProps = ComponentProps<'div'> & {
|
|
7
|
+
loading?: boolean;
|
|
8
|
+
title?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const LoadingBox = ({
|
|
12
|
+
loading = false,
|
|
13
|
+
title = 'Loading',
|
|
14
|
+
className,
|
|
15
|
+
children,
|
|
16
|
+
...props
|
|
17
|
+
}: LoadingBoxProps) => (
|
|
18
|
+
<div data-slot="loading-box" className={cn('cn-loading-box', className)} {...props}>
|
|
19
|
+
<div
|
|
20
|
+
data-slot="loading-box-overlay"
|
|
21
|
+
data-loading={loading || undefined}
|
|
22
|
+
inert={!loading}
|
|
23
|
+
role="status"
|
|
24
|
+
className="cn-loading-box-overlay"
|
|
25
|
+
>
|
|
26
|
+
<Spinner aria-hidden className="cn-loading-box-spinner" />
|
|
27
|
+
|
|
28
|
+
<span className="sr-only">{title}</span>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
{/* display:contents so this purely exists to carry `inert`, it doesn't
|
|
32
|
+
add a box that could change how children participate in whatever
|
|
33
|
+
layout the consumer's own content establishes. Blocks focus/clicks
|
|
34
|
+
on content that's about to be replaced once loading starts, rather
|
|
35
|
+
than just visually covering it while leaving it reachable. */}
|
|
36
|
+
<div data-slot="loading-box-content" inert={loading} className="contents">
|
|
37
|
+
{children}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/*
|
|
3
|
+
MARK: Modal overlay
|
|
4
|
+
Semi-transparent backdrop. Radix sets data-state to open or closed
|
|
5
|
+
which drives enter and exit fade transitions.
|
|
6
|
+
*/
|
|
7
|
+
.cn-modal-overlay {
|
|
8
|
+
@apply bg-black/50 cn-z-overlay;
|
|
9
|
+
|
|
10
|
+
&[data-state='open'] {
|
|
11
|
+
animation: cn-modal-overlay-in 150ms ease-out;
|
|
12
|
+
}
|
|
13
|
+
&[data-state='closed'] {
|
|
14
|
+
animation: cn-modal-overlay-out 150ms ease-in;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@keyframes cn-modal-overlay-in {
|
|
19
|
+
from {
|
|
20
|
+
opacity: 0;
|
|
21
|
+
}
|
|
22
|
+
to {
|
|
23
|
+
opacity: 1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@keyframes cn-modal-overlay-out {
|
|
28
|
+
from {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
}
|
|
31
|
+
to {
|
|
32
|
+
opacity: 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
MARK: Modal content
|
|
38
|
+
The panel box with white background and GDS form-width border.
|
|
39
|
+
No rounded corners. The fixed position and centring transform are
|
|
40
|
+
applied via Tailwind in the TSX. Only visual appearance is set here.
|
|
41
|
+
|
|
42
|
+
Add a max-w-* class to ModalContent to widen for specific modals.
|
|
43
|
+
*/
|
|
44
|
+
.cn-modal-content {
|
|
45
|
+
@apply panel cn-z-overlay p-gds-4 w-full max-w-sm;
|
|
46
|
+
|
|
47
|
+
&[data-state='open'] {
|
|
48
|
+
animation: cn-modal-content-in 150ms ease-out;
|
|
49
|
+
}
|
|
50
|
+
&[data-state='closed'] {
|
|
51
|
+
animation: cn-modal-content-out 150ms ease-in;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@keyframes cn-modal-content-in {
|
|
56
|
+
from {
|
|
57
|
+
opacity: 0;
|
|
58
|
+
}
|
|
59
|
+
to {
|
|
60
|
+
opacity: 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@keyframes cn-modal-content-out {
|
|
65
|
+
from {
|
|
66
|
+
opacity: 1;
|
|
67
|
+
}
|
|
68
|
+
to {
|
|
69
|
+
opacity: 0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
MARK: Modal close button
|
|
75
|
+
Positioned absolutely in the top-right corner of the panel.
|
|
76
|
+
The parent cn-modal-content has position fixed which acts
|
|
77
|
+
as the containing block for absolute children.
|
|
78
|
+
*/
|
|
79
|
+
.cn-modal-close {
|
|
80
|
+
@apply absolute top-gds-2 right-gds-2;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* MARK: Modal header and footer */
|
|
84
|
+
|
|
85
|
+
.cn-modal-header {
|
|
86
|
+
@apply flex flex-col gap-gds-1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.cn-modal-footer {
|
|
90
|
+
@apply flex flex-col gap-gds-2 sm:flex-row;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* MARK: Vertical rhythm between sections
|
|
94
|
+
No layout opinion of its own on cn-modal-content, it needs to stay a
|
|
95
|
+
plain block so consumers can turn it into a flex row instead (e.g.
|
|
96
|
+
ErrorModal, an icon beside the content) without fighting a hardcoded
|
|
97
|
+
flex-direction. Wrap whatever sits inside (ModalHeader, ModalDescription,
|
|
98
|
+
ModalFooter, and anything else a specific modal needs, a form, a
|
|
99
|
+
Details section, ...) in a Stack instead, same as any other arbitrary
|
|
100
|
+
vertical content elsewhere in the library. */
|
|
101
|
+
|
|
102
|
+
/* MARK: Modal title and description */
|
|
103
|
+
|
|
104
|
+
.cn-modal-title {
|
|
105
|
+
@apply text-xl font-bold text-body;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.cn-modal-description {
|
|
109
|
+
@apply font-normal text-body;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -1,188 +1,127 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import { useArgs } from 'storybook/preview-api';
|
|
4
|
-
|
|
5
3
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
6
4
|
|
|
7
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Modal,
|
|
7
|
+
ModalClose,
|
|
8
|
+
ModalContent,
|
|
9
|
+
ModalDescription,
|
|
10
|
+
ModalFooter,
|
|
11
|
+
ModalHeader,
|
|
12
|
+
ModalTitle,
|
|
13
|
+
ModalTrigger,
|
|
14
|
+
} from './Modal';
|
|
15
|
+
import { Button } from '../Button/Button';
|
|
16
|
+
import { FormGroup } from '../Form/FormGroup';
|
|
17
|
+
import { HintText } from '../HintText/HintText';
|
|
18
|
+
import { Input } from '../Input/Input';
|
|
19
|
+
import { Label } from '../Label/Label';
|
|
20
|
+
import { Stack } from '../Layout/Stack/Stack';
|
|
8
21
|
|
|
9
22
|
const meta: Meta<typeof Modal> = {
|
|
10
23
|
title: 'Components/Modal',
|
|
11
24
|
component: Modal,
|
|
25
|
+
tags: ['autodocs'],
|
|
12
26
|
parameters: {
|
|
13
|
-
layout: 'fullscreen',
|
|
14
27
|
docs: {
|
|
15
28
|
description: {
|
|
16
|
-
component:
|
|
29
|
+
component:
|
|
30
|
+
"Use the Modal primitives to display dialog content in an overlay. ModalContent has no layout opinion of its own (so consumers can turn it into something other than a column, e.g. ErrorModal's icon-beside-content row), wrap ModalHeader/ModalDescription/ModalFooter and anything else in a Stack for spacing between them, as below.",
|
|
17
31
|
},
|
|
18
32
|
},
|
|
19
33
|
},
|
|
20
|
-
// removed the auto-docs for now as that will cause all modals to open immediately
|
|
21
|
-
// tags: ['autodocs'],
|
|
22
34
|
argTypes: {
|
|
23
|
-
|
|
24
|
-
control: 'boolean',
|
|
25
|
-
description: 'Whether the modal is open',
|
|
26
|
-
},
|
|
27
|
-
onClose: {
|
|
28
|
-
action: 'onClose',
|
|
29
|
-
description: 'Callback function called when the modal should close',
|
|
30
|
-
},
|
|
31
|
-
children: {
|
|
32
|
-
control: false,
|
|
33
|
-
description: 'Content to display inside the modal',
|
|
34
|
-
},
|
|
35
|
+
children: { control: false },
|
|
35
36
|
},
|
|
36
|
-
decorators: [
|
|
37
|
-
(Story, context) => {
|
|
38
|
-
const [{ isOpen }, updateArgs] = useArgs();
|
|
39
|
-
|
|
40
|
-
const handleClose = () => {
|
|
41
|
-
updateArgs({ isOpen: false });
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// Override onClose prop to use local handler
|
|
45
|
-
return <Story args={{ ...context.args, isOpen, onClose: handleClose }} />;
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
37
|
};
|
|
49
38
|
|
|
50
39
|
export default meta;
|
|
40
|
+
|
|
51
41
|
type Story = StoryObj<typeof Modal>;
|
|
52
42
|
|
|
53
43
|
export const Default: Story = {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
children: (
|
|
57
|
-
<div>
|
|
58
|
-
<h2 className="mb-4 text-xl font-bold">Modal Title</h2>
|
|
59
|
-
|
|
60
|
-
<p className="mb-4 text-gray-600">
|
|
61
|
-
This is a basic modal with some content. You can close it by clicking the X button,
|
|
62
|
-
pressing Escape, or clicking outside the modal.
|
|
63
|
-
</p>
|
|
64
|
-
|
|
65
|
-
<div className="flex gap-2">
|
|
66
|
-
<button className="px-4 py-2 text-white bg-blue-500 rounded hover:bg-blue-600">
|
|
67
|
-
Confirm
|
|
68
|
-
</button>
|
|
69
|
-
|
|
70
|
-
<button className="px-4 py-2 text-gray-700 bg-gray-300 rounded hover:bg-gray-400">
|
|
71
|
-
Cancel
|
|
72
|
-
</button>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
),
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export const Closed: Story = {
|
|
80
|
-
args: {
|
|
81
|
-
isOpen: false,
|
|
82
|
-
children: (
|
|
83
|
-
<div>
|
|
84
|
-
<h2 className="mb-4 text-xl font-bold">You won't see this</h2>
|
|
44
|
+
render: () => {
|
|
45
|
+
const [open, setOpen] = useState(false);
|
|
85
46
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
47
|
+
return (
|
|
48
|
+
<Modal open={open} onOpenChange={setOpen}>
|
|
49
|
+
<ModalTrigger asChild>
|
|
50
|
+
<Button>Open modal</Button>
|
|
51
|
+
</ModalTrigger>
|
|
52
|
+
|
|
53
|
+
<ModalContent>
|
|
54
|
+
<Stack gap="sm">
|
|
55
|
+
<ModalHeader>
|
|
56
|
+
<ModalTitle>Confirm action</ModalTitle>
|
|
57
|
+
</ModalHeader>
|
|
58
|
+
|
|
59
|
+
<ModalDescription>
|
|
60
|
+
Are you sure you want to continue? This action cannot be undone.
|
|
61
|
+
</ModalDescription>
|
|
62
|
+
|
|
63
|
+
<ModalFooter>
|
|
64
|
+
<Button appearance="secondary" onClick={() => setOpen(false)}>
|
|
65
|
+
Cancel
|
|
66
|
+
</Button>
|
|
67
|
+
|
|
68
|
+
<ModalClose asChild>
|
|
69
|
+
<Button>Confirm</Button>
|
|
70
|
+
</ModalClose>
|
|
71
|
+
</ModalFooter>
|
|
72
|
+
</Stack>
|
|
73
|
+
</ModalContent>
|
|
74
|
+
</Modal>
|
|
75
|
+
);
|
|
89
76
|
},
|
|
90
77
|
};
|
|
91
78
|
|
|
92
|
-
export const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
</div>
|
|
101
|
-
),
|
|
79
|
+
export const WithFormContent: Story = {
|
|
80
|
+
parameters: {
|
|
81
|
+
docs: {
|
|
82
|
+
description: {
|
|
83
|
+
story:
|
|
84
|
+
"Arbitrary content (a FormGroup here, could be anything) sits in the same Stack as ModalHeader/ModalDescription/ModalFooter, getting the same gap on both sides automatically, no extra spacing rules needed regardless of what's between them.",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
102
87
|
},
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export const Interactive: Story = {
|
|
106
88
|
render: () => {
|
|
107
|
-
const [
|
|
108
|
-
const [selectedModal, setSelectedModal] = useState<string | null>(null);
|
|
109
|
-
|
|
110
|
-
const openModal = (type: string) => {
|
|
111
|
-
setSelectedModal(type);
|
|
112
|
-
setIsOpen(true);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const closeModal = () => {
|
|
116
|
-
setIsOpen(false);
|
|
117
|
-
setSelectedModal(null);
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const renderModalContent = () => {
|
|
121
|
-
switch (selectedModal) {
|
|
122
|
-
case 'info':
|
|
123
|
-
return (
|
|
124
|
-
<div>
|
|
125
|
-
<h3 className="mb-2 text-lg font-semibold">Information</h3>
|
|
126
|
-
|
|
127
|
-
<p className="text-gray-600">This is an informational modal.</p>
|
|
128
|
-
</div>
|
|
129
|
-
);
|
|
130
|
-
case 'warning':
|
|
131
|
-
return (
|
|
132
|
-
<div>
|
|
133
|
-
<h3 className="mb-2 text-lg font-semibold text-yellow-600">Warning</h3>
|
|
134
|
-
|
|
135
|
-
<p className="text-gray-600">This action requires confirmation.</p>
|
|
136
|
-
</div>
|
|
137
|
-
);
|
|
138
|
-
case 'error':
|
|
139
|
-
return (
|
|
140
|
-
<div>
|
|
141
|
-
<h3 className="mb-2 text-lg font-semibold text-red-600">Error</h3>
|
|
142
|
-
|
|
143
|
-
<p className="text-gray-600">Something went wrong. Please try again.</p>
|
|
144
|
-
</div>
|
|
145
|
-
);
|
|
146
|
-
default:
|
|
147
|
-
return <p>Default modal content</p>;
|
|
148
|
-
}
|
|
149
|
-
};
|
|
89
|
+
const [open, setOpen] = useState(false);
|
|
150
90
|
|
|
151
91
|
return (
|
|
152
|
-
<
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
</div>
|
|
92
|
+
<Modal open={open} onOpenChange={setOpen}>
|
|
93
|
+
<ModalTrigger asChild>
|
|
94
|
+
<Button>Open modal</Button>
|
|
95
|
+
</ModalTrigger>
|
|
96
|
+
|
|
97
|
+
<ModalContent>
|
|
98
|
+
<Stack gap="sm">
|
|
99
|
+
<ModalHeader>
|
|
100
|
+
<ModalTitle>Rename item</ModalTitle>
|
|
101
|
+
</ModalHeader>
|
|
102
|
+
|
|
103
|
+
<ModalDescription>Choose a new name for this item.</ModalDescription>
|
|
104
|
+
|
|
105
|
+
<FormGroup>
|
|
106
|
+
<Label htmlFor="rename-item">Name</Label>
|
|
107
|
+
|
|
108
|
+
<HintText>As it should appear in the list</HintText>
|
|
109
|
+
|
|
110
|
+
<Input id="rename-item" aria-describedby="rename-item-hint" defaultValue="Untitled" />
|
|
111
|
+
</FormGroup>
|
|
112
|
+
|
|
113
|
+
<ModalFooter>
|
|
114
|
+
<Button appearance="secondary" onClick={() => setOpen(false)}>
|
|
115
|
+
Cancel
|
|
116
|
+
</Button>
|
|
117
|
+
|
|
118
|
+
<ModalClose asChild>
|
|
119
|
+
<Button>Save</Button>
|
|
120
|
+
</ModalClose>
|
|
121
|
+
</ModalFooter>
|
|
122
|
+
</Stack>
|
|
123
|
+
</ModalContent>
|
|
124
|
+
</Modal>
|
|
186
125
|
);
|
|
187
126
|
},
|
|
188
127
|
};
|
|
@@ -1,83 +1,149 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { ComponentProps } from 'react';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { Dialog as DialogPrimitive } from 'radix-ui';
|
|
6
|
+
import { LuX } from 'react-icons/lu';
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
8
|
+
import { cn } from '../../utils/components';
|
|
9
|
+
import { Button } from '../Button/Button';
|
|
10
10
|
|
|
11
|
-
type
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export type ModalProps = ComponentProps<typeof DialogPrimitive.Root>;
|
|
12
|
+
|
|
13
|
+
export const Modal = ({ ...props }: ModalProps) => (
|
|
14
|
+
<DialogPrimitive.Root data-slot="modal" {...props} />
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export type ModalTriggerProps = ComponentProps<typeof DialogPrimitive.Trigger>;
|
|
18
|
+
|
|
19
|
+
export const ModalTrigger = ({ ...props }: ModalTriggerProps) => (
|
|
20
|
+
<DialogPrimitive.Trigger data-slot="modal-trigger" {...props} />
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export type ModalPortalProps = ComponentProps<typeof DialogPrimitive.Portal>;
|
|
24
|
+
|
|
25
|
+
export const ModalPortal = ({ ...props }: ModalPortalProps) => (
|
|
26
|
+
<DialogPrimitive.Portal data-slot="modal-portal" {...props} />
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export type ModalCloseProps = ComponentProps<typeof DialogPrimitive.Close>;
|
|
30
|
+
|
|
31
|
+
export const ModalClose = ({ ...props }: ModalCloseProps) => (
|
|
32
|
+
<DialogPrimitive.Close data-slot="modal-close" {...props} />
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export type ModalOverlayProps = ComponentProps<typeof DialogPrimitive.Overlay>;
|
|
36
|
+
|
|
37
|
+
export const ModalOverlay = ({ className, ...props }: ModalOverlayProps) => (
|
|
38
|
+
<DialogPrimitive.Overlay
|
|
39
|
+
data-slot="modal-overlay"
|
|
40
|
+
className={cn('cn-modal-overlay fixed inset-0 isolate', className)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export type ModalContentProps = ComponentProps<typeof DialogPrimitive.Content> & {
|
|
46
|
+
showCloseButton?: boolean;
|
|
15
47
|
};
|
|
16
48
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
className="relative z-10 w-full max-w-md p-6 bg-white rounded-lg shadow-lg"
|
|
65
|
-
onClick={(event) => {
|
|
66
|
-
// stop event from bubbling to the `dialog` element, as we don't want the modal to close if you click the content
|
|
67
|
-
event.stopPropagation();
|
|
68
|
-
}}
|
|
69
|
-
>
|
|
70
|
-
<button
|
|
71
|
-
onClick={handleClose}
|
|
72
|
-
className="absolute text-gray-600 text-static-xl top-2 right-2 hover:text-black"
|
|
73
|
-
aria-label="Close modal"
|
|
74
|
-
>
|
|
75
|
-
<IoMdCloseCircle size={20} />
|
|
76
|
-
</button>
|
|
77
|
-
{children}
|
|
78
|
-
</div>
|
|
79
|
-
</dialog>,
|
|
80
|
-
document.body,
|
|
81
|
-
)
|
|
82
|
-
: null;
|
|
49
|
+
export const ModalContent = ({
|
|
50
|
+
className,
|
|
51
|
+
children,
|
|
52
|
+
showCloseButton = true,
|
|
53
|
+
...props
|
|
54
|
+
}: ModalContentProps) => (
|
|
55
|
+
<ModalPortal>
|
|
56
|
+
<ModalOverlay />
|
|
57
|
+
|
|
58
|
+
<DialogPrimitive.Content
|
|
59
|
+
data-slot="modal-content"
|
|
60
|
+
className={cn(
|
|
61
|
+
`cn-modal-content fixed top-1/2 left-1/2 w-full -translate-x-1/2 -translate-y-1/2
|
|
62
|
+
outline-none`,
|
|
63
|
+
className,
|
|
64
|
+
)}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
{children}
|
|
68
|
+
|
|
69
|
+
{showCloseButton && (
|
|
70
|
+
<div className="cn-modal-close">
|
|
71
|
+
<DialogPrimitive.Close data-slot="modal-close" asChild>
|
|
72
|
+
<Button variant="icon" appearance="ghost">
|
|
73
|
+
<LuX />
|
|
74
|
+
|
|
75
|
+
<span className="sr-only">Close</span>
|
|
76
|
+
</Button>
|
|
77
|
+
</DialogPrimitive.Close>
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
</DialogPrimitive.Content>
|
|
81
|
+
</ModalPortal>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
export type ModalHeaderProps = ComponentProps<'div'>;
|
|
85
|
+
|
|
86
|
+
export const ModalHeader = ({ className, ...props }: ModalHeaderProps) => (
|
|
87
|
+
<div
|
|
88
|
+
data-slot="modal-header"
|
|
89
|
+
className={cn('cn-modal-header flex flex-col', className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export type ModalFooterProps = ComponentProps<'div'> & {
|
|
95
|
+
showCloseButton?: boolean;
|
|
83
96
|
};
|
|
97
|
+
|
|
98
|
+
export const ModalFooter = ({
|
|
99
|
+
className,
|
|
100
|
+
showCloseButton = false,
|
|
101
|
+
children,
|
|
102
|
+
...props
|
|
103
|
+
}: ModalFooterProps) => (
|
|
104
|
+
<div
|
|
105
|
+
data-slot="modal-footer"
|
|
106
|
+
className={cn(
|
|
107
|
+
'cn-modal-footer flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
|
|
108
|
+
className,
|
|
109
|
+
)}
|
|
110
|
+
{...props}
|
|
111
|
+
>
|
|
112
|
+
{children}
|
|
113
|
+
|
|
114
|
+
{showCloseButton && (
|
|
115
|
+
<DialogPrimitive.Close asChild>
|
|
116
|
+
<Button appearance="secondary">Close</Button>
|
|
117
|
+
</DialogPrimitive.Close>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
export type ModalTitleProps = ComponentProps<typeof DialogPrimitive.Title>;
|
|
123
|
+
|
|
124
|
+
export const ModalTitle = ({ className, ...props }: ModalTitleProps) => (
|
|
125
|
+
<DialogPrimitive.Title
|
|
126
|
+
data-slot="modal-title"
|
|
127
|
+
className={cn('cn-modal-title cn-font-heading', className)}
|
|
128
|
+
{...props}
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
export type ModalDescriptionProps = ComponentProps<typeof DialogPrimitive.Description>;
|
|
133
|
+
|
|
134
|
+
// Renders a <p> (Description's own default), which can only contain
|
|
135
|
+
// phrasing content, children here is assumed to be plain descriptive text
|
|
136
|
+
// for that reason. Anything else (a Details/SummaryList section, a copy
|
|
137
|
+
// button, ...) belongs as a sibling of ModalDescription instead, not
|
|
138
|
+
// nested inside it, both because a <p> can't legally contain block
|
|
139
|
+
// content (a browser silently closes it early on hitting one, which
|
|
140
|
+
// desyncs SSR/client markup into a hydration error) and because
|
|
141
|
+
// aria-describedby content should read as plain description text, not
|
|
142
|
+
// fold in interactive elements. See ErrorModal for the sibling pattern.
|
|
143
|
+
export const ModalDescription = ({ className, ...props }: ModalDescriptionProps) => (
|
|
144
|
+
<DialogPrimitive.Description
|
|
145
|
+
data-slot="modal-description"
|
|
146
|
+
className={cn('cn-modal-description', className)}
|
|
147
|
+
{...props}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: NoScript banner
|
|
3
|
+
Full-width accent-coloured bar. Intended as the very first element in
|
|
4
|
+
the DOM, above the header, same position as the cookie banner, so it's
|
|
5
|
+
unmissable and consistent across every app. role="region" + aria-label
|
|
6
|
+
keeps it discoverable via landmark navigation even though it sits
|
|
7
|
+
outside both the header and main landmarks. */
|
|
8
|
+
.cn-noscript-banner {
|
|
9
|
+
@apply bg-accent text-on-accent py-gds-4;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* MARK: Inner wrapper
|
|
13
|
+
Centres and constrains content, matching cn-cookie-banner-inner. */
|
|
14
|
+
.cn-noscript-banner-inner {
|
|
15
|
+
@apply mx-auto max-w-page px-gds-4;
|
|
16
|
+
}
|
|
17
|
+
}
|