@arbor-education/design-system.components 0.25.8 → 1.0.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 (432) hide show
  1. package/.gather/instructions/project-overview.md +1 -1
  2. package/.github/CODEOWNERS +1 -0
  3. package/.snyk +3 -0
  4. package/CHANGELOG.md +20 -0
  5. package/CODEOWNERS +2 -1
  6. package/CONTRIBUTING.md +7 -5
  7. package/component-library.md +97 -3
  8. package/dist/components/datePicker/DatePicker.d.ts.map +1 -1
  9. package/dist/components/datePicker/DatePicker.js +8 -6
  10. package/dist/components/datePicker/DatePicker.js.map +1 -1
  11. package/dist/components/datePicker/DatePickerCalendar.d.ts +10 -0
  12. package/dist/components/datePicker/DatePickerCalendar.d.ts.map +1 -0
  13. package/dist/components/datePicker/DatePickerCalendar.js +9 -0
  14. package/dist/components/datePicker/DatePickerCalendar.js.map +1 -0
  15. package/dist/components/dateTimePicker/DateTimePicker.d.ts.map +1 -1
  16. package/dist/components/dateTimePicker/DateTimePicker.js +11 -8
  17. package/dist/components/dateTimePicker/DateTimePicker.js.map +1 -1
  18. package/dist/components/emptyState/EmptyState.d.ts +22 -0
  19. package/dist/components/emptyState/EmptyState.d.ts.map +1 -0
  20. package/dist/components/emptyState/EmptyState.js +11 -0
  21. package/dist/components/emptyState/EmptyState.js.map +1 -0
  22. package/dist/components/emptyState/EmptyState.stories.d.ts +117 -0
  23. package/dist/components/emptyState/EmptyState.stories.d.ts.map +1 -0
  24. package/dist/components/emptyState/EmptyState.stories.js +332 -0
  25. package/dist/components/emptyState/EmptyState.stories.js.map +1 -0
  26. package/dist/components/emptyState/EmptyState.test.d.ts +2 -0
  27. package/dist/components/emptyState/EmptyState.test.d.ts.map +1 -0
  28. package/dist/components/emptyState/EmptyState.test.js +80 -0
  29. package/dist/components/emptyState/EmptyState.test.js.map +1 -0
  30. package/dist/components/favourite/FavouriteButton.d.ts +18 -0
  31. package/dist/components/favourite/FavouriteButton.d.ts.map +1 -0
  32. package/dist/components/favourite/FavouriteButton.js +16 -0
  33. package/dist/components/favourite/FavouriteButton.js.map +1 -0
  34. package/dist/components/favourite/FavouriteButton.test.d.ts +2 -0
  35. package/dist/components/favourite/FavouriteButton.test.d.ts.map +1 -0
  36. package/dist/components/favourite/FavouriteButton.test.js +37 -0
  37. package/dist/components/favourite/FavouriteButton.test.js.map +1 -0
  38. package/dist/components/filterBar/FilterBar.d.ts +1 -1
  39. package/dist/components/filterBar/FilterBar.stories.d.ts +1 -1
  40. package/dist/components/icon/Icon.d.ts +1 -1
  41. package/dist/components/icon/Icon.d.ts.map +1 -1
  42. package/dist/components/icon/Icon.js +1 -1
  43. package/dist/components/icon/Icon.js.map +1 -1
  44. package/dist/components/pill/Pill.d.ts +3 -2
  45. package/dist/components/pill/Pill.d.ts.map +1 -1
  46. package/dist/components/pill/Pill.js +15 -9
  47. package/dist/components/pill/Pill.js.map +1 -1
  48. package/dist/components/pill/Pill.stories.d.ts +21 -3
  49. package/dist/components/pill/Pill.stories.d.ts.map +1 -1
  50. package/dist/components/pill/Pill.stories.js +275 -40
  51. package/dist/components/pill/Pill.stories.js.map +1 -1
  52. package/dist/components/pill/Pill.test.js +64 -3
  53. package/dist/components/pill/Pill.test.js.map +1 -1
  54. package/dist/components/sideNavigation/SideNavigation.d.ts +34 -0
  55. package/dist/components/sideNavigation/SideNavigation.d.ts.map +1 -0
  56. package/dist/components/sideNavigation/SideNavigation.js +30 -0
  57. package/dist/components/sideNavigation/SideNavigation.js.map +1 -0
  58. package/dist/components/sideNavigation/SideNavigation.stories.d.ts +72 -0
  59. package/dist/components/sideNavigation/SideNavigation.stories.d.ts.map +1 -0
  60. package/dist/components/sideNavigation/SideNavigation.stories.js +298 -0
  61. package/dist/components/sideNavigation/SideNavigation.stories.js.map +1 -0
  62. package/dist/components/sideNavigation/SideNavigation.test.d.ts +2 -0
  63. package/dist/components/sideNavigation/SideNavigation.test.d.ts.map +1 -0
  64. package/dist/components/sideNavigation/SideNavigation.test.js +74 -0
  65. package/dist/components/sideNavigation/SideNavigation.test.js.map +1 -0
  66. package/dist/components/sideNavigation/SideNavigationContext.d.ts +10 -0
  67. package/dist/components/sideNavigation/SideNavigationContext.d.ts.map +1 -0
  68. package/dist/components/sideNavigation/SideNavigationContext.js +15 -0
  69. package/dist/components/sideNavigation/SideNavigationContext.js.map +1 -0
  70. package/dist/components/{sidebarNav/SidebarNavGroup.d.ts → sideNavigation/SideNavigationGroup.d.ts} +3 -3
  71. package/dist/components/sideNavigation/SideNavigationGroup.d.ts.map +1 -0
  72. package/dist/components/sideNavigation/SideNavigationGroup.js +16 -0
  73. package/dist/components/sideNavigation/SideNavigationGroup.js.map +1 -0
  74. package/dist/components/sideNavigation/SideNavigationGroup.test.d.ts +2 -0
  75. package/dist/components/sideNavigation/SideNavigationGroup.test.d.ts.map +1 -0
  76. package/dist/components/sideNavigation/SideNavigationGroup.test.js +58 -0
  77. package/dist/components/sideNavigation/SideNavigationGroup.test.js.map +1 -0
  78. package/dist/components/{sidebarNav/SidebarNavItem.d.ts → sideNavigation/SideNavigationItem.d.ts} +11 -12
  79. package/dist/components/sideNavigation/SideNavigationItem.d.ts.map +1 -0
  80. package/dist/components/sideNavigation/SideNavigationItem.js +28 -0
  81. package/dist/components/sideNavigation/SideNavigationItem.js.map +1 -0
  82. package/dist/components/sideNavigation/SideNavigationItem.test.d.ts +2 -0
  83. package/dist/components/sideNavigation/SideNavigationItem.test.d.ts.map +1 -0
  84. package/dist/components/sideNavigation/SideNavigationItem.test.js +65 -0
  85. package/dist/components/sideNavigation/SideNavigationItem.test.js.map +1 -0
  86. package/dist/components/sideNavigation/SideNavigationItemFavourite.d.ts +8 -0
  87. package/dist/components/sideNavigation/SideNavigationItemFavourite.d.ts.map +1 -0
  88. package/dist/components/{sidebarNav/SidebarNavItemFavourite.js → sideNavigation/SideNavigationItemFavourite.js} +5 -5
  89. package/dist/components/sideNavigation/SideNavigationItemFavourite.js.map +1 -0
  90. package/dist/components/sideNavigation/SideNavigationItemFavourite.test.d.ts +2 -0
  91. package/dist/components/sideNavigation/SideNavigationItemFavourite.test.d.ts.map +1 -0
  92. package/dist/components/sideNavigation/SideNavigationItemFavourite.test.js +31 -0
  93. package/dist/components/sideNavigation/SideNavigationItemFavourite.test.js.map +1 -0
  94. package/dist/components/sideNavigation/SideNavigationTitle.d.ts +4 -0
  95. package/dist/components/sideNavigation/SideNavigationTitle.d.ts.map +1 -0
  96. package/dist/components/sideNavigation/SideNavigationTitle.js +7 -0
  97. package/dist/components/sideNavigation/SideNavigationTitle.js.map +1 -0
  98. package/dist/components/sideNavigation/SideNavigationTitle.test.d.ts +2 -0
  99. package/dist/components/sideNavigation/SideNavigationTitle.test.d.ts.map +1 -0
  100. package/dist/components/sideNavigation/SideNavigationTitle.test.js +22 -0
  101. package/dist/components/sideNavigation/SideNavigationTitle.test.js.map +1 -0
  102. package/dist/components/sideNavigation/SideNavigationTooltip.d.ts +7 -0
  103. package/dist/components/sideNavigation/SideNavigationTooltip.d.ts.map +1 -0
  104. package/dist/components/{sidebarNav/SidebarNavTooltip.js → sideNavigation/SideNavigationTooltip.js} +2 -2
  105. package/dist/components/sideNavigation/SideNavigationTooltip.js.map +1 -0
  106. package/dist/components/sideNavigation/SideNavigationTooltip.test.d.ts +2 -0
  107. package/dist/components/sideNavigation/SideNavigationTooltip.test.d.ts.map +1 -0
  108. package/dist/components/sideNavigation/SideNavigationTooltip.test.js +26 -0
  109. package/dist/components/sideNavigation/SideNavigationTooltip.test.js.map +1 -0
  110. package/dist/components/sideNavigation/resolvePanelItemProps.d.ts +4 -0
  111. package/dist/components/sideNavigation/resolvePanelItemProps.d.ts.map +1 -0
  112. package/dist/components/{sidebarNav → sideNavigation}/resolvePanelItemProps.js +5 -4
  113. package/dist/components/sideNavigation/resolvePanelItemProps.js.map +1 -0
  114. package/dist/components/sideNavigation/resolvePanelItemProps.test.d.ts.map +1 -0
  115. package/dist/components/{sidebarNav → sideNavigation}/resolvePanelItemProps.test.js +18 -4
  116. package/dist/components/sideNavigation/resolvePanelItemProps.test.js.map +1 -0
  117. package/dist/components/sideNavigation/types.d.ts +47 -0
  118. package/dist/components/sideNavigation/types.d.ts.map +1 -0
  119. package/dist/components/sideNavigation/types.js +2 -0
  120. package/dist/components/sideNavigation/types.js.map +1 -0
  121. package/dist/components/sidebar/Sidebar.d.ts +13 -0
  122. package/dist/components/sidebar/Sidebar.d.ts.map +1 -0
  123. package/dist/components/sidebar/Sidebar.js +9 -0
  124. package/dist/components/sidebar/Sidebar.js.map +1 -0
  125. package/dist/components/sidebar/Sidebar.stories.d.ts +48 -0
  126. package/dist/components/sidebar/Sidebar.stories.d.ts.map +1 -0
  127. package/dist/components/sidebar/Sidebar.stories.js +444 -0
  128. package/dist/components/sidebar/Sidebar.stories.js.map +1 -0
  129. package/dist/components/sidebar/Sidebar.test.d.ts +2 -0
  130. package/dist/components/sidebar/Sidebar.test.d.ts.map +1 -0
  131. package/dist/components/sidebar/Sidebar.test.js +81 -0
  132. package/dist/components/sidebar/Sidebar.test.js.map +1 -0
  133. package/dist/components/sidebar/SidebarItem.d.ts +7 -0
  134. package/dist/components/sidebar/SidebarItem.d.ts.map +1 -0
  135. package/dist/components/sidebar/SidebarItem.js +11 -0
  136. package/dist/components/sidebar/SidebarItem.js.map +1 -0
  137. package/dist/components/singleUser/SingleUser.d.ts +1 -1
  138. package/dist/components/tabs/TabsItem.stories.d.ts +2 -2
  139. package/dist/components/toast/Toast.d.ts +11 -2
  140. package/dist/components/toast/Toast.d.ts.map +1 -1
  141. package/dist/components/toast/Toast.js +5 -3
  142. package/dist/components/toast/Toast.js.map +1 -1
  143. package/dist/components/toast/Toast.stories.d.ts +15 -3
  144. package/dist/components/toast/Toast.stories.d.ts.map +1 -1
  145. package/dist/components/toast/Toast.stories.js +43 -41
  146. package/dist/components/toast/Toast.stories.js.map +1 -1
  147. package/dist/components/toast/Toast.test.js +19 -4
  148. package/dist/components/toast/Toast.test.js.map +1 -1
  149. package/dist/components/toast/ToastAction.d.ts +7 -0
  150. package/dist/components/toast/ToastAction.d.ts.map +1 -0
  151. package/dist/components/toast/ToastAction.js +9 -0
  152. package/dist/components/toast/ToastAction.js.map +1 -0
  153. package/dist/components/tooltip/TooltipWrapper.d.ts +1 -0
  154. package/dist/components/tooltip/TooltipWrapper.d.ts.map +1 -1
  155. package/dist/components/tooltip/TooltipWrapper.js +2 -2
  156. package/dist/components/tooltip/TooltipWrapper.js.map +1 -1
  157. package/dist/components/widget/Widget.d.ts +18 -0
  158. package/dist/components/widget/Widget.d.ts.map +1 -0
  159. package/dist/components/widget/Widget.js +51 -0
  160. package/dist/components/widget/Widget.js.map +1 -0
  161. package/dist/components/widget/Widget.stories.d.ts +128 -0
  162. package/dist/components/widget/Widget.stories.d.ts.map +1 -0
  163. package/dist/components/widget/Widget.stories.js +417 -0
  164. package/dist/components/widget/Widget.stories.js.map +1 -0
  165. package/dist/components/widget/Widget.test.d.ts +2 -0
  166. package/dist/components/widget/Widget.test.d.ts.map +1 -0
  167. package/dist/components/widget/Widget.test.js +110 -0
  168. package/dist/components/widget/Widget.test.js.map +1 -0
  169. package/dist/components/widget/WidgetBody.d.ts +6 -0
  170. package/dist/components/widget/WidgetBody.d.ts.map +1 -0
  171. package/dist/components/widget/WidgetBody.js +20 -0
  172. package/dist/components/widget/WidgetBody.js.map +1 -0
  173. package/dist/components/widget/WidgetContext.d.ts +20 -0
  174. package/dist/components/widget/WidgetContext.d.ts.map +1 -0
  175. package/dist/components/widget/WidgetContext.js +10 -0
  176. package/dist/components/widget/WidgetContext.js.map +1 -0
  177. package/dist/components/widget/WidgetHeader.d.ts +8 -0
  178. package/dist/components/widget/WidgetHeader.d.ts.map +1 -0
  179. package/dist/components/widget/WidgetHeader.js +56 -0
  180. package/dist/components/widget/WidgetHeader.js.map +1 -0
  181. package/dist/components/widget/types.d.ts +43 -0
  182. package/dist/components/widget/types.d.ts.map +1 -0
  183. package/dist/components/widget/types.js +2 -0
  184. package/dist/components/widget/types.js.map +1 -0
  185. package/dist/components/widgetCalendar/WidgetCalendar.d.ts +35 -0
  186. package/dist/components/widgetCalendar/WidgetCalendar.d.ts.map +1 -0
  187. package/dist/components/widgetCalendar/WidgetCalendar.js +22 -0
  188. package/dist/components/widgetCalendar/WidgetCalendar.js.map +1 -0
  189. package/dist/components/widgetCalendar/WidgetCalendar.stories.d.ts +60 -0
  190. package/dist/components/widgetCalendar/WidgetCalendar.stories.d.ts.map +1 -0
  191. package/dist/components/widgetCalendar/WidgetCalendar.stories.js +140 -0
  192. package/dist/components/widgetCalendar/WidgetCalendar.stories.js.map +1 -0
  193. package/dist/components/widgetCalendar/WidgetCalendar.test.d.ts +2 -0
  194. package/dist/components/widgetCalendar/WidgetCalendar.test.d.ts.map +1 -0
  195. package/dist/components/widgetCalendar/WidgetCalendar.test.js +88 -0
  196. package/dist/components/widgetCalendar/WidgetCalendar.test.js.map +1 -0
  197. package/dist/components/widgetCalendar/WidgetCalendarItem.d.ts +17 -0
  198. package/dist/components/widgetCalendar/WidgetCalendarItem.d.ts.map +1 -0
  199. package/dist/components/widgetCalendar/WidgetCalendarItem.js +25 -0
  200. package/dist/components/widgetCalendar/WidgetCalendarItem.js.map +1 -0
  201. package/dist/components/widgetFavourites/WidgetFavourites.d.ts +12 -0
  202. package/dist/components/widgetFavourites/WidgetFavourites.d.ts.map +1 -0
  203. package/dist/components/widgetFavourites/WidgetFavourites.js +23 -0
  204. package/dist/components/widgetFavourites/WidgetFavourites.js.map +1 -0
  205. package/dist/components/widgetFavourites/WidgetFavourites.stories.d.ts +84 -0
  206. package/dist/components/widgetFavourites/WidgetFavourites.stories.d.ts.map +1 -0
  207. package/dist/components/widgetFavourites/WidgetFavourites.stories.js +210 -0
  208. package/dist/components/widgetFavourites/WidgetFavourites.stories.js.map +1 -0
  209. package/dist/components/widgetFavourites/WidgetFavourites.test.d.ts +2 -0
  210. package/dist/components/widgetFavourites/WidgetFavourites.test.d.ts.map +1 -0
  211. package/dist/components/widgetFavourites/WidgetFavourites.test.js +67 -0
  212. package/dist/components/widgetFavourites/WidgetFavourites.test.js.map +1 -0
  213. package/dist/components/widgetFavourites/WidgetFavouritesItem.d.ts +3 -0
  214. package/dist/components/widgetFavourites/WidgetFavouritesItem.d.ts.map +1 -0
  215. package/dist/components/widgetFavourites/WidgetFavouritesItem.js +10 -0
  216. package/dist/components/widgetFavourites/WidgetFavouritesItem.js.map +1 -0
  217. package/dist/components/widgetFavourites/types.d.ts +25 -0
  218. package/dist/components/widgetFavourites/types.d.ts.map +1 -0
  219. package/dist/components/widgetFavourites/types.js +2 -0
  220. package/dist/components/widgetFavourites/types.js.map +1 -0
  221. package/dist/components/widgetNotifications/WidgetNotifications.d.ts +33 -0
  222. package/dist/components/widgetNotifications/WidgetNotifications.d.ts.map +1 -0
  223. package/dist/components/widgetNotifications/WidgetNotifications.js +18 -0
  224. package/dist/components/widgetNotifications/WidgetNotifications.js.map +1 -0
  225. package/dist/components/widgetNotifications/WidgetNotifications.stories.d.ts +60 -0
  226. package/dist/components/widgetNotifications/WidgetNotifications.stories.d.ts.map +1 -0
  227. package/dist/components/widgetNotifications/WidgetNotifications.stories.js +269 -0
  228. package/dist/components/widgetNotifications/WidgetNotifications.stories.js.map +1 -0
  229. package/dist/components/widgetNotifications/WidgetNotifications.test.d.ts +2 -0
  230. package/dist/components/widgetNotifications/WidgetNotifications.test.d.ts.map +1 -0
  231. package/dist/components/widgetNotifications/WidgetNotifications.test.js +79 -0
  232. package/dist/components/widgetNotifications/WidgetNotifications.test.js.map +1 -0
  233. package/dist/components/widgetNotifications/WidgetNotificationsItem.d.ts +14 -0
  234. package/dist/components/widgetNotifications/WidgetNotificationsItem.d.ts.map +1 -0
  235. package/dist/components/widgetNotifications/WidgetNotificationsItem.js +14 -0
  236. package/dist/components/widgetNotifications/WidgetNotificationsItem.js.map +1 -0
  237. package/dist/index.css +850 -205
  238. package/dist/index.css.map +1 -1
  239. package/dist/index.d.ts +15 -9
  240. package/dist/index.d.ts.map +1 -1
  241. package/dist/index.js +15 -8
  242. package/dist/index.js.map +1 -1
  243. package/dist/utils/favourites/favouriteConstants.d.ts +12 -0
  244. package/dist/utils/favourites/favouriteConstants.d.ts.map +1 -0
  245. package/dist/utils/favourites/favouriteConstants.js +16 -0
  246. package/dist/utils/favourites/favouriteConstants.js.map +1 -0
  247. package/dist/utils/favourites/favouriteConstants.test.d.ts +2 -0
  248. package/dist/utils/favourites/favouriteConstants.test.d.ts.map +1 -0
  249. package/dist/utils/favourites/favouriteConstants.test.js +12 -0
  250. package/dist/utils/favourites/favouriteConstants.test.js.map +1 -0
  251. package/dist/utils/hooks/useControllableBoolean.d.ts.map +1 -0
  252. package/dist/utils/hooks/useControllableBoolean.js.map +1 -0
  253. package/dist/utils/hooks/useControllableBoolean.test.d.ts +2 -0
  254. package/dist/utils/hooks/useControllableBoolean.test.d.ts.map +1 -0
  255. package/dist/utils/hooks/useControllableBoolean.test.js +34 -0
  256. package/dist/utils/hooks/useControllableBoolean.test.js.map +1 -0
  257. package/package.json +1 -1
  258. package/src/components/datePicker/DatePicker.tsx +30 -35
  259. package/src/components/datePicker/DatePickerCalendar.tsx +46 -0
  260. package/src/components/dateTimePicker/DateTimePicker.tsx +66 -38
  261. package/src/components/dateTimePicker/dateTimePicker.scss +0 -2
  262. package/src/components/emptyState/EmptyState.stories.tsx +487 -0
  263. package/src/components/emptyState/EmptyState.test.tsx +140 -0
  264. package/src/components/emptyState/EmptyState.tsx +84 -0
  265. package/src/components/emptyState/emptyState.scss +63 -0
  266. package/src/components/favourite/FavouriteButton.test.tsx +83 -0
  267. package/src/components/favourite/FavouriteButton.tsx +70 -0
  268. package/src/components/favourite/favouriteButton.scss +37 -0
  269. package/src/components/icon/Icon.tsx +2 -2
  270. package/src/components/link/link.scss +26 -0
  271. package/src/components/pill/Pill.stories.tsx +432 -41
  272. package/src/components/pill/Pill.test.tsx +74 -3
  273. package/src/components/pill/Pill.tsx +26 -11
  274. package/src/components/sideNavigation/SideNavigation.stories.tsx +578 -0
  275. package/src/components/sideNavigation/SideNavigation.test.tsx +109 -0
  276. package/src/components/sideNavigation/SideNavigation.tsx +98 -0
  277. package/src/components/sideNavigation/SideNavigationContext.tsx +25 -0
  278. package/src/components/sideNavigation/SideNavigationGroup.test.tsx +103 -0
  279. package/src/components/{sidebarNav/SidebarNavGroup.tsx → sideNavigation/SideNavigationGroup.tsx} +8 -8
  280. package/src/components/sideNavigation/SideNavigationItem.test.tsx +137 -0
  281. package/src/components/sideNavigation/SideNavigationItem.tsx +125 -0
  282. package/src/components/sideNavigation/SideNavigationItemFavourite.test.tsx +54 -0
  283. package/src/components/{sidebarNav/SidebarNavItemFavourite.tsx → sideNavigation/SideNavigationItemFavourite.tsx} +7 -7
  284. package/src/components/sideNavigation/SideNavigationTitle.test.tsx +23 -0
  285. package/src/components/sideNavigation/SideNavigationTitle.tsx +13 -0
  286. package/src/components/sideNavigation/SideNavigationTooltip.test.tsx +44 -0
  287. package/src/components/{sidebarNav/SidebarNavTooltip.tsx → sideNavigation/SideNavigationTooltip.tsx} +2 -2
  288. package/src/components/{sidebarNav → sideNavigation}/resolvePanelItemProps.test.ts +20 -4
  289. package/src/components/{sidebarNav → sideNavigation}/resolvePanelItemProps.ts +8 -7
  290. package/src/components/{sidebarNav/sidebarNav.scss → sideNavigation/sideNavigation.scss} +37 -136
  291. package/src/components/sideNavigation/types.ts +56 -0
  292. package/src/components/sidebar/Sidebar.stories.tsx +670 -0
  293. package/src/components/sidebar/Sidebar.test.tsx +155 -0
  294. package/src/components/sidebar/Sidebar.tsx +27 -0
  295. package/src/components/sidebar/SidebarItem.tsx +64 -0
  296. package/src/components/sidebar/sidebar.scss +65 -0
  297. package/src/components/toast/Toast.stories.tsx +75 -67
  298. package/src/components/toast/Toast.test.tsx +40 -10
  299. package/src/components/toast/Toast.tsx +19 -5
  300. package/src/components/toast/ToastAction.tsx +17 -0
  301. package/src/components/toast/toast.scss +37 -0
  302. package/src/components/tooltip/TooltipWrapper.tsx +3 -1
  303. package/src/components/widget/Widget.stories.tsx +600 -0
  304. package/src/components/widget/Widget.test.tsx +263 -0
  305. package/src/components/widget/Widget.tsx +102 -0
  306. package/src/components/widget/WidgetBody.tsx +66 -0
  307. package/src/components/widget/WidgetContext.tsx +31 -0
  308. package/src/components/widget/WidgetHeader.tsx +155 -0
  309. package/src/components/widget/types.ts +47 -0
  310. package/src/components/widget/widget.scss +218 -0
  311. package/src/components/widgetCalendar/WidgetCalendar.stories.tsx +241 -0
  312. package/src/components/widgetCalendar/WidgetCalendar.test.tsx +137 -0
  313. package/src/components/widgetCalendar/WidgetCalendar.tsx +105 -0
  314. package/src/components/widgetCalendar/WidgetCalendarItem.tsx +105 -0
  315. package/src/components/widgetCalendar/widgetCalendar.scss +124 -0
  316. package/src/components/widgetFavourites/WidgetFavourites.stories.tsx +341 -0
  317. package/src/components/widgetFavourites/WidgetFavourites.test.tsx +127 -0
  318. package/src/components/widgetFavourites/WidgetFavourites.tsx +83 -0
  319. package/src/components/widgetFavourites/WidgetFavouritesItem.tsx +37 -0
  320. package/src/components/widgetFavourites/types.ts +26 -0
  321. package/src/components/widgetFavourites/widgetFavourites.scss +84 -0
  322. package/src/components/widgetNotifications/WidgetNotifications.stories.tsx +417 -0
  323. package/src/components/widgetNotifications/WidgetNotifications.test.tsx +152 -0
  324. package/src/components/widgetNotifications/WidgetNotifications.tsx +112 -0
  325. package/src/components/widgetNotifications/WidgetNotificationsItem.tsx +88 -0
  326. package/src/components/widgetNotifications/widgetNotifications.scss +186 -0
  327. package/src/docs/Contributing.mdx +1 -1
  328. package/src/index.scss +9 -1
  329. package/src/index.ts +22 -18
  330. package/src/tokens.scss +12 -2
  331. package/src/utils/favourites/favouriteConstants.test.ts +19 -0
  332. package/src/utils/favourites/favouriteConstants.ts +21 -0
  333. package/src/utils/hooks/useControllableBoolean.test.ts +48 -0
  334. package/dist/components/sidebarNav/SidebarNav.d.ts +0 -46
  335. package/dist/components/sidebarNav/SidebarNav.d.ts.map +0 -1
  336. package/dist/components/sidebarNav/SidebarNav.js +0 -102
  337. package/dist/components/sidebarNav/SidebarNav.js.map +0 -1
  338. package/dist/components/sidebarNav/SidebarNav.stories.d.ts +0 -61
  339. package/dist/components/sidebarNav/SidebarNav.stories.d.ts.map +0 -1
  340. package/dist/components/sidebarNav/SidebarNav.stories.js +0 -253
  341. package/dist/components/sidebarNav/SidebarNav.stories.js.map +0 -1
  342. package/dist/components/sidebarNav/SidebarNav.test.d.ts +0 -2
  343. package/dist/components/sidebarNav/SidebarNav.test.d.ts.map +0 -1
  344. package/dist/components/sidebarNav/SidebarNav.test.js +0 -240
  345. package/dist/components/sidebarNav/SidebarNav.test.js.map +0 -1
  346. package/dist/components/sidebarNav/SidebarNavContext.d.ts +0 -13
  347. package/dist/components/sidebarNav/SidebarNavContext.d.ts.map +0 -1
  348. package/dist/components/sidebarNav/SidebarNavContext.js +0 -15
  349. package/dist/components/sidebarNav/SidebarNavContext.js.map +0 -1
  350. package/dist/components/sidebarNav/SidebarNavGroup.d.ts.map +0 -1
  351. package/dist/components/sidebarNav/SidebarNavGroup.js +0 -16
  352. package/dist/components/sidebarNav/SidebarNavGroup.js.map +0 -1
  353. package/dist/components/sidebarNav/SidebarNavItem.d.ts.map +0 -1
  354. package/dist/components/sidebarNav/SidebarNavItem.js +0 -44
  355. package/dist/components/sidebarNav/SidebarNavItem.js.map +0 -1
  356. package/dist/components/sidebarNav/SidebarNavItemFavourite.d.ts +0 -8
  357. package/dist/components/sidebarNav/SidebarNavItemFavourite.d.ts.map +0 -1
  358. package/dist/components/sidebarNav/SidebarNavItemFavourite.js.map +0 -1
  359. package/dist/components/sidebarNav/SidebarNavPanel.d.ts +0 -4
  360. package/dist/components/sidebarNav/SidebarNavPanel.d.ts.map +0 -1
  361. package/dist/components/sidebarNav/SidebarNavPanel.js +0 -9
  362. package/dist/components/sidebarNav/SidebarNavPanel.js.map +0 -1
  363. package/dist/components/sidebarNav/SidebarNavPanelNav.d.ts +0 -10
  364. package/dist/components/sidebarNav/SidebarNavPanelNav.d.ts.map +0 -1
  365. package/dist/components/sidebarNav/SidebarNavPanelNav.js +0 -21
  366. package/dist/components/sidebarNav/SidebarNavPanelNav.js.map +0 -1
  367. package/dist/components/sidebarNav/SidebarNavRail.d.ts +0 -6
  368. package/dist/components/sidebarNav/SidebarNavRail.d.ts.map +0 -1
  369. package/dist/components/sidebarNav/SidebarNavRail.js +0 -7
  370. package/dist/components/sidebarNav/SidebarNavRail.js.map +0 -1
  371. package/dist/components/sidebarNav/SidebarNavRailItem.d.ts +0 -10
  372. package/dist/components/sidebarNav/SidebarNavRailItem.d.ts.map +0 -1
  373. package/dist/components/sidebarNav/SidebarNavRailItem.js +0 -25
  374. package/dist/components/sidebarNav/SidebarNavRailItem.js.map +0 -1
  375. package/dist/components/sidebarNav/SidebarNavRailList.d.ts +0 -4
  376. package/dist/components/sidebarNav/SidebarNavRailList.d.ts.map +0 -1
  377. package/dist/components/sidebarNav/SidebarNavRailList.js +0 -7
  378. package/dist/components/sidebarNav/SidebarNavRailList.js.map +0 -1
  379. package/dist/components/sidebarNav/SidebarNavRailSlot.d.ts +0 -6
  380. package/dist/components/sidebarNav/SidebarNavRailSlot.d.ts.map +0 -1
  381. package/dist/components/sidebarNav/SidebarNavRailSlot.js +0 -7
  382. package/dist/components/sidebarNav/SidebarNavRailSlot.js.map +0 -1
  383. package/dist/components/sidebarNav/SidebarNavSeparator.d.ts +0 -6
  384. package/dist/components/sidebarNav/SidebarNavSeparator.d.ts.map +0 -1
  385. package/dist/components/sidebarNav/SidebarNavSeparator.js +0 -8
  386. package/dist/components/sidebarNav/SidebarNavSeparator.js.map +0 -1
  387. package/dist/components/sidebarNav/SidebarNavTitle.d.ts +0 -4
  388. package/dist/components/sidebarNav/SidebarNavTitle.d.ts.map +0 -1
  389. package/dist/components/sidebarNav/SidebarNavTitle.js +0 -7
  390. package/dist/components/sidebarNav/SidebarNavTitle.js.map +0 -1
  391. package/dist/components/sidebarNav/SidebarNavTooltip.d.ts +0 -7
  392. package/dist/components/sidebarNav/SidebarNavTooltip.d.ts.map +0 -1
  393. package/dist/components/sidebarNav/SidebarNavTooltip.js.map +0 -1
  394. package/dist/components/sidebarNav/SidebarNavTrigger.d.ts +0 -8
  395. package/dist/components/sidebarNav/SidebarNavTrigger.d.ts.map +0 -1
  396. package/dist/components/sidebarNav/SidebarNavTrigger.js +0 -15
  397. package/dist/components/sidebarNav/SidebarNavTrigger.js.map +0 -1
  398. package/dist/components/sidebarNav/index.d.ts +0 -4
  399. package/dist/components/sidebarNav/index.d.ts.map +0 -1
  400. package/dist/components/sidebarNav/index.js +0 -3
  401. package/dist/components/sidebarNav/index.js.map +0 -1
  402. package/dist/components/sidebarNav/resolvePanelItemProps.d.ts +0 -4
  403. package/dist/components/sidebarNav/resolvePanelItemProps.d.ts.map +0 -1
  404. package/dist/components/sidebarNav/resolvePanelItemProps.js.map +0 -1
  405. package/dist/components/sidebarNav/resolvePanelItemProps.test.d.ts.map +0 -1
  406. package/dist/components/sidebarNav/resolvePanelItemProps.test.js.map +0 -1
  407. package/dist/components/sidebarNav/types.d.ts +0 -100
  408. package/dist/components/sidebarNav/types.d.ts.map +0 -1
  409. package/dist/components/sidebarNav/types.js +0 -4
  410. package/dist/components/sidebarNav/types.js.map +0 -1
  411. package/dist/components/sidebarNav/useControllableBoolean.d.ts.map +0 -1
  412. package/dist/components/sidebarNav/useControllableBoolean.js.map +0 -1
  413. package/src/components/sidebarNav/SidebarNav.stories.tsx +0 -484
  414. package/src/components/sidebarNav/SidebarNav.test.tsx +0 -611
  415. package/src/components/sidebarNav/SidebarNav.tsx +0 -230
  416. package/src/components/sidebarNav/SidebarNavContext.tsx +0 -28
  417. package/src/components/sidebarNav/SidebarNavItem.tsx +0 -162
  418. package/src/components/sidebarNav/SidebarNavPanel.tsx +0 -20
  419. package/src/components/sidebarNav/SidebarNavPanelNav.tsx +0 -55
  420. package/src/components/sidebarNav/SidebarNavRail.tsx +0 -20
  421. package/src/components/sidebarNav/SidebarNavRailItem.tsx +0 -83
  422. package/src/components/sidebarNav/SidebarNavRailList.tsx +0 -11
  423. package/src/components/sidebarNav/SidebarNavRailSlot.tsx +0 -15
  424. package/src/components/sidebarNav/SidebarNavSeparator.tsx +0 -19
  425. package/src/components/sidebarNav/SidebarNavTitle.tsx +0 -13
  426. package/src/components/sidebarNav/SidebarNavTrigger.tsx +0 -52
  427. package/src/components/sidebarNav/index.ts +0 -6
  428. package/src/components/sidebarNav/types.ts +0 -126
  429. /package/dist/components/{sidebarNav → sideNavigation}/resolvePanelItemProps.test.d.ts +0 -0
  430. /package/dist/{components/sidebarNav → utils/hooks}/useControllableBoolean.d.ts +0 -0
  431. /package/dist/{components/sidebarNav → utils/hooks}/useControllableBoolean.js +0 -0
  432. /package/src/{components/sidebarNav → utils/hooks}/useControllableBoolean.ts +0 -0
