@arbor-education/design-system.components 0.14.0 → 0.16.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 (320) hide show
  1. package/.gather/skills/write-stories/SKILL.md +207 -271
  2. package/.storybook/preview.ts +5 -0
  3. package/CHANGELOG.md +27 -0
  4. package/README.md +8 -0
  5. package/component-library.md +144 -13
  6. package/dist/components/articleCard/ArticleCard.stories.d.ts +137 -11
  7. package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -1
  8. package/dist/components/articleCard/ArticleCard.stories.js +358 -91
  9. package/dist/components/articleCard/ArticleCard.stories.js.map +1 -1
  10. package/dist/components/avatar/Avatar.stories.d.ts +6 -6
  11. package/dist/components/avatar/Avatar.stories.d.ts.map +1 -1
  12. package/dist/components/avatar/Avatar.stories.js +393 -49
  13. package/dist/components/avatar/Avatar.stories.js.map +1 -1
  14. package/dist/components/avatarGroup/AvatarGroup.stories.d.ts +9 -7
  15. package/dist/components/avatarGroup/AvatarGroup.stories.d.ts.map +1 -1
  16. package/dist/components/avatarGroup/AvatarGroup.stories.js +688 -65
  17. package/dist/components/avatarGroup/AvatarGroup.stories.js.map +1 -1
  18. package/dist/components/banner/Banner.stories.d.ts.map +1 -1
  19. package/dist/components/banner/Banner.stories.js +7 -3
  20. package/dist/components/banner/Banner.stories.js.map +1 -1
  21. package/dist/components/card/Card.stories.d.ts +105 -4
  22. package/dist/components/card/Card.stories.d.ts.map +1 -1
  23. package/dist/components/card/Card.stories.js +336 -18
  24. package/dist/components/card/Card.stories.js.map +1 -1
  25. package/dist/components/combobox/Combobox.stories.d.ts +134 -21
  26. package/dist/components/combobox/Combobox.stories.d.ts.map +1 -1
  27. package/dist/components/combobox/Combobox.stories.js +676 -175
  28. package/dist/components/combobox/Combobox.stories.js.map +1 -1
  29. package/dist/components/datePicker/DatePicker.stories.d.ts +119 -27
  30. package/dist/components/datePicker/DatePicker.stories.d.ts.map +1 -1
  31. package/dist/components/datePicker/DatePicker.stories.js +575 -47
  32. package/dist/components/datePicker/DatePicker.stories.js.map +1 -1
  33. package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts +155 -39
  34. package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts.map +1 -1
  35. package/dist/components/dateTimePicker/DateTimePicker.stories.js +674 -103
  36. package/dist/components/dateTimePicker/DateTimePicker.stories.js.map +1 -1
  37. package/dist/components/editableText/EditableText.stories.d.ts +53 -12
  38. package/dist/components/editableText/EditableText.stories.d.ts.map +1 -1
  39. package/dist/components/editableText/EditableText.stories.js +401 -64
  40. package/dist/components/editableText/EditableText.stories.js.map +1 -1
  41. package/dist/components/formField/FormField.d.ts +4 -0
  42. package/dist/components/formField/FormField.d.ts.map +1 -1
  43. package/dist/components/formField/FormField.js +2 -1
  44. package/dist/components/formField/FormField.js.map +1 -1
  45. package/dist/components/formField/FormField.test.js +5 -0
  46. package/dist/components/formField/FormField.test.js.map +1 -1
  47. package/dist/components/formField/fieldset/Fieldset.stories.d.ts +56 -4
  48. package/dist/components/formField/fieldset/Fieldset.stories.d.ts.map +1 -1
  49. package/dist/components/formField/fieldset/Fieldset.stories.js +534 -28
  50. package/dist/components/formField/fieldset/Fieldset.stories.js.map +1 -1
  51. package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts +3 -1
  52. package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts.map +1 -1
  53. package/dist/components/formField/inputs/checkbox/CheckboxInput.js +1 -1
  54. package/dist/components/formField/inputs/checkbox/CheckboxInput.js.map +1 -1
  55. package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.d.ts +95 -1
  56. package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.d.ts.map +1 -1
  57. package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.js +386 -9
  58. package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.js.map +1 -1
  59. package/dist/components/formField/inputs/number/NumberInput.d.ts.map +1 -1
  60. package/dist/components/formField/inputs/number/NumberInput.js +14 -2
  61. package/dist/components/formField/inputs/number/NumberInput.js.map +1 -1
  62. package/dist/components/formField/inputs/number/NumberInput.test.js +21 -0
  63. package/dist/components/formField/inputs/number/NumberInput.test.js.map +1 -1
  64. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts +6 -2
  65. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts.map +1 -1
  66. package/dist/components/formField/inputs/radio/RadioButtonGroup.js.map +1 -1
  67. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
  68. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +61 -49
  69. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
  70. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts +188 -166
  71. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts.map +1 -1
  72. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js +821 -160
  73. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js.map +1 -1
  74. package/dist/components/formField/inputs/time/TimeInput.stories.d.ts +176 -22
  75. package/dist/components/formField/inputs/time/TimeInput.stories.d.ts.map +1 -1
  76. package/dist/components/formField/inputs/time/TimeInput.stories.js +851 -92
  77. package/dist/components/formField/inputs/time/TimeInput.stories.js.map +1 -1
  78. package/dist/components/formField/label/Label.stories.d.ts +54 -5
  79. package/dist/components/formField/label/Label.stories.d.ts.map +1 -1
  80. package/dist/components/formField/label/Label.stories.js +238 -4
  81. package/dist/components/formField/label/Label.stories.js.map +1 -1
  82. package/dist/components/icoText/IcoText.stories.d.ts +32 -6
  83. package/dist/components/icoText/IcoText.stories.d.ts.map +1 -1
  84. package/dist/components/icoText/IcoText.stories.js +309 -14
  85. package/dist/components/icoText/IcoText.stories.js.map +1 -1
  86. package/dist/components/kpiCard/KPICard.stories.d.ts +100 -2
  87. package/dist/components/kpiCard/KPICard.stories.d.ts.map +1 -1
  88. package/dist/components/kpiCard/KPICard.stories.js +354 -10
  89. package/dist/components/kpiCard/KPICard.stories.js.map +1 -1
  90. package/dist/components/kvpList/KVPList.stories.d.ts +57 -4
  91. package/dist/components/kvpList/KVPList.stories.d.ts.map +1 -1
  92. package/dist/components/kvpList/KVPList.stories.js +403 -10
  93. package/dist/components/kvpList/KVPList.stories.js.map +1 -1
  94. package/dist/components/modal/Modal.stories.d.ts +113 -9
  95. package/dist/components/modal/Modal.stories.d.ts.map +1 -1
  96. package/dist/components/modal/Modal.stories.js +633 -13
  97. package/dist/components/modal/Modal.stories.js.map +1 -1
  98. package/dist/components/modal/modalManager/ModalManager.stories.d.ts +34 -10
  99. package/dist/components/modal/modalManager/ModalManager.stories.d.ts.map +1 -1
  100. package/dist/components/modal/modalManager/ModalManager.stories.js +463 -85
  101. package/dist/components/modal/modalManager/ModalManager.stories.js.map +1 -1
  102. package/dist/components/pill/Pill.d.ts.map +1 -1
  103. package/dist/components/pill/Pill.js +1 -1
  104. package/dist/components/pill/Pill.js.map +1 -1
  105. package/dist/components/pill/Pill.stories.d.ts.map +1 -1
  106. package/dist/components/pill/Pill.stories.js +11 -13
  107. package/dist/components/pill/Pill.stories.js.map +1 -1
  108. package/dist/components/row/Row.stories.d.ts +1 -2
  109. package/dist/components/row/Row.stories.d.ts.map +1 -1
  110. package/dist/components/row/Row.stories.js +360 -50
  111. package/dist/components/row/Row.stories.js.map +1 -1
  112. package/dist/components/searchBar/SearchBar.stories.d.ts +52 -4
  113. package/dist/components/searchBar/SearchBar.stories.d.ts.map +1 -1
  114. package/dist/components/searchBar/SearchBar.stories.js +428 -36
  115. package/dist/components/searchBar/SearchBar.stories.js.map +1 -1
  116. package/dist/components/section/Section.stories.d.ts +11 -41
  117. package/dist/components/section/Section.stories.d.ts.map +1 -1
  118. package/dist/components/section/Section.stories.js +494 -56
  119. package/dist/components/section/Section.stories.js.map +1 -1
  120. package/dist/components/singleUser/SingleUser.stories.d.ts +5 -4
  121. package/dist/components/singleUser/SingleUser.stories.d.ts.map +1 -1
  122. package/dist/components/singleUser/SingleUser.stories.js +303 -31
  123. package/dist/components/singleUser/SingleUser.stories.js.map +1 -1
  124. package/dist/components/slideoverManager/SlideoverManager.stories.d.ts +32 -11
  125. package/dist/components/slideoverManager/SlideoverManager.stories.d.ts.map +1 -1
  126. package/dist/components/slideoverManager/SlideoverManager.stories.js +380 -84
  127. package/dist/components/slideoverManager/SlideoverManager.stories.js.map +1 -1
  128. package/dist/components/table/DSDefaultColDef.d.ts.map +1 -1
  129. package/dist/components/table/DSDefaultColDef.js +4 -3
  130. package/dist/components/table/DSDefaultColDef.js.map +1 -1
  131. package/dist/components/table/Table.d.ts +7 -1
  132. package/dist/components/table/Table.d.ts.map +1 -1
  133. package/dist/components/table/Table.js +12 -5
  134. package/dist/components/table/Table.js.map +1 -1
  135. package/dist/components/table/Table.stories.d.ts +3 -0
  136. package/dist/components/table/Table.stories.d.ts.map +1 -1
  137. package/dist/components/table/Table.stories.js +445 -3
  138. package/dist/components/table/Table.stories.js.map +1 -1
  139. package/dist/components/table/Table.test.js +184 -0
  140. package/dist/components/table/Table.test.js.map +1 -1
  141. package/dist/components/table/TableFooter.stories.d.ts +49 -0
  142. package/dist/components/table/TableFooter.stories.d.ts.map +1 -0
  143. package/dist/components/table/TableFooter.stories.js +137 -0
  144. package/dist/components/table/TableFooter.stories.js.map +1 -0
  145. package/dist/components/table/TableHeader.stories.d.ts +93 -0
  146. package/dist/components/table/TableHeader.stories.d.ts.map +1 -0
  147. package/dist/components/table/TableHeader.stories.js +176 -0
  148. package/dist/components/table/TableHeader.stories.js.map +1 -0
  149. package/dist/components/table/cellEditors/DateCellEditor.stories.d.ts +44 -0
  150. package/dist/components/table/cellEditors/DateCellEditor.stories.d.ts.map +1 -0
  151. package/dist/components/table/cellEditors/DateCellEditor.stories.js +186 -0
  152. package/dist/components/table/cellEditors/DateCellEditor.stories.js.map +1 -0
  153. package/dist/components/table/cellEditors/NumberCellEditor.d.ts +13 -0
  154. package/dist/components/table/cellEditors/NumberCellEditor.d.ts.map +1 -0
  155. package/dist/components/table/cellEditors/NumberCellEditor.js +35 -0
  156. package/dist/components/table/cellEditors/NumberCellEditor.js.map +1 -0
  157. package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.d.ts +40 -0
  158. package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.d.ts.map +1 -0
  159. package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.js +209 -0
  160. package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.js.map +1 -0
  161. package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.d.ts +48 -0
  162. package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.d.ts.map +1 -0
  163. package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.js +244 -0
  164. package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.js.map +1 -0
  165. package/dist/components/table/cellRenderers/CheckboxCellRenderer.d.ts.map +1 -1
  166. package/dist/components/table/cellRenderers/CheckboxCellRenderer.js +3 -1
  167. package/dist/components/table/cellRenderers/CheckboxCellRenderer.js.map +1 -1
  168. package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.d.ts +64 -0
  169. package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.d.ts.map +1 -0
  170. package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.js +241 -0
  171. package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.js.map +1 -0
  172. package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.d.ts +55 -0
  173. package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.d.ts.map +1 -0
  174. package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.js +245 -0
  175. package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.js.map +1 -0
  176. package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.d.ts +67 -0
  177. package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.d.ts.map +1 -0
  178. package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.js +221 -0
  179. package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.js.map +1 -0
  180. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.d.ts +75 -0
  181. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.d.ts.map +1 -0
  182. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.js +270 -0
  183. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.js.map +1 -0
  184. package/dist/components/table/columnFilters/BooleanFilter.stories.d.ts +57 -0
  185. package/dist/components/table/columnFilters/BooleanFilter.stories.d.ts.map +1 -0
  186. package/dist/components/table/columnFilters/BooleanFilter.stories.js +198 -0
  187. package/dist/components/table/columnFilters/BooleanFilter.stories.js.map +1 -0
  188. package/dist/components/table/columnFilters/TimeFilter.stories.d.ts +58 -0
  189. package/dist/components/table/columnFilters/TimeFilter.stories.d.ts.map +1 -0
  190. package/dist/components/table/columnFilters/TimeFilter.stories.js +207 -0
  191. package/dist/components/table/columnFilters/TimeFilter.stories.js.map +1 -0
  192. package/dist/components/table/pagination/PaginationPanel.stories.d.ts +113 -0
  193. package/dist/components/table/pagination/PaginationPanel.stories.d.ts.map +1 -0
  194. package/dist/components/table/pagination/PaginationPanel.stories.js +272 -0
  195. package/dist/components/table/pagination/PaginationPanel.stories.js.map +1 -0
  196. package/dist/components/table/tableControls/TableControls.stories.d.ts +151 -0
  197. package/dist/components/table/tableControls/TableControls.stories.d.ts.map +1 -0
  198. package/dist/components/table/tableControls/TableControls.stories.js +356 -0
  199. package/dist/components/table/tableControls/TableControls.stories.js.map +1 -0
  200. package/dist/components/table/tableControls/TableSettingsDropdown.d.ts +27 -1
  201. package/dist/components/table/tableControls/TableSettingsDropdown.d.ts.map +1 -1
  202. package/dist/components/table/tableControls/TableSettingsDropdown.js +53 -26
  203. package/dist/components/table/tableControls/TableSettingsDropdown.js.map +1 -1
  204. package/dist/components/table/tableControls/TableSettingsDropdown.test.d.ts +2 -0
  205. package/dist/components/table/tableControls/TableSettingsDropdown.test.d.ts.map +1 -0
  206. package/dist/components/table/tableControls/TableSettingsDropdown.test.js +178 -0
  207. package/dist/components/table/tableControls/TableSettingsDropdown.test.js.map +1 -0
  208. package/dist/components/tabs/Tabs.stories.d.ts +22 -4
  209. package/dist/components/tabs/Tabs.stories.d.ts.map +1 -1
  210. package/dist/components/tabs/Tabs.stories.js +398 -22
  211. package/dist/components/tabs/Tabs.stories.js.map +1 -1
  212. package/dist/components/tabs/TabsItem.stories.d.ts +54 -1
  213. package/dist/components/tabs/TabsItem.stories.d.ts.map +1 -1
  214. package/dist/components/tabs/TabsItem.stories.js +61 -9
  215. package/dist/components/tabs/TabsItem.stories.js.map +1 -1
  216. package/dist/components/toast/Toast.stories.d.ts +103 -10
  217. package/dist/components/toast/Toast.stories.d.ts.map +1 -1
  218. package/dist/components/toast/Toast.stories.js +409 -47
  219. package/dist/components/toast/Toast.stories.js.map +1 -1
  220. package/dist/components/toggle/Toggle.stories.d.ts +61 -46
  221. package/dist/components/toggle/Toggle.stories.d.ts.map +1 -1
  222. package/dist/components/toggle/Toggle.stories.js +311 -122
  223. package/dist/components/toggle/Toggle.stories.js.map +1 -1
  224. package/dist/components/tooltip/Tooltip.stories.d.ts +78 -6
  225. package/dist/components/tooltip/Tooltip.stories.d.ts.map +1 -1
  226. package/dist/components/tooltip/Tooltip.stories.js +413 -7
  227. package/dist/components/tooltip/Tooltip.stories.js.map +1 -1
  228. package/dist/components/tooltip/TooltipWrapper.stories.d.ts +71 -7
  229. package/dist/components/tooltip/TooltipWrapper.stories.d.ts.map +1 -1
  230. package/dist/components/tooltip/TooltipWrapper.stories.js +238 -10
  231. package/dist/components/tooltip/TooltipWrapper.stories.js.map +1 -1
  232. package/dist/index.css +27 -0
  233. package/dist/index.css.map +1 -1
  234. package/dist/index.d.ts +3 -2
  235. package/dist/index.d.ts.map +1 -1
  236. package/dist/index.js +3 -2
  237. package/dist/index.js.map +1 -1
  238. package/dist/utils/PopupParentContext.stories.d.ts +17 -0
  239. package/dist/utils/PopupParentContext.stories.d.ts.map +1 -0
  240. package/dist/utils/PopupParentContext.stories.js +266 -0
  241. package/dist/utils/PopupParentContext.stories.js.map +1 -0
  242. package/dist/utils/getDefaultPopupParent.d.ts.map +1 -1
  243. package/dist/utils/getDefaultPopupParent.js +6 -0
  244. package/dist/utils/getDefaultPopupParent.js.map +1 -1
  245. package/package.json +1 -1
  246. package/src/components/articleCard/ArticleCard.stories.tsx +524 -111
  247. package/src/components/avatar/Avatar.stories.tsx +504 -59
  248. package/src/components/avatarGroup/AvatarGroup.stories.tsx +977 -175
  249. package/src/components/banner/Banner.stories.tsx +7 -3
  250. package/src/components/card/Card.stories.tsx +466 -36
  251. package/src/components/combobox/Combobox.stories.tsx +867 -260
  252. package/src/components/datePicker/DatePicker.stories.tsx +777 -60
  253. package/src/components/dateTimePicker/DateTimePicker.stories.tsx +910 -132
  254. package/src/components/editableText/EditableText.stories.tsx +567 -91
  255. package/src/components/formField/FormField.test.tsx +6 -0
  256. package/src/components/formField/FormField.tsx +5 -0
  257. package/src/components/formField/fieldset/Fieldset.stories.tsx +761 -51
  258. package/src/components/formField/inputs/checkbox/CheckboxGroup.tsx +1 -1
  259. package/src/components/formField/inputs/checkbox/CheckboxInput.tsx +1 -1
  260. package/src/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.tsx +504 -11
  261. package/src/components/formField/inputs/number/NumberInput.test.tsx +28 -0
  262. package/src/components/formField/inputs/number/NumberInput.tsx +15 -0
  263. package/src/components/formField/inputs/radio/RadioButtonGroup.tsx +17 -4
  264. package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +71 -59
  265. package/src/components/formField/inputs/selectDropdown/SelectDropdown.stories.tsx +1079 -168
  266. package/src/components/formField/inputs/time/TimeInput.stories.tsx +1140 -104
  267. package/src/components/formField/label/Label.stories.tsx +317 -8
  268. package/src/components/icoText/IcoText.stories.tsx +442 -31
  269. package/src/components/kpiCard/KPICard.stories.tsx +475 -30
  270. package/src/components/kvpList/KVPList.stories.tsx +593 -26
  271. package/src/components/modal/Modal.stories.tsx +963 -26
  272. package/src/components/modal/modalManager/ModalManager.stories.tsx +612 -454
  273. package/src/components/pill/Pill.stories.tsx +11 -13
  274. package/src/components/pill/Pill.tsx +1 -0
  275. package/src/components/row/Row.stories.tsx +474 -58
  276. package/src/components/searchBar/SearchBar.stories.tsx +570 -38
  277. package/src/components/section/Section.stories.tsx +723 -70
  278. package/src/components/singleUser/SingleUser.stories.tsx +393 -34
  279. package/src/components/slideoverManager/SlideoverManager.stories.tsx +572 -342
  280. package/src/components/table/DSDefaultColDef.ts +25 -5
  281. package/src/components/table/Table.stories.tsx +504 -3
  282. package/src/components/table/Table.test.tsx +255 -0
  283. package/src/components/table/Table.tsx +15 -2
  284. package/src/components/table/TableFooter.stories.tsx +196 -0
  285. package/src/components/table/TableHeader.stories.tsx +251 -0
  286. package/src/components/table/cellEditors/DateCellEditor.stories.tsx +245 -0
  287. package/src/components/table/cellEditors/NumberCellEditor.tsx +83 -0
  288. package/src/components/table/cellEditors/numberCellEditor.scss +11 -0
  289. package/src/components/table/cellRenderers/BooleanCellRenderer.stories.tsx +278 -0
  290. package/src/components/table/cellRenderers/ButtonCellRenderer.stories.tsx +333 -0
  291. package/src/components/table/cellRenderers/CheckboxCellRenderer.stories.tsx +337 -0
  292. package/src/components/table/cellRenderers/CheckboxCellRenderer.tsx +5 -1
  293. package/src/components/table/cellRenderers/DefaultCellRenderer.stories.tsx +342 -0
  294. package/src/components/table/cellRenderers/InlineTextCellRenderer.stories.tsx +292 -0
  295. package/src/components/table/cellRenderers/SelectDropdownCellRenderer.stories.tsx +369 -0
  296. package/src/components/table/columnFilters/BooleanFilter.stories.tsx +268 -0
  297. package/src/components/table/columnFilters/TimeFilter.stories.tsx +281 -0
  298. package/src/components/table/pagination/PaginationPanel.stories.tsx +327 -0
  299. package/src/components/table/table.scss +11 -0
  300. package/src/components/table/tableControls/TableControls.stories.tsx +415 -0
  301. package/src/components/table/tableControls/TableSettingsDropdown.test.tsx +207 -0
  302. package/src/components/table/tableControls/TableSettingsDropdown.tsx +103 -39
  303. package/src/components/tabs/Tabs.stories.tsx +540 -60
  304. package/src/components/tabs/TabsItem.stories.tsx +82 -8
  305. package/src/components/toast/Toast.stories.tsx +539 -77
  306. package/src/components/toggle/Toggle.stories.tsx +371 -135
  307. package/src/components/tooltip/Tooltip.stories.tsx +606 -15
  308. package/src/components/tooltip/TooltipWrapper.stories.tsx +348 -12
  309. package/src/docs/Contributing.mdx +241 -0
  310. package/src/docs/UsingComponents.mdx +93 -0
  311. package/src/docs/Welcome.mdx +68 -0
  312. package/src/global.scss +7 -0
  313. package/src/index.scss +1 -0
  314. package/src/index.ts +3 -2
  315. package/src/utils/PopupParentContext.stories.tsx +367 -0
  316. package/src/utils/getDefaultPopupParent.ts +6 -0
  317. package/.ralph/storybook-upgrade/knowledge.md +0 -308
  318. package/.ralph/storybook-upgrade/prd.json +0 -777
  319. package/.ralph/storybook-upgrade/progress.md +0 -342
  320. package/src/components/table/TableWIP.mdx +0 -3
