@navikt/ds-react 6.2.0 → 6.3.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 (271) hide show
  1. package/cjs/form/combobox/ComboboxProvider.js +5 -1
  2. package/cjs/form/combobox/ComboboxProvider.js.map +1 -1
  3. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +14 -12
  4. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  5. package/cjs/form/combobox/FilteredOptions/filtered-options-util.d.ts +3 -3
  6. package/cjs/form/combobox/FilteredOptions/filtered-options-util.js +1 -3
  7. package/cjs/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
  8. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.d.ts +8 -5
  9. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +8 -13
  10. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  11. package/cjs/form/combobox/Input/Input.js +9 -7
  12. package/cjs/form/combobox/Input/Input.js.map +1 -1
  13. package/cjs/form/combobox/SelectedOptions/SelectedOptions.d.ts +2 -1
  14. package/cjs/form/combobox/SelectedOptions/SelectedOptions.js +3 -3
  15. package/cjs/form/combobox/SelectedOptions/SelectedOptions.js.map +1 -1
  16. package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.d.ts +10 -7
  17. package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js +6 -8
  18. package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
  19. package/cjs/form/combobox/combobox-utils.d.ts +10 -0
  20. package/cjs/form/combobox/combobox-utils.js +27 -0
  21. package/cjs/form/combobox/combobox-utils.js.map +1 -0
  22. package/cjs/form/combobox/customOptionsContext.d.ts +5 -4
  23. package/cjs/form/combobox/customOptionsContext.js +1 -1
  24. package/cjs/form/combobox/customOptionsContext.js.map +1 -1
  25. package/cjs/form/combobox/types.d.ts +22 -11
  26. package/cjs/form/file-upload/FileUpload.context.d.ts +8 -0
  27. package/cjs/form/file-upload/FileUpload.context.js +7 -0
  28. package/cjs/form/file-upload/FileUpload.context.js.map +1 -0
  29. package/cjs/form/file-upload/FileUpload.d.ts +118 -0
  30. package/cjs/form/file-upload/FileUpload.js +73 -0
  31. package/cjs/form/file-upload/FileUpload.js.map +1 -0
  32. package/cjs/form/file-upload/FileUpload.types.d.ts +55 -0
  33. package/cjs/form/file-upload/FileUpload.types.js +8 -0
  34. package/cjs/form/file-upload/FileUpload.types.js.map +1 -0
  35. package/cjs/form/file-upload/i18n/get.d.ts +2 -0
  36. package/cjs/form/file-upload/i18n/get.js +38 -0
  37. package/cjs/form/file-upload/i18n/get.js.map +1 -0
  38. package/cjs/form/file-upload/i18n/i18n.context.d.ts +11 -0
  39. package/cjs/form/file-upload/i18n/i18n.context.js +39 -0
  40. package/cjs/form/file-upload/i18n/i18n.context.js.map +1 -0
  41. package/cjs/form/file-upload/i18n/i18n.types.d.ts +13 -0
  42. package/cjs/form/file-upload/i18n/i18n.types.js +3 -0
  43. package/cjs/form/file-upload/i18n/i18n.types.js.map +1 -0
  44. package/cjs/form/file-upload/i18n/locales/nb.json +20 -0
  45. package/cjs/form/file-upload/i18n/merge.d.ts +2 -0
  46. package/cjs/form/file-upload/i18n/merge.js +29 -0
  47. package/cjs/form/file-upload/i18n/merge.js.map +1 -0
  48. package/cjs/form/file-upload/index.d.ts +7 -0
  49. package/cjs/form/file-upload/index.js +16 -0
  50. package/cjs/form/file-upload/index.js.map +1 -0
  51. package/cjs/form/file-upload/parts/Trigger.d.ts +7 -0
  52. package/cjs/form/file-upload/parts/Trigger.js +43 -0
  53. package/cjs/form/file-upload/parts/Trigger.js.map +1 -0
  54. package/cjs/form/file-upload/parts/dropzone/Dropzone.d.ts +4 -0
  55. package/cjs/form/file-upload/parts/dropzone/Dropzone.js +106 -0
  56. package/cjs/form/file-upload/parts/dropzone/Dropzone.js.map +1 -0
  57. package/cjs/form/file-upload/parts/dropzone/dropzone.types.d.ts +18 -0
  58. package/cjs/form/file-upload/parts/dropzone/dropzone.types.js +3 -0
  59. package/cjs/form/file-upload/parts/dropzone/dropzone.types.js.map +1 -0
  60. package/cjs/form/file-upload/parts/dropzone/useDropzone.d.ts +13 -0
  61. package/cjs/form/file-upload/parts/dropzone/useDropzone.js +34 -0
  62. package/cjs/form/file-upload/parts/dropzone/useDropzone.js.map +1 -0
  63. package/cjs/form/file-upload/parts/item/Item.d.ts +55 -0
  64. package/cjs/form/file-upload/parts/item/Item.js +79 -0
  65. package/cjs/form/file-upload/parts/item/Item.js.map +1 -0
  66. package/cjs/form/file-upload/parts/item/Item.types.d.ts +5 -0
  67. package/cjs/form/file-upload/parts/item/Item.types.js +3 -0
  68. package/cjs/form/file-upload/parts/item/Item.types.js.map +1 -0
  69. package/cjs/form/file-upload/parts/item/ItemButton.d.ts +12 -0
  70. package/cjs/form/file-upload/parts/item/ItemButton.js +22 -0
  71. package/cjs/form/file-upload/parts/item/ItemButton.js.map +1 -0
  72. package/cjs/form/file-upload/parts/item/ItemIcon.d.ts +9 -0
  73. package/cjs/form/file-upload/parts/item/ItemIcon.js +51 -0
  74. package/cjs/form/file-upload/parts/item/ItemIcon.js.map +1 -0
  75. package/cjs/form/file-upload/parts/item/ItemName.d.ts +9 -0
  76. package/cjs/form/file-upload/parts/item/ItemName.js +32 -0
  77. package/cjs/form/file-upload/parts/item/ItemName.js.map +1 -0
  78. package/cjs/form/file-upload/parts/item/utils/download-file.d.ts +1 -0
  79. package/cjs/form/file-upload/parts/item/utils/download-file.js +13 -0
  80. package/cjs/form/file-upload/parts/item/utils/download-file.js.map +1 -0
  81. package/cjs/form/file-upload/parts/item/utils/file-type-checker.d.ts +2 -0
  82. package/cjs/form/file-upload/parts/item/utils/file-type-checker.js +6 -0
  83. package/cjs/form/file-upload/parts/item/utils/file-type-checker.js.map +1 -0
  84. package/cjs/form/file-upload/parts/item/utils/format-file-size.d.ts +2 -0
  85. package/cjs/form/file-upload/parts/item/utils/format-file-size.js +24 -0
  86. package/cjs/form/file-upload/parts/item/utils/format-file-size.js.map +1 -0
  87. package/cjs/form/file-upload/useFileUpload.d.ts +12 -0
  88. package/cjs/form/file-upload/useFileUpload.js +33 -0
  89. package/cjs/form/file-upload/useFileUpload.js.map +1 -0
  90. package/cjs/form/file-upload/utils/is-accepted-file-type.d.ts +1 -0
  91. package/cjs/form/file-upload/utils/is-accepted-file-type.js +26 -0
  92. package/cjs/form/file-upload/utils/is-accepted-file-type.js.map +1 -0
  93. package/cjs/form/file-upload/utils/is-accepted-size.d.ts +1 -0
  94. package/cjs/form/file-upload/utils/is-accepted-size.js +11 -0
  95. package/cjs/form/file-upload/utils/is-accepted-size.js.map +1 -0
  96. package/cjs/form/file-upload/utils/validate-files.d.ts +8 -0
  97. package/cjs/form/file-upload/utils/validate-files.js +48 -0
  98. package/cjs/form/file-upload/utils/validate-files.js.map +1 -0
  99. package/cjs/index.d.ts +1 -0
  100. package/cjs/index.js +3 -1
  101. package/cjs/index.js.map +1 -1
  102. package/cjs/loader/Loader.d.ts +0 -7
  103. package/cjs/loader/Loader.js.map +1 -1
  104. package/cjs/table/DataCell.d.ts +1 -3
  105. package/cjs/table/DataCell.js.map +1 -1
  106. package/cjs/util/create-context.d.ts +1 -0
  107. package/cjs/util/create-context.js.map +1 -1
  108. package/cjs/util/hooks/useEventListener.d.ts +1 -1
  109. package/cjs/util/hooks/useMergeRefs.d.ts +1 -1
  110. package/esm/form/combobox/ComboboxProvider.js +5 -1
  111. package/esm/form/combobox/ComboboxProvider.js.map +1 -1
  112. package/esm/form/combobox/FilteredOptions/FilteredOptions.js +14 -12
  113. package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  114. package/esm/form/combobox/FilteredOptions/filtered-options-util.d.ts +3 -3
  115. package/esm/form/combobox/FilteredOptions/filtered-options-util.js +1 -3
  116. package/esm/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
  117. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.d.ts +8 -5
  118. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +8 -13
  119. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  120. package/esm/form/combobox/Input/Input.js +9 -7
  121. package/esm/form/combobox/Input/Input.js.map +1 -1
  122. package/esm/form/combobox/SelectedOptions/SelectedOptions.d.ts +2 -1
  123. package/esm/form/combobox/SelectedOptions/SelectedOptions.js +3 -3
  124. package/esm/form/combobox/SelectedOptions/SelectedOptions.js.map +1 -1
  125. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.d.ts +10 -7
  126. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js +6 -8
  127. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
  128. package/esm/form/combobox/combobox-utils.d.ts +10 -0
  129. package/esm/form/combobox/combobox-utils.js +22 -0
  130. package/esm/form/combobox/combobox-utils.js.map +1 -0
  131. package/esm/form/combobox/customOptionsContext.d.ts +5 -4
  132. package/esm/form/combobox/customOptionsContext.js +1 -1
  133. package/esm/form/combobox/customOptionsContext.js.map +1 -1
  134. package/esm/form/combobox/types.d.ts +22 -11
  135. package/esm/form/file-upload/FileUpload.context.d.ts +8 -0
  136. package/esm/form/file-upload/FileUpload.context.js +3 -0
  137. package/esm/form/file-upload/FileUpload.context.js.map +1 -0
  138. package/esm/form/file-upload/FileUpload.d.ts +118 -0
  139. package/esm/form/file-upload/FileUpload.js +44 -0
  140. package/esm/form/file-upload/FileUpload.js.map +1 -0
  141. package/esm/form/file-upload/FileUpload.types.d.ts +55 -0
  142. package/esm/form/file-upload/FileUpload.types.js +5 -0
  143. package/esm/form/file-upload/FileUpload.types.js.map +1 -0
  144. package/esm/form/file-upload/i18n/get.d.ts +2 -0
  145. package/esm/form/file-upload/i18n/get.js +34 -0
  146. package/esm/form/file-upload/i18n/get.js.map +1 -0
  147. package/esm/form/file-upload/i18n/i18n.context.d.ts +11 -0
  148. package/esm/form/file-upload/i18n/i18n.context.js +32 -0
  149. package/esm/form/file-upload/i18n/i18n.context.js.map +1 -0
  150. package/esm/form/file-upload/i18n/i18n.types.d.ts +13 -0
  151. package/esm/form/file-upload/i18n/i18n.types.js +2 -0
  152. package/esm/form/file-upload/i18n/i18n.types.js.map +1 -0
  153. package/esm/form/file-upload/i18n/locales/nb.json +20 -0
  154. package/esm/form/file-upload/i18n/merge.d.ts +2 -0
  155. package/esm/form/file-upload/i18n/merge.js +25 -0
  156. package/esm/form/file-upload/i18n/merge.js.map +1 -0
  157. package/esm/form/file-upload/index.d.ts +7 -0
  158. package/esm/form/file-upload/index.js +6 -0
  159. package/esm/form/file-upload/index.js.map +1 -0
  160. package/esm/form/file-upload/parts/Trigger.d.ts +7 -0
  161. package/esm/form/file-upload/parts/Trigger.js +18 -0
  162. package/esm/form/file-upload/parts/Trigger.js.map +1 -0
  163. package/esm/form/file-upload/parts/dropzone/Dropzone.d.ts +4 -0
  164. package/esm/form/file-upload/parts/dropzone/Dropzone.js +78 -0
  165. package/esm/form/file-upload/parts/dropzone/Dropzone.js.map +1 -0
  166. package/esm/form/file-upload/parts/dropzone/dropzone.types.d.ts +18 -0
  167. package/esm/form/file-upload/parts/dropzone/dropzone.types.js +2 -0
  168. package/esm/form/file-upload/parts/dropzone/dropzone.types.js.map +1 -0
  169. package/esm/form/file-upload/parts/dropzone/useDropzone.d.ts +13 -0
  170. package/esm/form/file-upload/parts/dropzone/useDropzone.js +30 -0
  171. package/esm/form/file-upload/parts/dropzone/useDropzone.js.map +1 -0
  172. package/esm/form/file-upload/parts/item/Item.d.ts +55 -0
  173. package/esm/form/file-upload/parts/item/Item.js +50 -0
  174. package/esm/form/file-upload/parts/item/Item.js.map +1 -0
  175. package/esm/form/file-upload/parts/item/Item.types.d.ts +5 -0
  176. package/esm/form/file-upload/parts/item/Item.types.js +2 -0
  177. package/esm/form/file-upload/parts/item/Item.types.js.map +1 -0
  178. package/esm/form/file-upload/parts/item/ItemButton.d.ts +12 -0
  179. package/esm/form/file-upload/parts/item/ItemButton.js +17 -0
  180. package/esm/form/file-upload/parts/item/ItemButton.js.map +1 -0
  181. package/esm/form/file-upload/parts/item/ItemIcon.d.ts +9 -0
  182. package/esm/form/file-upload/parts/item/ItemIcon.js +46 -0
  183. package/esm/form/file-upload/parts/item/ItemIcon.js.map +1 -0
  184. package/esm/form/file-upload/parts/item/ItemName.d.ts +9 -0
  185. package/esm/form/file-upload/parts/item/ItemName.js +27 -0
  186. package/esm/form/file-upload/parts/item/ItemName.js.map +1 -0
  187. package/esm/form/file-upload/parts/item/utils/download-file.d.ts +1 -0
  188. package/esm/form/file-upload/parts/item/utils/download-file.js +9 -0
  189. package/esm/form/file-upload/parts/item/utils/download-file.js.map +1 -0
  190. package/esm/form/file-upload/parts/item/utils/file-type-checker.d.ts +2 -0
  191. package/esm/form/file-upload/parts/item/utils/file-type-checker.js +2 -0
  192. package/esm/form/file-upload/parts/item/utils/file-type-checker.js.map +1 -0
  193. package/esm/form/file-upload/parts/item/utils/format-file-size.d.ts +2 -0
  194. package/esm/form/file-upload/parts/item/utils/format-file-size.js +20 -0
  195. package/esm/form/file-upload/parts/item/utils/format-file-size.js.map +1 -0
  196. package/esm/form/file-upload/useFileUpload.d.ts +12 -0
  197. package/esm/form/file-upload/useFileUpload.js +29 -0
  198. package/esm/form/file-upload/useFileUpload.js.map +1 -0
  199. package/esm/form/file-upload/utils/is-accepted-file-type.d.ts +1 -0
  200. package/esm/form/file-upload/utils/is-accepted-file-type.js +22 -0
  201. package/esm/form/file-upload/utils/is-accepted-file-type.js.map +1 -0
  202. package/esm/form/file-upload/utils/is-accepted-size.d.ts +1 -0
  203. package/esm/form/file-upload/utils/is-accepted-size.js +7 -0
  204. package/esm/form/file-upload/utils/is-accepted-size.js.map +1 -0
  205. package/esm/form/file-upload/utils/validate-files.d.ts +8 -0
  206. package/esm/form/file-upload/utils/validate-files.js +44 -0
  207. package/esm/form/file-upload/utils/validate-files.js.map +1 -0
  208. package/esm/index.d.ts +1 -0
  209. package/esm/index.js +1 -0
  210. package/esm/index.js.map +1 -1
  211. package/esm/loader/Loader.d.ts +0 -7
  212. package/esm/loader/Loader.js.map +1 -1
  213. package/esm/table/DataCell.d.ts +1 -3
  214. package/esm/table/DataCell.js.map +1 -1
  215. package/esm/util/create-context.d.ts +1 -0
  216. package/esm/util/create-context.js.map +1 -1
  217. package/esm/util/hooks/useEventListener.d.ts +1 -1
  218. package/esm/util/hooks/useMergeRefs.d.ts +1 -1
  219. package/package.json +13 -3
  220. package/src/form/combobox/ComboboxProvider.tsx +7 -3
  221. package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +22 -15
  222. package/src/form/combobox/FilteredOptions/filtered-options-util.ts +5 -10
  223. package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +19 -29
  224. package/src/form/combobox/Input/Input.tsx +14 -8
  225. package/src/form/combobox/SelectedOptions/SelectedOptions.tsx +8 -5
  226. package/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx +24 -25
  227. package/src/form/combobox/combobox-utils.test.ts +67 -0
  228. package/src/form/combobox/combobox-utils.ts +32 -0
  229. package/src/form/combobox/combobox.stories.tsx +67 -32
  230. package/src/form/combobox/combobox.test.tsx +32 -1
  231. package/src/form/combobox/customOptionsContext.tsx +9 -8
  232. package/src/form/combobox/types.ts +23 -11
  233. package/src/form/file-upload/FileUpload.context.tsx +9 -0
  234. package/src/form/file-upload/FileUpload.tsx +142 -0
  235. package/src/form/file-upload/FileUpload.types.ts +57 -0
  236. package/src/form/file-upload/file-upload-dropzone.stories.tsx +123 -0
  237. package/src/form/file-upload/file-upload-item.stories.tsx +136 -0
  238. package/src/form/file-upload/file-upload.stories.tsx +236 -0
  239. package/src/form/file-upload/i18n/get.ts +48 -0
  240. package/src/form/file-upload/i18n/i18n.context.test.tsx +92 -0
  241. package/src/form/file-upload/i18n/i18n.context.ts +67 -0
  242. package/src/form/file-upload/i18n/i18n.types.ts +20 -0
  243. package/src/form/file-upload/i18n/locales/nb.json +20 -0
  244. package/src/form/file-upload/i18n/merge.ts +35 -0
  245. package/src/form/file-upload/index.ts +21 -0
  246. package/src/form/file-upload/parts/Trigger.tsx +48 -0
  247. package/src/form/file-upload/parts/dropzone/Dropzone.tsx +181 -0
  248. package/src/form/file-upload/parts/dropzone/dropzone.types.ts +22 -0
  249. package/src/form/file-upload/parts/dropzone/useDropzone.ts +43 -0
  250. package/src/form/file-upload/parts/item/Item.tsx +165 -0
  251. package/src/form/file-upload/parts/item/Item.types.ts +6 -0
  252. package/src/form/file-upload/parts/item/ItemButton.tsx +52 -0
  253. package/src/form/file-upload/parts/item/ItemIcon.tsx +74 -0
  254. package/src/form/file-upload/parts/item/ItemName.tsx +58 -0
  255. package/src/form/file-upload/parts/item/utils/download-file.ts +9 -0
  256. package/src/form/file-upload/parts/item/utils/file-type-checker.ts +4 -0
  257. package/src/form/file-upload/parts/item/utils/format-file-size.test.ts +76 -0
  258. package/src/form/file-upload/parts/item/utils/format-file-size.ts +25 -0
  259. package/src/form/file-upload/useFileUpload.ts +54 -0
  260. package/src/form/file-upload/utils/is-accepted-file-type.test.ts +69 -0
  261. package/src/form/file-upload/utils/is-accepted-file-type.ts +25 -0
  262. package/src/form/file-upload/utils/is-accepted-size.test.ts +26 -0
  263. package/src/form/file-upload/utils/is-accepted-size.ts +7 -0
  264. package/src/form/file-upload/utils/validate-files.test.ts +132 -0
  265. package/src/form/file-upload/utils/validate-files.ts +62 -0
  266. package/src/index.ts +14 -0
  267. package/src/internal-header/header.stories.tsx +8 -5
  268. package/src/loader/Loader.tsx +0 -7
  269. package/src/table/DataCell.tsx +1 -6
  270. package/src/util/create-context.tsx +1 -0
  271. package/src/util/hooks/useMergeRefs.ts +1 -1
