@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,225 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useState,
|
|
10
|
+
type CSSProperties,
|
|
11
|
+
type ComponentProps,
|
|
12
|
+
type MouseEvent,
|
|
13
|
+
} from 'react';
|
|
14
|
+
|
|
15
|
+
import { LuX } from 'react-icons/lu';
|
|
16
|
+
|
|
17
|
+
import { cn } from '../../utils/components';
|
|
18
|
+
import { Button } from '../Button/Button';
|
|
19
|
+
|
|
20
|
+
// MARK: Types
|
|
21
|
+
|
|
22
|
+
type SidebarSide = 'left' | 'right' | 'top' | 'bottom';
|
|
23
|
+
type SidebarOrientation = 'horizontal' | 'vertical';
|
|
24
|
+
|
|
25
|
+
type SidebarContextValue = {
|
|
26
|
+
open: boolean;
|
|
27
|
+
setOpen: (open: boolean) => void;
|
|
28
|
+
toggleSidebar: () => void;
|
|
29
|
+
isMobile: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type SidebarProviderProps = ComponentProps<'div'> & {
|
|
33
|
+
open?: boolean;
|
|
34
|
+
onOpenChange?: (open: boolean) => void;
|
|
35
|
+
defaultOpen?: boolean;
|
|
36
|
+
/** Width of the sidebar panel for left/right sidebars. */
|
|
37
|
+
width?: string;
|
|
38
|
+
/** Height of the sidebar panel for top/bottom sidebars. */
|
|
39
|
+
height?: string;
|
|
40
|
+
/** Must be 'vertical' when using side="top" or side="bottom". */
|
|
41
|
+
orientation?: SidebarOrientation;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type SidebarProps = ComponentProps<'aside'> & {
|
|
45
|
+
side?: SidebarSide;
|
|
46
|
+
variant?: 'push' | 'overlay';
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// MARK: Context
|
|
50
|
+
|
|
51
|
+
const SidebarContext = createContext<SidebarContextValue | null>(null);
|
|
52
|
+
|
|
53
|
+
export const useSidebar = (): SidebarContextValue => {
|
|
54
|
+
const context = useContext(SidebarContext);
|
|
55
|
+
|
|
56
|
+
if (!context) {
|
|
57
|
+
throw new Error('useSidebar must be used inside SidebarProvider');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return context;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const useIsMobile = (breakpoint = 640) => {
|
|
64
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const mq = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
|
|
68
|
+
|
|
69
|
+
setIsMobile(mq.matches);
|
|
70
|
+
const handler = (event: MediaQueryListEvent) => setIsMobile(event.matches);
|
|
71
|
+
|
|
72
|
+
mq.addEventListener('change', handler);
|
|
73
|
+
|
|
74
|
+
return () => mq.removeEventListener('change', handler);
|
|
75
|
+
}, [breakpoint]);
|
|
76
|
+
|
|
77
|
+
return isMobile;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// MARK: SidebarProvider
|
|
81
|
+
|
|
82
|
+
export const SidebarProvider = ({
|
|
83
|
+
open: controlledOpen,
|
|
84
|
+
onOpenChange,
|
|
85
|
+
defaultOpen = true,
|
|
86
|
+
width = '16rem',
|
|
87
|
+
height = '12rem',
|
|
88
|
+
orientation = 'horizontal',
|
|
89
|
+
children,
|
|
90
|
+
className,
|
|
91
|
+
style,
|
|
92
|
+
...props
|
|
93
|
+
}: SidebarProviderProps) => {
|
|
94
|
+
const [_open, _setOpen] = useState(defaultOpen);
|
|
95
|
+
const open = controlledOpen ?? _open;
|
|
96
|
+
const isMobile = useIsMobile();
|
|
97
|
+
|
|
98
|
+
const setOpen = useCallback(
|
|
99
|
+
(value: boolean) => {
|
|
100
|
+
_setOpen(value);
|
|
101
|
+
onOpenChange?.(value);
|
|
102
|
+
},
|
|
103
|
+
[onOpenChange],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const toggleSidebar = useCallback(() => setOpen(!open), [open, setOpen]);
|
|
107
|
+
|
|
108
|
+
const contextValue = useMemo(
|
|
109
|
+
() => ({ open, setOpen, toggleSidebar, isMobile }),
|
|
110
|
+
[open, setOpen, toggleSidebar, isMobile],
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<SidebarContext.Provider value={contextValue}>
|
|
115
|
+
<div
|
|
116
|
+
data-slot="sidebar-provider"
|
|
117
|
+
data-orientation={orientation}
|
|
118
|
+
className={cn('cn-sidebar-provider', className)}
|
|
119
|
+
style={
|
|
120
|
+
{
|
|
121
|
+
'--cn-sidebar-width': width,
|
|
122
|
+
'--cn-sidebar-height': height,
|
|
123
|
+
...style,
|
|
124
|
+
} as CSSProperties
|
|
125
|
+
}
|
|
126
|
+
{...props}
|
|
127
|
+
>
|
|
128
|
+
{children}
|
|
129
|
+
</div>
|
|
130
|
+
</SidebarContext.Provider>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// MARK: Sidebar
|
|
135
|
+
|
|
136
|
+
export const Sidebar = ({
|
|
137
|
+
side = 'left',
|
|
138
|
+
variant = 'push',
|
|
139
|
+
children,
|
|
140
|
+
className,
|
|
141
|
+
'aria-label': ariaLabel = 'Sidebar',
|
|
142
|
+
...props
|
|
143
|
+
}: SidebarProps) => {
|
|
144
|
+
const { open, isMobile, setOpen } = useSidebar();
|
|
145
|
+
const state = open ? 'expanded' : 'collapsed';
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div
|
|
149
|
+
data-state={state}
|
|
150
|
+
data-side={side}
|
|
151
|
+
data-variant={variant}
|
|
152
|
+
data-mobile={isMobile || undefined}
|
|
153
|
+
className="cn-sidebar-root"
|
|
154
|
+
>
|
|
155
|
+
<div aria-hidden="true" className="cn-sidebar-gap" />
|
|
156
|
+
|
|
157
|
+
{isMobile && open && (
|
|
158
|
+
<div aria-hidden="true" className="cn-sidebar-backdrop" onClick={() => setOpen(false)} />
|
|
159
|
+
)}
|
|
160
|
+
|
|
161
|
+
<aside
|
|
162
|
+
data-slot="sidebar"
|
|
163
|
+
data-state={state}
|
|
164
|
+
aria-label={ariaLabel}
|
|
165
|
+
inert={open ? undefined : true}
|
|
166
|
+
className={cn('cn-sidebar', className)}
|
|
167
|
+
{...props}
|
|
168
|
+
>
|
|
169
|
+
{children}
|
|
170
|
+
</aside>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// MARK: SidebarClose
|
|
176
|
+
|
|
177
|
+
export const SidebarClose = ({
|
|
178
|
+
className,
|
|
179
|
+
'aria-label': ariaLabel = 'Close sidebar',
|
|
180
|
+
onClick,
|
|
181
|
+
...props
|
|
182
|
+
}: Omit<ComponentProps<'button'>, 'children'>) => {
|
|
183
|
+
const { setOpen } = useSidebar();
|
|
184
|
+
|
|
185
|
+
const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
|
|
186
|
+
setOpen(false);
|
|
187
|
+
onClick?.(event);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<Button
|
|
192
|
+
variant="icon"
|
|
193
|
+
appearance="ghost"
|
|
194
|
+
data-slot="sidebar-close"
|
|
195
|
+
className={cn(className)}
|
|
196
|
+
aria-label={ariaLabel}
|
|
197
|
+
onClick={handleClick}
|
|
198
|
+
{...props}
|
|
199
|
+
>
|
|
200
|
+
<LuX aria-hidden />
|
|
201
|
+
</Button>
|
|
202
|
+
);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// MARK: SidebarInset
|
|
206
|
+
|
|
207
|
+
export const SidebarInset = ({ className, ...props }: ComponentProps<'div'>) => (
|
|
208
|
+
<div data-slot="sidebar-inset" className={cn('cn-sidebar-inset', className)} {...props} />
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// MARK: Structure
|
|
212
|
+
|
|
213
|
+
export const SidebarHeader = ({ className, ...props }: ComponentProps<'div'>) => (
|
|
214
|
+
<div data-slot="sidebar-header" className={cn('cn-sidebar-header', className)} {...props} />
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
export const SidebarContent = ({ className, ...props }: ComponentProps<'div'>) => (
|
|
218
|
+
<div data-slot="sidebar-content" className={cn('cn-sidebar-content', className)} {...props} />
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
export const SidebarFooter = ({ className, ...props }: ComponentProps<'div'>) => (
|
|
222
|
+
<div data-slot="sidebar-footer" className={cn('cn-sidebar-footer', className)} {...props} />
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
export type { SidebarSide, SidebarOrientation, SidebarProviderProps, SidebarProps };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: Skeleton
|
|
3
|
+
Base placeholder shape shared by every per-component skeleton.
|
|
4
|
+
inline-block so it drops into text flow (heading/paragraph/button label
|
|
5
|
+
bars) without a forced line break, size it via className. Purely
|
|
6
|
+
decorative (aria-hidden), if you're assembling several of these into a
|
|
7
|
+
loading page, wrap the whole group in one role="status" region rather
|
|
8
|
+
than announcing loading per shape. */
|
|
9
|
+
.cn-skeleton {
|
|
10
|
+
/* bg-wash (5% black) is too faint to read clearly against a white page,
|
|
11
|
+
it's tuned for subtle hover backgrounds, not something that needs to
|
|
12
|
+
stand out. bg-muted (20% black, the same token dividers/disabled
|
|
13
|
+
states use) has enough contrast to actually read as "loading". */
|
|
14
|
+
@apply inline-block bg-muted animate-pulse;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { Skeleton } from './Skeleton';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Skeleton> = {
|
|
6
|
+
title: 'Components/Skeleton',
|
|
7
|
+
component: Skeleton,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
"Base placeholder shape shared by every per-component skeleton (HeadingSkeleton, ParagraphSkeleton, ButtonSkeleton, ...). Size it via `className`. Prefer reusing a real component's own classes over building a shape from scratch, so the skeleton always matches that component's real dimensions and never causes a layout shift when the real content loads in.",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
|
|
21
|
+
type Story = StoryObj<typeof Skeleton>;
|
|
22
|
+
|
|
23
|
+
export const Default: Story = {
|
|
24
|
+
render: () => <Skeleton className="h-4 w-48" />,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const Shapes: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<div className="flex flex-col gap-gds-2">
|
|
30
|
+
<Skeleton className="h-4 w-64" />
|
|
31
|
+
|
|
32
|
+
<Skeleton className="h-4 w-48" />
|
|
33
|
+
|
|
34
|
+
<Skeleton className="h-10 w-32" />
|
|
35
|
+
|
|
36
|
+
<Skeleton className="size-10" />
|
|
37
|
+
</div>
|
|
38
|
+
),
|
|
39
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils/components';
|
|
4
|
+
|
|
5
|
+
export type SkeletonProps = ComponentProps<'div'> & {
|
|
6
|
+
/** Explicitly disallow children, a skeleton is a placeholder shape, not a content wrapper. */
|
|
7
|
+
children?: never;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const Skeleton = ({ className, ...props }: SkeletonProps) => (
|
|
11
|
+
<div data-slot="skeleton" aria-hidden className={cn('cn-skeleton', className)} {...props} />
|
|
12
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.cn-skip-link {
|
|
3
|
+
@apply w-full block underline text-on-focus;
|
|
4
|
+
|
|
5
|
+
text-decoration-thickness: max(1px, 0.0625rem);
|
|
6
|
+
text-underline-offset: 0.1578em;
|
|
7
|
+
|
|
8
|
+
&:not(:focus) {
|
|
9
|
+
@apply sr-only;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
&:focus {
|
|
13
|
+
@apply bg-focus outline-focus p-gds-3;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { SkipLink } from './SkipLink';
|
|
4
|
+
import { Paragraph } from '../Paragraph/Paragraph';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof SkipLink> = {
|
|
7
|
+
title: 'Components/SkipLink',
|
|
8
|
+
component: SkipLink,
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component: 'Use a SkipLink to associate text with form controls and improve accessibility.',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
argTypes: {
|
|
17
|
+
contentId: { control: 'text' },
|
|
18
|
+
},
|
|
19
|
+
args: {
|
|
20
|
+
contentId: 'main-content',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof SkipLink>;
|
|
27
|
+
|
|
28
|
+
export const Example: Story = {
|
|
29
|
+
render: (args) => (
|
|
30
|
+
<div>
|
|
31
|
+
<SkipLink contentId={args.contentId} />
|
|
32
|
+
|
|
33
|
+
<main id="main-content">
|
|
34
|
+
<Paragraph>
|
|
35
|
+
This is the main content. Click inside this example and press tab to see the skip link.
|
|
36
|
+
</Paragraph>
|
|
37
|
+
</main>
|
|
38
|
+
</div>
|
|
39
|
+
),
|
|
40
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils/components';
|
|
4
|
+
|
|
5
|
+
export type SkipLinkProps = Omit<ComponentProps<'a'>, 'href' | 'children' | 'target' | 'ref'> & {
|
|
6
|
+
contentId: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const SkipLink = ({ className, contentId, ...props }: SkipLinkProps) => {
|
|
10
|
+
return (
|
|
11
|
+
<a href={`#${contentId}`} className={cn('cn-skip-link', className)} {...props}>
|
|
12
|
+
Skip to main content
|
|
13
|
+
</a>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* MARK: Spinner
|
|
3
|
+
h-10 w-10 is a reasonable general-purpose default, override via
|
|
4
|
+
className for a specific context (e.g. LoadingBox sizes it larger). */
|
|
5
|
+
.cn-spinner {
|
|
6
|
+
@apply h-10 w-10;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* animation-duration is forced to 0.01ms under prefers-reduced-motion by
|
|
10
|
+
the shared base rule (see @tpzdsp/eslint-config-dsp's utilities.css),
|
|
11
|
+
no extra handling needed here. */
|
|
12
|
+
.cn-spinner-rect {
|
|
13
|
+
animation: cn-spinner-fade 1s linear infinite;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@keyframes cn-spinner-fade {
|
|
17
|
+
0% {
|
|
18
|
+
opacity: 0.250075;
|
|
19
|
+
}
|
|
20
|
+
0.01% {
|
|
21
|
+
opacity: 0.25;
|
|
22
|
+
}
|
|
23
|
+
0.03% {
|
|
24
|
+
opacity: 1;
|
|
25
|
+
}
|
|
26
|
+
100% {
|
|
27
|
+
opacity: 0.250075;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { Spinner } from './Spinner';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Spinner> = {
|
|
6
|
+
title: 'Components/Spinner',
|
|
7
|
+
component: Spinner,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
"GOV.UK-style loading spinner: 12 rects in a circle, each flashing then fading, staggered to sweep round like a clock. Sizes and colours via className, it's currentColor-based. Usually used via LoadingBox rather than directly.",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
|
|
21
|
+
type Story = StoryObj<typeof Spinner>;
|
|
22
|
+
|
|
23
|
+
export const Default: Story = {};
|
|
24
|
+
|
|
25
|
+
export const CustomSize: Story = {
|
|
26
|
+
render: () => <Spinner className="h-20 w-20" />,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const CustomColor: Story = {
|
|
30
|
+
parameters: {
|
|
31
|
+
docs: {
|
|
32
|
+
description: { story: 'fill is currentColor, so it follows the text colour utility.' },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
render: () => <Spinner className="text-accent" />,
|
|
36
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils/components';
|
|
4
|
+
|
|
5
|
+
// 12 rects arranged in a circle, each fading in then slowly back out (see
|
|
6
|
+
// Spinner.css), staggered by delay so the flashes appear to sweep round like
|
|
7
|
+
// a clock. Matches GOV.UK's own loading spinner. Fixed, stable keys (not
|
|
8
|
+
// array index) even though these never reorder, same convention as this
|
|
9
|
+
// library's skeleton components.
|
|
10
|
+
const SPINNER_RECTS = Array.from({ length: 12 }, (_, index) => ({
|
|
11
|
+
key: `rect-${index}`,
|
|
12
|
+
angle: index * 30,
|
|
13
|
+
delayMs: index * 83,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
export type SpinnerProps = Omit<ComponentProps<'svg'>, 'title'> & {
|
|
17
|
+
title?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const Spinner = ({ className, title = 'Loading', ...props }: SpinnerProps) => (
|
|
21
|
+
<svg
|
|
22
|
+
data-slot="spinner"
|
|
23
|
+
viewBox="-25 -25 50 50"
|
|
24
|
+
preserveAspectRatio="xMidYMid meet"
|
|
25
|
+
fill="currentColor"
|
|
26
|
+
className={cn('cn-spinner', className)}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{title ? <title>{title}</title> : null}
|
|
30
|
+
|
|
31
|
+
{SPINNER_RECTS.map(({ key, angle, delayMs }) => (
|
|
32
|
+
<rect
|
|
33
|
+
key={key}
|
|
34
|
+
className="cn-spinner-rect"
|
|
35
|
+
width="12"
|
|
36
|
+
height="5"
|
|
37
|
+
rx="2.5"
|
|
38
|
+
ry="2.5"
|
|
39
|
+
opacity="0"
|
|
40
|
+
transform={`rotate(${angle}, 0, 2) translate(10 0)`}
|
|
41
|
+
style={{ animationDelay: `${delayMs}ms` }}
|
|
42
|
+
/>
|
|
43
|
+
))}
|
|
44
|
+
</svg>
|
|
45
|
+
);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { StartButton, StartButtonContent } from './StartButton';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof StartButton> = {
|
|
6
|
+
title: 'Components/StartButton',
|
|
7
|
+
component: StartButton,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
"Use a start button for the main call to action on your service’s Start page. It renders as a link (an `<a>`), not a `<button>`, matching the GDS start button: it navigates to the first page of a service rather than submitting data or triggering a JS action, so it needs a real `href`. Pass `asChild` to render your own element instead (e.g. Next.js's `Link`, for prefetching and base-path handling), nesting `StartButtonContent` inside it so both modes render identically.",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
argTypes: {
|
|
18
|
+
appearance: {
|
|
19
|
+
control: { type: 'radio' },
|
|
20
|
+
options: ['primary', 'secondary'],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default meta;
|
|
26
|
+
|
|
27
|
+
type Story = StoryObj<typeof StartButton>;
|
|
28
|
+
|
|
29
|
+
export const Primary: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
appearance: 'primary',
|
|
32
|
+
href: '#',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const Secondary: Story = {
|
|
37
|
+
args: {
|
|
38
|
+
appearance: 'secondary',
|
|
39
|
+
href: '#',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const CustomText: Story = {
|
|
44
|
+
args: {
|
|
45
|
+
appearance: 'primary',
|
|
46
|
+
href: '#',
|
|
47
|
+
children: 'Explore data',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const AsChild: Story = {
|
|
52
|
+
parameters: {
|
|
53
|
+
docs: {
|
|
54
|
+
description: {
|
|
55
|
+
story:
|
|
56
|
+
'Use `asChild` to render your own element as the actual link, e.g. Next.js\'s `Link`, so it handles prefetching and the app\'s base path rather than a plain `<a>`. The child stands in for a framework link component here; nest `StartButtonContent` inside it to get the same "Start now" text and chevron.',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
render: () => (
|
|
61
|
+
<StartButton asChild appearance="primary">
|
|
62
|
+
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
|
63
|
+
<a href="#">
|
|
64
|
+
<StartButtonContent />
|
|
65
|
+
</a>
|
|
66
|
+
</StartButton>
|
|
67
|
+
),
|
|
68
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { LuChevronRight } from 'react-icons/lu';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../utils/components';
|
|
6
|
+
import { LinkButton } from '../LinkButton/LinkButton';
|
|
7
|
+
import type { LinkButtonAppearance } from '../LinkButton/LinkButton';
|
|
8
|
+
|
|
9
|
+
export type StartButtonProps = ComponentProps<'a'> & {
|
|
10
|
+
asChild?: boolean;
|
|
11
|
+
appearance: Extract<LinkButtonAppearance, 'primary' | 'secondary'>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type StartButtonContentProps = {
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const StartButton = ({
|
|
19
|
+
appearance,
|
|
20
|
+
asChild = false,
|
|
21
|
+
children,
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
}: StartButtonProps) => {
|
|
25
|
+
return (
|
|
26
|
+
<LinkButton
|
|
27
|
+
asChild={asChild}
|
|
28
|
+
className={cn('cn-start-button', className)}
|
|
29
|
+
appearance={appearance}
|
|
30
|
+
variant="cta"
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
{asChild ? children : <StartButtonContent>{children}</StartButtonContent>}
|
|
34
|
+
</LinkButton>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// The "Start now" text + chevron markup StartButton renders internally. Reuse
|
|
39
|
+
// this inside your own link element when composing StartButton with
|
|
40
|
+
// `asChild` (e.g. wrapping Next.js's `Link`), so both modes render identically:
|
|
41
|
+
// <StartButton asChild appearance="primary"><NextLink href="/start"><StartButtonContent /></NextLink></StartButton>
|
|
42
|
+
export const StartButtonContent = ({ children }: StartButtonContentProps) => {
|
|
43
|
+
return (
|
|
44
|
+
<span className="cn-start-button-content">
|
|
45
|
+
<span className="cn-start-button-text">{children ?? 'Start now'}</span>
|
|
46
|
+
|
|
47
|
+
<LuChevronRight className="cn-start-button-icon" />
|
|
48
|
+
</span>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { SubmitButton } from './SubmitButton';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof SubmitButton> = {
|
|
6
|
+
title: 'Components/SubmitButton',
|
|
7
|
+
component: SubmitButton,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component: 'Use SubmitButton for an action that submits a form.',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
argTypes: {
|
|
17
|
+
children: { control: 'text' },
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
|
|
23
|
+
type Story = StoryObj<typeof SubmitButton>;
|
|
24
|
+
|
|
25
|
+
export const Default: Story = {};
|
|
26
|
+
|
|
27
|
+
export const CustomText: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
children: 'Click to submit',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils/components';
|
|
4
|
+
import { Button } from '../Button/Button';
|
|
5
|
+
|
|
6
|
+
export type SubmitButtonProps = Omit<ComponentProps<typeof Button>, 'type'>;
|
|
7
|
+
|
|
8
|
+
export const SubmitButton = ({ className, children, ...props }: SubmitButtonProps) => {
|
|
9
|
+
return (
|
|
10
|
+
<Button {...props} type="submit" className={cn(className, 'cn-submit-button')}>
|
|
11
|
+
{children ?? 'Submit'}
|
|
12
|
+
</Button>
|
|
13
|
+
);
|
|
14
|
+
};
|