@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,218 +1,1020 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import {
3
+ Controls,
4
+ Heading as DocHeading,
5
+ Markdown,
6
+ Primary as DocPrimary,
7
+ Stories,
8
+ Subtitle,
9
+ Title,
10
+ } from '@storybook/addon-docs/blocks';
11
+ import { useState } from 'react';
2
12
  import { Avatar } from 'Components/avatar/Avatar';
13
+ import { Button } from 'Components/button/Button';
3
14
  import { AvatarGroup } from './AvatarGroup';
4
15
 
16
+ // ---------------------------------------------------------------------------
17
+ // Content strings for the custom DocsPage
18
+ // ---------------------------------------------------------------------------
19
+
20
+ const DESCRIPTION_INTRO = [
21
+ '`AvatarGroup` renders a horizontal stack of overlapping Avatar thumbnails with an optional overflow badge,',
22
+ 'supporting both a data-driven `items` API and a composable `children` API for maximum flexibility.',
23
+ ].join(' ');
24
+
25
+ const PROPS_INTRO
26
+ = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
27
+
28
+ const USAGE_GUIDANCE = [
29
+ '### When to use',
30
+ '',
31
+ '- Showing a compact summary of participants, collaborators, or assignees on a record (e.g. teachers on a timetable slot, guardians linked to a student).',
32
+ '- Representing "who has access" or "who is watching" in a list view where space is limited.',
33
+ '- Surfacing a count of hidden members when there are more people than screen real estate allows.',
34
+ '',
35
+ '---',
36
+ '',
37
+ '### When NOT to use',
38
+ '',
39
+ '| Scenario | Better alternative |',
40
+ '|---|---|',
41
+ '| You need to show full names alongside each avatar | A list of `<Avatar>` components with adjacent labels |',
42
+ '| You need click / interactive behaviour on each avatar | Wrap individual `<Avatar>` components in buttons |',
43
+ '| You only have one person to show | A single `<Avatar>` is cleaner and more semantic |',
44
+ '| The avatars represent non-people entities (subjects, clubs) | An icon list or tag list |',
45
+ ].join('\n');
46
+
47
+ const DEVELOPER_NOTES = [
48
+ '### Critical usage patterns',
49
+ '',
50
+ '**Always supply `label`**',
51
+ '',
52
+ 'Every `AvatarGroup` in production must have an `aria-label` so screen-reader users know what the group represents:',
53
+ '',
54
+ '```tsx',
55
+ '<AvatarGroup label="Form tutors for Year 11" items={tutors} />',
56
+ '```',
57
+ '',
58
+ '**Two mutually exclusive APIs**',
59
+ '',
60
+ 'Choose one and stick to it — mixing both on the same component causes a TypeScript error:',
61
+ '',
62
+ '```tsx',
63
+ '// ✅ Items API — pass an array of AvatarGroupItem objects',
64
+ '<AvatarGroup items={staff} label="Staff" />',
65
+ '',
66
+ '// ✅ Children API — compose Avatar elements directly',
67
+ '<AvatarGroup label="Staff">',
68
+ ' <Avatar initials="DZ" alt="Dorothy Zbornak" />',
69
+ ' <Avatar initials="SP" alt="Sophia Petrillo" />',
70
+ '</AvatarGroup>',
71
+ '```',
72
+ '',
73
+ '**Group `size` wins over individual sizes**',
74
+ '',
75
+ 'If you pass `size` to the group, it overrides any `size` set on individual items. This is intentional:',
76
+ '',
77
+ '```tsx',
78
+ '// All avatars will render at large, regardless of individual size props',
79
+ '<AvatarGroup size="large" items={staff} label="Staff" />',
80
+ '```',
81
+ '',
82
+ '**`listOrder="descending"` shows the LAST N items**',
83
+ '',
84
+ 'When combined with `showMaxItems`, `ascending` shows the first N items and `descending` shows the last N.',
85
+ 'Use `descending` for "most recent contributors" patterns where the latest entries are at the end of the array:',
86
+ '',
87
+ '```tsx',
88
+ '// Shows the 3 most recently active teachers (last 3 in the array)',
89
+ '<AvatarGroup',
90
+ ' items={recentTeachers}',
91
+ ' showMaxItems={3}',
92
+ ' listOrder="descending"',
93
+ ' label="Most recently active teachers"',
94
+ '/>',
95
+ '```',
96
+ '',
97
+ '**Custom overflow label**',
98
+ '',
99
+ 'Pass a function to `overflowCountLabel` to build a fully descriptive accessible label:',
100
+ '',
101
+ '```tsx',
102
+ '<AvatarGroup',
103
+ ' items={students}',
104
+ ' showMaxItems={5}',
105
+ ' overflowCountLabel={(n) => `${n} more students not shown`}',
106
+ ' label="Students in tutor group 7B"',
107
+ '/>',
108
+ '```',
109
+ '',
110
+ '---',
111
+ '',
112
+ '### Accessibility',
113
+ '',
114
+ '- The root element is a `<ul>` — each avatar is a `<li>`. Screen readers receive correct list semantics and item count.',
115
+ '- Supply `label` (maps to `aria-label` on the `<ul>`) so the list has a meaningful name.',
116
+ '- The overflow badge uses `role="status"` and an `aria-label` derived from `overflowCountLabel` (default: `"plus N more"`).',
117
+ '- Set `presentAllUpdatesToScreenReader` when the group updates dynamically — this adds `aria-live="polite"` and `aria-atomic="true"` to the overflow badge so changes are announced.',
118
+ '',
119
+ '---',
120
+ '',
121
+ '### TypeScript types',
122
+ '',
123
+ '```ts',
124
+ "import { AvatarGroup } from '@arbor-education/design-system.components';",
125
+ "import type { AvatarGroupItem, AvatarGroupListOrder } from '@arbor-education/design-system.components';",
126
+ '',
127
+ 'type AvatarGroupProps = AvatarGroup.Props;',
128
+ '```',
129
+ ].join('\n');
130
+
131
+ const AVATAR_GROUP_ITEM_REFERENCE = [
132
+ '`AvatarGroupItem` is the union type accepted by the `items` array. It supports two shapes so you can',
133
+ 'pass either raw data objects or pre-rendered `<Avatar>` elements:',
134
+ '',
135
+ '```ts',
136
+ "import type { AvatarGroupItem } from '@arbor-education/design-system.components';",
137
+ '',
138
+ '// Shape 1 — plain AvatarProps object (most common, data-driven):',
139
+ 'const item: AvatarGroupItem = { initials: "DZ", alt: "Dorothy Zbornak", size: "medium" };',
140
+ '',
141
+ '// Shape 2 — pre-rendered <Avatar> element:',
142
+ 'const item: AvatarGroupItem = <Avatar initials="DZ" alt="Dorothy Zbornak" />;',
143
+ '```',
144
+ '',
145
+ 'When using the `items` API, the group normalises both shapes internally so you can freely mix them:',
146
+ '',
147
+ '```tsx',
148
+ 'const mixed: AvatarGroupItem[] = [',
149
+ ' { initials: "DZ", alt: "Dorothy Zbornak" }, // plain object',
150
+ ' <Avatar initials="RN" alt="Rose Nylund" size="large" />, // element',
151
+ '];',
152
+ '',
153
+ '<AvatarGroup items={mixed} label="Mixed item types" />',
154
+ '```',
155
+ '',
156
+ 'Use the **items API** when avatar data comes from an API response (plain objects). Use the **children API**',
157
+ '(`<AvatarGroup><Avatar .../></AvatarGroup>`) when you already have `<Avatar>` instances in your render tree.',
158
+ ].join('\n');
159
+
160
+ const RELATED_COMPONENTS = [
161
+ '## Related components',
162
+ '',
163
+ '[Avatar](?path=/docs/components-avatar--docs) · [Section](?path=/docs/components-section--docs)',
164
+ ].join('\n');
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Custom DocsPage
168
+ // ---------------------------------------------------------------------------
169
+
170
+ function AvatarGroupDocsPage() {
171
+ return (
172
+ <>
173
+ <Title />
174
+ <Subtitle />
175
+ <Markdown>{DESCRIPTION_INTRO}</Markdown>
176
+ <DocHeading>Interactive example</DocHeading>
177
+ <Markdown>{PROPS_INTRO}</Markdown>
178
+ <DocPrimary />
179
+ <Controls />
180
+ <DocHeading>Usage guidance</DocHeading>
181
+ <Markdown>{USAGE_GUIDANCE}</Markdown>
182
+ <DocHeading>Developer notes</DocHeading>
183
+ <Markdown>{DEVELOPER_NOTES}</Markdown>
184
+ <DocHeading>AvatarGroupItem reference</DocHeading>
185
+ <Markdown>{AVATAR_GROUP_ITEM_REFERENCE}</Markdown>
186
+ <DocHeading>Examples</DocHeading>
187
+ <Stories title="" />
188
+ <Markdown>{RELATED_COMPONENTS}</Markdown>
189
+ </>
190
+ );
191
+ }
192
+
193
+ // ---------------------------------------------------------------------------
194
+ // Meta
195
+ // ---------------------------------------------------------------------------
196
+
5
197
  const meta: Meta<typeof AvatarGroup> = {
6
198
  title: 'Components/AvatarGroup',
7
199
  component: AvatarGroup,
8
200
  tags: ['autodocs'],
201
+ parameters: {
202
+ layout: 'padded',
203
+ docs: { page: AvatarGroupDocsPage },
204
+ },
205
+ argTypes: {
206
+ items: {
207
+ description: [
208
+ 'Data-driven API. An array of `AvatarGroupItem` values — each item is either an `AvatarProps`',
209
+ 'plain object (`{ initials, alt, src, size, ... }`) or a pre-rendered `<Avatar>` element.',
210
+ 'Mutually exclusive with `children`.',
211
+ ].join(' '),
212
+ control: false,
213
+ table: {
214
+ type: { summary: 'readonly AvatarGroupItem[]' },
215
+ },
216
+ },
217
+ children: {
218
+ description: [
219
+ 'Composable API. Render `<Avatar>` elements as children.',
220
+ 'Fragments are supported — nested Avatars inside `<>...</>` are collected automatically.',
221
+ 'Mutually exclusive with `items`.',
222
+ ].join(' '),
223
+ control: false,
224
+ table: {
225
+ type: { summary: 'React.ReactNode' },
226
+ },
227
+ },
228
+ showMaxItems: {
229
+ description: [
230
+ 'Maximum number of avatars to render.',
231
+ 'Any additional avatars are replaced by the `+N` overflow count badge.',
232
+ 'When omitted, all avatars are shown with no badge.',
233
+ ].join(' '),
234
+ control: { type: 'number', min: 1, max: 20, step: 1 },
235
+ table: {
236
+ type: { summary: 'number' },
237
+ defaultValue: { summary: 'undefined (show all)' },
238
+ },
239
+ },
240
+ listOrder: {
241
+ description: [
242
+ '`ascending` (default) shows the **first** N items; `descending` shows the **last** N items.',
243
+ 'Use `descending` for "most recent contributors" patterns where the latest entries are at the end of the array.',
244
+ ].join(' '),
245
+ control: { type: 'radio' },
246
+ options: ['ascending', 'descending'],
247
+ table: {
248
+ type: { summary: "'ascending' | 'descending'" },
249
+ defaultValue: { summary: "'ascending'" },
250
+ },
251
+ },
252
+ label: {
253
+ description: [
254
+ 'Sets `aria-label` on the root `<ul>`.',
255
+ '**Required in production** — without it, screen readers announce a generic unnamed list.',
256
+ ].join(' '),
257
+ control: 'text',
258
+ table: {
259
+ type: { summary: 'string' },
260
+ defaultValue: { summary: 'undefined' },
261
+ },
262
+ },
263
+ overflowCountLabel: {
264
+ description: [
265
+ 'Accessible label for the `+N` overflow badge.',
266
+ 'Pass a static string or a function receiving the overflow count.',
267
+ 'Defaults to `"plus N more"` if omitted.',
268
+ ].join(' '),
269
+ control: false,
270
+ table: {
271
+ type: { summary: 'string | ((count: number) => string)' },
272
+ defaultValue: { summary: '"plus N more"' },
273
+ },
274
+ },
275
+ presentAllUpdatesToScreenReader: {
276
+ description: [
277
+ 'When `true`, adds `aria-live="polite"` and `aria-atomic="true"` to the overflow badge,',
278
+ 'so dynamic additions/removals are announced to screen-reader users.',
279
+ 'Only enable on lists that update at runtime.',
280
+ ].join(' '),
281
+ control: 'boolean',
282
+ table: {
283
+ type: { summary: 'boolean' },
284
+ defaultValue: { summary: 'false' },
285
+ },
286
+ },
287
+ size: {
288
+ description: [
289
+ 'Group-level size override.',
290
+ '**This wins over any `size` set on individual Avatar items inside the group**,',
291
+ 'ensuring visual consistency across the whole group.',
292
+ ].join(' '),
293
+ control: { type: 'select' },
294
+ options: ['small', 'medium', 'large', 'extra-large'],
295
+ table: {
296
+ type: { summary: "'small' | 'medium' | 'large' | 'extra-large'" },
297
+ defaultValue: { summary: 'undefined (each avatar uses its own size)' },
298
+ },
299
+ },
300
+ className: {
301
+ description: 'Additional CSS class names applied to the root `<ul>` element.',
302
+ control: 'text',
303
+ table: {
304
+ type: { summary: 'string' },
305
+ defaultValue: { summary: 'undefined' },
306
+ },
307
+ },
308
+ },
9
309
  };
