@tpzdsp/next-toolkit 2.5.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +21 -12
- package/src/assets/styles/globals.css +75 -95
- package/src/assets/styles/ol.css +57 -16
- package/src/assets/styles/utils.css +80 -0
- package/src/components/Accordion/Accordion.css +85 -0
- package/src/components/Accordion/Accordion.stories.tsx +264 -0
- package/src/components/Accordion/Accordion.tsx +98 -0
- package/src/components/Alert/Alert.css +129 -0
- package/src/components/Alert/Alert.stories.tsx +291 -0
- package/src/components/Alert/Alert.tsx +76 -0
- package/src/components/BackLink/BackLink.css +12 -0
- package/src/components/BackLink/BackLink.stories.tsx +52 -0
- package/src/components/BackLink/BackLink.tsx +44 -0
- package/src/components/BackToTop/BackToTop.css +23 -0
- package/src/components/BackToTop/BackToTop.stories.tsx +41 -0
- package/src/components/BackToTop/BackToTop.tsx +48 -0
- package/src/components/Breadcrumb/Breadcrumb.css +42 -0
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +94 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +135 -0
- package/src/components/Button/Button.css +229 -0
- package/src/components/Button/Button.stories.tsx +140 -20
- package/src/components/Button/Button.tsx +53 -28
- package/src/components/Button/ButtonSkeleton.stories.tsx +50 -0
- package/src/components/Button/ButtonSkeleton.tsx +14 -0
- package/src/components/ButtonGroup/ButtonGroup.css +52 -0
- package/src/components/ButtonGroup/ButtonGroup.stories.tsx +156 -0
- package/src/components/ButtonGroup/ButtonGroup.tsx +50 -0
- package/src/components/ButtonLink/ButtonLink.css +114 -0
- package/src/components/ButtonLink/ButtonLink.stories.tsx +38 -51
- package/src/components/ButtonLink/ButtonLink.tsx +54 -23
- package/src/components/Card/Card.css +13 -0
- package/src/components/Card/Card.stories.tsx +98 -57
- package/src/components/Card/Card.tsx +4 -13
- package/src/components/CardGroup/CardGroup.css +10 -0
- package/src/components/CardGroup/CardGroup.stories.tsx +81 -0
- package/src/components/CardGroup/CardGroup.tsx +9 -0
- package/src/components/Checkbox/Checkbox.css +49 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +86 -0
- package/src/components/Checkbox/Checkbox.tsx +32 -0
- package/src/components/Chip/Chip.css +54 -0
- package/src/components/Chip/Chip.stories.tsx +94 -0
- package/src/components/Chip/Chip.tsx +34 -0
- package/src/components/CookieBanner/CookieBanner.css +19 -0
- package/src/components/CookieBanner/CookieBanner.stories.tsx +52 -0
- package/src/components/CookieBanner/CookieBanner.tsx +55 -0
- package/src/components/CopyButton/CopyButton.stories.tsx +28 -0
- package/src/components/CopyButton/CopyButton.tsx +65 -0
- package/src/components/DateInput/DateInput.css +13 -0
- package/src/components/DateInput/DateInput.stories.tsx +131 -0
- package/src/components/DateInput/DateInput.test.tsx +21 -0
- package/src/components/DateInput/DateInput.tsx +98 -0
- package/src/components/Details/Details.css +28 -0
- package/src/components/Details/Details.stories.tsx +48 -0
- package/src/components/Details/Details.tsx +41 -0
- package/src/components/Dropdown/Dropdown.css +81 -0
- package/src/components/Dropdown/Dropdown.stories.tsx +131 -0
- package/src/components/Dropdown/Dropdown.tsx +69 -0
- package/src/components/ErrorBoundary/ErrorBoundary.stories.tsx +4 -39
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +7 -4
- package/src/components/ErrorBoundary/ErrorFallback.tsx +32 -66
- package/src/components/ErrorModal/ErrorModal.css +34 -0
- package/src/components/ErrorModal/ErrorModal.stories.tsx +100 -0
- package/src/components/ErrorModal/ErrorModal.tsx +123 -0
- package/src/components/ErrorText/ErrorText.css +22 -0
- package/src/components/ErrorText/ErrorText.stories.tsx +42 -27
- package/src/components/ErrorText/ErrorText.tsx +10 -8
- package/src/components/ExternalLink/ExternalLink.css +12 -0
- package/src/components/ExternalLink/ExternalLink.stories.tsx +43 -0
- package/src/components/ExternalLink/ExternalLink.tsx +43 -0
- package/src/components/Fieldset/Fieldset.css +103 -0
- package/src/components/Fieldset/Fieldset.stories.tsx +304 -0
- package/src/components/Fieldset/Fieldset.tsx +38 -0
- package/src/components/FileUpload/FileUpload.css +85 -0
- package/src/components/FileUpload/FileUpload.stories.tsx +106 -0
- package/src/components/FileUpload/FileUpload.tsx +99 -0
- package/src/components/{layout/footer → Footer}/Copyright.tsx +3 -2
- package/src/components/Footer/Footer.stories.tsx +27 -0
- package/src/components/Footer/Footer.tsx +46 -0
- package/src/components/Footer/Licence.tsx +18 -0
- package/src/components/Footer/MetaLinks.tsx +47 -0
- package/src/components/Footer/Small/SmallFooter.stories.tsx +3 -0
- package/src/components/Footer/Small/SmallFooter.tsx +53 -41
- package/src/components/Form/FormGroup.css +18 -0
- package/src/components/Form/FormGroup.stories.tsx +87 -0
- package/src/components/Form/FormGroup.tsx +16 -0
- package/src/components/{layout/header → Header}/Header.stories.tsx +26 -12
- package/src/components/Header/Header.test.tsx +87 -0
- package/src/components/Header/Header.tsx +304 -0
- package/src/components/Header/HeaderAppContent.tsx +120 -0
- package/src/components/Heading/Heading.css +40 -0
- package/src/components/Heading/Heading.stories.tsx +15 -5
- package/src/components/Heading/Heading.tsx +21 -44
- package/src/components/Heading/HeadingSkeleton.stories.tsx +30 -0
- package/src/components/Heading/HeadingSkeleton.tsx +14 -0
- package/src/components/Hero/Hero.css +19 -0
- package/src/components/Hero/Hero.stories.tsx +41 -0
- package/src/components/Hero/Hero.tsx +14 -0
- package/src/components/HintText/HintText.css +5 -0
- package/src/components/HintText/HintText.stories.tsx +29 -0
- package/src/components/HintText/HintText.tsx +13 -0
- package/src/components/InfoBox/InfoBox.css +38 -0
- package/src/components/InfoBox/InfoBox.stories.tsx +51 -403
- package/src/components/InfoBox/InfoBox.tsx +31 -158
- package/src/components/Input/Input.css +77 -0
- package/src/components/Input/Input.stories.tsx +123 -0
- package/src/components/Input/Input.tsx +74 -0
- package/src/components/InputGroup/InputGroup.css +140 -0
- package/src/components/InputGroup/InputGroup.stories.tsx +125 -0
- package/src/components/InputGroup/InputGroup.tsx +47 -0
- package/src/components/InsetText/InsetText.css +5 -0
- package/src/components/InsetText/InsetText.stories.tsx +30 -0
- package/src/components/InsetText/InsetText.tsx +9 -0
- package/src/components/Keyboard/Keyboard.css +9 -0
- package/src/components/Keyboard/Keyboard.stories.tsx +63 -0
- package/src/components/Keyboard/Keyboard.tsx +30 -0
- package/src/components/Label/Label.css +27 -0
- package/src/components/Label/Label.stories.tsx +40 -0
- package/src/components/Label/Label.tsx +17 -0
- package/src/components/LayerSwitcher/LayerSwitcher.css +59 -0
- package/src/components/LayerSwitcher/LayerSwitcher.stories.tsx +112 -0
- package/src/components/LayerSwitcher/LayerSwitcher.tsx +61 -0
- package/src/components/Layout/401/401.stories.tsx +23 -0
- package/src/components/Layout/401/401.tsx +1 -0
- package/src/components/Layout/403/403.stories.tsx +23 -0
- package/src/components/Layout/403/403.tsx +21 -0
- package/src/components/Layout/404/404.stories.tsx +23 -0
- package/src/components/Layout/404/404.tsx +28 -0
- package/src/components/Layout/500/500.stories.tsx +56 -0
- package/src/components/Layout/500/500.tsx +63 -0
- package/src/components/Layout/ButtonGroup/ButtonGroup.css +52 -0
- package/src/components/Layout/ButtonGroup/ButtonGroup.stories.tsx +156 -0
- package/src/components/Layout/ButtonGroup/ButtonGroup.tsx +50 -0
- package/src/components/Layout/HeadingGroup/HeadingGroup.css +21 -0
- package/src/components/Layout/HeadingGroup/HeadingGroup.stories.tsx +84 -0
- package/src/components/Layout/HeadingGroup/HeadingGroup.tsx +9 -0
- package/src/components/Layout/MainWrapper/MainWrapper.css +12 -0
- package/src/components/Layout/MainWrapper/MainWrapper.stories.tsx +36 -0
- package/src/components/Layout/MainWrapper/MainWrapper.tsx +14 -0
- package/src/components/Layout/PageShell/PageShell.css +42 -0
- package/src/components/Layout/PageShell/PageShell.stories.tsx +108 -0
- package/src/components/Layout/PageShell/PageShell.tsx +31 -0
- package/src/components/Layout/ParagraphGroup/ParagraphGroup.css +5 -0
- package/src/components/Layout/ParagraphGroup/ParagraphGroup.stories.tsx +34 -0
- package/src/components/Layout/ParagraphGroup/ParagraphGroup.tsx +9 -0
- package/src/components/Layout/Stack/Stack.css +29 -0
- package/src/components/Layout/Stack/Stack.stories.tsx +83 -0
- package/src/components/Layout/Stack/Stack.tsx +19 -0
- package/src/components/Layout/WidthContainer/WidthContainer.css +17 -0
- package/src/components/Layout/WidthContainer/WidthContainer.stories.tsx +59 -0
- package/src/components/Layout/WidthContainer/WidthContainer.tsx +13 -0
- package/src/components/Legend/Legend.css +95 -0
- package/src/components/Legend/Legend.stories.tsx +81 -0
- package/src/components/Legend/Legend.tsx +32 -0
- package/src/components/Legend/LegendDot.tsx +15 -0
- package/src/components/Legend/LegendPanel.stories.tsx +101 -0
- package/src/components/Legend/LegendPanel.tsx +68 -0
- package/src/components/Legend/LegendSkeleton.stories.tsx +87 -0
- package/src/components/Legend/LegendSkeleton.tsx +73 -0
- package/src/components/LegendSelect/LegendSelect.css +8 -0
- package/src/components/LegendSelect/LegendSelect.stories.tsx +80 -0
- package/src/components/LegendSelect/LegendSelect.tsx +26 -0
- package/src/components/Link/Link.css +98 -0
- package/src/components/Link/Link.stories.tsx +70 -0
- package/src/components/Link/Link.tsx +52 -0
- package/src/components/LinkButton/LinkButton.css +67 -0
- package/src/components/LinkButton/LinkButton.stories.tsx +90 -42
- package/src/components/LinkButton/LinkButton.tsx +50 -63
- package/src/components/LinkList/LinkList.css +28 -0
- package/src/components/LinkList/LinkList.stories.tsx +46 -0
- package/src/components/LinkList/LinkList.tsx +23 -0
- package/src/components/List/Li.tsx +9 -0
- package/src/components/List/List.css +9 -0
- package/src/components/List/OrderedList.stories.tsx +36 -0
- package/src/components/List/OrderedList.tsx +9 -0
- package/src/components/List/UnorderedList.stories.tsx +36 -0
- package/src/components/List/UnorderedList.tsx +9 -0
- package/src/components/LoadingBox/LoadingBox.css +27 -0
- package/src/components/LoadingBox/LoadingBox.stories.tsx +66 -0
- package/src/components/LoadingBox/LoadingBox.tsx +40 -0
- package/src/components/Modal/Modal.css +111 -0
- package/src/components/Modal/Modal.stories.tsx +94 -155
- package/src/components/Modal/Modal.tsx +141 -75
- package/src/components/NoScriptBanner/NoScriptBanner.css +17 -0
- package/src/components/NoScriptBanner/NoScriptBanner.stories.tsx +46 -0
- package/src/components/NoScriptBanner/NoScriptBanner.tsx +26 -0
- package/src/components/Paragraph/Paragraph.css +22 -0
- package/src/components/Paragraph/Paragraph.stories.tsx +1 -42
- package/src/components/Paragraph/Paragraph.tsx +4 -3
- package/src/components/Paragraph/ParagraphSkeleton.stories.tsx +44 -0
- package/src/components/Paragraph/ParagraphSkeleton.tsx +33 -0
- package/src/components/PasswordInput/PasswordInput.css +3 -0
- package/src/components/PasswordInput/PasswordInput.stories.tsx +47 -0
- package/src/components/PasswordInput/PasswordInput.tsx +45 -0
- package/src/components/PhaseBanner/PhaseBanner.css +12 -0
- package/src/components/PhaseBanner/PhaseBanner.stories.tsx +61 -0
- package/src/components/PhaseBanner/PhaseBanner.tsx +17 -0
- package/src/components/Popover/Popover.css +43 -0
- package/src/components/Popover/Popover.stories.tsx +102 -0
- package/src/components/Popover/Popover.tsx +40 -0
- package/src/components/Radio/Radio.css +69 -0
- package/src/components/Radio/Radio.stories.tsx +169 -0
- package/src/components/Radio/Radio.tsx +56 -0
- package/src/components/Resizable/Resizable.css +86 -0
- package/src/components/Resizable/Resizable.stories.tsx +123 -0
- package/src/components/Resizable/Resizable.tsx +86 -0
- package/src/components/ScrollArea/ScrollArea.css +9 -0
- package/src/components/ScrollArea/ScrollArea.stories.tsx +42 -0
- package/src/components/ScrollArea/ScrollArea.tsx +11 -0
- package/src/components/SearchBox/SearchBox.stories.tsx +88 -0
- package/src/components/SearchBox/SearchBox.tsx +36 -0
- package/src/components/Select/AsyncSelect.stories.tsx +84 -0
- package/src/components/Select/AsyncSelect.tsx +41 -0
- package/src/components/Select/Select.css +202 -0
- package/src/components/Select/Select.stories.tsx +162 -0
- package/src/components/Select/Select.test.tsx +36 -0
- package/src/components/Select/Select.tsx +296 -0
- package/src/components/Select/SelectSkeleton.stories.tsx +51 -0
- package/src/components/Select/SelectSkeleton.tsx +23 -0
- package/src/components/Separator/Separator.css +41 -0
- package/src/components/Separator/Separator.stories.tsx +63 -0
- package/src/components/Separator/Separator.tsx +42 -0
- package/src/components/Sidebar/ResizableSidebar.tsx +118 -0
- package/src/components/Sidebar/Sidebar.css +203 -0
- package/src/components/Sidebar/Sidebar.stories.tsx +438 -0
- package/src/components/Sidebar/Sidebar.tsx +225 -0
- package/src/components/Skeleton/Skeleton.css +16 -0
- package/src/components/Skeleton/Skeleton.stories.tsx +39 -0
- package/src/components/Skeleton/Skeleton.tsx +12 -0
- package/src/components/SkipLink/SkipLink.css +16 -0
- package/src/components/SkipLink/SkipLink.stories.tsx +40 -0
- package/src/components/SkipLink/SkipLink.tsx +15 -0
- package/src/components/Spinner/Spinner.css +30 -0
- package/src/components/Spinner/Spinner.stories.tsx +36 -0
- package/src/components/Spinner/Spinner.tsx +45 -0
- package/src/components/StartButton/StartButton.css +13 -0
- package/src/components/StartButton/StartButton.stories.tsx +68 -0
- package/src/components/StartButton/StartButton.tsx +50 -0
- package/src/components/SubmitButton/SubmitButton.stories.tsx +31 -0
- package/src/components/SubmitButton/SubmitButton.tsx +14 -0
- package/src/components/SummaryList/SummaryList.css +71 -0
- package/src/components/SummaryList/SummaryList.stories.tsx +211 -0
- package/src/components/SummaryList/SummaryList.tsx +41 -0
- package/src/components/SummaryList/SummaryListSkeleton.stories.tsx +67 -0
- package/src/components/SummaryList/SummaryListSkeleton.tsx +40 -0
- package/src/components/Tabs/Tabs.css +84 -0
- package/src/components/Tabs/Tabs.stories.tsx +133 -0
- package/src/components/Tabs/Tabs.tsx +66 -0
- package/src/components/Tag/Tag.css +8 -0
- package/src/components/Tag/Tag.stories.tsx +96 -0
- package/src/components/Tag/Tag.tsx +34 -0
- package/src/components/Textarea/Textarea.css +38 -0
- package/src/components/Textarea/Textarea.stories.tsx +131 -0
- package/src/components/Textarea/Textarea.tsx +50 -0
- package/src/components/TextareaCount/TextareaCount.css +17 -0
- package/src/components/TextareaCount/TextareaCount.stories.tsx +131 -0
- package/src/components/TextareaCount/TextareaCount.tsx +108 -0
- package/src/components/Tooltip/Tooltip.css +43 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +110 -0
- package/src/components/Tooltip/Tooltip.tsx +53 -0
- package/src/components/WarningText/WarningText.css +20 -0
- package/src/components/WarningText/WarningText.stories.tsx +29 -0
- package/src/components/WarningText/WarningText.tsx +17 -0
- package/src/components/index.ts +343 -61
- package/src/errors/errorDisplay.ts +70 -0
- package/src/errors/index.ts +1 -0
- package/src/http/constants.ts +2 -0
- package/src/http/fetch.ts +7 -3
- package/src/map/LayerSwitcherPanel.tsx +2 -173
- package/src/map/LegendShim.tsx +2 -0
- package/src/map/MapComponent.tsx +7 -17
- package/src/map/MapControlsOverlay.tsx +68 -175
- package/src/map/Popup.tsx +71 -24
- package/src/map/geocoder/Geocoder.test.tsx +46 -3
- package/src/map/geocoder/Geocoder.tsx +155 -278
- package/src/map/index.ts +1 -1
- package/src/types/utils.ts +0 -10
- package/src/utils/components.ts +96 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/utils.ts +0 -12
- package/src/assets/images/defra-logo.svg +0 -51
- package/src/assets/images/ea-logo.svg +0 -58
- package/src/assets/images/ogl.svg +0 -1
- package/src/components/Button/Button.test.tsx +0 -53
- package/src/components/ButtonLink/ButtonLink.test.tsx +0 -154
- package/src/components/Card/Card.test.tsx +0 -51
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +0 -75
- package/src/components/ErrorBoundary/ErrorFallback.test.tsx +0 -107
- package/src/components/ErrorText/ErrorText.test.tsx +0 -33
- package/src/components/Heading/Heading.test.tsx +0 -22
- package/src/components/Hint/Hint.stories.tsx +0 -58
- package/src/components/Hint/Hint.test.tsx +0 -33
- package/src/components/Hint/Hint.tsx +0 -12
- package/src/components/InfoBox/InfoBox.test.tsx +0 -330
- package/src/components/InfoBox/types.ts +0 -6
- package/src/components/LinkButton/LinkButton.test.tsx +0 -177
- package/src/components/Modal/Modal.test.tsx +0 -246
- package/src/components/NotificationBanner/NotificationBanner.stories.tsx +0 -45
- package/src/components/NotificationBanner/NotificationBanner.test.tsx +0 -58
- package/src/components/NotificationBanner/NotificationBanner.tsx +0 -45
- package/src/components/Paragraph/Paragraph.test.tsx +0 -10
- package/src/components/SlidingPanel/SlidingPanel.stories.tsx +0 -129
- package/src/components/SlidingPanel/SlidingPanel.test.tsx +0 -83
- package/src/components/SlidingPanel/SlidingPanel.tsx +0 -258
- package/src/components/accordion/Accordion.stories.tsx +0 -234
- package/src/components/accordion/Accordion.test.tsx +0 -192
- package/src/components/accordion/Accordion.tsx +0 -73
- package/src/components/backToTop/BackToTop.stories.tsx +0 -407
- package/src/components/backToTop/BackToTop.test.tsx +0 -57
- package/src/components/backToTop/BackToTop.tsx +0 -134
- package/src/components/chip/Chip.stories.tsx +0 -61
- package/src/components/chip/Chip.test.tsx +0 -31
- package/src/components/chip/Chip.tsx +0 -23
- package/src/components/container/Container.tsx +0 -42
- package/src/components/cookieBanner/CookieBanner.stories.tsx +0 -257
- package/src/components/cookieBanner/CookieBanner.test.tsx +0 -68
- package/src/components/cookieBanner/CookieBanner.tsx +0 -73
- package/src/components/divider/RuleDivider.stories.tsx +0 -254
- package/src/components/divider/RuleDivider.test.tsx +0 -164
- package/src/components/divider/RuleDivider.tsx +0 -23
- package/src/components/dropdown/DropdownMenu.test.tsx +0 -211
- package/src/components/dropdown/DropdownMenu.tsx +0 -108
- package/src/components/dropdown/useDropdownMenu.ts +0 -249
- package/src/components/form/Input.stories.tsx +0 -435
- package/src/components/form/Input.test.tsx +0 -206
- package/src/components/form/Input.tsx +0 -23
- package/src/components/form/TextArea.stories.tsx +0 -464
- package/src/components/form/TextArea.test.tsx +0 -232
- package/src/components/form/TextArea.tsx +0 -23
- package/src/components/layout/footer/Footer.tsx +0 -26
- package/src/components/layout/footer/Licence.tsx +0 -19
- package/src/components/layout/footer/MetaLinks.tsx +0 -36
- package/src/components/layout/header/Header.test.tsx +0 -36
- package/src/components/layout/header/Header.tsx +0 -90
- package/src/components/layout/header/HeaderAuthClient.test.tsx +0 -45
- package/src/components/layout/header/HeaderAuthClient.tsx +0 -40
- package/src/components/layout/header/HeaderNavClient.test.tsx +0 -44
- package/src/components/layout/header/HeaderNavClient.tsx +0 -67
- package/src/components/link/ExternalLink.test.tsx +0 -102
- package/src/components/link/ExternalLink.tsx +0 -29
- package/src/components/link/Link.tsx +0 -26
- package/src/components/select/Select.stories.tsx +0 -336
- package/src/components/select/Select.test.tsx +0 -474
- package/src/components/select/Select.tsx +0 -125
- package/src/components/select/SelectSkeleton.stories.tsx +0 -194
- package/src/components/select/SelectSkeleton.test.tsx +0 -104
- package/src/components/select/SelectSkeleton.tsx +0 -23
- package/src/components/select/common.ts +0 -3
- package/src/components/select/index.ts +0 -18
- package/src/components/skipLink/SkipLink.stories.tsx +0 -346
- package/src/components/skipLink/SkipLink.test.tsx +0 -22
- package/src/components/skipLink/SkipLink.tsx +0 -49
- package/src/map/LegendPanel.tsx +0 -85
- /package/src/{components → assets}/images/DefraLogo.tsx +0 -0
- /package/src/{components → assets}/images/EaLogo.tsx +0 -0
- /package/src/{components → assets}/images/OglLogo.tsx +0 -0
- /package/src/components/{googleAnalytics → GoogleAnalytics}/GlobalVars.tsx +0 -0
- /package/src/components/{googleAnalytics → GoogleAnalytics}/GoogleAnalytics.tsx +0 -0
|
@@ -1,173 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
import { FocusTrap } from 'focus-trap-react';
|
|
6
|
-
import BaseLayer from 'ol/layer/Base';
|
|
7
|
-
import { createPortal } from 'react-dom';
|
|
8
|
-
|
|
9
|
-
import { cn } from '../utils';
|
|
10
|
-
|
|
11
|
-
type LayerSwitcherPanelProps = {
|
|
12
|
-
isOpen: boolean;
|
|
13
|
-
onClose: () => void;
|
|
14
|
-
layers: BaseLayer[];
|
|
15
|
-
activeLayerName: string | null;
|
|
16
|
-
onSelectLayer: (layerName: string) => void;
|
|
17
|
-
buttonRect: DOMRect | null;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export const LayerSwitcherPanel = ({
|
|
21
|
-
isOpen,
|
|
22
|
-
onClose,
|
|
23
|
-
layers,
|
|
24
|
-
activeLayerName,
|
|
25
|
-
onSelectLayer,
|
|
26
|
-
buttonRect,
|
|
27
|
-
}: LayerSwitcherPanelProps) => {
|
|
28
|
-
const [isMounted, setIsMounted] = useState(false);
|
|
29
|
-
const [isTrapActive, setIsTrapActive] = useState(false);
|
|
30
|
-
const panelRef = useRef<HTMLDivElement>(null);
|
|
31
|
-
const closeButtonRef = useRef<HTMLButtonElement>(null);
|
|
32
|
-
|
|
33
|
-
const handleClose = () => {
|
|
34
|
-
setIsTrapActive(false);
|
|
35
|
-
onClose();
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
setIsMounted(true);
|
|
40
|
-
}, []);
|
|
41
|
-
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
if (isOpen) {
|
|
44
|
-
setIsTrapActive(true);
|
|
45
|
-
} else {
|
|
46
|
-
setIsTrapActive(false);
|
|
47
|
-
}
|
|
48
|
-
}, [isOpen]);
|
|
49
|
-
|
|
50
|
-
if (!isMounted || !isOpen) {
|
|
51
|
-
return <></>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Calculate position based on button position
|
|
55
|
-
const top = buttonRect ? buttonRect.bottom + 8 : 118;
|
|
56
|
-
const right = 48; // 3rem = 48px
|
|
57
|
-
|
|
58
|
-
// Calculate max-height to prevent panel from going off-screen
|
|
59
|
-
// Leave 16px padding at the bottom
|
|
60
|
-
const maxHeight = buttonRect
|
|
61
|
-
? `${window.innerHeight - buttonRect.bottom - 24}px`
|
|
62
|
-
: 'calc(100vh - 150px)';
|
|
63
|
-
|
|
64
|
-
const panelClasses = cn(
|
|
65
|
-
'fixed bg-white rounded-lg shadow-lg border border-gray-200',
|
|
66
|
-
'flex flex-col',
|
|
67
|
-
'p-2 gap-2',
|
|
68
|
-
'w-[180px] max-w-[250px]',
|
|
69
|
-
'transition-all duration-300 ease-in-out',
|
|
70
|
-
'z-[9999]',
|
|
71
|
-
isOpen ? 'opacity-100 translate-x-0' : 'opacity-0 translate-x-full pointer-events-none',
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
const contentClasses = cn(
|
|
75
|
-
'grid grid-cols-1 gap-2',
|
|
76
|
-
'flex-1 min-h-0',
|
|
77
|
-
'overflow-y-auto overflow-x-hidden',
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
return createPortal(
|
|
81
|
-
<FocusTrap
|
|
82
|
-
active={isTrapActive}
|
|
83
|
-
focusTrapOptions={{
|
|
84
|
-
clickOutsideDeactivates: true,
|
|
85
|
-
escapeDeactivates: true,
|
|
86
|
-
onDeactivate: handleClose,
|
|
87
|
-
returnFocusOnDeactivate: true,
|
|
88
|
-
initialFocus: false, // Don't auto-focus close button
|
|
89
|
-
fallbackFocus: () => panelRef.current ?? document.body,
|
|
90
|
-
}}
|
|
91
|
-
>
|
|
92
|
-
{/* We use role="dialog" instead of <dialog> tag for better portal positioning control */}
|
|
93
|
-
<div
|
|
94
|
-
ref={panelRef}
|
|
95
|
-
role="dialog"
|
|
96
|
-
aria-modal="true"
|
|
97
|
-
aria-label="Basemap switcher"
|
|
98
|
-
aria-describedby="layer-switcher-description"
|
|
99
|
-
style={{ top, right, maxHeight }}
|
|
100
|
-
className={panelClasses}
|
|
101
|
-
>
|
|
102
|
-
{/* Screen reader description */}
|
|
103
|
-
<div id="layer-switcher-description" className="sr-only">
|
|
104
|
-
Select a basemap layer. Use arrow keys to navigate between options, Enter or Space to
|
|
105
|
-
select. Press Escape to close.
|
|
106
|
-
</div>
|
|
107
|
-
{/* Header with close button */}
|
|
108
|
-
<div className="flex justify-end flex-shrink-0">
|
|
109
|
-
<button
|
|
110
|
-
ref={closeButtonRef}
|
|
111
|
-
type="button"
|
|
112
|
-
onClick={handleClose}
|
|
113
|
-
aria-label="Close basemap switcher"
|
|
114
|
-
tabIndex={0}
|
|
115
|
-
className="w-8 h-8 flex items-center justify-center rounded text-gray-600 hover:bg-focus
|
|
116
|
-
focus:outline focus:outline-2 focus:outline-focus focus:bg-focus transition-colors"
|
|
117
|
-
>
|
|
118
|
-
<span className="text-xl leading-none" aria-hidden="true">
|
|
119
|
-
×
|
|
120
|
-
</span>
|
|
121
|
-
</button>
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
{/* Content with basemap buttons */}
|
|
125
|
-
<div className={contentClasses} role="radiogroup" aria-label="Basemap options">
|
|
126
|
-
{layers.map((layer) => {
|
|
127
|
-
const name = layer.get('name') as string;
|
|
128
|
-
const image = layer.get('image') as string;
|
|
129
|
-
const isActive = name === activeLayerName;
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<button
|
|
133
|
-
key={name}
|
|
134
|
-
type="button"
|
|
135
|
-
role="radio"
|
|
136
|
-
aria-checked={isActive}
|
|
137
|
-
aria-label={`${name} basemap${isActive ? ', currently selected' : ''}`}
|
|
138
|
-
onClick={() => onSelectLayer(name)}
|
|
139
|
-
className={cn(
|
|
140
|
-
'flex flex-col items-center justify-start gap-2',
|
|
141
|
-
'w-full p-2',
|
|
142
|
-
'border-2 rounded',
|
|
143
|
-
'transition-all duration-200',
|
|
144
|
-
'focus:outline focus:outline-2 focus:outline-focus',
|
|
145
|
-
isActive
|
|
146
|
-
? 'bg-focus text-black border-focus'
|
|
147
|
-
: 'bg-white text-black border-gray-300 hover:bg-focus',
|
|
148
|
-
)}
|
|
149
|
-
>
|
|
150
|
-
<div className="flex items-center justify-center w-full flex-shrink-0">
|
|
151
|
-
<img
|
|
152
|
-
src={image}
|
|
153
|
-
alt={`Preview of ${name}`}
|
|
154
|
-
title={name}
|
|
155
|
-
className={cn(
|
|
156
|
-
'max-w-[60px] max-h-[60px] w-auto h-auto object-contain border',
|
|
157
|
-
isActive ? 'border-black' : 'grayscale border-black',
|
|
158
|
-
)}
|
|
159
|
-
/>
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<div className="text-xs leading-tight text-center w-full flex-shrink-0 px-1">
|
|
163
|
-
{name}
|
|
164
|
-
</div>
|
|
165
|
-
</button>
|
|
166
|
-
);
|
|
167
|
-
})}
|
|
168
|
-
</div>
|
|
169
|
-
</div>
|
|
170
|
-
</FocusTrap>,
|
|
171
|
-
document.body,
|
|
172
|
-
);
|
|
173
|
-
};
|
|
1
|
+
export { LayerSwitcher } from '../components/LayerSwitcher/LayerSwitcher';
|
|
2
|
+
export type { LayerSwitcherProps, LayerOption } from '../components/LayerSwitcher/LayerSwitcher';
|
package/src/map/MapComponent.tsx
CHANGED
|
@@ -13,11 +13,13 @@ import { getPopupPositionClass, LAYER_NAMES } from './utils';
|
|
|
13
13
|
import type { PopupDirection } from '../types/map';
|
|
14
14
|
|
|
15
15
|
export type MapComponentProps = {
|
|
16
|
-
osMapsApiKey?: string;
|
|
17
16
|
basePath: string;
|
|
18
17
|
children?: ReactNode;
|
|
19
18
|
};
|
|
20
19
|
|
|
20
|
+
// Where the popup-container div itself sits relative to the clicked
|
|
21
|
+
// coordinate. Popup owns the matching arrow-position lookup for its own
|
|
22
|
+
// pointer, since that's internal to how it renders, not where it's placed.
|
|
21
23
|
const positionTransforms: Record<PopupDirection, string> = {
|
|
22
24
|
'bottom-left': 'top-[-2em] right-[1em]',
|
|
23
25
|
'top-left': 'bottom-[-2em] right-[1em]',
|
|
@@ -25,13 +27,6 @@ const positionTransforms: Record<PopupDirection, string> = {
|
|
|
25
27
|
'bottom-right': 'top-[-2em] left-[1em]',
|
|
26
28
|
};
|
|
27
29
|
|
|
28
|
-
const arrowStyles: Record<PopupDirection, string> = {
|
|
29
|
-
'bottom-left': 'right-[-.5em] top-[1em]',
|
|
30
|
-
'bottom-right': 'left-[-.5em] top-[1em]',
|
|
31
|
-
'top-left': 'right-[-.5em] bottom-[1em]',
|
|
32
|
-
'top-right': 'left-[-.5em] bottom-[1em]',
|
|
33
|
-
};
|
|
34
|
-
|
|
35
30
|
/**
|
|
36
31
|
* This component manages the main map, this is where the basemap layers
|
|
37
32
|
* and basic controls are added to the map. The map component encapsulates
|
|
@@ -42,7 +37,7 @@ const arrowStyles: Record<PopupDirection, string> = {
|
|
|
42
37
|
*
|
|
43
38
|
* @return {*}
|
|
44
39
|
*/
|
|
45
|
-
const MapComponentBase = ({
|
|
40
|
+
const MapComponentBase = ({ basePath, children }: MapComponentProps) => {
|
|
46
41
|
const [popupCoordinate, setPopupCoordinate] = useState<number[] | null>(null);
|
|
47
42
|
const [popupPositionClass, setPopupPositionClass] = useState<PopupDirection>('bottom-right');
|
|
48
43
|
|
|
@@ -65,11 +60,6 @@ const MapComponentBase = ({ osMapsApiKey, basePath, children }: MapComponentProp
|
|
|
65
60
|
return;
|
|
66
61
|
}
|
|
67
62
|
|
|
68
|
-
if (!osMapsApiKey) {
|
|
69
|
-
console.warn('OS Maps API key not provided. Geocoder will not be available.');
|
|
70
|
-
// Continue with map initialization but skip geocoder
|
|
71
|
-
}
|
|
72
|
-
|
|
73
63
|
// Initialise map's basemap layers.
|
|
74
64
|
const layers = initializeBasemapLayers(basePath);
|
|
75
65
|
|
|
@@ -160,10 +150,10 @@ const MapComponentBase = ({ osMapsApiKey, basePath, children }: MapComponentProp
|
|
|
160
150
|
};
|
|
161
151
|
|
|
162
152
|
return (
|
|
163
|
-
<div className="flex
|
|
153
|
+
<div className="flex grow min-h-0">
|
|
164
154
|
<div
|
|
165
155
|
ref={mapRef}
|
|
166
|
-
className="flex
|
|
156
|
+
className="flex grow relative z-10"
|
|
167
157
|
id="map"
|
|
168
158
|
role="application"
|
|
169
159
|
aria-label="Map"
|
|
@@ -178,7 +168,7 @@ const MapComponentBase = ({ osMapsApiKey, basePath, children }: MapComponentProp
|
|
|
178
168
|
<Popup
|
|
179
169
|
onClose={closePopup}
|
|
180
170
|
clickedCoord={popupCoordinate}
|
|
181
|
-
|
|
171
|
+
direction={popupPositionClass}
|
|
182
172
|
>
|
|
183
173
|
{children}
|
|
184
174
|
</Popup>
|
|
@@ -1,97 +1,52 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
useCallback,
|
|
5
|
-
useEffect,
|
|
6
|
-
useMemo,
|
|
7
|
-
useRef,
|
|
8
|
-
useState,
|
|
9
|
-
type ReactNode,
|
|
10
|
-
type RefObject,
|
|
11
|
-
} from 'react';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState, type RefObject } from 'react';
|
|
12
4
|
|
|
13
5
|
import BaseLayer from 'ol/layer/Base';
|
|
14
|
-
import {
|
|
6
|
+
import { LuMaximize2, LuMinimize2, LuMinus, LuPlus } from 'react-icons/lu';
|
|
15
7
|
|
|
16
|
-
import { LayerSwitcherPanel } from './LayerSwitcherPanel';
|
|
17
|
-
import { LegendPanel } from './LegendPanel';
|
|
18
8
|
import { useMap } from './MapContext';
|
|
19
|
-
|
|
20
|
-
type
|
|
9
|
+
import { Button } from '../components/Button/Button';
|
|
10
|
+
import type { LayerOption } from '../components/LayerSwitcher/LayerSwitcher';
|
|
11
|
+
import { LayerSwitcher } from '../components/LayerSwitcher/LayerSwitcher';
|
|
12
|
+
import { ButtonGroup } from '../components/Layout/ButtonGroup/ButtonGroup';
|
|
13
|
+
import type { LegendOption } from '../components/Legend/Legend';
|
|
14
|
+
import { Legend } from '../components/Legend/Legend';
|
|
15
|
+
|
|
16
|
+
export type MapControlsOverlayProps = {
|
|
21
17
|
mapContainerRef: RefObject<HTMLDivElement | null>;
|
|
22
|
-
|
|
23
|
-
isLegendEnabled?: boolean;
|
|
24
|
-
legendContent?: ReactNode;
|
|
25
|
-
// Whether the map is the currently visible view (e.g. the "Map" tab is
|
|
26
|
-
// active). The legend renders via a portal to document.body, so it isn't
|
|
27
|
-
// hidden by the normal tab-panel `hidden` mechanism when another tab is
|
|
28
|
-
// shown — this closes it explicitly instead.
|
|
29
|
-
isActive?: boolean;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
type ControlButtonProps = {
|
|
33
|
-
ariaLabel: string;
|
|
34
|
-
title: string;
|
|
35
|
-
icon: ReactNode;
|
|
36
|
-
onClick: () => void;
|
|
37
|
-
isPressed?: boolean;
|
|
38
|
-
isDisabled?: boolean;
|
|
39
|
-
hasPopup?: boolean;
|
|
40
|
-
isExpanded?: boolean;
|
|
41
|
-
buttonRef?: RefObject<HTMLButtonElement | null>;
|
|
18
|
+
legendItems?: LegendOption[];
|
|
42
19
|
};
|
|
43
20
|
|
|
44
|
-
const
|
|
45
|
-
ariaLabel,
|
|
46
|
-
title,
|
|
47
|
-
icon,
|
|
48
|
-
onClick,
|
|
49
|
-
isPressed,
|
|
50
|
-
isDisabled = false,
|
|
51
|
-
hasPopup,
|
|
52
|
-
isExpanded,
|
|
53
|
-
buttonRef,
|
|
54
|
-
}: ControlButtonProps) => (
|
|
55
|
-
<button
|
|
56
|
-
ref={buttonRef}
|
|
57
|
-
type="button"
|
|
58
|
-
className="ol-btn"
|
|
59
|
-
aria-label={ariaLabel}
|
|
60
|
-
title={title}
|
|
61
|
-
onClick={onClick}
|
|
62
|
-
disabled={isDisabled}
|
|
63
|
-
aria-pressed={isPressed}
|
|
64
|
-
aria-haspopup={hasPopup ? 'dialog' : undefined}
|
|
65
|
-
aria-expanded={isExpanded}
|
|
66
|
-
>
|
|
67
|
-
<span aria-hidden="true">{icon}</span>
|
|
68
|
-
</button>
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
export const MapControlsOverlay = ({
|
|
72
|
-
mapContainerRef,
|
|
73
|
-
isLegendVisible = false,
|
|
74
|
-
isLegendEnabled = false,
|
|
75
|
-
legendContent,
|
|
76
|
-
isActive = true,
|
|
77
|
-
}: MapControlsOverlayProps) => {
|
|
21
|
+
export const MapControlsOverlay = ({ mapContainerRef, legendItems }: MapControlsOverlayProps) => {
|
|
78
22
|
const { map, getLayers } = useMap();
|
|
79
23
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
80
|
-
const [isLayerSwitcherOpen, setIsLayerSwitcherOpen] = useState(false);
|
|
81
|
-
const [layerSwitcherButtonRect, setLayerSwitcherButtonRect] = useState<DOMRect | null>(null);
|
|
82
|
-
const layerSwitcherButtonRef = useRef<HTMLButtonElement>(null);
|
|
83
|
-
const [isLegendOpen, setIsLegendOpen] = useState(false);
|
|
84
|
-
const [legendButtonRect, setLegendButtonRect] = useState<DOMRect | null>(null);
|
|
85
|
-
const legendButtonRef = useRef<HTMLButtonElement>(null);
|
|
86
24
|
|
|
87
25
|
const basemapLayers = useMemo(
|
|
88
26
|
() => (getLayers()?.filter((l: BaseLayer) => l.get('basemap') === true) ?? []) as BaseLayer[],
|
|
89
27
|
[getLayers],
|
|
90
28
|
);
|
|
91
29
|
|
|
92
|
-
const
|
|
93
|
-
(
|
|
94
|
-
|
|
30
|
+
const layerOptions = useMemo<LayerOption[]>(
|
|
31
|
+
() =>
|
|
32
|
+
basemapLayers.map((l) => ({
|
|
33
|
+
name: l.get('name') as string,
|
|
34
|
+
image: l.get('image') as string | undefined,
|
|
35
|
+
})),
|
|
36
|
+
[basemapLayers],
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// OL layer visibility is mutated directly (setVisible below), which doesn't
|
|
40
|
+
// trigger a React re-render on its own, so this can't just be read during
|
|
41
|
+
// render like a derived value, it needs to be actual state, synced here
|
|
42
|
+
// once basemapLayers is available and updated directly on selection.
|
|
43
|
+
const [activeLayerName, setActiveLayerName] = useState<string | null>(null);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const visible = basemapLayers.find((l: BaseLayer) => l.getVisible());
|
|
47
|
+
|
|
48
|
+
setActiveLayerName((visible?.get('name') as string | undefined) ?? null);
|
|
49
|
+
}, [basemapLayers]);
|
|
95
50
|
|
|
96
51
|
const handleFullScreen = useCallback(() => {
|
|
97
52
|
const container = mapContainerRef.current;
|
|
@@ -110,40 +65,6 @@ export const MapControlsOverlay = ({
|
|
|
110
65
|
}
|
|
111
66
|
}, [isFullScreen, map, mapContainerRef]);
|
|
112
67
|
|
|
113
|
-
const handleLayerSwitcherToggle = useCallback(() => {
|
|
114
|
-
const rect = layerSwitcherButtonRef.current?.getBoundingClientRect() ?? null;
|
|
115
|
-
|
|
116
|
-
setLayerSwitcherButtonRect(rect);
|
|
117
|
-
setIsLayerSwitcherOpen((prev) => !prev);
|
|
118
|
-
}, []);
|
|
119
|
-
|
|
120
|
-
const handleLayerSwitcherClose = useCallback(() => {
|
|
121
|
-
setIsLayerSwitcherOpen(false);
|
|
122
|
-
layerSwitcherButtonRef.current?.focus();
|
|
123
|
-
}, []);
|
|
124
|
-
|
|
125
|
-
const handleLegendToggle = useCallback(() => {
|
|
126
|
-
const rect = legendButtonRef.current?.getBoundingClientRect() ?? null;
|
|
127
|
-
|
|
128
|
-
setLegendButtonRect(rect);
|
|
129
|
-
setIsLegendOpen((prev) => !prev);
|
|
130
|
-
}, []);
|
|
131
|
-
|
|
132
|
-
const handleLegendClose = useCallback(() => {
|
|
133
|
-
setIsLegendOpen(false);
|
|
134
|
-
legendButtonRef.current?.focus();
|
|
135
|
-
}, []);
|
|
136
|
-
|
|
137
|
-
// Force-close on leaving the map view rather than remembering the open
|
|
138
|
-
// state — the legend describes what's on the map, so re-opening it after
|
|
139
|
-
// returning is a deliberate, cheap action rather than something worth
|
|
140
|
-
// restoring automatically.
|
|
141
|
-
useEffect(() => {
|
|
142
|
-
if (!isActive) {
|
|
143
|
-
setIsLegendOpen(false);
|
|
144
|
-
}
|
|
145
|
-
}, [isActive]);
|
|
146
|
-
|
|
147
68
|
const handleSelectLayer = useCallback(
|
|
148
69
|
(layerName: string) => {
|
|
149
70
|
const current = basemapLayers.find((l: BaseLayer) => l.getVisible());
|
|
@@ -151,6 +72,7 @@ export const MapControlsOverlay = ({
|
|
|
151
72
|
|
|
152
73
|
current?.setVisible(false);
|
|
153
74
|
next?.setVisible(true);
|
|
75
|
+
setActiveLayerName(layerName);
|
|
154
76
|
},
|
|
155
77
|
[basemapLayers],
|
|
156
78
|
);
|
|
@@ -181,75 +103,46 @@ export const MapControlsOverlay = ({
|
|
|
181
103
|
}
|
|
182
104
|
}, [map]);
|
|
183
105
|
|
|
184
|
-
const fullScreenLabel = isFullScreen ? 'Exit full screen' : 'Toggle full screen';
|
|
185
|
-
const legendLabel = isLegendOpen ? 'Hide legend' : 'Show legend';
|
|
186
|
-
|
|
187
106
|
return (
|
|
188
|
-
|
|
189
|
-
<
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
/>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
title="Basemap selector"
|
|
201
|
-
icon={<LuLayers size={20} />}
|
|
202
|
-
onClick={handleLayerSwitcherToggle}
|
|
203
|
-
hasPopup
|
|
204
|
-
isExpanded={isLayerSwitcherOpen}
|
|
205
|
-
/>
|
|
206
|
-
|
|
207
|
-
<ControlButton
|
|
208
|
-
ariaLabel="Zoom in"
|
|
209
|
-
title="Zoom in"
|
|
210
|
-
icon={<LuPlus size={20} />}
|
|
211
|
-
onClick={handleZoomIn}
|
|
212
|
-
/>
|
|
213
|
-
|
|
214
|
-
<ControlButton
|
|
215
|
-
ariaLabel="Zoom out"
|
|
216
|
-
title="Zoom out"
|
|
217
|
-
icon={<LuMinus size={20} />}
|
|
218
|
-
onClick={handleZoomOut}
|
|
219
|
-
/>
|
|
220
|
-
|
|
221
|
-
{isLegendVisible ? (
|
|
222
|
-
<ControlButton
|
|
223
|
-
buttonRef={legendButtonRef}
|
|
224
|
-
ariaLabel={legendLabel}
|
|
225
|
-
title={legendLabel}
|
|
226
|
-
icon={<LuList size={20} />}
|
|
227
|
-
onClick={handleLegendToggle}
|
|
228
|
-
hasPopup
|
|
229
|
-
isExpanded={isLegendOpen}
|
|
230
|
-
isDisabled={!isLegendEnabled}
|
|
231
|
-
/>
|
|
232
|
-
) : null}
|
|
233
|
-
</div>
|
|
234
|
-
|
|
235
|
-
<LayerSwitcherPanel
|
|
236
|
-
isOpen={isLayerSwitcherOpen}
|
|
237
|
-
onClose={handleLayerSwitcherClose}
|
|
238
|
-
layers={basemapLayers}
|
|
107
|
+
<ButtonGroup orientation="vertical" compressed role="toolbar" aria-label="Map controls">
|
|
108
|
+
<Button
|
|
109
|
+
variant="icon"
|
|
110
|
+
appearance="panel"
|
|
111
|
+
aria-label={isFullScreen ? 'Exit full screen' : 'Enter full screen'}
|
|
112
|
+
onClick={handleFullScreen}
|
|
113
|
+
>
|
|
114
|
+
{isFullScreen ? <LuMinimize2 aria-hidden /> : <LuMaximize2 aria-hidden />}
|
|
115
|
+
</Button>
|
|
116
|
+
|
|
117
|
+
<LayerSwitcher
|
|
118
|
+
layers={layerOptions}
|
|
239
119
|
activeLayerName={activeLayerName}
|
|
240
120
|
onSelectLayer={handleSelectLayer}
|
|
241
|
-
buttonRect={layerSwitcherButtonRect}
|
|
242
121
|
/>
|
|
243
122
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
123
|
+
<Button
|
|
124
|
+
variant="icon"
|
|
125
|
+
appearance="panel"
|
|
126
|
+
aria-label="Zoom in"
|
|
127
|
+
onClick={handleZoomIn}
|
|
128
|
+
disabled={!map}
|
|
129
|
+
>
|
|
130
|
+
<LuPlus aria-hidden />
|
|
131
|
+
</Button>
|
|
132
|
+
|
|
133
|
+
<Button
|
|
134
|
+
variant="icon"
|
|
135
|
+
appearance="panel"
|
|
136
|
+
aria-label="Zoom out"
|
|
137
|
+
onClick={handleZoomOut}
|
|
138
|
+
disabled={!map}
|
|
139
|
+
>
|
|
140
|
+
<LuMinus aria-hidden />
|
|
141
|
+
</Button>
|
|
142
|
+
|
|
143
|
+
{legendItems !== undefined ? (
|
|
144
|
+
<Legend items={legendItems} disabled={legendItems.length === 0} />
|
|
252
145
|
) : null}
|
|
253
|
-
|
|
146
|
+
</ButtonGroup>
|
|
254
147
|
);
|
|
255
148
|
};
|
package/src/map/Popup.tsx
CHANGED
|
@@ -1,48 +1,95 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { LuX } from 'react-icons/lu';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import { Button } from '../components/Button/Button';
|
|
8
|
+
import type { PopupDirection } from '../types/map';
|
|
9
|
+
import { cn } from '../utils/components';
|
|
10
|
+
|
|
11
|
+
// Which corner of the popup box points back at the clicked coordinate,
|
|
12
|
+
// matching MapComponent's own positionTransforms lookup (which decides
|
|
13
|
+
// where the box itself sits) so the two stay in sync.
|
|
14
|
+
const POPUP_ARROW_POSITIONS = {
|
|
15
|
+
'bottom-left': 'right-[-.5em] top-[1em]',
|
|
16
|
+
'bottom-right': 'left-[-.5em] top-[1em]',
|
|
17
|
+
'top-left': 'right-[-.5em] bottom-[1em]',
|
|
18
|
+
'top-right': 'left-[-.5em] bottom-[1em]',
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
// The arrow only ever pokes out the left or right edge (see the offsets
|
|
22
|
+
// above), never top/bottom, so it only ever needs to point left or right.
|
|
23
|
+
// Same rotation math @radix-ui/react-popper uses for its own Arrow (see
|
|
24
|
+
// PopperArrow's per-side transform): the un-rotated shape points down,
|
|
25
|
+
// rotate(90deg) makes it point left, rotate(-90deg) makes it point right.
|
|
26
|
+
const POPUP_ARROW_ROTATIONS = {
|
|
27
|
+
'bottom-left': '-rotate-90',
|
|
28
|
+
'bottom-right': 'rotate-90',
|
|
29
|
+
'top-left': '-rotate-90',
|
|
30
|
+
'top-right': 'rotate-90',
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export type PopupProps = {
|
|
8
34
|
children: ReactNode;
|
|
9
35
|
onClose: () => void;
|
|
10
36
|
clickedCoord: number[] | null;
|
|
11
|
-
|
|
37
|
+
direction?: PopupDirection;
|
|
12
38
|
};
|
|
13
39
|
|
|
40
|
+
// Not built on Modal: it's a non-modal panel pinned to a map coordinate
|
|
41
|
+
// (with a directional arrow pointing back at it) rather than a centred,
|
|
42
|
+
// backdrop-blocking dialog, but it borrows Modal's visual language (panel,
|
|
43
|
+
// no rounded corners, same close button treatment) so the two feel related.
|
|
44
|
+
// One padded box holding everything (close button and content both), same
|
|
45
|
+
// as ModalContent, rather than nesting a second, separately-padded box
|
|
46
|
+
// inside it, so the two actually share the exact same padding math.
|
|
14
47
|
export const Popup = ({
|
|
15
48
|
children,
|
|
16
49
|
onClose,
|
|
17
50
|
clickedCoord,
|
|
18
|
-
|
|
51
|
+
direction = 'bottom-left',
|
|
19
52
|
}: PopupProps) => {
|
|
20
53
|
if (!clickedCoord) {
|
|
21
|
-
return
|
|
54
|
+
return null;
|
|
22
55
|
}
|
|
23
56
|
|
|
24
57
|
return (
|
|
25
|
-
<div
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
</button>
|
|
58
|
+
<div
|
|
59
|
+
role="dialog"
|
|
60
|
+
aria-label="Feature details"
|
|
61
|
+
className="cn-z-top panel relative w-75 p-gds-4"
|
|
62
|
+
>
|
|
63
|
+
<div className="absolute top-gds-2 right-gds-2">
|
|
64
|
+
<Button variant="icon" appearance="ghost" onClick={onClose}>
|
|
65
|
+
<LuX />
|
|
34
66
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
rounded-lg divide-y divide-gray-300"
|
|
38
|
-
>
|
|
39
|
-
{children}
|
|
67
|
+
<span className="sr-only">Close popup</span>
|
|
68
|
+
</Button>
|
|
40
69
|
</div>
|
|
41
70
|
|
|
42
|
-
<div
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
71
|
+
<div className="flex max-h-75 flex-col gap-gds-2 overflow-y-auto">{children}</div>
|
|
72
|
+
|
|
73
|
+
{/* Same shape/proportions as PopoverContent/TooltipContent's own
|
|
74
|
+
Radix Arrow (viewBox 0 0 30 10, width 10, height 5), reimplemented
|
|
75
|
+
rather than reused directly: Radix's Arrow depends on its Popper
|
|
76
|
+
positioning context (floating-ui), which only exists inside a real
|
|
77
|
+
Popover.Content/Tooltip.Content, not this OpenLayers-positioned
|
|
78
|
+
overlay. */}
|
|
79
|
+
<svg
|
|
80
|
+
aria-hidden
|
|
81
|
+
width={10}
|
|
82
|
+
height={5}
|
|
83
|
+
viewBox="0 0 30 10"
|
|
84
|
+
preserveAspectRatio="none"
|
|
85
|
+
className={cn(
|
|
86
|
+
'fill-panel-border absolute -z-10',
|
|
87
|
+
POPUP_ARROW_POSITIONS[direction],
|
|
88
|
+
POPUP_ARROW_ROTATIONS[direction],
|
|
89
|
+
)}
|
|
90
|
+
>
|
|
91
|
+
<polygon points="0,0 30,0 15,10" />
|
|
92
|
+
</svg>
|
|
46
93
|
</div>
|
|
47
94
|
);
|
|
48
95
|
};
|