@@ -0,0 +1,9 @@
1
+ import { createContext } from "../../util/create-context";
2
+ import { ComponentTranslation } from "./i18n/i18n.types";
3
+
4
+ export type FileUploadLocaleContextProps = {
5
+ translations?: ComponentTranslation<"FileUpload">;
6
+ };
7
+
8
+ export const [FileUploadLocaleContextProvider, useFileUploadTranslation] =
9
+ createContext<FileUploadLocaleContextProps>();
@@ -0,0 +1,142 @@
1
+ import cl from "clsx";
2
+ import React, { HTMLAttributes, forwardRef } from "react";
3
+ import { FileUploadLocaleContextProvider } from "./FileUpload.context";
4
+ import { ComponentTranslation } from "./i18n/i18n.types";
5
+ import Trigger from "./parts/Trigger";
6
+ import Dropzone from "./parts/dropzone/Dropzone";
7
+ import Item from "./parts/item/Item";
8
+
9
+ interface FileUploadProps extends HTMLAttributes<HTMLDivElement> {
10
+ children: React.ReactNode;
11
+ /**
12
+ * i18n-API for customizing texts and labels
13
+ */
14
+ translations?: ComponentTranslation<"FileUpload">;
15
+ }
16
+
17
+ interface FileUploadComponent
18
+ extends React.ForwardRefExoticComponent<
19
+ FileUploadProps & React.RefAttributes<HTMLDivElement>
20
+ > {
21
+ /**
22
+ * Framed area to drag-n-drop files, upload files with button-click or copy-paste.
23
+ * @example
24
+ * Single file
25
+ * ```jsx
26
+ * <FileUpload.Dropzone
27
+ * label="Last opp fil"
28
+ * multiple={false}
29
+ * onSelect={onSelect}
30
+ * />
31
+ * ```
32
+ *
33
+ * @example
34
+ * Multiple files
35
+ * ```jsx
36
+ * <FileUpload.Dropzone
37
+ * label="Last opp fil"
38
+ * multiple={true}
39
+ * onSelect={onSelect}
40
+ * />
41
+ * ```
42
+ *
43
+ * @example
44
+ * Error
45
+ * ```jsx
46
+ * <FileUpload.Dropzone
47
+ * label="Last opp filer"
48
+ * onSelect={onSelect}
49
+ * error={error}
50
+ * />
51
+ * ```
52
+ */
53
+ Dropzone: typeof Dropzone;
54
+
55
+ /**
56
+ * Displays a file with status, file size, action and error message.
57
+ * @example
58
+ * Single
59
+ * ```jsx
60
+ * <FileUpload.Item file={file} status="uploading" />
61
+ * ```
62
+ *
63
+ * @example
64
+ * Multiple items can be semantically grouped as a list.
65
+ * ```jsx
66
+ * <FileUpload>
67
+ * <VStack gap="4" as="ul">
68
+ * <FileUpload.Item as="li" file={file} />
69
+ * <FileUpload.Item as="li" file={file2} />
70
+ * <FileUpload.Item as="li" file={file3} status="uploading" />
71
+ * </VStack>
72
+ * </FileUpload>
73
+ * ```
74
+ *
75
+ * @example
76
+ * Custom file object
77
+ * ```jsx
78
+ * <FileUpload.Item file={{ name: "fileName.pdf", size: 1_048_576 }} />
79
+ * ```
80
+ *
81
+ * @example
82
+ * Error
83
+ * ```jsx
84
+ * <FileUpload.Item file={file} error="Something went wrong" />
85
+ * ```
86
+ *
87
+ * @example
88
+ * Status & actions
89
+ * ```jsx
90
+ * <FileUpload.Item file={file} status="uploading" />
91
+ * <FileUpload.Item file={file} status="downloading" />
92
+ * <FileUpload.Item file={file} itemAction="retry" onRetry={...} />
93
+ * <FileUpload.Item file={file} itemAction="delete" onDelete={...} />
94
+ * ```
95
+ */
96
+ Item: typeof Item;
97
+ /**
98
+ * Wrapper for a button to trigger file select.
99
+ * @example
100
+ * ```jsx
101
+ * <FileUpload.Trigger onSelect={...}>
102
+ * <Button variant="secondary">Last opp filer</Button>
103
+ * </FileUpload.Trigger>
104
+ * ```
105
+ */
106
+ Trigger: typeof Trigger;
107
+ }
108
+
109
+ /**
110
+ * A set of components used to upload and display files.
111
+ * @see [📝 Documentation](https://aksel.nav.no/komponenter/core/fileupload)
112
+ * @example Dropzone
113
+ * ```jsx
114
+ * <FileUpload.Dropzone />
115
+ * ```
116
+ *
117
+ * @example
118
+ * Items
119
+ * ```jsx
120
+ * <VStack gap="4" as="ul">
121
+ * <FileUpload.Item as="li" file={myFile} />
122
+ * <FileUpload.Item as="li" file={mySecondFile} />
123
+ * </VStack>
124
+ * ```
125
+ */
126
+ export const FileUpload = forwardRef<HTMLDivElement, FileUploadProps>(
127
+ ({ children, className, translations, ...rest }: FileUploadProps, ref) => {
128
+ return (
129
+ <FileUploadLocaleContextProvider translations={translations}>
130
+ <div ref={ref} {...rest} className={cl("navds-file-upload", className)}>
131
+ {children}
132
+ </div>
133
+ </FileUploadLocaleContextProvider>
134
+ );
135
+ },
136
+ ) as FileUploadComponent;
137
+
138
+ FileUpload.Dropzone = Dropzone;
139
+ FileUpload.Item = Item;
140
+ FileUpload.Trigger = Trigger;
141
+
142
+ export default FileUpload;
@@ -0,0 +1,57 @@
1
+ export const fileRejectionReason = {
2
+ FileType: "fileType" as const,
3
+ FileSize: "fileSize" as const,
4
+ };
5
+
6
+ export type FileRejectionReason =
7
+ (typeof fileRejectionReason)[keyof typeof fileRejectionReason];
8
+
9
+ export type FileRejected = {
10
+ file: File;
11
+ error: true;
12
+ reasons: string[];
13
+ };
14
+ export type FileAccepted = { file: File; error: false };
15
+
16
+ export type FileObject = FileRejected | FileAccepted;
17
+ export type FileRejectedPartitioned = {
18
+ file: File;
19
+ reasons: string[];
20
+ };
21
+ export type FilesPartitioned = {
22
+ accepted: File[];
23
+ rejected: FileRejectedPartitioned[];
24
+ };
25
+
26
+ export interface FileUploadBaseProps {
27
+ /**
28
+ * Indicates if it is possible to select multiple files at once.
29
+ * @default true
30
+ */
31
+ multiple?: boolean;
32
+ /**
33
+ * Indicates which file types to accept.
34
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept
35
+ */
36
+ accept?: string;
37
+ /**
38
+ * Maximum size of a file to accept
39
+ */
40
+ maxSizeInBytes?: number;
41
+ /**
42
+ * Custom validator that is used to decide if a file is accepted or rejected.
43
+ * @return true if the file is accepted, otherwise a string with the reason for rejection
44
+ */
45
+ validator?: (file: File) => true | string;
46
+ /**
47
+ * Callback triggered on file select
48
+ */
49
+ onSelect: (files: FileObject[], partitionedFiles: FilesPartitioned) => void;
50
+ /**
51
+ * Disables the dropzone when current >= max, unless `disabled` prop is set to `false`.
52
+ */
53
+ fileLimit?: {
54
+ max: number;
55
+ current: number;
56
+ };
57
+ }
@@ -0,0 +1,123 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import { fireEvent, within } from "@storybook/test";
3
+ import React from "react";
4
+ import { ImageIcon } from "@navikt/aksel-icons";
5
+ import {
6
+ type FileObject,
7
+ UNSAFE_FileUpload as FileUpload,
8
+ type FilesPartitioned,
9
+ } from ".";
10
+
11
+ const meta: Meta<typeof FileUpload.Dropzone> = {
12
+ title: "ds-react/FileUpload/Dropzone",
13
+ component: FileUpload.Dropzone,
14
+ decorators: [
15
+ (Story) => (
16
+ <div style={{ width: 500, maxWidth: "100%" }}>
17
+ <Story />
18
+ </div>
19
+ ),
20
+ ],
21
+ };
22
+
23
+ export default meta;
24
+
25
+ const onSelect = (
26
+ allFiles: FileObject[],
27
+ { accepted, rejected }: FilesPartitioned,
28
+ ) => {
29
+ alert(
30
+ `Lastet opp ${allFiles.length} filer. Accepted: ${accepted.length}. Rejected: ${rejected.length}`,
31
+ );
32
+ };
33
+
34
+ export const Default: StoryObj<typeof FileUpload.Dropzone> = {
35
+ render: (props) => <FileUpload.Dropzone {...props} onSelect={console.log} />,
36
+ args: {
37
+ label: "Last opp filer",
38
+ description: "",
39
+ error: "",
40
+ multiple: true,
41
+ accept: "",
42
+ maxSizeInBytes: 0,
43
+ fileLimit: { max: 2, current: 1 },
44
+ },
45
+ argTypes: {
46
+ disabled: { control: { type: "boolean" } },
47
+ },
48
+ parameters: { chromatic: { disable: true } },
49
+ };
50
+
51
+ export const States: StoryObj = {
52
+ render: () => (
53
+ <div>
54
+ <h2>Disabled</h2>
55
+ <FileUpload.Dropzone
56
+ label="Disabled prop"
57
+ onSelect={console.log}
58
+ disabled
59
+ />
60
+ <FileUpload.Dropzone
61
+ label="FileLimit disabled"
62
+ onSelect={console.log}
63
+ fileLimit={{ max: 1, current: 2 }}
64
+ />
65
+
66
+ <h2>Error</h2>
67
+ <FileUpload.Dropzone
68
+ label="Last opp filer"
69
+ onSelect={onSelect}
70
+ error="Du må laste opp en fil"
71
+ description="Bruk filtype DOC, PPT eller PDF. Maks filstørrelse 10 MB."
72
+ />
73
+
74
+ <h2>Dragging</h2>
75
+ <FileUpload.Dropzone
76
+ label="Drag over test"
77
+ multiple={false}
78
+ onSelect={onSelect}
79
+ />
80
+ </div>
81
+ ),
82
+ };
83
+ States.play = async ({ canvasElement }) => {
84
+ const canvas = within(canvasElement);
85
+ const button = canvas.getByText("Velg fil");
86
+ fireEvent.dragEnter(button);
87
+ };
88
+
89
+ export const Translation: StoryObj = {
90
+ render: () => (
91
+ <div>
92
+ <h2>Single file</h2>
93
+ <FileUpload.Dropzone
94
+ label="Last opp fil"
95
+ multiple={false}
96
+ onSelect={onSelect}
97
+ />
98
+
99
+ <h2>Custom texts</h2>
100
+ <FileUpload.Dropzone
101
+ translations={{
102
+ dragAndDropMultiple: "Dra og slipp bilder i format .png",
103
+ buttonMultiple: "Velg bilder",
104
+ disabled: "Du kan ikke laste opp flere bilder",
105
+ }}
106
+ label="Last opp bilder"
107
+ onSelect={console.log}
108
+ icon={ImageIcon}
109
+ />
110
+
111
+ <h3>Disabled</h3>
112
+ <FileUpload.Dropzone
113
+ translations={{
114
+ disabled: "Du kan ikke laste opp flere bilder",
115
+ }}
116
+ label="Last opp bilder"
117
+ onSelect={console.log}
118
+ icon={ImageIcon}
119
+ disabled
120
+ />
121
+ </div>
122
+ ),
123
+ };
@@ -0,0 +1,136 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import React from "react";
3
+ import { FileItem, UNSAFE_FileUpload as FileUpload } from ".";
4
+ import { VStack } from "../../layout/stack";
5
+
6
+ const meta: Meta<typeof FileUpload.Item> = {
7
+ title: "ds-react/FileUpload/Item",
8
+ component: FileUpload.Item,
9
+ decorators: [
10
+ (Story) => (
11
+ <div style={{ width: 400, maxWidth: "100%" }}>
12
+ <Story />
13
+ </div>
14
+ ),
15
+ ],
16
+ };
17
+
18
+ export default meta;
19
+
20
+ const onDelete = (file: FileItem) => alert(`Delete ${file.name}`);
21
+ const onRetry = (file: FileItem) => alert(`Retry ${file.name}`);
22
+ const fileTxt = new File(["abc".repeat(10000)], "file.txt");
23
+ const filePng = new File(["abc".repeat(10000)], "file.png");
24
+ const filePdf = new File(["abc".repeat(100000)], "file.pdf");
25
+ const fileXlsx = new File(["abc"], "file.xlsx");
26
+ const fileCsv = new File(["abc"], "file.csv");
27
+ const filePptx = new File(["abc"], "file.pptx");
28
+ const fileWebp = new File(["abc"], "file.webp");
29
+ const fileDocx = new File(["abc"], "file.docx");
30
+
31
+ export const Icons: StoryObj<typeof FileUpload.Item> = {
32
+ render: () => (
33
+ <FileUpload>
34
+ <VStack gap="5" as="ul">
35
+ <FileUpload.Item file={fileTxt} as="li" />
36
+ <FileUpload.Item file={filePng} as="li" />
37
+ <FileUpload.Item file={fileWebp} as="li" />
38
+ <FileUpload.Item file={filePdf} as="li" />
39
+ <FileUpload.Item file={fileDocx} as="li" />
40
+ <FileUpload.Item file={fileDocx} as="li" status="uploading" />
41
+ <FileUpload.Item file={fileDocx} as="li" status="downloading" />
42
+ <FileUpload.Item file={fileXlsx} as="li" />
43
+ <FileUpload.Item file={fileCsv} as="li" />
44
+ <FileUpload.Item file={filePptx} as="li" />
45
+ </VStack>
46
+ </FileUpload>
47
+ ),
48
+ };
49
+
50
+ export const States: StoryObj<typeof FileUpload.Item> = {
51
+ render: () => (
52
+ <div>
53
+ <h2>Error</h2>
54
+ <FileUpload.Item file={fileTxt} error="Plain error" />
55
+ <h3>error + status</h3>
56
+ <FileUpload.Item
57
+ file={fileTxt}
58
+ error="Error og uploading"
59
+ status="uploading"
60
+ onRetry={() => onRetry(fileTxt)}
61
+ onDelete={() => onDelete(fileTxt)}
62
+ />
63
+ <h2>Item Actions</h2>
64
+ <h3>status + delete</h3>
65
+ <FileUpload.Item
66
+ file={fileDocx}
67
+ onDelete={() => onDelete(fileDocx)}
68
+ onRetry={() => onRetry(fileDocx)}
69
+ status="uploading"
70
+ itemAction="delete"
71
+ />
72
+ <h3>status + retry</h3>
73
+ <FileUpload.Item
74
+ file={fileDocx}
75
+ onDelete={() => onDelete(fileDocx)}
76
+ onRetry={() => onRetry(fileDocx)}
77
+ status="downloading"
78
+ itemAction="retry"
79
+ />
80
+ <h3>retry</h3>
81
+ <FileUpload.Item
82
+ file={fileCsv}
83
+ onRetry={() => onRetry(fileCsv)}
84
+ itemAction="retry"
85
+ />
86
+ <h3>delete</h3>
87
+ <FileUpload.Item
88
+ file={filePptx}
89
+ onDelete={() => onDelete(filePptx)}
90
+ itemAction="delete"
91
+ />
92
+ <h3>retry + error</h3>
93
+ <FileUpload.Item
94
+ file={fileCsv}
95
+ error="Error og onRetry"
96
+ onRetry={() => onRetry(fileCsv)}
97
+ itemAction="retry"
98
+ />
99
+ <h3>delete + error</h3>
100
+ <FileUpload.Item
101
+ file={filePptx}
102
+ error="Error og onDelete"
103
+ onDelete={() => onDelete(filePptx)}
104
+ itemAction="delete"
105
+ />
106
+ </div>
107
+ ),
108
+ };
109
+
110
+ export const Download: StoryObj = {
111
+ render: () => (
112
+ <VStack gap="5">
113
+ <FileUpload.Item
114
+ file={{
115
+ name: "with onClick.txt",
116
+ size: 1_048_576,
117
+ }}
118
+ onFileClick={() => alert("onFileClick")}
119
+ />
120
+ <FileUpload.Item
121
+ file={{
122
+ name: "with href.txt",
123
+ size: 1,
124
+ }}
125
+ href="https://www.nav.no"
126
+ />
127
+ <FileUpload.Item
128
+ file={{
129
+ name: "without href/onFileClick.txt",
130
+ size: 2_000_000,
131
+ }}
132
+ />
133
+ <FileUpload.Item file={fileTxt} />
134
+ </VStack>
135
+ ),
136
+ };
@@ -0,0 +1,236 @@
1
+ import { Meta, StoryFn, StoryObj } from "@storybook/react";
2
+ import React, { useEffect, useState } from "react";
3
+ import { UploadIcon } from "@navikt/aksel-icons";
4
+ import { UNSAFE_FileUpload as FileUpload, FileUploadItemProps } from ".";
5
+ import { Alert } from "../../alert";
6
+ import { Button } from "../../button";
7
+ import { VStack } from "../../layout/stack";
8
+ import { Heading } from "../../typography";
9
+ import {
10
+ FileObject,
11
+ FileRejected,
12
+ FileRejectionReason,
13
+ } from "./FileUpload.types";
14
+
15
+ const meta: Meta<typeof FileUpload.Dropzone> = {
16
+ title: "ds-react/FileUpload",
17
+ component: FileUpload.Dropzone,
18
+ };
19
+
20
+ export default meta;
21
+
22
+ const MAX_FILES = 3;
23
+ const MAX_SIZE_MB = 1;
24
+ const MAX_SIZE = MAX_SIZE_MB * 1024 * 1024;
25
+
26
+ const CustomItem = ({
27
+ index,
28
+ ...props
29
+ }: FileUploadItemProps & {
30
+ index: number;
31
+ onDelete: (event: React.MouseEvent<HTMLButtonElement>) => void;
32
+ }) => {
33
+ const [loading, setLoading] = useState(true);
34
+
35
+ useEffect(() => {
36
+ setTimeout(
37
+ () => {
38
+ setLoading(false);
39
+ },
40
+ 1700 * index + 1,
41
+ );
42
+ }, [index]);
43
+
44
+ return (
45
+ <FileUpload.Item
46
+ {...props}
47
+ status={loading ? "uploading" : "idle"}
48
+ itemAction="delete"
49
+ as="li"
50
+ />
51
+ );
52
+ };
53
+
54
+ export const Default: StoryFn = () => {
55
+ const [files, setFiles] = useState<FileObject[]>([]);
56
+
57
+ function addFiles(filesToAdd: FileObject[]) {
58
+ setFiles([...files, ...filesToAdd]);
59
+ }
60
+
61
+ function removeFile(fileToRemove: FileObject) {
62
+ setFiles(files.filter((file) => file !== fileToRemove));
63
+ }
64
+
65
+ const acceptedFiles = files.filter((file) => !file.error);
66
+ const rejectedFiles = files.filter((f): f is FileRejected => f.error);
67
+
68
+ return (
69
+ <FileUpload style={{ width: 500, maxWidth: "100%", margin: "0 auto" }}>
70
+ <VStack gap="6">
71
+ <FileUpload.Dropzone
72
+ label="Last opp filer til søknaden"
73
+ description={`Maks størrelse ${MAX_SIZE_MB} MB`}
74
+ accept=".doc,.docx,.xls,.xlsx,.pdf"
75
+ maxSizeInBytes={MAX_SIZE}
76
+ fileLimit={{ max: MAX_FILES, current: acceptedFiles.length }}
77
+ onSelect={addFiles}
78
+ />
79
+
80
+ {getListError(acceptedFiles) && (
81
+ <Alert variant="error">{getListError(acceptedFiles)}</Alert>
82
+ )}
83
+
84
+ {acceptedFiles.length > 0 && (
85
+ <VStack gap="2">
86
+ <Heading level="3" size="xsmall">
87
+ {`Vedlegg (${acceptedFiles.length} av maks ${MAX_FILES})`}
88
+ </Heading>
89
+ <VStack as="ul" gap="3">
90
+ {acceptedFiles.map((file, index) => (
91
+ <CustomItem
92
+ key={index}
93
+ index={index}
94
+ file={file.file}
95
+ onDelete={() => removeFile(file)}
96
+ />
97
+ ))}
98
+ </VStack>
99
+ </VStack>
100
+ )}
101
+ {rejectedFiles.length > 0 && (
102
+ <VStack gap="2">
103
+ <Heading level="3" size="xsmall">
104
+ Vedlegg med feil
105
+ </Heading>
106
+ <VStack as="ul" gap="3">
107
+ {rejectedFiles.map((rejected, index) => (
108
+ <CustomItem
109
+ key={index}
110
+ index={index}
111
+ file={rejected.file}
112
+ error={errors[rejected.reasons[0]]}
113
+ onDelete={() => removeFile(rejected)}
114
+ />
115
+ ))}
116
+ </VStack>
117
+ </VStack>
118
+ )}
119
+ </VStack>
120
+ </FileUpload>
121
+ );
122
+ };
123
+ Default.parameters = {
124
+ chromatic: { disable: true },
125
+ layout: "padded",
126
+ };
127
+
128
+ const errors: Record<FileRejectionReason, string> = {
129
+ fileType: "Filformatet støttes ikke",
130
+ fileSize: `Filen er større enn ${MAX_SIZE_MB} MB`,
131
+ };
132
+
133
+ function getListError(acceptedFiles: FileObject[]) {
134
+ const filesTooMany = acceptedFiles.length - MAX_FILES;
135
+ if (filesTooMany === 1)
136
+ return "Du har lagt ved en fil for mye, vennligst fjern en fil";
137
+ if (filesTooMany > 1)
138
+ return `Du har lagt ved ${filesTooMany} filer for mye, vennligst fjern ${filesTooMany} filer`;
139
+ }
140
+
141
+ export const Single: StoryFn = () => {
142
+ const [files, setFiles] = useState<FileObject[]>([]);
143
+
144
+ function addFiles(filesToAdd: FileObject[]) {
145
+ setFiles(filesToAdd);
146
+ }
147
+
148
+ function removeFile() {
149
+ setFiles([]);
150
+ }
151
+
152
+ return (
153
+ <VStack gap="6" style={{ width: 500, maxWidth: "100%" }}>
154
+ <FileUpload.Dropzone
155
+ label="Last opp fil til søknaden"
156
+ description={`Maks størrelse ${MAX_SIZE_MB} MB`}
157
+ accept=".doc,.docx,.xls,.xlsx,.pdf"
158
+ maxSizeInBytes={MAX_SIZE}
159
+ fileLimit={{ max: 1, current: files.length }}
160
+ multiple={false}
161
+ onSelect={addFiles}
162
+ />
163
+ {files.map((file) => (
164
+ <FileUpload.Item
165
+ key={file.file.name}
166
+ file={file.file}
167
+ error={file.error ? errors[file.reasons[0]] : undefined}
168
+ onDelete={removeFile}
169
+ />
170
+ ))}
171
+ </VStack>
172
+ );
173
+ };
174
+ Single.parameters = { chromatic: { disable: true } };
175
+
176
+ export const Translation = () => (
177
+ <FileUpload
178
+ translations={{
179
+ dropzone: {
180
+ dragAndDropMultiple: "Dra og slipp bilder i format .png",
181
+ buttonMultiple: "Velg bilder",
182
+ or: "eventuelt",
183
+ disabled: "Du kan ikke laste opp flere bilder",
184
+ },
185
+ item: {
186
+ deleteButtonTitle: "Slett bilde",
187
+ downloading: "Laster bilde...",
188
+ uploading: "Laster opp bilde...",
189
+ retryButtonTitle: "Last opp bilde på nytt",
190
+ },
191
+ }}
192
+ >
193
+ <VStack gap="3" style={{ width: 500, maxWidth: "100%" }}>
194
+ <FileUpload.Dropzone label="Last opp bilder" onSelect={console.log} />
195
+ <FileUpload.Item
196
+ file={{ name: "eksempel.png", size: 200000 }}
197
+ onDelete={() => null}
198
+ />
199
+ <FileUpload.Item
200
+ file={{ name: "eksempel.png", size: 200000 }}
201
+ itemAction="retry"
202
+ onRetry={() => null}
203
+ />
204
+ <FileUpload.Item
205
+ file={{ name: "eksempel.png", size: 200000 }}
206
+ status="downloading"
207
+ />
208
+ <FileUpload.Item
209
+ file={{ name: "eksempel.png", size: 200000 }}
210
+ status="uploading"
211
+ />
212
+ <FileUpload.Item
213
+ file={{ name: "eksempel.png", size: 200000 }}
214
+ status="uploading"
215
+ translations={{ uploading: "Sender bilde..." }}
216
+ />
217
+ </VStack>
218
+ </FileUpload>
219
+ );
220
+
221
+ export const TriggerWithButton: StoryObj<typeof FileUpload.Trigger> = {
222
+ render: (props) => {
223
+ return (
224
+ <FileUpload.Trigger {...props} onSelect={console.log}>
225
+ <Button variant="secondary" icon={<UploadIcon aria-hidden />}>
226
+ Last opp filer
227
+ </Button>
228
+ </FileUpload.Trigger>
229
+ );
230
+ },
231
+ args: {
232
+ multiple: true,
233
+ accept: "",
234
+ maxSizeInBytes: 0,
235
+ },
236
+ };