10
310
 
11
311
  export default meta;
12
-
312
+ // Use StoryObj<typeof AvatarGroup> (not typeof meta) so render-only stories are
313
+ // not forced to provide required args — template components handle their own instances.
13
314
  type Story = StoryObj<typeof AvatarGroup>;
14
315
 
15
- const sampleSrc = (id: number) => `https://i.pravatar.cc/150?img=${id}`;
316
+ // ---------------------------------------------------------------------------
317
+ // Shared fixture data
318
+ // ---------------------------------------------------------------------------
16
319
 
17
- export const FromProps: Story = {
18
- render: () => (
19
- <AvatarGroup
20
- label="Team from props"
21
- items={[
22
- { src: sampleSrc(1), alt: 'Member 1' },
23
- { src: sampleSrc(2), alt: 'Member 2' },
24
- { initials: 'CM', alt: 'Chris Montgomery' },
25
- ]}
26
- />
27
- ),
28
- };
320
+ const STAFF_ITEMS: AvatarGroup.Item[] = [
321
+ { initials: 'DZ', alt: 'Dorothy Zbornak' },
322
+ { initials: 'RN', alt: 'Rose Nylund' },
323
+ { initials: 'BD', alt: 'Blanche Devereaux' },
324
+ { initials: 'SP', alt: 'Sophia Petrillo' },
325
+ { initials: 'MW', alt: 'Miles Webber' },
326
+ { initials: 'SZ', alt: 'Stan Zbornak' },
327
+ { initials: 'CN', alt: 'Charlie Nylund' },
328
+ ];
29
329
 