@@ -50,10 +50,10 @@ const USAGE_GUIDANCE = [
50
50
  '',
51
51
  '```tsx',
52
52
  '// BAD — Pill is a filter control, not an action trigger',
53
- '<Pill text="Save student" onclick={handleSave} />',
53
+ '<Pill text="Save student" onChange={handleSave} />',
54
54
  '',
55
55
  '// GOOD — Pill used as an auto-apply filter',
56
- '<Pill text="Year 7" onclick={checked => onFilterChange("year7", checked)} />',
56
+ '<Pill text="Year 7" onChange={checked => onFilterChange("year7", checked)} />',
57
57
  '```',
58
58
  '',
59
59
  '---',
@@ -71,40 +71,37 @@ const USAGE_GUIDANCE = [
71
71
  ].join('\n');
72
72
 
73
73
  const DEVELOPER_NOTES = [
74
- '### Quirks to know before shipping',
74
+ '### Controlled vs uncontrolled',
75
75
  '',
76
- '**`onclick` fires on mount, not only on user interaction.** The implementation uses',
77
- '`useEffect(() => { if (onclick) onclick(checked); }, [checked, onclick])`, which means React',
78
- 'runs it after the first render. Your handler will receive the initial `initialValue` (default',
79
- '`false`) the moment the component mounts — before the user has clicked anything.',
80
- '',
81
- 'To suppress the mount-time call, guard with a ref:',
76
+ '**Uncontrolled** (default) pass `initialValue` to set the starting state and let the pill',
77
+ 'own its checked state from there. Use `onChange` to be notified of changes without taking',
78
+ 'ownership of the state.',
82
79
  '',
83
80
  '```tsx',
84
- 'const hasMounted = useRef(false);',
85
- '',
86
- '<Pill',
87
- ' text="Autumn term"',
88
- ' onclick={(checked) => {',
89
- ' if (!hasMounted.current) { hasMounted.current = true; return; }',
90
- ' handleFilter(checked);',
91
- ' }}',
92
- '/>',
81
+ '<Pill text="Autumn term" initialValue={false} onChange={checked => console.log(checked)} />',
93
82
  '```',
94
83
  '',
95
- '**Pill is uncontrolled there is no `value` prop.** Once mounted, its state is private.',
96
- 'To reset a pill from outside (e.g. a "Clear all filters" button), change its `key` prop to',
97
- 'force React to remount it. Note that remounting also triggers the mount-time `onclick` firing.',
84
+ '**Controlled**pass `checked` to make the parent own the state. Every time `checked` changes,',
85
+ 'the pill syncs to match. Wire `onChange` back to your state setter so clicks are reflected.',
98
86
  '',
99
87
  '```tsx',
100
- 'const [resetKey, setResetKey] = useState(0);',
88
+ 'const [active, setActive] = useState(false);',
101
89
  '',
102
- '<Pill key={resetKey} text="Autumn term" onclick={handleToggle} />',
103
- '<Button type="button" onClick={() => setResetKey(k => k + 1)}>Clear filters</Button>',
90
+ '<Pill text="Autumn term" checked={active} onChange={setActive} />',
91
+ '<button onClick={() => setActive(false)}>Clear</button>',
104
92
  '```',
105
93
  '',
106
94
  '---',
107
95
  '',
96
+ '### When `onChange` fires',
97
+ '',
98
+ '`onChange` fires only in response to user interaction — a click on the pill (or its embedded',
99
+ 'checkbox). It receives the new checked value. It does **not** fire on mount, and it does **not**',
100
+ 'fire when the controlled `checked` prop is updated by the parent, so driving the pill externally',
101
+ '(e.g. a "Clear all filters" button) never re-triggers your handler.',
102
+ '',
103
+ '---',
104
+ '',
108
105
  '### Accessibility',
109
106
  '',
110
107
  '**Known limitation:** `Pill` renders as a `<span>` with an `onClick` handler. It is not natively',
@@ -125,7 +122,7 @@ const DEVELOPER_NOTES = [
125
122
  " if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); handleToggle(); }",
126
123
  ' }}',
127
124
  '>',
128
- ' <Pill text="Year 7" onclick={handleToggle} />',
125
+ ' <Pill text="Year 7" onChange={handleToggle} />',
129
126
  '</span>',
130
127
  '```',
131
128
  ].join('\n');
@@ -203,21 +200,35 @@ const meta = {
203
200
  'Sets the initial checked/active state on first mount. Defaults to `false` (inactive).',
204
201
  'Use `initialValue={true}` to pre-select a pill — for example, pre-activating an "All"',
205
202
  'pill when a filter row first loads.',
206
- '**Note:** when `initialValue={true}`, the `onclick` callback fires on mount with `true`.',
203
+ 'Only applies in uncontrolled mode; when `checked` is provided, the parent owns the state.',
207
204
  ].join(' '),
208
205
  table: {
209
206
  type: { summary: 'boolean' },
210
207
  defaultValue: { summary: 'false' },
211
208
  },
212
209
  },
213
- onclick: {
210
+ checked: {
211
+ control: 'boolean',
212
+ description: [
213
+ 'Makes the pill **controlled** — the parent owns the checked state.',
214
+ 'When provided, the pill syncs to this value whenever it changes.',
215
+ 'Pair with `onChange` so that user clicks are reflected back into your state.',
216
+ 'Omit to use **uncontrolled** mode where `initialValue` sets the starting state',
217
+ 'and the pill manages itself from there.',
218
+ ].join(' '),
219
+ table: {
220
+ type: { summary: 'boolean' },
221
+ defaultValue: { summary: 'undefined' },
222
+ },
223
+ },
224
+ onChange: {
214
225
  control: false,
215
226
  description: [
216
- 'Callback fired each time the pill\'s checked state changes.',
227
+ 'Callback fired each time the user toggles the pill (a click on the pill or its checkbox).',
217
228
  'Receives `true` when the pill becomes active/checked, `false` when it becomes inactive.',
218
- 'Example: `onclick={checked => onFilterChange("year7", checked)}`.',
219
- '**Quirk:** also fires on mount with the current `initialValue`. Guard with a `useRef`',
220
- 'if you need to suppress the mount-time call. See the component description for the pattern.',
229
+ 'Example: `onChange={checked => onFilterChange("year7", checked)}`.',
230
+ 'Fires only on user interaction never on mount, and never when a controlled `checked`',
231
+ 'prop is updated by the parent.',
221
232
  ].join(' '),
222
233
  table: {
223
234
  type: { summary: '(checked: boolean) => void' },
@@ -270,7 +281,7 @@ const FilterGroupTemplate = () => {
270
281
  key={year}
271
282
  text={year}
272
283
  initialValue={active === year}
273
- onclick={checked => setActive(checked ? year : null)}
284
+ onChange={checked => setActive(checked ? year : null)}
274
285
  />
275
286
  ))}
276
287
  </div>
@@ -307,7 +318,7 @@ const MultiSelectFilterGroupTemplate = () => {
307
318
  key={status}
308
319
  text={status}
309
320
  checkbox
310
- onclick={checked => toggle(status, checked)}
321
+ onChange={checked => toggle(status, checked)}
311
322
  />
312
323
  ))}
