@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,200 +1,1111 @@
1
- import type { Meta } from '@storybook/react-vite';
1
+ import { useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
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';
2
13
  import { SelectDropdown } from './SelectDropdown';
3
14
 
4
- const meta: Meta<typeof SelectDropdown> = {
15
+ // ---------------------------------------------------------------------------
16
+ // Docs page content
17
+ // ---------------------------------------------------------------------------
18
+
19
+ const DESCRIPTION_INTRO = [
20
+ 'SelectDropdown is a stylised dropdown select input that matches the Arbor design language.',
21
+ 'It supports single-select and multi-select, option grouping, icons, two-line item layouts,',
22
+ 'and both controlled and uncontrolled state management.',
23
+ '',
24
+ '> **Built on [Radix UI DropdownMenu](https://www.radix-ui.com/primitives/docs/components/dropdown-menu).**',
25
+ '> The dropdown panel renders via a portal — do **not** place SelectDropdown inside a container',
26
+ '> with `overflow: hidden`, or the panel will be clipped.',
27
+ ].join('\n');
28
+
29
+ const USAGE_GUIDANCE = [
30
+ '### When to use',
31
+ '',
32
+ '- Replacing a native `<select>` when you need the Arbor visual style',
33
+ '- Offering a list of options for single or multi-selection within a form',
34
+ '- Displaying options with icons, section headers, or group separators',
35
+ '- When built-in form serialisation is useful (a hidden `<input type="hidden">` is always rendered)',
36
+ '',
37
+ '---',
38
+ '',
39
+ '### When NOT to use',
40
+ '',
41
+ '| Situation | Use instead |',
42
+ '|---|---|',
43
+ '| Need native browser select for mobile or performance | Plain `<select>` inside a FormField |',
44
+ '| Need async search or type-to-filter | Combobox |',
45
+ '| Need individual options to be disabled | Custom `Dropdown` composition |',
46
+ '| Thousands of options | No virtualisation — consider a search-first pattern |',
47
+ ].join('\n');
48
+
49
+ const DEVELOPER_NOTES = [
50
+ '### Critical usage patterns',
51
+ '',
52
+ '**`errorText` does not exist on SelectDropdown.** Error text belongs on `FormField`. Pass `errorText`',
53
+ 'to `FormField` and use `inputType="selectDropdown"` — it automatically wires `hasError`,',
54
+ '`aria-invalid`, and `aria-describedby` for you.',
55
+ '',
56
+ '**`hasError` is visual-only.** It applies error styling to the trigger button but tells screen readers',
57
+ 'nothing. Always pair it with `aria-invalid={true}`.',
58
+ '',
59
+ '**`id` is dual-purpose.** It is set as both the `name` on the hidden form input and the `id` on the',
60
+ 'trigger button. Set this when using inside a `<form>` or when you need a `<label htmlFor>` to target the trigger.',
61
+ '',
62
+ '**Multi-select count display.** With `multiple` and no `placeholder` prop, the trigger shows',
63
+ '`"Select (2)"` when two items are selected. Setting a `placeholder` suppresses the count — the',
64
+ 'trigger shows your placeholder string instead. Choose deliberately.',
65
+ '',
66
+ '**Controlled vs uncontrolled.** Providing `selectedValues` activates controlled mode — the component',
67
+ 'stops managing its own state. You must update `selectedValues` via `onSelectionChange` or the',
68
+ 'dropdown will appear frozen. Do not mix `initialSelectedValues` with `selectedValues`.',
69
+ '',
70
+ '**Groups are automatic.** Add `group: "Year 7"` to options and the component clusters them under',
71
+ '`<h3>` section headers. Headers only appear when two or more distinct `group` values exist.',
72
+ '',
73
+ '---',
74
+ '',
75
+ '### Accessibility',
76
+ '',
77
+ '- Always pair `hasError` with `aria-invalid={true}` — `hasError` is visual-only',
78
+ '- Use `aria-describedBy` to link to your error/hint message element',
79
+ '- When used standalone (outside FormField), set `id` and associate a `<label htmlFor={id}>`',
80
+ '- When used inside `FormField`, label association and ARIA wiring are automatic — prefer that pattern',
81
+ '- Keyboard navigation is built in via Radix UI: `↑ ↓` to move through options, `Enter` to select, `Esc` to close',
82
+ '',
83
+ '---',
84
+ '',
85
+ '### TypeScript types',
86
+ '',
87
+ '```ts',
88
+ "import { SelectDropdown } from '@arbor-education/design-system.components';",
89
+ '',
90
+ 'function MySelect(props: SelectDropdown.Props) { ... }',
91
+ '```',
92
+ '',
93
+ '| Type | Description |',
94
+ '|---|---|',
95
+ '| `SelectDropdown.Props` | Full props interface (`SelectDropdownInputProps`) |',
96
+ ].join('\n');
97
+
98
+ const RELATED_COMPONENTS = [
99
+ '## Related components',
100
+ '',
101
+ '[FormField](?path=/docs/components-formfield--docs) · [Dropdown](?path=/docs/components-dropdown--docs) · [ColourPickerDropdown](?path=/docs/components-formfield-inputs-colourpickerdropdown--docs)',
102
+ ].join('\n');
103
+
104
+ const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
105
+
106
+ function SelectDropdownDocsPage() {
107
+ return (
108
+ <>
109
+ <Title />
110
+ <Subtitle />
111
+ <Markdown>{DESCRIPTION_INTRO}</Markdown>
112
+ <DocHeading>Interactive example</DocHeading>
113
+ <Markdown>{PROPS_INTRO}</Markdown>
114
+ <DocPrimary />
115
+ <Controls />
116
+ <DocHeading>Usage guidance</DocHeading>
117
+ <Markdown>{USAGE_GUIDANCE}</Markdown>
118
+ <DocHeading>Developer notes</DocHeading>
119
+ <Markdown>{DEVELOPER_NOTES}</Markdown>
120
+ <DocHeading>Examples</DocHeading>
121
+ <Stories title="" />
122
+ <Markdown>{RELATED_COMPONENTS}</Markdown>
123
+ </>
124
+ );
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Option fixtures
129
+ // ---------------------------------------------------------------------------
130
+
131
+ const yearGroupOptions = [
132
+ { label: 'Year 7', value: 'year7' },
133
+ { label: 'Year 8', value: 'year8' },
134
+ { label: 'Year 9', value: 'year9' },
135
+ { label: 'Year 10', value: 'year10' },
136
+ { label: 'Year 11', value: 'year11' },
137
+ ];
138
+
139
+ const subjectOptions = [
140
+ { label: 'Mathematics', value: 'maths' },
141
+ { label: 'English', value: 'english' },
142
+ { label: 'Science', value: 'science' },
143
+ { label: 'History', value: 'history' },
144
+ { label: 'Geography', value: 'geography' },
145
+ { label: 'Art', value: 'art' },
146
+ ];
147
+
148
+ const subjectOptionsWithIcons = [
149
+ { label: 'Mathematics', value: 'maths', icon: 'chart-column-increasing' as const },
150
+ { label: 'English', value: 'english', icon: 'book-open' as const },
151
+ { label: 'Science', value: 'science', icon: 'circle-check' as const },
152
+ { label: 'History', value: 'history', icon: 'clock-3' as const },
153
+ { label: 'Geography', value: 'geography', icon: 'flag' as const },
154
+ ];
155
+
156
+ const attendanceReasonOptions = [
157
+ { label: 'Authorised absence', value: 'authorised', header: 'Planned' },
158
+ { label: 'Holiday', value: 'holiday', header: 'Planned' },
159
+ { label: 'Medical appointment', value: 'medical', header: 'Health' },
160
+ { label: 'Illness', value: 'illness', header: 'Health' },
161
+ { label: 'Unauthorised absence', value: 'unauthorised', header: 'Unplanned' },
162
+ { label: 'Late arrival', value: 'late', header: 'Unplanned' },
163
+ ];
164
+
165
+ const groupedYearOptions = [
166
+ { label: 'Year 7', value: 'year7', group: 'Lower school' },
167
+ { label: 'Year 8', value: 'year8', group: 'Lower school' },
168
+ { label: 'Year 9', value: 'year9', group: 'Lower school' },
169
+ { label: 'Year 10', value: 'year10', group: 'Upper school' },
170
+ { label: 'Year 11', value: 'year11', group: 'Upper school' },
171
+ { label: 'Year 12', value: 'year12', group: 'Sixth form' },
172
+ { label: 'Year 13', value: 'year13', group: 'Sixth form' },
173
+ ];
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Meta
177
+ // ---------------------------------------------------------------------------
178
+
179
+ const meta = {
5
180
  title: 'Components/FormField/Inputs/SelectDropdown',
6
181
  component: SelectDropdown,
7
- };
182
+ tags: ['autodocs'],
183
+ parameters: {
184
+ layout: 'padded',
185
+ docs: {
186
+ page: SelectDropdownDocsPage,
187
+ },
188
+ },
189
+ argTypes: {
190
+ 'options': {
191
+ description: [
192
+ 'Array of option items to display in the dropdown.',
193
+ 'Each item must have a `value` string.',
194
+ '`label`, `icon`, `header`, and `group` are optional.',
195
+ ].join(' '),
196
+ control: false,
197
+ table: {
198
+ type: { summary: 'SelectDropdownItemProps[]' },
199
+ },
200
+ },
201
+ 'placeholder': {
202
+ description: [
203
+ 'Text shown in the trigger when nothing is selected.',
204
+ 'In multi-select mode, also shown when 2+ items are selected (suppressing the count display).',
205
+ 'Defaults to `"Select"`.',
206
+ ].join(' '),
207
+ control: 'text',
208
+ table: {
209
+ type: { summary: 'string' },
210
+ defaultValue: { summary: "'Select'" },
211
+ },
212
+ },
213
+ 'multiple': {
214
+ description: [
215
+ 'Enables multi-select mode.',
216
+ 'The dropdown stays open after each selection and items toggle on/off.',
217
+ ].join(' '),
218
+ control: 'boolean',
219
+ table: {
220
+ type: { summary: 'boolean' },
221
+ defaultValue: { summary: 'false' },
222
+ },
223
+ },
224
+ 'disabled': {
225
+ description: 'Disables the trigger button, preventing the dropdown from opening.',
226
+ control: 'boolean',
227
+ table: {
228
+ type: { summary: 'boolean' },
229
+ defaultValue: { summary: 'false' },
230
+ },
231
+ },
232
+ 'hasError': {
233
+ description: [
234
+ '**Visual only.** Applies error styling to the trigger button.',
235
+ 'Must be paired with `aria-invalid={true}` for screen reader support.',
236
+ 'Do not pass `errorText` here — that prop does not exist. Use `FormField` with `errorText` instead.',
237
+ ].join(' '),
238
+ control: 'boolean',
239
+ table: {
240
+ type: { summary: 'boolean' },
241
+ defaultValue: { summary: 'false' },
242
+ },
243
+ },
244
+ 'aria-invalid': {
245
+ description: [
246
+ 'Communicates an invalid state to screen readers via the trigger button.',
247
+ 'Always pair with `hasError`.',
248
+ ].join(' '),
249
+ control: 'boolean',
250
+ table: {
251
+ type: { summary: 'boolean' },
252
+ defaultValue: { summary: 'undefined' },
253
+ },
254
+ },
255
+ 'aria-describedBy': {
256
+ description: [
257
+ 'ID of the element that describes this field (e.g. an error message or hint).',
258
+ 'Passed through to the trigger button.',
259
+ ].join(' '),
260
+ control: 'text',
261
+ table: {
262
+ type: { summary: 'string' },
263
+ defaultValue: { summary: 'undefined' },
264
+ },
265
+ },
266
+ 'id': {
267
+ description: [
268
+ '**Dual-purpose.** Set as `name` on the hidden form input AND as `id` on the trigger button.',
269
+ 'Required for label association (`<label htmlFor={id}>`) and form submission.',
270
+ ].join(' '),
271
+ control: 'text',
272
+ table: {
273
+ type: { summary: 'string' },
274
+ defaultValue: { summary: 'undefined' },
275
+ },
276
+ },
277
+ 'alwaysShowPlaceholder': {
278
+ description: [
279
+ 'When `true`, the trigger always displays the placeholder regardless of what is selected.',
280
+ 'Use for action-style dropdowns (e.g. "Add filter") where the label must not change.',
281
+ ].join(' '),
282
+ control: 'boolean',
283
+ table: {
284
+ type: { summary: 'boolean' },
285
+ defaultValue: { summary: 'false' },
286
+ },
287
+ },
288
+ 'initialSelectedValues': {
289
+ description: [
290
+ 'Uncontrolled mode only — sets the initial selection on mount.',
291
+ 'Ignored if `selectedValues` is provided.',
292
+ ].join(' '),
293
+ control: false,
294
+ table: {
295
+ type: { summary: 'string[]' },
296
+ defaultValue: { summary: '[]' },
297
+ },
298
+ },
299
+ 'selectedValues': {
300
+ description: [
301
+ 'Activates fully controlled mode.',
302
+ 'You must update this via `onSelectionChange` — the component stops managing its own state.',
303
+ 'Do not mix with `initialSelectedValues`.',
304
+ ].join(' '),
305
+ control: false,
306
+ table: {
307
+ type: { summary: 'string[]' },
308
+ defaultValue: { summary: 'undefined' },
309
+ },
310
+ },
311
+ 'onSelectionChange': {
312
+ description: 'Fires after every selection change with the full updated array of selected values.',
313
+ action: 'onSelectionChange',
314
+ control: false,
315
+ table: {
316
+ type: { summary: '(value: string[]) => void' },
317
+ },
318
+ },
319
+ 'open': {
320
+ description: 'Controls the open/closed state of the dropdown externally.',
321
+ control: 'boolean',
322
+ table: {
323
+ type: { summary: 'boolean' },
324
+ defaultValue: { summary: 'undefined' },
325
+ },
326
+ },
327
+ 'onOpenChange': {
328
+ description: 'Fires when the dropdown attempts to open or close. Required when `open` is controlled.',
329
+ action: 'onOpenChange',
330
+ control: false,
331
+ table: {
332
+ type: { summary: '(open: boolean) => void' },
333
+ },
334
+ },
335
+ },
336
+ } satisfies Meta<typeof SelectDropdown>;
337
+
338
+ export default meta;
339
+ type Story = StoryObj<typeof SelectDropdown>;
340
+
341
+ // ---------------------------------------------------------------------------
342
+ // Helper: attach a per-story description to docs
343
+ // ---------------------------------------------------------------------------
8
344
 
9
- export const Default = {
10
- args: {
11
- title: 'titleValue',
12
- options: [
13
- { label: 'Option 1', value: 'option1' },
14
- { label: 'Option 2', value: 'option2' },
15
- { label: 'Option 3', value: 'option3' },
16
- ],
17
- multiple: false,
18
- onSelectionChange: (value: string[]) => { console.log(value); },
345
+ const withDescription = (story: Story, description: string): Story => ({
346
+ ...story,
347
+ parameters: {
348
+ ...story.parameters,
349
+ docs: {
350
+ ...story.parameters?.docs,
351
+ description: {
352
+ story: description,
353
+ },
354
+ },
19
355
  },
356
+ });
357
+
358
+ // ---------------------------------------------------------------------------
359
+ // Template components for stateful and composite stories
360
+ // ---------------------------------------------------------------------------
361
+
362
+ const WithInitialSelectionTemplate = () => (
363
+ <div style={{ maxWidth: '320px' }}>
364
+ <SelectDropdown
365
+ placeholder="Select a year group"
366
+ initialSelectedValues={['year9']}
367
+ options={yearGroupOptions}
368
+ onSelectionChange={values => console.log(values)}
369
+ />
370
+ </div>
371
+ );
372
+
373
+ const MultiSelectTemplate = () => (
374
+ <div style={{ maxWidth: '320px' }}>
375
+ <SelectDropdown
376
+ placeholder="Select subjects"
377
+ multiple
378
+ options={subjectOptions}
379
+ onSelectionChange={values => console.log(values)}
380
+ />
381
+ </div>
382
+ );
383
+
384
+ const MultiSelectWithCountTemplate = () => (
385
+ <div style={{ maxWidth: '320px' }}>
386
+ <SelectDropdown
387
+ multiple
388
+ initialSelectedValues={['maths', 'english', 'science']}
389
+ options={subjectOptions}
390
+ onSelectionChange={values => console.log(values)}
391
+ />
392
+ </div>
393
+ );
394
+
395
+ const DisabledTemplate = () => (
396
+ <div style={{ maxWidth: '320px' }}>
397
+ <SelectDropdown
398
+ placeholder="Select a year group"
399
+ disabled
400
+ options={yearGroupOptions}
401
+ onSelectionChange={values => console.log(values)}
402
+ />
403
+ </div>
404
+ );
405
+
406
+ const WithErrorTemplate = () => (
407
+ <div style={{ maxWidth: '320px', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-xsmall)' }}>
408
+ <SelectDropdown
409
+ placeholder="Select a year group"
410
+ hasError
411
+ aria-invalid
412
+ aria-describedBy="year-group-error"
413
+ options={yearGroupOptions}
414
+ onSelectionChange={values => console.log(values)}
415
+ />
416
+ <span
417
+ id="year-group-error"
418
+ style={{ fontSize: '0.875rem', color: 'var(--color-feedback-danger-600)' }}
419
+ >
420
+ Please select a year group
421
+ </span>
422
+ </div>
423
+ );
424
+
425
+ const WithIconsTemplate = () => (
426
+ <div style={{ maxWidth: '320px' }}>
427
+ <SelectDropdown
428
+ placeholder="Select a subject"
429
+ options={subjectOptionsWithIcons}
430
+ onSelectionChange={values => console.log(values)}
431
+ />
432
+ </div>
433
+ );
434
+
435
+ const WithItemHeadersTemplate = () => (
436
+ <div style={{ maxWidth: '320px' }}>
437
+ <SelectDropdown
438
+ placeholder="Select absence reason"
439
+ options={attendanceReasonOptions}
440
+ onSelectionChange={values => console.log(values)}
441
+ />
442
+ </div>
443
+ );
444
+
445
+ const WithGroupsTemplate = () => (
446
+ <div style={{ maxWidth: '320px' }}>
447
+ <SelectDropdown
448
+ placeholder="Select a year group"
449
+ options={groupedYearOptions}
450
+ onSelectionChange={values => console.log(values)}
451
+ />
452
+ </div>
453
+ );
454
+
455
+ const WithGroupsMultiSelectTemplate = () => (
456
+ <div style={{ maxWidth: '320px' }}>
457
+ <SelectDropdown
458
+ placeholder="Select year groups"
459
+ multiple
460
+ options={groupedYearOptions}
461
+ onSelectionChange={values => console.log(values)}
462
+ />
463
+ </div>
464
+ );
465
+
466
+ const AlwaysShowPlaceholderTemplate = () => (
467
+ <div style={{ maxWidth: '320px' }}>
468
+ <SelectDropdown
469
+ placeholder="Add filter"
470
+ alwaysShowPlaceholder
471
+ multiple
472
+ options={subjectOptions}
473
+ onSelectionChange={values => console.log(values)}
474
+ />
475
+ </div>
476
+ );
477
+
478
+ const ControlledSingleSelectTemplate = () => {
479
+ const [selected, setSelected] = useState<string[]>([]);
480
+ return (
481
+ <div style={{ maxWidth: '320px', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)' }}>
482
+ <SelectDropdown
483
+ placeholder="Select a subject"
484
+ selectedValues={selected}
485
+ onSelectionChange={setSelected}
486
+ options={subjectOptions}
487
+ />
488
+ <span style={{ fontSize: '0.875rem', color: 'var(--color-grey-600)' }}>
489
+ Selected:
490
+ {' '}
491
+ {selected.length > 0 ? selected.join(', ') : 'nothing yet'}
492
+ </span>
493
+ </div>
494
+ );
20
495
  };
21
496
 
22
- export const WithIcon = {
23
- args: {
24
- title: 'titleValue',
25
- options: [
26
- { label: 'Option 1', value: 'option1', icon: '3-dot' },
27
- { label: 'Option 2', value: 'option2', icon: 'user' },
28
- { label: 'Option 3', value: 'option3', icon: 'chart-spline' },
29
- ],
30
- multiple: true,
31
- onSelectionChange: (value: string[]) => { console.log(value); },
32
- },
497
+ const ControlledOpenTemplate = () => {
498
+ const [isOpen, setIsOpen] = useState(false);
499
+ return (
500
+ <div style={{ maxWidth: '320px', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-small)' }}>
501
+ <button type="button" onClick={() => setIsOpen(true)}>
502
+ Open dropdown externally
503
+ </button>
504
+ <SelectDropdown
505
+ placeholder="Select a subject"
506
+ open={isOpen}
507
+ onOpenChange={setIsOpen}
508
+ options={subjectOptions}
509
+ onSelectionChange={(values) => {
510
+ console.log(values);
511
+ setIsOpen(false);
512
+ }}
513
+ />
514
+ </div>
515
+ );
33
516
  };
34
517
 
35
- export const DefaultWithGroups = {
36
- args: {
37
- title: 'titleValue',
38
- multiple: true,
39
- options: [
40
- { label: 'Option 5', value: 'option5', group: 'Group 1' },
41
- { label: 'Option 6', value: 'option6', group: 'Group 1' },
42
- { label: 'Option 7', value: 'option7', group: 'Group 2' },
43
- { label: 'Option 8', value: 'option8', group: 'Group 2' },
44
- { label: 'Option 1', value: 'option1' },
45
- { label: 'Option 2', value: 'option2' },
46
- { label: 'Option 3', value: 'option3' },
47
- { label: 'Option 4', value: 'option4' },
48
- { label: 'Option 9', value: 'option9' },
49
- ],
50
- onSelectionChange: (value: string[]) => { console.log(value); },
518
+ const InsideFormFieldTemplate = () => (
519
+ <div style={{ maxWidth: '320px' }}>
520
+ <FormField
521
+ label="Year group"
522
+ id="year-group-field"
523
+ inputType="selectDropdown"
524
+ inputProps={{
525
+ placeholder: 'Select a year group',
526
+ options: yearGroupOptions,
527
+ onSelectionChange: values => console.log(values),
528
+ }}
529
+ />
530
+ </div>
531
+ );
532
+
533
+ const InsideFormFieldWithErrorTemplate = () => (
534
+ <div style={{ maxWidth: '320px' }}>
535
+ <FormField
536
+ label="Year group"
537
+ id="year-group-error-field"
538
+ inputType="selectDropdown"
539
+ errorText="Please select a year group"
540
+ inputProps={{
541
+ placeholder: 'Select a year group',
542
+ options: yearGroupOptions,
543
+ onSelectionChange: values => console.log(values),
544
+ }}
545
+ />
546
+ </div>
547
+ );
548
+
549
+ // ---------------------------------------------------------------------------
550
+ // Stories
551
+ // ---------------------------------------------------------------------------
552
+
553
+ export const Default: Story = withDescription(
554
+ {
555
+ args: {
556
+ options: yearGroupOptions,
557
+ placeholder: 'Select a year group',
558
+ multiple: false,
559
+ disabled: false,
560
+ },
561
+ render: args => (
562
+ <div style={{ maxWidth: '320px' }}>
563
+ <SelectDropdown {...args} />
564
+ </div>
565
+ ),
51
566
  },
52
- };
567
+ 'The interactive canvas — every prop is wired to the Controls panel above. Use `options`, `placeholder`, `multiple`, and `disabled` to explore the full range of configurations.',
568
+ );
569
+
570
+ export const WithInitialSelection: Story = withDescription(
571
+ {
572
+ render: WithInitialSelectionTemplate,
573
+ parameters: {
574
+ controls: { disable: true },
575
+ docs: {
576
+ source: {
577
+ language: 'tsx',
578
+ code: `
579
+ import { SelectDropdown } from '@arbor-education/design-system.components';
53
580
 
54
- export const DefaultMultiSelect = {
55
- args: {
56
- title: 'titleValue',
57
- options: [
58
- { label: 'Option 1', value: 'option1' },
59
- { label: 'Option 2', value: 'option2' },
60
- { label: 'Option 3', value: 'option3' },
61
- ],
62
- multiple: true,
63
- onSelectionChange: (value: string[]) => { console.log(value); },
581
+ function WithInitialSelectionExample() {
582
+ return (
583
+ <SelectDropdown
584
+ placeholder="Select a year group"
585
+ initialSelectedValues={['year9']}
586
+ options={[
587
+ { label: 'Year 7', value: 'year7' },
588
+ { label: 'Year 8', value: 'year8' },
589
+ { label: 'Year 9', value: 'year9' },
590
+ { label: 'Year 10', value: 'year10' },
591
+ { label: 'Year 11', value: 'year11' },
592
+ ]}
593
+ onSelectionChange={(values) => console.log(values)}
594
+ />
595
+ );
596
+ }
597
+ export default WithInitialSelectionExample;
598
+ `.trim(),
599
+ },
600
+ },
601
+ },
64
602
  },
65
- };
603
+ 'Uncontrolled mode with a pre-selected value via `initialSelectedValues`. The component manages its own state after mount — no `selectedValues` prop is needed. Use this when you only need to set a default and let the user take over.',
604
+ );
66
605
 
67
- export const Placeholder = {
68
- args: {
69
- placeholder: 'placeholder',
70
- title: 'titleValue',
71
- options: [
72
- { label: 'Option 1', value: 'option1' },
73
- { label: 'Option 2', value: 'option2' },
74
- { label: 'Option 3', value: 'option3' },
75
- ],
76
- onSelectionChange: (value: string[]) => { console.log(value); },
606
+ export const MultiSelect: Story = withDescription(
607
+ {
608
+ render: MultiSelectTemplate,
609
+ parameters: {
610
+ controls: { disable: true },
611
+ docs: {
612
+ source: {
613
+ language: 'tsx',
614
+ code: `
615
+ import { SelectDropdown } from '@arbor-education/design-system.components';
616
+
617
+ function MultiSelectExample() {
618
+ return (
619
+ <SelectDropdown
620
+ placeholder="Select subjects"
621
+ multiple
622
+ options={[
623
+ { label: 'Mathematics', value: 'maths' },
624
+ { label: 'English', value: 'english' },
625
+ { label: 'Science', value: 'science' },
626
+ { label: 'History', value: 'history' },
627
+ { label: 'Geography', value: 'geography' },
628
+ ]}
629
+ onSelectionChange={(values) => console.log(values)}
630
+ />
631
+ );
632
+ }
633
+ export default MultiSelectExample;
634
+ `.trim(),
635
+ },
636
+ },
637
+ },
77
638
  },
78
- };
639
+ '`multiple={true}` allows selecting more than one item. The dropdown stays open after each selection so users can make multiple choices without re-opening. Items toggle on and off.',
640
+ );
79
641
 
80
- export const EmptyPlaceHolder = {
81
- args: {
82
- placeholder: '',
83
- options: [
84
- { label: 'Option 1', value: 'option1' },
85
- { label: 'Option 2', value: 'option2' },
86
- { label: 'Option 3', value: 'option3' },
87
- ],
88
- disabled: false,
89
- onSelectionChange: (value: string[]) => { console.log(value); },
642
+ export const MultiSelectWithCount: Story = withDescription(
643
+ {
644
+ render: MultiSelectWithCountTemplate,
645
+ parameters: {
646
+ controls: { disable: true },
647
+ docs: {
648
+ source: {
649
+ language: 'tsx',
650
+ code: `
651
+ import { SelectDropdown } from '@arbor-education/design-system.components';
652
+
653
+ function MultiSelectWithCountExample() {
654
+ return (
655
+ // No placeholder prop — the trigger shows "Select (3)" when items are selected.
656
+ // Setting a placeholder would suppress this count display.
657
+ <SelectDropdown
658
+ multiple
659
+ initialSelectedValues={['maths', 'english', 'science']}
660
+ options={[
661
+ { label: 'Mathematics', value: 'maths' },
662
+ { label: 'English', value: 'english' },
663
+ { label: 'Science', value: 'science' },
664
+ { label: 'History', value: 'history' },
665
+ ]}
666
+ onSelectionChange={(values) => console.log(values)}
667
+ />
668
+ );
669
+ }
670
+ export default MultiSelectWithCountExample;
671
+ `.trim(),
672
+ },
673
+ },
674
+ },
90
675
  },
91
- };
676
+ [
677
+ 'When `multiple` is true and **no `placeholder` prop is set**, the trigger shows `"Select (3)"` when three items are selected.',
678
+ 'Setting a `placeholder` suppresses this count — the trigger shows your placeholder text instead.',
679
+ 'Choose deliberately based on whether you want users to see how many items are selected.',
680
+ ].join(' '),
681
+ );
92
682
 
93
- export const WithError = {
94
- args: {
95
- title: 'titleValue',
96
- options: [
97
- { label: 'Option 1', value: 'option1' },
98
- { label: 'Option 2', value: 'option2' },
99
- { label: 'Option 3', value: 'option3' },
100
- ],
101
- disabled: false,
102
- errorText: 'This field is required',
103
- onSelectionChange: (value: string[]) => { console.log(value); },
683
+ export const Disabled: Story = withDescription(
684
+ {
685
+ render: DisabledTemplate,
686
+ parameters: {
687
+ controls: { disable: true },
688
+ docs: {
689
+ source: {
690
+ language: 'tsx',
691
+ code: `
692
+ import { SelectDropdown } from '@arbor-education/design-system.components';
693
+
694
+ function DisabledExample() {
695
+ return (
696
+ <SelectDropdown
697
+ placeholder="Select a year group"
698
+ disabled
699
+ options={[
700
+ { label: 'Year 7', value: 'year7' },
701
+ { label: 'Year 8', value: 'year8' },
702
+ { label: 'Year 9', value: 'year9' },
703
+ ]}
704
+ onSelectionChange={(values) => console.log(values)}
705
+ />
706
+ );
707
+ }
708
+ export default DisabledExample;
709
+ `.trim(),
710
+ },
711
+ },
712
+ },
104
713
  },
105
- };
714
+ 'The `disabled` prop prevents the trigger from opening. Use when the field is not applicable in the current context — for example, a dependent field that requires another field to be filled first.',
715
+ );
106
716
 
107
- export const Disabled = {
108
- args: {
109
- title: 'titleValue',
110
- options: [
111
- { label: 'Option 1', value: 'option1' },
112
- { label: 'Option 2', value: 'option2' },
113
- { label: 'Option 3', value: 'option3' },
114
- ],
115
- disabled: true,
116
- onSelectionChange: (value: string[]) => { console.log(value); },
717
+ export const WithError: Story = withDescription(
718
+ {
719
+ render: WithErrorTemplate,
720
+ parameters: {
721
+ controls: { disable: true },
722
+ docs: {
723
+ source: {
724
+ language: 'tsx',
725
+ code: `
726
+ import { SelectDropdown } from '@arbor-education/design-system.components';
727
+
728
+ function WithErrorExample() {
729
+ return (
730
+ <div>
731
+ <SelectDropdown
732
+ placeholder="Select a year group"
733
+ hasError
734
+ aria-invalid
735
+ aria-describedBy="year-group-error"
736
+ options={[
737
+ { label: 'Year 7', value: 'year7' },
738
+ { label: 'Year 8', value: 'year8' },
739
+ { label: 'Year 9', value: 'year9' },
740
+ ]}
741
+ onSelectionChange={(values) => console.log(values)}
742
+ />
743
+ <span id="year-group-error">Please select a year group</span>
744
+ </div>
745
+ );
746
+ }
747
+ export default WithErrorExample;
748
+ `.trim(),
749
+ },
750
+ },
751
+ },
117
752
  },
118
- };
753
+ [
754
+ '`hasError` applies error styling to the trigger. `aria-invalid` tells screen readers the field is invalid.',
755
+ '`aria-describedBy` links to the error message element by ID.',
756
+ '**All three should be used together for an accessible error state.**',
757
+ 'When using `FormField`, these are wired automatically via `errorText` — prefer the `InsideFormFieldWithError` pattern.',
758
+ ].join(' '),
759
+ );
760
+
761
+ export const WithIcons: Story = withDescription(
762
+ {
763
+ render: WithIconsTemplate,
764
+ parameters: {
765
+ controls: { disable: true },
766
+ docs: {
767
+ source: {
768
+ language: 'tsx',
769
+ code: `
770
+ import { SelectDropdown } from '@arbor-education/design-system.components';
119
771
 
120
- export const WithGroups = {
121
- args: {
122
- title: 'titleValue',
123
- options: [
124
- { label: 'Option 1', value: 'option1', group: 'Group 1' },
125
- { label: 'Option 2', value: 'option2', group: 'Group 1' },
126
- { label: 'Option 4', value: 'option4', group: 'Group 2' },
127
- { label: 'Option 5', value: 'option5', group: 'Group 2' },
128
- { label: 'Option 3', value: 'option3' },
129
- { label: 'Option 6', value: 'option6' },
130
- { label: 'Option 7', value: 'option7' },
131
- ],
132
- onSelectionChange: (value: string[]) => { console.log(value); },
772
+ function WithIconsExample() {
773
+ return (
774
+ <SelectDropdown
775
+ placeholder="Select a subject"
776
+ options={[
777
+ { label: 'Mathematics', value: 'maths', icon: 'chart-column-increasing' },
778
+ { label: 'English', value: 'english', icon: 'book-open' },
779
+ { label: 'Science', value: 'science', icon: 'circle-check' },
780
+ { label: 'History', value: 'history', icon: 'clock-3' },
781
+ { label: 'Geography', value: 'geography', icon: 'flag' },
782
+ ]}
783
+ onSelectionChange={(values) => console.log(values)}
784
+ />
785
+ );
786
+ }
787
+ export default WithIconsExample;
788
+ `.trim(),
789
+ },
790
+ },
791
+ },
133
792
  },
134
- };
793
+ 'Options can include an `icon` prop to display an icon to the left of the label. Use icon names from the Arbor Icon component.',
794
+ );
795
+
796
+ export const WithItemHeaders: Story = withDescription(
797
+ {
798
+ render: WithItemHeadersTemplate,
799
+ parameters: {
800
+ controls: { disable: true },
801
+ docs: {
802
+ source: {
803
+ language: 'tsx',
804
+ code: `
805
+ import { SelectDropdown } from '@arbor-education/design-system.components';
135
806
 
136
- export const MultilineItems = {
137
- args: {
138
- title: 'titleValue',
139
- options: [
140
- { label: 'Option 1', value: 'option1', header: 'header1' },
141
- { label: 'Option 2', value: 'option2', header: 'header2' },
142
- { label: 'Option 3', value: 'option3', header: 'header3' },
143
- { label: 'Option 4', value: 'option4', header: 'header4' },
144
- ],
145
- onSelectionChange: (value: string[]) => { console.log(value); },
807
+ function WithItemHeadersExample() {
808
+ return (
809
+ <SelectDropdown
810
+ placeholder="Select absence reason"
811
+ options={[
812
+ { label: 'Authorised absence', value: 'authorised', header: 'Planned' },
813
+ { label: 'Holiday', value: 'holiday', header: 'Planned' },
814
+ { label: 'Medical appointment', value: 'medical', header: 'Health' },
815
+ { label: 'Illness', value: 'illness', header: 'Health' },
816
+ { label: 'Unauthorised absence', value: 'unauthorised', header: 'Unplanned' },
817
+ ]}
818
+ onSelectionChange={(values) => console.log(values)}
819
+ />
820
+ );
821
+ }
822
+ export default WithItemHeadersExample;
823
+ `.trim(),
824
+ },
825
+ },
826
+ },
146
827
  },
147
- };
828
+ 'The `header` prop on an option renders a small label above the option text, creating a two-line item layout. Use this to add context to complex options — for example, showing the category of an absence type alongside its name.',
829
+ );
830
+
831
+ export const WithGroups: Story = withDescription(
832
+ {
833
+ render: WithGroupsTemplate,
834
+ parameters: {
835
+ controls: { disable: true },
836
+ docs: {
837
+ source: {
838
+ language: 'tsx',
839
+ code: `
840
+ import { SelectDropdown } from '@arbor-education/design-system.components';
148
841
 
149
- export const MultilineItemsGrouped = {
150
- args: {
151
- title: 'titleValue',
152
- options: [
153
- { label: 'Option 1', value: 'option1', header: 'header1' },
154
- { label: 'Option 2', value: 'option2', header: 'header2' },
155
- { label: 'Option 3', value: 'option3', header: 'header3' },
156
- { label: 'Option 4', value: 'option4', header: 'header4' },
157
- { label: 'Option 5', value: 'option5', header: 'header5', group: 'Group 1' },
158
- { label: 'Option 6', value: 'option6', header: 'header6', group: 'Group 1' },
159
- { label: 'Option 7', value: 'option7', header: 'header7', group: 'Group 2' },
160
- { label: 'Option 8', value: 'option8', header: 'header8', group: 'Group 2' },
161
- { label: 'Option 9', value: 'option9', header: 'header9' },
162
- ],
163
- onSelectionChange: (value: string[]) => { console.log(value); },
842
+ function WithGroupsExample() {
843
+ return (
844
+ <SelectDropdown
845
+ placeholder="Select a year group"
846
+ options={[
847
+ { label: 'Year 7', value: 'year7', group: 'Lower school' },
848
+ { label: 'Year 8', value: 'year8', group: 'Lower school' },
849
+ { label: 'Year 9', value: 'year9', group: 'Lower school' },
850
+ { label: 'Year 10', value: 'year10', group: 'Upper school' },
851
+ { label: 'Year 11', value: 'year11', group: 'Upper school' },
852
+ { label: 'Year 12', value: 'year12', group: 'Sixth form' },
853
+ { label: 'Year 13', value: 'year13', group: 'Sixth form' },
854
+ ]}
855
+ onSelectionChange={(values) => console.log(values)}
856
+ />
857
+ );
858
+ }
859
+ export default WithGroupsExample;
860
+ `.trim(),
861
+ },
862
+ },
863
+ },
164
864
  },
165
- };
865
+ 'Adding a `group` string to options automatically clusters them under section headings. Group headers are only rendered when two or more distinct group values exist. Items without a `group` are rendered ungrouped.',
866
+ );
867
+
868
+ export const WithGroupsMultiSelect: Story = withDescription(
869
+ {
870
+ render: WithGroupsMultiSelectTemplate,
871
+ parameters: {
872
+ controls: { disable: true },
873
+ docs: {
874
+ source: {
875
+ language: 'tsx',
876
+ code: `
877
+ import { SelectDropdown } from '@arbor-education/design-system.components';
166
878
 
167
- export const MultilineItemsGroupedMultiSelect = {
168
- args: {
169
- title: 'titleValue',
170
- multiple: true,
171
- options: [
172
- { label: 'Option 5', value: 'option5', header: 'header5', group: 'Group 1' },
173
- { label: 'Option 6', value: 'option6', header: 'header6', group: 'Group 1' },
174
- { label: 'Option 7', value: 'option7', header: 'header7', group: 'Group 2' },
175
- { label: 'Option 8', value: 'option8', header: 'header8', group: 'Group 2' },
176
- { label: 'Option 1', value: 'option1', header: 'header1' },
177
- { label: 'Option 2', value: 'option2', header: 'header2' },
178
- { label: 'Option 3', value: 'option3', header: 'header3' },
179
- { label: 'Option 4', value: 'option4', header: 'header4' },
180
- { label: 'Option 9', value: 'option9', header: 'header9' },
181
- ],
182
- onSelectionChange: (value: string[]) => { console.log(value); },
879
+ function WithGroupsMultiSelectExample() {
880
+ return (
881
+ <SelectDropdown
882
+ placeholder="Select year groups"
883
+ multiple
884
+ options={[
885
+ { label: 'Year 7', value: 'year7', group: 'Lower school' },
886
+ { label: 'Year 8', value: 'year8', group: 'Lower school' },
887
+ { label: 'Year 9', value: 'year9', group: 'Lower school' },
888
+ { label: 'Year 10', value: 'year10', group: 'Upper school' },
889
+ { label: 'Year 11', value: 'year11', group: 'Upper school' },
890
+ ]}
891
+ onSelectionChange={(values) => console.log(values)}
892
+ />
893
+ );
894
+ }
895
+ export default WithGroupsMultiSelectExample;
896
+ `.trim(),
897
+ },
898
+ },
899
+ },
183
900
  },
184
- };
901
+ 'Grouped options combined with multi-select. The dropdown stays open while users make multiple selections across groups.',
902
+ );
903
+
904
+ export const AlwaysShowPlaceholder: Story = withDescription(
905
+ {
906
+ render: AlwaysShowPlaceholderTemplate,
907
+ parameters: {
908
+ controls: { disable: true },
909
+ docs: {
910
+ source: {
911
+ language: 'tsx',
912
+ code: `
913
+ import { SelectDropdown } from '@arbor-education/design-system.components';
185
914
 
186
- export const ControlledOpen = {
187
- args: {
188
- title: 'titleValue',
189
- options: [
190
- { label: 'Option 1', value: 'option1' },
191
- { label: 'Option 2', value: 'option2' },
192
- { label: 'Option 3', value: 'option3' },
193
- ],
194
- open: true,
195
- onOpenChange: (open: boolean) => { console.log('open changed:', open); },
196
- onSelectionChange: (value: string[]) => { console.log(value); },
915
+ function AlwaysShowPlaceholderExample() {
916
+ return (
917
+ <SelectDropdown
918
+ placeholder="Add filter"
919
+ alwaysShowPlaceholder
920
+ multiple
921
+ options={[
922
+ { label: 'Mathematics', value: 'maths' },
923
+ { label: 'English', value: 'english' },
924
+ { label: 'Science', value: 'science' },
925
+ ]}
926
+ onSelectionChange={(values) => console.log(values)}
927
+ />
928
+ );
929
+ }
930
+ export default AlwaysShowPlaceholderExample;
931
+ `.trim(),
932
+ },
933
+ },
934
+ },
197
935
  },
198
- };
936
+ '`alwaysShowPlaceholder` keeps the trigger label fixed regardless of selection state. Use for action-style dropdowns (e.g. "Add filter") where the trigger label should not change to reflect the selected value.',
937
+ );
199
938
 
200
- export default meta;
939
+ export const ControlledSingleSelect: Story = withDescription(
940
+ {
941
+ render: ControlledSingleSelectTemplate,
942
+ parameters: {
943
+ controls: { disable: true },
944
+ docs: {
945
+ source: {
946
+ language: 'tsx',
947
+ code: `
948
+ import { useState } from 'react';
949
+ import { SelectDropdown } from '@arbor-education/design-system.components';
950
+
951
+ function ControlledSingleSelectExample() {
952
+ const [selected, setSelected] = useState<string[]>([]);
953
+
954
+ return (
955
+ <SelectDropdown
956
+ placeholder="Select a subject"
957
+ selectedValues={selected}
958
+ onSelectionChange={setSelected}
959
+ options={[
960
+ { label: 'Mathematics', value: 'maths' },
961
+ { label: 'English', value: 'english' },
962
+ { label: 'Science', value: 'science' },
963
+ { label: 'History', value: 'history' },
964
+ ]}
965
+ />
966
+ );
967
+ }
968
+ export default ControlledSingleSelectExample;
969
+ `.trim(),
970
+ },
971
+ },
972
+ },
973
+ },
974
+ [
975
+ 'Fully controlled single-select. Providing `selectedValues` activates controlled mode — the component stops managing its own state.',
976
+ 'You **must** update `selectedValues` in response to `onSelectionChange`, otherwise the dropdown will appear frozen.',
977
+ 'Use this pattern when the selected value needs to be derived from, or synchronised with, external state.',
978
+ ].join(' '),
979
+ );
980
+
981
+ export const ControlledOpen: Story = withDescription(
982
+ {
983
+ render: ControlledOpenTemplate,
984
+ parameters: {
985
+ controls: { disable: true },
986
+ docs: {
987
+ source: {
988
+ language: 'tsx',
989
+ code: `
990
+ import { useState } from 'react';
991
+ import { SelectDropdown } from '@arbor-education/design-system.components';
992
+
993
+ function ControlledOpenExample() {
994
+ const [isOpen, setIsOpen] = useState(false);
995
+
996
+ return (
997
+ <div>
998
+ <button type="button" onClick={() => setIsOpen(true)}>Open dropdown externally</button>
999
+ <SelectDropdown
1000
+ placeholder="Select a subject"
1001
+ open={isOpen}
1002
+ onOpenChange={setIsOpen}
1003
+ options={[
1004
+ { label: 'Mathematics', value: 'maths' },
1005
+ { label: 'English', value: 'english' },
1006
+ { label: 'Science', value: 'science' },
1007
+ ]}
1008
+ onSelectionChange={(values) => {
1009
+ console.log(values);
1010
+ setIsOpen(false);
1011
+ }}
1012
+ />
1013
+ </div>
1014
+ );
1015
+ }
1016
+ export default ControlledOpenExample;
1017
+ `.trim(),
1018
+ },
1019
+ },
1020
+ },
1021
+ },
1022
+ '`open` and `onOpenChange` let you control the dropdown visibility externally. Use when you need to open the dropdown programmatically — from a toolbar button, a keyboard shortcut, or after completing a validation step.',
1023
+ );
1024
+
1025
+ export const InsideFormField: Story = withDescription(
1026
+ {
1027
+ render: InsideFormFieldTemplate,
1028
+ parameters: {
1029
+ controls: { disable: true },
1030
+ docs: {
1031
+ source: {
1032
+ language: 'tsx',
1033
+ code: `
1034
+ import { FormField } from '@arbor-education/design-system.components';
1035
+
1036
+ function InsideFormFieldExample() {
1037
+ return (
1038
+ <FormField
1039
+ label="Year group"
1040
+ id="year-group-field"
1041
+ inputType="selectDropdown"
1042
+ inputProps={{
1043
+ placeholder: 'Select a year group',
1044
+ options: [
1045
+ { label: 'Year 7', value: 'year7' },
1046
+ { label: 'Year 8', value: 'year8' },
1047
+ { label: 'Year 9', value: 'year9' },
1048
+ { label: 'Year 10', value: 'year10' },
1049
+ { label: 'Year 11', value: 'year11' },
1050
+ ],
1051
+ onSelectionChange: (values) => console.log(values),
1052
+ }}
1053
+ />
1054
+ );
1055
+ }
1056
+ export default InsideFormFieldExample;
1057
+ `.trim(),
1058
+ },
1059
+ },
1060
+ },
1061
+ },
1062
+ [
1063
+ 'The recommended way to use SelectDropdown in forms.',
1064
+ '`FormField` with `inputType="selectDropdown"` provides an accessible `<label>` linked to the trigger,',
1065
+ 'and automatically wires `aria-invalid` and `aria-describedby` when `errorText` is set.',
1066
+ 'Pass SelectDropdown props via `inputProps`.',
1067
+ ].join(' '),
1068
+ );
1069
+
1070
+ export const InsideFormFieldWithError: Story = withDescription(
1071
+ {
1072
+ render: InsideFormFieldWithErrorTemplate,
1073
+ parameters: {
1074
+ controls: { disable: true },
1075
+ docs: {
1076
+ source: {
1077
+ language: 'tsx',
1078
+ code: `
1079
+ import { FormField } from '@arbor-education/design-system.components';
1080
+
1081
+ function InsideFormFieldWithErrorExample() {
1082
+ return (
1083
+ <FormField
1084
+ label="Year group"
1085
+ id="year-group-error-field"
1086
+ inputType="selectDropdown"
1087
+ errorText="Please select a year group"
1088
+ inputProps={{
1089
+ placeholder: 'Select a year group',
1090
+ options: [
1091
+ { label: 'Year 7', value: 'year7' },
1092
+ { label: 'Year 8', value: 'year8' },
1093
+ { label: 'Year 9', value: 'year9' },
1094
+ ],
1095
+ onSelectionChange: (values) => console.log(values),
1096
+ }}
1097
+ />
1098
+ );
1099
+ }
1100
+ export default InsideFormFieldWithErrorExample;
1101
+ `.trim(),
1102
+ },
1103
+ },
1104
+ },
1105
+ },
1106
+ [
1107
+ 'Error state via `FormField`. Setting `errorText` on `FormField` automatically passes `hasError`,',
1108
+ '`aria-invalid`, and `aria-describedby` to the SelectDropdown, and renders the error message below the field.',
1109
+ 'This is the correct and recommended pattern for accessible form validation.',
1110
+ ].join(' '),
1111
+ );