@@ -1,105 +1,822 @@
1
+ import { useState } from 'react';
1
2
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { fn } from 'storybook/test';
3
+ import {
4
+ Controls,
5
+ Heading as DocHeading,
6
+ Markdown,
7
+ Primary as DocPrimary,
8
+ Stories,
9
+ Subtitle,
10
+ Title,
11
+ } from '@storybook/addon-docs/blocks';
12
+ import { FormField } from 'Components/formField/FormField';
3
13
  import { DatePicker } from './DatePicker';
4
14
 
15
+ // ---------------------------------------------------------------------------
16
+ // Docs page content
17
+ // ---------------------------------------------------------------------------
18
+
19
+ const DESCRIPTION_INTRO = [
20
+ 'DatePicker combines a native `<input type="date">` with a calendar popover for date selection.',
21
+ 'Built on [Radix UI Popover](https://www.radix-ui.com/primitives/docs/components/popover) and',
22
+ '[react-day-picker](https://daypicker.dev/).',
23
+ '',
24
+ '> **Portal rendering.** The calendar panel renders via a Radix portal — do **not** place inside',
25
+ '> a container with `overflow: hidden` or the calendar will be clipped.',
26
+ '',
27
+ 'When empty, a decorative span overlays the hint text (`DD/MM/YYYY` or `Pick a date`) because',
28
+ 'browsers do not reliably show `placeholder` on native date inputs. When a custom `placeholder` is',
29
+ 'provided and the field is empty, the input becomes read-only and the calendar opens on click only',
30
+ '— Tab focus does not trigger it.',
31
+ ].join('\n');
32
+
33
+ const USAGE_GUIDANCE = [
34
+ '### When to use',
35
+ '',
36
+ '- **Single date selection** — enrollment dates, event dates, term start/end dates',
37
+ '- **Date of birth entry** — with a clear format hint so users know the expected format',
38
+ '- **Booking and scheduling** — parent evenings, appointment slots, report deadlines',
39
+ '',
40
+ '---',
41
+ '',
42
+ '### When NOT to use',
43
+ '',
44
+ '| Situation | Use instead |',
45
+ '|---|---|',
46
+ '| Date ranges (from/to) | Two separate `DatePicker` fields |',
47
+ '| Date + time combined | `DatePicker` paired with `TimeInput` |',
48
+ '| Year-only or month-only selection | `SelectDropdown` with year/month options |',
49
+ '| Relative dates ("next week", "in 30 days") | `SelectDropdown` with relative option labels |',
50
+ ].join('\n');
51
+
52
+ const DEVELOPER_NOTES = [
53
+ '### Critical usage patterns',
54
+ '',
55
+ '**`onChange` receives a `Date` object — never a string.** Format for display with',
56
+ '`date?.toLocaleDateString(\'en-GB\')`. Do not call `date.toString()` or assume ISO format.',
57
+ '',
58
+ '**`value` and `defaultValue` are `Date` objects — not strings.** Month is zero-indexed:',
59
+ '`new Date(2024, 8, 1)` = 1 September 2024. Passing a string is a type error.',
60
+ '',
61
+ '```tsx',
62
+ '// ✅ Correct',
63
+ 'defaultValue={new Date(2024, 8, 1)}',
64
+ '',
65
+ '// ❌ Wrong — type error',
66
+ 'defaultValue="2024-09-01"',
67
+ '```',
68
+ '',
69
+ '**`hasError` is visual-only when used standalone.** It applies the red border but does NOT',
70
+ 'set `aria-invalid`. Always pair both when outside `<FormField>`:',
71
+ '',
72
+ '```tsx',
73
+ '<DatePicker hasError aria-invalid={true} aria-describedby="error-id" />',
74
+ '```',
75
+ '',
76
+ '**Controlled vs uncontrolled.** Pass `value` + `onChange` for controlled mode.',
77
+ 'Pass only `defaultValue` for uncontrolled. Do not pass both `value` and `defaultValue`.',
78
+ '',
79
+ '**Time preservation.** DatePicker preserves any time component from the previous value when the',
80
+ 'date changes — this enables clean DateTimePicker composition.',
81
+ '',
82
+ '---',
83
+ '',
84
+ '### Accessibility',
85
+ '',
86
+ '- Always label via `<FormField>` or `aria-label` / `aria-labelledby`',
87
+ '- Pair `hasError` with `aria-invalid={true}` when used standalone — `<FormField>` handles both',
88
+ '- `aria-describedby` should point to the error element ID for screen-reader coverage',
89
+ '- The calendar icon button has built-in screen reader text ("Open date picker")',
90
+ '- The calendar closes and returns focus to the input on date selection or Escape',
91
+ '',
92
+ '---',
93
+ '',
94
+ '### TypeScript types',
95
+ '',
96
+ '```ts',
97
+ "import { DatePicker } from '@arbor-education/design-system.components';",
98
+ '',
99
+ 'function MyDateField(props: DatePicker.Props) { ... }',
100
+ '```',
101
+ '',
102
+ '| Type | Description |',
103
+ '|---|---|',
104
+ '| `DatePicker.Props` | Full props interface |',
105
+ ].join('\n');
106
+
107
+ const RELATED_COMPONENTS = [
108
+ '## Related components',
109
+ '',
110
+ '[FormField](?path=/docs/components-formfield--docs) · [TimeInput](?path=/docs/components-formfield-inputs-timeinput--docs)',
111
+ ].join('\n');
112
+
113
+ const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // Docs page component
117
+ // ---------------------------------------------------------------------------
118
+
119
+ function DatePickerDocsPage() {
120
+ return (
121
+ <>
122
+ <Title />
123
+ <Subtitle />
124
+ <Markdown>{DESCRIPTION_INTRO}</Markdown>
125
+ <DocHeading>Interactive example</DocHeading>
126
+ <Markdown>{PROPS_INTRO}</Markdown>
127
+ <DocPrimary />
128
+ <Controls />
129
+ <DocHeading>Usage guidance</DocHeading>
130
+ <Markdown>{USAGE_GUIDANCE}</Markdown>
131
+ <DocHeading>Developer notes</DocHeading>
132
+ <Markdown>{DEVELOPER_NOTES}</Markdown>
133
+ <DocHeading>Examples</DocHeading>
134
+ <Stories title="" />
135
+ <Markdown>{RELATED_COMPONENTS}</Markdown>
136
+ </>
137
+ );
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Meta
142
+ // ---------------------------------------------------------------------------
143
+
5
144
  const meta = {
6
145
  title: 'Components/DatePicker',
7
146
  component: DatePicker,
8
- decorators: [
9
- Story => (
10
- <div style={{ maxWidth: '220px', width: '100%' }}>
11
- <Story />
12
- </div>
13
- ),
14
- ],
147
+ tags: ['autodocs'],
15
148
  parameters: {
16
- layout: 'centered',
149
+ layout: 'padded',
17
150
  docs: {
18
- description: {
19
- component:
20
- '`DatePicker` uses a native `type="date"` input (`yyyy-MM-dd`) with a synced calendar popover. When empty, hint copy uses a decorative span (browsers do not reliably show `placeholder` on native date fields). `displayFormat` controls that hint (`DD/MM/YYYY` vs `Pick a date`). The custom month/year header uses the design-system select control.',
21
- },
151
+ page: DatePickerDocsPage,
22
152
  },
23
153
  },
24
- tags: ['autodocs'],
25
- args: {
26
- onChange: fn(),
27
- },
28
154
  argTypes: {
29
- displayFormat: {
30
- control: 'inline-radio',
155
+ 'displayFormat': {
156
+ control: { type: 'inline-radio' },
31
157
  options: ['default', 'friendly'],
32
- description: 'Controls the empty-state hint text (`DD/MM/YYYY` vs `Pick a date`). The field value is always native ISO `yyyy-MM-dd`.',
158
+ description: [
159
+ 'Controls the empty-state hint overlay.',
160
+ '`"default"` shows `DD/MM/YYYY`; `"friendly"` shows `Pick a date`.',
161
+ 'Overridden by `placeholder` when that prop is provided.',
162
+ ].join(' '),
163
+ table: {
164
+ type: { summary: "'default' | 'friendly'" },
165
+ defaultValue: { summary: "'default'" },
166
+ },
167
+ },
168
+ 'placeholder': {
169
+ control: 'text',
170
+ description: [
171
+ 'Custom text for the empty-state overlay.',
172
+ 'Overrides the `displayFormat` hint.',
173
+ 'When set and the field is empty, the input becomes read-only — the calendar opens on click only, not Tab focus.',
174
+ ].join(' '),
175
+ table: {
176
+ type: { summary: 'string' },
177
+ defaultValue: { summary: 'undefined' },
178
+ },
179
+ },
180
+ 'onChange': {
181
+ control: false,
182
+ action: 'onChange',
183
+ description: [
184
+ 'Callback fired when the selected date changes.',
185
+ 'Receives a `Date` object, or `undefined` when the field is cleared.',
186
+ 'Never receives a string — format with `date?.toLocaleDateString(\'en-GB\')`.',
187
+ ].join(' '),
188
+ table: {
189
+ type: { summary: '(newDate?: Date) => void' },
190
+ },
191
+ },
192
+ 'hasError': {
193
+ control: 'boolean',
194
+ description: [
195
+ 'Applies error-state visual styling (red border).',
196
+ 'Does **not** set `aria-invalid` automatically when used standalone.',
197
+ 'Always pair with `aria-invalid={true}` for screen-reader coverage.',
198
+ '`<FormField>` handles both automatically when `errorText` is set.',
199
+ ].join(' '),
200
+ table: {
201
+ type: { summary: 'boolean' },
202
+ defaultValue: { summary: 'false' },
203
+ },
204
+ },
205
+ 'value': {
206
+ control: false,
207
+ description: [
208
+ 'Controlled selected date.',
209
+ 'Must be a `Date` object — not a string.',
210
+ 'Pair with `onChange` to keep state in sync.',
211
+ ].join(' '),
212
+ table: {
213
+ type: { summary: 'Date' },
214
+ defaultValue: { summary: 'undefined' },
215
+ },
216
+ },
217
+ 'defaultValue': {
218
+ control: false,
219
+ description: [
220
+ 'Uncontrolled initial date.',
221
+ 'Must be a `Date` object (month is zero-indexed: `new Date(2024, 8, 1)` = 1 Sep 2024).',
222
+ 'Use when you only need the value on form submit.',
223
+ ].join(' '),
224
+ table: {
225
+ type: { summary: 'Date' },
226
+ defaultValue: { summary: 'undefined' },
227
+ },
33
228
  },
34
- placeholder: {
229
+ 'id': {
35
230
  control: 'text',
36
- description: 'Optional override for the empty-state hint (defaults from `displayFormat`).',
231
+ description: 'HTML `id` for the input element. Required when inside `<FormField>` so the label `htmlFor` can be linked.',
232
+ table: {
233
+ type: { summary: 'string' },
234
+ },
37
235
  },
38
- className: {
236
+ 'className': {
39
237
  control: 'text',
40
- description: 'Additional CSS class names',
238
+ description: 'Additional CSS class names on the root wrapper element.',
239
+ table: {
240
+ type: { summary: 'string' },
241
+ },
242
+ },
243
+ 'autoFocus': {
244
+ control: 'boolean',
245
+ description: 'Autofocuses the date input on mount. Use sparingly — unexpected focus shifts can disorient screen-reader users.',
246
+ table: {
247
+ type: { summary: 'boolean' },
248
+ defaultValue: { summary: 'false' },
249
+ },
250
+ },
251
+ 'aria-invalid': {
252
+ control: 'boolean',
253
+ description: [
254
+ 'Marks the input as invalid for screen readers.',
255
+ 'Must be set manually when used standalone — `<FormField>` sets this automatically when `errorText` is provided.',
256
+ ].join(' '),
257
+ table: {
258
+ type: { summary: 'boolean | "true" | "false"' },
259
+ },
41
260
  },
42
- onChange: {
43
- description: 'Callback fired when the selected date changes. Receives the selected Date, or undefined when cleared.',
261
+ 'aria-describedby': {
262
+ control: 'text',
263
+ description: 'ID of the element describing the field (e.g. an error message). Links the input to the error for screen readers.',
264
+ table: {
265
+ type: { summary: 'string' },
266
+ },
44
267
  },
45
268
  },
46
269
  } satisfies Meta<typeof DatePicker>;
47
270
 
48
271
  export default meta;
49
- type Story = StoryObj<typeof meta>;
272
+ type Story = StoryObj<typeof DatePicker>;
50
273
 
51
- export const Default: Story = {
274
+ // ---------------------------------------------------------------------------
275
+ // Helper: attach a per-story description
276
+ // ---------------------------------------------------------------------------
277
+
278
+ const withDescription = (story: Story, description: string): Story => ({
279
+ ...story,
52
280
  parameters: {
281
+ ...story.parameters,
53
282
  docs: {
283
+ ...story.parameters?.docs,
54
284
  description: {
55
- story: 'Empty field shows hint **DD/MM/YYYY** (`displayFormat="default"`).',
285
+ story: description,
56
286
  },
57
287
  },
58
288
  },
289
+ });
290
+
291
+ // ---------------------------------------------------------------------------
292
+ // Named template components for stateful stories
293
+ // ---------------------------------------------------------------------------
294
+
295
+ const ControlledWithDisplayTemplate = () => {
296
+ const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
297
+ return (
298
+ <div style={{ maxWidth: '280px' }}>
299
+ <DatePicker
300
+ id="controlled-display"
301
+ onChange={setSelectedDate}
302
+ />
303
+ <p style={{ margin: 'var(--spacing-small) 0 0', color: 'var(--color-grey-600)' }}>
304
+ Selected:
305
+ {' '}
306
+ <code>
307
+ {selectedDate ? selectedDate.toLocaleDateString('en-GB') : '(none)'}
308
+ </code>
309
+ </p>
310
+ </div>
311
+ );
59
312
  };
60
313
 
61
- export const FriendlyFormat: Story = {
62
- name: 'Friendly Format',
63
- args: {
64
- displayFormat: 'friendly',
314
+ const ControlledWithValidationTemplate = () => {
315
+ const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
316
+ const [error, setError] = useState<string | undefined>(undefined);
317
+
318
+ const today = new Date();
319
+ today.setHours(0, 0, 0, 0);
320
+
321
+ const handleChange = (date: Date | undefined) => {
322
+ setSelectedDate(date);
323
+ if (!date) {
324
+ setError(undefined);
325
+ return;
326
+ }
327
+ const picked = new Date(date);
328
+ picked.setHours(0, 0, 0, 0);
329
+ if (picked <= today) {
330
+ setError('The event date must be in the future. Please pick a later date.');
331
+ }
332
+ else {
333
+ setError(undefined);
334
+ }
335
+ };
336
+
337
+ return (
338
+ <div style={{ maxWidth: '280px' }}>
339
+ <DatePicker
340
+ id="validated-date"
341
+ onChange={handleChange}
342
+ hasError={!!error}
343
+ aria-invalid={error ? true : undefined}
344
+ aria-describedby={error ? 'validated-date-error' : undefined}
345
+ />
346
+ {error && (
347
+ <p
348
+ id="validated-date-error"
349
+ role="alert"
350
+ style={{ margin: 'var(--spacing-small) 0 0', color: 'var(--color-semantic-destructive-600)', fontSize: '0.875rem' }}
351
+ >
352
+ {error}
353
+ </p>
354
+ )}
355
+ {!error && selectedDate && (
356
+ <p style={{ margin: 'var(--spacing-small) 0 0', color: 'var(--color-semantic-success-600)', fontSize: '0.875rem' }}>
357
+ Event date set:
358
+ {' '}
359
+ {selectedDate.toLocaleDateString('en-GB')}
360
+ </p>
361
+ )}
362
+ </div>
363
+ );
364
+ };
365
+
366
+ const WithFormFieldTemplate = () => (
367
+ <div style={{ maxWidth: '320px' }}>
368
+ <FormField
369
+ label="Pupil enrollment date"
370
+ id="ff-enrollment-date"
371
+ inputType="datePicker"
372
+ fieldDescription="Select the date the pupil will join the school."
373
+ inputProps={{}}
374
+ />
375
+ </div>
376
+ );
377
+
378
+ const WithFormFieldAndErrorTemplate = () => {
379
+ const [submitted, setSubmitted] = useState(false);
380
+ const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
381
+ const hasError = submitted && !selectedDate;
382
+
383
+ return (
384
+ <div style={{ maxWidth: '320px', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
385
+ <FormField
386
+ label="Parent evening appointment"
387
+ id="ff-parent-evening"
388
+ inputType="datePicker"
389
+ errorText={hasError ? 'Please select an appointment date before submitting.' : undefined}
390
+ inputProps={{
391
+ 'onChange': setSelectedDate,
392
+ hasError,
393
+ 'aria-invalid': hasError ? true : undefined,
394
+ }}
395
+ />
396
+ <div>
397
+ <button
398
+ type="button"
399
+ onClick={() => setSubmitted(true)}
400
+ style={{ padding: 'var(--spacing-small) var(--spacing-medium)' }}
401
+ >
402
+ Submit
403
+ </button>
404
+ </div>
405
+ {submitted && selectedDate && (
406
+ <p style={{ margin: 0, color: 'var(--color-semantic-success-600)', fontSize: '0.875rem' }}>
407
+ Appointment booked for
408
+ {' '}
409
+ {selectedDate.toLocaleDateString('en-GB')}
410
+ .
411
+ </p>
412
+ )}
413
+ </div>
414
+ );
415
+ };
416
+
417
+ // ---------------------------------------------------------------------------
418
+ // Stories
419
+ // ---------------------------------------------------------------------------
420
+
421
+ export const Default: Story = withDescription(
422
+ {
423
+ args: {
424
+ id: 'default-date',
425
+ },
426
+ render: args => (
427
+ <div style={{ maxWidth: '280px' }}>
428
+ <DatePicker {...args} />
429
+ </div>
430
+ ),
65
431
  },
66
- parameters: {
67
- docs: {
68
- description: {
69
- story: 'Empty field shows hint **Pick a date**.',
432
+ [
433
+ 'The interactive canvas — every prop is wired to the Controls panel.',
434
+ 'Empty field shows the **DD/MM/YYYY** hint overlay (`displayFormat="default"`).',
435
+ 'Click the calendar icon or type directly into the field to select a date.',
436
+ ].join(' '),
437
+ );
438
+
439
+ export const FriendlyFormat: Story = withDescription(
440
+ {
441
+ args: {
442
+ displayFormat: 'friendly',
443
+ id: 'friendly-date',
444
+ },
445
+ render: args => (
446
+ <div style={{ maxWidth: '280px' }}>
447
+ <DatePicker {...args} />
448
+ </div>
449
+ ),
450
+ parameters: {
451
+ controls: { disable: true },
452
+ docs: {
453
+ source: {
454
+ language: 'tsx',
455
+ code: `
456
+ import { DatePicker } from '@arbor-education/design-system.components';
457
+
458
+ function FriendlyFormatExample() {
459
+ return (
460
+ <DatePicker
461
+ id="event-date"
462
+ displayFormat="friendly"
463
+ onChange={(date) => {
464
+ // date is Date | undefined — not a string
465
+ console.log('Event date:', date?.toLocaleDateString('en-GB'));
466
+ }}
467
+ />
468
+ );
469
+ }
470
+ export default FriendlyFormatExample;
471
+ `.trim(),
472
+ },
70
473
  },
71
474
  },
72
475
  },
73
- };
476
+ '`displayFormat="friendly"` shows **Pick a date** instead of the `DD/MM/YYYY` format hint. Use for contexts where a conversational label reads better than a technical format string.',
477
+ );
478
+
479
+ export const WithDefaultValue: Story = withDescription(
480
+ {
481
+ render: () => (
482
+ <div style={{ maxWidth: '280px' }}>
483
+ {/* September 1 2024 — month is zero-indexed (Jan = 0, Sep = 8) */}
484
+ <DatePicker
485
+ id="default-value-date"
486
+ defaultValue={new Date(2024, 8, 1)}
487
+ />
488
+ </div>
489
+ ),
490
+ parameters: {
491
+ controls: { disable: true },
492
+ docs: {
493
+ source: {
494
+ language: 'tsx',
495
+ code: `
496
+ import { DatePicker } from '@arbor-education/design-system.components';
74
497
 
75
- /** `placeholder` overrides the default empty-state hint. */
76
- export const PlaceholderCustomOverride: Story = {
77
- name: 'Placeholder · custom copy',
78
- args: {
79
- displayFormat: 'default',
80
- placeholder: 'e.g. 25/12/2024',
498
+ function EditEnrollmentDate() {
499
+ // Month is zero-indexed: 8 = September
500
+ const currentEnrollmentDate = new Date(2024, 8, 1);
501
+
502
+ return (
503
+ <DatePicker
504
+ id="enrollment-date"
505
+ defaultValue={currentEnrollmentDate}
506
+ onChange={(date) => {
507
+ // date is Date | undefined — not a string
508
+ console.log('Updated to:', date?.toLocaleDateString('en-GB'));
509
+ }}
510
+ />
511
+ );
512
+ }
513
+ export default EditEnrollmentDate;
514
+ `.trim(),
515
+ },
516
+ },
517
+ },
81
518
  },
82
- parameters: {
83
- docs: {
84
- description: {
85
- story: 'The optional `placeholder` prop replaces the default hint.',
519
+ [
520
+ 'Use `defaultValue` to pre-populate an uncontrolled DatePicker — for example when an admin opens a',
521
+ 'pupil\'s enrollment record to edit it. The prop must be a `Date` object (month is zero-indexed:',
522
+ '`new Date(2024, 8, 1)` = 1 September 2024). Passing a string is a type error.',
523
+ ].join(' '),
524
+ );
525
+
526
+ export const WithPlaceholder: Story = withDescription(
527
+ {
528
+ render: () => (
529
+ <div style={{ maxWidth: '280px' }}>
530
+ <DatePicker
531
+ id="placeholder-date"
532
+ placeholder="Choose term start date"
533
+ />
534
+ </div>
535
+ ),
536
+ parameters: {
537
+ controls: { disable: true },
538
+ docs: {
539
+ source: {
540
+ language: 'tsx',
541
+ code: `
542
+ import { DatePicker } from '@arbor-education/design-system.components';
543
+
544
+ function TermStartDatePicker() {
545
+ return (
546
+ <DatePicker
547
+ id="term-start-date"
548
+ placeholder="Choose term start date"
549
+ onChange={(date) => {
550
+ console.log('Term start:', date?.toLocaleDateString('en-GB'));
551
+ }}
552
+ />
553
+ );
554
+ }
555
+ export default TermStartDatePicker;
556
+ `.trim(),
557
+ },
86
558
  },
87
559
  },
88
560
  },
89
- };
561
+ [
562
+ 'When `placeholder` is set and the field is empty, the input becomes **read-only** and the',
563
+ 'calendar opens on **click only** — Tab focus does not trigger it. This prevents the popup',
564
+ 'from unexpectedly opening as keyboard users navigate through a form.',
565
+ ].join(' '),
566
+ );
567
+
568
+ export const ErrorState: Story = withDescription(
569
+ {
570
+ render: () => (
571
+ <div style={{ maxWidth: '280px' }}>
572
+ <DatePicker
573
+ id="error-state-date"
574
+ hasError
575
+ aria-invalid={true}
576
+ aria-describedby="error-state-msg"
577
+ />
578
+ <p
579
+ id="error-state-msg"
580
+ role="alert"
581
+ style={{ margin: 'var(--spacing-xsmall) 0 0', color: 'var(--color-semantic-destructive-600)', fontSize: '0.875rem' }}
582
+ >
583
+ Please select a valid enrollment date.
584
+ </p>
585
+ </div>
586
+ ),
587
+ parameters: {
588
+ controls: { disable: true },
589
+ docs: {
590
+ source: {
591
+ language: 'tsx',
592
+ code: `
593
+ import { DatePicker } from '@arbor-education/design-system.components';
90
594
 
91
- /** Friendly format with custom hint copy. */
92
- export const PlaceholderFriendlyWithCustomCopy: Story = {
93
- name: 'Placeholder · friendly format, custom hint',
94
- args: {
95
- displayFormat: 'friendly',
96
- placeholder: 'Choose a date…',
595
+ // Standalone set both hasError (visual) and aria-invalid (screen reader) yourself.
596
+ // Inside <FormField> both are handled automatically when errorText is set.
597
+ function EnrollmentDateWithError() {
598
+ return (
599
+ <>
600
+ <DatePicker
601
+ id="enrollment-date"
602
+ hasError
603
+ aria-invalid={true}
604
+ aria-describedby="enrollment-date-error"
605
+ />
606
+ <p id="enrollment-date-error" role="alert">
607
+ Please select a valid enrollment date.
608
+ </p>
609
+ </>
610
+ );
611
+ }
612
+ export default EnrollmentDateWithError;
613
+ `.trim(),
614
+ },
615
+ },
616
+ },
97
617
  },
98
- parameters: {
99
- docs: {
100
- description: {
101
- story: '`displayFormat="friendly"` with a custom hint instead of **Pick a date**.',
618
+ [
619
+ 'The error state requires **both** `hasError` (visual red border) and `aria-invalid={true}`',
620
+ '(screen-reader signal). Use `aria-describedby` to link the input to the error message element.',
621
+ 'When using `<FormField>`, set `errorText` and the component handles all three automatically.',
622
+ ].join(' '),
623
+ );
624
+
625
+ export const ControlledWithDisplay: Story = withDescription(
626
+ {
627
+ render: () => <ControlledWithDisplayTemplate />,
628
+ parameters: {
629
+ controls: { disable: true },
630
+ docs: {
631
+ source: {
632
+ language: 'tsx',
633
+ code: `
634
+ import { useState } from 'react';
635
+ import { DatePicker } from '@arbor-education/design-system.components';
636
+
637
+ function ControlledDatePickerExample() {
638
+ const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
639
+
640
+ return (
641
+ <div>
642
+ <DatePicker
643
+ id="event-date"
644
+ onChange={setSelectedDate}
645
+ />
646
+ <p>
647
+ Selected:{' '}
648
+ <code>
649
+ {selectedDate
650
+ ? selectedDate.toLocaleDateString('en-GB')
651
+ : '(none)'}
652
+ </code>
653
+ </p>
654
+ </div>
655
+ );
656
+ }
657
+ export default ControlledDatePickerExample;
658
+ `.trim(),
659
+ },
102
660
  },
103
661
  },
104
662
  },
105
- };
663
+ [
664
+ '`onChange` receives a `Date` object (or `undefined`) — never a string. Use',
665
+ '`date.toLocaleDateString(\'en-GB\')` for human-readable display.',
666
+ 'This controlled example stores the value in state and renders it below the field.',
667
+ ].join(' '),
668
+ );
669
+
670
+ export const ControlledWithValidation: Story = withDescription(
671
+ {
672
+ render: () => <ControlledWithValidationTemplate />,
673
+ parameters: {
674
+ controls: { disable: true },
675
+ docs: {
676
+ source: {
677
+ language: 'tsx',
678
+ code: `
679
+ import { useState } from 'react';
680
+ import { DatePicker } from '@arbor-education/design-system.components';
681
+
682
+ function FutureDatePicker() {
683
+ const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
684
+ const [error, setError] = useState<string | undefined>(undefined);
685
+
686
+ const today = new Date();
687
+ today.setHours(0, 0, 0, 0);
688
+
689
+ function handleChange(date: Date | undefined) {
690
+ setSelectedDate(date);
691
+ if (!date) { setError(undefined); return; }
692
+ const picked = new Date(date);
693
+ picked.setHours(0, 0, 0, 0);
694
+ if (picked <= today) {
695
+ setError('The event date must be in the future. Please pick a later date.');
696
+ } else {
697
+ setError(undefined);
698
+ }
699
+ }
700
+
701
+ return (
702
+ <div>
703
+ <DatePicker
704
+ id="event-date"
705
+ onChange={handleChange}
706
+ hasError={!!error}
707
+ aria-invalid={error ? true : undefined}
708
+ aria-describedby={error ? 'event-date-error' : undefined}
709
+ />
710
+ {error && (
711
+ <p id="event-date-error" role="alert">
712
+ {error}
713
+ </p>
714
+ )}
715
+ </div>
716
+ );
717
+ }
718
+ export default FutureDatePicker;
719
+ `.trim(),
720
+ },
721
+ },
722
+ },
723
+ },
724
+ [
725
+ 'Real-time validation: only future dates are accepted. Selecting today or a past date triggers',
726
+ 'error styling (`hasError` + `aria-invalid` + `aria-describedby`). Clearing the date removes the error.',
727
+ 'The accessible `role="alert"` paragraph announces the error to screen readers immediately.',
728
+ ].join(' '),
729
+ );
730
+
731
+ export const WithFormField: Story = withDescription(
732
+ {
733
+ render: () => <WithFormFieldTemplate />,
734
+ parameters: {
735
+ controls: { disable: true },
736
+ docs: {
737
+ source: {
738
+ language: 'tsx',
739
+ code: `
740
+ import { FormField } from '@arbor-education/design-system.components';
741
+
742
+ function EnrollmentDateField() {
743
+ return (
744
+ <FormField
745
+ label="Pupil enrollment date"
746
+ id="enrollment-date"
747
+ inputType="datePicker"
748
+ fieldDescription="Select the date the pupil will join the school."
749
+ inputProps={{
750
+ onChange: (date) => {
751
+ // date is Date | undefined
752
+ console.log('Enrollment date:', date?.toLocaleDateString('en-GB'));
753
+ },
754
+ }}
755
+ />
756
+ );
757
+ }
758
+ export default EnrollmentDateField;
759
+ `.trim(),
760
+ },
761
+ },
762
+ },
763
+ },
764
+ [
765
+ 'The recommended form usage: `<FormField inputType="datePicker">` provides the accessible',
766
+ 'label, description, and error layout. Pass DatePicker-specific props through `inputProps`.',
767
+ 'The `label`, `fieldDescription`, and `errorText` props belong on `<FormField>` itself.',
768
+ ].join(' '),
769
+ );
770
+
771
+ export const WithFormFieldAndError: Story = withDescription(
772
+ {
773
+ render: () => <WithFormFieldAndErrorTemplate />,
774
+ parameters: {
775
+ controls: { disable: true },
776
+ docs: {
777
+ source: {
778
+ language: 'tsx',
779
+ code: `
780
+ import { useState } from 'react';
781
+ import { FormField } from '@arbor-education/design-system.components';
782
+
783
+ function ParentEveningForm() {
784
+ const [submitted, setSubmitted] = useState(false);
785
+ const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
786
+ const hasError = submitted && !selectedDate;
787
+
788
+ return (
789
+ <div>
790
+ <FormField
791
+ label="Parent evening appointment"
792
+ id="parent-evening-date"
793
+ inputType="datePicker"
794
+ errorText={
795
+ hasError
796
+ ? 'Please select an appointment date before submitting.'
797
+ : undefined
798
+ }
799
+ inputProps={{
800
+ onChange: setSelectedDate,
801
+ hasError,
802
+ 'aria-invalid': hasError ? true : undefined,
803
+ }}
804
+ />
805
+ <button type="button" onClick={() => setSubmitted(true)}>
806
+ Submit
807
+ </button>
808
+ </div>
809
+ );
810
+ }
811
+ export default ParentEveningForm;
812
+ `.trim(),
813
+ },
814
+ },
815
+ },
816
+ },
817
+ [
818
+ 'A submit-gated form: clicking Submit without a date triggers the full error pattern.',
819
+ '`errorText` on `<FormField>` renders the message in the correct accessible layout below the input,',
820
+ 'linked via `aria-describedby` automatically. Once a date is chosen, the error clears.',
821
+ ].join(' '),
822
+ );