@arbor-education/design-system.components 0.15.0 → 0.16.1

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 (304) hide show
  1. package/.gather/skills/write-stories/SKILL.md +207 -271
  2. package/.storybook/preview.ts +5 -0
  3. package/CHANGELOG.md +23 -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/radio/RadioButtonGroup.d.ts +6 -2
  60. package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts.map +1 -1
  61. package/dist/components/formField/inputs/radio/RadioButtonGroup.js.map +1 -1
  62. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
  63. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +61 -49
  64. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
  65. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts +188 -166
  66. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts.map +1 -1
  67. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js +821 -160
  68. package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js.map +1 -1
  69. package/dist/components/formField/inputs/time/TimeInput.stories.d.ts +176 -22
  70. package/dist/components/formField/inputs/time/TimeInput.stories.d.ts.map +1 -1
  71. package/dist/components/formField/inputs/time/TimeInput.stories.js +851 -92
  72. package/dist/components/formField/inputs/time/TimeInput.stories.js.map +1 -1
  73. package/dist/components/formField/label/Label.stories.d.ts +54 -5
  74. package/dist/components/formField/label/Label.stories.d.ts.map +1 -1
  75. package/dist/components/formField/label/Label.stories.js +238 -4
  76. package/dist/components/formField/label/Label.stories.js.map +1 -1
  77. package/dist/components/icoText/IcoText.stories.d.ts +32 -6
  78. package/dist/components/icoText/IcoText.stories.d.ts.map +1 -1
  79. package/dist/components/icoText/IcoText.stories.js +309 -14
  80. package/dist/components/icoText/IcoText.stories.js.map +1 -1
  81. package/dist/components/kpiCard/KPICard.stories.d.ts +100 -2
  82. package/dist/components/kpiCard/KPICard.stories.d.ts.map +1 -1
  83. package/dist/components/kpiCard/KPICard.stories.js +354 -10
  84. package/dist/components/kpiCard/KPICard.stories.js.map +1 -1
  85. package/dist/components/kvpList/KVPList.stories.d.ts +57 -4
  86. package/dist/components/kvpList/KVPList.stories.d.ts.map +1 -1
  87. package/dist/components/kvpList/KVPList.stories.js +403 -10
  88. package/dist/components/kvpList/KVPList.stories.js.map +1 -1
  89. package/dist/components/modal/Modal.stories.d.ts +113 -9
  90. package/dist/components/modal/Modal.stories.d.ts.map +1 -1
  91. package/dist/components/modal/Modal.stories.js +633 -13
  92. package/dist/components/modal/Modal.stories.js.map +1 -1
  93. package/dist/components/modal/modalManager/ModalManager.stories.d.ts +34 -10
  94. package/dist/components/modal/modalManager/ModalManager.stories.d.ts.map +1 -1
  95. package/dist/components/modal/modalManager/ModalManager.stories.js +463 -85
  96. package/dist/components/modal/modalManager/ModalManager.stories.js.map +1 -1
  97. package/dist/components/pill/Pill.d.ts.map +1 -1
  98. package/dist/components/pill/Pill.js +1 -1
  99. package/dist/components/pill/Pill.js.map +1 -1
  100. package/dist/components/pill/Pill.stories.d.ts.map +1 -1
  101. package/dist/components/pill/Pill.stories.js +11 -13
  102. package/dist/components/pill/Pill.stories.js.map +1 -1
  103. package/dist/components/row/Row.stories.d.ts +1 -2
  104. package/dist/components/row/Row.stories.d.ts.map +1 -1
  105. package/dist/components/row/Row.stories.js +360 -50
  106. package/dist/components/row/Row.stories.js.map +1 -1
  107. package/dist/components/searchBar/SearchBar.stories.d.ts +52 -4
  108. package/dist/components/searchBar/SearchBar.stories.d.ts.map +1 -1
  109. package/dist/components/searchBar/SearchBar.stories.js +428 -36
  110. package/dist/components/searchBar/SearchBar.stories.js.map +1 -1
  111. package/dist/components/section/Section.stories.d.ts +11 -41
  112. package/dist/components/section/Section.stories.d.ts.map +1 -1
  113. package/dist/components/section/Section.stories.js +494 -56
  114. package/dist/components/section/Section.stories.js.map +1 -1
  115. package/dist/components/singleUser/SingleUser.stories.d.ts +5 -4
  116. package/dist/components/singleUser/SingleUser.stories.d.ts.map +1 -1
  117. package/dist/components/singleUser/SingleUser.stories.js +303 -31
  118. package/dist/components/singleUser/SingleUser.stories.js.map +1 -1
  119. package/dist/components/slideoverManager/SlideoverManager.stories.d.ts +32 -11
  120. package/dist/components/slideoverManager/SlideoverManager.stories.d.ts.map +1 -1
  121. package/dist/components/slideoverManager/SlideoverManager.stories.js +380 -84
  122. package/dist/components/slideoverManager/SlideoverManager.stories.js.map +1 -1
  123. package/dist/components/table/DSDefaultColDef.d.ts.map +1 -1
  124. package/dist/components/table/DSDefaultColDef.js +4 -3
  125. package/dist/components/table/DSDefaultColDef.js.map +1 -1
  126. package/dist/components/table/Table.d.ts +6 -1
  127. package/dist/components/table/Table.d.ts.map +1 -1
  128. package/dist/components/table/Table.js +8 -3
  129. package/dist/components/table/Table.js.map +1 -1
  130. package/dist/components/table/Table.stories.d.ts +3 -0
  131. package/dist/components/table/Table.stories.d.ts.map +1 -1
  132. package/dist/components/table/Table.stories.js +384 -5
  133. package/dist/components/table/Table.stories.js.map +1 -1
  134. package/dist/components/table/Table.test.js +30 -0
  135. package/dist/components/table/Table.test.js.map +1 -1
  136. package/dist/components/table/TableFooter.stories.d.ts +49 -0
  137. package/dist/components/table/TableFooter.stories.d.ts.map +1 -0
  138. package/dist/components/table/TableFooter.stories.js +137 -0
  139. package/dist/components/table/TableFooter.stories.js.map +1 -0
  140. package/dist/components/table/TableHeader.stories.d.ts +93 -0
  141. package/dist/components/table/TableHeader.stories.d.ts.map +1 -0
  142. package/dist/components/table/TableHeader.stories.js +176 -0
  143. package/dist/components/table/TableHeader.stories.js.map +1 -0
  144. package/dist/components/table/cellEditors/DateCellEditor.stories.d.ts +44 -0
  145. package/dist/components/table/cellEditors/DateCellEditor.stories.d.ts.map +1 -0
  146. package/dist/components/table/cellEditors/DateCellEditor.stories.js +186 -0
  147. package/dist/components/table/cellEditors/DateCellEditor.stories.js.map +1 -0
  148. package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.d.ts +40 -0
  149. package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.d.ts.map +1 -0
  150. package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.js +209 -0
  151. package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.js.map +1 -0
  152. package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.d.ts +48 -0
  153. package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.d.ts.map +1 -0
  154. package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.js +244 -0
  155. package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.js.map +1 -0
  156. package/dist/components/table/cellRenderers/CheckboxCellRenderer.d.ts.map +1 -1
  157. package/dist/components/table/cellRenderers/CheckboxCellRenderer.js +3 -1
  158. package/dist/components/table/cellRenderers/CheckboxCellRenderer.js.map +1 -1
  159. package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.d.ts +64 -0
  160. package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.d.ts.map +1 -0
  161. package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.js +241 -0
  162. package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.js.map +1 -0
  163. package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.d.ts +55 -0
  164. package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.d.ts.map +1 -0
  165. package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.js +245 -0
  166. package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.js.map +1 -0
  167. package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.d.ts +67 -0
  168. package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.d.ts.map +1 -0
  169. package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.js +221 -0
  170. package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.js.map +1 -0
  171. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.d.ts +75 -0
  172. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.d.ts.map +1 -0
  173. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.js +270 -0
  174. package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.js.map +1 -0
  175. package/dist/components/table/columnFilters/BooleanFilter.stories.d.ts +57 -0
  176. package/dist/components/table/columnFilters/BooleanFilter.stories.d.ts.map +1 -0
  177. package/dist/components/table/columnFilters/BooleanFilter.stories.js +198 -0
  178. package/dist/components/table/columnFilters/BooleanFilter.stories.js.map +1 -0
  179. package/dist/components/table/columnFilters/TimeFilter.stories.d.ts +58 -0
  180. package/dist/components/table/columnFilters/TimeFilter.stories.d.ts.map +1 -0
  181. package/dist/components/table/columnFilters/TimeFilter.stories.js +207 -0
  182. package/dist/components/table/columnFilters/TimeFilter.stories.js.map +1 -0
  183. package/dist/components/table/pagination/PaginationPanel.stories.d.ts +113 -0
  184. package/dist/components/table/pagination/PaginationPanel.stories.d.ts.map +1 -0
  185. package/dist/components/table/pagination/PaginationPanel.stories.js +272 -0
  186. package/dist/components/table/pagination/PaginationPanel.stories.js.map +1 -0
  187. package/dist/components/table/tableControls/HideColumnsDropdown.d.ts.map +1 -1
  188. package/dist/components/table/tableControls/HideColumnsDropdown.js +9 -3
  189. package/dist/components/table/tableControls/HideColumnsDropdown.js.map +1 -1
  190. package/dist/components/table/tableControls/TableControls.stories.d.ts +151 -0
  191. package/dist/components/table/tableControls/TableControls.stories.d.ts.map +1 -0
  192. package/dist/components/table/tableControls/TableControls.stories.js +356 -0
  193. package/dist/components/table/tableControls/TableControls.stories.js.map +1 -0
  194. package/dist/components/table/tableControls/TableSettingsDropdown.d.ts +27 -1
  195. package/dist/components/table/tableControls/TableSettingsDropdown.d.ts.map +1 -1
  196. package/dist/components/table/tableControls/TableSettingsDropdown.js +53 -26
  197. package/dist/components/table/tableControls/TableSettingsDropdown.js.map +1 -1
  198. package/dist/components/table/tableControls/TableSettingsDropdown.test.d.ts +2 -0
  199. package/dist/components/table/tableControls/TableSettingsDropdown.test.d.ts.map +1 -0
  200. package/dist/components/table/tableControls/TableSettingsDropdown.test.js +178 -0
  201. package/dist/components/table/tableControls/TableSettingsDropdown.test.js.map +1 -0
  202. package/dist/components/tabs/Tabs.stories.d.ts +22 -4
  203. package/dist/components/tabs/Tabs.stories.d.ts.map +1 -1
  204. package/dist/components/tabs/Tabs.stories.js +398 -22
  205. package/dist/components/tabs/Tabs.stories.js.map +1 -1
  206. package/dist/components/tabs/TabsItem.stories.d.ts +54 -1
  207. package/dist/components/tabs/TabsItem.stories.d.ts.map +1 -1
  208. package/dist/components/tabs/TabsItem.stories.js +61 -9
  209. package/dist/components/tabs/TabsItem.stories.js.map +1 -1
  210. package/dist/components/toast/Toast.stories.d.ts +103 -10
  211. package/dist/components/toast/Toast.stories.d.ts.map +1 -1
  212. package/dist/components/toast/Toast.stories.js +409 -47
  213. package/dist/components/toast/Toast.stories.js.map +1 -1
  214. package/dist/components/toggle/Toggle.stories.d.ts +61 -46
  215. package/dist/components/toggle/Toggle.stories.d.ts.map +1 -1
  216. package/dist/components/toggle/Toggle.stories.js +311 -122
  217. package/dist/components/toggle/Toggle.stories.js.map +1 -1
  218. package/dist/components/tooltip/Tooltip.stories.d.ts +78 -6
  219. package/dist/components/tooltip/Tooltip.stories.d.ts.map +1 -1
  220. package/dist/components/tooltip/Tooltip.stories.js +413 -7
  221. package/dist/components/tooltip/Tooltip.stories.js.map +1 -1
  222. package/dist/components/tooltip/TooltipWrapper.stories.d.ts +71 -7
  223. package/dist/components/tooltip/TooltipWrapper.stories.d.ts.map +1 -1
  224. package/dist/components/tooltip/TooltipWrapper.stories.js +238 -10
  225. package/dist/components/tooltip/TooltipWrapper.stories.js.map +1 -1
  226. package/dist/index.css +8 -0
  227. package/dist/index.css.map +1 -1
  228. package/dist/utils/PopupParentContext.stories.d.ts +17 -0
  229. package/dist/utils/PopupParentContext.stories.d.ts.map +1 -0
  230. package/dist/utils/PopupParentContext.stories.js +266 -0
  231. package/dist/utils/PopupParentContext.stories.js.map +1 -0
  232. package/dist/utils/getDefaultPopupParent.d.ts.map +1 -1
  233. package/dist/utils/getDefaultPopupParent.js +6 -0
  234. package/dist/utils/getDefaultPopupParent.js.map +1 -1
  235. package/package.json +1 -1
  236. package/src/components/articleCard/ArticleCard.stories.tsx +524 -111
  237. package/src/components/avatar/Avatar.stories.tsx +504 -59
  238. package/src/components/avatarGroup/AvatarGroup.stories.tsx +977 -175
  239. package/src/components/banner/Banner.stories.tsx +7 -3
  240. package/src/components/card/Card.stories.tsx +466 -36
  241. package/src/components/combobox/Combobox.stories.tsx +867 -260
  242. package/src/components/datePicker/DatePicker.stories.tsx +777 -60
  243. package/src/components/dateTimePicker/DateTimePicker.stories.tsx +910 -132
  244. package/src/components/editableText/EditableText.stories.tsx +567 -91
  245. package/src/components/formField/FormField.test.tsx +6 -0
  246. package/src/components/formField/FormField.tsx +5 -0
  247. package/src/components/formField/fieldset/Fieldset.stories.tsx +761 -51
  248. package/src/components/formField/inputs/checkbox/CheckboxGroup.tsx +1 -1
  249. package/src/components/formField/inputs/checkbox/CheckboxInput.tsx +1 -1
  250. package/src/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.tsx +504 -11
  251. package/src/components/formField/inputs/radio/RadioButtonGroup.tsx +17 -4
  252. package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +71 -59
  253. package/src/components/formField/inputs/selectDropdown/SelectDropdown.stories.tsx +1079 -168
  254. package/src/components/formField/inputs/time/TimeInput.stories.tsx +1140 -104
  255. package/src/components/formField/label/Label.stories.tsx +317 -8
  256. package/src/components/icoText/IcoText.stories.tsx +442 -31
  257. package/src/components/kpiCard/KPICard.stories.tsx +475 -30
  258. package/src/components/kvpList/KVPList.stories.tsx +593 -26
  259. package/src/components/modal/Modal.stories.tsx +963 -26
  260. package/src/components/modal/modalManager/ModalManager.stories.tsx +612 -454
  261. package/src/components/pill/Pill.stories.tsx +11 -13
  262. package/src/components/pill/Pill.tsx +1 -0
  263. package/src/components/row/Row.stories.tsx +474 -58
  264. package/src/components/searchBar/SearchBar.stories.tsx +570 -38
  265. package/src/components/section/Section.stories.tsx +723 -70
  266. package/src/components/singleUser/SingleUser.stories.tsx +393 -34
  267. package/src/components/slideoverManager/SlideoverManager.stories.tsx +572 -342
  268. package/src/components/table/DSDefaultColDef.ts +25 -5
  269. package/src/components/table/Table.stories.tsx +460 -5
  270. package/src/components/table/Table.test.tsx +53 -0
  271. package/src/components/table/Table.tsx +9 -2
  272. package/src/components/table/TableFooter.stories.tsx +196 -0
  273. package/src/components/table/TableHeader.stories.tsx +251 -0
  274. package/src/components/table/cellEditors/DateCellEditor.stories.tsx +245 -0
  275. package/src/components/table/cellRenderers/BooleanCellRenderer.stories.tsx +278 -0
  276. package/src/components/table/cellRenderers/ButtonCellRenderer.stories.tsx +333 -0
  277. package/src/components/table/cellRenderers/CheckboxCellRenderer.stories.tsx +337 -0
  278. package/src/components/table/cellRenderers/CheckboxCellRenderer.tsx +5 -1
  279. package/src/components/table/cellRenderers/DefaultCellRenderer.stories.tsx +342 -0
  280. package/src/components/table/cellRenderers/InlineTextCellRenderer.stories.tsx +292 -0
  281. package/src/components/table/cellRenderers/SelectDropdownCellRenderer.stories.tsx +369 -0
  282. package/src/components/table/columnFilters/BooleanFilter.stories.tsx +268 -0
  283. package/src/components/table/columnFilters/TimeFilter.stories.tsx +281 -0
  284. package/src/components/table/pagination/PaginationPanel.stories.tsx +327 -0
  285. package/src/components/table/tableControls/HideColumnsDropdown.tsx +11 -2
  286. package/src/components/table/tableControls/TableControls.stories.tsx +415 -0
  287. package/src/components/table/tableControls/TableSettingsDropdown.test.tsx +207 -0
  288. package/src/components/table/tableControls/TableSettingsDropdown.tsx +103 -39
  289. package/src/components/tabs/Tabs.stories.tsx +540 -60
  290. package/src/components/tabs/TabsItem.stories.tsx +82 -8
  291. package/src/components/toast/Toast.stories.tsx +539 -77
  292. package/src/components/toggle/Toggle.stories.tsx +371 -135
  293. package/src/components/tooltip/Tooltip.stories.tsx +606 -15
  294. package/src/components/tooltip/TooltipWrapper.stories.tsx +348 -12
  295. package/src/docs/Contributing.mdx +241 -0
  296. package/src/docs/UsingComponents.mdx +93 -0
  297. package/src/docs/Welcome.mdx +68 -0
  298. package/src/global.scss +7 -0
  299. package/src/utils/PopupParentContext.stories.tsx +367 -0
  300. package/src/utils/getDefaultPopupParent.ts +6 -0
  301. package/.ralph/storybook-upgrade/knowledge.md +0 -308
  302. package/.ralph/storybook-upgrade/prd.json +0 -777
  303. package/.ralph/storybook-upgrade/progress.md +0 -342
  304. package/src/components/table/TableWIP.mdx +0 -3
