@tpzdsp/next-toolkit 2.5.0 → 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 +22 -13
- 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,303 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { type ReactNode, useEffect, useId, useRef, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { LuEllipsis, LuSearch, LuX } from 'react-icons/lu';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ButtonLink,
|
|
9
|
+
DefraLogo,
|
|
10
|
+
ExternalLink,
|
|
11
|
+
FormGroup,
|
|
12
|
+
Label,
|
|
13
|
+
Link,
|
|
14
|
+
PopoverContent,
|
|
15
|
+
PopoverTrigger,
|
|
16
|
+
SearchBox,
|
|
17
|
+
Separator,
|
|
18
|
+
WidthContainer,
|
|
19
|
+
Popover,
|
|
20
|
+
Paragraph,
|
|
21
|
+
ParagraphGroup,
|
|
22
|
+
} from '@tpzdsp/next-toolkit/components';
|
|
23
|
+
import { cn } from '@tpzdsp/next-toolkit/utils';
|
|
24
|
+
|
|
25
|
+
import type { Credentials } from '../../types/auth';
|
|
26
|
+
|
|
27
|
+
export type HeaderProps = {
|
|
28
|
+
appContent?: ReactNode;
|
|
29
|
+
credentials?: Credentials | null;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const DSP_NAV_LINKS = [
|
|
33
|
+
{
|
|
34
|
+
href: '/appgallery',
|
|
35
|
+
label: 'Apps',
|
|
36
|
+
mainNav: '@max-[520px]/main:hidden',
|
|
37
|
+
drawerNav: 'hidden @max-[520px]/drawer:block',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
href: '/apiportal',
|
|
41
|
+
label: 'APIs',
|
|
42
|
+
mainNav: '@max-[620px]/main:hidden',
|
|
43
|
+
drawerNav: 'hidden @max-[620px]/drawer:block',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
href: '/survey',
|
|
47
|
+
label: 'Survey',
|
|
48
|
+
mainNav: '@max-[720px]/main:hidden',
|
|
49
|
+
drawerNav: 'hidden @max-[720px]/drawer:block',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
href: '/support',
|
|
53
|
+
label: 'Support',
|
|
54
|
+
mainNav: '@max-[820px]/main:hidden',
|
|
55
|
+
drawerNav: 'hidden @max-[820px]/drawer:block',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
href: '/register',
|
|
59
|
+
label: 'Create an account',
|
|
60
|
+
mainNav: '@max-[970px]/main:hidden',
|
|
61
|
+
drawerNav: 'hidden @max-[970px]/drawer:block',
|
|
62
|
+
},
|
|
63
|
+
] as const;
|
|
64
|
+
|
|
65
|
+
const HOME_URL = '/';
|
|
66
|
+
const LOGIN_URL = '/login';
|
|
67
|
+
const LOGOUT_URL = '/logout';
|
|
68
|
+
const DEFRA_URL =
|
|
69
|
+
'https://www.gov.uk/government/organisations/department-for-environment-food-rural-affairs';
|
|
70
|
+
const GLOBAL_SEARCH_URL = '/globalSearch';
|
|
71
|
+
const GLOBAL_SEARCH_QUERY_PARAM = 'query';
|
|
72
|
+
|
|
73
|
+
export const Header = ({ appContent, credentials }: HeaderProps) => {
|
|
74
|
+
const navDrawerId = useId();
|
|
75
|
+
const searchDrawerId = useId();
|
|
76
|
+
const searchInputId = useId();
|
|
77
|
+
|
|
78
|
+
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
|
79
|
+
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
|
80
|
+
const [redirectUri, setRedirectUri] = useState('');
|
|
81
|
+
|
|
82
|
+
const triggerRef = useRef<HTMLLIElement>(null);
|
|
83
|
+
const drawerRef = useRef<HTMLDivElement>(null);
|
|
84
|
+
const hasOpenedRef = useRef(false);
|
|
85
|
+
|
|
86
|
+
// window isn't available during SSR, and the raw value needs encoding
|
|
87
|
+
// anyway (an unescaped ?/&/# in the current URL would otherwise corrupt
|
|
88
|
+
// this query string), so this is deferred to a client-only effect rather
|
|
89
|
+
// than read directly during render. Full absolute URL (not just path +
|
|
90
|
+
// search), sign-in/sign-out live on a separate origin, so it needs the
|
|
91
|
+
// hostname to know where to send the user back to.
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
setRedirectUri(encodeURIComponent(window.location.href));
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
// Move focus into the drawer when it opens; return it to the trigger when it closes.
|
|
97
|
+
// hasOpenedRef prevents a focus jump on initial render.
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (isDrawerOpen) {
|
|
100
|
+
hasOpenedRef.current = true;
|
|
101
|
+
|
|
102
|
+
const firstFocusable = drawerRef.current?.querySelector<HTMLElement>(
|
|
103
|
+
'a[href], button:not([disabled])',
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
firstFocusable?.focus();
|
|
107
|
+
} else if (hasOpenedRef.current) {
|
|
108
|
+
triggerRef.current?.querySelector<HTMLElement>('button')?.focus();
|
|
109
|
+
}
|
|
110
|
+
}, [isDrawerOpen]);
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (!isDrawerOpen) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
118
|
+
if (e.key === 'Escape') {
|
|
119
|
+
setIsDrawerOpen(false);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
124
|
+
|
|
125
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
126
|
+
}, [isDrawerOpen]);
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<header className="w-full text-white border-b border-neutral-mid">
|
|
130
|
+
<WidthContainer className="flex items-center justify-center bg-accent z-10 relative">
|
|
131
|
+
<div
|
|
132
|
+
className="w-full h-14 flex p-gds-2 justify-between items-center gap-gds-4
|
|
133
|
+
@container/main"
|
|
134
|
+
>
|
|
135
|
+
<div className="flex h-full min-w-0 items-center gap-gds-4">
|
|
136
|
+
<div className="flex shrink-0 gap-gds-2 h-full items-center">
|
|
137
|
+
<ExternalLink
|
|
138
|
+
href={DEFRA_URL}
|
|
139
|
+
appearance="inverse"
|
|
140
|
+
variant="plain"
|
|
141
|
+
className="flex gap-gds-1 h-full items-center"
|
|
142
|
+
>
|
|
143
|
+
<DefraLogo aria-hidden className="h-full w-auto shrink-0 block" />
|
|
144
|
+
|
|
145
|
+
<abbr
|
|
146
|
+
title="Department for Environment, Food, and Rural Affairs"
|
|
147
|
+
className="sr-only xs:not-sr-only"
|
|
148
|
+
>
|
|
149
|
+
DEFRA
|
|
150
|
+
</abbr>
|
|
151
|
+
</ExternalLink>
|
|
152
|
+
|
|
153
|
+
<Separator orientation="vertical" className="bg-white" />
|
|
154
|
+
|
|
155
|
+
<Link href={HOME_URL} appearance="inverse" className="text-nowrap">
|
|
156
|
+
<span className="hidden xs:inline">Data Services Platform</span>
|
|
157
|
+
|
|
158
|
+
<span className="inline xs:hidden">
|
|
159
|
+
<abbr title="Data Services Platform">DSP</abbr>
|
|
160
|
+
</span>
|
|
161
|
+
</Link>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<nav aria-label="Primary navigation" className="min-w-0">
|
|
165
|
+
<ul className="flex items-center gap-gds-4">
|
|
166
|
+
{DSP_NAV_LINKS.map((item) => (
|
|
167
|
+
<li className={cn('flex items-center nav-item', item.mainNav)} key={item.href}>
|
|
168
|
+
<ExternalLink href={item.href} appearance="inverse" variant="plain">
|
|
169
|
+
{item.label}
|
|
170
|
+
</ExternalLink>
|
|
171
|
+
</li>
|
|
172
|
+
))}
|
|
173
|
+
<li ref={triggerRef} className="flex items-center more-btn shrink-0">
|
|
174
|
+
<ButtonLink
|
|
175
|
+
aria-label="Navigation drawer"
|
|
176
|
+
aria-expanded={isDrawerOpen}
|
|
177
|
+
aria-controls={navDrawerId}
|
|
178
|
+
appearance="inverse"
|
|
179
|
+
variant="icon"
|
|
180
|
+
onClick={() => setIsDrawerOpen(!isDrawerOpen)}
|
|
181
|
+
>
|
|
182
|
+
<LuEllipsis aria-hidden />
|
|
183
|
+
</ButtonLink>
|
|
184
|
+
</li>
|
|
185
|
+
</ul>
|
|
186
|
+
</nav>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div className="flex shrink-0 gap-gds-2 h-full items-center">
|
|
190
|
+
{credentials ? (
|
|
191
|
+
<Popover>
|
|
192
|
+
<PopoverTrigger asChild>
|
|
193
|
+
<ButtonLink appearance="inverse">Profile</ButtonLink>
|
|
194
|
+
</PopoverTrigger>
|
|
195
|
+
|
|
196
|
+
<PopoverContent sideOffset={0}>
|
|
197
|
+
<ParagraphGroup>
|
|
198
|
+
<Paragraph>
|
|
199
|
+
<div className="font-bold">Signed in as:</div>
|
|
200
|
+
|
|
201
|
+
<div>{credentials.user.email}</div>
|
|
202
|
+
</Paragraph>
|
|
203
|
+
|
|
204
|
+
<Link href={`${LOGOUT_URL}?redirect_uri=${redirectUri}`}>Sign-out</Link>
|
|
205
|
+
</ParagraphGroup>
|
|
206
|
+
</PopoverContent>
|
|
207
|
+
</Popover>
|
|
208
|
+
) : (
|
|
209
|
+
<Link
|
|
210
|
+
href={`${LOGIN_URL}?redirect_uri=${redirectUri}`}
|
|
211
|
+
appearance="inverse"
|
|
212
|
+
className="text-nowrap"
|
|
213
|
+
>
|
|
214
|
+
Sign-in
|
|
215
|
+
</Link>
|
|
216
|
+
)}
|
|
217
|
+
|
|
218
|
+
<Separator orientation="vertical" className="bg-white" />
|
|
219
|
+
|
|
220
|
+
<ButtonLink
|
|
221
|
+
appearance="inverse"
|
|
222
|
+
variant="icon"
|
|
223
|
+
aria-label="Search the Data Services Platform"
|
|
224
|
+
aria-controls={searchDrawerId}
|
|
225
|
+
aria-expanded={isSearchOpen}
|
|
226
|
+
onClick={() => setIsSearchOpen(!isSearchOpen)}
|
|
227
|
+
>
|
|
228
|
+
<LuSearch aria-hidden />
|
|
229
|
+
</ButtonLink>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</WidthContainer>
|
|
233
|
+
|
|
234
|
+
<div
|
|
235
|
+
id={searchDrawerId}
|
|
236
|
+
inert={!isSearchOpen}
|
|
237
|
+
className={cn(
|
|
238
|
+
'w-full bg-wash grid transition-[grid-template-rows] duration-200',
|
|
239
|
+
isSearchOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
|
|
240
|
+
)}
|
|
241
|
+
>
|
|
242
|
+
<WidthContainer className="min-h-0 overflow-hidden">
|
|
243
|
+
<div className="w-full h-40 p-gds-6 flex items-center">
|
|
244
|
+
<form action={GLOBAL_SEARCH_URL} className="w-full">
|
|
245
|
+
<FormGroup>
|
|
246
|
+
<Label htmlFor={searchInputId} className="font-bold text-2xl">
|
|
247
|
+
Search DSP
|
|
248
|
+
</Label>
|
|
249
|
+
|
|
250
|
+
<SearchBox id={searchInputId} name={GLOBAL_SEARCH_QUERY_PARAM} />
|
|
251
|
+
</FormGroup>
|
|
252
|
+
</form>
|
|
253
|
+
</div>
|
|
254
|
+
</WidthContainer>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<div
|
|
258
|
+
id={navDrawerId}
|
|
259
|
+
ref={drawerRef}
|
|
260
|
+
inert={!isDrawerOpen}
|
|
261
|
+
className={cn(
|
|
262
|
+
`w-full bg-accent grid transition-[grid-template-rows] duration-200 border-b
|
|
263
|
+
border-neutral-mid`,
|
|
264
|
+
isDrawerOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
|
|
265
|
+
)}
|
|
266
|
+
>
|
|
267
|
+
<WidthContainer className="bg-accent min-h-0 overflow-hidden">
|
|
268
|
+
<div
|
|
269
|
+
className="w-full min-h-14 flex p-gds-2 justify-between items-center gap-gds-4
|
|
270
|
+
@container/drawer"
|
|
271
|
+
>
|
|
272
|
+
<nav aria-label="More navigation">
|
|
273
|
+
<ul className="flex flex-wrap w-full gap-x-gds-2 gap-y-gds-2">
|
|
274
|
+
{DSP_NAV_LINKS.map((item) => (
|
|
275
|
+
<li className={cn('flex items-center nav-item', item.drawerNav)} key={item.href}>
|
|
276
|
+
<ExternalLink href={item.href} appearance="inverse" variant="plain">
|
|
277
|
+
{item.label}
|
|
278
|
+
</ExternalLink>
|
|
279
|
+
</li>
|
|
280
|
+
))}
|
|
281
|
+
</ul>
|
|
282
|
+
</nav>
|
|
283
|
+
|
|
284
|
+
<ButtonLink
|
|
285
|
+
aria-label="Close"
|
|
286
|
+
appearance="inverse"
|
|
287
|
+
variant="icon"
|
|
288
|
+
onClick={() => setIsDrawerOpen(false)}
|
|
289
|
+
>
|
|
290
|
+
<LuX aria-hidden />
|
|
291
|
+
</ButtonLink>
|
|
292
|
+
</div>
|
|
293
|
+
</WidthContainer>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
{appContent ? (
|
|
297
|
+
<WidthContainer className="bg-body-secondary">
|
|
298
|
+
<div className="w-full h-14 flex p-gds-2 items-center">{appContent}</div>
|
|
299
|
+
</WidthContainer>
|
|
300
|
+
) : null}
|
|
301
|
+
</header>
|
|
302
|
+
);
|
|
303
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
import { LuEllipsis } from 'react-icons/lu';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ButtonLink,
|
|
7
|
+
DropdownMenu,
|
|
8
|
+
DropdownMenuContent,
|
|
9
|
+
DropdownMenuItem,
|
|
10
|
+
DropdownMenuTrigger,
|
|
11
|
+
Link,
|
|
12
|
+
} from '@tpzdsp/next-toolkit/components';
|
|
13
|
+
import type { LinkProps } from '@tpzdsp/next-toolkit/components';
|
|
14
|
+
|
|
15
|
+
import type { NavLink } from '../../types';
|
|
16
|
+
|
|
17
|
+
export type HeaderAppContentProps = {
|
|
18
|
+
/** App name, shown on the left. Plain text, or a NavLink if it should link
|
|
19
|
+
* somewhere (e.g. back to the app's home page). */
|
|
20
|
+
title: string | NavLink;
|
|
21
|
+
links?: NavLink[];
|
|
22
|
+
/** Defaults to this library's own Link (a plain <a>). Pass your app's own Link
|
|
23
|
+
* wrapper instead if it needs Next.js client-side routing, e.g. one that uses
|
|
24
|
+
* asChild internally with next/link, it just needs to accept the same props. */
|
|
25
|
+
linkComponent?: ComponentType<LinkProps>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Omit<LinkProps, ...> (not a fixed prop list): when this sits inside
|
|
29
|
+
// DropdownMenuItem asChild, Radix clones this component and merges its own
|
|
30
|
+
// props onto it (role="menuitem", tabIndex, the keyboard/selection
|
|
31
|
+
// handlers roving-tabindex needs, className). Without a rest spread here to
|
|
32
|
+
// forward all of that through to LinkComponent, Radix's merge dead-ends at
|
|
33
|
+
// this component and every one of those props is silently dropped, not
|
|
34
|
+
// just a styling gap, menuitem role and keyboard handling would be missing
|
|
35
|
+
// too.
|
|
36
|
+
type HeaderAppContentNavLinkProps = Omit<LinkProps, 'href' | 'children'> & {
|
|
37
|
+
link: NavLink;
|
|
38
|
+
linkComponent: ComponentType<LinkProps>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const HeaderAppContentNavLink = ({
|
|
42
|
+
link,
|
|
43
|
+
linkComponent: LinkComponent,
|
|
44
|
+
appearance = 'inverse',
|
|
45
|
+
...props
|
|
46
|
+
}: HeaderAppContentNavLinkProps) => (
|
|
47
|
+
<LinkComponent
|
|
48
|
+
href={link.url}
|
|
49
|
+
appearance={appearance}
|
|
50
|
+
target={link.openInNewTab ? '_blank' : undefined}
|
|
51
|
+
rel={link.openInNewTab ? 'noopener noreferrer' : undefined}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
{link.label}
|
|
55
|
+
</LinkComponent>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
export const HeaderAppContent = ({
|
|
59
|
+
title,
|
|
60
|
+
links = [],
|
|
61
|
+
linkComponent: LinkComponent = Link,
|
|
62
|
+
}: HeaderAppContentProps) => (
|
|
63
|
+
<div className="@container/app-nav flex w-full items-center justify-between gap-gds-4">
|
|
64
|
+
<div className="flex shrink-0 items-center gap-gds-2 text-lg font-bold">
|
|
65
|
+
{typeof title === 'string' ? (
|
|
66
|
+
title
|
|
67
|
+
) : (
|
|
68
|
+
<HeaderAppContentNavLink link={title} linkComponent={LinkComponent} />
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
{links.length > 0 ? (
|
|
73
|
+
<>
|
|
74
|
+
{/* 650px: below this the inline list and the title no longer
|
|
75
|
+
comfortably fit side by side. A single width rather than
|
|
76
|
+
per-link breakpoints (contrast Header.tsx's own DSP_NAV_LINKS,
|
|
77
|
+
which hides one specific, known link at a time): links here are
|
|
78
|
+
an arbitrary, app-supplied list, there's no fixed set to
|
|
79
|
+
hand-tune individual cutoffs for, so this collapses the whole
|
|
80
|
+
list to a menu at once instead. A reasonable default, not a
|
|
81
|
+
computed value, nudge it if a specific app's title/link lengths
|
|
82
|
+
need something else. */}
|
|
83
|
+
<nav aria-label="App" className="hidden min-w-0 @min-[650px]/app-nav:block">
|
|
84
|
+
<ul className="flex items-center gap-gds-4">
|
|
85
|
+
{links.map((link) => (
|
|
86
|
+
<li key={link.url}>
|
|
87
|
+
<HeaderAppContentNavLink link={link} linkComponent={LinkComponent} />
|
|
88
|
+
</li>
|
|
89
|
+
))}
|
|
90
|
+
</ul>
|
|
91
|
+
</nav>
|
|
92
|
+
|
|
93
|
+
<DropdownMenu>
|
|
94
|
+
<DropdownMenuTrigger asChild>
|
|
95
|
+
<ButtonLink
|
|
96
|
+
appearance="inverse"
|
|
97
|
+
variant="icon"
|
|
98
|
+
aria-label="App navigation"
|
|
99
|
+
className="@min-[650px]/app-nav:hidden"
|
|
100
|
+
>
|
|
101
|
+
<LuEllipsis aria-hidden />
|
|
102
|
+
</ButtonLink>
|
|
103
|
+
</DropdownMenuTrigger>
|
|
104
|
+
|
|
105
|
+
<DropdownMenuContent align="end">
|
|
106
|
+
{links.map((link) => (
|
|
107
|
+
<DropdownMenuItem key={link.url} asChild>
|
|
108
|
+
<HeaderAppContentNavLink
|
|
109
|
+
link={link}
|
|
110
|
+
linkComponent={LinkComponent}
|
|
111
|
+
appearance="contrast"
|
|
112
|
+
/>
|
|
113
|
+
</DropdownMenuItem>
|
|
114
|
+
))}
|
|
115
|
+
</DropdownMenuContent>
|
|
116
|
+
</DropdownMenu>
|
|
117
|
+
</>
|
|
118
|
+
) : null}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: Heading */
|
|
3
|
+
.cn-heading {
|
|
4
|
+
@apply font-bold;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.cn-heading-1 {
|
|
8
|
+
@apply text-[2rem] sm:text-5xl;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.cn-heading-2 {
|
|
12
|
+
@apply text-2xl sm:text-4xl;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.cn-heading-3 {
|
|
16
|
+
@apply text-xl sm:text-3xl;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.cn-heading-4 {
|
|
20
|
+
@apply text-lg sm:text-2xl;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.cn-heading-5 {
|
|
24
|
+
@apply text-base sm:text-xl;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.cn-heading-6 {
|
|
28
|
+
@apply text-sm sm:text-lg;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* MARK: Skeleton
|
|
32
|
+
Sized in em so it scales with whichever cn-heading-N font-size applies,
|
|
33
|
+
one rule covers every level. Shorter than the line-height (~1.2-1.5x
|
|
34
|
+
font-size for every text-* utility here) so it sits inside the line box
|
|
35
|
+
rather than stretching it, the heading ends up exactly as tall as it
|
|
36
|
+
would be with real text. */
|
|
37
|
+
.cn-heading-skeleton-bar {
|
|
38
|
+
@apply h-[0.7em] w-3/4;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -2,16 +2,26 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
|
2
2
|
|
|
3
3
|
import { Heading, type HeadingProps } from './Heading';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const meta: Meta<typeof Heading> = {
|
|
6
|
+
title: 'Components/Heading/Heading',
|
|
7
7
|
component: Heading,
|
|
8
|
-
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component: 'Responsive heading text.',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
9
19
|
|
|
10
20
|
export const AllHeadings: StoryObj<typeof Heading> = {
|
|
11
21
|
render: () => (
|
|
12
22
|
<div>
|
|
13
|
-
{(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as HeadingProps['
|
|
14
|
-
<Heading key={type}
|
|
23
|
+
{(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as HeadingProps['level'][]).map((type) => (
|
|
24
|
+
<Heading key={type} level={type}>
|
|
15
25
|
{type.toUpperCase()} Example
|
|
16
26
|
</Heading>
|
|
17
27
|
))}
|
|
@@ -1,50 +1,27 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
2
3
|
import { cn } from '../../utils';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
// h1 through h6 have identical props
|
|
6
|
+
export type HeadingProps = ComponentProps<'h1'> & {
|
|
7
|
+
level: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
6
8
|
};
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
const HEADING_LEVELS = {
|
|
11
|
+
h1: 'cn-heading-1',
|
|
12
|
+
h2: 'cn-heading-2',
|
|
13
|
+
h3: 'cn-heading-3',
|
|
14
|
+
h4: 'cn-heading-4',
|
|
15
|
+
h5: 'cn-heading-5',
|
|
16
|
+
h6: 'cn-heading-6',
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export const Heading = ({ level, className, children, ...props }: HeadingProps) => {
|
|
20
|
+
const Comp = level;
|
|
10
21
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{children}
|
|
17
|
-
</h1>
|
|
18
|
-
);
|
|
19
|
-
case 'h2':
|
|
20
|
-
return (
|
|
21
|
-
<h2 className={cn('py-3 text-3xl font-bold text-text-primary', className)} {...props}>
|
|
22
|
-
{children}
|
|
23
|
-
</h2>
|
|
24
|
-
);
|
|
25
|
-
case 'h3':
|
|
26
|
-
return (
|
|
27
|
-
<h3 className={cn('py-3 text-xl font-bold text-text-primary', className)} {...props}>
|
|
28
|
-
{children}
|
|
29
|
-
</h3>
|
|
30
|
-
);
|
|
31
|
-
case 'h4':
|
|
32
|
-
return (
|
|
33
|
-
<h4 className={cn('py-3 text-lg font-bold text-text-primary', className)} {...props}>
|
|
34
|
-
{children}
|
|
35
|
-
</h4>
|
|
36
|
-
);
|
|
37
|
-
case 'h5':
|
|
38
|
-
return (
|
|
39
|
-
<h5 className={cn('py-2 text-base font-bold text-text-primary', className)} {...props}>
|
|
40
|
-
{children}
|
|
41
|
-
</h5>
|
|
42
|
-
);
|
|
43
|
-
case 'h6':
|
|
44
|
-
return (
|
|
45
|
-
<h6 className={cn('py-2 text-sm font-bold text-text-primary', className)} {...props}>
|
|
46
|
-
{children}
|
|
47
|
-
</h6>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
22
|
+
return (
|
|
23
|
+
<Comp className={cn('cn-heading', HEADING_LEVELS[level], className)} {...props}>
|
|
24
|
+
{children}
|
|
25
|
+
</Comp>
|
|
26
|
+
);
|
|
50
27
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import type { HeadingProps } from './Heading';
|
|
4
|
+
import { HeadingSkeleton } from './HeadingSkeleton';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof HeadingSkeleton> = {
|
|
7
|
+
title: 'Components/Heading/Skeleton',
|
|
8
|
+
component: HeadingSkeleton,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component:
|
|
14
|
+
'Placeholder for a Heading while its real text is loading. Renders a real Heading at the given level, so the font-size and line-height (and therefore the rendered height) always match what real text at that level would produce, no layout shift when it swaps in.',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
|
|
22
|
+
export const AllLevels: StoryObj<typeof HeadingSkeleton> = {
|
|
23
|
+
render: () => (
|
|
24
|
+
<div className="flex flex-col gap-gds-2">
|
|
25
|
+
{(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as HeadingProps['level'][]).map((level) => (
|
|
26
|
+
<HeadingSkeleton key={level} level={level} />
|
|
27
|
+
))}
|
|
28
|
+
</div>
|
|
29
|
+
),
|
|
30
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Heading } from './Heading';
|
|
4
|
+
import { Skeleton } from '../Skeleton/Skeleton';
|
|
5
|
+
|
|
6
|
+
export type HeadingSkeletonProps = Omit<ComponentProps<typeof Heading>, 'children'> & {
|
|
7
|
+
children?: never;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const HeadingSkeleton = ({ className, ...props }: HeadingSkeletonProps) => (
|
|
11
|
+
<Heading aria-hidden className={className} {...props}>
|
|
12
|
+
<Skeleton className="cn-heading-skeleton-bar" />
|
|
13
|
+
</Heading>
|
|
14
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* Full-bleed breakout: Hero is meant to span the viewport regardless of
|
|
3
|
+
where it's mounted, but it's built on WidthContainer, so if it's ever
|
|
4
|
+
nested inside another WidthContainer/MainWrapper (e.g. rendered as page
|
|
5
|
+
content under a shared layout's <main>), it would otherwise inherit that
|
|
6
|
+
ancestor's max-w-page cap. left: 50% shifts it to the viewport's
|
|
7
|
+
horizontal center regardless of the containing block's width, as long as
|
|
8
|
+
that containing block is itself symmetrically centered, true here since
|
|
9
|
+
every ancestor up to the viewport is either full-width or mx-auto; width:
|
|
10
|
+
100vw + the negative margins then pull its edges out to the true viewport
|
|
11
|
+
edges. Pair with overflow-x-clip on PageShell (or another page-level
|
|
12
|
+
ancestor), 100vw includes the scrollbar gutter in browsers that reserve
|
|
13
|
+
space for it, which can otherwise introduce a few pixels of unwanted
|
|
14
|
+
horizontal scroll. */
|
|
15
|
+
.cn-hero {
|
|
16
|
+
@apply bg-accent text-on-accent py-gds-4 sm:py-gds-6;
|
|
17
|
+
position: relative;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { Hero } from './Hero';
|
|
4
|
+
import { Heading } from '../Heading/Heading';
|
|
5
|
+
import { ButtonGroup } from '../Layout/ButtonGroup/ButtonGroup';
|
|
6
|
+
import { HeadingGroup } from '../Layout/HeadingGroup/HeadingGroup';
|
|
7
|
+
import { Paragraph } from '../Paragraph/Paragraph';
|
|
8
|
+
import { StartButton } from '../StartButton/StartButton';
|
|
9
|
+
|
|
10
|
+
const meta: Meta<typeof Hero> = {
|
|
11
|
+
title: 'Components/Hero',
|
|
12
|
+
component: Hero,
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
parameters: {
|
|
15
|
+
docs: {
|
|
16
|
+
description: {
|
|
17
|
+
component: 'Use a Hero to associate text with form controls and improve accessibility.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
|
|
25
|
+
type Story = StoryObj<typeof Hero>;
|
|
26
|
+
|
|
27
|
+
export const Default: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<Hero>
|
|
30
|
+
<ButtonGroup orientation="vertical">
|
|
31
|
+
<HeadingGroup>
|
|
32
|
+
<Heading level="h1">UK Wetland Inventory</Heading>
|
|
33
|
+
|
|
34
|
+
<Paragraph>Explore the UK's vital wetland habits</Paragraph>
|
|
35
|
+
</HeadingGroup>
|
|
36
|
+
|
|
37
|
+
<StartButton appearance="secondary" href="#" />
|
|
38
|
+
</ButtonGroup>
|
|
39
|
+
</Hero>
|
|
40
|
+
),
|
|
41
|
+
};
|