@arbor-education/design-system.components 4.7.0-alpha.0 → 4.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/.github/workflows/chromatic.yml +1 -1
  2. package/.nvmrc +1 -1
  3. package/CHANGELOG.md +42 -0
  4. package/dist/components/datePicker/DatePicker.d.ts +2 -0
  5. package/dist/components/datePicker/DatePicker.d.ts.map +1 -1
  6. package/dist/components/datePicker/DatePicker.js +2 -2
  7. package/dist/components/datePicker/DatePicker.js.map +1 -1
  8. package/dist/components/datePicker/DatePicker.stories.d.ts +18 -0
  9. package/dist/components/datePicker/DatePicker.stories.d.ts.map +1 -1
  10. package/dist/components/datePicker/DatePicker.stories.js +30 -8
  11. package/dist/components/datePicker/DatePicker.stories.js.map +1 -1
  12. package/dist/components/datePicker/DatePicker.test.js +9 -1
  13. package/dist/components/datePicker/DatePicker.test.js.map +1 -1
  14. package/dist/components/dateTimePicker/DateTimePicker.d.ts +2 -0
  15. package/dist/components/dateTimePicker/DateTimePicker.d.ts.map +1 -1
  16. package/dist/components/dateTimePicker/DateTimePicker.js +2 -2
  17. package/dist/components/dateTimePicker/DateTimePicker.js.map +1 -1
  18. package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts +18 -0
  19. package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts.map +1 -1
  20. package/dist/components/dateTimePicker/DateTimePicker.stories.js +32 -9
  21. package/dist/components/dateTimePicker/DateTimePicker.stories.js.map +1 -1
  22. package/dist/components/dateTimePicker/DateTimePicker.test.js +9 -1
  23. package/dist/components/dateTimePicker/DateTimePicker.test.js.map +1 -1
  24. package/dist/components/fileUpload/FileUpload.d.ts +12 -4
  25. package/dist/components/fileUpload/FileUpload.d.ts.map +1 -1
  26. package/dist/components/fileUpload/FileUpload.js +32 -7
  27. package/dist/components/fileUpload/FileUpload.js.map +1 -1
  28. package/dist/components/fileUpload/FileUpload.stories.d.ts +1 -0
  29. package/dist/components/fileUpload/FileUpload.stories.d.ts.map +1 -1
  30. package/dist/components/fileUpload/FileUpload.stories.js +59 -6
  31. package/dist/components/fileUpload/FileUpload.stories.js.map +1 -1
  32. package/dist/components/fileUpload/FileUpload.test.js +102 -1
  33. package/dist/components/fileUpload/FileUpload.test.js.map +1 -1
  34. package/dist/components/fileUpload/FileUploadLinkModal.d.ts +14 -0
  35. package/dist/components/fileUpload/FileUploadLinkModal.d.ts.map +1 -0
  36. package/dist/components/fileUpload/FileUploadLinkModal.js +61 -0
  37. package/dist/components/fileUpload/FileUploadLinkModal.js.map +1 -0
  38. package/dist/components/filterBar/FilterBar.d.ts +1 -0
  39. package/dist/components/filterBar/FilterBar.d.ts.map +1 -1
  40. package/dist/components/filterBar/FilterBar.js +7 -1
  41. package/dist/components/filterBar/FilterBar.js.map +1 -1
  42. package/dist/components/filterBar/FilterBar.test.js +25 -1
  43. package/dist/components/filterBar/FilterBar.test.js.map +1 -1
  44. package/dist/components/formField/inputs/text/TextInput.d.ts.map +1 -1
  45. package/dist/components/formField/inputs/text/TextInput.js +2 -2
  46. package/dist/components/formField/inputs/text/TextInput.js.map +1 -1
  47. package/dist/components/kpiCard/KPICard.stories.js +2 -2
  48. package/dist/components/kpiCard/KPICard.stories.js.map +1 -1
  49. package/dist/components/sideNavigation/SideNavigation.d.ts +5 -0
  50. package/dist/components/sideNavigation/SideNavigation.d.ts.map +1 -1
  51. package/dist/components/sideNavigation/SideNavigation.js +5 -4
  52. package/dist/components/sideNavigation/SideNavigation.js.map +1 -1
  53. package/dist/components/sideNavigation/SideNavigation.stories.d.ts.map +1 -1
  54. package/dist/components/sideNavigation/SideNavigation.stories.js +6 -2
  55. package/dist/components/sideNavigation/SideNavigation.stories.js.map +1 -1
  56. package/dist/components/sideNavigation/SideNavigation.test.js +13 -0
  57. package/dist/components/sideNavigation/SideNavigation.test.js.map +1 -1
  58. package/dist/components/sideNavigation/SideNavigationTitle.d.ts.map +1 -1
  59. package/dist/components/sideNavigation/SideNavigationTitle.js +1 -1
  60. package/dist/components/sideNavigation/SideNavigationTitle.js.map +1 -1
  61. package/dist/components/sidebar/Sidebar.stories.d.ts.map +1 -1
  62. package/dist/components/sidebar/Sidebar.stories.js +3 -11
  63. package/dist/components/sidebar/Sidebar.stories.js.map +1 -1
  64. package/dist/components/table/columnFilters/TimeFilter/TimeFilter.d.ts.map +1 -1
  65. package/dist/components/table/columnFilters/TimeFilter/TimeFilter.js +1 -1
  66. package/dist/components/table/columnFilters/TimeFilter/TimeFilter.js.map +1 -1
  67. package/dist/components/table/columnFilters/TimeFilter/TimeFilter.test.js +2 -2
  68. package/dist/components/table/columnFilters/filterResetButton/FilterResetButton.d.ts +1 -0
  69. package/dist/components/table/columnFilters/filterResetButton/FilterResetButton.d.ts.map +1 -1
  70. package/dist/components/table/columnFilters/filterResetButton/FilterResetButton.js +2 -2
  71. package/dist/components/table/columnFilters/filterResetButton/FilterResetButton.js.map +1 -1
  72. package/dist/components/table/pagination/PageSizeSelector.js +1 -1
  73. package/dist/components/table/pagination/PageSizeSelector.js.map +1 -1
  74. package/dist/components/table/pagination/Pagination.test.js +3 -3
  75. package/dist/components/table/pagination/Pagination.test.js.map +1 -1
  76. package/dist/index.css +20 -1
  77. package/dist/index.css.map +1 -1
  78. package/package.json +1 -1
  79. package/src/components/datePicker/DatePicker.stories.tsx +30 -3
  80. package/src/components/datePicker/DatePicker.test.tsx +15 -0
  81. package/src/components/datePicker/DatePicker.tsx +6 -0
  82. package/src/components/dateTimePicker/DateTimePicker.stories.tsx +32 -1
  83. package/src/components/dateTimePicker/DateTimePicker.test.tsx +15 -0
  84. package/src/components/dateTimePicker/DateTimePicker.tsx +6 -0
  85. package/src/components/fileUpload/FileUpload.stories.tsx +74 -6
  86. package/src/components/fileUpload/FileUpload.test.tsx +139 -1
  87. package/src/components/fileUpload/FileUpload.tsx +109 -14
  88. package/src/components/fileUpload/FileUploadLinkModal.tsx +109 -0
  89. package/src/components/fileUpload/fileUpload.scss +24 -1
  90. package/src/components/filterBar/FilterBar.test.tsx +50 -0
  91. package/src/components/filterBar/FilterBar.tsx +10 -1
  92. package/src/components/formField/inputs/text/TextInput.tsx +2 -0
  93. package/src/components/kpiCard/KPICard.stories.tsx +2 -2
  94. package/src/components/sideNavigation/SideNavigation.stories.tsx +6 -2
  95. package/src/components/sideNavigation/SideNavigation.test.tsx +21 -0
  96. package/src/components/sideNavigation/SideNavigation.tsx +19 -2
  97. package/src/components/sideNavigation/SideNavigationTitle.tsx +5 -3
  98. package/src/components/sidebar/Sidebar.stories.tsx +3 -16
  99. package/src/components/table/columnFilters/TimeFilter/TimeFilter.test.tsx +2 -2
  100. package/src/components/table/columnFilters/TimeFilter/TimeFilter.tsx +4 -1
  101. package/src/components/table/columnFilters/filterResetButton/FilterResetButton.tsx +3 -2
  102. package/src/components/table/pagination/PageSizeSelector.tsx +1 -1
  103. package/src/components/table/pagination/Pagination.test.tsx +3 -3
  104. package/.changeset/twenty-groups-wonder.md +0 -5
  105. package/.changeset/yellow-falcons-search.md +0 -5
  106. package/dist/components/button/Button.story.d.ts +0 -47
  107. package/dist/components/button/Button.story.d.ts.map +0 -1
  108. package/dist/components/button/Button.story.js +0 -123
  109. package/dist/components/button/Button.story.js.map +0 -1
  110. package/dist/components/button/ButtonUsageExample.d.ts +0 -2
  111. package/dist/components/button/ButtonUsageExample.d.ts.map +0 -1
  112. package/dist/components/button/ButtonUsageExample.js +0 -11
  113. package/dist/components/button/ButtonUsageExample.js.map +0 -1
  114. package/dist/components/combobox/useElementWidth.d.ts +0 -2
  115. package/dist/components/combobox/useElementWidth.d.ts.map +0 -1
  116. package/dist/components/combobox/useElementWidth.js +0 -31
  117. package/dist/components/combobox/useElementWidth.js.map +0 -1
  118. package/dist/components/combobox/useVisibleChips.d.ts +0 -21
  119. package/dist/components/combobox/useVisibleChips.d.ts.map +0 -1
  120. package/dist/components/combobox/useVisibleChips.js +0 -59
  121. package/dist/components/combobox/useVisibleChips.js.map +0 -1
  122. package/dist/components/combobox/useVisibleChips.test.d.ts +0 -2
  123. package/dist/components/combobox/useVisibleChips.test.d.ts.map +0 -1
  124. package/dist/components/combobox/useVisibleChips.test.js +0 -81
  125. package/dist/components/combobox/useVisibleChips.test.js.map +0 -1
  126. package/dist/components/heading/HeadingInnerContainer.d.ts +0 -5
  127. package/dist/components/heading/HeadingInnerContainer.d.ts.map +0 -1
  128. package/dist/components/heading/HeadingInnerContainer.js +0 -7
  129. package/dist/components/heading/HeadingInnerContainer.js.map +0 -1
  130. package/dist/components/icoText/IcoText.d.ts +0 -37
  131. package/dist/components/icoText/IcoText.d.ts.map +0 -1
  132. package/dist/components/icoText/IcoText.js +0 -29
  133. package/dist/components/icoText/IcoText.js.map +0 -1
  134. package/dist/components/icoText/IcoText.stories.d.ts +0 -60
  135. package/dist/components/icoText/IcoText.stories.d.ts.map +0 -1
  136. package/dist/components/icoText/IcoText.stories.js +0 -319
  137. package/dist/components/icoText/IcoText.stories.js.map +0 -1
  138. package/dist/components/icoText/IcoText.test.d.ts +0 -2
  139. package/dist/components/icoText/IcoText.test.d.ts.map +0 -1
  140. package/dist/components/icoText/IcoText.test.js +0 -27
  141. package/dist/components/icoText/IcoText.test.js.map +0 -1
  142. package/dist/components/sidebarNav/SidebarNav.d.ts +0 -46
  143. package/dist/components/sidebarNav/SidebarNav.d.ts.map +0 -1
  144. package/dist/components/sidebarNav/SidebarNav.js +0 -102
  145. package/dist/components/sidebarNav/SidebarNav.js.map +0 -1
  146. package/dist/components/sidebarNav/SidebarNav.stories.d.ts +0 -61
  147. package/dist/components/sidebarNav/SidebarNav.stories.d.ts.map +0 -1
  148. package/dist/components/sidebarNav/SidebarNav.stories.js +0 -253
  149. package/dist/components/sidebarNav/SidebarNav.stories.js.map +0 -1
  150. package/dist/components/sidebarNav/SidebarNav.test.d.ts +0 -2
  151. package/dist/components/sidebarNav/SidebarNav.test.d.ts.map +0 -1
  152. package/dist/components/sidebarNav/SidebarNav.test.js +0 -240
  153. package/dist/components/sidebarNav/SidebarNav.test.js.map +0 -1
  154. package/dist/components/sidebarNav/SidebarNavContext.d.ts +0 -13
  155. package/dist/components/sidebarNav/SidebarNavContext.d.ts.map +0 -1
  156. package/dist/components/sidebarNav/SidebarNavContext.js +0 -15
  157. package/dist/components/sidebarNav/SidebarNavContext.js.map +0 -1
  158. package/dist/components/sidebarNav/SidebarNavGroup.d.ts +0 -10
  159. package/dist/components/sidebarNav/SidebarNavGroup.d.ts.map +0 -1
  160. package/dist/components/sidebarNav/SidebarNavGroup.js +0 -16
  161. package/dist/components/sidebarNav/SidebarNavGroup.js.map +0 -1
  162. package/dist/components/sidebarNav/SidebarNavItem.d.ts +0 -32
  163. package/dist/components/sidebarNav/SidebarNavItem.d.ts.map +0 -1
  164. package/dist/components/sidebarNav/SidebarNavItem.js +0 -44
  165. package/dist/components/sidebarNav/SidebarNavItem.js.map +0 -1
  166. package/dist/components/sidebarNav/SidebarNavItemFavourite.d.ts +0 -8
  167. package/dist/components/sidebarNav/SidebarNavItemFavourite.d.ts.map +0 -1
  168. package/dist/components/sidebarNav/SidebarNavItemFavourite.js +0 -14
  169. package/dist/components/sidebarNav/SidebarNavItemFavourite.js.map +0 -1
  170. package/dist/components/sidebarNav/SidebarNavPanel.d.ts +0 -4
  171. package/dist/components/sidebarNav/SidebarNavPanel.d.ts.map +0 -1
  172. package/dist/components/sidebarNav/SidebarNavPanel.js +0 -9
  173. package/dist/components/sidebarNav/SidebarNavPanel.js.map +0 -1
  174. package/dist/components/sidebarNav/SidebarNavPanelNav.d.ts +0 -10
  175. package/dist/components/sidebarNav/SidebarNavPanelNav.d.ts.map +0 -1
  176. package/dist/components/sidebarNav/SidebarNavPanelNav.js +0 -21
  177. package/dist/components/sidebarNav/SidebarNavPanelNav.js.map +0 -1
  178. package/dist/components/sidebarNav/SidebarNavRail.d.ts +0 -6
  179. package/dist/components/sidebarNav/SidebarNavRail.d.ts.map +0 -1
  180. package/dist/components/sidebarNav/SidebarNavRail.js +0 -7
  181. package/dist/components/sidebarNav/SidebarNavRail.js.map +0 -1
  182. package/dist/components/sidebarNav/SidebarNavRailItem.d.ts +0 -10
  183. package/dist/components/sidebarNav/SidebarNavRailItem.d.ts.map +0 -1
  184. package/dist/components/sidebarNav/SidebarNavRailItem.js +0 -25
  185. package/dist/components/sidebarNav/SidebarNavRailItem.js.map +0 -1
  186. package/dist/components/sidebarNav/SidebarNavRailList.d.ts +0 -4
  187. package/dist/components/sidebarNav/SidebarNavRailList.d.ts.map +0 -1
  188. package/dist/components/sidebarNav/SidebarNavRailList.js +0 -7
  189. package/dist/components/sidebarNav/SidebarNavRailList.js.map +0 -1
  190. package/dist/components/sidebarNav/SidebarNavRailSlot.d.ts +0 -6
  191. package/dist/components/sidebarNav/SidebarNavRailSlot.d.ts.map +0 -1
  192. package/dist/components/sidebarNav/SidebarNavRailSlot.js +0 -7
  193. package/dist/components/sidebarNav/SidebarNavRailSlot.js.map +0 -1
  194. package/dist/components/sidebarNav/SidebarNavSeparator.d.ts +0 -6
  195. package/dist/components/sidebarNav/SidebarNavSeparator.d.ts.map +0 -1
  196. package/dist/components/sidebarNav/SidebarNavSeparator.js +0 -8
  197. package/dist/components/sidebarNav/SidebarNavSeparator.js.map +0 -1
  198. package/dist/components/sidebarNav/SidebarNavTitle.d.ts +0 -4
  199. package/dist/components/sidebarNav/SidebarNavTitle.d.ts.map +0 -1
  200. package/dist/components/sidebarNav/SidebarNavTitle.js +0 -7
  201. package/dist/components/sidebarNav/SidebarNavTitle.js.map +0 -1
  202. package/dist/components/sidebarNav/SidebarNavTooltip.d.ts +0 -7
  203. package/dist/components/sidebarNav/SidebarNavTooltip.d.ts.map +0 -1
  204. package/dist/components/sidebarNav/SidebarNavTooltip.js +0 -9
  205. package/dist/components/sidebarNav/SidebarNavTooltip.js.map +0 -1
  206. package/dist/components/sidebarNav/SidebarNavTrigger.d.ts +0 -8
  207. package/dist/components/sidebarNav/SidebarNavTrigger.d.ts.map +0 -1
  208. package/dist/components/sidebarNav/SidebarNavTrigger.js +0 -15
  209. package/dist/components/sidebarNav/SidebarNavTrigger.js.map +0 -1
  210. package/dist/components/sidebarNav/index.d.ts +0 -4
  211. package/dist/components/sidebarNav/index.d.ts.map +0 -1
  212. package/dist/components/sidebarNav/index.js +0 -3
  213. package/dist/components/sidebarNav/index.js.map +0 -1
  214. package/dist/components/sidebarNav/resolvePanelItemProps.d.ts +0 -4
  215. package/dist/components/sidebarNav/resolvePanelItemProps.d.ts.map +0 -1
  216. package/dist/components/sidebarNav/resolvePanelItemProps.js +0 -43
  217. package/dist/components/sidebarNav/resolvePanelItemProps.js.map +0 -1
  218. package/dist/components/sidebarNav/resolvePanelItemProps.test.d.ts +0 -2
  219. package/dist/components/sidebarNav/resolvePanelItemProps.test.d.ts.map +0 -1
  220. package/dist/components/sidebarNav/resolvePanelItemProps.test.js +0 -52
  221. package/dist/components/sidebarNav/resolvePanelItemProps.test.js.map +0 -1
  222. package/dist/components/sidebarNav/types.d.ts +0 -100
  223. package/dist/components/sidebarNav/types.d.ts.map +0 -1
  224. package/dist/components/sidebarNav/types.js +0 -4
  225. package/dist/components/sidebarNav/types.js.map +0 -1
  226. package/dist/components/sidebarNav/useControllableBoolean.d.ts +0 -9
  227. package/dist/components/sidebarNav/useControllableBoolean.d.ts.map +0 -1
  228. package/dist/components/sidebarNav/useControllableBoolean.js +0 -15
  229. package/dist/components/sidebarNav/useControllableBoolean.js.map +0 -1
  230. package/dist/components/slideover/Slideover.stories.d.ts +0 -10
  231. package/dist/components/slideover/Slideover.stories.d.ts.map +0 -1
  232. package/dist/components/slideover/Slideover.stories.js +0 -12
  233. package/dist/components/slideover/Slideover.stories.js.map +0 -1
  234. package/dist/components/table/BulkActionsDropdown.d.ts +0 -12
  235. package/dist/components/table/BulkActionsDropdown.d.ts.map +0 -1
  236. package/dist/components/table/BulkActionsDropdown.js +0 -11
  237. package/dist/components/table/BulkActionsDropdown.js.map +0 -1
  238. package/dist/components/table/HideColumnsDropdown.d.ts +0 -9
  239. package/dist/components/table/HideColumnsDropdown.d.ts.map +0 -1
  240. package/dist/components/table/HideColumnsDropdown.js +0 -33
  241. package/dist/components/table/HideColumnsDropdown.js.map +0 -1
  242. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.d.ts +0 -8
  243. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.d.ts.map +0 -1
  244. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.js +0 -19
  245. package/dist/components/table/cellRenderers/SelectDropdownCellEditor.js.map +0 -1
  246. package/dist/components/table/hooks/useHistory.d.ts +0 -20
  247. package/dist/components/table/hooks/useHistory.d.ts.map +0 -1
  248. package/dist/components/table/hooks/useHistory.js +0 -73
  249. package/dist/components/table/hooks/useHistory.js.map +0 -1
  250. package/dist/components/table/hooks/useTableHistory.d.ts +0 -21
  251. package/dist/components/table/hooks/useTableHistory.d.ts.map +0 -1
  252. package/dist/components/table/hooks/useTableHistory.js +0 -88
  253. package/dist/components/table/hooks/useTableHistory.js.map +0 -1
  254. package/dist/components/table/pagination/TableSettingsDropdown.d.ts +0 -2
  255. package/dist/components/table/pagination/TableSettingsDropdown.d.ts.map +0 -1
  256. package/dist/components/table/pagination/TableSettingsDropdown.js +0 -42
  257. package/dist/components/table/pagination/TableSettingsDropdown.js.map +0 -1
  258. package/dist/components/table/tableHeader/TableHeader.d.ts +0 -14
  259. package/dist/components/table/tableHeader/TableHeader.d.ts.map +0 -1
  260. package/dist/components/table/tableHeader/TableHeader.js +0 -55
  261. package/dist/components/table/tableHeader/TableHeader.js.map +0 -1
  262. package/dist/components/table/tableHeader/index.d.ts +0 -2
  263. package/dist/components/table/tableHeader/index.d.ts.map +0 -1
  264. package/dist/components/table/tableHeader/index.js +0 -2
  265. package/dist/components/table/tableHeader/index.js.map +0 -1
  266. package/dist/components/table/tableTheme.d.ts +0 -2
  267. package/dist/components/table/tableTheme.d.ts.map +0 -1
  268. package/dist/components/table/tableTheme.js +0 -18
  269. package/dist/components/table/tableTheme.js.map +0 -1
  270. package/dist/components/table/types.d.ts +0 -169
  271. package/dist/components/table/types.d.ts.map +0 -1
  272. package/dist/components/table/types.js +0 -11
  273. package/dist/components/table/types.js.map +0 -1
  274. package/dist/components/table/utils/setAgGridLicenseKey.d.ts +0 -2
  275. package/dist/components/table/utils/setAgGridLicenseKey.d.ts.map +0 -1
  276. package/dist/components/table/utils/setAgGridLicenseKey.js +0 -11
  277. package/dist/components/table/utils/setAgGridLicenseKey.js.map +0 -1
  278. package/dist/layoutTest.stories.d.ts +0 -6
  279. package/dist/layoutTest.stories.d.ts.map +0 -1
  280. package/dist/layoutTest.stories.js +0 -9
  281. package/dist/layoutTest.stories.js.map +0 -1
  282. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.d.ts +0 -39
  283. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.d.ts.map +0 -1
  284. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.js +0 -112
  285. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.js.map +0 -1
  286. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.stories.d.ts +0 -184
  287. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.stories.d.ts.map +0 -1
  288. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.stories.js +0 -317
  289. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.stories.js.map +0 -1
  290. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.test.d.ts +0 -2
  291. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.test.d.ts.map +0 -1
  292. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.test.js +0 -160
  293. package/dist/pages/assessmentBuilderPage/AssessmentBuilderPage.test.js.map +0 -1
  294. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.d.ts +0 -11
  295. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.d.ts.map +0 -1
  296. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.js +0 -13
  297. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.js.map +0 -1
  298. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.stories.d.ts +0 -174
  299. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.stories.d.ts.map +0 -1
  300. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.stories.js +0 -212
  301. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.stories.js.map +0 -1
  302. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.test.d.ts +0 -2
  303. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.test.d.ts.map +0 -1
  304. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.test.js +0 -84
  305. package/dist/pages/assessmentSettingsPage/AssessmentSettingsPage.test.js.map +0 -1
  306. package/dist/pages/assessmentsPage/AssessmentsPage.d.ts +0 -16
  307. package/dist/pages/assessmentsPage/AssessmentsPage.d.ts.map +0 -1
  308. package/dist/pages/assessmentsPage/AssessmentsPage.js +0 -31
  309. package/dist/pages/assessmentsPage/AssessmentsPage.js.map +0 -1
  310. package/dist/pages/assessmentsPage/AssessmentsPage.stories.d.ts +0 -162
  311. package/dist/pages/assessmentsPage/AssessmentsPage.stories.d.ts.map +0 -1
  312. package/dist/pages/assessmentsPage/AssessmentsPage.stories.js +0 -290
  313. package/dist/pages/assessmentsPage/AssessmentsPage.stories.js.map +0 -1
  314. package/dist/pages/assessmentsPage/AssessmentsPage.test.d.ts +0 -2
  315. package/dist/pages/assessmentsPage/AssessmentsPage.test.d.ts.map +0 -1
  316. package/dist/pages/assessmentsPage/AssessmentsPage.test.js +0 -109
  317. package/dist/pages/assessmentsPage/AssessmentsPage.test.js.map +0 -1
  318. package/dist/pages/customReportWriterPage/CustomReportWriterPage.d.ts +0 -40
  319. package/dist/pages/customReportWriterPage/CustomReportWriterPage.d.ts.map +0 -1
  320. package/dist/pages/customReportWriterPage/CustomReportWriterPage.js +0 -156
  321. package/dist/pages/customReportWriterPage/CustomReportWriterPage.js.map +0 -1
  322. package/dist/pages/customReportWriterPage/CustomReportWriterPage.stories.d.ts +0 -239
  323. package/dist/pages/customReportWriterPage/CustomReportWriterPage.stories.d.ts.map +0 -1
  324. package/dist/pages/customReportWriterPage/CustomReportWriterPage.stories.js +0 -318
  325. package/dist/pages/customReportWriterPage/CustomReportWriterPage.stories.js.map +0 -1
  326. package/dist/pages/customReportWriterPage/CustomReportWriterPage.test.d.ts +0 -2
  327. package/dist/pages/customReportWriterPage/CustomReportWriterPage.test.d.ts.map +0 -1
  328. package/dist/pages/customReportWriterPage/CustomReportWriterPage.test.js +0 -183
  329. package/dist/pages/customReportWriterPage/CustomReportWriterPage.test.js.map +0 -1
  330. package/dist/pages/gradeSetsPage/GradeSetsPage.d.ts +0 -26
  331. package/dist/pages/gradeSetsPage/GradeSetsPage.d.ts.map +0 -1
  332. package/dist/pages/gradeSetsPage/GradeSetsPage.js +0 -33
  333. package/dist/pages/gradeSetsPage/GradeSetsPage.js.map +0 -1
  334. package/dist/pages/gradeSetsPage/GradeSetsPage.stories.d.ts +0 -176
  335. package/dist/pages/gradeSetsPage/GradeSetsPage.stories.d.ts.map +0 -1
  336. package/dist/pages/gradeSetsPage/GradeSetsPage.stories.js +0 -237
  337. package/dist/pages/gradeSetsPage/GradeSetsPage.stories.js.map +0 -1
  338. package/dist/pages/gradeSetsPage/GradeSetsPage.test.d.ts +0 -2
  339. package/dist/pages/gradeSetsPage/GradeSetsPage.test.d.ts.map +0 -1
  340. package/dist/pages/gradeSetsPage/GradeSetsPage.test.js +0 -91
  341. package/dist/pages/gradeSetsPage/GradeSetsPage.test.js.map +0 -1
  342. package/dist/pages/studentProfilePage/StudentProfilePage.d.ts +0 -48
  343. package/dist/pages/studentProfilePage/StudentProfilePage.d.ts.map +0 -1
  344. package/dist/pages/studentProfilePage/StudentProfilePage.js +0 -52
  345. package/dist/pages/studentProfilePage/StudentProfilePage.js.map +0 -1
  346. package/dist/pages/studentProfilePage/StudentProfilePage.stories.d.ts +0 -218
  347. package/dist/pages/studentProfilePage/StudentProfilePage.stories.d.ts.map +0 -1
  348. package/dist/pages/studentProfilePage/StudentProfilePage.stories.js +0 -273
  349. package/dist/pages/studentProfilePage/StudentProfilePage.stories.js.map +0 -1
  350. package/dist/pages/studentProfilePage/StudentProfilePage.test.d.ts +0 -2
  351. package/dist/pages/studentProfilePage/StudentProfilePage.test.d.ts.map +0 -1
  352. package/dist/pages/studentProfilePage/StudentProfilePage.test.js +0 -197
  353. package/dist/pages/studentProfilePage/StudentProfilePage.test.js.map +0 -1
  354. package/dist/utils/PopupParentContext.d.ts +0 -3
  355. package/dist/utils/PopupParentContext.d.ts.map +0 -1
  356. package/dist/utils/PopupParentContext.js +0 -6
  357. package/dist/utils/PopupParentContext.js.map +0 -1
  358. package/dist/utils/PopupParentContext.stories.d.ts +0 -17
  359. package/dist/utils/PopupParentContext.stories.d.ts.map +0 -1
  360. package/dist/utils/PopupParentContext.stories.js +0 -266
  361. package/dist/utils/PopupParentContext.stories.js.map +0 -1
  362. package/dist/utils/getDefaultPopupParent.d.ts +0 -2
  363. package/dist/utils/getDefaultPopupParent.d.ts.map +0 -1
  364. package/dist/utils/getDefaultPopupParent.js +0 -19
  365. package/dist/utils/getDefaultPopupParent.js.map +0 -1