@@ -1,262 +1,763 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Controls, Heading as DocHeading, Markdown, Primary as DocPrimary, Stories, Subtitle, Title, } from '@storybook/addon-docs/blocks';
2
3
  import { useRef, useState } from 'react';
3
4
  import { Icon } from '../icon/Icon';
4
5
  import { comboboxAsyncSearchOptions, comboboxGroupedPeopleOptions, comboboxPeopleOptions, } from '../../mocks/comboboxStoryOptions';
5
6
  import { Combobox } from './Combobox';
7
+ // ─── Docs-page content ────────────────────────────────────────────────────────
8
+ const DESCRIPTION_INTRO = `
9
+ The \`Combobox\` component is a searchable, accessible select field that supports
10
+ single-select, multi-select, grouped options, async search, and item creation —
11
+ built on Radix UI Popover for reliable portal-based positioning.
12
+ `.trim();
13
+ const USAGE_GUIDANCE = `
14
+ ### When to use
15
+
16
+ - Selecting one or more items from a searchable list (teachers, pupils, subjects, year groups).
17
+ - Filtering a dataset where the user needs to type to narrow down many options.
18
+ - Allowing users to create new values on the fly (e.g. adding a new tag or subject name).
19
+ - Async-loaded lists where options are fetched from an API as the user types.
20
+
21
+ ---
22
+
23
+ ### When NOT to use
24
+
25
+ | Situation | Use instead |
26
+ |-----------|-------------|
27
+ | A short list (≤ 6 items) with no need to search | \`SelectDropdown\` |
28
+ | Free-text search that triggers a navigation or query action | \`SearchBar\` |
29
+ | A simple true/false toggle | \`Checkbox\` or \`Toggle\` |
30
+ | Picking a date or time | \`DatePicker\` / \`TimePicker\` |
31
+ `.trim();
32
+ const DEVELOPER_NOTES = `
33
+ > **Built on Radix UI Popover.** The dropdown renders into a portal — do NOT place
34
+ > the Combobox inside a container with \`overflow: hidden\` or \`position: relative\`
35
+ > that clips its stacking context. This will cause the dropdown to be cut off.
36
+
37
+ ---
38
+
39
+ ### \`value\` is always \`string[]\`
40
+
41
+ > **Critical:** Even in single-select mode, \`value\` and \`defaultValue\` are
42
+ > **always arrays**. This is the most common integration bug.
43
+
44
+ \`\`\`tsx
45
+ // ✅ Correct — value is string[]
46
+ <Combobox value={['alice-johnson']} onValueChange={(vals) => setVal(vals)} />
47
+
48
+ // ❌ Wrong — value must not be a plain string
49
+ <Combobox value="alice-johnson" />
50
+ \`\`\`
51
+
52
+ ---
53
+
54
+ ### Controlled vs uncontrolled
55
+
56
+ Use \`value\` + \`onValueChange\` for controlled mode. Use \`defaultValue\` for
57
+ uncontrolled initial selection. Do not mix both.
58
+
59
+ \`\`\`tsx
60
+ // Controlled
61
+ const [selected, setSelected] = useState<string[]>([]);
62
+ <Combobox value={selected} onValueChange={setSelected} options={options} />
63
+
64
+ // Uncontrolled with initial value
65
+ <Combobox defaultValue={['alice-johnson']} options={options} />
66
+ \`\`\`
67
+
68
+ ---
69
+
70
+ ### Async search
71
+
72
+ When you pass \`onSearch\`, **all client-side filtering is disabled** — you are
73
+ responsible for updating the \`options\` array yourself (e.g. from an API response).
74
+ The component does NOT debounce; add your own debounce inside \`onSearch\`.
75
+
76
+ \`\`\`tsx
77
+ <Combobox
78
+ options={results}
79
+ onSearch={async (query) => {
80
+ const data = await fetchStaff(query);
81
+ setResults(data);
82
+ }}
83
+ />
84
+ \`\`\`
85
+
86
+ ---
87
+
88
+ ### Keyboard navigation
89
+
90
+ | Key | Action |
91
+ |-----|--------|
92
+ | \`↓\` / \`↑\` | Move focus through options |
93
+ | \`Enter\` | Select the focused option |
94
+ | \`Escape\` | Close the dropdown |
95
+ | \`Backspace\` | Remove the last selected tag (multi-select) |
96
+ | \`Tab\` | Close the dropdown and advance focus |
97
+
98
+ ---
99
+
100
+ ### Grouped options
101
+
102
+ Options with the same \`group\` string are rendered under a shared group heading.
103
+ The \`group\` string is **case-sensitive** and must match exactly across all options
104
+ in the same group.
105
+
106
+ \`\`\`tsx
107
+ const options = [
108
+ { value: 'alice', label: 'Alice Johnson', group: 'Teachers' },
109
+ { value: 'bob', label: 'Bob Smith', group: 'Teachers' }, // same group ✅
110
+ { value: 'carol', label: 'Carol White', group: 'teachers' }, // different group ❌
111
+ ];
112
+ \`\`\`
113
+
114
+ ---
115
+
116
+ ### Accessibility
117
+
118
+ - The trigger renders a combobox role with \`aria-expanded\`, \`aria-haspopup\`, and
119
+ \`aria-controls\` wired to the listbox.
120
+ - Options use \`aria-selected\` and \`aria-disabled\` where appropriate.
121
+ - Pass \`aria-label\` when there is no visible \`<label>\` element nearby.
122
+ - Pass \`aria-describedby\` to link to a \`<FormField>\` hint or error message.
123
+ - Pass \`aria-invalid\` alongside \`hasError\` to signal validation failure to
124
+ assistive technology.
125
+
126
+ ---
127
+
128
+ ### Sub-components (escape hatches)
129
+
130
+ The compound sub-components (\`Combobox.Trigger\`, \`Combobox.ButtonTrigger\`,
131
+ \`Combobox.Listbox\`) exist for advanced layout needs where the trigger and listbox
132
+ must be separated in the DOM. For 99% of use cases, use \`<Combobox />\` directly.
133
+ `.trim();
134
+ const RELATED_COMPONENTS = [
135
+ '## Related components',
136
+ '',
137
+ '[SelectDropdown](?path=/docs/components-formfield-inputs-selectdropdown--docs) · [SearchBar](?path=/docs/components-searchbar--docs) · [FormField](?path=/docs/components-formfield--docs) · [CheckboxInput](?path=/docs/components-formfield-inputs-checkbox--docs)',
138
+ ].join('\n');
139
+ const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
140
+ // ─── Docs page ────────────────────────────────────────────────────────────────
141
+ function ComboboxDocsPage() {
142
+ return (_jsxs(_Fragment, { children: [_jsx(Title, {}), _jsx(Subtitle, {}), _jsx(Markdown, { children: DESCRIPTION_INTRO }), _jsx(DocHeading, { children: "Interactive example" }), _jsx(Markdown, { children: PROPS_INTRO }), _jsx(DocPrimary, {}), _jsx(Controls, {}), _jsx(DocHeading, { children: "Usage guidance" }), _jsx(Markdown, { children: USAGE_GUIDANCE }), _jsx(DocHeading, { children: "Developer notes" }), _jsx(Markdown, { children: DEVELOPER_NOTES }), _jsx(DocHeading, { children: "Examples" }), _jsx(Stories, { title: "" }), _jsx(Markdown, { children: RELATED_COMPONENTS })] }));
143
+ }
144
+ // ─── Meta ─────────────────────────────────────────────────────────────────────
6
145
  const meta = {
7
146
  title: 'Components/Combobox',
8
147
  component: Combobox,
9
148
  tags: ['autodocs'],
10
149
  parameters: {
150
+ layout: 'padded',
11
151
  docs: {
12
- description: {
13
- component: 'Default usage is `<Combobox … />` with props such as `triggerVariant` and `multiple`. `Combobox.Trigger`, `Combobox.ButtonTrigger`, and `Combobox.Listbox` are composition escape hatches.',
14
- },
152
+ page: ComboboxDocsPage,
15
153
  },
16
154
  },
155
+ decorators: [
156
+ (Story) => (_jsx("div", { "data-surface": "base", "data-colour-mode": "light", style: { padding: 'var(--size-space-400)', maxWidth: 480, minHeight: 300 }, children: _jsx(Story, {}) })),
157
+ ],
17
158
  argTypes: {
18
- dropdownOnFocus: {
159
+ options: {
160
+ control: 'object',
161
+ description: 'Array of `ComboboxOption` objects `{ value: string; label: string; tagLabel?: string; iconName?: IconName; disabled?: boolean; group?: string }`.',
162
+ },
163
+ multiple: {
19
164
  control: 'boolean',
20
- description: 'When true, focusing the input opens the dropdown if there are items to show.',
165
+ description: 'Allow multiple selections. Each selected value is shown as a tag chip.',
21
166
  },
22
- showDropdownTrigger: {
167
+ placeholder: {
168
+ control: 'text',
169
+ description: 'Placeholder text shown in the trigger when no value is selected.',
170
+ },
171
+ disabled: {
172
+ control: 'boolean',
173
+ description: 'Disables the entire combobox — trigger and dropdown become non-interactive.',
174
+ },
175
+ loading: {
176
+ control: 'boolean',
177
+ description: 'Shows a loading indicator inside the dropdown while options are being fetched.',
178
+ },
179
+ hasError: {
180
+ control: 'boolean',
181
+ description: 'Applies error styling to the trigger. Pair with `aria-invalid` for full accessibility.',
182
+ },
183
+ searchType: {
184
+ control: 'inline-radio',
185
+ options: ['prefix', 'substring'],
186
+ description: '`prefix` (default) matches from the start of the label. `substring` matches anywhere. You can also pass a custom function `(query, options) => ComboboxOption[]`.',
187
+ },
188
+ highlightStringMatches: {
23
189
  control: 'boolean',
24
- description: 'When true, shows the chevron trigger button.',
190
+ description: 'Highlight the matching portion of each option label while the user is typing.',
191
+ },
192
+ selectedValueDisplay: {
193
+ control: 'inline-radio',
194
+ options: ['tags', 'text'],
195
+ description: '`tags` (default) renders each selected value as a chip. `text` shows a comma-separated string — useful in space-constrained button triggers.',
196
+ },
197
+ showClearAll: {
198
+ control: 'boolean',
199
+ description: 'Show a "Clear all" button inside the trigger when one or more values are selected.',
200
+ },
201
+ clearAllLabel: {
202
+ control: 'text',
203
+ description: 'Label for the clear-all button. Defaults to `"Clear all"`.',
25
204
  },
26
205
  triggerVariant: {
27
206
  control: 'inline-radio',
28
207
  options: ['input', 'button'],
29
- description: 'Choose trigger style: input-in-trigger or button-style summary trigger.',
208
+ description: '`input` (default) renders an inline text field. `button` renders a clickable button; the search field moves into the dropdown.',
209
+ },
210
+ allowCreate: {
211
+ control: 'boolean',
212
+ description: 'Show a "Create X" row when the typed query does not match any existing option.',
213
+ },
214
+ showDropdownTrigger: {
215
+ control: 'boolean',
216
+ description: 'Show or hide the chevron button at the end of the trigger. Defaults to `true`.',
217
+ },
218
+ dropdownOnFocus: {
219
+ control: 'boolean',
220
+ description: 'Open the dropdown automatically when the trigger receives focus. Defaults to `true`.',
221
+ },
222
+ showSelectionCountBadge: {
223
+ control: 'boolean',
224
+ description: 'Show a badge with the number of selected items (useful with `triggerVariant="button"`).',
30
225
  },
226
+ // Callbacks — not controllable via the controls panel
227
+ onValueChange: { control: false },
228
+ onSearch: { control: false },
229
+ onCreateNew: { control: false },
230
+ onDeleteCreated: { control: false },
231
+ renderOption: { control: false },
232
+ getTagLabel: { control: false },
233
+ triggerEndContent: { control: false },
31
234
  },
32
- decorators: [
33
- Story => (_jsx("div", { "data-surface": "base", "data-colour-mode": "light", className: "bg-surface text-on-surface-default", style: { padding: 32, maxWidth: 420 }, children: _jsx(Story, {}) })),
34
- ],
35
235
  };
36
236
  export default meta;
37
- const withDescription = (story, description) => ({
38
- ...story,
237
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
238
+ function withDescription(description, story) {
239
+ return {
240
+ ...story,
241
+ parameters: {
242
+ ...story.parameters,
243
+ docs: {
244
+ ...story.parameters?.docs,
245
+ description: { story: description },
246
+ },
247
+ },
248
+ };
249
+ }
250
+ // ─── Stories ──────────────────────────────────────────────────────────────────
251
+ // 1. Default — wired to Controls
252
+ export const Default = {
253
+ args: {
254
+ options: comboboxPeopleOptions,
255
+ placeholder: 'Select a teacher...',
256
+ multiple: false,
257
+ disabled: false,
258
+ loading: false,
259
+ hasError: false,
260
+ searchType: 'prefix',
261
+ highlightStringMatches: false,
262
+ selectedValueDisplay: 'tags',
263
+ showClearAll: false,
264
+ },
265
+ render: args => _jsx(Combobox, { ...args }),
266
+ };
267
+ // 2. ControlledSingleSelect
268
+ function ControlledSingleSelectTemplate() {
269
+ const [value, setValue] = useState([]);
270
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--size-space-300)' }, children: [_jsx(Combobox, { id: "controlled-combobox", options: comboboxPeopleOptions, value: value, onValueChange: setValue, placeholder: "Select a teacher..." }), _jsxs("p", { className: "ds-text", style: { margin: 0 }, children: ["Selected:", ' ', value.length > 0 ? value.join(', ') : '(none)'] })] }));
271
+ }
272
+ export const ControlledSingleSelect = withDescription('Controlled mode: `value` and `onValueChange` are managed by the parent component. **`value` is always `string[]`** — even for single-select. This is the most common integration mistake.', {
273
+ render: () => _jsx(ControlledSingleSelectTemplate, {}),
39
274
  parameters: {
40
- ...story.parameters,
41
275
  docs: {
42
- ...story.parameters?.docs,
43
- description: {
44
- story: description,
276
+ source: {
277
+ code: `
278
+ function ControlledExample() {
279
+ // value is ALWAYS string[] — even for single-select
280
+ const [value, setValue] = useState<string[]>([]);
281
+
282
+ return (
283
+ <FormField label="Class teacher" htmlFor="teacher-select">
284
+ <Combobox
285
+ id="teacher-select"
286
+ options={teacherOptions}
287
+ value={value}
288
+ onValueChange={setValue}
289
+ placeholder="Select a teacher..."
290
+ />
291
+ </FormField>
292
+ );
293
+ }
294
+ `.trim(),
45
295
  },
46
296
  },
47
297
  },
48
298
  });
49
- /** Enough rows to exceed `.ds-combobox__listbox` max-height so keyboard nav scrolls. */
50
- const manyScrollableOptions = Array.from({ length: 45 }, (_, i) => ({
51
- value: `scroll-demo-${i + 1}`,
52
- label: `Option ${i + 1}`,
53
- iconName: 'user',
54
- }));
55
- const timeOptions = ['13:00', '13:30', '14:00', '14:30', '15:00'].map(time => ({
56
- value: time,
57
- label: time,
58
- }));
59
- export const ScrollableLongList = withDescription({
60
- args: {
61
- options: manyScrollableOptions,
62
- placeholder: 'Open the list and move with arrow keys…',
63
- },
64
- }, 'Exercises a scrollable listbox with enough items to validate keyboard navigation and active-row scrolling.');
65
- export const ButtonTriggerSingleSelect = withDescription({
299
+ // 3. SingleSelect
300
+ export const SingleSelect = withDescription('Single-select mode (default). Type to filter the list by prefix. Press Enter or click to select.', {
66
301
  args: {
67
302
  options: comboboxPeopleOptions,
68
- placeholder: 'Students',
69
- triggerVariant: 'button',
70
- },
71
- }, 'Shows the button-style trigger in single-select mode before the search input moves into the popover.');
72
- export const ButtonTriggerPlainTextValue = withDescription({
73
- args: {
74
- options: timeOptions,
75
- placeholder: 'Select time',
76
- triggerVariant: 'button',
77
- selectedValueDisplay: 'text',
78
- showDropdownTrigger: false,
79
- triggerEndContent: _jsx(Icon, { name: "clock-3", size: 16 }),
80
- defaultValue: ['14:30'],
303
+ placeholder: 'Select a teacher...',
81
304
  },
82
- }, 'Uses plain-text selected-value rendering instead of tags, which suits a single selected time or similarly compact values.');
83
- export const ButtonTriggerMultiSelect = withDescription({
305
+ });
306
+ // 4. MultiSelect
307
+ export const MultiSelect = withDescription('Set `multiple` to allow multiple selections — each selected value is shown as a removable tag chip. Press Backspace to remove the last tag.', {
84
308
  args: {
85
- options: manyScrollableOptions,
86
- defaultValue: ['scroll-demo-1', 'scroll-demo-2', 'scroll-demo-3'],
87
- placeholder: 'Students',
309
+ options: comboboxPeopleOptions,
88
310
  multiple: true,
89
- triggerVariant: 'button',
311
+ placeholder: 'Select teachers...',
90
312
  },
91
- }, 'Demonstrates the button trigger variant with multiple selected chips and the selection count badge.');
92
- export const SingleSelect = withDescription({
313
+ parameters: {
314
+ docs: {
315
+ source: {
316
+ code: '<Combobox options={teacherOptions} multiple placeholder="Select teachers..." />',
317
+ },
318
+ },
319
+ },
320
+ });
321
+ // 5. WithDefaultValue
322
+ export const WithDefaultValue = withDescription('`defaultValue` sets the initial selection in uncontrolled mode. Note that `defaultValue` must be a `string[]` even for single-select.', {
93
323
  args: {
94
324
  options: comboboxPeopleOptions,
95
- placeholder: 'Search people...',
325
+ defaultValue: ['alice-johnson'],
326
+ },
327
+ parameters: {
328
+ docs: {
329
+ source: {
330
+ code: '<Combobox options={teacherOptions} defaultValue={[\'alice-johnson\']} />',
331
+ },
332
+ },
96
333
  },
97
- }, 'Basic single-select Combobox with built-in client-side prefix matching.');
98
- export const SubstringSearch = withDescription({
334
+ });
335
+ // 6. SubstringSearch
336
+ export const SubstringSearch = withDescription('Pass `searchType="substring"` to match anywhere in the label — not just the start. Type "son" to see it match "Alice Johnson" and "Alice Williamson".', {
99
337
  args: {
100
338
  options: comboboxPeopleOptions,
101
- placeholder: 'Search people...',
102
339
  searchType: 'substring',
340
+ placeholder: 'Type part of a name...',
341
+ },
342
+ parameters: {
343
+ docs: {
344
+ source: {
345
+ code: '<Combobox options={teacherOptions} searchType="substring" placeholder="Type part of a name..." />',
346
+ },
347
+ },
103
348
  },
104
- }, 'Switches the built-in matching mode from prefix search to substring search.');
105
- export const HighlightedMatches = withDescription({
349
+ });
350
+ // 7. HighlightedMatches
351
+ export const HighlightedMatches = withDescription('Add `highlightStringMatches` to highlight the matching portion of each option label as the user types. Works best with `searchType="substring"`.', {
106
352
  args: {
107
353
  options: comboboxPeopleOptions,
108
- placeholder: 'Search people...',
109
354
  searchType: 'substring',
110
355
  highlightStringMatches: true,
356
+ placeholder: 'Type to highlight matches...',
357
+ },
358
+ parameters: {
359
+ docs: {
360
+ source: {
361
+ code: '<Combobox options={teacherOptions} searchType="substring" highlightStringMatches placeholder="Type to highlight matches..." />',
362
+ },
363
+ },
111
364
  },
112
- }, 'Highlights the matched portions of each option while using substring filtering.');
113
- export const MultiSelect = withDescription({
365
+ });
366
+ // 8. WithGroups
367
+ export const WithGroups = withDescription('Options with the same `group` string are visually grouped under a shared heading. The `group` value must match exactly (case-sensitive) across all options in the same group.', {
114
368
  args: {
115
- options: comboboxPeopleOptions,
369
+ options: comboboxGroupedPeopleOptions,
116
370
  multiple: true,
117
- placeholder: 'Search people...',
118
- showClearAll: true,
371
+ placeholder: 'Select staff...',
119
372
  },
120
- }, 'Basic multi-select Combobox with removable chips and a clear-all action.');
121
- export const WithDefaultValue = withDescription({
373
+ parameters: {
374
+ docs: {
375
+ source: {
376
+ code: `
377
+ const options = [
378
+ { value: 'alice', label: 'Alice Johnson', group: 'Teachers' },
379
+ { value: 'bob', label: 'Bob Smith', group: 'Teachers' },
380
+ { value: 'carol', label: 'Carol White', group: 'Support Staff' },
381
+ { value: 'rachel', label: 'Rachel Green', group: 'Admin' },
382
+ ];
383
+
384
+ <Combobox options={options} multiple placeholder="Select staff..." />
385
+ `.trim(),
386
+ },
387
+ },
388
+ },
389
+ });
390
+ // 9. Disabled
391
+ export const Disabled = withDescription('The entire combobox is non-interactive when `disabled` is set. The trigger becomes visually muted and cannot be clicked or focused.', {
122
392
  args: {
123
393
  options: comboboxPeopleOptions,
124
- multiple: true,
125
- defaultValue: ['alice', 'bob', 'charlie'],
126
- placeholder: 'Search people...',
127
- showClearAll: true,
394
+ disabled: true,
395
+ placeholder: 'Not available',
396
+ },
397
+ parameters: {
398
+ docs: {
399
+ source: {
400
+ code: '<Combobox options={teacherOptions} disabled placeholder="Not available" />',
401
+ },
402
+ },
128
403
  },
129
- }, 'Starts with preselected values so chip rendering and removal can be reviewed immediately.');
130
- export const WithGroups = withDescription({
404
+ });
405
+ // 10. ErrorState
406
+ export const ErrorState = withDescription('Set `hasError` to apply error styling to the trigger. Always pair with `aria-invalid` so assistive technology can identify the invalid field. Wrap in `FormField` to display the error message.', {
131
407
  args: {
132
- options: comboboxGroupedPeopleOptions,
408
+ 'options': comboboxPeopleOptions,
409
+ 'hasError': true,
410
+ 'aria-invalid': true,
411
+ 'placeholder': 'Select a teacher...',
412
+ },
413
+ render: args => _jsx(Combobox, { ...args }),
414
+ parameters: {
415
+ docs: {
416
+ source: {
417
+ code: `
418
+ <Combobox
419
+ options={teacherOptions}
420
+ hasError
421
+ aria-invalid
422
+ placeholder="Select a teacher..."
423
+ />
424
+ `.trim(),
425
+ },
426
+ },
427
+ },
428
+ });
429
+ // 11. LoadingState
430
+ export const LoadingState = withDescription('Set `loading` to show a loading indicator inside the dropdown while options are being fetched. Use this during async search operations before the results arrive.', {
431
+ args: {
432
+ options: [],
433
+ loading: true,
434
+ placeholder: 'Searching...',
435
+ },
436
+ parameters: {
437
+ docs: {
438
+ source: {
439
+ code: `
440
+ // Typically used while an async fetch is in-flight:
441
+ <Combobox
442
+ options={results}
443
+ loading={isFetching}
444
+ onSearch={handleSearch}
445
+ placeholder="Search staff..."
446
+ />
447
+ `.trim(),
448
+ },
449
+ },
450
+ },
451
+ });
452
+ // 12. ButtonTriggerSingleSelect
453
+ export const ButtonTriggerSingleSelect = withDescription('`triggerVariant="button"` renders a clickable button as the trigger instead of an inline input. The search field moves inside the dropdown. Ideal for toolbar filters and compact layouts.', {
454
+ args: {
455
+ options: comboboxPeopleOptions,
456
+ triggerVariant: 'button',
457
+ placeholder: 'Filter by teacher',
458
+ },
459
+ parameters: {
460
+ docs: {
461
+ source: {
462
+ code: '<Combobox options={teacherOptions} triggerVariant="button" placeholder="Filter by teacher" />',
463
+ },
464
+ },
465
+ },
466
+ });
467
+ // 13. ButtonTriggerMultiSelect
468
+ export const ButtonTriggerMultiSelect = withDescription('Button trigger with `multiple` enabled. Use `showSelectionCountBadge` to show a badge with the number of selected items — useful when the trigger label should not grow in size.', {
469
+ args: {
470
+ options: comboboxPeopleOptions,
471
+ triggerVariant: 'button',
133
472
  multiple: true,
134
- placeholder: 'Search people...',
473
+ placeholder: 'Filter by teachers',
474
+ showSelectionCountBadge: true,
135
475
  },
136
- }, 'Shows grouped options in the dropdown while keeping the same multi-select interaction model.');
137
- export const Disabled = withDescription({
476
+ parameters: {
477
+ docs: {
478
+ source: {
479
+ code: '<Combobox options={teacherOptions} triggerVariant="button" multiple placeholder="Filter by teachers" showSelectionCountBadge />',
480
+ },
481
+ },
482
+ },
483
+ });
484
+ // 14. ButtonTriggerPlainTextValue
485
+ export const ButtonTriggerPlainTextValue = withDescription('`selectedValueDisplay="text"` shows the selected value(s) as a comma-separated text string instead of tag chips — useful when space is limited inside a button trigger.', {
138
486
  args: {
139
487
  options: comboboxPeopleOptions,
140
- disabled: true,
141
- defaultValue: ['alice'],
142
- placeholder: 'Search people...',
488
+ triggerVariant: 'button',
489
+ selectedValueDisplay: 'text',
490
+ multiple: true,
491
+ placeholder: 'Filter by teacher',
492
+ },
493
+ parameters: {
494
+ docs: {
495
+ source: {
496
+ code: '<Combobox options={teacherOptions} triggerVariant="button" selectedValueDisplay="text" multiple placeholder="Filter by teacher" />',
497
+ },
498
+ },
143
499
  },
144
- }, 'Displays the disabled state with an existing selected value.');
145
- export const HiddenTrigger = withDescription({
500
+ });
501
+ // 15. HiddenTrigger
502
+ export const HiddenTrigger = withDescription('Set `showDropdownTrigger={false}` to hide the chevron button at the end of the input. The dropdown can still be opened by typing or focusing the field.', {
146
503
  args: {
147
504
  options: comboboxPeopleOptions,
148
- placeholder: 'Search people...',
149
505
  showDropdownTrigger: false,
506
+ placeholder: 'No chevron shown...',
507
+ },
508
+ parameters: {
509
+ docs: {
510
+ source: {
511
+ code: '<Combobox options={teacherOptions} showDropdownTrigger={false} placeholder="No chevron shown..." />',
512
+ },
513
+ },
150
514
  },
151
- }, 'Hides the chevron trigger while keeping focus, typing, and keyboard opening behavior.');
152
- export const ManualOpenOnFocus = withDescription({
515
+ });
516
+ // 16. ManualOpenOnFocus
517
+ export const ManualOpenOnFocus = withDescription('Set `dropdownOnFocus={false}` to prevent the dropdown from opening automatically on focus — the user must start typing or click the chevron to open it.', {
153
518
  args: {
154
519
  options: comboboxPeopleOptions,
155
- placeholder: 'Search people...',
156
520
  dropdownOnFocus: false,
521
+ placeholder: 'Focus me — dropdown stays closed',
157
522
  },
158
- }, 'Disables focus-open so the list appears only after typing or explicit keyboard interaction.');
159
- export const CustomOptionLayout = withDescription({
160
- args: {
161
- options: comboboxPeopleOptions,
162
- placeholder: 'Search people...',
163
- multiple: true,
164
- renderOption: (option, selected) => (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", children: _jsx("input", { type: "checkbox", checked: selected, readOnly: true, tabIndex: -1 }) }), _jsx("span", { className: "ds-combobox__option-label", children: option.label })] })),
523
+ parameters: {
524
+ docs: {
525
+ source: {
526
+ code: '<Combobox options={teacherOptions} dropdownOnFocus={false} placeholder="Focus me — dropdown stays closed" />',
527
+ },
528
+ },
165
529
  },
166
- }, 'Uses `renderOption` to swap the row content while the Combobox still owns selection and listbox behavior.');
167
- const delay = (ms) => new Promise((resolve) => {
168
- setTimeout(resolve, ms);
169
530
  });
170
- async function searchPeople(query) {
171
- await delay(700);
172
- const q = query.trim().toLowerCase();
173
- if (!q)
174
- return comboboxAsyncSearchOptions;
175
- return comboboxAsyncSearchOptions.filter(p => p.label.toLowerCase().includes(q));
176
- }
177
- const AsyncSearchTemplate = ({ allowCreate = false, searchType = 'substring', highlightStringMatches = true, }) => {
178
- const [options, setOptions] = useState([]);
179
- const [loading, setLoading] = useState(false);
180
- const requestIdRef = useRef(0);
531
+ // ─── Async search template ─────────────────────────────────────────────────────
532
+ function AsyncSearchTemplate() {
533
+ const [options, setOptions] = useState(comboboxPeopleOptions);
534
+ const timerRef = useRef(null);
181
535
  const handleSearch = (query) => {
182
- const id = ++requestIdRef.current;
183
- setLoading(true);
184
- void searchPeople(query).then((next) => {
185
- if (id !== requestIdRef.current)
186
- return;
187
- setOptions(next);
188
- setLoading(false);
189
- });
536
+ if (timerRef.current)
537
+ clearTimeout(timerRef.current);
538
+ timerRef.current = setTimeout(() => {
539
+ setOptions(comboboxAsyncSearchOptions.filter(o => o.label.toLowerCase().startsWith(query.toLowerCase())));
540
+ }, 300);
190
541
  };
191
- return (_jsx(Combobox, { options: options, multiple: true, placeholder: "Type to search...", loading: loading, allowCreate: allowCreate, searchType: searchType, highlightStringMatches: highlightStringMatches, onSearch: handleSearch }));
192
- };
193
- export const AsyncSearch = withDescription({
194
- args: {
195
- allowCreate: false,
196
- searchType: 'substring',
197
- highlightStringMatches: true,
542
+ return _jsx(Combobox, { options: options, onSearch: handleSearch, placeholder: "Search staff..." });
543
+ }
544
+ // 17. AsyncSearch
545
+ export const AsyncSearch = withDescription('Pass `onSearch` to take control of filtering. The component disables all client-side matching — you update the `options` array yourself (e.g. via an API call). This example simulates a 300 ms network delay.', {
546
+ render: () => _jsx(AsyncSearchTemplate, {}),
547
+ parameters: {
548
+ docs: {
549
+ source: {
550
+ code: `
551
+ function AsyncSearchExample() {
552
+ const [options, setOptions] = useState<ComboboxOption[]>([]);
553
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
554
+
555
+ const handleSearch = (query: string) => {
556
+ if (timerRef.current) clearTimeout(timerRef.current);
557
+ timerRef.current = setTimeout(async () => {
558
+ const results = await fetchStaff(query);
559
+ setOptions(results);
560
+ }, 300);
561
+ };
562
+
563
+ return (
564
+ <Combobox
565
+ options={options}
566
+ onSearch={handleSearch}
567
+ placeholder="Search staff..."
568
+ />
569
+ );
570
+ }
571
+ `.trim(),
572
+ },
573
+ },
198
574
  },
199
- render: args => (_jsx(AsyncSearchTemplate, { allowCreate: args.allowCreate, searchType: args.searchType, highlightStringMatches: args.highlightStringMatches })),
200
- }, 'Simulates async search results with local data so loading, filtering, and optional create flows can be reviewed.');
201
- const CreateNewTemplate = () => {
575
+ });
576
+ // ─── Create new template ───────────────────────────────────────────────────────
577
+ function CreateNewTemplate() {
202
578
  const [options, setOptions] = useState(comboboxPeopleOptions);
203
- const handleCreate = (name) => {
204
- const newOpt = {
205
- value: name.toLowerCase().replace(/\s/g, '-'),
206
- label: name,
207
- iconName: 'user',
208
- };
209
- setOptions(prev => [...prev, newOpt]);
210
- return newOpt;
579
+ const handleCreate = (input) => {
580
+ const newOption = { value: input.toLowerCase().replace(/\s+/g, '-'), label: input };
581
+ setOptions(prev => [...prev, newOption]);
582
+ return newOption;
211
583
  };
212
- const handleDelete = (value) => {
213
- setOptions(prev => prev.filter(opt => opt.value !== value));
214
- };
215
- return (_jsx(Combobox, { options: options, multiple: true, placeholder: "Search or create...", allowCreate: true, onCreateNew: handleCreate, onDeleteCreated: handleDelete }));
216
- };
217
- export const CreateNew = withDescription({
584
+ return (_jsx(Combobox, { options: options, multiple: true, allowCreate: true, onCreateNew: handleCreate, placeholder: "Select or add a teacher..." }));
585
+ }
586
+ // 18. CreateNew
587
+ export const CreateNew = withDescription('Set `allowCreate` to show a "Create X" option when the typed query does not match any existing option. The `onCreateNew` callback receives the typed string and must return a new `ComboboxOption`.', {
218
588
  render: () => _jsx(CreateNewTemplate, {}),
219
- }, 'Demonstrates multi-select creation and deletion of user-added options.');
220
- const SingleSelectCreateTemplate = () => {
221
- const [options, setOptions] = useState(comboboxPeopleOptions);
222
- const handleCreate = (name) => {
223
- const newOpt = {
224
- value: name.toLowerCase().replace(/\s/g, '-'),
225
- label: name,
226
- iconName: 'user',
227
- };
228
- setOptions(prev => [...prev, newOpt]);
229
- return newOpt;
589
+ parameters: {
590
+ docs: {
591
+ source: {
592
+ code: `
593
+ function CreateNewExample() {
594
+ const [options, setOptions] = useState(initialOptions);
595
+
596
+ const handleCreate = (input: string) => {
597
+ const newOption = {
598
+ value: input.toLowerCase().replace(/\\s+/g, '-'),
599
+ label: input,
230
600
  };
231
- const handleDelete = (value) => {
232
- setOptions(prev => prev.filter(opt => opt.value !== value));
601
+ setOptions((prev) => [...prev, newOption]);
602
+ return newOption;
603
+ };
604
+
605
+ return (
606
+ <Combobox
607
+ options={options}
608
+ multiple
609
+ allowCreate
610
+ onCreateNew={handleCreate}
611
+ placeholder="Select or add a teacher..."
612
+ />
613
+ );
614
+ }
615
+ `.trim(),
616
+ },
617
+ },
618
+ },
619
+ });
620
+ // ─── Single select create template ────────────────────────────────────────────
621
+ function SingleSelectCreateTemplate() {
622
+ const [options, setOptions] = useState(comboboxPeopleOptions);
623
+ const handleCreate = (input) => {
624
+ const newOption = { value: input.toLowerCase().replace(/\s+/g, '-'), label: input };
625
+ setOptions(prev => [...prev, newOption]);
626
+ return newOption;
233
627
  };
234
- return (_jsx(Combobox, { options: options, placeholder: "Search or create...", allowCreate: true, onCreateNew: handleCreate, onDeleteCreated: handleDelete }));
235
- };
236
- export const SingleSelectCreate = withDescription({
628
+ return (_jsx(Combobox, { options: options, allowCreate: true, onCreateNew: handleCreate, placeholder: "Select or add a teacher..." }));
629
+ }
630
+ // 19. SingleSelectCreate
631
+ export const SingleSelectCreate = withDescription('`allowCreate` works in single-select mode too — useful for fields like "Add a subject" where the user can either pick from existing values or type a new one.', {
237
632
  render: () => _jsx(SingleSelectCreateTemplate, {}),
238
- }, 'Shows single-select creation where a newly created option becomes the current value immediately.');
239
- export const CustomTagLabel = withDescription({
633
+ parameters: {
634
+ docs: {
635
+ source: {
636
+ code: `
637
+ function SingleSelectCreateExample() {
638
+ const [options, setOptions] = useState(initialOptions);
639
+
640
+ const handleCreate = (input: string) => {
641
+ const newOption = {
642
+ value: input.toLowerCase().replace(/\\s+/g, '-'),
643
+ label: input,
644
+ };
645
+ setOptions((prev) => [...prev, newOption]);
646
+ return newOption;
647
+ };
648
+
649
+ return (
650
+ <Combobox
651
+ options={options}
652
+ allowCreate
653
+ onCreateNew={handleCreate}
654
+ placeholder="Select or add a teacher..."
655
+ />
656
+ );
657
+ }
658
+ `.trim(),
659
+ },
660
+ },
661
+ },
662
+ });
663
+ // 20. CustomOptionLayout
664
+ export const CustomOptionLayout = withDescription('Use `renderOption` to fully customise how each option is rendered inside the dropdown — useful for adding avatars, icons, or additional metadata alongside the label.', {
240
665
  args: {
241
- options: [
242
- { value: 'u1', label: 'Alice Johnson (Year 10, Class A)', tagLabel: 'Alice J.', iconName: 'user' },
243
- { value: 'u2', label: 'Bob Smith (Year 11, Class B)', tagLabel: 'Bob S.', iconName: 'user' },
244
- { value: 'u3', label: 'Charlie Brown (Year 10, Class A)', tagLabel: 'Charlie B.', iconName: 'user' },
245
- ],
666
+ options: comboboxPeopleOptions,
246
667
  multiple: true,
247
- placeholder: 'Search students...',
248
- showClearAll: true,
668
+ placeholder: 'Select teachers...',
669
+ renderOption: (option) => (_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: 'var(--size-space-200)' }, children: [_jsx(Icon, { name: "user", size: 16 }), _jsx("span", { children: option.label })] })),
670
+ },
671
+ parameters: {
672
+ docs: {
673
+ source: {
674
+ code: `
675
+ <Combobox
676
+ options={teacherOptions}
677
+ multiple
678
+ placeholder="Select teachers..."
679
+ renderOption={(option) => (
680
+ <span style={{ display: 'flex', alignItems: 'center', gap: 'var(--size-space-200)' }}>
681
+ <Icon name="user" size={16} />
682
+ <span>{option.label}</span>
683
+ </span>
684
+ )}
685
+ />
686
+ `.trim(),
687
+ },
688
+ },
249
689
  },
250
- }, 'Uses `tagLabel` to shorten selected chip text while preserving fuller option labels in the dropdown.');
251
- export const WithDisabledOptions = withDescription({
690
+ });
691
+ // 21. CustomTagLabel
692
+ export const CustomTagLabel = withDescription('Use `getTagLabel` to customise the label shown inside a selected tag chip — useful when the full option label is too long to display comfortably in the trigger.', {
693
+ args: {
694
+ options: comboboxPeopleOptions,
695
+ multiple: true,
696
+ placeholder: 'Select teachers...',
697
+ getTagLabel: (option) => option.label.split(' ')[0] ?? option.label,
698
+ },
699
+ parameters: {
700
+ docs: {
701
+ source: {
702
+ code: `
703
+ <Combobox
704
+ options={teacherOptions}
705
+ multiple
706
+ placeholder="Select teachers..."
707
+ getTagLabel={(option) => option.label.split(' ')[0]}
708
+ />
709
+ `.trim(),
710
+ },
711
+ },
712
+ },
713
+ });
714
+ // 22. WithDisabledOptions
715
+ export const WithDisabledOptions = withDescription('Individual options can be disabled by setting `disabled: true` on the option object. Disabled options are visible but cannot be selected.', {
252
716
  args: {
253
- options: [
254
- { value: 'alice', label: 'Alice Johnson', iconName: 'user' },
255
- { value: 'bob', label: 'Bob Smith', iconName: 'user', disabled: true },
256
- { value: 'charlie', label: 'Charlie Brown', iconName: 'user' },
257
- ],
717
+ options: comboboxPeopleOptions.map((o, i) => ({ ...o, disabled: i % 3 === 1 })),
258
718
  multiple: true,
259
- placeholder: 'Search people...',
719
+ placeholder: 'Select teachers...',
260
720
  },
261
- }, 'Includes a disabled option to show how unavailable rows appear and behave inside the listbox.');
721
+ parameters: {
722
+ docs: {
723
+ source: {
724
+ code: `
725
+ const options = [
726
+ { value: 'alice', label: 'Alice Johnson' },
727
+ { value: 'bob', label: 'Bob Smith', disabled: true },
728
+ { value: 'carol', label: 'Carol White' },
729
+ { value: 'dan', label: 'Dan Brown', disabled: true },
730
+ ];
731
+
732
+ <Combobox options={options} multiple placeholder="Select teachers..." />
733
+ `.trim(),
734
+ },
735
+ },
736
+ },
737
+ });
738
+ // 23. ScrollableLongList
739
+ export const ScrollableLongList = withDescription('The dropdown scrolls automatically when there are many options. Here we render 50 generated options to demonstrate the scrollable list and confirm search still works at scale.', {
740
+ args: {
741
+ options: Array.from({ length: 50 }, (_, i) => ({
742
+ value: `option-${i}`,
743
+ label: `Option ${i + 1}`,
744
+ })),
745
+ multiple: true,
746
+ placeholder: 'Search 50 options...',
747
+ },
748
+ parameters: {
749
+ docs: {
750
+ source: {
751
+ code: `
752
+ const options = Array.from({ length: 50 }, (_, i) => ({
753
+ value: \`option-\${i}\`,
754
+ label: \`Option \${i + 1}\`,
755
+ }));
756
+
757
+ <Combobox options={options} multiple placeholder="Search 50 options..." />
758
+ `.trim(),
759
+ },
760
+ },
761
+ },
762
+ });
262
763
  //# sourceMappingURL=Combobox.stories.js.map