313
324
  </div>
@@ -359,14 +370,14 @@ const WithAllOptionTemplate = () => {
359
370
  <Pill
360
371
  text="All"
361
372
  initialValue={allActive}
362
- onclick={handleAllClick}
373
+ onChange={handleAllClick}
363
374
  />
364
375
  {subjects.map(subject => (
365
376
  <Pill
366
377
  key={subject}
367
378
  text={subject}
368
379
  initialValue={activeFilters.has(subject)}
369
- onclick={checked => handleSubjectClick(subject, checked)}
380
+ onChange={checked => handleSubjectClick(subject, checked)}
370
381
  />
371
382
  ))}
372
383
  </div>
@@ -374,6 +385,77 @@ const WithAllOptionTemplate = () => {
374
385
  );
375
386
  };
376
387
 
388
+ const UncontrolledTemplate = () => {
389
+ const [lastValue, setLastValue] = useState<boolean | null>(null);
390
+
391
+ return (
392
+ <div style={{ padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
393
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
394
+ Pill owns its own state — parent only observes via
395
+ {' '}
396
+ <code>onChange</code>
397
+ .
398
+ </p>
399
+ <Pill text="Autumn term" initialValue={false} onChange={v => setLastValue(v)} />
400
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
401
+ Last
402
+ {' '}
403
+ <code>onChange</code>
404
+ {': '}
405
+ <code>{lastValue === null ? '(not fired yet)' : String(lastValue)}</code>
406
+ </p>
407
+ </div>
408
+ );
409
+ };
410
+
411
+ const ControlledTemplate = () => {
412
+ const [isChecked, setIsChecked] = useState(false);
413
+
414
+ return (
415
+ <div style={{ padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
416
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
417
+ Parent owns the state — click the pill or the external button to observe they stay in sync.
418
+ </p>
419
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-small)', alignItems: 'center' }}>
420
+ <Pill text="Autumn term" checked={isChecked} onChange={setIsChecked} />
421
+ <button
422
+ type="button"
423
+ style={{
424
+ padding: 'var(--spacing-xsmall) var(--spacing-medium)',
425
+ border: 'var(--border-weight) solid var(--color-grey-300)',
426
+ borderRadius: 'var(--border-radius-small)',
427
+ background: 'var(--color-white)',
428
+ cursor: 'pointer',
429
+ fontSize: 'var(--font-size-1-13)',
430
+ }}
431
+ onClick={() => setIsChecked(v => !v)}
432
+ >
433
+ Toggle from outside
434
+ </button>
435
+ <button
436
+ type="button"
437
+ style={{
438
+ padding: 'var(--spacing-xsmall) var(--spacing-medium)',
439
+ border: 'var(--border-weight) solid var(--color-grey-300)',
440
+ borderRadius: 'var(--border-radius-small)',
441
+ background: 'var(--color-white)',
442
+ cursor: 'pointer',
443
+ fontSize: 'var(--font-size-1-13)',
444
+ }}
445
+ onClick={() => setIsChecked(false)}
446
+ >
447
+ Reset
448
+ </button>
449
+ </div>
450
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
451
+ External state:
452
+ {' '}
453
+ <code>{String(isChecked)}</code>
454
+ </p>
455
+ </div>
456
+ );
457
+ };
458
+
377
459
  const AllVariantsTemplate = () => (
378
460
  <div
379
461
  style={{
@@ -539,6 +621,133 @@ const AccessibilityNoteTemplate = () => (
539
621
  </div>
540
622
  );
541
623
 
624
+ const OnChangeBehaviourTemplate = () => {
625
+ const [log, setLog] = useState<boolean[]>([]);
626
+
627
+ const handleChange = (value: boolean) => {
628
+ setLog(prev => [value, ...prev]);
629
+ };
630
+
631
+ return (
632
+ <div style={{ padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
633
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
634
+ Click the pill. Each click fires `onChange` with the new value — nothing is logged until
635
+ you interact.
636
+ </p>
637
+ <Pill text="Autumn term" onChange={handleChange} />
638
+ <div
639
+ style={{
640
+ border: '1px solid var(--color-grey-300)',
641
+ borderRadius: 'var(--border-radius-small)',
642
+ padding: 'var(--spacing-small)',
643
+ minHeight: '4rem',
644
+ maxHeight: '12rem',
645
+ overflowY: 'auto',
646
+ background: 'var(--color-grey-050)',
647
+ }}
648
+ >
649
+ {log.length === 0
650
+ ? (
651
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
652
+ Waiting for events...
653
+ </p>
654
+ )
655
+ : (
656
+ log.map((value, i) => (
657
+ <p
658
+ key={i}
659
+ className="ds-text"
660
+ style={{ margin: '0 0 var(--spacing-xsmall)' }}
661
+ >
662
+ {`onChange(${String(value)})`}
663
+ </p>
664
+ ))
665
+ )}
666
+ </div>
667
+ </div>
668
+ );
669
+ };
670
+
671
+ const SUBJECTS = ['Maths', 'English', 'Science', 'History', 'Art', 'French', 'PE'];
672
+
673
+ const ControlledCheckboxModeTemplate = () => {
674
+ const [selected, setSelected] = useState<string[]>(['Maths', 'Science']);
675
+
676
+ const toggle = (subject: string) => {
677
+ setSelected(prev =>
678
+ prev.includes(subject)
679
+ ? prev.filter(s => s !== subject)
680
+ : [...prev, subject],
681
+ );
682
+ };
683
+
684
+ return (
685
+ <div style={{ padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
686
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
687
+ Parent owns a string[] of selected subjects and passes
688
+ {' '}
689
+ <code>
690
+ checked=
691
+ {'{selected.includes(subject)}'}
692
+ </code>
693
+ {' '}
694
+ to each pill.
695
+ </p>
696
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-small)' }}>
697
+ {SUBJECTS.map(subject => (
698
+ <Pill
699
+ key={subject}
700
+ text={subject}
701
+ checkbox
702
+ checked={selected.includes(subject)}
703
+ onChange={() => toggle(subject)}
704
+ />
705
+ ))}
706
+ </div>
707
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)', fontStyle: 'italic' }}>
708
+ {selected.length === 0 ? 'No subjects selected' : `Subjects: ${selected.join(', ')}`}
709
+ </p>
710
+ </div>
711
+ );
712
+ };
713
+
714
+ const ALL_PILLS = [
715
+ 'Year 7', 'Year 8', 'Year 9', 'Year 10', 'Year 11', 'Year 12', 'Year 13',
716
+ 'Maths', 'English', 'Science', 'History', 'Art', 'French', 'PE', 'PE', 'Drama',
717
+ ];
718
+
719
+ const PillGroupWrappingTemplate = () => (
720
+ <div style={{ padding: 'var(--spacing-xlarge)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)' }}>
721
+ <p className="ds-text" style={{ margin: 0, color: 'var(--color-grey-600)' }}>
722
+ 16 pills in a 380px container — the consumer&apos;s flex wrapper is what makes them wrap.
723
+ Pills themselves are
724
+ {' '}
725
+ <code>width: fit-content</code>
726
+ {' '}
727
+ and will overflow without
728
+ {' '}
729
+ <code>flex-wrap: wrap</code>
730
+ {' '}
731
+ on the parent.
732
+ </p>
733
+ <div
734
+ style={{
735
+ maxWidth: '380px',
736
+ display: 'flex',
737
+ flexWrap: 'wrap',
738
+ gap: 'var(--spacing-small)',
739
+ padding: 'var(--spacing-small)',
740
+ border: '1px dashed var(--color-grey-300)',
741
+ borderRadius: 'var(--border-radius-small)',
742
+ }}
743
+ >
744
+ {ALL_PILLS.map((label, i) => (
745
+ <Pill key={`${label}-${i}`} text={label} />
746
+ ))}
747
+ </div>
748
+ </div>
749
+ );
750
+
542
751
  // ---------------------------------------------------------------------------
543
752
  // Stories
544
753
  // ---------------------------------------------------------------------------
@@ -549,11 +758,73 @@ export const Default: Story = withDescription(
549
758
  text: 'Year 7',
550
759
  checkbox: false,
551
760
  initialValue: false,
552
- onclick: fn(),
761
+ onChange: fn(),
553
762
  },
554
763
  render: args => <Pill {...args} />,
555
764
  },
556
- 'The interactive canvas. Every prop is wired to the Controls panel below — toggle `checkbox` to switch between single-select and multi-select modes, toggle `initialValue` to start in the active/checked state. The `onclick` callback fires into the Actions panel on every state change.',
765
+ 'The interactive canvas. Every prop is wired to the Controls panel below — toggle `checkbox` to switch between single-select and multi-select modes, toggle `initialValue` to start in the active/checked state. The `onChange` callback fires into the Actions panel on every state change.',
766
+ );
767
+
768
+ export const Uncontrolled: Story = withDescription(
769
+ {
770
+ render: UncontrolledTemplate,
771
+ parameters: {
772
+ docs: {
773
+ source: {
774
+ language: 'tsx',
775
+ code: `
776
+ import { useState } from 'react';
777
+ import { Pill } from '@arbor-education/design-system.components';
778
+
779
+ function UncontrolledExample() {
780
+ const [lastValue, setLastValue] = useState<boolean | null>(null);
781
+
782
+ return (
783
+ <>
784
+ <Pill text="Autumn term" initialValue={false} onChange={v => setLastValue(v)} />
785
+ <p>Last onChange: {lastValue === null ? '(not fired yet)' : String(lastValue)}</p>
786
+ </>
787
+ );
788
+ }
789
+ export default UncontrolledExample;
790
+ `.trim(),
791
+ },
792
+ },
793
+ },
794
+ },
795
+ 'The pill owns its own checked state — `initialValue` seeds it on first mount and the pill manages itself from there. The parent observes changes via `onChange` but does NOT hold the source of truth. Click the pill to toggle it; the last `onChange` value echoes below.',
796
+ );
797
+
798
+ export const Controlled: Story = withDescription(
799
+ {
800
+ render: ControlledTemplate,
801
+ parameters: {
802
+ docs: {
803
+ source: {
804
+ language: 'tsx',
805
+ code: `
806
+ import { useState } from 'react';
807
+ import { Pill } from '@arbor-education/design-system.components';
808
+
809
+ function ControlledExample() {
810
+ const [isChecked, setIsChecked] = useState(false);
811
+
812
+ return (
813
+ <>
814
+ <Pill text="Autumn term" checked={isChecked} onChange={setIsChecked} />
815
+ <button type="button" onClick={() => setIsChecked(v => !v)}>Toggle from outside</button>
816
+ <button type="button" onClick={() => setIsChecked(false)}>Reset</button>
817
+ <p>External state: {String(isChecked)}</p>
818
+ </>
819
+ );
820
+ }
821
+ export default ControlledExample;
822
+ `.trim(),
823
+ },
824
+ },
825
+ },
826
+ },
827
+ 'The parent holds the source of truth via `checked` + `onChange`. Clicking the pill fires `onChange` → parent updates its state → `checked` prop changes → pill syncs. The external "Toggle from outside" button demonstrates that the parent can drive the pill\'s state independently of user clicks. Use this pattern when you need to reset or synchronise pills from elsewhere in your UI (e.g. a "Clear all filters" button).',
557
828
  );
558
829
 
559
830
  export const ToggleActive: Story = withDescription(
@@ -689,7 +960,7 @@ function YearGroupFilter() {
689
960
  key={year}
690
961
  text={year}
691
962
  initialValue={active === year}
692
- onclick={checked => setActive(checked ? year : null)}
963
+ onChange={checked => setActive(checked ? year : null)}
693
964
  />
694
965
  ))}
695
966
  </div>
@@ -735,7 +1006,7 @@ function StatusFilter() {
735
1006
  key={status}
736
1007
  text={status}
737
1008
  checkbox
738
- onclick={checked => toggle(status, checked)}
1009
+ onChange={checked => toggle(status, checked)}
739
1010
  />
740
1011
  ))}
741
1012
  </div>
@@ -787,13 +1058,13 @@ function SubjectFilter() {
787
1058
 
788
1059
  return (
789
1060
  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-small)' }}>
790
- <Pill text="All" initialValue={allActive} onclick={handleAllClick} />
1061
+ <Pill text="All" initialValue={allActive} onChange={handleAllClick} />
791
1062
  {subjects.map(subject => (
792
1063
  <Pill
793
1064
  key={subject}
794
1065
  text={subject}
795
1066
  initialValue={activeFilters.has(subject)}
796
- onclick={checked => handleSubjectClick(subject, checked)}
1067
+ onChange={checked => handleSubjectClick(subject, checked)}
797
1068
  />
798
1069
  ))}
799
1070
  </div>
@@ -875,3 +1146,123 @@ export default AccessibilityNoteExample;
875
1146
  },
876
1147
  '**Read before shipping to production.** Toggle-mode `Pill` is not keyboard-accessible — it renders as `<span onClick>` with no `tabIndex` or ARIA role. Checkbox mode IS accessible. This story documents the limitation and shows an accessible checkbox-mode example you can interact with via keyboard. See the component description for the toggle-mode wrapper workaround.',
877
1148
  );
1149
+
1150
+ export const OnChangeBehaviour: Story = withDescription(
1151
+ {
1152
+ render: OnChangeBehaviourTemplate,
1153
+ parameters: {
1154
+ controls: { disable: true },
1155
+ docs: {
1156
+ source: {
1157
+ language: 'tsx',
1158
+ code: `
1159
+ import { useState } from 'react';
1160
+ import { Pill } from '@arbor-education/design-system.components';
1161
+
1162
+ function PillOnChangeLog() {
1163
+ const [log, setLog] = useState<boolean[]>([]);
1164
+
1165
+ return (
1166
+ <>
1167
+ <Pill
1168
+ text="Autumn term"
1169
+ onChange={checked => setLog(prev => [checked, ...prev])}
1170
+ />
1171
+ <ul>
1172
+ {log.map((value, i) => <li key={i}>onChange({String(value)})</li>)}
1173
+ </ul>
1174
+ </>
1175
+ );
1176
+ }
1177
+ export default PillOnChangeLog;
1178
+ `.trim(),
1179
+ },
1180
+ },
1181
+ },
1182
+ },
1183
+ '`onChange` fires only on user interaction — once per click, with the new checked value. Nothing is logged until you click the pill: it does not fire on mount, and it does not fire when a controlled `checked` prop is updated by the parent. Wire it straight to a filter handler or state setter without needing a mount guard.',
1184
+ );
1185
+
1186
+ export const ControlledCheckboxMode: Story = withDescription(
1187
+ {
1188
+ render: ControlledCheckboxModeTemplate,
1189
+ parameters: {
1190
+ controls: { disable: true },
1191
+ docs: {
1192
+ source: {
1193
+ language: 'tsx',
1194
+ code: `
1195
+ import { useState } from 'react';
1196
+ import { Pill } from '@arbor-education/design-system.components';
1197
+
1198
+ const SUBJECTS = ['Maths', 'English', 'Science', 'History', 'Art', 'French', 'PE'];
1199
+
1200
+ function SubjectFilter() {
1201
+ const [selected, setSelected] = useState<string[]>(['Maths', 'Science']);
1202
+
1203
+ const toggle = (subject: string) => {
1204
+ setSelected(prev =>
1205
+ prev.includes(subject)
1206
+ ? prev.filter(s => s !== subject)
1207
+ : [...prev, subject],
1208
+ );
1209
+ };
1210
+
1211
+ return (
1212
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-small)' }}>
1213
+ {SUBJECTS.map(subject => (
1214
+ <Pill
1215
+ key={subject}
1216
+ text={subject}
1217
+ checkbox
1218
+ checked={selected.includes(subject)}
1219
+ onChange={() => toggle(subject)}
1220
+ />
1221
+ ))}
1222
+ </div>
1223
+ );
1224
+ }
1225
+ export default SubjectFilter;
1226
+ `.trim(),
1227
+ },
1228
+ },
1229
+ },
1230
+ },
1231
+ 'The canonical controlled multi-select pattern. The parent holds a `string[]` of selected subjects and passes `checked={selected.includes(subject)}` to each pill. `onChange` updates the array. Use this when filter state needs to live outside the component — e.g. in a URL param, a form, or a shared store. Two subjects are pre-selected to demonstrate the starting state.',
1232
+ );
1233
+
1234
+ export const PillGroupWrapping: Story = withDescription(
1235
+ {
1236
+ render: PillGroupWrappingTemplate,
1237
+ parameters: {
1238
+ controls: { disable: true },
1239
+ docs: {
1240
+ source: {
1241
+ language: 'tsx',
1242
+ code: `
1243
+ import { Pill } from '@arbor-education/design-system.components';
1244
+
1245
+ function FilterPillGroup() {
1246
+ const labels = [
1247
+ 'Year 7', 'Year 8', 'Year 9', 'Year 10', 'Year 11', 'Year 12', 'Year 13',
1248
+ 'Maths', 'English', 'Science', 'History', 'Art',
1249
+ ];
1250
+
1251
+ // flex-wrap: wrap on the PARENT is what makes pills wrap.
1252
+ // Pills themselves are width: fit-content and will overflow without it.
1253
+ return (
1254
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-small)' }}>
1255
+ {labels.map(label => (
1256
+ <Pill key={label} text={label} />
1257
+ ))}
1258
+ </div>
1259
+ );
1260
+ }
1261
+ export default FilterPillGroup;
1262
+ `.trim(),
1263
+ },
1264
+ },
1265
+ },
1266
+ },
1267
+ 'Pills must wrap — never scroll horizontally. The wrapper here is constrained to 380px so you can see the wrapping in action. In production the container width comes from the page layout. The key CSS is `display: flex; flex-wrap: wrap; gap: var(--spacing-small)` on the **parent** — Pill itself is `width: fit-content` and will overflow without it.',
1268
+ );