@arbor-education/design-system.components 0.25.9 → 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 (415) hide show
  1. package/.gather/instructions/project-overview.md +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/CONTRIBUTING.md +7 -5
  4. package/component-library.md +97 -3
  5. package/dist/components/emptyState/EmptyState.d.ts +22 -0
  6. package/dist/components/emptyState/EmptyState.d.ts.map +1 -0
  7. package/dist/components/emptyState/EmptyState.js +11 -0
  8. package/dist/components/emptyState/EmptyState.js.map +1 -0
  9. package/dist/components/emptyState/EmptyState.stories.d.ts +117 -0
  10. package/dist/components/emptyState/EmptyState.stories.d.ts.map +1 -0
  11. package/dist/components/emptyState/EmptyState.stories.js +332 -0
  12. package/dist/components/emptyState/EmptyState.stories.js.map +1 -0
  13. package/dist/components/emptyState/EmptyState.test.d.ts +2 -0
  14. package/dist/components/emptyState/EmptyState.test.d.ts.map +1 -0
  15. package/dist/components/emptyState/EmptyState.test.js +80 -0
  16. package/dist/components/emptyState/EmptyState.test.js.map +1 -0
  17. package/dist/components/favourite/FavouriteButton.d.ts +18 -0
  18. package/dist/components/favourite/FavouriteButton.d.ts.map +1 -0
  19. package/dist/components/favourite/FavouriteButton.js +16 -0
  20. package/dist/components/favourite/FavouriteButton.js.map +1 -0
  21. package/dist/components/favourite/FavouriteButton.test.d.ts +2 -0
  22. package/dist/components/favourite/FavouriteButton.test.d.ts.map +1 -0
  23. package/dist/components/favourite/FavouriteButton.test.js +37 -0
  24. package/dist/components/favourite/FavouriteButton.test.js.map +1 -0
  25. package/dist/components/filterBar/FilterBar.d.ts +1 -1
  26. package/dist/components/filterBar/FilterBar.stories.d.ts +1 -1
  27. package/dist/components/icon/Icon.d.ts +1 -1
  28. package/dist/components/icon/Icon.d.ts.map +1 -1
  29. package/dist/components/icon/Icon.js +1 -1
  30. package/dist/components/icon/Icon.js.map +1 -1
  31. package/dist/components/pill/Pill.d.ts +3 -2
  32. package/dist/components/pill/Pill.d.ts.map +1 -1
  33. package/dist/components/pill/Pill.js +15 -9
  34. package/dist/components/pill/Pill.js.map +1 -1
  35. package/dist/components/pill/Pill.stories.d.ts +21 -3
  36. package/dist/components/pill/Pill.stories.d.ts.map +1 -1
  37. package/dist/components/pill/Pill.stories.js +275 -40
  38. package/dist/components/pill/Pill.stories.js.map +1 -1
  39. package/dist/components/pill/Pill.test.js +64 -3
  40. package/dist/components/pill/Pill.test.js.map +1 -1
  41. package/dist/components/sideNavigation/SideNavigation.d.ts +34 -0
  42. package/dist/components/sideNavigation/SideNavigation.d.ts.map +1 -0
  43. package/dist/components/sideNavigation/SideNavigation.js +30 -0
  44. package/dist/components/sideNavigation/SideNavigation.js.map +1 -0
  45. package/dist/components/sideNavigation/SideNavigation.stories.d.ts +72 -0
  46. package/dist/components/sideNavigation/SideNavigation.stories.d.ts.map +1 -0
  47. package/dist/components/sideNavigation/SideNavigation.stories.js +298 -0
  48. package/dist/components/sideNavigation/SideNavigation.stories.js.map +1 -0
  49. package/dist/components/sideNavigation/SideNavigation.test.d.ts +2 -0
  50. package/dist/components/sideNavigation/SideNavigation.test.d.ts.map +1 -0
  51. package/dist/components/sideNavigation/SideNavigation.test.js +74 -0
  52. package/dist/components/sideNavigation/SideNavigation.test.js.map +1 -0
  53. package/dist/components/sideNavigation/SideNavigationContext.d.ts +10 -0
  54. package/dist/components/sideNavigation/SideNavigationContext.d.ts.map +1 -0
  55. package/dist/components/sideNavigation/SideNavigationContext.js +15 -0
  56. package/dist/components/sideNavigation/SideNavigationContext.js.map +1 -0
  57. package/dist/components/{sidebarNav/SidebarNavGroup.d.ts → sideNavigation/SideNavigationGroup.d.ts} +3 -3
  58. package/dist/components/sideNavigation/SideNavigationGroup.d.ts.map +1 -0
  59. package/dist/components/sideNavigation/SideNavigationGroup.js +16 -0
  60. package/dist/components/sideNavigation/SideNavigationGroup.js.map +1 -0
  61. package/dist/components/sideNavigation/SideNavigationGroup.test.d.ts +2 -0
  62. package/dist/components/sideNavigation/SideNavigationGroup.test.d.ts.map +1 -0
  63. package/dist/components/sideNavigation/SideNavigationGroup.test.js +58 -0
  64. package/dist/components/sideNavigation/SideNavigationGroup.test.js.map +1 -0
  65. package/dist/components/{sidebarNav/SidebarNavItem.d.ts → sideNavigation/SideNavigationItem.d.ts} +11 -12
  66. package/dist/components/sideNavigation/SideNavigationItem.d.ts.map +1 -0
  67. package/dist/components/sideNavigation/SideNavigationItem.js +28 -0
  68. package/dist/components/sideNavigation/SideNavigationItem.js.map +1 -0
  69. package/dist/components/sideNavigation/SideNavigationItem.test.d.ts +2 -0
  70. package/dist/components/sideNavigation/SideNavigationItem.test.d.ts.map +1 -0
  71. package/dist/components/sideNavigation/SideNavigationItem.test.js +65 -0
  72. package/dist/components/sideNavigation/SideNavigationItem.test.js.map +1 -0
  73. package/dist/components/sideNavigation/SideNavigationItemFavourite.d.ts +8 -0
  74. package/dist/components/sideNavigation/SideNavigationItemFavourite.d.ts.map +1 -0
  75. package/dist/components/{sidebarNav/SidebarNavItemFavourite.js → sideNavigation/SideNavigationItemFavourite.js} +5 -5
  76. package/dist/components/sideNavigation/SideNavigationItemFavourite.js.map +1 -0
  77. package/dist/components/sideNavigation/SideNavigationItemFavourite.test.d.ts +2 -0
  78. package/dist/components/sideNavigation/SideNavigationItemFavourite.test.d.ts.map +1 -0
  79. package/dist/components/sideNavigation/SideNavigationItemFavourite.test.js +31 -0
  80. package/dist/components/sideNavigation/SideNavigationItemFavourite.test.js.map +1 -0
  81. package/dist/components/sideNavigation/SideNavigationTitle.d.ts +4 -0
  82. package/dist/components/sideNavigation/SideNavigationTitle.d.ts.map +1 -0
  83. package/dist/components/sideNavigation/SideNavigationTitle.js +7 -0
  84. package/dist/components/sideNavigation/SideNavigationTitle.js.map +1 -0
  85. package/dist/components/sideNavigation/SideNavigationTitle.test.d.ts +2 -0
  86. package/dist/components/sideNavigation/SideNavigationTitle.test.d.ts.map +1 -0
  87. package/dist/components/sideNavigation/SideNavigationTitle.test.js +22 -0
  88. package/dist/components/sideNavigation/SideNavigationTitle.test.js.map +1 -0
  89. package/dist/components/sideNavigation/SideNavigationTooltip.d.ts +7 -0
  90. package/dist/components/sideNavigation/SideNavigationTooltip.d.ts.map +1 -0
  91. package/dist/components/{sidebarNav/SidebarNavTooltip.js → sideNavigation/SideNavigationTooltip.js} +2 -2
  92. package/dist/components/sideNavigation/SideNavigationTooltip.js.map +1 -0
  93. package/dist/components/sideNavigation/SideNavigationTooltip.test.d.ts +2 -0
  94. package/dist/components/sideNavigation/SideNavigationTooltip.test.d.ts.map +1 -0
  95. package/dist/components/sideNavigation/SideNavigationTooltip.test.js +26 -0
  96. package/dist/components/sideNavigation/SideNavigationTooltip.test.js.map +1 -0
  97. package/dist/components/sideNavigation/resolvePanelItemProps.d.ts +4 -0
  98. package/dist/components/sideNavigation/resolvePanelItemProps.d.ts.map +1 -0
  99. package/dist/components/{sidebarNav → sideNavigation}/resolvePanelItemProps.js +5 -4
  100. package/dist/components/sideNavigation/resolvePanelItemProps.js.map +1 -0
  101. package/dist/components/sideNavigation/resolvePanelItemProps.test.d.ts.map +1 -0
  102. package/dist/components/{sidebarNav → sideNavigation}/resolvePanelItemProps.test.js +18 -4
  103. package/dist/components/sideNavigation/resolvePanelItemProps.test.js.map +1 -0
  104. package/dist/components/sideNavigation/types.d.ts +47 -0
  105. package/dist/components/sideNavigation/types.d.ts.map +1 -0
  106. package/dist/components/sideNavigation/types.js +2 -0
  107. package/dist/components/sideNavigation/types.js.map +1 -0
  108. package/dist/components/sidebar/Sidebar.d.ts +13 -0
  109. package/dist/components/sidebar/Sidebar.d.ts.map +1 -0
  110. package/dist/components/sidebar/Sidebar.js +9 -0
  111. package/dist/components/sidebar/Sidebar.js.map +1 -0
  112. package/dist/components/sidebar/Sidebar.stories.d.ts +48 -0
  113. package/dist/components/sidebar/Sidebar.stories.d.ts.map +1 -0
  114. package/dist/components/sidebar/Sidebar.stories.js +444 -0
  115. package/dist/components/sidebar/Sidebar.stories.js.map +1 -0
  116. package/dist/components/sidebar/Sidebar.test.d.ts +2 -0
  117. package/dist/components/sidebar/Sidebar.test.d.ts.map +1 -0
  118. package/dist/components/sidebar/Sidebar.test.js +81 -0
  119. package/dist/components/sidebar/Sidebar.test.js.map +1 -0
  120. package/dist/components/sidebar/SidebarItem.d.ts +7 -0
  121. package/dist/components/sidebar/SidebarItem.d.ts.map +1 -0
  122. package/dist/components/sidebar/SidebarItem.js +11 -0
  123. package/dist/components/sidebar/SidebarItem.js.map +1 -0
  124. package/dist/components/singleUser/SingleUser.d.ts +1 -1
  125. package/dist/components/tabs/TabsItem.stories.d.ts +2 -2
  126. package/dist/components/toast/Toast.d.ts +11 -2
  127. package/dist/components/toast/Toast.d.ts.map +1 -1
  128. package/dist/components/toast/Toast.js +5 -3
  129. package/dist/components/toast/Toast.js.map +1 -1
  130. package/dist/components/toast/Toast.stories.d.ts +15 -3
  131. package/dist/components/toast/Toast.stories.d.ts.map +1 -1
  132. package/dist/components/toast/Toast.stories.js +43 -41
  133. package/dist/components/toast/Toast.stories.js.map +1 -1
  134. package/dist/components/toast/Toast.test.js +19 -4
  135. package/dist/components/toast/Toast.test.js.map +1 -1
  136. package/dist/components/toast/ToastAction.d.ts +7 -0
  137. package/dist/components/toast/ToastAction.d.ts.map +1 -0
  138. package/dist/components/toast/ToastAction.js +9 -0
  139. package/dist/components/toast/ToastAction.js.map +1 -0
  140. package/dist/components/tooltip/TooltipWrapper.d.ts +1 -0
  141. package/dist/components/tooltip/TooltipWrapper.d.ts.map +1 -1
  142. package/dist/components/tooltip/TooltipWrapper.js +2 -2
  143. package/dist/components/tooltip/TooltipWrapper.js.map +1 -1
  144. package/dist/components/widget/Widget.d.ts +18 -0
  145. package/dist/components/widget/Widget.d.ts.map +1 -0
  146. package/dist/components/widget/Widget.js +51 -0
  147. package/dist/components/widget/Widget.js.map +1 -0
  148. package/dist/components/widget/Widget.stories.d.ts +128 -0
  149. package/dist/components/widget/Widget.stories.d.ts.map +1 -0
  150. package/dist/components/widget/Widget.stories.js +417 -0
  151. package/dist/components/widget/Widget.stories.js.map +1 -0
  152. package/dist/components/widget/Widget.test.d.ts +2 -0
  153. package/dist/components/widget/Widget.test.d.ts.map +1 -0
  154. package/dist/components/widget/Widget.test.js +110 -0
  155. package/dist/components/widget/Widget.test.js.map +1 -0
  156. package/dist/components/widget/WidgetBody.d.ts +6 -0
  157. package/dist/components/widget/WidgetBody.d.ts.map +1 -0
  158. package/dist/components/widget/WidgetBody.js +20 -0
  159. package/dist/components/widget/WidgetBody.js.map +1 -0
  160. package/dist/components/widget/WidgetContext.d.ts +20 -0
  161. package/dist/components/widget/WidgetContext.d.ts.map +1 -0
  162. package/dist/components/widget/WidgetContext.js +10 -0
  163. package/dist/components/widget/WidgetContext.js.map +1 -0
  164. package/dist/components/widget/WidgetHeader.d.ts +8 -0
  165. package/dist/components/widget/WidgetHeader.d.ts.map +1 -0
  166. package/dist/components/widget/WidgetHeader.js +56 -0
  167. package/dist/components/widget/WidgetHeader.js.map +1 -0
  168. package/dist/components/widget/types.d.ts +43 -0
  169. package/dist/components/widget/types.d.ts.map +1 -0
  170. package/dist/components/widget/types.js +2 -0
  171. package/dist/components/widget/types.js.map +1 -0
  172. package/dist/components/widgetCalendar/WidgetCalendar.d.ts +35 -0
  173. package/dist/components/widgetCalendar/WidgetCalendar.d.ts.map +1 -0
  174. package/dist/components/widgetCalendar/WidgetCalendar.js +22 -0
  175. package/dist/components/widgetCalendar/WidgetCalendar.js.map +1 -0
  176. package/dist/components/widgetCalendar/WidgetCalendar.stories.d.ts +60 -0
  177. package/dist/components/widgetCalendar/WidgetCalendar.stories.d.ts.map +1 -0
  178. package/dist/components/widgetCalendar/WidgetCalendar.stories.js +140 -0
  179. package/dist/components/widgetCalendar/WidgetCalendar.stories.js.map +1 -0
  180. package/dist/components/widgetCalendar/WidgetCalendar.test.d.ts +2 -0
  181. package/dist/components/widgetCalendar/WidgetCalendar.test.d.ts.map +1 -0
  182. package/dist/components/widgetCalendar/WidgetCalendar.test.js +88 -0
  183. package/dist/components/widgetCalendar/WidgetCalendar.test.js.map +1 -0
  184. package/dist/components/widgetCalendar/WidgetCalendarItem.d.ts +17 -0
  185. package/dist/components/widgetCalendar/WidgetCalendarItem.d.ts.map +1 -0
  186. package/dist/components/widgetCalendar/WidgetCalendarItem.js +25 -0
  187. package/dist/components/widgetCalendar/WidgetCalendarItem.js.map +1 -0
  188. package/dist/components/widgetFavourites/WidgetFavourites.d.ts +12 -0
  189. package/dist/components/widgetFavourites/WidgetFavourites.d.ts.map +1 -0
  190. package/dist/components/widgetFavourites/WidgetFavourites.js +23 -0
  191. package/dist/components/widgetFavourites/WidgetFavourites.js.map +1 -0
  192. package/dist/components/widgetFavourites/WidgetFavourites.stories.d.ts +84 -0
  193. package/dist/components/widgetFavourites/WidgetFavourites.stories.d.ts.map +1 -0
  194. package/dist/components/widgetFavourites/WidgetFavourites.stories.js +210 -0
  195. package/dist/components/widgetFavourites/WidgetFavourites.stories.js.map +1 -0
  196. package/dist/components/widgetFavourites/WidgetFavourites.test.d.ts +2 -0
  197. package/dist/components/widgetFavourites/WidgetFavourites.test.d.ts.map +1 -0
  198. package/dist/components/widgetFavourites/WidgetFavourites.test.js +67 -0
  199. package/dist/components/widgetFavourites/WidgetFavourites.test.js.map +1 -0
  200. package/dist/components/widgetFavourites/WidgetFavouritesItem.d.ts +3 -0
  201. package/dist/components/widgetFavourites/WidgetFavouritesItem.d.ts.map +1 -0
  202. package/dist/components/widgetFavourites/WidgetFavouritesItem.js +10 -0
  203. package/dist/components/widgetFavourites/WidgetFavouritesItem.js.map +1 -0
  204. package/dist/components/widgetFavourites/types.d.ts +25 -0
  205. package/dist/components/widgetFavourites/types.d.ts.map +1 -0
  206. package/dist/components/widgetFavourites/types.js +2 -0
  207. package/dist/components/widgetFavourites/types.js.map +1 -0
  208. package/dist/components/widgetNotifications/WidgetNotifications.d.ts +33 -0
  209. package/dist/components/widgetNotifications/WidgetNotifications.d.ts.map +1 -0
  210. package/dist/components/widgetNotifications/WidgetNotifications.js +18 -0
  211. package/dist/components/widgetNotifications/WidgetNotifications.js.map +1 -0
  212. package/dist/components/widgetNotifications/WidgetNotifications.stories.d.ts +60 -0
  213. package/dist/components/widgetNotifications/WidgetNotifications.stories.d.ts.map +1 -0
  214. package/dist/components/widgetNotifications/WidgetNotifications.stories.js +269 -0
  215. package/dist/components/widgetNotifications/WidgetNotifications.stories.js.map +1 -0
  216. package/dist/components/widgetNotifications/WidgetNotifications.test.d.ts +2 -0
  217. package/dist/components/widgetNotifications/WidgetNotifications.test.d.ts.map +1 -0
  218. package/dist/components/widgetNotifications/WidgetNotifications.test.js +79 -0
  219. package/dist/components/widgetNotifications/WidgetNotifications.test.js.map +1 -0
  220. package/dist/components/widgetNotifications/WidgetNotificationsItem.d.ts +14 -0
  221. package/dist/components/widgetNotifications/WidgetNotificationsItem.d.ts.map +1 -0
  222. package/dist/components/widgetNotifications/WidgetNotificationsItem.js +14 -0
  223. package/dist/components/widgetNotifications/WidgetNotificationsItem.js.map +1 -0
  224. package/dist/index.css +756 -78
  225. package/dist/index.css.map +1 -1
  226. package/dist/index.d.ts +15 -9
  227. package/dist/index.d.ts.map +1 -1
  228. package/dist/index.js +15 -8
  229. package/dist/index.js.map +1 -1
  230. package/dist/utils/favourites/favouriteConstants.d.ts +12 -0
  231. package/dist/utils/favourites/favouriteConstants.d.ts.map +1 -0
  232. package/dist/utils/favourites/favouriteConstants.js +16 -0
  233. package/dist/utils/favourites/favouriteConstants.js.map +1 -0
  234. package/dist/utils/favourites/favouriteConstants.test.d.ts +2 -0
  235. package/dist/utils/favourites/favouriteConstants.test.d.ts.map +1 -0
  236. package/dist/utils/favourites/favouriteConstants.test.js +12 -0
  237. package/dist/utils/favourites/favouriteConstants.test.js.map +1 -0
  238. package/dist/utils/hooks/useControllableBoolean.d.ts.map +1 -0
  239. package/dist/utils/hooks/useControllableBoolean.js.map +1 -0
  240. package/dist/utils/hooks/useControllableBoolean.test.d.ts +2 -0
  241. package/dist/utils/hooks/useControllableBoolean.test.d.ts.map +1 -0
  242. package/dist/utils/hooks/useControllableBoolean.test.js +34 -0
  243. package/dist/utils/hooks/useControllableBoolean.test.js.map +1 -0
  244. package/package.json +1 -1
  245. package/src/components/emptyState/EmptyState.stories.tsx +487 -0
  246. package/src/components/emptyState/EmptyState.test.tsx +140 -0
  247. package/src/components/emptyState/EmptyState.tsx +84 -0
  248. package/src/components/emptyState/emptyState.scss +63 -0
  249. package/src/components/favourite/FavouriteButton.test.tsx +83 -0
  250. package/src/components/favourite/FavouriteButton.tsx +70 -0
  251. package/src/components/favourite/favouriteButton.scss +37 -0
  252. package/src/components/icon/Icon.tsx +2 -2
  253. package/src/components/link/link.scss +26 -0
  254. package/src/components/pill/Pill.stories.tsx +432 -41
  255. package/src/components/pill/Pill.test.tsx +74 -3
  256. package/src/components/pill/Pill.tsx +26 -11
  257. package/src/components/sideNavigation/SideNavigation.stories.tsx +578 -0
  258. package/src/components/sideNavigation/SideNavigation.test.tsx +109 -0
  259. package/src/components/sideNavigation/SideNavigation.tsx +98 -0
  260. package/src/components/sideNavigation/SideNavigationContext.tsx +25 -0
  261. package/src/components/sideNavigation/SideNavigationGroup.test.tsx +103 -0
  262. package/src/components/{sidebarNav/SidebarNavGroup.tsx → sideNavigation/SideNavigationGroup.tsx} +8 -8
  263. package/src/components/sideNavigation/SideNavigationItem.test.tsx +137 -0
  264. package/src/components/sideNavigation/SideNavigationItem.tsx +125 -0
  265. package/src/components/sideNavigation/SideNavigationItemFavourite.test.tsx +54 -0
  266. package/src/components/{sidebarNav/SidebarNavItemFavourite.tsx → sideNavigation/SideNavigationItemFavourite.tsx} +7 -7
  267. package/src/components/sideNavigation/SideNavigationTitle.test.tsx +23 -0
  268. package/src/components/sideNavigation/SideNavigationTitle.tsx +13 -0
  269. package/src/components/sideNavigation/SideNavigationTooltip.test.tsx +44 -0
  270. package/src/components/{sidebarNav/SidebarNavTooltip.tsx → sideNavigation/SideNavigationTooltip.tsx} +2 -2
  271. package/src/components/{sidebarNav → sideNavigation}/resolvePanelItemProps.test.ts +20 -4
  272. package/src/components/{sidebarNav → sideNavigation}/resolvePanelItemProps.ts +8 -7
  273. package/src/components/{sidebarNav/sidebarNav.scss → sideNavigation/sideNavigation.scss} +37 -136
  274. package/src/components/sideNavigation/types.ts +56 -0
  275. package/src/components/sidebar/Sidebar.stories.tsx +670 -0
  276. package/src/components/sidebar/Sidebar.test.tsx +155 -0
  277. package/src/components/sidebar/Sidebar.tsx +27 -0
  278. package/src/components/sidebar/SidebarItem.tsx +64 -0
  279. package/src/components/sidebar/sidebar.scss +65 -0
  280. package/src/components/toast/Toast.stories.tsx +75 -67
  281. package/src/components/toast/Toast.test.tsx +40 -10
  282. package/src/components/toast/Toast.tsx +19 -5
  283. package/src/components/toast/ToastAction.tsx +17 -0
  284. package/src/components/toast/toast.scss +37 -0
  285. package/src/components/tooltip/TooltipWrapper.tsx +3 -1
  286. package/src/components/widget/Widget.stories.tsx +600 -0
  287. package/src/components/widget/Widget.test.tsx +263 -0
  288. package/src/components/widget/Widget.tsx +102 -0
  289. package/src/components/widget/WidgetBody.tsx +66 -0
  290. package/src/components/widget/WidgetContext.tsx +31 -0
  291. package/src/components/widget/WidgetHeader.tsx +155 -0
  292. package/src/components/widget/types.ts +47 -0
  293. package/src/components/widget/widget.scss +218 -0
  294. package/src/components/widgetCalendar/WidgetCalendar.stories.tsx +241 -0
  295. package/src/components/widgetCalendar/WidgetCalendar.test.tsx +137 -0
  296. package/src/components/widgetCalendar/WidgetCalendar.tsx +105 -0
  297. package/src/components/widgetCalendar/WidgetCalendarItem.tsx +105 -0
  298. package/src/components/widgetCalendar/widgetCalendar.scss +124 -0
  299. package/src/components/widgetFavourites/WidgetFavourites.stories.tsx +341 -0
  300. package/src/components/widgetFavourites/WidgetFavourites.test.tsx +127 -0
  301. package/src/components/widgetFavourites/WidgetFavourites.tsx +83 -0
  302. package/src/components/widgetFavourites/WidgetFavouritesItem.tsx +37 -0
  303. package/src/components/widgetFavourites/types.ts +26 -0
  304. package/src/components/widgetFavourites/widgetFavourites.scss +84 -0
  305. package/src/components/widgetNotifications/WidgetNotifications.stories.tsx +417 -0
  306. package/src/components/widgetNotifications/WidgetNotifications.test.tsx +152 -0
  307. package/src/components/widgetNotifications/WidgetNotifications.tsx +112 -0
  308. package/src/components/widgetNotifications/WidgetNotificationsItem.tsx +88 -0
  309. package/src/components/widgetNotifications/widgetNotifications.scss +186 -0
  310. package/src/docs/Contributing.mdx +1 -1
  311. package/src/index.scss +9 -1
  312. package/src/index.ts +22 -18
  313. package/src/tokens.scss +12 -2
  314. package/src/utils/favourites/favouriteConstants.test.ts +19 -0
  315. package/src/utils/favourites/favouriteConstants.ts +21 -0
  316. package/src/utils/hooks/useControllableBoolean.test.ts +48 -0
  317. package/dist/components/sidebarNav/SidebarNav.d.ts +0 -46
  318. package/dist/components/sidebarNav/SidebarNav.d.ts.map +0 -1
  319. package/dist/components/sidebarNav/SidebarNav.js +0 -102
  320. package/dist/components/sidebarNav/SidebarNav.js.map +0 -1
  321. package/dist/components/sidebarNav/SidebarNav.stories.d.ts +0 -61
  322. package/dist/components/sidebarNav/SidebarNav.stories.d.ts.map +0 -1
  323. package/dist/components/sidebarNav/SidebarNav.stories.js +0 -253
  324. package/dist/components/sidebarNav/SidebarNav.stories.js.map +0 -1
  325. package/dist/components/sidebarNav/SidebarNav.test.d.ts +0 -2
  326. package/dist/components/sidebarNav/SidebarNav.test.d.ts.map +0 -1
  327. package/dist/components/sidebarNav/SidebarNav.test.js +0 -240
  328. package/dist/components/sidebarNav/SidebarNav.test.js.map +0 -1
  329. package/dist/components/sidebarNav/SidebarNavContext.d.ts +0 -13
  330. package/dist/components/sidebarNav/SidebarNavContext.d.ts.map +0 -1
  331. package/dist/components/sidebarNav/SidebarNavContext.js +0 -15
  332. package/dist/components/sidebarNav/SidebarNavContext.js.map +0 -1
  333. package/dist/components/sidebarNav/SidebarNavGroup.d.ts.map +0 -1
  334. package/dist/components/sidebarNav/SidebarNavGroup.js +0 -16
  335. package/dist/components/sidebarNav/SidebarNavGroup.js.map +0 -1
  336. package/dist/components/sidebarNav/SidebarNavItem.d.ts.map +0 -1
  337. package/dist/components/sidebarNav/SidebarNavItem.js +0 -44
  338. package/dist/components/sidebarNav/SidebarNavItem.js.map +0 -1
  339. package/dist/components/sidebarNav/SidebarNavItemFavourite.d.ts +0 -8
  340. package/dist/components/sidebarNav/SidebarNavItemFavourite.d.ts.map +0 -1
  341. package/dist/components/sidebarNav/SidebarNavItemFavourite.js.map +0 -1
  342. package/dist/components/sidebarNav/SidebarNavPanel.d.ts +0 -4
  343. package/dist/components/sidebarNav/SidebarNavPanel.d.ts.map +0 -1
  344. package/dist/components/sidebarNav/SidebarNavPanel.js +0 -9
  345. package/dist/components/sidebarNav/SidebarNavPanel.js.map +0 -1
  346. package/dist/components/sidebarNav/SidebarNavPanelNav.d.ts +0 -10
  347. package/dist/components/sidebarNav/SidebarNavPanelNav.d.ts.map +0 -1
  348. package/dist/components/sidebarNav/SidebarNavPanelNav.js +0 -21
  349. package/dist/components/sidebarNav/SidebarNavPanelNav.js.map +0 -1
  350. package/dist/components/sidebarNav/SidebarNavRail.d.ts +0 -6
  351. package/dist/components/sidebarNav/SidebarNavRail.d.ts.map +0 -1
  352. package/dist/components/sidebarNav/SidebarNavRail.js +0 -7
  353. package/dist/components/sidebarNav/SidebarNavRail.js.map +0 -1
  354. package/dist/components/sidebarNav/SidebarNavRailItem.d.ts +0 -10
  355. package/dist/components/sidebarNav/SidebarNavRailItem.d.ts.map +0 -1
  356. package/dist/components/sidebarNav/SidebarNavRailItem.js +0 -25
  357. package/dist/components/sidebarNav/SidebarNavRailItem.js.map +0 -1
  358. package/dist/components/sidebarNav/SidebarNavRailList.d.ts +0 -4
  359. package/dist/components/sidebarNav/SidebarNavRailList.d.ts.map +0 -1
  360. package/dist/components/sidebarNav/SidebarNavRailList.js +0 -7
  361. package/dist/components/sidebarNav/SidebarNavRailList.js.map +0 -1
  362. package/dist/components/sidebarNav/SidebarNavRailSlot.d.ts +0 -6
  363. package/dist/components/sidebarNav/SidebarNavRailSlot.d.ts.map +0 -1
  364. package/dist/components/sidebarNav/SidebarNavRailSlot.js +0 -7
  365. package/dist/components/sidebarNav/SidebarNavRailSlot.js.map +0 -1
  366. package/dist/components/sidebarNav/SidebarNavSeparator.d.ts +0 -6
  367. package/dist/components/sidebarNav/SidebarNavSeparator.d.ts.map +0 -1
  368. package/dist/components/sidebarNav/SidebarNavSeparator.js +0 -8
  369. package/dist/components/sidebarNav/SidebarNavSeparator.js.map +0 -1
  370. package/dist/components/sidebarNav/SidebarNavTitle.d.ts +0 -4
  371. package/dist/components/sidebarNav/SidebarNavTitle.d.ts.map +0 -1
  372. package/dist/components/sidebarNav/SidebarNavTitle.js +0 -7
  373. package/dist/components/sidebarNav/SidebarNavTitle.js.map +0 -1
  374. package/dist/components/sidebarNav/SidebarNavTooltip.d.ts +0 -7
  375. package/dist/components/sidebarNav/SidebarNavTooltip.d.ts.map +0 -1
  376. package/dist/components/sidebarNav/SidebarNavTooltip.js.map +0 -1
  377. package/dist/components/sidebarNav/SidebarNavTrigger.d.ts +0 -8
  378. package/dist/components/sidebarNav/SidebarNavTrigger.d.ts.map +0 -1
  379. package/dist/components/sidebarNav/SidebarNavTrigger.js +0 -15
  380. package/dist/components/sidebarNav/SidebarNavTrigger.js.map +0 -1
  381. package/dist/components/sidebarNav/index.d.ts +0 -4
  382. package/dist/components/sidebarNav/index.d.ts.map +0 -1
  383. package/dist/components/sidebarNav/index.js +0 -3
  384. package/dist/components/sidebarNav/index.js.map +0 -1
  385. package/dist/components/sidebarNav/resolvePanelItemProps.d.ts +0 -4
  386. package/dist/components/sidebarNav/resolvePanelItemProps.d.ts.map +0 -1
  387. package/dist/components/sidebarNav/resolvePanelItemProps.js.map +0 -1
  388. package/dist/components/sidebarNav/resolvePanelItemProps.test.d.ts.map +0 -1
  389. package/dist/components/sidebarNav/resolvePanelItemProps.test.js.map +0 -1
  390. package/dist/components/sidebarNav/types.d.ts +0 -100
  391. package/dist/components/sidebarNav/types.d.ts.map +0 -1
  392. package/dist/components/sidebarNav/types.js +0 -4
  393. package/dist/components/sidebarNav/types.js.map +0 -1
  394. package/dist/components/sidebarNav/useControllableBoolean.d.ts.map +0 -1
  395. package/dist/components/sidebarNav/useControllableBoolean.js.map +0 -1
  396. package/src/components/sidebarNav/SidebarNav.stories.tsx +0 -484
  397. package/src/components/sidebarNav/SidebarNav.test.tsx +0 -611
  398. package/src/components/sidebarNav/SidebarNav.tsx +0 -230
  399. package/src/components/sidebarNav/SidebarNavContext.tsx +0 -28
  400. package/src/components/sidebarNav/SidebarNavItem.tsx +0 -162
  401. package/src/components/sidebarNav/SidebarNavPanel.tsx +0 -20
  402. package/src/components/sidebarNav/SidebarNavPanelNav.tsx +0 -55
  403. package/src/components/sidebarNav/SidebarNavRail.tsx +0 -20
  404. package/src/components/sidebarNav/SidebarNavRailItem.tsx +0 -83
  405. package/src/components/sidebarNav/SidebarNavRailList.tsx +0 -11
  406. package/src/components/sidebarNav/SidebarNavRailSlot.tsx +0 -15
  407. package/src/components/sidebarNav/SidebarNavSeparator.tsx +0 -19
  408. package/src/components/sidebarNav/SidebarNavTitle.tsx +0 -13
  409. package/src/components/sidebarNav/SidebarNavTrigger.tsx +0 -52
  410. package/src/components/sidebarNav/index.ts +0 -6
  411. package/src/components/sidebarNav/types.ts +0 -126
  412. /package/dist/components/{sidebarNav → sideNavigation}/resolvePanelItemProps.test.d.ts +0 -0
  413. /package/dist/{components/sidebarNav → utils/hooks}/useControllableBoolean.d.ts +0 -0
  414. /package/dist/{components/sidebarNav → utils/hooks}/useControllableBoolean.js +0 -0
  415. /package/src/{components/sidebarNav → utils/hooks}/useControllableBoolean.ts +0 -0