30
- export const SizeOverridesPerAvatar: Story = {
31
- name: 'Size Override',
32
- render: () => (
33
- <AvatarGroup
34
- label="All medium even though children are set as small"
35
- size="medium"
36
- showMaxItems={4}
37
- items={[
38
- <Avatar
39
- key="1"
40
- size="small"
41
- initials="S1"
42
- alt="Small 1"
43
- />,
44
- <Avatar
45
- key="2"
46
- size="small"
47
- initials="S2"
48
- alt="Small 2"
49
- />,
50
- { size: 'small', initials: 'S3', alt: 'Small 3' },
51
- { size: 'small', initials: 'S4', alt: 'Small 4' },
52
- { size: 'small', initials: 'S5', alt: 'Small 5' },
53
- ]}
54
- />
55
- ),
56
- };
330
+ const STUDENT_ITEMS: AvatarGroup.Item[] = [
331
+ { initials: 'AT', alt: 'Aisha Thompson' },
332
+ { initials: 'CF', alt: 'Callum Fraser' },
333
+ { initials: 'FA', alt: 'Fatima Al-Rashid' },
334
+ { initials: 'OB', alt: 'Oliver Bennett' },
335
+ { initials: 'PS', alt: 'Priya Sharma' },
336
+ { initials: 'DM', alt: 'Declan Murphy' },
337
+ { initials: 'YT', alt: 'Yuki Tanaka' },
338
+ { initials: 'AO', alt: 'Amara Osei' },
339
+ ];
340
+
341
+ // ---------------------------------------------------------------------------
342
+ // Helper: attach a per-story description to docs
343
+ // ---------------------------------------------------------------------------
57
344
 
