@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,42 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: PageShell */
|
|
3
|
+
.cn-page-shell {
|
|
4
|
+
/* Clips the few extra pixels Hero's full-bleed breakout can introduce
|
|
5
|
+
(100vw includes the scrollbar gutter in browsers that reserve space
|
|
6
|
+
for it), rather than a real horizontal scrollbar appearing. Nothing
|
|
7
|
+
meaningful is ever clipped, the breakout is already sized to reach
|
|
8
|
+
exactly the viewport edges. */
|
|
9
|
+
@apply flex flex-col overflow-x-clip;
|
|
10
|
+
|
|
11
|
+
& > header,
|
|
12
|
+
& > footer {
|
|
13
|
+
@apply shrink-0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
& > main {
|
|
17
|
+
@apply flex-1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* MARK: Static
|
|
22
|
+
Normal document scroll: main grows to fill any leftover viewport height,
|
|
23
|
+
footer sits right after it in flow, the whole page scrolls together once
|
|
24
|
+
content overflows. */
|
|
25
|
+
.cn-page-shell-variant-static {
|
|
26
|
+
@apply min-h-screen;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* MARK: Scroll-locked
|
|
30
|
+
App-shell style: the page is pinned to the viewport height and never
|
|
31
|
+
scrolls itself, header and footer stay permanently visible, main gets its
|
|
32
|
+
own scrollbar. min-h-0 overrides the flex item default of
|
|
33
|
+
min-height: auto, which otherwise refuses to let main shrink below its
|
|
34
|
+
content size and silently breaks the internal scroll. */
|
|
35
|
+
.cn-page-shell-variant-scroll-locked {
|
|
36
|
+
@apply h-screen overflow-hidden;
|
|
37
|
+
|
|
38
|
+
& > main {
|
|
39
|
+
@apply min-h-0 overflow-y-auto;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { PageShell } from './PageShell';
|
|
4
|
+
import type { NavLink } from '../../../types/navigation';
|
|
5
|
+
import { Footer } from '../../Footer/Footer';
|
|
6
|
+
import { Header } from '../../Header/Header';
|
|
7
|
+
import { Heading } from '../../Heading/Heading';
|
|
8
|
+
import { Paragraph } from '../../Paragraph/Paragraph';
|
|
9
|
+
import { HeadingGroup } from '../HeadingGroup/HeadingGroup';
|
|
10
|
+
import { MainWrapper } from '../MainWrapper/MainWrapper';
|
|
11
|
+
import { ParagraphGroup } from '../ParagraphGroup/ParagraphGroup';
|
|
12
|
+
|
|
13
|
+
const navLinks: NavLink[] = [
|
|
14
|
+
{ label: 'Home', url: '/', openInNewTab: false },
|
|
15
|
+
{ label: 'API', url: '/api-docs', openInNewTab: false },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const headerArgs = {
|
|
19
|
+
dspUrl: 'https://environment.data.gov.uk',
|
|
20
|
+
appName: 'Wetlands Inventory',
|
|
21
|
+
navLinks,
|
|
22
|
+
credentials: null,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const LongContent = () => (
|
|
26
|
+
<HeadingGroup>
|
|
27
|
+
<Heading level="h1">Water quality</Heading>
|
|
28
|
+
|
|
29
|
+
<ParagraphGroup>
|
|
30
|
+
{Array.from({ length: 20 }, (_, index) => (
|
|
31
|
+
<Paragraph key={`paragraph-${index}`}>
|
|
32
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
33
|
+
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
34
|
+
ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
|
35
|
+
</Paragraph>
|
|
36
|
+
))}
|
|
37
|
+
</ParagraphGroup>
|
|
38
|
+
</HeadingGroup>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const meta: Meta<typeof PageShell> = {
|
|
42
|
+
title: 'Components/Layout/PageShell',
|
|
43
|
+
component: PageShell,
|
|
44
|
+
tags: ['autodocs'],
|
|
45
|
+
parameters: {
|
|
46
|
+
layout: 'fullscreen',
|
|
47
|
+
docs: {
|
|
48
|
+
description: {
|
|
49
|
+
component:
|
|
50
|
+
'Divides the page into header/main/footer. Compose it directly with Header, MainWrapper, and Footer, PageShell sizes them by tag (`> header`, `> main`, `> footer`), no slot props needed.',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
argTypes: {
|
|
55
|
+
variant: {
|
|
56
|
+
control: { type: 'radio' },
|
|
57
|
+
options: ['static', 'scroll-locked'],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default meta;
|
|
63
|
+
|
|
64
|
+
type Story = StoryObj<typeof PageShell>;
|
|
65
|
+
|
|
66
|
+
export const Static: Story = {
|
|
67
|
+
parameters: {
|
|
68
|
+
docs: {
|
|
69
|
+
description: {
|
|
70
|
+
story:
|
|
71
|
+
'For static, document-like pages. Main grows to fill any leftover viewport height, but the whole page scrolls together once content overflows, scroll down to see the footer follow the content rather than staying pinned.',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
render: () => (
|
|
76
|
+
<PageShell variant="static">
|
|
77
|
+
<Header {...headerArgs} />
|
|
78
|
+
|
|
79
|
+
<MainWrapper>
|
|
80
|
+
<LongContent />
|
|
81
|
+
</MainWrapper>
|
|
82
|
+
|
|
83
|
+
<Footer />
|
|
84
|
+
</PageShell>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const ScrollLocked: Story = {
|
|
89
|
+
parameters: {
|
|
90
|
+
docs: {
|
|
91
|
+
description: {
|
|
92
|
+
story:
|
|
93
|
+
'For interactive, app-like pages (e.g. maps). Header and footer stay permanently visible, main gets its own internal scrollbar instead of the page scrolling. Scroll within the main content area to see it, the header and footer never move.',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
render: () => (
|
|
98
|
+
<PageShell variant="scroll-locked">
|
|
99
|
+
<Header {...headerArgs} />
|
|
100
|
+
|
|
101
|
+
<MainWrapper>
|
|
102
|
+
<LongContent />
|
|
103
|
+
</MainWrapper>
|
|
104
|
+
|
|
105
|
+
<Footer />
|
|
106
|
+
</PageShell>
|
|
107
|
+
),
|
|
108
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../../utils/components';
|
|
4
|
+
|
|
5
|
+
const PAGE_SHELL_VARIANTS = {
|
|
6
|
+
static: 'cn-page-shell-variant-static',
|
|
7
|
+
'scroll-locked': 'cn-page-shell-variant-scroll-locked',
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
export type PageShellVariant = keyof typeof PAGE_SHELL_VARIANTS;
|
|
11
|
+
|
|
12
|
+
export type PageShellProps = ComponentProps<'div'> & {
|
|
13
|
+
variant?: PageShellVariant;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Divides the page into header/main/footer using tag selectors (`> header`,
|
|
18
|
+
* `> main`, `> footer`), so it composes directly with Header, MainWrapper,
|
|
19
|
+
* and Footer with no slot props needed:
|
|
20
|
+
* `<PageShell variant="static"><Header /><MainWrapper>...</MainWrapper><Footer /></PageShell>`
|
|
21
|
+
*/
|
|
22
|
+
export const PageShell = ({ variant = 'static', className, ...props }: PageShellProps) => {
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
data-slot="page-shell"
|
|
26
|
+
data-variant={variant}
|
|
27
|
+
className={cn('cn-page-shell', PAGE_SHELL_VARIANTS[variant], className)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { ParagraphGroup } from './ParagraphGroup';
|
|
4
|
+
import { Paragraph } from '../../Paragraph/Paragraph';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof ParagraphGroup> = {
|
|
7
|
+
title: 'Components/Layout/ParagraphGroup',
|
|
8
|
+
component: ParagraphGroup,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component: 'Use a ParagraphGroup to group a heading and paragraph with responsive spacing.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
argTypes: {},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
|
|
22
|
+
type Story = StoryObj<typeof ParagraphGroup>;
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<ParagraphGroup>
|
|
27
|
+
<Paragraph>Hello from paragraph one.</Paragraph>
|
|
28
|
+
|
|
29
|
+
<Paragraph>Hello from paragraph two.</Paragraph>
|
|
30
|
+
|
|
31
|
+
<Paragraph>Hello from paragraph three.</Paragraph>
|
|
32
|
+
</ParagraphGroup>
|
|
33
|
+
),
|
|
34
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../../utils/components';
|
|
4
|
+
|
|
5
|
+
export type ParagraphGroupProps = ComponentProps<'div'>;
|
|
6
|
+
|
|
7
|
+
export const ParagraphGroup = ({ className, ...props }: ParagraphGroupProps) => {
|
|
8
|
+
return <div className={cn('cn-paragraph-group', className)} {...props} />;
|
|
9
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: Stack
|
|
3
|
+
Generic vertical rhythm between arbitrary sibling blocks (HeadingGroup,
|
|
4
|
+
ParagraphGroup, ButtonGroup, Card, etc.) as a plain flex+gap column.
|
|
5
|
+
Unlike the *Group components, Stack has no opinion on what it contains,
|
|
6
|
+
it only owns the gap between its direct children. */
|
|
7
|
+
/* Deliberately relies on the flex default of align-items: stretch: most
|
|
8
|
+
things stacked are block-level content (Card, ParagraphGroup, sections)
|
|
9
|
+
that's meant to fill the width anyway, matching normal block flow.
|
|
10
|
+
A lone element that shouldn't stretch (a bare Button) should be wrapped
|
|
11
|
+
in something row-based (e.g. ButtonGroup) rather than Stack opting every
|
|
12
|
+
child out by default, that made the common case need w-full everywhere
|
|
13
|
+
for the sake of the rarer one. */
|
|
14
|
+
.cn-stack {
|
|
15
|
+
@apply flex flex-col;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.cn-stack-gap-sm {
|
|
19
|
+
@apply gap-gds-4 @sm:gap-gds-5;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.cn-stack-gap-base {
|
|
23
|
+
@apply gap-gds-6 @sm:gap-gds-7;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.cn-stack-gap-lg {
|
|
27
|
+
@apply gap-gds-8 @sm:gap-gds-9;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { Stack } from './Stack';
|
|
4
|
+
import { Button } from '../../Button/Button';
|
|
5
|
+
import { Heading } from '../../Heading/Heading';
|
|
6
|
+
import { Paragraph } from '../../Paragraph/Paragraph';
|
|
7
|
+
import { ButtonGroup } from '../ButtonGroup/ButtonGroup';
|
|
8
|
+
import { HeadingGroup } from '../HeadingGroup/HeadingGroup';
|
|
9
|
+
import { ParagraphGroup } from '../ParagraphGroup/ParagraphGroup';
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof Stack> = {
|
|
12
|
+
title: 'Components/Layout/Stack',
|
|
13
|
+
component: Stack,
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
parameters: {
|
|
16
|
+
docs: {
|
|
17
|
+
description: {
|
|
18
|
+
component:
|
|
19
|
+
"Use Stack for consistent vertical rhythm between sibling blocks that have nothing else in common, e.g. a HeadingGroup followed by a ButtonGroup. The *Group components (HeadingGroup, ParagraphGroup) only space their own children, they intentionally have no opinion on the gap to whatever comes before or after them, that gap is Stack's job.",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
argTypes: {
|
|
24
|
+
gap: {
|
|
25
|
+
control: { type: 'radio' },
|
|
26
|
+
options: ['sm', 'base', 'lg'],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
args: {
|
|
30
|
+
gap: 'base',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
|
|
36
|
+
type Story = StoryObj<typeof Stack>;
|
|
37
|
+
|
|
38
|
+
export const Default: Story = {
|
|
39
|
+
render: (args) => (
|
|
40
|
+
<Stack {...args}>
|
|
41
|
+
<HeadingGroup>
|
|
42
|
+
<Heading level="h2">Water quality</Heading>
|
|
43
|
+
|
|
44
|
+
<Paragraph>Explore water quality sample data collected across England.</Paragraph>
|
|
45
|
+
</HeadingGroup>
|
|
46
|
+
|
|
47
|
+
<ParagraphGroup>
|
|
48
|
+
<Paragraph>This service lets you search, filter, and download the data.</Paragraph>
|
|
49
|
+
|
|
50
|
+
<Paragraph>You'll need to sign in to save a search for later.</Paragraph>
|
|
51
|
+
</ParagraphGroup>
|
|
52
|
+
|
|
53
|
+
<ButtonGroup>
|
|
54
|
+
<Button appearance="primary">Start now</Button>
|
|
55
|
+
|
|
56
|
+
<Button appearance="secondary">Find out more</Button>
|
|
57
|
+
</ButtonGroup>
|
|
58
|
+
</Stack>
|
|
59
|
+
),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const Gaps: Story = {
|
|
63
|
+
parameters: {
|
|
64
|
+
docs: {
|
|
65
|
+
description: {
|
|
66
|
+
story: 'The three gap sizes side by side, each Stack contains the same two paragraphs.',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
render: () => (
|
|
71
|
+
<Stack gap="lg">
|
|
72
|
+
{(['sm', 'base', 'lg'] as const).map((gap) => (
|
|
73
|
+
<Stack key={gap} gap={gap} className="rounded-sm border border-divider p-gds-3">
|
|
74
|
+
<Paragraph className="text-sm text-body-secondary">gap="{gap}"</Paragraph>
|
|
75
|
+
|
|
76
|
+
<Paragraph>First block</Paragraph>
|
|
77
|
+
|
|
78
|
+
<Paragraph>Second block</Paragraph>
|
|
79
|
+
</Stack>
|
|
80
|
+
))}
|
|
81
|
+
</Stack>
|
|
82
|
+
),
|
|
83
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../../utils/components';
|
|
4
|
+
|
|
5
|
+
const STACK_GAPS = {
|
|
6
|
+
sm: 'cn-stack-gap-sm',
|
|
7
|
+
base: 'cn-stack-gap-base',
|
|
8
|
+
lg: 'cn-stack-gap-lg',
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export type StackGap = keyof typeof STACK_GAPS;
|
|
12
|
+
|
|
13
|
+
export type StackProps = ComponentProps<'div'> & {
|
|
14
|
+
gap?: StackGap;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const Stack = ({ gap = 'base', className, ...props }: StackProps) => {
|
|
18
|
+
return <div className={cn('cn-stack', STACK_GAPS[gap], className)} {...props} />;
|
|
19
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: WidthContainer
|
|
3
|
+
GDS "width container" object: centred, padded, capped at max-w-page
|
|
4
|
+
content width. Duplicates rather than @apply's the upstream
|
|
5
|
+
@tpzdsp/eslint-config-dsp .page-wrapper class, that class lives in
|
|
6
|
+
@layer components (not @utility) in an external package, and Tailwind
|
|
7
|
+
only guarantees @apply works across files for @utility-defined classes.
|
|
8
|
+
Also a container-query provider (@container): its own px-* padding still
|
|
9
|
+
responds to the viewport (it IS the viewport-ish boundary at the top of
|
|
10
|
+
the page), but anything nested inside it (Stack, ParagraphGroup,
|
|
11
|
+
HeadingGroup) should size against this element's actual content width,
|
|
12
|
+
not the viewport, so their spacing stays correct if WidthContainer is
|
|
13
|
+
ever nested somewhere narrower than the page. */
|
|
14
|
+
.cn-width-container {
|
|
15
|
+
@apply w-full max-w-page mx-auto px-gds-3 sm:px-gds-6 @container h-full;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { WidthContainer } from './WidthContainer';
|
|
4
|
+
import { Paragraph } from '../../Paragraph/Paragraph';
|
|
5
|
+
import { ParagraphGroup } from '../ParagraphGroup/ParagraphGroup';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof WidthContainer> = {
|
|
8
|
+
title: 'Components/Layout/WidthContainer',
|
|
9
|
+
component: WidthContainer,
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
parameters: {
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component:
|
|
15
|
+
'Use a WidthContainer to place content in a single column centered on the page, as per GDS guidelines. View the individual stories on a wide screen to see what the container does.',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
argTypes: {},
|
|
20
|
+
args: {},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
|
|
25
|
+
type Story = StoryObj<typeof WidthContainer>;
|
|
26
|
+
|
|
27
|
+
export const RestrictedWidth: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<WidthContainer>
|
|
30
|
+
<ParagraphGroup>
|
|
31
|
+
<Paragraph>I am content with a restricted width</Paragraph>
|
|
32
|
+
|
|
33
|
+
<Paragraph>
|
|
34
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
35
|
+
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
36
|
+
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
37
|
+
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
|
38
|
+
</Paragraph>
|
|
39
|
+
</ParagraphGroup>
|
|
40
|
+
</WidthContainer>
|
|
41
|
+
),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const ColouredBackground: Story = {
|
|
45
|
+
render: () => (
|
|
46
|
+
<WidthContainer className="bg-accent">
|
|
47
|
+
<ParagraphGroup>
|
|
48
|
+
<Paragraph className="text-on-accent">I am content with a restricted width</Paragraph>
|
|
49
|
+
|
|
50
|
+
<Paragraph className="text-on-accent">
|
|
51
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
52
|
+
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
53
|
+
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
54
|
+
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
|
55
|
+
</Paragraph>
|
|
56
|
+
</ParagraphGroup>
|
|
57
|
+
</WidthContainer>
|
|
58
|
+
),
|
|
59
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../../utils/components';
|
|
4
|
+
|
|
5
|
+
export type WidthContainerProps = ComponentProps<'div'>;
|
|
6
|
+
|
|
7
|
+
export const WidthContainer = ({ className, children, ...props }: WidthContainerProps) => {
|
|
8
|
+
return (
|
|
9
|
+
<div className={cn('w-full', className)} {...props}>
|
|
10
|
+
<div className="cn-width-container">{children}</div>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: Panel */
|
|
3
|
+
.cn-legend-panel {
|
|
4
|
+
@apply flex flex-col gap-gds-2;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* MARK: Title
|
|
8
|
+
Matches cn-label's own sizing/colour (text-lg text-body, not bold, no
|
|
9
|
+
margin) rather than its own values, a legend title plays the same
|
|
10
|
+
role for a group of controls that a Label plays for one. */
|
|
11
|
+
.cn-legend-title {
|
|
12
|
+
@apply m-0 text-lg text-body;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* MARK: Items list */
|
|
16
|
+
.cn-legend-items {
|
|
17
|
+
@apply m-0 p-0 list-none flex flex-col gap-gds-1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* MARK: Item */
|
|
21
|
+
.cn-legend-item {
|
|
22
|
+
@apply flex items-center gap-gds-2 text-sm;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* MARK: Option (interactive row)
|
|
26
|
+
Ghost-button base via cn-button, mirroring cn-layer-switcher-option's
|
|
27
|
+
interaction states so the two selectable-list patterns stay consistent. */
|
|
28
|
+
.cn-legend-option {
|
|
29
|
+
@apply cn-button flex items-center gap-gds-2 p-gds-1 text-sm text-left bg-transparent text-body
|
|
30
|
+
w-full;
|
|
31
|
+
|
|
32
|
+
&:focus {
|
|
33
|
+
@apply outline-none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&:hover:not(:disabled) {
|
|
37
|
+
@apply bg-transparent;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&:focus:not(:active):not(:hover) {
|
|
41
|
+
@apply shadow-none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&:active:not(:disabled) {
|
|
45
|
+
@apply bg-black/5 transform-none;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* MARK: Option selected state */
|
|
50
|
+
.cn-legend-option[aria-checked='true'] {
|
|
51
|
+
@apply bg-accent text-on-accent;
|
|
52
|
+
|
|
53
|
+
&:focus:not(:active):not(:hover) {
|
|
54
|
+
@apply bg-accent text-on-accent;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&:hover:not(:disabled) {
|
|
58
|
+
@apply bg-focus text-on-focus;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* MARK: Dot
|
|
63
|
+
Inherits the cn-legend-dot utility from LegendSelect.css which adds a
|
|
64
|
+
white outline ring when the dot is inside a selected react-select option.
|
|
65
|
+
No additional styling needed here, colour is set inline via style prop. */
|
|
66
|
+
.cn-legend-dot {
|
|
67
|
+
@apply size-3 shrink-0 rounded-full;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.cn-legend-option[aria-checked='true'] .cn-legend-dot {
|
|
71
|
+
outline: var(--focus-ring-width) solid var(--color-panel);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* MARK: Skeleton
|
|
75
|
+
Reuses cn-legend-panel/title/items/item directly for the structural
|
|
76
|
+
wrappers, dot and label bars get their own sizing so a real LegendPanel
|
|
77
|
+
swaps in without any layout shift. */
|
|
78
|
+
.cn-legend-skeleton-title {
|
|
79
|
+
@apply h-4 w-20;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.cn-legend-skeleton-dot {
|
|
83
|
+
@apply size-3 shrink-0 rounded-full;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* h-4 (1rem) undershoots text-sm's real rendered height (0.875rem font
|
|
87
|
+
size * 1.5 line-height = 1.3125rem ≈ 21px), a 5px-per-row shortfall
|
|
88
|
+
that compounds into the whole list reading visibly shorter than the
|
|
89
|
+
real, non-skeleton items once loaded, most noticeable in interactive
|
|
90
|
+
mode where cn-legend-option's own padding otherwise makes each row
|
|
91
|
+
tall enough for the gap to stand out. */
|
|
92
|
+
.cn-legend-skeleton-label {
|
|
93
|
+
@apply h-5.25 w-24;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { Legend } from './Legend';
|
|
4
|
+
import type { LegendOption } from './Legend';
|
|
5
|
+
|
|
6
|
+
const HABITAT_ITEMS: LegendOption[] = [
|
|
7
|
+
{ value: 'bogs', label: 'Bogs', color: '#3b82f6' },
|
|
8
|
+
{ value: 'dwarf-shrub', label: 'Dwarf shrub heath', color: '#14b8a6' },
|
|
9
|
+
{ value: 'fens', label: 'Fens, marsh & swamp', color: '#ec4899' },
|
|
10
|
+
{ value: 'littoral', label: 'Littoral sediments', color: '#f97316' },
|
|
11
|
+
{ value: 'neutral', label: 'Neutral grassland', color: '#a855f7' },
|
|
12
|
+
{ value: 'sublittoral', label: 'Sublittoral sediment', color: '#22c55e' },
|
|
13
|
+
{ value: 'supralittoral-rock', label: 'Supralittoral Rock', color: '#eab308' },
|
|
14
|
+
{ value: 'supralittoral-sed', label: 'Supralittoral sediment', color: '#6b7280' },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const meta: Meta<typeof Legend> = {
|
|
18
|
+
title: 'Components/Legend',
|
|
19
|
+
component: Legend,
|
|
20
|
+
tags: ['autodocs'],
|
|
21
|
+
parameters: {
|
|
22
|
+
docs: {
|
|
23
|
+
description: {
|
|
24
|
+
component: `
|
|
25
|
+
Icon button that opens a popover showing a map legend. Intended for use inside a map toolbar alongside other panel-appearance controls.
|
|
26
|
+
|
|
27
|
+
Shares the \`LegendOption\` type (\`value\`, \`label\`, \`color\`) with \`LegendSelect\`, so the same dataset drives both the filter control and the legend.
|
|
28
|
+
|
|
29
|
+
**Props:**
|
|
30
|
+
- \`items\`: array of \`LegendOption\` objects
|
|
31
|
+
- \`title\`: heading shown at the top of the popover (default: \`"Legend"\`)
|
|
32
|
+
`.trim(),
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
|
|
40
|
+
type Story = StoryObj<typeof Legend>;
|
|
41
|
+
|
|
42
|
+
export const Default: Story = {
|
|
43
|
+
render: () => (
|
|
44
|
+
<div className="flex items-start">
|
|
45
|
+
<Legend items={HABITAT_ITEMS} />
|
|
46
|
+
</div>
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const CustomTitle: Story = {
|
|
51
|
+
parameters: {
|
|
52
|
+
docs: {
|
|
53
|
+
description: { story: 'Pass a custom `title` to override the default "Legend" heading.' },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
render: () => (
|
|
57
|
+
<div className="flex items-start">
|
|
58
|
+
<Legend items={HABITAT_ITEMS} title="Habitat types" />
|
|
59
|
+
</div>
|
|
60
|
+
),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const FewItems: Story = {
|
|
64
|
+
parameters: {
|
|
65
|
+
docs: {
|
|
66
|
+
description: { story: 'Compact legend with only a handful of categories.' },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
render: () => (
|
|
70
|
+
<div className="flex items-start">
|
|
71
|
+
<Legend
|
|
72
|
+
items={[
|
|
73
|
+
{ value: 'low', label: 'Low risk', color: '#22c55e' },
|
|
74
|
+
{ value: 'medium', label: 'Medium risk', color: '#eab308' },
|
|
75
|
+
{ value: 'high', label: 'High risk', color: '#ef4444' },
|
|
76
|
+
]}
|
|
77
|
+
title="Flood risk"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
),
|
|
81
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { LuList } from 'react-icons/lu';
|
|
4
|
+
|
|
5
|
+
import { LegendPanel } from './LegendPanel';
|
|
6
|
+
import type { LegendOption } from './LegendPanel';
|
|
7
|
+
import { Button } from '../Button/Button';
|
|
8
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover';
|
|
9
|
+
|
|
10
|
+
export type { LegendOption };
|
|
11
|
+
|
|
12
|
+
export type LegendProps = Omit<
|
|
13
|
+
ComponentProps<typeof Button>,
|
|
14
|
+
'variant' | 'appearance' | 'children'
|
|
15
|
+
> & {
|
|
16
|
+
items: LegendOption[];
|
|
17
|
+
title?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const Legend = ({ items, title = 'Legend', ...props }: LegendProps) => (
|
|
21
|
+
<Popover>
|
|
22
|
+
<PopoverTrigger asChild>
|
|
23
|
+
<Button variant="icon" appearance="panel" aria-label={title} {...props}>
|
|
24
|
+
<LuList aria-hidden />
|
|
25
|
+
</Button>
|
|
26
|
+
</PopoverTrigger>
|
|
27
|
+
|
|
28
|
+
<PopoverContent side="left" align="start" sideOffset={8} className="w-52">
|
|
29
|
+
<LegendPanel items={items} title={title} />
|
|
30
|
+
</PopoverContent>
|
|
31
|
+
</Popover>
|
|
32
|
+
);
|