@@ -12,6 +12,7 @@ import { fn } from 'storybook/test';
12
12
  import { useEffect, useRef, useState } from 'react';
13
13
  import { Button } from 'Components/button/Button';
14
14
  import { FileUpload } from './FileUpload.js';
15
+ import type { FileUploadLink } from './FileUpload.js';
15
16
  import type { FileUploadProps } from './FileUpload.js';
16
17
 
17
18
  // ─── Content strings ──────────────────────────────────────────────────────────
@@ -88,8 +89,12 @@ it is exported you can inline the shape or declare your own local type:
88
89
  type UploadedFile = {
89
90
  fileName: string;
90
91
  icon?: string; // any IconName from the design system Icon component
91
- fileType: string; // e.g. 'PDF', 'XLSX', 'DOCX'
92
- fileSize: string; // human-readable, e.g. '1.2 MB'
92
+ fileType?: string; // e.g. 'PDF', 'XLSX', 'DOCX'
93
+ fileSize?: string; // human-readable, e.g. '1.2 MB'
94
+ href?: string; // renders the row as a link; fileName becomes the link name
95
+ linkProps?: object; // extra anchor attributes (target/rel default to new tab, noopener)
96
+ renderLink?: (args: WidgetRenderLinkArgs) => ReactElement; // e.g. router links
97
+ onEdit?: (link: { url: string; name: string }) => void; // link rows only
93
98
  onDownload?: () => void;
94
99
  onDelete: () => void;
95
100
  progressPercentage?: number; // 0–100; hidden at exactly 100
@@ -103,7 +108,7 @@ const RELATED_COMPONENTS = `
103
108
 
104
109
  - [Progress](?path=/docs/components-progress--docs) — the progress bar used
105
110
  inside each file row to indicate upload completion.
106
- - [Button](?path=/docs/components-button--docs) — the "click to upload",
111
+ - [Button](?path=/docs/components-button--docs) — the "Browse files",
107
112
  download, and delete buttons inside the component.
108
113
  - [Card](?path=/docs/components-card--docs) — the visual wrapper for both the
109
114
  dropzone area and the file list.
@@ -174,13 +179,13 @@ const meta = {
174
179
  dropzoneContent: {
175
180
  description:
176
181
  'Custom content rendered inside the dropzone area. Defaults to a '
177
- + '"Drag and drop, or click to upload" paragraph. Pass a `ReactNode` to '
182
+ + '"Drag and drop, add a link or" paragraph with a "Browse files" button. Pass a `ReactNode` to '
178
183
  + 'replace this with your own copy or layout.',
179
184
  control: false,
180
185
  table: {
181
186
  type: { summary: 'ReactNode' },
182
187
  defaultValue: {
183
- summary: '<p>Drag and drop, or <Button variant="text-link">click to upload</Button></p>',
188
+ summary: '<p>Drag and drop, add a link or</p> + <Button variant="secondary">Browse files</Button>',
184
189
  },
185
190
  },
186
191
  },
@@ -325,6 +330,69 @@ export default UploadWithFilesExample;
325
330
 
326
331
  // ─────────────────────────────────────────────────────────────────────────────
327
332
 
333
+ export const WithLinks: Story = withDescription(
334
+ {
335
+ render: () => {
336
+ const [links, setLinks] = useState<FileUploadLink[]>([
337
+ { name: 'Pastoral policy', url: 'https://example.com/pastoral-policy' },
338
+ ]);
339
+ return (
340
+ <div style={{ maxWidth: '560px', padding: 'var(--spacing-large)' }}>
341
+ <FileUpload
342
+ uploadFiles={noop}
343
+ onAddLink={link => setLinks(previous => [...previous, link])}
344
+ uploadedFiles={[
345
+ {
346
+ fileName: 'spring-term-report-2024.pdf',
347
+ fileType: 'PDF',
348
+ fileSize: '1.2 MB',
349
+ onDownload: noop,
350
+ onDelete: noop,
351
+ },
352
+ ...links.map((link, index) => ({
353
+ fileName: link.name,
354
+ href: link.url,
355
+ onEdit: (updated: FileUploadLink) =>
356
+ setLinks(previous => previous.map((item, i) => (i === index ? updated : item))),
357
+ onDelete: () => setLinks(previous => previous.filter((_, i) => i !== index)),
358
+ })),
359
+ ]}
360
+ />
361
+ </div>
362
+ );
363
+ },
364
+ parameters: {
365
+ controls: { disable: true },
366
+ docs: {
367
+ source: {
368
+ language: 'tsx',
369
+ code: `
370
+ <FileUpload
371
+ uploadFiles={(files) => { /* start upload */ }}
372
+ onAddLink={({ url, name }) => { /* save the new link */ }}
373
+ uploadedFiles={[
374
+ { fileName: 'report.pdf', fileType: 'PDF', fileSize: '1.2 MB', onDelete: () => {} },
375
+ {
376
+ fileName: 'Pastoral policy',
377
+ href: 'https://example.com/pastoral-policy',
378
+ onEdit: ({ url, name }) => { /* save the edited link */ },
379
+ onDelete: () => {},
380
+ },
381
+ ]}
382
+ />
383
+ `.trim(),
384
+ },
385
+ },
386
+ },
387
+ },
388
+ 'A mixed list of file rows and link rows. Passing `onAddLink` turns "add a link" into '
389
+ + 'a trigger for a modal form (name and URL). A row with `href` renders as a link that '
390
+ + 'opens in a new tab, with copy and delete actions; adding `onEdit` shows an edit action '
391
+ + 'that opens the same modal pre-filled.',
392
+ );
393
+
394
+ // ─────────────────────────────────────────────────────────────────────────────
395
+
328
396
  export const SingleFile: Story = withDescription(
329
397
  {
330
398
  render: () => (
@@ -721,7 +789,7 @@ export default CustomDropzoneExample;
721
789
  },
722
790
  },
723
791
  'Pass a `ReactNode` to `dropzoneContent` to replace the default '
724
- + '"Drag and drop, or click to upload" copy. This is useful when you need '
792
+ + 'default "Drag and drop" copy. This is useful when you need '
725
793
  + 'context-specific instructions or a branded call-to-action button.',
726
794
  );
727
795
 
@@ -15,7 +15,13 @@ describe('FileUpload', () => {
15
15
  describe('dropzone', () => {
16
16
  test('renders default dropzone content', () => {
17
17
  render(<FileUpload uploadedFiles={[]} uploadFiles={vi.fn()} />);
18
- expect(screen.getByText('click to upload')).toBeInTheDocument();
18
+ expect(screen.getByRole('button', { name: 'Browse files' })).toBeInTheDocument();
19
+ expect(screen.getByText('Drag and drop or')).toBeInTheDocument();
20
+ });
21
+
22
+ test('does not render the add a link trigger without onAddLink', () => {
23
+ render(<FileUpload uploadedFiles={[]} uploadFiles={vi.fn()} />);
24
+ expect(screen.queryByRole('button', { name: 'add a link' })).not.toBeInTheDocument();
19
25
  });
20
26
 
21
27
  test('renders custom dropzoneContent', () => {
@@ -169,4 +175,136 @@ describe('FileUpload', () => {
169
175
  expect(container.firstChild).toHaveClass('ds-file-upload', 'my-custom-class');
170
176
  });
171
177
  });
178
+ describe('link rows', () => {
179
+ const linkRow = {
180
+ fileName: 'Pastoral policy',
181
+ href: 'https://example.com/policy',
182
+ onDelete: vi.fn(),
183
+ };
184
+
185
+ test('renders a link row as an anchor opening in a new tab', () => {
186
+ render(<FileUpload uploadedFiles={[linkRow]} uploadFiles={vi.fn()} />);
187
+ const anchor = screen.getByRole('link', { name: 'Pastoral policy' });
188
+ expect(anchor).toHaveAttribute('href', 'https://example.com/policy');
189
+ expect(anchor).toHaveAttribute('target', '_blank');
190
+ expect(anchor).toHaveAttribute('rel', 'noopener noreferrer');
191
+ expect(screen.getByText('https://example.com/policy')).toBeInTheDocument();
192
+ });
193
+
194
+ test('merges linkProps onto the anchor', () => {
195
+ render(
196
+ <FileUpload
197
+ uploadedFiles={[{ ...linkRow, linkProps: { target: '_self' } }]}
198
+ uploadFiles={vi.fn()}
199
+ />,
200
+ );
201
+ expect(screen.getByRole('link', { name: 'Pastoral policy' })).toHaveAttribute('target', '_self');
202
+ });
203
+
204
+ test('uses renderLink when provided', () => {
205
+ render(
206
+ <FileUpload
207
+ uploadedFiles={[{
208
+ ...linkRow,
209
+ renderLink: ({ href, className, children }) => (
210
+ <a href={href} className={className} data-custom="true">{children}</a>
211
+ ),
212
+ }]}
213
+ uploadFiles={vi.fn()}
214
+ />,
215
+ );
216
+ expect(screen.getByRole('link', { name: 'Pastoral policy' })).toHaveAttribute('data-custom', 'true');
217
+ });
218
+
219
+ test('does not render a link for rows without href', () => {
220
+ render(<FileUpload uploadedFiles={[baseFile]} uploadFiles={vi.fn()} />);
221
+ expect(screen.queryByRole('link')).not.toBeInTheDocument();
222
+ expect(screen.queryByRole('button', { name: 'Edit link' })).not.toBeInTheDocument();
223
+ });
224
+
225
+ test('calls onDelete without triggering other actions', async () => {
226
+ const onDelete = vi.fn();
227
+ const onEdit = vi.fn();
228
+ render(
229
+ <FileUpload uploadedFiles={[{ ...linkRow, onDelete, onEdit }]} uploadFiles={vi.fn()} />,
230
+ );
231
+ await userEvent.click(screen.getByRole('button', { name: 'Delete link' }));
232
+ expect(onDelete).toHaveBeenCalledOnce();
233
+ expect(onEdit).not.toHaveBeenCalled();
234
+ });
235
+
236
+ test('copies the link to the clipboard', async () => {
237
+ const user = userEvent.setup();
238
+ render(<FileUpload uploadedFiles={[linkRow]} uploadFiles={vi.fn()} />);
239
+ await user.click(screen.getByRole('button', { name: 'Copy link' }));
240
+ expect(await navigator.clipboard.readText()).toBe('https://example.com/policy');
241
+ });
242
+ });
243
+
244
+ describe('adding a link', () => {
245
+ test('opens the form from the add a link trigger and submits trimmed values', async () => {
246
+ const onAddLink = vi.fn();
247
+ const uploadFiles = vi.fn();
248
+ render(<FileUpload uploadedFiles={[]} uploadFiles={uploadFiles} onAddLink={onAddLink} />);
249
+
250
+ await userEvent.click(screen.getByRole('button', { name: 'add a link' }));
251
+ expect(screen.getByRole('dialog', { name: 'Add URL attachment' })).toBeInTheDocument();
252
+ await userEvent.type(screen.getByLabelText('Name of link'), ' Policy ');
253
+ await userEvent.type(screen.getByLabelText('Paste your link URL here'), ' https://example.com ');
254
+ await userEvent.click(screen.getByRole('button', { name: 'Add' }));
255
+
256
+ expect(onAddLink).toHaveBeenCalledWith({ name: 'Policy', url: 'https://example.com' });
257
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
258
+ });
259
+
260
+ test('shows validation errors and does not submit invalid values', async () => {
261
+ const onAddLink = vi.fn();
262
+ render(<FileUpload uploadedFiles={[]} uploadFiles={vi.fn()} onAddLink={onAddLink} />);
263
+
264
+ await userEvent.click(screen.getByRole('button', { name: 'add a link' }));
265
+ await userEvent.click(screen.getByRole('button', { name: 'Add' }));
266
+ expect(screen.getByText('Name is required.')).toBeInTheDocument();
267
+ expect(screen.getByText('URL is required.')).toBeInTheDocument();
268
+
269
+ await userEvent.type(screen.getByLabelText('Name of link'), 'Policy');
270
+ await userEvent.type(screen.getByLabelText('Paste your link URL here'), 'ftp://example.com');
271
+ await userEvent.click(screen.getByRole('button', { name: 'Add' }));
272
+ expect(screen.getByText('Enter a valid URL starting with http:// or https://.')).toBeInTheDocument();
273
+ expect(onAddLink).not.toHaveBeenCalled();
274
+ });
275
+
276
+ test('closes the form on cancel', async () => {
277
+ render(<FileUpload uploadedFiles={[]} uploadFiles={vi.fn()} onAddLink={vi.fn()} />);
278
+ await userEvent.click(screen.getByRole('button', { name: 'add a link' }));
279
+ await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
280
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
281
+ });
282
+ });
283
+
284
+ describe('editing a link', () => {
285
+ test('opens a pre-filled form and calls onEdit with the updated link', async () => {
286
+ const onEdit = vi.fn();
287
+ render(
288
+ <FileUpload
289
+ uploadFiles={vi.fn()}
290
+ uploadedFiles={[{
291
+ fileName: 'Policy',
292
+ href: 'https://example.com',
293
+ onEdit,
294
+ onDelete: vi.fn(),
295
+ }]}
296
+ />,
297
+ );
298
+
299
+ await userEvent.click(screen.getByRole('button', { name: 'Edit link' }));
300
+ expect(screen.getByLabelText('Name of link')).toHaveValue('Policy');
301
+ expect(screen.getByLabelText('Paste your link URL here')).toHaveValue('https://example.com');
302
+
303
+ await userEvent.clear(screen.getByLabelText('Name of link'));
304
+ await userEvent.type(screen.getByLabelText('Name of link'), 'Updated');
305
+ await userEvent.click(screen.getByRole('button', { name: 'Update' }));
306
+
307
+ expect(onEdit).toHaveBeenCalledWith({ name: 'Updated', url: 'https://example.com' });
308
+ });
309
+ });
172
310
  });
@@ -4,15 +4,25 @@ import { Card } from 'Components/card/Card';
4
4
  import type { IconName } from 'Components/icon/allowedIcons';
5
5
  import { Icon } from 'Components/icon/Icon';
6
6
  import { Separator } from 'Components/separator/Separator';
7
- import type { ReactNode } from 'react';
7
+ import { useState, type AnchorHTMLAttributes, type ReactElement, type ReactNode } from 'react';
8
8
  import Dropzone from 'react-dropzone';
9
9
  import { Progress } from 'Components/progress/Progress';
10
+ import type { WidgetRenderLinkArgs } from 'Components/widget/types';
11
+ import { FileUploadLinkModal, type FileUploadLink } from './FileUploadLinkModal.js';
12
+
13
+ export type { FileUploadLink };
14
+
15
+ type LinkAnchorProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'className' | 'children'>;
10
16
 
11
17
  type UploadedFile = {
12
18
  fileName: string;
13
19
  icon?: IconName;
14
- fileType: string;
15
- fileSize: string;
20
+ fileType?: string;
21
+ fileSize?: string;
22
+ href?: string;
23
+ linkProps?: LinkAnchorProps;
24
+ renderLink?: (args: WidgetRenderLinkArgs) => ReactElement;
25
+ onEdit?: (link: FileUploadLink) => void;
16
26
  onDownload?: () => void;
17
27
  onDelete: () => void;
18
28
  progressPercentage?: number;
@@ -24,6 +34,21 @@ export type FileUploadProps = {
24
34
  uploadedFiles: UploadedFile[];
25
35
  uploadFiles: (files: File[]) => void;
26
36
  dropzoneContent?: ReactNode;
37
+ onAddLink?: (link: FileUploadLink) => void;
38
+ };
39
+
40
+ type LinkPopupState = { mode: 'add' } | { mode: 'edit'; index: number } | null;
41
+
42
+ const renderFileLink = ({ href = '', linkProps, renderLink }: UploadedFile, children: ReactNode) => {
43
+ const className = 'ds-file-upload__file-link';
44
+ if (renderLink) {
45
+ return renderLink({ href, className, children, linkProps });
46
+ }
47
+ return (
48
+ <a href={href} target="_blank" rel="noopener noreferrer" className={className} {...linkProps}>
49
+ {children}
50
+ </a>
51
+ );
27
52
  };
28
53
 
29
54
  export const FileUpload = (props: FileUploadProps) => {
@@ -31,14 +56,39 @@ export const FileUpload = (props: FileUploadProps) => {
31
56
  className,
32
57
  uploadedFiles,
33
58
  uploadFiles,
34
- dropzoneContent = (
59
+ onAddLink,
60
+ } = props;
61
+
62
+ const [linkPopup, setLinkPopup] = useState<LinkPopupState>(null);
63
+
64
+ const dropzoneContent = props.dropzoneContent ?? (
65
+ <>
35
66
  <p>
36
- Drag and drop, or
37
- {' '}
38
- <Button variant="text-link">click to upload</Button>
67
+ {onAddLink
68
+ ? (
69
+ <>
70
+ Drag and drop,
71
+ {' '}
72
+ <Button
73
+ variant="text-link"
74
+ onClick={(event) => {
75
+ event.stopPropagation();
76
+ setLinkPopup({ mode: 'add' });
77
+ }}
78
+ >
79
+ add a link
80
+ </Button>
81
+ {' '}
82
+ or
83
+ </>
84
+ )
85
+ : 'Drag and drop or'}
39
86
  </p>
40
- ),
41
- } = props;
87
+ <Button variant="secondary">Browse files</Button>
88
+ </>
89
+ );
90
+
91
+ const editedFile = linkPopup?.mode === 'edit' ? uploadedFiles[linkPopup.index] : undefined;
42
92
 
43
93
  return (
44
94
  <div
@@ -58,7 +108,7 @@ export const FileUpload = (props: FileUploadProps) => {
58
108
  {...getRootProps()}
59
109
  className="ds-file-upload__dropzone-input"
60
110
  >
61
- <input {...getInputProps()} />
111
+ <input {...getInputProps({ 'aria-label': 'Upload files' })} />
62
112
  <div className="ds-file-upload__dropzone-content">
63
113
  <span className="ds-file-upload__icon-wrapper">
64
114
  <Icon
@@ -75,19 +125,45 @@ export const FileUpload = (props: FileUploadProps) => {
75
125
  </Card>
76
126
  )}
77
127
  </Dropzone>
128
+ {linkPopup?.mode === 'add' && onAddLink && (
129
+ <FileUploadLinkModal
130
+ title="Add URL attachment"
131
+ submitLabel="Add"
132
+ onSubmit={(link) => {
133
+ onAddLink(link);
134
+ setLinkPopup(null);
135
+ }}
136
+ onCancel={() => setLinkPopup(null)}
137
+ />
138
+ )}
139
+ {editedFile?.href && editedFile.onEdit && (
140
+ <FileUploadLinkModal
141
+ title="Update URL attachment"
142
+ submitLabel="Update"
143
+ initialLink={{ url: editedFile.href, name: editedFile.fileName }}
144
+ onSubmit={(link) => {
145
+ editedFile.onEdit?.(link);
146
+ setLinkPopup(null);
147
+ }}
148
+ onCancel={() => setLinkPopup(null)}
149
+ />
150
+ )}
78
151
  {uploadedFiles.length > 0 && (
79
152
  <Card className="ds-file-upload__file-list">
80
153
  {uploadedFiles.map((file, index) => {
81
154
  const {
82
155
  fileName,
83
- icon = 'file',
84
156
  fileSize,
85
157
  fileType,
158
+ href,
159
+ onEdit,
86
160
  onDelete,
87
161
  onDownload,
88
162
  hasUploadFailed,
89
163
  progressPercentage,
90
164
  } = file;
165
+ const icon = file.icon ?? (href ? 'link' : 'file');
166
+ const nameNode = <span className="ds-file-upload__file-name">{fileName}</span>;
91
167
  return (
92
168
  <div key={fileName} className="ds-file-upload__file-outer">
93
169
  <span className="ds-file-upload__file">
@@ -95,7 +171,7 @@ export const FileUpload = (props: FileUploadProps) => {
95
171
  <Icon size={24} name={icon} color="var(--color-brand-600)" />
96
172
  </span>
97
173
  <span className="ds-file-upload__file-details">
98
- <span className="ds-file-upload__file-name">{fileName}</span>
174
+ {href ? renderFileLink(file, nameNode) : nameNode}
99
175
  {
100
176
  hasUploadFailed
101
177
  ? (
@@ -105,9 +181,27 @@ export const FileUpload = (props: FileUploadProps) => {
105
181
  Upload failed
106
182
  </span>
107
183
  )
108
- : <span className="ds-file-upload__file-meta">{`${fileType}, ${fileSize}`}</span>
184
+ : <span className="ds-file-upload__file-meta">{href ?? `${fileType}, ${fileSize}`}</span>
109
185
  }
110
186
  </span>
187
+ {href && (
188
+ <Button
189
+ variant="secondary"
190
+ isRound
191
+ onClick={() => navigator.clipboard.writeText(href)}
192
+ >
193
+ <Icon name="copy" screenReaderText="Copy link" />
194
+ </Button>
195
+ )}
196
+ {href && onEdit && (
197
+ <Button
198
+ variant="secondary"
199
+ isRound
200
+ onClick={() => setLinkPopup({ mode: 'edit', index })}
201
+ >
202
+ <Icon name="pencil" screenReaderText="Edit link" />
203
+ </Button>
204
+ )}
111
205
  {onDownload && (
112
206
  <Button
113
207
  variant="secondary"
@@ -123,7 +217,7 @@ export const FileUpload = (props: FileUploadProps) => {
123
217
  isRound
124
218
  onClick={onDelete}
125
219
  >
126
- <Icon name="trash" screenReaderText="Delete file" />
220
+ <Icon name="trash" screenReaderText={href ? 'Delete link' : 'Delete file'} />
127
221
  </Button>
128
222
  )}
129
223
  </span>
@@ -132,6 +226,7 @@ export const FileUpload = (props: FileUploadProps) => {
132
226
  <Progress
133
227
  value={progressPercentage}
134
228
  className="ds-file-upload__progress-bar"
229
+ aria-label="Upload progress bar"
135
230
  indicatorClassName={classNames('ds-file-upload__progress-bar-indicator', {
136
231
  'ds-file-upload__progress-bar-indicator--failed': hasUploadFailed,
137
232
  })}
@@ -0,0 +1,109 @@
1
+ import { Button } from 'Components/button/Button';
2
+ import { FormField } from 'Components/formField/FormField';
3
+ import { Modal } from 'Components/modal/Modal';
4
+ import { useId, useState, type FormEvent } from 'react';
5
+
6
+ export type FileUploadLink = {
7
+ url: string;
8
+ name: string;
9
+ };
10
+
11
+ type FileUploadLinkModalProps = {
12
+ title: string;
13
+ submitLabel: string;
14
+ initialLink?: FileUploadLink;
15
+ onSubmit: (link: FileUploadLink) => void;
16
+ onCancel: () => void;
17
+ };
18
+
19
+ type LinkErrors = Partial<Record<keyof FileUploadLink, string>>;
20
+
21
+ const isHttpUrl = (value: string) => {
22
+ try {
23
+ const { protocol } = new URL(value);
24
+ return protocol === 'http:' || protocol === 'https:';
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ };
30
+
31
+ const validate = ({ url, name }: FileUploadLink): LinkErrors => {
32
+ const errors: LinkErrors = {};
33
+ if (!name) {
34
+ errors.name = 'Name is required.';
35
+ }
36
+ if (!url) {
37
+ errors.url = 'URL is required.';
38
+ }
39
+ else if (!isHttpUrl(url)) {
40
+ errors.url = 'Enter a valid URL starting with http:// or https://.';
41
+ }
42
+ return errors;
43
+ };
44
+
45
+ export const FileUploadLinkModal = (props: FileUploadLinkModalProps) => {
46
+ const { title, submitLabel, initialLink, onSubmit, onCancel } = props;
47
+ const formId = useId();
48
+ const nameId = useId();
49
+ const urlId = useId();
50
+ const [url, setUrl] = useState(initialLink?.url ?? '');
51
+ const [name, setName] = useState(initialLink?.name ?? '');
52
+ const [errors, setErrors] = useState<LinkErrors>({});
53
+
54
+ const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
55
+ event.preventDefault();
56
+ const link = { url: url.trim(), name: name.trim() };
57
+ const validationErrors = validate(link);
58
+ setErrors(validationErrors);
59
+ if (Object.keys(validationErrors).length === 0) {
60
+ onSubmit(link);
61
+ }
62
+ };
63
+
64
+ return (
65
+ <Modal open title={title} closeHandler={onCancel}>
66
+ <Modal.Body>
67
+ <form
68
+ id={formId}
69
+ className="ds-file-upload__link-form"
70
+ onSubmit={handleSubmit}
71
+ noValidate
72
+ >
73
+ <FormField
74
+ inputType="text"
75
+ id={nameId}
76
+ label="Name of link"
77
+ errorText={errors.name}
78
+ inputProps={{
79
+ value: name,
80
+ onChange: (event) => {
81
+ setName(event.target.value);
82
+ setErrors(previous => ({ ...previous, name: undefined }));
83
+ },
84
+ }}
85
+ />
86
+ <FormField
87
+ inputType="text"
88
+ id={urlId}
89
+ label="Paste your link URL here"
90
+ errorText={errors.url}
91
+ inputProps={{
92
+ type: 'url',
93
+ value: url,
94
+ placeholder: 'https://',
95
+ onChange: (event) => {
96
+ setUrl(event.target.value);
97
+ setErrors(previous => ({ ...previous, url: undefined }));
98
+ },
99
+ }}
100
+ />
101
+ </form>
102
+ </Modal.Body>
103
+ <Modal.Footer>
104
+ <Button type="button" variant="secondary" onClick={onCancel}>Cancel</Button>
105
+ <Button type="submit" form={formId}>{submitLabel}</Button>
106
+ </Modal.Footer>
107
+ </Modal>
108
+ );
109
+ };
@@ -12,6 +12,7 @@
12
12
 
13
13
  &__file-details {
14
14
  flex-grow: 1;
15
+ min-width: 0;
15
16
  display: flex;
16
17
  flex-direction: column;
17
18
  }
@@ -22,10 +23,32 @@
22
23
  }
23
24
 
24
25
  &__file-meta {
25
- color: var(--color-grey-500);
26
+ color: var(--color-grey-600);
26
27
  font-size: var(--font-size-2-13);
28
+ overflow: hidden;
29
+ text-overflow: ellipsis;
30
+ white-space: nowrap;
27
31
  }
28
32
 
33
+ &__file-link {
34
+ color: inherit;
35
+ text-decoration: none;
36
+ overflow: hidden;
37
+ text-overflow: ellipsis;
38
+ white-space: nowrap;
39
+
40
+ &:hover .ds-file-upload__file-name {
41
+ text-decoration: underline;
42
+ }
43
+ }
44
+
45
+ &__link-form {
46
+ display: flex;
47
+ flex-direction: column;
48
+ gap: var(--spacing-medium);
49
+ }
50
+
51
+
29
52
  &__icon-wrapper {
30
53
  background-color: var(--color-brand-050);
31
54
  border-radius: var(--border-radius-small);