@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.
Files changed (357) hide show
  1. package/package.json +21 -12
  2. package/src/assets/styles/globals.css +75 -95
  3. package/src/assets/styles/ol.css +57 -16
  4. package/src/assets/styles/utils.css +80 -0
  5. package/src/components/Accordion/Accordion.css +85 -0
  6. package/src/components/Accordion/Accordion.stories.tsx +264 -0
  7. package/src/components/Accordion/Accordion.tsx +98 -0
  8. package/src/components/Alert/Alert.css +129 -0
  9. package/src/components/Alert/Alert.stories.tsx +291 -0
  10. package/src/components/Alert/Alert.tsx +76 -0
  11. package/src/components/BackLink/BackLink.css +12 -0
  12. package/src/components/BackLink/BackLink.stories.tsx +52 -0
  13. package/src/components/BackLink/BackLink.tsx +44 -0
  14. package/src/components/BackToTop/BackToTop.css +23 -0
  15. package/src/components/BackToTop/BackToTop.stories.tsx +41 -0
  16. package/src/components/BackToTop/BackToTop.tsx +48 -0
  17. package/src/components/Breadcrumb/Breadcrumb.css +42 -0
  18. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +94 -0
  19. package/src/components/Breadcrumb/Breadcrumb.tsx +135 -0
  20. package/src/components/Button/Button.css +229 -0
  21. package/src/components/Button/Button.stories.tsx +140 -20
  22. package/src/components/Button/Button.tsx +53 -28
  23. package/src/components/Button/ButtonSkeleton.stories.tsx +50 -0
  24. package/src/components/Button/ButtonSkeleton.tsx +14 -0
  25. package/src/components/ButtonGroup/ButtonGroup.css +52 -0
  26. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +156 -0
  27. package/src/components/ButtonGroup/ButtonGroup.tsx +50 -0
  28. package/src/components/ButtonLink/ButtonLink.css +114 -0
  29. package/src/components/ButtonLink/ButtonLink.stories.tsx +38 -51
  30. package/src/components/ButtonLink/ButtonLink.tsx +54 -23
  31. package/src/components/Card/Card.css +13 -0
  32. package/src/components/Card/Card.stories.tsx +98 -57
  33. package/src/components/Card/Card.tsx +4 -13
  34. package/src/components/CardGroup/CardGroup.css +10 -0
  35. package/src/components/CardGroup/CardGroup.stories.tsx +81 -0
  36. package/src/components/CardGroup/CardGroup.tsx +9 -0
  37. package/src/components/Checkbox/Checkbox.css +49 -0
  38. package/src/components/Checkbox/Checkbox.stories.tsx +86 -0
  39. package/src/components/Checkbox/Checkbox.tsx +32 -0
  40. package/src/components/Chip/Chip.css +54 -0
  41. package/src/components/Chip/Chip.stories.tsx +94 -0
  42. package/src/components/Chip/Chip.tsx +34 -0
  43. package/src/components/CookieBanner/CookieBanner.css +19 -0
  44. package/src/components/CookieBanner/CookieBanner.stories.tsx +52 -0
  45. package/src/components/CookieBanner/CookieBanner.tsx +55 -0
  46. package/src/components/CopyButton/CopyButton.stories.tsx +28 -0
  47. package/src/components/CopyButton/CopyButton.tsx +65 -0
  48. package/src/components/DateInput/DateInput.css +13 -0
  49. package/src/components/DateInput/DateInput.stories.tsx +131 -0
  50. package/src/components/DateInput/DateInput.test.tsx +21 -0
  51. package/src/components/DateInput/DateInput.tsx +98 -0
  52. package/src/components/Details/Details.css +28 -0
  53. package/src/components/Details/Details.stories.tsx +48 -0
  54. package/src/components/Details/Details.tsx +41 -0
  55. package/src/components/Dropdown/Dropdown.css +81 -0
  56. package/src/components/Dropdown/Dropdown.stories.tsx +131 -0
  57. package/src/components/Dropdown/Dropdown.tsx +69 -0
  58. package/src/components/ErrorBoundary/ErrorBoundary.stories.tsx +4 -39
  59. package/src/components/ErrorBoundary/ErrorBoundary.tsx +7 -4
  60. package/src/components/ErrorBoundary/ErrorFallback.tsx +32 -66
  61. package/src/components/ErrorModal/ErrorModal.css +34 -0
  62. package/src/components/ErrorModal/ErrorModal.stories.tsx +100 -0
  63. package/src/components/ErrorModal/ErrorModal.tsx +123 -0
  64. package/src/components/ErrorText/ErrorText.css +22 -0
  65. package/src/components/ErrorText/ErrorText.stories.tsx +42 -27
  66. package/src/components/ErrorText/ErrorText.tsx +10 -8
  67. package/src/components/ExternalLink/ExternalLink.css +12 -0
  68. package/src/components/ExternalLink/ExternalLink.stories.tsx +43 -0
  69. package/src/components/ExternalLink/ExternalLink.tsx +43 -0
  70. package/src/components/Fieldset/Fieldset.css +103 -0
  71. package/src/components/Fieldset/Fieldset.stories.tsx +304 -0
  72. package/src/components/Fieldset/Fieldset.tsx +38 -0
  73. package/src/components/FileUpload/FileUpload.css +85 -0
  74. package/src/components/FileUpload/FileUpload.stories.tsx +106 -0
  75. package/src/components/FileUpload/FileUpload.tsx +99 -0
  76. package/src/components/{layout/footer → Footer}/Copyright.tsx +3 -2
  77. package/src/components/Footer/Footer.stories.tsx +27 -0
  78. package/src/components/Footer/Footer.tsx +46 -0
  79. package/src/components/Footer/Licence.tsx +18 -0
  80. package/src/components/Footer/MetaLinks.tsx +47 -0
  81. package/src/components/Footer/Small/SmallFooter.stories.tsx +3 -0
  82. package/src/components/Footer/Small/SmallFooter.tsx +53 -41
  83. package/src/components/Form/FormGroup.css +18 -0
  84. package/src/components/Form/FormGroup.stories.tsx +87 -0
  85. package/src/components/Form/FormGroup.tsx +16 -0
  86. package/src/components/{layout/header → Header}/Header.stories.tsx +26 -12
  87. package/src/components/Header/Header.test.tsx +87 -0
  88. package/src/components/Header/Header.tsx +304 -0
  89. package/src/components/Header/HeaderAppContent.tsx +120 -0
  90. package/src/components/Heading/Heading.css +40 -0
  91. package/src/components/Heading/Heading.stories.tsx +15 -5
  92. package/src/components/Heading/Heading.tsx +21 -44
  93. package/src/components/Heading/HeadingSkeleton.stories.tsx +30 -0
  94. package/src/components/Heading/HeadingSkeleton.tsx +14 -0
  95. package/src/components/Hero/Hero.css +19 -0
  96. package/src/components/Hero/Hero.stories.tsx +41 -0
  97. package/src/components/Hero/Hero.tsx +14 -0
  98. package/src/components/HintText/HintText.css +5 -0
  99. package/src/components/HintText/HintText.stories.tsx +29 -0
  100. package/src/components/HintText/HintText.tsx +13 -0
  101. package/src/components/InfoBox/InfoBox.css +38 -0
  102. package/src/components/InfoBox/InfoBox.stories.tsx +51 -403
  103. package/src/components/InfoBox/InfoBox.tsx +31 -158
  104. package/src/components/Input/Input.css +77 -0
  105. package/src/components/Input/Input.stories.tsx +123 -0
  106. package/src/components/Input/Input.tsx +74 -0
  107. package/src/components/InputGroup/InputGroup.css +140 -0
  108. package/src/components/InputGroup/InputGroup.stories.tsx +125 -0
  109. package/src/components/InputGroup/InputGroup.tsx +47 -0
  110. package/src/components/InsetText/InsetText.css +5 -0
  111. package/src/components/InsetText/InsetText.stories.tsx +30 -0
  112. package/src/components/InsetText/InsetText.tsx +9 -0
  113. package/src/components/Keyboard/Keyboard.css +9 -0
  114. package/src/components/Keyboard/Keyboard.stories.tsx +63 -0
  115. package/src/components/Keyboard/Keyboard.tsx +30 -0
  116. package/src/components/Label/Label.css +27 -0
  117. package/src/components/Label/Label.stories.tsx +40 -0
  118. package/src/components/Label/Label.tsx +17 -0
  119. package/src/components/LayerSwitcher/LayerSwitcher.css +59 -0
  120. package/src/components/LayerSwitcher/LayerSwitcher.stories.tsx +112 -0
  121. package/src/components/LayerSwitcher/LayerSwitcher.tsx +61 -0
  122. package/src/components/Layout/401/401.stories.tsx +23 -0
  123. package/src/components/Layout/401/401.tsx +1 -0
  124. package/src/components/Layout/403/403.stories.tsx +23 -0
  125. package/src/components/Layout/403/403.tsx +21 -0
  126. package/src/components/Layout/404/404.stories.tsx +23 -0
  127. package/src/components/Layout/404/404.tsx +28 -0
  128. package/src/components/Layout/500/500.stories.tsx +56 -0
  129. package/src/components/Layout/500/500.tsx +63 -0
  130. package/src/components/Layout/ButtonGroup/ButtonGroup.css +52 -0
  131. package/src/components/Layout/ButtonGroup/ButtonGroup.stories.tsx +156 -0
  132. package/src/components/Layout/ButtonGroup/ButtonGroup.tsx +50 -0
  133. package/src/components/Layout/HeadingGroup/HeadingGroup.css +21 -0
  134. package/src/components/Layout/HeadingGroup/HeadingGroup.stories.tsx +84 -0
  135. package/src/components/Layout/HeadingGroup/HeadingGroup.tsx +9 -0
  136. package/src/components/Layout/MainWrapper/MainWrapper.css +12 -0
  137. package/src/components/Layout/MainWrapper/MainWrapper.stories.tsx +36 -0
  138. package/src/components/Layout/MainWrapper/MainWrapper.tsx +14 -0
  139. package/src/components/Layout/PageShell/PageShell.css +42 -0
  140. package/src/components/Layout/PageShell/PageShell.stories.tsx +108 -0
  141. package/src/components/Layout/PageShell/PageShell.tsx +31 -0
  142. package/src/components/Layout/ParagraphGroup/ParagraphGroup.css +5 -0
  143. package/src/components/Layout/ParagraphGroup/ParagraphGroup.stories.tsx +34 -0
  144. package/src/components/Layout/ParagraphGroup/ParagraphGroup.tsx +9 -0
  145. package/src/components/Layout/Stack/Stack.css +29 -0
  146. package/src/components/Layout/Stack/Stack.stories.tsx +83 -0
  147. package/src/components/Layout/Stack/Stack.tsx +19 -0
  148. package/src/components/Layout/WidthContainer/WidthContainer.css +17 -0
  149. package/src/components/Layout/WidthContainer/WidthContainer.stories.tsx +59 -0
  150. package/src/components/Layout/WidthContainer/WidthContainer.tsx +13 -0
  151. package/src/components/Legend/Legend.css +95 -0
  152. package/src/components/Legend/Legend.stories.tsx +81 -0
  153. package/src/components/Legend/Legend.tsx +32 -0
  154. package/src/components/Legend/LegendDot.tsx +15 -0
  155. package/src/components/Legend/LegendPanel.stories.tsx +101 -0
  156. package/src/components/Legend/LegendPanel.tsx +68 -0
  157. package/src/components/Legend/LegendSkeleton.stories.tsx +87 -0
  158. package/src/components/Legend/LegendSkeleton.tsx +73 -0
  159. package/src/components/LegendSelect/LegendSelect.css +8 -0
  160. package/src/components/LegendSelect/LegendSelect.stories.tsx +80 -0
  161. package/src/components/LegendSelect/LegendSelect.tsx +26 -0
  162. package/src/components/Link/Link.css +98 -0
  163. package/src/components/Link/Link.stories.tsx +70 -0
  164. package/src/components/Link/Link.tsx +52 -0
  165. package/src/components/LinkButton/LinkButton.css +67 -0
  166. package/src/components/LinkButton/LinkButton.stories.tsx +90 -42
  167. package/src/components/LinkButton/LinkButton.tsx +50 -63
  168. package/src/components/LinkList/LinkList.css +28 -0
  169. package/src/components/LinkList/LinkList.stories.tsx +46 -0
  170. package/src/components/LinkList/LinkList.tsx +23 -0
  171. package/src/components/List/Li.tsx +9 -0
  172. package/src/components/List/List.css +9 -0
  173. package/src/components/List/OrderedList.stories.tsx +36 -0
  174. package/src/components/List/OrderedList.tsx +9 -0
  175. package/src/components/List/UnorderedList.stories.tsx +36 -0
  176. package/src/components/List/UnorderedList.tsx +9 -0
  177. package/src/components/LoadingBox/LoadingBox.css +27 -0
  178. package/src/components/LoadingBox/LoadingBox.stories.tsx +66 -0
  179. package/src/components/LoadingBox/LoadingBox.tsx +40 -0
  180. package/src/components/Modal/Modal.css +111 -0
  181. package/src/components/Modal/Modal.stories.tsx +94 -155
  182. package/src/components/Modal/Modal.tsx +141 -75
  183. package/src/components/NoScriptBanner/NoScriptBanner.css +17 -0
  184. package/src/components/NoScriptBanner/NoScriptBanner.stories.tsx +46 -0
  185. package/src/components/NoScriptBanner/NoScriptBanner.tsx +26 -0
  186. package/src/components/Paragraph/Paragraph.css +22 -0
  187. package/src/components/Paragraph/Paragraph.stories.tsx +1 -42
  188. package/src/components/Paragraph/Paragraph.tsx +4 -3
  189. package/src/components/Paragraph/ParagraphSkeleton.stories.tsx +44 -0
  190. package/src/components/Paragraph/ParagraphSkeleton.tsx +33 -0
  191. package/src/components/PasswordInput/PasswordInput.css +3 -0
  192. package/src/components/PasswordInput/PasswordInput.stories.tsx +47 -0
  193. package/src/components/PasswordInput/PasswordInput.tsx +45 -0
  194. package/src/components/PhaseBanner/PhaseBanner.css +12 -0
  195. package/src/components/PhaseBanner/PhaseBanner.stories.tsx +61 -0
  196. package/src/components/PhaseBanner/PhaseBanner.tsx +17 -0
  197. package/src/components/Popover/Popover.css +43 -0
  198. package/src/components/Popover/Popover.stories.tsx +102 -0
  199. package/src/components/Popover/Popover.tsx +40 -0
  200. package/src/components/Radio/Radio.css +69 -0
  201. package/src/components/Radio/Radio.stories.tsx +169 -0
  202. package/src/components/Radio/Radio.tsx +56 -0
  203. package/src/components/Resizable/Resizable.css +86 -0
  204. package/src/components/Resizable/Resizable.stories.tsx +123 -0
  205. package/src/components/Resizable/Resizable.tsx +86 -0
  206. package/src/components/ScrollArea/ScrollArea.css +9 -0
  207. package/src/components/ScrollArea/ScrollArea.stories.tsx +42 -0
  208. package/src/components/ScrollArea/ScrollArea.tsx +11 -0
  209. package/src/components/SearchBox/SearchBox.stories.tsx +88 -0
  210. package/src/components/SearchBox/SearchBox.tsx +36 -0
  211. package/src/components/Select/AsyncSelect.stories.tsx +84 -0
  212. package/src/components/Select/AsyncSelect.tsx +41 -0
  213. package/src/components/Select/Select.css +202 -0
  214. package/src/components/Select/Select.stories.tsx +162 -0
  215. package/src/components/Select/Select.test.tsx +36 -0
  216. package/src/components/Select/Select.tsx +296 -0
  217. package/src/components/Select/SelectSkeleton.stories.tsx +51 -0
  218. package/src/components/Select/SelectSkeleton.tsx +23 -0
  219. package/src/components/Separator/Separator.css +41 -0
  220. package/src/components/Separator/Separator.stories.tsx +63 -0
  221. package/src/components/Separator/Separator.tsx +42 -0
  222. package/src/components/Sidebar/ResizableSidebar.tsx +118 -0
  223. package/src/components/Sidebar/Sidebar.css +203 -0
  224. package/src/components/Sidebar/Sidebar.stories.tsx +438 -0
  225. package/src/components/Sidebar/Sidebar.tsx +225 -0
  226. package/src/components/Skeleton/Skeleton.css +16 -0
  227. package/src/components/Skeleton/Skeleton.stories.tsx +39 -0
  228. package/src/components/Skeleton/Skeleton.tsx +12 -0
  229. package/src/components/SkipLink/SkipLink.css +16 -0
  230. package/src/components/SkipLink/SkipLink.stories.tsx +40 -0
  231. package/src/components/SkipLink/SkipLink.tsx +15 -0
  232. package/src/components/Spinner/Spinner.css +30 -0
  233. package/src/components/Spinner/Spinner.stories.tsx +36 -0
  234. package/src/components/Spinner/Spinner.tsx +45 -0
  235. package/src/components/StartButton/StartButton.css +13 -0
  236. package/src/components/StartButton/StartButton.stories.tsx +68 -0
  237. package/src/components/StartButton/StartButton.tsx +50 -0
  238. package/src/components/SubmitButton/SubmitButton.stories.tsx +31 -0
  239. package/src/components/SubmitButton/SubmitButton.tsx +14 -0
  240. package/src/components/SummaryList/SummaryList.css +71 -0
  241. package/src/components/SummaryList/SummaryList.stories.tsx +211 -0
  242. package/src/components/SummaryList/SummaryList.tsx +41 -0
  243. package/src/components/SummaryList/SummaryListSkeleton.stories.tsx +67 -0
  244. package/src/components/SummaryList/SummaryListSkeleton.tsx +40 -0
  245. package/src/components/Tabs/Tabs.css +84 -0
  246. package/src/components/Tabs/Tabs.stories.tsx +133 -0
  247. package/src/components/Tabs/Tabs.tsx +66 -0
  248. package/src/components/Tag/Tag.css +8 -0
  249. package/src/components/Tag/Tag.stories.tsx +96 -0
  250. package/src/components/Tag/Tag.tsx +34 -0
  251. package/src/components/Textarea/Textarea.css +38 -0
  252. package/src/components/Textarea/Textarea.stories.tsx +131 -0
  253. package/src/components/Textarea/Textarea.tsx +50 -0
  254. package/src/components/TextareaCount/TextareaCount.css +17 -0
  255. package/src/components/TextareaCount/TextareaCount.stories.tsx +131 -0
  256. package/src/components/TextareaCount/TextareaCount.tsx +108 -0
  257. package/src/components/Tooltip/Tooltip.css +43 -0
  258. package/src/components/Tooltip/Tooltip.stories.tsx +110 -0
  259. package/src/components/Tooltip/Tooltip.tsx +53 -0
  260. package/src/components/WarningText/WarningText.css +20 -0
  261. package/src/components/WarningText/WarningText.stories.tsx +29 -0
  262. package/src/components/WarningText/WarningText.tsx +17 -0
  263. package/src/components/index.ts +343 -61
  264. package/src/errors/errorDisplay.ts +70 -0
  265. package/src/errors/index.ts +1 -0
  266. package/src/http/constants.ts +2 -0
  267. package/src/http/fetch.ts +7 -3
  268. package/src/map/LayerSwitcherPanel.tsx +2 -173
  269. package/src/map/LegendShim.tsx +2 -0
  270. package/src/map/MapComponent.tsx +7 -17
  271. package/src/map/MapControlsOverlay.tsx +68 -175
  272. package/src/map/Popup.tsx +71 -24
  273. package/src/map/geocoder/Geocoder.test.tsx +46 -3
  274. package/src/map/geocoder/Geocoder.tsx +155 -278
  275. package/src/map/index.ts +1 -1
  276. package/src/types/utils.ts +0 -10
  277. package/src/utils/components.ts +96 -0
  278. package/src/utils/index.ts +1 -0
  279. package/src/utils/utils.ts +0 -12
  280. package/src/assets/images/defra-logo.svg +0 -51
  281. package/src/assets/images/ea-logo.svg +0 -58
  282. package/src/assets/images/ogl.svg +0 -1
  283. package/src/components/Button/Button.test.tsx +0 -53
  284. package/src/components/ButtonLink/ButtonLink.test.tsx +0 -154
  285. package/src/components/Card/Card.test.tsx +0 -51
  286. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +0 -75
  287. package/src/components/ErrorBoundary/ErrorFallback.test.tsx +0 -107
  288. package/src/components/ErrorText/ErrorText.test.tsx +0 -33
  289. package/src/components/Heading/Heading.test.tsx +0 -22
  290. package/src/components/Hint/Hint.stories.tsx +0 -58
  291. package/src/components/Hint/Hint.test.tsx +0 -33
  292. package/src/components/Hint/Hint.tsx +0 -12
  293. package/src/components/InfoBox/InfoBox.test.tsx +0 -330
  294. package/src/components/InfoBox/types.ts +0 -6
  295. package/src/components/LinkButton/LinkButton.test.tsx +0 -177
  296. package/src/components/Modal/Modal.test.tsx +0 -246
  297. package/src/components/NotificationBanner/NotificationBanner.stories.tsx +0 -45
  298. package/src/components/NotificationBanner/NotificationBanner.test.tsx +0 -58
  299. package/src/components/NotificationBanner/NotificationBanner.tsx +0 -45
  300. package/src/components/Paragraph/Paragraph.test.tsx +0 -10
  301. package/src/components/SlidingPanel/SlidingPanel.stories.tsx +0 -129
  302. package/src/components/SlidingPanel/SlidingPanel.test.tsx +0 -83
  303. package/src/components/SlidingPanel/SlidingPanel.tsx +0 -258
  304. package/src/components/accordion/Accordion.stories.tsx +0 -234
  305. package/src/components/accordion/Accordion.test.tsx +0 -192
  306. package/src/components/accordion/Accordion.tsx +0 -73
  307. package/src/components/backToTop/BackToTop.stories.tsx +0 -407
  308. package/src/components/backToTop/BackToTop.test.tsx +0 -57
  309. package/src/components/backToTop/BackToTop.tsx +0 -134
  310. package/src/components/chip/Chip.stories.tsx +0 -61
  311. package/src/components/chip/Chip.test.tsx +0 -31
  312. package/src/components/chip/Chip.tsx +0 -23
  313. package/src/components/container/Container.tsx +0 -42
  314. package/src/components/cookieBanner/CookieBanner.stories.tsx +0 -257
  315. package/src/components/cookieBanner/CookieBanner.test.tsx +0 -68
  316. package/src/components/cookieBanner/CookieBanner.tsx +0 -73
  317. package/src/components/divider/RuleDivider.stories.tsx +0 -254
  318. package/src/components/divider/RuleDivider.test.tsx +0 -164
  319. package/src/components/divider/RuleDivider.tsx +0 -23
  320. package/src/components/dropdown/DropdownMenu.test.tsx +0 -211
  321. package/src/components/dropdown/DropdownMenu.tsx +0 -108
  322. package/src/components/dropdown/useDropdownMenu.ts +0 -249
  323. package/src/components/form/Input.stories.tsx +0 -435
  324. package/src/components/form/Input.test.tsx +0 -206
  325. package/src/components/form/Input.tsx +0 -23
  326. package/src/components/form/TextArea.stories.tsx +0 -464
  327. package/src/components/form/TextArea.test.tsx +0 -232
  328. package/src/components/form/TextArea.tsx +0 -23
  329. package/src/components/layout/footer/Footer.tsx +0 -26
  330. package/src/components/layout/footer/Licence.tsx +0 -19
  331. package/src/components/layout/footer/MetaLinks.tsx +0 -36
  332. package/src/components/layout/header/Header.test.tsx +0 -36
  333. package/src/components/layout/header/Header.tsx +0 -90
  334. package/src/components/layout/header/HeaderAuthClient.test.tsx +0 -45
  335. package/src/components/layout/header/HeaderAuthClient.tsx +0 -40
  336. package/src/components/layout/header/HeaderNavClient.test.tsx +0 -44
  337. package/src/components/layout/header/HeaderNavClient.tsx +0 -67
  338. package/src/components/link/ExternalLink.test.tsx +0 -102
  339. package/src/components/link/ExternalLink.tsx +0 -29
  340. package/src/components/link/Link.tsx +0 -26
  341. package/src/components/select/Select.stories.tsx +0 -336
  342. package/src/components/select/Select.test.tsx +0 -474
  343. package/src/components/select/Select.tsx +0 -125
  344. package/src/components/select/SelectSkeleton.stories.tsx +0 -194
  345. package/src/components/select/SelectSkeleton.test.tsx +0 -104
  346. package/src/components/select/SelectSkeleton.tsx +0 -23
  347. package/src/components/select/common.ts +0 -3
  348. package/src/components/select/index.ts +0 -18
  349. package/src/components/skipLink/SkipLink.stories.tsx +0 -346
  350. package/src/components/skipLink/SkipLink.test.tsx +0 -22
  351. package/src/components/skipLink/SkipLink.tsx +0 -49
  352. package/src/map/LegendPanel.tsx +0 -85
  353. /package/src/{components → assets}/images/DefraLogo.tsx +0 -0
  354. /package/src/{components → assets}/images/EaLogo.tsx +0 -0
  355. /package/src/{components → assets}/images/OglLogo.tsx +0 -0
  356. /package/src/components/{googleAnalytics → GoogleAnalytics}/GlobalVars.tsx +0 -0
  357. /package/src/components/{googleAnalytics → GoogleAnalytics}/GoogleAnalytics.tsx +0 -0