@@ -0,0 +1,98 @@
1
+ import classNames from 'classnames';
2
+ import type { HTMLAttributes } from 'react';
3
+ import { useMemo } from 'react';
4
+ import { Tooltip as TooltipPrimitive } from 'radix-ui';
5
+ import { SideNavigationProvider } from './SideNavigationContext.js';
6
+ import { SideNavigationGroup } from './SideNavigationGroup.js';
7
+ import type { SideNavigationGroupProps } from './SideNavigationGroup.js';
8
+ import { SideNavigationItem } from './SideNavigationItem.js';
9
+ import type { SideNavigationItemProps } from './SideNavigationItem.js';
10
+ import { SideNavigationTitle } from './SideNavigationTitle.js';
11
+ import type { SideNavigationTitleProps } from './SideNavigationTitle.js';
12
+ import { resolvePanelItemProps } from './resolvePanelItemProps.js';
13
+ import type {
14
+ SideNavigationGroupNode,
15
+ SideNavigationItemNode,
16
+ SideNavigationNode,
17
+ SideNavigationRenderLinkArgs,
18
+ } from './types.js';
19
+
20
+ export type SideNavigationProps = {
21
+ ariaLabel?: string;
22
+ items?: SideNavigationNode[];
23
+ renderLink?: (args: SideNavigationRenderLinkArgs) => React.ReactElement;
24
+ } & Omit<HTMLAttributes<HTMLElement>, 'children'> & {
25
+ children?: React.ReactNode;
26
+ };
27
+
28
+ function renderItemNode(node: SideNavigationItemNode) {
29
+ return <SideNavigationItem key={node.id} {...resolvePanelItemProps(node)} />;
30
+ }
31
+
32
+ function renderNodes(nodes: SideNavigationNode[]): React.ReactNode {
33
+ return nodes.map((node) => {
34
+ if (node.type === 'group') {
35
+ return (
36
+ <SideNavigationGroup
37
+ key={node.id}
38
+ label={node.label}
39
+ defaultExpanded={node.defaultExpanded}
40
+ expanded={node.expanded}
41
+ onExpandedChange={node.onExpandedChange}
42
+ >
43
+ {renderNodes(node.children ?? [])}
44
+ </SideNavigationGroup>
45
+ );
46
+ }
47
+
48
+ return renderItemNode(node);
49
+ });
50
+ }
51
+
52
+ const SideNavigationImpl = (props: SideNavigationProps) => {
53
+ const {
54
+ ariaLabel = 'Side navigation',
55
+ items,
56
+ renderLink,
57
+ className,
58
+ children,
59
+ ...rest
60
+ } = props;
61
+
62
+ const contextValue = useMemo(() => ({ renderLink }), [renderLink]);
63
+
64
+ return (
65
+ <TooltipPrimitive.Provider delayDuration={400}>
66
+ <SideNavigationProvider value={contextValue}>
67
+ <nav aria-label={ariaLabel} className={classNames('ds-side-navigation', className)} {...rest}>
68
+ <ul className="ds-side-navigation__list">
69
+ {items ? renderNodes(items) : children}
70
+ </ul>
71
+ </nav>
72
+ </SideNavigationProvider>
73
+ </TooltipPrimitive.Provider>
74
+ );
75
+ };
76
+
77
+ type SideNavigationComponent = typeof SideNavigationImpl & {
78
+ Group: typeof SideNavigationGroup;
79
+ Item: typeof SideNavigationItem;
80
+ Title: typeof SideNavigationTitle;
81
+ };
82
+
83
+ export const SideNavigation = SideNavigationImpl as SideNavigationComponent;
84
+
85
+ SideNavigation.Group = SideNavigationGroup;
86
+ SideNavigation.Item = SideNavigationItem;
87
+ SideNavigation.Title = SideNavigationTitle;
88
+
89
+ export namespace SideNavigation {
90
+ export type Props = SideNavigationProps;
91
+ export type GroupProps = SideNavigationGroupProps;
92
+ export type ItemProps = SideNavigationItemProps;
93
+ export type TitleProps = SideNavigationTitleProps;
94
+ export type Node = SideNavigationNode;
95
+ export type ItemNode = SideNavigationItemNode;
96
+ export type GroupNode = SideNavigationGroupNode;
97
+ export type RenderLinkArgs = SideNavigationRenderLinkArgs;
98
+ }
@@ -0,0 +1,25 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { SideNavigationRenderLinkArgs } from './types.js';
3
+
4
+ export type SideNavigationContextValue = {
5
+ renderLink?: (args: SideNavigationRenderLinkArgs) => React.ReactElement;
6
+ };
7
+
8
+ const SideNavigationContext = createContext<SideNavigationContextValue | null>(null);
9
+
10
+ export function SideNavigationProvider(props: { value: SideNavigationContextValue; children: React.ReactNode }) {
11
+ const { value, children } = props;
12
+ return (
13
+ <SideNavigationContext.Provider value={value}>
14
+ {children}
15
+ </SideNavigationContext.Provider>
16
+ );
17
+ }
18
+
19
+ export function useSideNavigationContext() {
20
+ const ctx = useContext(SideNavigationContext);
21
+ if (!ctx) {
22
+ throw new Error('SideNavigation components must be used within <SideNavigation>.');
23
+ }
24
+ return ctx;
25
+ }
@@ -0,0 +1,103 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { describe, expect, test, vi } from 'vitest';
5
+ import { SideNavigationGroup } from './SideNavigationGroup.js';
6
+
7
+ describe('SideNavigationGroup', () => {
8
+ test('renders the label on a button with aria-expanded reflecting the collapsed state', () => {
9
+ render(
10
+ <SideNavigationGroup label="Registers">
11
+ <li>Daily attendance</li>
12
+ </SideNavigationGroup>,
13
+ );
14
+ const trigger = screen.getByRole('button', { name: 'Registers' });
15
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
16
+ });
17
+
18
+ test('defaults to expanded when defaultExpanded is true', () => {
19
+ render(
20
+ <SideNavigationGroup label="Registers" defaultExpanded>
21
+ <li>Daily attendance</li>
22
+ </SideNavigationGroup>,
23
+ );
24
+ expect(screen.getByRole('button', { name: 'Registers' })).toHaveAttribute('aria-expanded', 'true');
25
+ });
26
+
27
+ test('toggles aria-expanded and the children list aria-hidden when clicked (uncontrolled)', async () => {
28
+ render(
29
+ <SideNavigationGroup label="Registers">
30
+ <li>Daily attendance</li>
31
+ </SideNavigationGroup>,
32
+ );
33
+
34
+ const trigger = screen.getByRole('button', { name: 'Registers' });
35
+ const childrenList = document.getElementById(trigger.getAttribute('aria-controls')!);
36
+ expect(childrenList).toHaveAttribute('aria-hidden', 'true');
37
+
38
+ await userEvent.click(trigger);
39
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
40
+ expect(childrenList).toHaveAttribute('aria-hidden', 'false');
41
+
42
+ await userEvent.click(trigger);
43
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
44
+ expect(childrenList).toHaveAttribute('aria-hidden', 'true');
45
+ });
46
+
47
+ test('calls onExpandedChange when toggled', async () => {
48
+ const onExpandedChange = vi.fn();
49
+ render(
50
+ <SideNavigationGroup label="Registers" onExpandedChange={onExpandedChange}>
51
+ <li>Daily attendance</li>
52
+ </SideNavigationGroup>,
53
+ );
54
+
55
+ await userEvent.click(screen.getByRole('button', { name: 'Registers' }));
56
+ expect(onExpandedChange).toHaveBeenCalledWith(true);
57
+ });
58
+
59
+ test('is fully controlled via the expanded prop', async () => {
60
+ const onExpandedChange = vi.fn();
61
+ const { rerender } = render(
62
+ <SideNavigationGroup label="Registers" expanded={false} onExpandedChange={onExpandedChange}>
63
+ <li>Daily attendance</li>
64
+ </SideNavigationGroup>,
65
+ );
66
+
67
+ const trigger = screen.getByRole('button', { name: 'Registers' });
68
+ await userEvent.click(trigger);
69
+
70
+ expect(onExpandedChange).toHaveBeenCalledWith(true);
71
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
72
+
73
+ rerender(
74
+ <SideNavigationGroup label="Registers" expanded onExpandedChange={onExpandedChange}>
75
+ <li>Daily attendance</li>
76
+ </SideNavigationGroup>,
77
+ );
78
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
79
+ });
80
+
81
+ test('supports nested groups collapsing independently', async () => {
82
+ render(
83
+ <SideNavigationGroup label="A">
84
+ <SideNavigationGroup label="B">
85
+ <li>C</li>
86
+ </SideNavigationGroup>
87
+ </SideNavigationGroup>,
88
+ );
89
+
90
+ const triggerA = screen.getByRole('button', { name: 'A' });
91
+ const triggerB = screen.getByRole('button', { name: 'B', hidden: true });
92
+ const groupBChildren = document.getElementById(triggerB.getAttribute('aria-controls')!);
93
+
94
+ expect(groupBChildren).toHaveAttribute('aria-hidden', 'true');
95
+
96
+ await userEvent.click(triggerA);
97
+ expect(groupBChildren).toHaveAttribute('aria-hidden', 'true');
98
+
99
+ await userEvent.click(screen.getByRole('button', { name: 'B' }));
100
+ expect(groupBChildren).toHaveAttribute('aria-hidden', 'false');
101
+ expect(screen.getByText('C')).toBeInTheDocument();
102
+ });
103
+ });
@@ -2,9 +2,9 @@ import { Icon } from 'Components/icon/Icon';
2
2
  import classNames from 'classnames';