58
- export const ComposableChildren: Story = {
59
- name: 'Composable Children',
345
+ const withDescription = (story: Story, description: string): Story => ({
346
+ ...story,
60
347
  parameters: {
61
- docs: {
62
- description: {
63
- story:
64
- 'Pass `<Avatar />` nodes as `children`.',
65
- },
66
- },
348
+ ...story.parameters,
349
+ docs: { ...story.parameters?.docs, description: { story: description } },
67
350
  },
68
- render: () => (
69
- <AvatarGroup
70
- label="Team (children API)"
71
- showMaxItems={3}
72
- listOrder="ascending"
351
+ });
352
+
353
+ // ---------------------------------------------------------------------------
354
+ // Template components for stateful stories
355
+ // ---------------------------------------------------------------------------
356
+
357
+ const LiveOverflowCounterTemplate = () => {
358
+ const initialItems: AvatarGroup.Item[] = [
359
+ { initials: 'AT', alt: 'Aisha Thompson' },
360
+ { initials: 'CF', alt: 'Callum Fraser' },
361
+ { initials: 'FA', alt: 'Fatima Al-Rashid' },
362
+ ];
363
+
364
+ const extraItems: AvatarGroup.Item[] = [
365
+ { initials: 'OB', alt: 'Oliver Bennett' },
366
+ { initials: 'PS', alt: 'Priya Sharma' },
367
+ { initials: 'DM', alt: 'Declan Murphy' },
368
+ { initials: 'YT', alt: 'Yuki Tanaka' },
369
+ ];
370
+
371
+ const [items, setItems] = useState<AvatarGroup.Item[]>(initialItems);
372
+
373
+ const addItem = () => {
374
+ const next = extraItems[items.length - initialItems.length];
375
+ if (next) setItems(prev => [...prev, next]);
376
+ };
377
+
378
+ const removeItem = () => {
379
+ if (items.length > 1) setItems(prev => prev.slice(0, -1));
380
+ };
381
+
382
+ return (
383
+ <div
384
+ style={{
385
+ display: 'flex',
386
+ flexDirection: 'column',
387
+ gap: 'var(--spacing-large)',
388
+ }}
73
389
  >
74
- <>
75
- <Avatar
76
- size="small"
77
- initials="A"
78
- alt="User A"
79
- />
80
- <Avatar
81
- size="small"
82
- initials="B"
83
- alt="User B"
84
- />
85
- </>
86
- <Avatar
87
- size="small"
88
- initials="C"
89
- alt="User C"
390
+ <AvatarGroup
391
+ items={items}
392
+ showMaxItems={3}
393
+ size="medium"
394
+ label="Active session participants"
395
+ overflowCountLabel={n => `${n} more participants`}
396
+ presentAllUpdatesToScreenReader
90
397
  />
91
- <Avatar
92
- size="small"
93
- initials="D"
94
- alt="User D"
398
+ <div style={{ display: 'flex', gap: 'var(--spacing-medium)' }}>
399
+ <Button
400
+ variant="secondary"
401
+ size="S"
402
+ onClick={addItem}
403
+ disabled={items.length >= initialItems.length + extraItems.length}
404
+ >
405
+ Add participant
406
+ </Button>
407
+ <Button
408
+ variant="secondary"
409
+ size="S"
410
+ onClick={removeItem}
411
+ disabled={items.length <= 1}
412
+ >
413
+ Remove participant
414
+ </Button>
415
+ </div>
416
+ <p
417
+ className="ds-text"
418
+ style={{ margin: 0, color: 'var(--color-grey-600)' }}
419
+ >
420
+ {items.length}
421
+ {' '}
422
+ participant
423
+ {items.length !== 1 ? 's' : ''}
424
+ {' '}
425
+ total — overflow badge shows
426
+ {' '}
427
+ {Math.max(0, items.length - 3)}
428
+ {' '}
429
+ hidden
430
+ </p>
431
+ </div>
432
+ );
433
+ };
434
+
435
+ // ---------------------------------------------------------------------------
436
+ // Stories
437
+ // ---------------------------------------------------------------------------
438
+
439
+ export const Default: Story = withDescription(
440
+ {
441
+ args: {
442
+ showMaxItems: 4,
443
+ listOrder: 'ascending',
444
+ label: 'Teaching staff',
445
+ size: 'medium',
446
+ presentAllUpdatesToScreenReader: false,
447
+ },
448
+ render: ({ showMaxItems, listOrder, label, size, presentAllUpdatesToScreenReader, className }) => (
449
+ <AvatarGroup
450
+ items={STAFF_ITEMS}
451
+ showMaxItems={showMaxItems}
452
+ listOrder={listOrder}
453
+ label={label}
454
+ size={size}
455
+ presentAllUpdatesToScreenReader={presentAllUpdatesToScreenReader}
456
+ className={className}
95
457
  />
96
- <Avatar
97
- size="small"
98
- initials="E"
99
- alt="User E"
458
+ ),
459
+ },
460
+ 'A fully interactive example — the `items` array is fixed at 7 staff members so the overflow badge is visible by default. '
461
+ + 'Use the **Controls** panel below to change `showMaxItems`, `size`, `listOrder`, and other props.',
462
+ );
463
+
464
+ export const ItemsApi: Story = withDescription(
465
+ {
466
+ parameters: {
467
+ controls: { disable: true },
468
+ docs: {
469
+ source: {
470
+ language: 'tsx',
471
+ code: `
472
+ import { AvatarGroup } from '@arbor-education/design-system.components';
473
+
474
+ const students = [
475
+ { initials: 'AT', alt: 'Aisha Thompson' },
476
+ { initials: 'CF', alt: 'Callum Fraser' },
477
+ { initials: 'FA', alt: 'Fatima Al-Rashid' },
478
+ { initials: 'OB', alt: 'Oliver Bennett' },
479
+ { initials: 'PS', alt: 'Priya Sharma' },
480
+ ];
481
+
482
+ function TutorGroupAvatars() {
483
+ return (
484
+ <AvatarGroup
485
+ items={students}
486
+ label="Tutor group 9C"
487
+ size="medium"
488
+ />
489
+ );
490
+ }
491
+ export default TutorGroupAvatars;
492
+ `.trim(),
493
+ },
494
+ },
495
+ },
496
+ render: () => (
497
+ <AvatarGroup
498
+ items={STUDENT_ITEMS.slice(0, 5)}
499
+ label="Tutor group 9C"
500
+ size="medium"
100
501
  />
502
+ ),
503
+ },
504
+ 'The **items API** is the recommended approach when avatar data arrives as a plain array (e.g. from an API response). '
505
+ + 'Pass an array of `AvatarGroupItem` values — each can be a plain `AvatarProps` object (`{ initials, alt, src, size, ... }`) '
506
+ + 'or a pre-rendered `<Avatar>` element.',
507
+ );
508
+
509
+ export const ChildrenApi: Story = withDescription(
510
+ {
511
+ parameters: {
512
+ controls: { disable: true },
513
+ docs: {
514
+ source: {
515
+ language: 'tsx',
516
+ code: `
517
+ import { Avatar, AvatarGroup } from '@arbor-education/design-system.components';
518
+
519
+ function SLTAvatars() {
520
+ return (
521
+ <AvatarGroup label="Senior leadership team" size="medium">
522
+ <Avatar initials="MP" alt="Margaret Pemberton" />
523
+ <Avatar initials="DO" alt="David Okafor" />
524
+ <Avatar initials="SR" alt="Siobhan Reilly" />
525
+ <Avatar initials="RK" alt="Raj Krishnamurthy" />
101
526
  </AvatarGroup>
102
- ),
103
- };
527
+ );
528
+ }
529
+ export default SLTAvatars;
530
+ `.trim(),
531
+ },
532
+ },
533
+ },
534
+ render: () => (
535
+ <AvatarGroup label="Senior leadership team" size="medium">
536
+ <Avatar initials="MP" alt="Margaret Pemberton" />
537
+ <Avatar initials="DO" alt="David Okafor" />
538
+ <Avatar initials="SR" alt="Siobhan Reilly" />
539
+ <Avatar initials="RK" alt="Raj Krishnamurthy" />
540
+ </AvatarGroup>
541
+ ),
542
+ },
543
+ 'The **children API** lets you compose `<Avatar>` elements directly — useful when you already have Avatar '
544
+ + 'components in your render tree and just want to group them. '
545
+ + 'Fragments are supported: Avatars inside `<>...</>` wrappers are collected automatically. '
546
+ + 'The `items` and `children` APIs are mutually exclusive; pick one per component instance.',
547
+ );
548
+
549
+ export const AllSizes: Story = withDescription(
550
+ {
551
+ parameters: {
552
+ controls: { disable: true },
553
+ docs: {
554
+ source: {
555
+ language: 'tsx',
556
+ code: `
557
+ import { AvatarGroup } from '@arbor-education/design-system.components';
558
+
559
+ const staff = [
560
+ { initials: 'DZ', alt: 'Dorothy Zbornak' },
561
+ { initials: 'RN', alt: 'Rose Nylund' },
562
+ { initials: 'BD', alt: 'Blanche Devereaux' },
563
+ { initials: 'SP', alt: 'Sophia Petrillo' },
564
+ ];
104
565
 
105
- export const ComposableChildrenDescending: Story = {
106
- name: 'Composed + Descending Order',
107
- render: () => (
566
+ function AllSizesExample() {
567
+ return (
568
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
569
+ <AvatarGroup items={staff} size="small" label="Staff — small" showMaxItems={3} />
570
+ <AvatarGroup items={staff} size="medium" label="Staff — medium" showMaxItems={3} />
571
+ <AvatarGroup items={staff} size="large" label="Staff — large" showMaxItems={3} />
572
+ <AvatarGroup items={staff} size="extra-large" label="Staff — extra-large" showMaxItems={3} />
573
+ </div>
574
+ );
575
+ }
576
+ export default AllSizesExample;
577
+ `.trim(),
578
+ },
579
+ },
580
+ },
581
+ render: () => (
582
+ <div
583
+ style={{
584
+ display: 'flex',
585
+ flexDirection: 'column',
586
+ gap: 'var(--spacing-large)',
587
+ }}
588
+ >
589
+ {(['small', 'medium', 'large', 'extra-large'] as const).map(size => (
590
+ <div
591
+ key={size}
592
+ style={{
593
+ display: 'flex',
594
+ alignItems: 'center',
595
+ gap: 'var(--spacing-medium)',
596
+ }}
597
+ >
598
+ <span
599
+ className="ds-text"
600
+ style={{ width: '6rem', color: 'var(--color-grey-600)', flexShrink: 0 }}
601
+ >
602
+ {size}
603
+ </span>
604
+ <AvatarGroup
605
+ items={STAFF_ITEMS.slice(0, 4)}
606
+ size={size}
607
+ label={`Staff group — ${size}`}
608
+ showMaxItems={3}
609
+ />
610
+ </div>
611
+ ))}
612
+ </div>
613
+ ),
614
+ },
615
+ 'All four avatar sizes — `small`, `medium`, `large`, and `extra-large` — shown side by side. '
616
+ + 'Use `size` on the group (not on individual items) to keep the stack visually consistent.',
617
+ );
618
+
619
+ export const GroupSizeOverride: Story = withDescription(
620
+ {
621
+ parameters: {
622
+ controls: { disable: true },
623
+ docs: {
624
+ source: {
625
+ language: 'tsx',
626
+ code: `
627
+ import { AvatarGroup } from '@arbor-education/design-system.components';
628
+
629
+ // Each item declares a different individual size
630
+ const mixedItems = [
631
+ { initials: 'DZ', alt: 'Dorothy Zbornak', size: 'small' as const },
632
+ { initials: 'RN', alt: 'Rose Nylund', size: 'extra-large' as const },
633
+ { initials: 'BD', alt: 'Blanche Devereaux', size: 'large' as const },
634
+ { initials: 'SP', alt: 'Sophia Petrillo', size: 'small' as const },
635
+ ];
636
+
637
+ function GroupSizeOverrideExample() {
638
+ return (
639
+ <>
640
+ {/* Without group size — renders mixed, inconsistent heights */}
641
+ <AvatarGroup items={mixedItems} label="Mixed sizes — no override" />
642
+
643
+ {/* With group size="medium" — ALL avatars flatten to medium */}
644
+ <AvatarGroup items={mixedItems} size="medium" label="Group size override to medium" />
645
+ </>
646
+ );
647
+ }
648
+ export default GroupSizeOverrideExample;
649
+ `.trim(),
650
+ },
651
+ },
652
+ },
653
+ render: () => {
654
+ const mixedItems: AvatarGroup.Item[] = [
655
+ { initials: 'DZ', alt: 'Dorothy Zbornak', size: 'small' },
656
+ { initials: 'RN', alt: 'Rose Nylund', size: 'extra-large' },
657
+ { initials: 'BD', alt: 'Blanche Devereaux', size: 'large' },
658
+ { initials: 'SP', alt: 'Sophia Petrillo', size: 'small' },
659
+ ];
660
+ return (
661
+ <div
662
+ style={{
663
+ display: 'flex',
664
+ flexDirection: 'column',
665
+ gap: 'var(--spacing-large)',
666
+ }}
667
+ >
668
+ <div>
669
+ <p
670
+ className="ds-text"
671
+ style={{ marginBottom: 'var(--spacing-small)', color: 'var(--color-grey-600)' }}
672
+ >
673
+ Without group size — mixed individual sizes (small / extra-large / large / small):
674
+ </p>
675
+ <AvatarGroup items={mixedItems} label="Mixed sizes — no group override" />
676
+ </div>
677
+ <div>
678
+ <p
679
+ className="ds-text"
680
+ style={{ marginBottom: 'var(--spacing-small)', color: 'var(--color-grey-600)' }}
681
+ >
682
+ With
683
+ {' '}
684
+ <code>size="medium"</code>
685
+ {' '}
686
+ on the group — individual sizes are overridden:
687
+ </p>
688
+ <AvatarGroup items={mixedItems} size="medium" label="Mixed sizes — group override to medium" />
689
+ </div>
690
+ </div>
691
+ );
692
+ },
693
+ },
694
+ 'When you pass `size` to the group, it **overrides** any `size` set on individual items. '
695
+ + 'The top row has four items each declaring a different size — the result is visually inconsistent. '
696
+ + 'The bottom row adds `size="medium"` to the group and all avatars snap to the same height.',
697
+ );
698
+
699
+ export const WithOverflow: Story = withDescription(
700
+ {
701
+ parameters: {
702
+ controls: { disable: true },
703
+ docs: {
704
+ source: {
705
+ language: 'tsx',
706
+ code: `
707
+ import { AvatarGroup } from '@arbor-education/design-system.components';
708
+
709
+ const students = [
710
+ { initials: 'AT', alt: 'Aisha Thompson' },
711
+ { initials: 'CF', alt: 'Callum Fraser' },
712
+ { initials: 'FA', alt: 'Fatima Al-Rashid' },
713
+ { initials: 'OB', alt: 'Oliver Bennett' },
714
+ { initials: 'PS', alt: 'Priya Sharma' },
715
+ { initials: 'DM', alt: 'Declan Murphy' },
716
+ { initials: 'YT', alt: 'Yuki Tanaka' },
717
+ { initials: 'AO', alt: 'Amara Osei' },
718
+ ];
719
+
720
+ function OverflowExample() {
721
+ return (
108
722
  <AvatarGroup
109
- label="Last three visible"
723
+ items={students}
724
+ showMaxItems={4}
725
+ label="Year 8 English students"
110
726
  size="medium"
111
- showMaxItems={3}
112
- listOrder="descending"
113
- >
114
- <Avatar
115
- initials="A"
116
- alt="User A"
117
- />
118
- <Avatar
119
- initials="B"
120
- alt="User B"
121
- />
122
- <Avatar
123
- initials="C"
124
- alt="User C"
727
+ />
728
+ );
729
+ }
730
+ export default OverflowExample;
731
+ `.trim(),
732
+ },
733
+ },
734
+ },
735
+ render: () => (
736
+ <AvatarGroup
737
+ items={STUDENT_ITEMS}
738
+ showMaxItems={4}
739
+ label="Year 8 English students"
740
+ size="medium"
125
741
  />
126
- <Avatar
127
- initials="D"
128
- alt="User D"
742
+ ),
743
+ },
744
+ '`showMaxItems` caps the rendered avatars and replaces the remainder with a `+N` overflow badge. '
745
+ + 'Here 8 students are in the list but only 4 are shown — the badge reads **+4**. '
746
+ + 'The badge carries `role="status"` and an accessible label (`"plus 4 more"` by default).',
747
+ );
748
+
749
+ export const DescendingOverflow: Story = withDescription(
750
+ {
751
+ parameters: {
752
+ controls: { disable: true },
753
+ docs: {
754
+ source: {
755
+ language: 'tsx',
756
+ code: `
757
+ import { AvatarGroup } from '@arbor-education/design-system.components';
758
+
759
+ // Items ordered chronologically — most recent is last in the array
760
+ const recentContributors = [
761
+ { initials: 'HN', alt: 'Harriet Nwosu' }, // oldest
762
+ { initials: 'LM', alt: 'Leo Marchetti' },
763
+ { initials: 'SuP', alt: 'Sunita Patel' },
764
+ { initials: 'TH', alt: 'Tomás Herrera' },
765
+ { initials: 'IC', alt: 'Imogen Clarke' },
766
+ { initials: 'KA', alt: 'Kwame Asante' }, // most recent
767
+ ];
768
+
769
+ function DescendingExample() {
770
+ return (
771
+ <>
772
+ {/* ascending — shows FIRST 3 (Harriet, Leo, Sunita) */}
773
+ <AvatarGroup
774
+ items={recentContributors}
775
+ showMaxItems={3}
776
+ listOrder="ascending"
777
+ label="Recent contributors — ascending"
129
778
  />
130
- <Avatar
131
- initials="E"
132
- alt="User E"
779
+
780
+ {/* descending — shows LAST 3 (Tomás, Imogen, Kwame) */}
781
+ <AvatarGroup
782
+ items={recentContributors}
783
+ showMaxItems={3}
784
+ listOrder="descending"
785
+ label="Recent contributors — descending"
133
786
  />
134
- </AvatarGroup>
135
- ),
136
- };
787
+ </>
788
+ );
789
+ }
790
+ export default DescendingExample;
791
+ `.trim(),
792
+ },
793
+ },
794
+ },
795
+ render: () => {
796
+ const contributors: AvatarGroup.Item[] = [
797
+ { initials: 'HN', alt: 'Harriet Nwosu' },
798
+ { initials: 'LM', alt: 'Leo Marchetti' },
799
+ { initials: 'SuP', alt: 'Sunita Patel' },
800
+ { initials: 'TH', alt: 'Tomás Herrera' },
801
+ { initials: 'IC', alt: 'Imogen Clarke' },
802
+ { initials: 'KA', alt: 'Kwame Asante' },
803
+ ];
804
+ return (
805
+ <div
806
+ style={{
807
+ display: 'flex',
808
+ flexDirection: 'column',
809
+ gap: 'var(--spacing-large)',
810
+ }}
811
+ >
812
+ <div>
813
+ <p
814
+ className="ds-text"
815
+ style={{ marginBottom: 'var(--spacing-small)', color: 'var(--color-grey-600)' }}
816
+ >
817
+ <code>listOrder="ascending"</code>
818
+ {' '}
819
+ — shows the
820
+ {' '}
821
+ <strong>first</strong>
822
+ {' '}
823
+ 3 (Harriet, Leo, Sunita):
824
+ </p>
825
+ <AvatarGroup
826
+ items={contributors}
827
+ showMaxItems={3}
828
+ listOrder="ascending"
829
+ label="Recent contributors — ascending"
830
+ size="medium"
831
+ />
832
+ </div>
833
+ <div>
834
+ <p
835
+ className="ds-text"
836
+ style={{ marginBottom: 'var(--spacing-small)', color: 'var(--color-grey-600)' }}
837
+ >
838
+ <code>listOrder="descending"</code>
839
+ {' '}
840
+ — shows the
841
+ {' '}
842
+ <strong>last</strong>
843
+ {' '}
844
+ 3 (Tomás, Imogen, Kwame):
845
+ </p>
846
+ <AvatarGroup
847
+ items={contributors}
848
+ showMaxItems={3}
849
+ listOrder="descending"
850
+ label="Recent contributors — descending"
851
+ size="medium"
852
+ />
853
+ </div>
854
+ </div>
855
+ );
856
+ },
857
+ },
858
+ '`listOrder` controls **which** items are shown when `showMaxItems` trims the list. '
859
+ + '`ascending` (default) keeps the **first** N items; `descending` keeps the **last** N items. '
860
+ + 'This is ideal for "most recent contributors" patterns where new entries are appended to the end of the array.',
861
+ );
137
862
 
138
- export const AscendingWithOverflow: Story = {
139
- render: () => (
140
- <AvatarGroup
141
- label="Ascending overflow"
142
- listOrder="ascending"
143
- showMaxItems={3}
144
- items={[
145
- { initials: 'A', alt: 'User A' },
146
- { initials: 'B', alt: 'User B' },
147
- { initials: 'C', alt: 'User C' },
148
- { initials: 'D', alt: 'User D' },
149
- { initials: 'E', alt: 'User E' },
150
- ]}
151
- />
152
- ),
153
- };
863
+ export const CustomOverflowLabel: Story = withDescription(
864
+ {
865
+ parameters: {
866
+ controls: { disable: true },
867
+ docs: {
868
+ source: {
869
+ language: 'tsx',
870
+ code: `
871
+ import { AvatarGroup } from '@arbor-education/design-system.components';
154
872
 
155
- export const DescendingWithOverflow: Story = {
156
- render: () => (
873
+ function CustomOverflowLabelExample() {
874
+ return (
157
875
  <AvatarGroup
158
- label="Descending overflow"
159
- listOrder="descending"
876
+ items={students}
160
877
  showMaxItems={3}
161
- items={[
162
- { initials: 'A', alt: 'User A' },
163
- { initials: 'B', alt: 'User B' },
164
- { initials: 'C', alt: 'User C' },
165
- { initials: 'D', alt: 'User D' },
166
- { initials: 'E', alt: 'User E' },
167
- ]}
878
+ label="Students in tutor group 7B"
879
+ size="medium"
880
+ overflowCountLabel={(n) => \`\${n} more students not shown\`}
168
881
  />
169
- ),
170
- };
171
-
172
- export const CustomOverflowLabel: Story = {
173
- parameters: {
174
- docs: {
175
- description: {
176
- story:
177
- 'Custom `overflowCountLabel` values replace the full screen-reader label for the overflow badge instead of appending to the visible `+N` text.',
882
+ );
883
+ }
884
+ export default CustomOverflowLabelExample;
885
+ `.trim(),
886
+ },
178
887
  },
179
888
  },
889
+ render: () => (
890
+ <AvatarGroup
891
+ items={STUDENT_ITEMS}
892
+ showMaxItems={3}
893
+ label="Students in tutor group 7B"
894
+ size="medium"
895
+ overflowCountLabel={n => `${n} more students not shown`}
896
+ />
897
+ ),
180
898
  },
181
- render: () => (
899
+ '`overflowCountLabel` accepts a **function** receiving the overflow count and returning an accessible label string. '
900
+ + 'This example produces `aria-label="5 more students not shown"` on the badge — far more descriptive than the default `"plus 5 more"`. '
901
+ + 'A plain `string` also works if the label does not need to reflect the count dynamically.',
902
+ );
903
+
904
+ export const WithAriaLabel: Story = withDescription(
905
+ {
906
+ parameters: {
907
+ controls: { disable: true },
908
+ docs: {
909
+ source: {
910
+ language: 'tsx',
911
+ code: `
912
+ import { AvatarGroup } from '@arbor-education/design-system.components';
913
+
914
+ function WithAriaLabelExample() {
915
+ return (
916
+ // Always supply label — without it, screen readers announce a nameless list
182
917
  <AvatarGroup
183
- label="Custom overflow copy"
184
- showMaxItems={2}
185
- overflowCountLabel={n => `${n} more students`}
186
- items={[
187
- { initials: 'J', alt: 'Jacob' },
188
- { initials: 'E', alt: 'Edward' },
189
- { initials: 'B', alt: 'Bella' },
190
- ]}
918
+ items={formTutors}
919
+ label="Form tutors for Year 11"
920
+ size="medium"
191
921
  />
192
- ),
193
- };
922
+ );
923
+ }
924
+ export default WithAriaLabelExample;
925
+ `.trim(),
926
+ },
927
+ },
928
+ },
929
+ render: () => (
930
+ <div
931
+ style={{
932
+ display: 'flex',
933
+ flexDirection: 'column',
934
+ gap: 'var(--spacing-large)',
935
+ }}
936
+ >
937
+ <div>
938
+ <p
939
+ className="ds-text"
940
+ style={{ marginBottom: 'var(--spacing-small)', color: 'var(--color-grey-600)' }}
941
+ >
942
+ With
943
+ {' '}
944
+ <code>label</code>
945
+ {' '}
946
+ — screen reader announces &ldquo;Form tutors for Year 11, list, 4 items&rdquo;:
947
+ </p>
948
+ <AvatarGroup items={STAFF_ITEMS.slice(0, 4)} label="Form tutors for Year 11" size="medium" />
949
+ </div>
950
+ <div>
951
+ <p
952
+ className="ds-text"
953
+ style={{ marginBottom: 'var(--spacing-small)', color: 'var(--color-semantic-destructive-600)' }}
954
+ >
955
+ Without
956
+ {' '}
957
+ <code>label</code>
958
+ {' '}
959
+ — screen reader announces &ldquo;list, 4 items&rdquo; (avoid in production):
960
+ </p>
961
+ <AvatarGroup items={STAFF_ITEMS.slice(0, 4)} size="medium" />
962
+ </div>
963
+ </div>
964
+ ),
965
+ },
966
+ 'The `label` prop sets `aria-label` on the root `<ul>`. '
967
+ + 'Without it, screen-reader users hear "list of N items" with no context about what the group represents. '
968
+ + '**Always provide a meaningful label in production.**',
969
+ );
194
970
 
195
- export const AnnouncedOverflowUpdates: Story = {
196
- name: 'Announced Overflow Updates',
197
- parameters: {
198
- docs: {
199
- description: {
200
- story:
201
- 'Enables polite live announcements for the built-in overflow badge. Overflow members are not rendered; only the count is shown.',
971
+ export const LiveOverflowCounter: Story = withDescription(
972
+ {
973
+ render: () => <LiveOverflowCounterTemplate />,
974
+ parameters: {
975
+ controls: { disable: true },
976
+ docs: {
977
+ source: {
978
+ language: 'tsx',
979
+ code: `
980
+ import { useState } from 'react';
981
+ import { Button, AvatarGroup } from '@arbor-education/design-system.components';
982
+
983
+ function LiveOverflowCounterExample() {
984
+ const [items, setItems] = useState([
985
+ { initials: 'AT', alt: 'Aisha Thompson' },
986
+ { initials: 'CF', alt: 'Callum Fraser' },
987
+ { initials: 'FA', alt: 'Fatima Al-Rashid' },
988
+ ]);
989
+
990
+ return (
991
+ <>
992
+ <AvatarGroup
993
+ items={items}
994
+ showMaxItems={3}
995
+ size="medium"
996
+ label="Active session participants"
997
+ overflowCountLabel={(n) => \`\${n} more participants\`}
998
+ presentAllUpdatesToScreenReader
999
+ />
1000
+ <Button
1001
+ variant="secondary"
1002
+ size="S"
1003
+ onClick={() => setItems(prev => [...prev, { initials: 'NP', alt: 'New Participant' }])}
1004
+ >
1005
+ Add participant
1006
+ </Button>
1007
+ </>
1008
+ );
1009
+ }
1010
+ export default LiveOverflowCounterExample;
1011
+ `.trim(),
1012
+ },
202
1013
  },
203
1014
  },
204
1015
  },
205
- render: () => (
206
- <AvatarGroup
207
- label="Live updates team"
208
- showMaxItems={2}
209
- presentAllUpdatesToScreenReader
210
- items={[
211
- { initials: 'A', alt: 'User A' },
212
- { initials: 'B', alt: 'User B' },
213
- { initials: 'C', alt: 'User C' },
214
- { initials: 'D', alt: 'User D' },
215
- ]}
216
- />
217
- ),
218
- };
1016
+ 'A live demo of `presentAllUpdatesToScreenReader`. '
1017
+ + 'Use the buttons to add or remove participants — the overflow badge updates and each count change is announced '
1018
+ + 'to screen readers via `aria-live="polite"`. '
1019
+ + 'Only enable this on groups whose membership changes at runtime — on static groups it creates unnecessary noise.',
1020
+ );