@@ -0,0 +1,42 @@
1
+ @layer components {
2
+ /* MARK: PageShell */
3
+ .cn-page-shell {
4
+ /* Clips the few extra pixels Hero's full-bleed breakout can introduce
5
+ (100vw includes the scrollbar gutter in browsers that reserve space
6
+ for it), rather than a real horizontal scrollbar appearing. Nothing
7
+ meaningful is ever clipped, the breakout is already sized to reach
8
+ exactly the viewport edges. */
9
+ @apply flex flex-col overflow-x-clip;
10
+
11
+ & > header,
12
+ & > footer {
13
+ @apply shrink-0;
14
+ }
15
+
16
+ & > main {
17
+ @apply flex-1;
18
+ }
19
+ }
20
+
21
+ /* MARK: Static
22
+ Normal document scroll: main grows to fill any leftover viewport height,
23
+ footer sits right after it in flow, the whole page scrolls together once
24
+ content overflows. */
25
+ .cn-page-shell-variant-static {
26
+ @apply min-h-screen;
27
+ }
28
+
29
+ /* MARK: Scroll-locked
30
+ App-shell style: the page is pinned to the viewport height and never
31
+ scrolls itself, header and footer stay permanently visible, main gets its
32
+ own scrollbar. min-h-0 overrides the flex item default of
33
+ min-height: auto, which otherwise refuses to let main shrink below its
34
+ content size and silently breaks the internal scroll. */
35
+ .cn-page-shell-variant-scroll-locked {
36
+ @apply h-screen overflow-hidden;
37
+
38
+ & > main {
39
+ @apply min-h-0 overflow-y-auto;
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,108 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { PageShell } from './PageShell';
4
+ import type { NavLink } from '../../../types/navigation';
5
+ import { Footer } from '../../Footer/Footer';
6
+ import { Header } from '../../Header/Header';
7
+ import { Heading } from '../../Heading/Heading';
8
+ import { Paragraph } from '../../Paragraph/Paragraph';
9
+ import { HeadingGroup } from '../HeadingGroup/HeadingGroup';
10
+ import { MainWrapper } from '../MainWrapper/MainWrapper';
11
+ import { ParagraphGroup } from '../ParagraphGroup/ParagraphGroup';
12
+
13
+ const navLinks: NavLink[] = [
14
+ { label: 'Home', url: '/', openInNewTab: false },
15
+ { label: 'API', url: '/api-docs', openInNewTab: false },
16
+ ];
17
+
18
+ const headerArgs = {
19
+ dspUrl: 'https://environment.data.gov.uk',
20
+ appName: 'Wetlands Inventory',
21
+ navLinks,
22
+ credentials: null,
23
+ };
24
+
25
+ const LongContent = () => (
26
+ <HeadingGroup>
27
+ <Heading level="h1">Water quality</Heading>
28
+
29
+ <ParagraphGroup>
30
+ {Array.from({ length: 20 }, (_, index) => (
31
+ <Paragraph key={`paragraph-${index}`}>
32
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
33
+ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
34
+ ullamco laboris nisi ut aliquip ex ea commodo consequat.
35
+ </Paragraph>
36
+ ))}
37
+ </ParagraphGroup>
38
+ </HeadingGroup>
39
+ );
40
+
41
+ const meta: Meta<typeof PageShell> = {
42
+ title: 'Components/Layout/PageShell',
43
+ component: PageShell,
44
+ tags: ['autodocs'],
45
+ parameters: {
46
+ layout: 'fullscreen',
47
+ docs: {
48
+ description: {
49
+ component:
50
+ 'Divides the page into header/main/footer. Compose it directly with Header, MainWrapper, and Footer, PageShell sizes them by tag (`> header`, `> main`, `> footer`), no slot props needed.',
51
+ },
52
+ },
53
+ },
54
+ argTypes: {
55
+ variant: {
56
+ control: { type: 'radio' },
57
+ options: ['static', 'scroll-locked'],
58
+ },
59
+ },
60
+ };
61
+
62
+ export default meta;
63
+
64
+ type Story = StoryObj<typeof PageShell>;
65
+
66
+ export const Static: Story = {
67
+ parameters: {
68
+ docs: {
69
+ description: {
70
+ story:
71
+ 'For static, document-like pages. Main grows to fill any leftover viewport height, but the whole page scrolls together once content overflows, scroll down to see the footer follow the content rather than staying pinned.',
72
+ },
73
+ },
74
+ },
75
+ render: () => (
76
+ <PageShell variant="static">
77
+ <Header {...headerArgs} />
78
+
79
+ <MainWrapper>
80
+ <LongContent />
81
+ </MainWrapper>
82
+
83
+ <Footer accessibilityStatementHref="/accessibility-statement" />
84
+ </PageShell>
85
+ ),
86
+ };
87
+
88
+ export const ScrollLocked: Story = {
89
+ parameters: {
90
+ docs: {
91
+ description: {
92
+ story:
93
+ 'For interactive, app-like pages (e.g. maps). Header and footer stay permanently visible, main gets its own internal scrollbar instead of the page scrolling. Scroll within the main content area to see it, the header and footer never move.',
94
+ },
95
+ },
96
+ },
97
+ render: () => (
98
+ <PageShell variant="scroll-locked">
99
+ <Header {...headerArgs} />
100
+
101
+ <MainWrapper>
102
+ <LongContent />
103
+ </MainWrapper>
104
+
105
+ <Footer accessibilityStatementHref="/accessibility-statement" />
106
+ </PageShell>
107
+ ),
108
+ };
@@ -0,0 +1,31 @@
1
+ import type { ComponentProps } from 'react';
2
+
3
+ import { cn } from '../../../utils/components';
4
+
5
+ const PAGE_SHELL_VARIANTS = {
6
+ static: 'cn-page-shell-variant-static',
7
+ 'scroll-locked': 'cn-page-shell-variant-scroll-locked',
8
+ } as const;
9
+
10
+ export type PageShellVariant = keyof typeof PAGE_SHELL_VARIANTS;
11
+
12
+ export type PageShellProps = ComponentProps<'div'> & {
13
+ variant?: PageShellVariant;
14
+ };
15
+
16
+ /**
17
+ * Divides the page into header/main/footer using tag selectors (`> header`,
18
+ * `> main`, `> footer`), so it composes directly with Header, MainWrapper,
19
+ * and Footer with no slot props needed:
20
+ * `<PageShell variant="static"><Header /><MainWrapper>...</MainWrapper><Footer /></PageShell>`
21
+ */
22
+ export const PageShell = ({ variant = 'static', className, ...props }: PageShellProps) => {
23
+ return (
24
+ <div
25
+ data-slot="page-shell"
26
+ data-variant={variant}
27
+ className={cn('cn-page-shell', PAGE_SHELL_VARIANTS[variant], className)}
28
+ {...props}
29
+ />
30
+ );
31
+ };
@@ -0,0 +1,5 @@
1
+ @layer components {
2
+ .cn-paragraph-group {
3
+ @apply flex flex-col gap-gds-1 @sm:gap-gds-2;
4
+ }
5
+ }
@@ -0,0 +1,34 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { ParagraphGroup } from './ParagraphGroup';
4
+ import { Paragraph } from '../../Paragraph/Paragraph';
5
+
6
+ const meta: Meta<typeof ParagraphGroup> = {
7
+ title: 'Components/Layout/ParagraphGroup',
8
+ component: ParagraphGroup,
9
+ tags: ['autodocs'],
10
+ parameters: {
11
+ docs: {
12
+ description: {
13
+ component: 'Use a ParagraphGroup to group a heading and paragraph with responsive spacing.',
14
+ },
15
+ },
16
+ },
17
+ argTypes: {},
18
+ };
19
+
20
+ export default meta;
21
+
22
+ type Story = StoryObj<typeof ParagraphGroup>;
23
+
24
+ export const Default: Story = {
25
+ render: () => (
26
+ <ParagraphGroup>
27
+ <Paragraph>Hello from paragraph one.</Paragraph>
28
+
29
+ <Paragraph>Hello from paragraph two.</Paragraph>
30
+
31
+ <Paragraph>Hello from paragraph three.</Paragraph>
32
+ </ParagraphGroup>
33
+ ),
34
+ };
@@ -0,0 +1,9 @@
1
+ import type { ComponentProps } from 'react';
2
+
3
+ import { cn } from '../../../utils/components';
4
+
5
+ export type ParagraphGroupProps = ComponentProps<'div'>;
6
+
7
+ export const ParagraphGroup = ({ className, ...props }: ParagraphGroupProps) => {
8
+ return <div className={cn('cn-paragraph-group', className)} {...props} />;
9
+ };
@@ -0,0 +1,29 @@
1
+ @layer components {
2
+ /* MARK: Stack
3
+ Generic vertical rhythm between arbitrary sibling blocks (HeadingGroup,
4
+ ParagraphGroup, ButtonGroup, Card, etc.) as a plain flex+gap column.
5
+ Unlike the *Group components, Stack has no opinion on what it contains,
6
+ it only owns the gap between its direct children. */
7
+ /* Deliberately relies on the flex default of align-items: stretch: most
8
+ things stacked are block-level content (Card, ParagraphGroup, sections)
9
+ that's meant to fill the width anyway, matching normal block flow.
10
+ A lone element that shouldn't stretch (a bare Button) should be wrapped
11
+ in something row-based (e.g. ButtonGroup) rather than Stack opting every
12
+ child out by default, that made the common case need w-full everywhere
13
+ for the sake of the rarer one. */
14
+ .cn-stack {
15
+ @apply flex flex-col;
16
+ }
17
+
18
+ .cn-stack-gap-sm {
19
+ @apply gap-gds-4 @sm:gap-gds-5;
20
+ }
21
+
22
+ .cn-stack-gap-base {
23
+ @apply gap-gds-6 @sm:gap-gds-7;
24
+ }
25
+
26
+ .cn-stack-gap-lg {
27
+ @apply gap-gds-8 @sm:gap-gds-9;
28
+ }
29
+ }
@@ -0,0 +1,83 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { Stack } from './Stack';
4
+ import { Button } from '../../Button/Button';
5
+ import { Heading } from '../../Heading/Heading';
6
+ import { Paragraph } from '../../Paragraph/Paragraph';
7
+ import { ButtonGroup } from '../ButtonGroup/ButtonGroup';
8
+ import { HeadingGroup } from '../HeadingGroup/HeadingGroup';
9
+ import { ParagraphGroup } from '../ParagraphGroup/ParagraphGroup';
10
+
11
+ const meta: Meta<typeof Stack> = {
12
+ title: 'Components/Layout/Stack',
13
+ component: Stack,
14
+ tags: ['autodocs'],
15
+ parameters: {
16
+ docs: {
17
+ description: {
18
+ component:
19
+ "Use Stack for consistent vertical rhythm between sibling blocks that have nothing else in common, e.g. a HeadingGroup followed by a ButtonGroup. The *Group components (HeadingGroup, ParagraphGroup) only space their own children, they intentionally have no opinion on the gap to whatever comes before or after them, that gap is Stack's job.",
20
+ },
21
+ },
22
+ },
23
+ argTypes: {
24
+ gap: {
25
+ control: { type: 'radio' },
26
+ options: ['sm', 'base', 'lg'],
27
+ },
28
+ },
29
+ args: {
30
+ gap: 'base',
31
+ },
32
+ };
33
+
34
+ export default meta;
35
+
36
+ type Story = StoryObj<typeof Stack>;
37
+
38
+ export const Default: Story = {
39
+ render: (args) => (
40
+ <Stack {...args}>
41
+ <HeadingGroup>
42
+ <Heading level="h2">Water quality</Heading>
43
+
44
+ <Paragraph>Explore water quality sample data collected across England.</Paragraph>
45
+ </HeadingGroup>
46
+
47
+ <ParagraphGroup>
48
+ <Paragraph>This service lets you search, filter, and download the data.</Paragraph>
49
+
50
+ <Paragraph>You&apos;ll need to sign in to save a search for later.</Paragraph>
51
+ </ParagraphGroup>
52
+
53
+ <ButtonGroup>
54
+ <Button appearance="primary">Start now</Button>
55
+
56
+ <Button appearance="secondary">Find out more</Button>
57
+ </ButtonGroup>
58
+ </Stack>
59
+ ),
60
+ };
61
+
62
+ export const Gaps: Story = {
63
+ parameters: {
64
+ docs: {
65
+ description: {
66
+ story: 'The three gap sizes side by side, each Stack contains the same two paragraphs.',
67
+ },
68
+ },
69
+ },
70
+ render: () => (
71
+ <Stack gap="lg">
72
+ {(['sm', 'base', 'lg'] as const).map((gap) => (
73
+ <Stack key={gap} gap={gap} className="rounded-sm border border-divider p-gds-3">
74
+ <Paragraph className="text-sm text-body-secondary">gap=&quot;{gap}&quot;</Paragraph>
75
+
76
+ <Paragraph>First block</Paragraph>
77
+
78
+ <Paragraph>Second block</Paragraph>
79
+ </Stack>
80
+ ))}
81
+ </Stack>
82
+ ),
83
+ };
@@ -0,0 +1,19 @@
1
+ import type { ComponentProps } from 'react';
2
+
3
+ import { cn } from '../../../utils/components';
4
+
5
+ const STACK_GAPS = {
6
+ sm: 'cn-stack-gap-sm',
7
+ base: 'cn-stack-gap-base',
8
+ lg: 'cn-stack-gap-lg',
9
+ } as const;
10
+
11
+ export type StackGap = keyof typeof STACK_GAPS;
12
+
13
+ export type StackProps = ComponentProps<'div'> & {
14
+ gap?: StackGap;
15
+ };
16
+
17
+ export const Stack = ({ gap = 'base', className, ...props }: StackProps) => {
18
+ return <div className={cn('cn-stack', STACK_GAPS[gap], className)} {...props} />;
19
+ };
@@ -0,0 +1,17 @@
1
+ @layer components {
2
+ /* MARK: WidthContainer
3
+ GDS "width container" object: centred, padded, capped at max-w-page
4
+ content width. Duplicates rather than @apply's the upstream
5
+ @tpzdsp/eslint-config-dsp .page-wrapper class, that class lives in
6
+ @layer components (not @utility) in an external package, and Tailwind
7
+ only guarantees @apply works across files for @utility-defined classes.
8
+ Also a container-query provider (@container): its own px-* padding still
9
+ responds to the viewport (it IS the viewport-ish boundary at the top of
10
+ the page), but anything nested inside it (Stack, ParagraphGroup,
11
+ HeadingGroup) should size against this element's actual content width,
12
+ not the viewport, so their spacing stays correct if WidthContainer is
13
+ ever nested somewhere narrower than the page. */
14
+ .cn-width-container {
15
+ @apply w-full max-w-page mx-auto px-gds-3 sm:px-gds-6 @container h-full;
16
+ }
17
+ }
@@ -0,0 +1,59 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { WidthContainer } from './WidthContainer';
4
+ import { Paragraph } from '../../Paragraph/Paragraph';
5
+ import { ParagraphGroup } from '../ParagraphGroup/ParagraphGroup';
6
+
7
+ const meta: Meta<typeof WidthContainer> = {
8
+ title: 'Components/Layout/WidthContainer',
9
+ component: WidthContainer,
10
+ tags: ['autodocs'],
11
+ parameters: {
12
+ docs: {
13
+ description: {
14
+ component:
15
+ 'Use a WidthContainer to place content in a single column centered on the page, as per GDS guidelines. View the individual stories on a wide screen to see what the container does.',
16
+ },
17
+ },
18
+ },
19
+ argTypes: {},
20
+ args: {},
21
+ };
22
+
23
+ export default meta;
24
+
25
+ type Story = StoryObj<typeof WidthContainer>;
26
+
27
+ export const RestrictedWidth: Story = {
28
+ render: () => (
29
+ <WidthContainer>
30
+ <ParagraphGroup>
31
+ <Paragraph>I am content with a restricted width</Paragraph>
32
+
33
+ <Paragraph>
34
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
35
+ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
36
+ ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
37
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
38
+ </Paragraph>
39
+ </ParagraphGroup>
40
+ </WidthContainer>
41
+ ),
42
+ };
43
+
44
+ export const ColouredBackground: Story = {
45
+ render: () => (
46
+ <WidthContainer className="bg-accent">
47
+ <ParagraphGroup>
48
+ <Paragraph className="text-on-accent">I am content with a restricted width</Paragraph>
49
+
50
+ <Paragraph className="text-on-accent">
51
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
52
+ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
53
+ ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
54
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
55
+ </Paragraph>
56
+ </ParagraphGroup>
57
+ </WidthContainer>
58
+ ),
59
+ };
@@ -0,0 +1,13 @@
1
+ import type { ComponentProps } from 'react';
2
+
3
+ import { cn } from '../../../utils/components';
4
+
5
+ export type WidthContainerProps = ComponentProps<'div'>;
6
+
7
+ export const WidthContainer = ({ className, children, ...props }: WidthContainerProps) => {
8
+ return (
9
+ <div className={cn('w-full', className)} {...props}>
10
+ <div className="cn-width-container">{children}</div>
11
+ </div>
12
+ );
13
+ };
@@ -0,0 +1,95 @@
1
+ @layer components {
2
+ /* MARK: Panel */
3
+ .cn-legend-panel {
4
+ @apply flex flex-col gap-gds-2;
5
+ }
6
+
7
+ /* MARK: Title
8
+ Matches cn-label's own sizing/colour (text-lg text-body, not bold, no
9
+ margin) rather than its own values, a legend title plays the same
10
+ role for a group of controls that a Label plays for one. */
11
+ .cn-legend-title {
12
+ @apply m-0 text-lg text-body;
13
+ }
14
+
15
+ /* MARK: Items list */
16
+ .cn-legend-items {
17
+ @apply m-0 p-0 list-none flex flex-col gap-gds-1;
18
+ }
19
+
20
+ /* MARK: Item */
21
+ .cn-legend-item {
22
+ @apply flex items-center gap-gds-2 text-sm;
23
+ }
24
+
25
+ /* MARK: Option (interactive row)
26
+ Ghost-button base via cn-button, mirroring cn-layer-switcher-option's
27
+ interaction states so the two selectable-list patterns stay consistent. */
28
+ .cn-legend-option {
29
+ @apply cn-button flex items-center gap-gds-2 p-gds-1 text-sm text-left bg-transparent text-body
30
+ w-full;
31
+
32
+ &:focus {
33
+ @apply outline-none;
34
+ }
35
+
36
+ &:hover:not(:disabled) {
37
+ @apply bg-transparent;
38
+ }
39
+
40
+ &:focus:not(:active):not(:hover) {
41
+ @apply shadow-none;
42
+ }
43
+
44
+ &:active:not(:disabled) {
45
+ @apply bg-black/5 transform-none;
46
+ }
47
+ }
48
+
49
+ /* MARK: Option selected state */
50
+ .cn-legend-option[aria-checked='true'] {
51
+ @apply bg-accent text-on-accent;
52
+
53
+ &:focus:not(:active):not(:hover) {
54
+ @apply bg-accent text-on-accent;
55
+ }
56
+
57
+ &:hover:not(:disabled) {
58
+ @apply bg-focus text-on-focus;
59
+ }
60
+ }
61
+
62
+ /* MARK: Dot
63
+ Inherits the cn-legend-dot utility from LegendSelect.css which adds a
64
+ white outline ring when the dot is inside a selected react-select option.
65
+ No additional styling needed here, colour is set inline via style prop. */
66
+ .cn-legend-dot {
67
+ @apply size-3 shrink-0 rounded-full;
68
+ }
69
+
70
+ .cn-legend-option[aria-checked='true'] .cn-legend-dot {
71
+ outline: var(--focus-ring-width) solid var(--color-panel);
72
+ }
73
+
74
+ /* MARK: Skeleton
75
+ Reuses cn-legend-panel/title/items/item directly for the structural
76
+ wrappers, dot and label bars get their own sizing so a real LegendPanel
77
+ swaps in without any layout shift. */
78
+ .cn-legend-skeleton-title {
79
+ @apply h-4 w-20;
80
+ }
81
+
82
+ .cn-legend-skeleton-dot {
83
+ @apply size-3 shrink-0 rounded-full;
84
+ }
85
+
86
+ /* h-4 (1rem) undershoots text-sm's real rendered height (0.875rem font
87
+ size * 1.5 line-height = 1.3125rem ≈ 21px), a 5px-per-row shortfall
88
+ that compounds into the whole list reading visibly shorter than the
89
+ real, non-skeleton items once loaded, most noticeable in interactive
90
+ mode where cn-legend-option's own padding otherwise makes each row
91
+ tall enough for the gap to stand out. */
92
+ .cn-legend-skeleton-label {
93
+ @apply h-5.25 w-24;
94
+ }
95
+ }
@@ -0,0 +1,81 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { Legend } from './Legend';
4
+ import type { LegendOption } from './Legend';
5
+
6
+ const HABITAT_ITEMS: LegendOption[] = [
7
+ { value: 'bogs', label: 'Bogs', color: '#3b82f6' },
8
+ { value: 'dwarf-shrub', label: 'Dwarf shrub heath', color: '#14b8a6' },
9
+ { value: 'fens', label: 'Fens, marsh & swamp', color: '#ec4899' },
10
+ { value: 'littoral', label: 'Littoral sediments', color: '#f97316' },
11
+ { value: 'neutral', label: 'Neutral grassland', color: '#a855f7' },
12
+ { value: 'sublittoral', label: 'Sublittoral sediment', color: '#22c55e' },
13
+ { value: 'supralittoral-rock', label: 'Supralittoral Rock', color: '#eab308' },
14
+ { value: 'supralittoral-sed', label: 'Supralittoral sediment', color: '#6b7280' },
15
+ ];
16
+
17
+ const meta: Meta<typeof Legend> = {
18
+ title: 'Components/Legend',
19
+ component: Legend,
20
+ tags: ['autodocs'],
21
+ parameters: {
22
+ docs: {
23
+ description: {
24
+ component: `
25
+ Icon button that opens a popover showing a map legend. Intended for use inside a map toolbar alongside other panel-appearance controls.
26
+
27
+ Shares the \`LegendOption\` type (\`value\`, \`label\`, \`color\`) with \`LegendSelect\`, so the same dataset drives both the filter control and the legend.
28
+
29
+ **Props:**
30
+ - \`items\`: array of \`LegendOption\` objects
31
+ - \`title\`: heading shown at the top of the popover (default: \`"Legend"\`)
32
+ `.trim(),
33
+ },
34
+ },
35
+ },
36
+ };
37
+
38
+ export default meta;
39
+
40
+ type Story = StoryObj<typeof Legend>;
41
+
42
+ export const Default: Story = {
43
+ render: () => (
44
+ <div className="flex items-start">
45
+ <Legend items={HABITAT_ITEMS} />
46
+ </div>
47
+ ),
48
+ };
49
+
50
+ export const CustomTitle: Story = {
51
+ parameters: {
52
+ docs: {
53
+ description: { story: 'Pass a custom `title` to override the default "Legend" heading.' },
54
+ },
55
+ },
56
+ render: () => (
57
+ <div className="flex items-start">
58
+ <Legend items={HABITAT_ITEMS} title="Habitat types" />
59
+ </div>
60
+ ),
61
+ };
62
+
63
+ export const FewItems: Story = {
64
+ parameters: {
65
+ docs: {
66
+ description: { story: 'Compact legend with only a handful of categories.' },
67
+ },
68
+ },
69
+ render: () => (
70
+ <div className="flex items-start">
71
+ <Legend
72
+ items={[
73
+ { value: 'low', label: 'Low risk', color: '#22c55e' },
74
+ { value: 'medium', label: 'Medium risk', color: '#eab308' },
75
+ { value: 'high', label: 'High risk', color: '#ef4444' },
76
+ ]}
77
+ title="Flood risk"
78
+ />
79
+ </div>
80
+ ),
81
+ };
@@ -0,0 +1,32 @@
1
+ import type { ComponentProps } from 'react';
2
+
3
+ import { LuList } from 'react-icons/lu';
4
+
5
+ import { LegendPanel } from './LegendPanel';
6
+ import type { LegendOption } from './LegendPanel';
7
+ import { Button } from '../Button/Button';
8
+ import { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover';
9
+
10
+ export type { LegendOption };
11
+
12
+ export type LegendProps = Omit<
13
+ ComponentProps<typeof Button>,
14
+ 'variant' | 'appearance' | 'children'
15
+ > & {
16
+ items: LegendOption[];
17
+ title?: string;
18
+ };
19
+
20
+ export const Legend = ({ items, title = 'Legend', ...props }: LegendProps) => (
21
+ <Popover>
22
+ <PopoverTrigger asChild>
23
+ <Button variant="icon" appearance="panel" aria-label={title} {...props}>
24
+ <LuList aria-hidden />
25
+ </Button>
26
+ </PopoverTrigger>
27
+
28
+ <PopoverContent side="left" align="start" sideOffset={8} className="w-52">
29
+ <LegendPanel items={items} title={title} />
30
+ </PopoverContent>
31
+ </Popover>
32
+ );