@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,497 +1,655 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import { useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
+ import {
4
+ Heading as DocHeading,
5
+ Markdown,
6
+ Stories,
7
+ Subtitle,
8
+ Title,
9
+ } from '@storybook/addon-docs/blocks';
2
10
  import { ModalManager } from './ModalManager';
3
- import { ModalUtils } from '../../../utils/ModalUtils';
4
- import { Button } from '../../button/Button';
5
- import { Section } from '../../section/Section';
6
- import { FormField } from '../../formField/FormField';
7
- import { generateUuid } from '../../../utils/generateUuid';
8
- import { Modal } from '../Modal';
9
- import { fn } from 'storybook/test';
11
+ import { Modal } from 'Components/modal/Modal';
12
+ import { Button } from 'Components/button/Button';
13
+ import { FormField } from 'Components/formField/FormField';
10
14
  import { Icon } from 'Components/icon/Icon';
11
15
 
12
- const meta: Meta = {
16
+ // ---------------------------------------------------------------------------
17
+ // Docs page content
18
+ // ---------------------------------------------------------------------------
19
+
20
+ const DESCRIPTION_INTRO = [
21
+ '`ModalManager` is a singleton container that opens and closes modals programmatically via PubSub events.',
22
+ 'Mount it once near your app root, then call `ModalUtils.addModal(props)` from anywhere in the component',
23
+ 'tree — no prop drilling, no shared state. For modals that are tightly coupled to their trigger,',
24
+ 'prefer the raw [`Modal`](?path=/docs/components-modals-modal--docs) component with local state instead.',
25
+ ].join(' ');
26
+
27
+ const USAGE_GUIDANCE = [
28
+ '### When to use',
29
+ '',
30
+ '- **Deep action handlers** — triggering a modal from an API response, a table row action, or a',
31
+ ' notification handler that has no direct access to modal state',
32
+ '- **Cross-cutting concerns** — session-expiry dialogs, global error modals, or app-level confirmations',
33
+ ' that can be triggered from any module',
34
+ '- **Avoiding prop drilling** — when the modal trigger and the `Modal` render point are far apart in',
35
+ ' the component tree',
36
+ '',
37
+ '---',
38
+ '',
39
+ '### When NOT to use',
40
+ '',
41
+ '| Situation | Use instead |',
42
+ '|---|---|',
43
+ '| Modal is directly triggered by a parent component | Raw [`Modal`](?path=/docs/components-modals-modal--docs) with local `useState` |',
44
+ '| Multiple modals open simultaneously | Not supported — `ModalManager` holds one modal at a time |',
45
+ '| You need full control of `open`/`closeHandler` lifecycle | Raw `Modal` component |',
46
+ ].join('\n');
47
+
48
+ const DEVELOPER_NOTES = [
49
+ '### Setup',
50
+ '',
51
+ 'Mount `<ModalManager />` once, near your application root:',
52
+ '',
53
+ '```tsx',
54
+ "import { ModalManager } from '@arbor-education/design-system.components';",
55
+ '',
56
+ 'function App() {',
57
+ ' return (',
58
+ ' <>',
59
+ ' <ModalManager />',
60
+ ' <Router>',
61
+ ' {/* rest of your app */}',
62
+ ' </Router>',
63
+ ' </>',
64
+ ' );',
65
+ '}',
66
+ '```',
67
+ '',
68
+ '---',
69
+ '',
70
+ '### ModalUtils API',
71
+ '',
72
+ '```ts',
73
+ "import { ModalUtils } from '@arbor-education/design-system.components';",
74
+ '',
75
+ '// Open a modal — pass any ModalProps except open and closeHandler',
76
+ 'ModalUtils.addModal({',
77
+ ' title: "Confirm deletion",',
78
+ ' children: (',
79
+ ' <>',
80
+ ' <Modal.Body>Are you sure?</Modal.Body>',
81
+ ' <Modal.Footer>',
82
+ ' <Button onClick={() => ModalUtils.removeModal()}>Cancel</Button>',
83
+ ' <Button variant="primary-destructive" onClick={handleDelete}>Delete</Button>',
84
+ ' </Modal.Footer>',
85
+ ' </>',
86
+ ' ),',
87
+ '});',
88
+ '',
89
+ '// Close the current modal',
90
+ 'ModalUtils.removeModal();',
91
+ '',
92
+ '// Close all modals (also clears any queued modal state)',
93
+ 'ModalUtils.removeAllModals();',
94
+ '```',
95
+ '',
96
+ '---',
97
+ '',
98
+ '### Critical notes',
99
+ '',
100
+ '**Do NOT pass `closeHandler` via `ModalUtils.addModal()`.**',
101
+ '`ModalManager` always provides its own `closeHandler` — passing one in `modalProps` would override it',
102
+ 'and break the X button and Escape key dismissal.',
103
+ '',
104
+ '**`ModalManager` holds one modal at a time.**',
105
+ 'Calling `ModalUtils.addModal()` while a modal is open replaces it immediately. If you need a queue or',
106
+ 'multiple layered modals, manage that state yourself and call `addModal` once you are ready to show the next.',
107
+ '',
108
+ '**The `modal` prop sets an initial modal at mount.**',
109
+ 'Useful for route-based or server-rendered scenarios where the modal state is known before the component mounts.',
110
+ 'Once mounted, further changes are driven by `ModalUtils`.',
111
+ '',
112
+ '---',
113
+ '',
114
+ '### Accessibility',
115
+ '',
116
+ '- Focus trapping and restoration are inherited from the underlying `Modal` component',
117
+ '- `ModalManager` always provides a `closeHandler`, so the X button and Escape key are always active',
118
+ '- Ensure modal content includes an accessible title — use `title` prop or `Modal.Header` + `Modal.Title`',
119
+ '',
120
+ '---',
121
+ '',
122
+ '### TypeScript types',
123
+ '',
124
+ '```ts',
125
+ "import { ModalManager } from '@arbor-education/design-system.components';",
126
+ "import type { ModalProps } from '@arbor-education/design-system.components';",
127
+ '',
128
+ '// ModalManagerProps',
129
+ 'type ModalManagerProps = {',
130
+ ' modal?: ModalProps; // optional initial modal shown at mount',
131
+ '};',
132
+ '```',
133
+ ].join('\n');
134
+
135
+ const RELATED_COMPONENTS = [
136
+ '## Related components',
137
+ '',
138
+ '[Modal](?path=/docs/components-modals-modal--docs) · [Button](?path=/docs/components-button--docs) · [FormField](?path=/docs/components-formfield--docs)',
139
+ ].join('\n');
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // Docs page
143
+ // ---------------------------------------------------------------------------
144
+
145
+ function ModalManagerDocsPage() {
146
+ return (
147
+ <>
148
+ <Title />
149
+ <Subtitle />
150
+ <Markdown>{DESCRIPTION_INTRO}</Markdown>
151
+ <DocHeading>Usage guidance</DocHeading>
152
+ <Markdown>{USAGE_GUIDANCE}</Markdown>
153
+ <DocHeading>Developer notes</DocHeading>
154
+ <Markdown>{DEVELOPER_NOTES}</Markdown>
155
+ <DocHeading>Examples</DocHeading>
156
+ <Stories title="" />
157
+ <Markdown>{RELATED_COMPONENTS}</Markdown>
158
+ </>
159
+ );
160
+ }
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Meta
164
+ // ---------------------------------------------------------------------------
165
+
166
+ const meta = {
13
167
  title: 'Components/Modals/ModalManager',
168
+ component: ModalManager,
169
+ tags: ['autodocs'],
14
170
  parameters: {
15
- layout: 'fullscreen',
171
+ layout: 'padded',
16
172
  docs: {
17
- description: {
18
- component: 'The ModalManager is a container that manages the display of modals. It listens for events to add or remove modals.',
173
+ page: ModalManagerDocsPage,
174
+ },
175
+ },
176
+ argTypes: {
177
+ modal: {
178
+ control: false,
179
+ description: [
180
+ 'Optional initial modal to show at mount. Pass any `ModalProps` value — `open` and `closeHandler`',
181
+ 'are always managed by `ModalManager` itself and cannot be overridden.',
182
+ 'Once mounted, further changes are driven by `ModalUtils.addModal()` and `ModalUtils.removeModal()`.',
183
+ ].join(' '),
184
+ table: {
185
+ type: { summary: 'ModalProps' },
186
+ defaultValue: { summary: 'undefined' },
19
187
  },
20
188
  },
21
189
  },
22
-
23
- tags: ['autodocs'],
24
- };
190
+ } satisfies Meta<typeof ModalManager>;
25
191
 
26
192
  export default meta;
193
+ type Story = StoryObj<typeof meta>;
27
194
 
28
- type Story = StoryObj;
195
+ // ---------------------------------------------------------------------------
196
+ // Helper
197
+ // ---------------------------------------------------------------------------
29
198
 
30
- let modalCount = 0;
199
+ const withDescription = (story: Story, description: string): Story => ({
200
+ ...story,
201
+ parameters: {
202
+ ...story.parameters,
203
+ docs: {
204
+ ...story.parameters?.docs,
205
+ description: {
206
+ story: description,
207
+ },
208
+ },
209
+ },
210
+ });
31
211
 
32
- const addModalWithContent = (content: React.ReactNode) => {
33
- modalCount++;
34
- ModalUtils.addModal({
35
- children: content,
36
- });
37
- };
212
+ // ---------------------------------------------------------------------------
213
+ // Templates
214
+ // NOTE: Story templates use raw <Modal> with local useState so the portal
215
+ // renders to document.body and works correctly in Storybook's canvas context.
216
+ // Production code uses ModalUtils.addModal() — see each story's source code.
217
+ // ---------------------------------------------------------------------------
38
218
 
39
- const removeModal = () => {
40
- if (modalCount > 0) {
41
- modalCount--;
42
- }
43
- ModalUtils.removeModal();
219
+ const DefaultTemplate = () => {
220
+ const [open, setOpen] = useState(false);
221
+ return (
222
+ <>
223
+ <Modal
224
+ open={open}
225
+ closeHandler={() => setOpen(false)}
226
+ title="Assessment Period Added"
227
+ >
228
+ <Modal.Body>
229
+ <p>The Spring 2026 assessment period has been added successfully. Pupils will now be able to submit work for grading.</p>
230
+ </Modal.Body>
231
+ </Modal>
232
+ <Button onClick={() => setOpen(true)}>Open modal</Button>
233
+ </>
234
+ );
44
235
  };
45
236
 
46
- export const Default: Story = {
47
- render: () => (
237
+ const WithFooterActionsTemplate = () => {
238
+ const [open, setOpen] = useState(false);
239
+ return (
48
240
  <>
49
- <ModalManager />
50
- <div style={{ padding: '1rem', display: 'flex', gap: '1rem' }}>
51
- <Button
52
- onClick={() =>
53
- addModalWithContent(
54
- <>
55
- <Modal.Header>
56
- <Modal.Title>Simple Modal</Modal.Title>
57
- </Modal.Header>
58
- <Modal.Body>
59
- <p>This is a simple modal with basic content.</p>
60
- </Modal.Body>
61
- </>,
62
- )}
63
- >
64
- Open Simple Modal
65
- </Button>
66
- </div>
241
+ <Modal
242
+ open={open}
243
+ closeHandler={() => setOpen(false)}
244
+ title="Edit Pupil Details"
245
+ >
246
+ <Modal.Body>
247
+ <p>Update the details below. Changes will be saved to the pupil record immediately.</p>
248
+ </Modal.Body>
249
+ <Modal.Footer>
250
+ <div style={{ display: 'flex', gap: 'var(--spacing-small)', justifyContent: 'flex-end' }}>
251
+ <Button variant="secondary" onClick={() => setOpen(false)}>Cancel</Button>
252
+ <Button variant="primary" onClick={() => setOpen(false)}>Save changes</Button>
253
+ </div>
254
+ </Modal.Footer>
255
+ </Modal>
256
+ <Button onClick={() => setOpen(true)}>Edit pupil details</Button>
67
257
  </>
68
- ),
258
+ );
69
259
  };
70
260
 
71
- export const WithHeaderAndFooter: Story = {
72
- render: () => {
73
- const handleSave = () => {
74
- alert('Save clicked!');
75
- ModalUtils.removeModal();
76
- };
77
-
78
- const handleCancel = () => {
79
- ModalUtils.removeModal();
80
- };
261
+ const WithFormContentTemplate = () => {
262
+ const [open, setOpen] = useState(false);
263
+ return (
264
+ <>
265
+ <Modal open={open} closeHandler={() => setOpen(false)} title="Add New Staff Member">
266
+ <Modal.Body>
267
+ <FormField
268
+ id="staff-first-name"
269
+ label="First name"
270
+ inputType="text"
271
+ inputProps={{ placeholder: 'e.g. Sarah' }}
272
+ />
273
+ <FormField
274
+ id="staff-last-name"
275
+ label="Last name"
276
+ inputType="text"
277
+ inputProps={{ placeholder: 'e.g. Clarke' }}
278
+ />
279
+ <FormField
280
+ id="staff-email"
281
+ label="Email address"
282
+ inputType="text"
283
+ inputProps={{ placeholder: 'e.g. s.clarke@school.edu', type: 'email' }}
284
+ fieldDescription="The staff member will receive a login invitation at this address."
285
+ />
286
+ </Modal.Body>
287
+ <Modal.Footer>
288
+ <div style={{ display: 'flex', gap: 'var(--spacing-small)', justifyContent: 'flex-end' }}>
289
+ <Button variant="secondary" onClick={() => setOpen(false)}>Cancel</Button>
290
+ <Button variant="primary" onClick={() => setOpen(false)}>Add staff member</Button>
291
+ </div>
292
+ </Modal.Footer>
293
+ </Modal>
294
+ <Button onClick={() => setOpen(true)}>Add staff member</Button>
295
+ </>
296
+ );
297
+ };
81
298
 
82
- return (
83
- <>
84
- <ModalManager />
85
- <div style={{ padding: '1rem', display: 'flex', gap: '1rem' }}>
86
- <Button
87
- onClick={() =>
88
- addModalWithContent(
89
- <>
90
- <Modal.Header>
91
- <Modal.Title>Modal with Header and Footer</Modal.Title>
92
- </Modal.Header>
93
- <Modal.Body>
94
- <p>This modal has both a header with a close button and a footer with action buttons.</p>
95
- </Modal.Body>
96
- <Modal.Footer>
97
- <div style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}>
98
- <Button variant="secondary" onClick={handleCancel}>
99
- Cancel
100
- </Button>
101
- <Button variant="primary" onClick={handleSave}>
102
- Save Changes
103
- </Button>
104
- </div>
105
- </Modal.Footer>
106
- </>,
107
- )}
108
- >
109
- Open Modal with Footer
110
- </Button>
111
- </div>
112
- </>
113
- );
114
- },
299
+ const DeleteConfirmationTemplate = () => {
300
+ const [open, setOpen] = useState(false);
301
+ return (
302
+ <>
303
+ <Modal open={open} title="Remove Emily Clarke?">
304
+ <Modal.Body>
305
+ <p>
306
+ Removing Emily Clarke will permanently delete her assessment records for this term.
307
+ This action cannot be undone.
308
+ </p>
309
+ </Modal.Body>
310
+ <Modal.Footer>
311
+ <div style={{ display: 'flex', gap: 'var(--spacing-small)', justifyContent: 'flex-end' }}>
312
+ <Button variant="secondary" onClick={() => setOpen(false)}>Cancel</Button>
313
+ <Button variant="primary-destructive" onClick={() => setOpen(false)}>Remove pupil</Button>
314
+ </div>
315
+ </Modal.Footer>
316
+ </Modal>
317
+ <Button variant="secondary-destructive" onClick={() => setOpen(true)}>Remove pupil</Button>
318
+ </>
319
+ );
115
320
  };
116
321
 
117
- export const WithSections: Story = {
118
- render: () => (
322
+ const ErrorModalTemplate = () => {
323
+ const [open, setOpen] = useState(false);
324
+ return (
119
325
  <>
120
- <ModalManager />
121
- <div style={{ padding: '1rem', display: 'flex', gap: '1rem' }}>
122
- <Button
123
- onClick={() =>
124
- addModalWithContent(
125
- <>
126
- <Modal.Header>
127
- <Modal.Title>Modal with Sections</Modal.Title>
128
- </Modal.Header>
129
- <Modal.Body>
130
- <Section title="Section 1" collapsible>
131
- <p>
132
- Aliquip ea tempor officia irure do qui culpa. Laborum proident laboris aliqua ad Lorem eiusmod fugiat dolore qui occaecat adipisicing.
133
- Dolore quis nisi occaecat commodo labore ad nulla aliquip in. Enim labore nisi laborum nostrud officia ad nulla ut quis.
134
- Aute excepteur quis amet aliquip. Excepteur Lorem cupidatat adipisicing consectetur ipsum Lorem.
135
- </p>
136
- </Section>
137
- <Section title="Section 2" collapsible collapsed>
138
- <p>
139
- Veniam adipisicing culpa id esse duis in officia consequat veniam ad ullamco sint sunt anim.
140
- Incididunt exercitation dolor Lorem anim aute ad deserunt. Nulla culpa sint aliqua ea Lorem sint.
141
- Elit sit consectetur minim cillum consectetur pariatur Lorem labore consectetur nisi consectetur officia exercitation.
142
- Est consectetur commodo deserunt ipsum aliqua occaecat qui sunt consectetur.
143
- </p>
144
- </Section>
145
- <Section title="Section 3" collapsible>
146
- <p>
147
- Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
148
- totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
149
- </p>
150
- </Section>
151
- </Modal.Body>
152
- </>,
153
- )}
154
- >
155
- Open Modal with Sections
156
- </Button>
157
- </div>
326
+ <Modal open={open} closeHandler={() => setOpen(false)}>
327
+ <Modal.Header>
328
+ <Icon name="triangle-alert" size={24} color="var(--color-semantic-destructive-500)" />
329
+ <Modal.Title>Sorry, we cannot find this page</Modal.Title>
330
+ </Modal.Header>
331
+ <Modal.Body>
332
+ <p>
333
+ <strong>Please try finding the page using the navigation.</strong>
334
+ {' '}
335
+ If you cannot find what you are looking for, contact support with the error ID below.
336
+ </p>
337
+ <p style={{ marginTop: 'var(--spacing-small)', fontFamily: 'monospace', fontSize: 'var(--font-size-2-13)' }}>
338
+ ID: f47ecbd4-319b-11f0-a5ae-06f9b8bc5d95
339
+ </p>
340
+ </Modal.Body>
341
+ <Modal.Footer>
342
+ <div style={{ display: 'flex', gap: 'var(--spacing-small)', justifyContent: 'flex-end' }}>
343
+ <Button variant="tertiary" onClick={() => setOpen(false)}>Close</Button>
344
+ <Button variant="primary" onClick={() => setOpen(false)}>Contact support</Button>
345
+ </div>
346
+ </Modal.Footer>
347
+ </Modal>
348
+ <Button onClick={() => setOpen(true)}>Simulate page not found</Button>
158
349
  </>
159
- ),
350
+ );
160
351
  };
161
352
 
162
- export const WithFormFields: Story = {
163
- render: () => {
164
- const handleSubmit = () => {
165
- alert('Form submitted!');
166
- ModalUtils.removeModal();
167
- };
353
+ // ---------------------------------------------------------------------------
354
+ // Stories
355
+ // ---------------------------------------------------------------------------
168
356
 
169
- return (
170
- <>
171
- <ModalManager />
172
- <div style={{ padding: '1rem', display: 'flex', gap: '1rem' }}>
173
- <Button
174
- onClick={() =>
175
- addModalWithContent(
176
- <>
177
- <Modal.Header>
178
- <Modal.Title>User Information Form</Modal.Title>
179
- </Modal.Header>
180
- <Modal.Body>
181
- <FormField
182
- id={generateUuid()}
183
- label="Your Name"
184
- inputType="text"
185
- inputProps={{ placeholder: 'e.g. Jane Doe' }}
186
- fieldDescription="Please enter your full name."
187
- />
188
- <FormField
189
- id={generateUuid()}
190
- label="Email Address"
191
- inputType="text"
192
- inputProps={{ placeholder: 'e.g. jane@example.com', type: 'email' }}
193
- />
194
- <FormField
195
- id={generateUuid()}
196
- label="Message"
197
- inputType="textarea"
198
- inputProps={{ placeholder: 'Enter your message here...', rows: 4 }}
199
- fieldDescription="Maximum 500 characters."
200
- />
201
- <FormField
202
- id={generateUuid()}
203
- label="Password"
204
- inputType="selectDropdown"
205
- inputProps={{
206
- options: [{ label: 'Option 1', value: 'option1' }, { label: 'Option 2', value: 'option2' }, { label: 'Option 3', value: 'option3' }],
207
- onSelectionChange: fn(),
208
- }}
209
- />
210
- </Modal.Body>
211
- <Modal.Footer>
212
- <div style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}>
213
- <Button variant="secondary" onClick={removeModal}>
214
- Cancel
215
- </Button>
216
- <Button variant="primary" onClick={handleSubmit}>
217
- Submit
218
- </Button>
219
- </div>
220
- </Modal.Footer>
221
- </>,
222
- )}
223
- >
224
- Open Form Modal
225
- </Button>
226
- </div>
227
- </>
228
- );
229
- },
230
- };
357
+ export const Default: Story = withDescription(
358
+ {
359
+ render: DefaultTemplate,
360
+ parameters: {
361
+ controls: { disable: true },
362
+ docs: {
363
+ source: {
364
+ language: 'tsx',
365
+ code: `
366
+ import { ModalManager, ModalUtils, Modal, Button } from '@arbor-education/design-system.components';
231
367
 
232
- export const WithScrollableContent: Story = {
233
- render: () => {
234
- const handleClose = () => {
235
- ModalUtils.removeModal();
236
- };
368
+ // Mount once at your app root
369
+ function App() {
370
+ return (
371
+ <>
372
+ <ModalManager />
373
+ {/* rest of your app */}
374
+ </>
375
+ );
376
+ }
237
377
 
238
- return (
239
- <>
240
- <ModalManager />
241
- <div style={{ padding: '1rem', display: 'flex', gap: '1rem' }}>
242
- <Button
243
- onClick={() =>
244
- addModalWithContent(
245
- <>
246
- <Modal.Header>
247
- <Modal.Title>Long Content Modal</Modal.Title>
248
- </Modal.Header>
249
- <Modal.Body>
250
- <Section title="Introduction" collapsible>
251
- <p>
252
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
253
- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
254
- </p>
255
- </Section>
256
- <Section title="Details" collapsible>
257
- <p>
258
- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
259
- Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
260
- </p>
261
- <FormField
262
- id={generateUuid()}
263
- label="Field 1"
264
- inputType="text"
265
- inputProps={{ placeholder: 'Enter value' }}
266
- />
267
- <FormField
268
- id={generateUuid()}
269
- label="Field 2"
270
- inputType="text"
271
- inputProps={{ placeholder: 'Enter value' }}
272
- />
273
- </Section>
274
- <Section title="Additional Information" collapsible>
275
- <p>
276
- Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
277
- totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
278
- Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
279
- eos qui ratione voluptatem sequi nesciunt.
280
- </p>
281
- <FormField
282
- id={generateUuid()}
283
- label="Long Text Field"
284
- inputType="textarea"
285
- inputProps={{ placeholder: 'Enter long text', rows: 6 }}
286
- />
287
- </Section>
288
- <Section title="More Content" collapsible>
289
- <p>
290
- Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit,
291
- sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
292
- Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam.
293
- </p>
294
- <FormField
295
- id={generateUuid()}
296
- label="Field 3"
297
- inputType="number"
298
- inputProps={{ placeholder: 'Enter number' }}
299
- />
300
- <FormField
301
- id={generateUuid()}
302
- label="Field 4"
303
- inputType="text"
304
- inputProps={{ placeholder: 'Enter value' }}
305
- />
306
- </Section>
307
- <Section title="Final Section" collapsible collapsed>
308
- <p>
309
- At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque
310
- corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa
311
- qui officia deserunt mollitia animi, id est laborum et dolorum fuga.
312
- </p>
313
- <FormField
314
- id={generateUuid()}
315
- label="Final Field"
316
- inputType="text"
317
- inputProps={{ placeholder: 'Enter value' }}
318
- />
319
- </Section>
320
- </Modal.Body>
321
- <Modal.Footer>
322
- <div style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}>
323
- <Button variant="secondary" onClick={handleClose}>
324
- Close
325
- </Button>
326
- <Button variant="primary" onClick={handleClose}>
327
- Save
328
- </Button>
329
- </div>
330
- </Modal.Footer>
331
- </>,
332
- )}
333
- >
334
- Open Scrollable Modal
335
- </Button>
336
- </div>
337
- </>
338
- );
378
+ // Then trigger from anywhere:
379
+ function AssessmentActions() {
380
+ return (
381
+ <Button
382
+ onClick={() =>
383
+ ModalUtils.addModal({
384
+ title: 'Assessment Period Added',
385
+ children: (
386
+ <Modal.Body>
387
+ <p>The Spring 2026 assessment period has been added successfully.</p>
388
+ </Modal.Body>
389
+ ),
390
+ })
391
+ }
392
+ >
393
+ Open modal
394
+ </Button>
395
+ );
396
+ }
397
+ export default AssessmentActions;
398
+ `.trim(),
399
+ },
400
+ },
401
+ },
339
402
  },
340
- };
403
+ [
404
+ 'The canonical `ModalManager` usage pattern. Mount `<ModalManager />` once at the app root,',
405
+ 'then call `ModalUtils.addModal(props)` from any component in the tree.',
406
+ '`ModalManager` always provides its own `closeHandler` — the X button and Escape key are always active.',
407
+ 'Click the button to open the modal, then dismiss it with the X button or by pressing Escape.',
408
+ ].join(' '),
409
+ );
341
410
 
342
- export const DestructiveAction: Story = {
343
- render: () => {
344
- const handleDelete = () => {
345
- alert('Item deleted!');
346
- ModalUtils.removeModal();
347
- };
411
+ export const WithFooterActions: Story = withDescription(
412
+ {
413
+ render: WithFooterActionsTemplate,
414
+ parameters: {
415
+ controls: { disable: true },
416
+ docs: {
417
+ source: {
418
+ language: 'tsx',
419
+ code: `
420
+ import { ModalUtils, Modal, Button } from '@arbor-education/design-system.components';
348
421
 
349
- const handleCancel = () => {
350
- ModalUtils.removeModal();
351
- };
422
+ function EditPupilButton() {
423
+ return (
424
+ <Button
425
+ onClick={() =>
426
+ ModalUtils.addModal({
427
+ title: 'Edit Pupil Details',
428
+ children: (
429
+ <>
430
+ <Modal.Body>
431
+ <p>Update the details below. Changes will be saved to the pupil record immediately.</p>
432
+ </Modal.Body>
433
+ <Modal.Footer>
434
+ <div style={{ display: 'flex', gap: 'var(--spacing-small)', justifyContent: 'flex-end' }}>
435
+ <Button variant="secondary" onClick={() => ModalUtils.removeModal()}>
436
+ Cancel
437
+ </Button>
438
+ <Button variant="primary" onClick={() => ModalUtils.removeModal()}>
439
+ Save changes
440
+ </Button>
441
+ </div>
442
+ </Modal.Footer>
443
+ </>
444
+ ),
445
+ })
446
+ }
447
+ >
448
+ Edit pupil details
449
+ </Button>
450
+ );
451
+ }
452
+ export default EditPupilButton;
453
+ `.trim(),
454
+ },
455
+ },
456
+ },
457
+ },
458
+ [
459
+ 'A modal with a full header, body, and footer. Use `Modal.Footer` for action buttons.',
460
+ 'Always place the primary action on the right and the cancel action on the left.',
461
+ 'Call `ModalUtils.removeModal()` inside button handlers to close the modal after the action completes.',
462
+ ].join(' '),
463
+ );
352
464
 
353
- return (
354
- <>
355
- <ModalManager />
356
- <div style={{ padding: '1rem', display: 'flex', gap: '1rem' }}>
357
- <Button
358
- onClick={() =>
359
- addModalWithContent(
360
- <>
361
- <Modal.Header>
362
- <Modal.Title>Confirm Deletion</Modal.Title>
363
- </Modal.Header>
364
- <Modal.Body>
365
- <p>Are you sure you want to delete this item? This action cannot be undone.</p>
366
- <p style={{ marginTop: '1rem', fontWeight: 'bold' }}>
367
- Item: Important Document.pdf
368
- </p>
369
- </Modal.Body>
370
- <Modal.Footer>
371
- <div style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}>
372
- <Button variant="secondary" onClick={handleCancel}>
373
- Cancel
374
- </Button>
375
- <Button variant="primary-destructive" onClick={handleDelete}>
376
- Delete
377
- </Button>
378
- </div>
379
- </Modal.Footer>
380
- </>,
381
- )}
382
- >
383
- Open Deletion Confirmation Modal
384
- </Button>
385
- </div>
386
- </>
387
- );
465
+ export const WithFormContent: Story = withDescription(
466
+ {
467
+ render: WithFormContentTemplate,
468
+ parameters: {
469
+ controls: { disable: true },
470
+ docs: {
471
+ source: {
472
+ language: 'tsx',
473
+ code: `
474
+ import { ModalUtils, Modal, Button, FormField } from '@arbor-education/design-system.components';
475
+
476
+ function AddStaffButton() {
477
+ return (
478
+ <Button
479
+ onClick={() =>
480
+ ModalUtils.addModal({
481
+ title: 'Add New Staff Member',
482
+ children: (
483
+ <>
484
+ <Modal.Body>
485
+ <FormField
486
+ id="staff-first-name"
487
+ label="First name"
488
+ inputType="text"
489
+ inputProps={{ placeholder: 'e.g. Sarah' }}
490
+ />
491
+ <FormField
492
+ id="staff-last-name"
493
+ label="Last name"
494
+ inputType="text"
495
+ inputProps={{ placeholder: 'e.g. Clarke' }}
496
+ />
497
+ <FormField
498
+ id="staff-email"
499
+ label="Email address"
500
+ inputType="text"
501
+ inputProps={{ placeholder: 'e.g. s.clarke@school.edu', type: 'email' }}
502
+ fieldDescription="The staff member will receive a login invitation at this address."
503
+ />
504
+ </Modal.Body>
505
+ <Modal.Footer>
506
+ <div style={{ display: 'flex', gap: 'var(--spacing-small)', justifyContent: 'flex-end' }}>
507
+ <Button variant="secondary" onClick={() => ModalUtils.removeModal()}>Cancel</Button>
508
+ <Button variant="primary" onClick={() => ModalUtils.removeModal()}>Add staff member</Button>
509
+ </div>
510
+ </Modal.Footer>
511
+ </>
512
+ ),
513
+ })
514
+ }
515
+ >
516
+ Add staff member
517
+ </Button>
518
+ );
519
+ }
520
+ export default AddStaffButton;
521
+ `.trim(),
522
+ },
523
+ },
524
+ },
388
525
  },
389
- };
526
+ [
527
+ 'A modal containing a form with `FormField` components. The `Modal.Body` provides a scrollable',
528
+ 'container so long forms scroll independently of the header and footer.',
529
+ 'Wrap form fields in `Modal.Body` and action buttons in `Modal.Footer` to preserve this layout.',
530
+ ].join(' '),
531
+ );
390
532
 
391
- export const SimpleConfirmation: Story = {
392
- render: () => {
393
- const handleConfirm = () => {
394
- alert('Confirmed!');
395
- ModalUtils.removeModal();
396
- };
533
+ export const DeleteConfirmation: Story = withDescription(
534
+ {
535
+ render: DeleteConfirmationTemplate,
536
+ parameters: {
537
+ controls: { disable: true },
538
+ docs: {
539
+ source: {
540
+ language: 'tsx',
541
+ code: `
542
+ import { ModalUtils, Modal, Button } from '@arbor-education/design-system.components';
397
543
 
398
- const handleCancel = () => {
399
- ModalUtils.removeModal();
400
- };
544
+ function RemovePupilButton() {
545
+ const handleDelete = () => {
546
+ // perform the deletion...
547
+ ModalUtils.removeModal();
548
+ };
401
549
 
402
- return (
403
- <>
404
- <ModalManager />
405
- <div style={{ padding: '1rem', display: 'flex', gap: '1rem' }}>
406
- <Button
407
- onClick={() =>
408
- addModalWithContent(
409
- <>
410
- <Modal.Header>
411
- <Modal.Title>Confirm Action</Modal.Title>
412
- </Modal.Header>
413
- <Modal.Body>
414
- <p>Are you sure you want to proceed with this action?</p>
415
- </Modal.Body>
416
- <Modal.Footer>
417
- <div style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}>
418
- <Button variant="secondary" onClick={handleCancel}>
419
- No
420
- </Button>
421
- <Button variant="primary" onClick={handleConfirm}>
422
- Yes
423
- </Button>
424
- </div>
425
- </Modal.Footer>
426
- </>,
427
- )}
428
- >
429
- Open Confirmation Modal
430
- </Button>
431
- </div>
432
- </>
433
- );
550
+ return (
551
+ <Button
552
+ variant="secondary-destructive"
553
+ onClick={() =>
554
+ ModalUtils.addModal({
555
+ title: 'Remove Emily Clarke?',
556
+ children: (
557
+ <>
558
+ <Modal.Body>
559
+ <p>
560
+ Removing Emily Clarke will permanently delete her assessment records for this term.
561
+ This action cannot be undone.
562
+ </p>
563
+ </Modal.Body>
564
+ <Modal.Footer>
565
+ <div style={{ display: 'flex', gap: 'var(--spacing-small)', justifyContent: 'flex-end' }}>
566
+ <Button variant="secondary" onClick={() => ModalUtils.removeModal()}>
567
+ Cancel
568
+ </Button>
569
+ <Button variant="primary-destructive" onClick={handleDelete}>
570
+ Remove pupil
571
+ </Button>
572
+ </div>
573
+ </Modal.Footer>
574
+ </>
575
+ ),
576
+ })
577
+ }
578
+ >
579
+ Remove pupil
580
+ </Button>
581
+ );
582
+ }
583
+ export default RemovePupilButton;
584
+ `.trim(),
585
+ },
586
+ },
587
+ },
434
588
  },
435
- };
589
+ [
590
+ 'The Arbor delete confirmation pattern. Use `variant="secondary-destructive"` on the trigger button',
591
+ 'and `variant="primary-destructive"` on the confirm action inside the modal.',
592
+ 'Always name the item being deleted in the modal title so users are certain of what they are removing.',
593
+ 'Cancel goes on the left; the destructive action goes on the right.',
594
+ ].join(' '),
595
+ );
436
596
 
437
- export const ExampleErrorModal: Story = {
438
- render: () => {
439
- const handleConfirm = () => {
440
- ModalUtils.removeModal();
441
- };
597
+ export const ErrorModal: Story = withDescription(
598
+ {
599
+ render: ErrorModalTemplate,
600
+ parameters: {
601
+ controls: { disable: true },
602
+ docs: {
603
+ source: {
604
+ language: 'tsx',
605
+ code: `
606
+ import { ModalUtils, Modal, Button, Icon } from '@arbor-education/design-system.components';
442
607
 
443
- const handleCancel = () => {
444
- ModalUtils.removeModal();
445
- };
446
- return (
447
- <>
448
- <ModalManager />
449
- <div style={{ padding: '1rem', display: 'flex', gap: '1rem' }}>
450
- <Button
451
- onClick={() =>
452
- addModalWithContent(
453
- <>
454
- <Modal.Header>
455
- <Icon name="triangle-alert" size={24} color="var(--color-semantic-destructive-500)" />
456
- <Modal.Title>Sorry, we can’t find this page</Modal.Title>
457
- </Modal.Header>
458
- <Modal.Body>
459
- <span>
460
- <b>Please try finding the page using the navigation.</b>
461
- {' '}
462
- If you can't find what you're looking for, there are two options depending on your support plan:
463
- </span>
464
- <ul>
465
- <li>
466
- <b>Arbor supported:</b>
467
- {' '}
468
- Press here to send an email to our Support Team using our Contact Us form (the error ID will be added automatically).
469
- </li>
470
- <li>
471
- <b>Partner supported:</b>
472
- {' '}
473
- Please get in touch with your Support Partner, and let them know the error ID below.
474
- </li>
475
- </ul>
476
- <span>ID:f47ecbd4-319b-11f0-a5ae-06f9b8bc5d95</span>
477
- </Modal.Body>
478
- <Modal.Footer>
479
- <div style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}>
480
- <Button variant="tertiary" onClick={handleCancel}>
481
- Close
482
- </Button>
483
- <Button variant="primary" onClick={handleConfirm}>
484
- Primary Action
485
- </Button>
486
- </div>
487
- </Modal.Footer>
488
- </>,
489
- )}
490
- >
491
- Open Deletion Confirmation Modal
492
- </Button>
493
- </div>
494
- </>
495
- );
608
+ function SimulateError() {
609
+ return (
610
+ <Button
611
+ onClick={() =>
612
+ ModalUtils.addModal({
613
+ children: (
614
+ <>
615
+ <Modal.Header>
616
+ <Icon name="triangle-alert" size={24} color="var(--color-semantic-destructive-500)" />
617
+ <Modal.Title>Sorry, we cannot find this page</Modal.Title>
618
+ </Modal.Header>
619
+ <Modal.Body>
620
+ <p>
621
+ <strong>Please try finding the page using the navigation.</strong>{' '}
622
+ If you cannot find what you are looking for, contact support with the error ID below.
623
+ </p>
624
+ <p style={{ fontFamily: 'monospace', fontSize: 'var(--font-size-2-13)' }}>
625
+ ID: f47ecbd4-319b-11f0-a5ae-06f9b8bc5d95
626
+ </p>
627
+ </Modal.Body>
628
+ <Modal.Footer>
629
+ <div style={{ display: 'flex', gap: 'var(--spacing-small)', justifyContent: 'flex-end' }}>
630
+ <Button variant="tertiary" onClick={() => ModalUtils.removeModal()}>Close</Button>
631
+ <Button variant="primary" onClick={() => ModalUtils.removeModal()}>Contact support</Button>
632
+ </div>
633
+ </Modal.Footer>
634
+ </>
635
+ ),
636
+ })
637
+ }
638
+ >
639
+ Simulate page not found
640
+ </Button>
641
+ );
642
+ }
643
+ export default SimulateError;
644
+ `.trim(),
645
+ },
646
+ },
647
+ },
496
648
  },
497
- };
649
+ [
650
+ 'An application-level error modal using `Modal.Header` for custom icon + title composition.',
651
+ 'Note that `title` is omitted from `ModalUtils.addModal()` here — when using `Modal.Header` manually,',
652
+ 'omit the `title` prop to avoid rendering two titles. Add `hideCloseButton` if you need to suppress',
653
+ 'the auto-rendered X button when placing `Modal.CloseButton` inside the header.',
654
+ ].join(' '),
655
+ );