3
3
  import type { HTMLAttributes } from 'react';
4
4
  import { useId } from 'react';
5
- import { useControllableBoolean } from './useControllableBoolean.js';
5
+ import { useControllableBoolean } from 'Utils/hooks/useControllableBoolean';
6
6
 
7
- export type SidebarNavGroupProps = {
7
+ export type SideNavigationGroupProps = {
8
8
  label: string;
9
9
  expanded?: boolean;
10
10
  defaultExpanded?: boolean;
@@ -12,7 +12,7 @@ export type SidebarNavGroupProps = {
12
12
  children: React.ReactNode;
13
13
  } & Omit<HTMLAttributes<HTMLLIElement>, 'children'>;
14
14
 
15
- export const SidebarNavGroup = (props: SidebarNavGroupProps) => {
15
+ export const SideNavigationGroup = (props: SideNavigationGroupProps) => {
16
16
  const {
17
17
  label,
18
18
  expanded,
@@ -31,10 +31,10 @@ export const SidebarNavGroup = (props: SidebarNavGroupProps) => {
31
31
  });
32
32
 
33
33
  return (
34
- <li className={classNames('ds-sidebar-nav__list-item', 'ds-sidebar-nav__group', className)} {...rest}>
34
+ <li className={classNames('ds-side-navigation__list-item', 'ds-side-navigation__group', className)} {...rest}>
35
35
  <button
36
36
  type="button"
37
- className="ds-sidebar-nav__group-link"
37
+ className="ds-side-navigation__group-link"
38
38
  aria-expanded={isExpanded}
39
39
  aria-controls={ulId}
40
40
  onClick={() => setExpanded(!isExpanded)}
@@ -42,15 +42,15 @@ export const SidebarNavGroup = (props: SidebarNavGroupProps) => {
42
42
  <Icon
43
43
  name="chevron-right"
44
44
  size={16}
45
- className="ds-sidebar-nav__group-chevron"
45
+ className="ds-side-navigation__group-chevron"
46
46
  />
47
- <span className="ds-sidebar-nav__group-label">{label}</span>
47
+ <span className="ds-side-navigation__group-label">{label}</span>
48
48
  </button>
49
49
 
50
50
  <ul
51
51
  id={ulId}
52
52
  aria-hidden={!isExpanded}
53
- className="ds-sidebar-nav__group-children"
53
+ className="ds-side-navigation__group-children"
54
54
  >
55
55
  {children}
56
56
  </ul>
@@ -0,0 +1,137 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { describe, expect, test, vi } from 'vitest';
5
+ import { SideNavigation } from './SideNavigation.js';
6
+ import { SideNavigationItem } from './SideNavigationItem.js';
7
+
8
+ describe('SideNavigationItem', () => {
9
+ test('renders as a link when href is provided', () => {
10
+ render(
11
+ <SideNavigation>
12
+ <SideNavigationItem itemId="daily" href="/daily">Daily Attendance</SideNavigationItem>
13
+ </SideNavigation>,
14
+ );
15
+ const link = screen.getByRole('link', { name: 'Daily Attendance' });
16
+ expect(link).toHaveAttribute('href', '/daily');
17
+ });
18
+
19
+ test('renders as a button when linkElement is "button"', async () => {
20
+ const onClick = vi.fn();
21
+ render(
22
+ <SideNavigation>
23
+ <SideNavigationItem itemId="action" linkElement="button" linkElementProps={{ onClick }}>
24
+ Run action
25
+ </SideNavigationItem>
26
+ </SideNavigation>,
27
+ );
28
+ const button = screen.getByRole('button', { name: 'Run action' });
29
+ await userEvent.click(button);
30
+ expect(onClick).toHaveBeenCalledOnce();
31
+ });
32
+
33
+ test('applies the current class and aria-current="page" when current is true', () => {
34
+ render(
35
+ <SideNavigation>
36
+ <SideNavigationItem itemId="daily" href="/daily" current>Daily Attendance</SideNavigationItem>
37
+ </SideNavigation>,
38
+ );
39
+ const link = screen.getByRole('link', { name: 'Daily Attendance' });
40
+ expect(link).toHaveClass('ds-side-navigation__item-link--current');
41
+ expect(link).toHaveAttribute('aria-current', 'page');
42
+ });
43
+
44
+ test('does not set aria-current when current is false', () => {
45
+ render(
46
+ <SideNavigation>
47
+ <SideNavigationItem itemId="daily" href="/daily">Daily Attendance</SideNavigationItem>
48
+ </SideNavigation>,
49
+ );
50
+ expect(screen.getByRole('link', { name: 'Daily Attendance' })).not.toHaveAttribute('aria-current');
51
+ });
52
+
53
+ test('assigns a testid scoped to itemId', () => {
54
+ render(
55
+ <SideNavigation>
56
+ <SideNavigationItem itemId="daily" href="/daily">Daily Attendance</SideNavigationItem>
57
+ </SideNavigation>,
58
+ );
59
+ expect(screen.getByTestId('side-navigation-item-daily')).toBeInTheDocument();
60
+ });
61
+
62
+ test('passes linkProps and linkElementProps through to the anchor', () => {
63
+ render(
64
+ <SideNavigation>
65
+ <SideNavigationItem
66
+ itemId="daily"
67
+ href="/daily"
68
+ linkProps={{ 'data-track': 'daily' }}
69
+ linkElementProps={{ 'aria-describedby': 'daily-desc' }}
70
+ >
71
+ Daily Attendance
72
+ </SideNavigationItem>
73
+ </SideNavigation>,
74
+ );
75
+ const link = screen.getByRole('link', { name: 'Daily Attendance' });
76
+ expect(link).toHaveAttribute('data-track', 'daily');
77
+ expect(link).toHaveAttribute('aria-describedby', 'daily-desc');
78
+ });
79
+
80
+ test('renders a favourite button separate from the item link when canFavourite is true', async () => {
81
+ const onFavouriteClick = vi.fn();
82
+ render(
83
+ <SideNavigation>
84
+ <SideNavigationItem
85
+ itemId="daily"
86
+ href="/daily"
87
+ canFavourite
88
+ isPressed
89
+ favouriteTooltip="Remove from favourites"
90
+ onFavouriteClick={onFavouriteClick}
91
+ >
92
+ Daily Attendance
93
+ </SideNavigationItem>
94
+ </SideNavigation>,
95
+ );
96
+
97
+ const listItem = screen.getByTestId('side-navigation-item-daily');
98
+ const link = screen.getByRole('link', { name: 'Daily Attendance' });
99
+ const favourite = screen.getByTestId('side-navigation-item-favourite-daily');
100
+
101
+ expect(listItem).toContainElement(link);
102
+ expect(listItem).toContainElement(favourite);
103
+ expect(link).not.toContainElement(favourite);
104
+ expect(favourite).toHaveAttribute('aria-pressed', 'true');
105
+
106
+ await userEvent.click(favourite);
107
+ expect(onFavouriteClick).toHaveBeenCalledOnce();
108
+ });
109
+
110
+ test('does not render a favourite button when canFavourite is not set', () => {
111
+ render(
112
+ <SideNavigation>
113
+ <SideNavigationItem itemId="daily" href="/daily">Daily Attendance</SideNavigationItem>
114
+ </SideNavigation>,
115
+ );
116
+ expect(screen.queryByTestId('side-navigation-item-favourite-daily')).not.toBeInTheDocument();
117
+ });
118
+
119
+ test('uses renderLink from the parent SideNavigation to render the anchor', () => {
120
+ const renderLink = vi.fn(({ href, className, children, onClick }) => (
121
+ <a href={href} className={className} data-custom-link="true" onClick={onClick}>
122
+ {children}
123
+ </a>
124
+ ));
125
+
126
+ render(
127
+ <SideNavigation renderLink={renderLink}>
128
+ <SideNavigationItem itemId="daily" href="/daily">Daily Attendance</SideNavigationItem>
129
+ </SideNavigation>,
130
+ );
131
+
132
+ expect(screen.getByText('Daily Attendance').closest('a')).toHaveAttribute('data-custom-link', 'true');
133
+ expect(renderLink).toHaveBeenCalledWith(
134
+ expect.objectContaining({ href: '/daily' }),
135
+ );
136
+ });
137
+ });
@@ -0,0 +1,125 @@
1
+ import classNames from 'classnames';
2
+ import type {
3
+ AnchorHTMLAttributes,
4
+ ButtonHTMLAttributes,
5
+ HTMLAttributes,
6
+ } from 'react';
7
+ import { useSideNavigationContext } from './SideNavigationContext.js';
8
+ import { SideNavigationItemFavourite } from './SideNavigationItemFavourite.js';
9
+ import type { SideNavigationLinkProps } from './types.js';
10
+
11
+ type SideNavigationItemFavouriteVariant
12
+ = | { canFavourite?: false; favouriteTooltip?: never }
13
+ | { canFavourite: true; favouriteTooltip: string };
14
+
15
+ type SideNavigationItemLinkVariant = {
16
+ linkElement?: 'link';
17
+ href: string;
18
+ linkElementProps?: Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'className' | 'children'>;
19
+ linkProps?: SideNavigationLinkProps;
20
+ };
21
+
22
+ type SideNavigationItemButtonVariant = {
23
+ linkElement: 'button';
24
+ href?: never;
25
+ linkElementProps?: ButtonHTMLAttributes<HTMLButtonElement>;
26
+ linkProps?: never;
27
+ };
28
+
29
+ export type SideNavigationItemProps = {
30
+ current?: boolean;
31
+ itemId: string;
32
+ isPressed?: boolean;
33
+ onFavouriteClick?: React.MouseEventHandler<HTMLButtonElement>;
34
+ children: React.ReactNode;
35
+ } & Omit<HTMLAttributes<HTMLLIElement>, 'children' | 'onClick'>
36
+ & SideNavigationItemFavouriteVariant
37
+ & (SideNavigationItemLinkVariant | SideNavigationItemButtonVariant);
38
+
39
+ export const SideNavigationItem = (props: SideNavigationItemProps) => {
40
+ const {
41
+ current,
42
+ itemId,
43
+ canFavourite,
44
+ favouriteTooltip,
45
+ isPressed = false,
46
+ onFavouriteClick,
47
+ children,
48
+ className,
49
+ linkElement = 'link',
50
+ href,
51
+ linkElementProps,
52
+ linkProps,
53
+ ...rest
54
+ } = props;
55
+
56
+ const { renderLink } = useSideNavigationContext();
57
+
58
+ const linkClassName = classNames(
59
+ 'ds-side-navigation__item-link',
60
+ { 'ds-side-navigation__item-link--current': Boolean(current) },
61
+ );
62
+
63
+ const linkChildren = <span className="ds-side-navigation__item-label">{children}</span>;
64
+
65
+ const anchorLinkElementProps = linkElementProps as AnchorHTMLAttributes<HTMLAnchorElement> | undefined;
66
+ const buttonLinkElementProps = linkElementProps as ButtonHTMLAttributes<HTMLButtonElement> | undefined;
67
+
68
+ const renderAnchorElement = () => renderLink
69
+ ? renderLink({
70
+ href: href!,
71
+ className: linkClassName,
72
+ ariaCurrent: current ? 'page' : undefined,
73
+ linkProps,
74
+ linkElementProps: anchorLinkElementProps,
75
+ onClick: anchorLinkElementProps?.onClick as React.MouseEventHandler | undefined,
76
+ children: linkChildren,
77
+ })
78
+ : (
79
+ <a
80
+ href={href}
81
+ className={linkClassName}
82
+ aria-current={current ? 'page' : undefined}
83
+ {...linkProps}
84
+ {...anchorLinkElementProps}
85
+ >
86
+ {linkChildren}
87
+ </a>
88
+ );
89
+
90
+ const linkElementNode = linkElement === 'button'
91
+ ? (
92
+ <button
93
+ type="button"
94
+ className={linkClassName}
95
+ aria-current={current ? 'page' : undefined}
96
+ {...buttonLinkElementProps}
97
+ >
98
+ {linkChildren}
99
+ </button>
100
+ )
101
+ : renderAnchorElement();
102
+
103
+ return (
104
+ <li
105
+ className={classNames(
106
+ 'ds-side-navigation__list-item',
107
+ 'ds-side-navigation__item',
108
+ { 'ds-side-navigation__item--has-favourite': canFavourite },
109
+ className,
110
+ )}
111
+ data-testid={`side-navigation-item-${itemId}`}
112
+ {...rest}
113
+ >
114
+ {linkElementNode}
115
+ {canFavourite === true && (
116
+ <SideNavigationItemFavourite
117
+ isPressed={isPressed}
118
+ itemId={itemId}
119
+ favouriteTooltip={favouriteTooltip}
120
+ onClick={onFavouriteClick}
121
+ />
122
+ )}
123
+ </li>
124
+ );
125
+ };
@@ -0,0 +1,54 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { Tooltip as TooltipPrimitive } from 'radix-ui';
5
+ import { describe, expect, test, vi } from 'vitest';
6
+ import { SideNavigationItemFavourite } from './SideNavigationItemFavourite.js';
7
+
8
+ const renderWithProvider = (ui: React.ReactElement) => render(
9
+ <TooltipPrimitive.Provider>{ui}</TooltipPrimitive.Provider>,
10
+ );
11
+
12
+ describe('SideNavigationItemFavourite', () => {
13
+ test('uses favouriteTooltip as the accessible name', () => {
14
+ renderWithProvider(
15
+ <SideNavigationItemFavourite itemId="daily" isPressed={false} favouriteTooltip="Add to favourites" />,
16
+ );
17
+ expect(screen.getByRole('button', { name: 'Add to favourites' })).toBeInTheDocument();
18
+ });
19
+
20
+ test('sets aria-pressed to reflect isPressed', () => {
21
+ renderWithProvider(
22
+ <SideNavigationItemFavourite itemId="daily" isPressed favouriteTooltip="Remove from favourites" />,
23
+ );
24
+ expect(screen.getByRole('button', { name: 'Remove from favourites' })).toHaveAttribute('aria-pressed', 'true');
25
+ });
26
+
27
+ test('assigns a testid scoped to itemId', () => {
28
+ renderWithProvider(
29
+ <SideNavigationItemFavourite itemId="daily" isPressed={false} favouriteTooltip="Add to favourites" />,
30
+ );
31
+ expect(screen.getByTestId('side-navigation-item-favourite-daily')).toBeInTheDocument();
32
+ });
33
+
34
+ test('calls onClick and prevents the click from bubbling to a parent link', async () => {
35
+ const onClick = vi.fn();
36
+ const parentOnClick = vi.fn();
37
+
38
+ renderWithProvider(
39
+ <div onClick={parentOnClick}>
40
+ <SideNavigationItemFavourite
41
+ itemId="daily"
42
+ isPressed={false}
43
+ favouriteTooltip="Add to favourites"
44
+ onClick={onClick}
45
+ />
46
+ </div>,
47
+ );
48
+
49
+ await userEvent.click(screen.getByRole('button', { name: 'Add to favourites' }));
50
+
51
+ expect(onClick).toHaveBeenCalledOnce();
52
+ expect(parentOnClick).not.toHaveBeenCalled();
53
+ });
54
+ });
@@ -1,15 +1,15 @@
1
1
  import { Icon } from 'Components/icon/Icon';
2
2
  import classNames from 'classnames';
3
3
  import type { ButtonHTMLAttributes } from 'react';
4
- import { SidebarNavTooltip } from './SidebarNavTooltip.js';
4
+ import { SideNavigationTooltip } from './SideNavigationTooltip.js';
5
5
 
6
- export type SidebarNavItemFavouriteProps = {
6
+ export type SideNavigationItemFavouriteProps = {
7
7
  isPressed: boolean;
8
8
  itemId: string;
9
9
  favouriteTooltip: string;
10
10
  } & ButtonHTMLAttributes<HTMLButtonElement>;
11
11
 
12
- export const SidebarNavItemFavourite = (props: SidebarNavItemFavouriteProps) => {
12
+ export const SideNavigationItemFavourite = (props: SideNavigationItemFavouriteProps) => {
13
13
  const {
14
14
  isPressed,
15
15
  itemId,
@@ -22,10 +22,10 @@ export const SidebarNavItemFavourite = (props: SidebarNavItemFavouriteProps) =>
22
22
  const button = (
23
23
  <button
24
24
  type="button"
25
- className={classNames('ds-sidebar-nav__item-favourite', className)}
25
+ className={classNames('ds-side-navigation__item-favourite', className)}
26
26
  aria-pressed={isPressed}
27
27
  aria-label={favouriteTooltip}
28
- data-testid={`sidebar-nav-panel-item-favourite-${itemId}`}
28
+ data-testid={`side-navigation-item-favourite-${itemId}`}
29
29
  onClick={(e) => {
30
30
  e.preventDefault();
31
31
  e.stopPropagation();
@@ -42,8 +42,8 @@ export const SidebarNavItemFavourite = (props: SidebarNavItemFavouriteProps) =>
42
42
  );
43
43
 
44
44
  return (
45
- <SidebarNavTooltip label={favouriteTooltip} side="left">
45
+ <SideNavigationTooltip label={favouriteTooltip} side="left">
46
46
  {button}
47
- </SidebarNavTooltip>
47
+ </SideNavigationTooltip>
48
48
  );
49
49
  };
@@ -0,0 +1,23 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { describe, expect, test } from 'vitest';
4
+ import { SideNavigationTitle } from './SideNavigationTitle.js';
5
+
6
+ describe('SideNavigationTitle', () => {
7
+ test('renders children inside a heading', () => {
8
+ render(<SideNavigationTitle>Attendance</SideNavigationTitle>);
9
+ expect(screen.getByRole('heading', { name: 'Attendance', level: 2 })).toBeInTheDocument();
10
+ });
11
+
12
+ test('applies the base class alongside a custom className', () => {
13
+ render(<SideNavigationTitle className="custom">Attendance</SideNavigationTitle>);
14
+ const heading = screen.getByRole('heading', { name: 'Attendance' });
15
+ expect(heading).toHaveClass('ds-side-navigation__title');
16
+ expect(heading).toHaveClass('custom');
17
+ });
18
+
19
+ test('passes through extra HTML attributes', () => {
20
+ render(<SideNavigationTitle data-testid="panel-title">Attendance</SideNavigationTitle>);
21
+ expect(screen.getByTestId('panel-title')).toBeInTheDocument();
22
+ });
23
+ });
@@ -0,0 +1,13 @@
1
+ import classNames from 'classnames';
2
+ import type { HTMLAttributes } from 'react';
3
+
4
+ export type SideNavigationTitleProps = HTMLAttributes<HTMLHeadingElement>;
5
+
6
+ export const SideNavigationTitle = (props: SideNavigationTitleProps) => {
7
+ const { className, children, ...rest } = props;
8
+ return (
9
+ <h2 className={classNames('ds-side-navigation__title', className)} {...rest}>
10
+ {children}
11
+ </h2>
12
+ );
13
+ };