@drivenets/design-system 0.0.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 (242) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +201 -0
  3. package/dist/index.cjs +5394 -0
  4. package/dist/index.css +1 -0
  5. package/dist/index.d.cts +9157 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.ts +9157 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +5335 -0
  10. package/dist/index.js.map +1 -0
  11. package/package.json +104 -0
  12. package/src/components/ds-alert-banner/ds-alert-banner.module.scss +116 -0
  13. package/src/components/ds-alert-banner/ds-alert-banner.tsx +115 -0
  14. package/src/components/ds-alert-banner/ds-alert-banner.types.tsx +47 -0
  15. package/src/components/ds-alert-banner/index.tsx +2 -0
  16. package/src/components/ds-breadcrumb/ds-breadcrumb.module.scss +99 -0
  17. package/src/components/ds-breadcrumb/ds-breadcrumb.tsx +94 -0
  18. package/src/components/ds-breadcrumb/ds-breadcrumb.types.tsx +32 -0
  19. package/src/components/ds-breadcrumb/index.tsx +2 -0
  20. package/src/components/ds-button/ds-button.tsx +12 -0
  21. package/src/components/ds-button/ds-button.types.ts +6 -0
  22. package/src/components/ds-button/index.ts +2 -0
  23. package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss +257 -0
  24. package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.tsx +34 -0
  25. package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.types.ts +41 -0
  26. package/src/components/ds-button/versions/ds-button-legacy/index.ts +2 -0
  27. package/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +580 -0
  28. package/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx +49 -0
  29. package/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts +41 -0
  30. package/src/components/ds-button/versions/ds-button-new/index.ts +2 -0
  31. package/src/components/ds-checkbox/ds-checkbox.module.scss +130 -0
  32. package/src/components/ds-checkbox/ds-checkbox.tsx +55 -0
  33. package/src/components/ds-checkbox/ds-checkbox.types.tsx +27 -0
  34. package/src/components/ds-checkbox/index.tsx +2 -0
  35. package/src/components/ds-chip/ds-chip.module.scss +71 -0
  36. package/src/components/ds-chip/ds-chip.tsx +73 -0
  37. package/src/components/ds-chip/ds-chip.types.tsx +46 -0
  38. package/src/components/ds-chip/index.tsx +2 -0
  39. package/src/components/ds-chip-group/ds-chip-group.module.scss +59 -0
  40. package/src/components/ds-chip-group/ds-chip-group.tsx +122 -0
  41. package/src/components/ds-chip-group/ds-chip-group.types.ts +52 -0
  42. package/src/components/ds-chip-group/hooks/use-chip-row-calculation.ts +76 -0
  43. package/src/components/ds-chip-group/index.ts +2 -0
  44. package/src/components/ds-confirmation/ds-confirmation.module.scss +58 -0
  45. package/src/components/ds-confirmation/ds-confirmation.tsx +121 -0
  46. package/src/components/ds-confirmation/ds-confirmation.types.tsx +28 -0
  47. package/src/components/ds-confirmation/index.ts +2 -0
  48. package/src/components/ds-dialog/ds-dialog.module.scss +50 -0
  49. package/src/components/ds-dialog/ds-dialog.tsx +70 -0
  50. package/src/components/ds-dialog/ds-dialog.types.tsx +48 -0
  51. package/src/components/ds-dialog/index.tsx +2 -0
  52. package/src/components/ds-drawer/ds-drawer.module.scss +212 -0
  53. package/src/components/ds-drawer/ds-drawer.tsx +160 -0
  54. package/src/components/ds-drawer/ds-drawer.types.tsx +54 -0
  55. package/src/components/ds-drawer/index.ts +2 -0
  56. package/src/components/ds-dropdown-menu/ds-dropdown-menu.module.scss +123 -0
  57. package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +396 -0
  58. package/src/components/ds-dropdown-menu/ds-dropdown-menu.types.tsx +262 -0
  59. package/src/components/ds-dropdown-menu/index.tsx +2 -0
  60. package/src/components/ds-expandable-text-input/ds-expandable-text-input.module.scss +70 -0
  61. package/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx +106 -0
  62. package/src/components/ds-expandable-text-input/ds-expandable-text-input.types.tsx +19 -0
  63. package/src/components/ds-expandable-text-input/index.ts +2 -0
  64. package/src/components/ds-file-upload/components/file-upload/file-upload.module.scss +90 -0
  65. package/src/components/ds-file-upload/components/file-upload/file-upload.tsx +106 -0
  66. package/src/components/ds-file-upload/components/file-upload/file-upload.types.tsx +94 -0
  67. package/src/components/ds-file-upload/components/file-upload/index.ts +2 -0
  68. package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.module.scss +125 -0
  69. package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.tsx +143 -0
  70. package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.types.tsx +26 -0
  71. package/src/components/ds-file-upload/components/file-upload-item/index.ts +2 -0
  72. package/src/components/ds-file-upload/ds-file-upload-api.types.ts +38 -0
  73. package/src/components/ds-file-upload/ds-file-upload.tsx +46 -0
  74. package/src/components/ds-file-upload/ds-file-upload.types.tsx +54 -0
  75. package/src/components/ds-file-upload/errors/file-upload-errors.ts +27 -0
  76. package/src/components/ds-file-upload/hooks/index.ts +2 -0
  77. package/src/components/ds-file-upload/hooks/use-file-upload.ts +350 -0
  78. package/src/components/ds-file-upload/index.ts +7 -0
  79. package/src/components/ds-file-upload/types/accept-types.ts +106 -0
  80. package/src/components/ds-file-upload/utils/accept-utils.ts +25 -0
  81. package/src/components/ds-file-upload/utils/constants.ts +21 -0
  82. package/src/components/ds-file-upload/utils/file-factory.ts +18 -0
  83. package/src/components/ds-file-upload/utils/format-file-size.ts +15 -0
  84. package/src/components/ds-file-upload/utils/generate-helper-text.ts +39 -0
  85. package/src/components/ds-file-upload/utils/get-error-message.ts +17 -0
  86. package/src/components/ds-file-upload/utils/is-file-equal.ts +8 -0
  87. package/src/components/ds-form-control/ds-form-control.module.scss +135 -0
  88. package/src/components/ds-form-control/ds-form-control.tsx +94 -0
  89. package/src/components/ds-form-control/ds-form-control.types.tsx +58 -0
  90. package/src/components/ds-form-control/index.tsx +2 -0
  91. package/src/components/ds-grid/ds-grid.module.scss +0 -0
  92. package/src/components/ds-grid/ds-grid.tsx +43 -0
  93. package/src/components/ds-grid/ds-grid.types.ts +55 -0
  94. package/src/components/ds-grid/index.ts +2 -0
  95. package/src/components/ds-icon/ds-icon.module.scss +25 -0
  96. package/src/components/ds-icon/ds-icon.tsx +33 -0
  97. package/src/components/ds-icon/ds-icon.types.ts +53 -0
  98. package/src/components/ds-icon/index.ts +2 -0
  99. package/src/components/ds-icon/material-icons.ts +6203 -0
  100. package/src/components/ds-modal/ds-modal.module.scss +100 -0
  101. package/src/components/ds-modal/ds-modal.tsx +136 -0
  102. package/src/components/ds-modal/ds-modal.types.tsx +44 -0
  103. package/src/components/ds-modal/index.tsx +2 -0
  104. package/src/components/ds-number-input/ds-number-input.module.scss +15 -0
  105. package/src/components/ds-number-input/ds-number-input.tsx +74 -0
  106. package/src/components/ds-number-input/ds-number-input.types.tsx +76 -0
  107. package/src/components/ds-number-input/index.tsx +2 -0
  108. package/src/components/ds-panel/ds-panel.module.scss +95 -0
  109. package/src/components/ds-panel/ds-panel.tsx +46 -0
  110. package/src/components/ds-panel/ds-panel.types.ts +17 -0
  111. package/src/components/ds-panel/index.tsx +2 -0
  112. package/src/components/ds-password-input/ds-password-input.module.scss +10 -0
  113. package/src/components/ds-password-input/ds-password-input.tsx +67 -0
  114. package/src/components/ds-password-input/ds-password-input.types.tsx +62 -0
  115. package/src/components/ds-password-input/index.tsx +2 -0
  116. package/src/components/ds-popover/ds-popover.module.scss +23 -0
  117. package/src/components/ds-popover/ds-popover.tsx +33 -0
  118. package/src/components/ds-popover/ds-popover.types.tsx +24 -0
  119. package/src/components/ds-popover/index.tsx +2 -0
  120. package/src/components/ds-radio-group/ds-radio-group.module.scss +138 -0
  121. package/src/components/ds-radio-group/ds-radio-group.tsx +115 -0
  122. package/src/components/ds-radio-group/ds-radio-group.types.tsx +93 -0
  123. package/src/components/ds-radio-group/index.tsx +2 -0
  124. package/src/components/ds-select/ds-select.module.scss +66 -0
  125. package/src/components/ds-select/ds-select.tsx +239 -0
  126. package/src/components/ds-select/ds-select.types.tsx +80 -0
  127. package/src/components/ds-select/index.tsx +2 -0
  128. package/src/components/ds-select/select-items-chips.tsx +56 -0
  129. package/src/components/ds-smart-tabs/ds-smart-tabs.module.scss +69 -0
  130. package/src/components/ds-smart-tabs/ds-smart-tabs.tsx +113 -0
  131. package/src/components/ds-smart-tabs/ds-smart-tabs.types.tsx +74 -0
  132. package/src/components/ds-smart-tabs/index.tsx +2 -0
  133. package/src/components/ds-spinner/ds-spinner.module.scss +39 -0
  134. package/src/components/ds-spinner/ds-spinner.tsx +51 -0
  135. package/src/components/ds-spinner/ds-spinner.types.tsx +20 -0
  136. package/src/components/ds-spinner/index.tsx +2 -0
  137. package/src/components/ds-status-badge/ds-status-badge.module.scss +79 -0
  138. package/src/components/ds-status-badge/ds-status-badge.tsx +42 -0
  139. package/src/components/ds-status-badge/ds-status-badge.types.tsx +46 -0
  140. package/src/components/ds-status-badge/index.tsx +2 -0
  141. package/src/components/ds-stepper/components/ds-step-content.tsx +32 -0
  142. package/src/components/ds-stepper/components/ds-step-description.tsx +6 -0
  143. package/src/components/ds-stepper/components/ds-step-label.tsx +6 -0
  144. package/src/components/ds-stepper/components/ds-step-next-button.tsx +13 -0
  145. package/src/components/ds-stepper/components/ds-step.tsx +59 -0
  146. package/src/components/ds-stepper/ds-stepper.module.scss +134 -0
  147. package/src/components/ds-stepper/ds-stepper.tsx +34 -0
  148. package/src/components/ds-stepper/ds-stepper.types.ts +12 -0
  149. package/src/components/ds-stepper/hooks/use-stepper.ts +20 -0
  150. package/src/components/ds-stepper/hooks/use-steps.ts +18 -0
  151. package/src/components/ds-stepper/index.tsx +6 -0
  152. package/src/components/ds-system-status/ds-system-status.module.scss +87 -0
  153. package/src/components/ds-system-status/ds-system-status.tsx +33 -0
  154. package/src/components/ds-system-status/ds-system-status.types.tsx +37 -0
  155. package/src/components/ds-system-status/index.tsx +10 -0
  156. package/src/components/ds-table/components/core-table/core-table.module.scss +32 -0
  157. package/src/components/ds-table/components/core-table/core-table.tsx +83 -0
  158. package/src/components/ds-table/components/core-table/index.ts +1 -0
  159. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +115 -0
  160. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx +70 -0
  161. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.types.ts +41 -0
  162. package/src/components/ds-table/components/ds-table-bulk-actions/index.ts +2 -0
  163. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +53 -0
  164. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.tsx +95 -0
  165. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.types.ts +67 -0
  166. package/src/components/ds-table/components/ds-table-cell/index.ts +2 -0
  167. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +81 -0
  168. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +99 -0
  169. package/src/components/ds-table/components/ds-table-header/ds-table-header.types.ts +11 -0
  170. package/src/components/ds-table/components/ds-table-header/index.ts +2 -0
  171. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +137 -0
  172. package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +205 -0
  173. package/src/components/ds-table/components/ds-table-row/ds-table-row.types.ts +22 -0
  174. package/src/components/ds-table/components/ds-table-row/index.ts +2 -0
  175. package/src/components/ds-table/context/ds-table-context.tsx +39 -0
  176. package/src/components/ds-table/ds-table.module.scss +60 -0
  177. package/src/components/ds-table/ds-table.tsx +312 -0
  178. package/src/components/ds-table/ds-table.types.ts +377 -0
  179. package/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx +116 -0
  180. package/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx +171 -0
  181. package/src/components/ds-table/filters/adapters/create-filter-adapter.ts +131 -0
  182. package/src/components/ds-table/filters/adapters/index.ts +12 -0
  183. package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.tsx +43 -0
  184. package/src/components/ds-table/filters/components/checkbox-filter/index.ts +2 -0
  185. package/src/components/ds-table/filters/components/range-filter/index.ts +2 -0
  186. package/src/components/ds-table/filters/components/range-filter/range-filter.module.scss +25 -0
  187. package/src/components/ds-table/filters/components/range-filter/range-filter.tsx +79 -0
  188. package/src/components/ds-table/filters/hooks/index.ts +1 -0
  189. package/src/components/ds-table/filters/hooks/use-table-filters.ts +234 -0
  190. package/src/components/ds-table/filters/index.ts +50 -0
  191. package/src/components/ds-table/filters/types/filter-adapter.types.ts +111 -0
  192. package/src/components/ds-table/hooks/use-drag-and-drop.tsx +107 -0
  193. package/src/components/ds-table/index.ts +4 -0
  194. package/src/components/ds-table/styles/_variables.scss +17 -0
  195. package/src/components/ds-table/styles/shared/ds-table-shared.module.scss +16 -0
  196. package/src/components/ds-table/utils/column-styling.ts +30 -0
  197. package/src/components/ds-table/utils/create-columns-grid-template.ts +23 -0
  198. package/src/components/ds-table/utils/merge-refs.ts +15 -0
  199. package/src/components/ds-table/utils/story-data-generator.ts +91 -0
  200. package/src/components/ds-text-input/ds-text-input.module.scss +27 -0
  201. package/src/components/ds-text-input/ds-text-input.tsx +75 -0
  202. package/src/components/ds-text-input/ds-text-input.types.tsx +110 -0
  203. package/src/components/ds-text-input/index.ts +2 -0
  204. package/src/components/ds-textarea/ds-textarea.module.scss +5 -0
  205. package/src/components/ds-textarea/ds-textarea.tsx +49 -0
  206. package/src/components/ds-textarea/ds-textarea.types.tsx +67 -0
  207. package/src/components/ds-textarea/index.tsx +2 -0
  208. package/src/components/ds-toast/ds-toast-context.tsx +79 -0
  209. package/src/components/ds-toast/ds-toast.module.scss +134 -0
  210. package/src/components/ds-toast/ds-toast.tsx +54 -0
  211. package/src/components/ds-toast/ds-toast.types.tsx +53 -0
  212. package/src/components/ds-toast/index.ts +3 -0
  213. package/src/components/ds-tooltip/ds-tooltip.module.scss +19 -0
  214. package/src/components/ds-tooltip/ds-tooltip.tsx +33 -0
  215. package/src/components/ds-tooltip/ds-tooltip.types.tsx +12 -0
  216. package/src/components/ds-tooltip/index.tsx +2 -0
  217. package/src/components/ds-typography/ds-typography.config.ts +32 -0
  218. package/src/components/ds-typography/ds-typography.module.scss +93 -0
  219. package/src/components/ds-typography/ds-typography.tsx +27 -0
  220. package/src/components/ds-typography/ds-typography.types.tsx +27 -0
  221. package/src/components/ds-typography/index.tsx +2 -0
  222. package/src/components/ds-vertical-tabs/ds-vertical-tabs.module.scss +60 -0
  223. package/src/components/ds-vertical-tabs/ds-vertical-tabs.tsx +80 -0
  224. package/src/components/ds-vertical-tabs/ds-vertical-tabs.types.ts +69 -0
  225. package/src/components/ds-vertical-tabs/index.ts +7 -0
  226. package/src/index.ts +34 -0
  227. package/src/styles/_colors.scss +65 -0
  228. package/src/styles/_grid.scss +111 -0
  229. package/src/styles/_mixin.scss +1 -0
  230. package/src/styles/_reset.scss +38 -0
  231. package/src/styles/_root.scss +390 -0
  232. package/src/styles/_scrollbars.scss +41 -0
  233. package/src/styles/_spacing.scss +47 -0
  234. package/src/styles/_typography.scss +301 -0
  235. package/src/styles/_utilities.scss +33 -0
  236. package/src/styles/_variables.scss +44 -0
  237. package/src/styles/mixins/_bounding-box.scss +53 -0
  238. package/src/styles/mixins/_list.scss +74 -0
  239. package/src/styles/shared/_dropdown.scss +89 -0
  240. package/src/styles/shared/_input.scss +365 -0
  241. package/src/styles/shared/_modal.scss +123 -0
  242. package/src/styles/styles.scss +12 -0
@@ -0,0 +1,143 @@
1
+ import type React from 'react';
2
+ import classNames from 'classnames';
3
+ import { Progress } from '@ark-ui/react';
4
+ import { DsTooltip } from '../../../ds-tooltip';
5
+ import { DsButton } from '../../../ds-button';
6
+ import { DsIcon } from '../../../ds-icon';
7
+ import { getErrorMessage } from '../../utils/get-error-message';
8
+ import type { FileUploadItemProps } from './file-upload-item.types';
9
+ import styles from './file-upload-item.module.scss';
10
+
11
+ /**
12
+ * Individual file upload item component
13
+ * Displays a single file with its upload status, progress, and action buttons
14
+ */
15
+ export const FileUploadItem: React.FC<FileUploadItemProps> = ({
16
+ id,
17
+ name,
18
+ progress,
19
+ hideProgress = false,
20
+ status,
21
+ errors,
22
+ onCancel,
23
+ onRetry,
24
+ onRemove,
25
+ onDelete,
26
+ }) => {
27
+ const fileItemClass = classNames(styles.fileItemContent, {
28
+ [styles.fileItemError]: status === 'error',
29
+ [styles.fileItemCompleted]: status === 'completed',
30
+ [styles.fileItemUploading]: status === 'uploading',
31
+ [styles.fileItemInterrupted]: status === 'interrupted' || status === 'cancelled',
32
+ });
33
+
34
+ return (
35
+ <div className={styles.fileItem}>
36
+ <div className={fileItemClass}>
37
+ <DsIcon className={styles.filePreview} icon="upload_file" size="tiny" />
38
+
39
+ <div className={styles.fileInfo}>
40
+ <div className={styles.fileName} title={name}>
41
+ {name}
42
+ </div>
43
+
44
+ <Progress.Root value={status === 'error' ? 1 : progress} className={styles.progressBar}>
45
+ <Progress.Track className={styles.progressTrack}>
46
+ <Progress.Range className={styles.progressRange} />
47
+ </Progress.Track>
48
+ </Progress.Root>
49
+ </div>
50
+
51
+ {(status === 'interrupted' || status === 'cancelled') && onRetry && (
52
+ <DsTooltip content="Retry">
53
+ <DsButton
54
+ type="button"
55
+ design="v1.2"
56
+ buttonType="tertiary"
57
+ aria-label={`Retry ${name} upload`}
58
+ onClick={() => onRetry(id)}
59
+ >
60
+ <DsIcon icon="refresh" size="small" />
61
+ </DsButton>
62
+ </DsTooltip>
63
+ )}
64
+
65
+ {(status === 'pending' || status === 'error' || status === 'interrupted') && (
66
+ <DsTooltip content="Remove">
67
+ <DsButton
68
+ type="button"
69
+ design="v1.2"
70
+ buttonType="tertiary"
71
+ aria-label={`Remove ${name} upload`}
72
+ onClick={() => onRemove?.(id)}
73
+ >
74
+ <DsIcon icon="do_not_disturb_on" size="small" />
75
+ </DsButton>
76
+ </DsTooltip>
77
+ )}
78
+
79
+ {status === 'uploading' && onCancel && (
80
+ <DsTooltip content="Cancel">
81
+ <DsButton
82
+ type="button"
83
+ design="v1.2"
84
+ buttonType="tertiary"
85
+ aria-label={`Cancel ${name} upload`}
86
+ onClick={() => onCancel(id)}
87
+ >
88
+ <DsIcon icon="close" size="small" />
89
+ </DsButton>
90
+ </DsTooltip>
91
+ )}
92
+
93
+ {status === 'completed' && (
94
+ <DsTooltip content="Delete">
95
+ <DsButton
96
+ type="button"
97
+ design="v1.2"
98
+ buttonType="tertiary"
99
+ aria-label={`Delete ${name}`}
100
+ onClick={() => onDelete?.(id)}
101
+ >
102
+ <DsIcon icon="delete" size="small" />
103
+ </DsButton>
104
+ </DsTooltip>
105
+ )}
106
+ </div>
107
+
108
+ {status === 'completed' && (
109
+ <div className={styles.completedText}>
110
+ <DsIcon icon="check_circle" size="tiny" filled />
111
+ Upload complete
112
+ </div>
113
+ )}
114
+
115
+ {status === 'interrupted' && (
116
+ <div className={styles.interruptedText}>
117
+ <DsIcon icon="info" size="tiny" filled />
118
+ Upload interrupted
119
+ </div>
120
+ )}
121
+
122
+ {status === 'cancelled' && (
123
+ <div className={styles.interruptedText}>
124
+ <DsIcon icon="info" size="tiny" filled />
125
+ Upload cancelled
126
+ </div>
127
+ )}
128
+
129
+ {status === 'error' &&
130
+ errors?.length &&
131
+ errors.map((error) => (
132
+ <div key={error} className={styles.errorMessage}>
133
+ <DsIcon icon="cancel" size="tiny" filled />
134
+ {getErrorMessage(error)}
135
+ </div>
136
+ ))}
137
+
138
+ {!hideProgress && status === 'uploading' && (
139
+ <span className={styles.progressText}>Uploading...({Math.round(progress)}%)</span>
140
+ )}
141
+ </div>
142
+ );
143
+ };
@@ -0,0 +1,26 @@
1
+ import type { UploadFileStatus } from '../../ds-file-upload-api.types';
2
+
3
+ export interface FileUploadItemProps {
4
+ /** Unique identifier for the file */
5
+ id: string;
6
+ /** Name of the file */
7
+ name: string;
8
+ /** Upload progress percentage (0-100) */
9
+ progress: number;
10
+ /** Current status of the file upload */
11
+ status: UploadFileStatus;
12
+ /** Array of error messages if any */
13
+ errors?: string[];
14
+
15
+ /** Whether to hide progress bar */
16
+ hideProgress?: boolean;
17
+
18
+ /** Callback when cancel button is clicked */
19
+ onCancel?: (fileId: string) => void;
20
+ /** Callback when retry button is clicked */
21
+ onRetry?: (fileId: string) => void;
22
+ /** Callback when remove button is clicked (for pending/error states) */
23
+ onRemove?: (fileId: string) => void;
24
+ /** Callback when delete button is clicked (for completed state) */
25
+ onDelete?: (fileId: string) => void;
26
+ }
@@ -0,0 +1,2 @@
1
+ export { FileUploadItem } from './file-upload-item';
2
+ export type { FileUploadItemProps } from './file-upload-item.types';
@@ -0,0 +1,38 @@
1
+ import type { FileUploadFileError } from '@ark-ui/react';
2
+
3
+ export type FileError = FileUploadFileError;
4
+ export type UploadFileStatus = 'pending' | 'uploading' | 'interrupted' | 'completed' | 'error' | 'cancelled';
5
+ export type FileMetadata = Record<string, unknown>;
6
+
7
+ export interface UploadedFile extends File {
8
+ id: string;
9
+ progress: number;
10
+ status: UploadFileStatus;
11
+ errors?: FileError[];
12
+ }
13
+
14
+ export interface FileUploadOptions {
15
+ file: File;
16
+ fileId: string;
17
+ metadata?: FileMetadata;
18
+ onProgress?: (progress: number) => void;
19
+ signal?: AbortSignal;
20
+ }
21
+
22
+ export interface FileUploadResult {
23
+ url: string;
24
+ metadata?: FileMetadata;
25
+ }
26
+
27
+ /**
28
+ * File upload adapter interface
29
+ * The upload method should:
30
+ * - resolve with FileUploadResult on success
31
+ * - reject with RetryableFileUploadError for transient failures
32
+ * - reject with FatalFileUploadError for permanent failures
33
+ */
34
+ export interface FileUploadAdapter {
35
+ upload: (options: FileUploadOptions) => FileUploadResult | Promise<FileUploadResult>;
36
+ cancel?: (fileId: string) => void | Promise<void>;
37
+ delete?: (fileId: string) => void | Promise<void>;
38
+ }
@@ -0,0 +1,46 @@
1
+ import type React from 'react';
2
+ import type { DsFileUploadProps } from './ds-file-upload.types';
3
+ import { FileUpload } from './components/file-upload';
4
+ import { useFileUpload } from './hooks';
5
+
6
+ /**
7
+ * Design system File Upload component (with state management)
8
+ */
9
+ const DsFileUpload: React.FC<DsFileUploadProps> = ({
10
+ adapter,
11
+ autoUpload = true,
12
+ maxConcurrent = 3,
13
+ metadata,
14
+ onFileUploadComplete,
15
+ onFileUploadError,
16
+ onFilesAdded,
17
+ onFileRemoved,
18
+ onFileDeleted,
19
+ onFileUploadCanceled,
20
+ onFileUploadRetried,
21
+ onAllFileUploadsComplete,
22
+ ...props
23
+ }) => {
24
+ const { getProps } = useFileUpload({
25
+ adapter,
26
+ autoUpload,
27
+ maxConcurrent,
28
+ maxFiles: props.maxFiles,
29
+ metadata,
30
+ onFileUploadComplete,
31
+ onFileUploadError,
32
+ onAllFileUploadsComplete,
33
+ });
34
+
35
+ const fileUploadProps = getProps({
36
+ onFilesAdded,
37
+ onFileRemoved,
38
+ onFileDeleted,
39
+ onFileUploadCanceled,
40
+ onFileUploadRetried,
41
+ });
42
+
43
+ return <FileUpload {...fileUploadProps} {...props} />;
44
+ };
45
+
46
+ export default DsFileUpload;
@@ -0,0 +1,54 @@
1
+ import type { FileUploadProps } from './components/file-upload';
2
+ import type { FileMetadata, FileUploadAdapter, FileUploadResult } from './ds-file-upload-api.types';
3
+
4
+ export interface DsFileUploadProps extends Omit<
5
+ FileUploadProps,
6
+ | 'files'
7
+ | 'acceptedFiles'
8
+ | 'onFileAccept'
9
+ | 'onFileReject'
10
+ | 'onFileRemove'
11
+ | 'onFileDelete'
12
+ | 'onFileCancel'
13
+ | 'onFileRetry'
14
+ | 'uploadProgress'
15
+ > {
16
+ /** Upload adapter implementation */
17
+ adapter: FileUploadAdapter;
18
+
19
+ /**
20
+ * Whether to start upload automatically when files are added
21
+ * @default true
22
+ */
23
+ autoUpload?: boolean;
24
+
25
+ /** Maximum number of concurrent uploads */
26
+ maxConcurrent?: number;
27
+
28
+ /** Additional metadata to attach to uploads */
29
+ metadata?: FileMetadata;
30
+
31
+ /** Called when a file upload completes successfully */
32
+ onFileUploadComplete?: (fileId: string, result: FileUploadResult) => void;
33
+
34
+ /** Called when a file upload fails */
35
+ onFileUploadError?: (fileId: string, error: string) => void;
36
+
37
+ /** Called when files are added (before upload) */
38
+ onFilesAdded?: (files: File[]) => void;
39
+
40
+ /** Called when a file is removed */
41
+ onFileRemoved?: (fileId: string) => void;
42
+
43
+ /** Called when a file is deleted */
44
+ onFileDeleted?: (fileId: string) => void;
45
+
46
+ /** Called when a file upload is canceled */
47
+ onFileUploadCanceled?: (fileId: string) => void;
48
+
49
+ /** Called when a file upload is retried */
50
+ onFileUploadRetried?: (fileId: string) => void;
51
+
52
+ /** Called when all file uploads are complete */
53
+ onAllFileUploadsComplete?: () => void;
54
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Base class for file upload errors
3
+ */
4
+ export abstract class FileUploadError extends Error {
5
+ abstract readonly isRetryable: boolean;
6
+
7
+ constructor(message: string) {
8
+ super(message);
9
+ this.name = this.constructor.name;
10
+ }
11
+ }
12
+
13
+ /**
14
+ * Retryable file upload error - indicates the upload can be retried
15
+ * Examples: network errors, timeouts, temporary server errors (503), user cancellation
16
+ */
17
+ export class RetryableFileUploadError extends FileUploadError {
18
+ readonly isRetryable = true;
19
+ }
20
+
21
+ /**
22
+ * Fatal file upload error - indicates the upload should not be retried
23
+ * Examples: file type not allowed, file too large, invalid credentials, malformed response
24
+ */
25
+ export class FatalFileUploadError extends FileUploadError {
26
+ readonly isRetryable = false;
27
+ }
@@ -0,0 +1,2 @@
1
+ export { useFileUpload } from './use-file-upload';
2
+ export type { UseFileUploadReturn } from './use-file-upload';
@@ -0,0 +1,350 @@
1
+ import { useState } from 'react';
2
+ import type {
3
+ FileUploadFileAcceptDetails,
4
+ FileUploadFileRejectDetails,
5
+ FileUploadFileRejection,
6
+ } from '@ark-ui/react';
7
+ import { createUploadedFile } from '../utils/file-factory';
8
+ import { isFileEqual } from '../utils/is-file-equal';
9
+ import type {
10
+ FileError,
11
+ FileMetadata,
12
+ FileUploadAdapter,
13
+ FileUploadResult,
14
+ UploadedFile,
15
+ UploadFileStatus,
16
+ } from '../ds-file-upload-api.types';
17
+ import { FileUploadError } from '../errors/file-upload-errors';
18
+ import type { FileUploadProps } from '../components/file-upload';
19
+
20
+ export interface UseFileUploadConfig {
21
+ adapter: FileUploadAdapter;
22
+ autoUpload?: boolean;
23
+ maxConcurrent?: number;
24
+ maxFiles?: number;
25
+ metadata?: FileMetadata;
26
+ onFileUploadComplete?: (fileId: string, result: FileUploadResult) => void;
27
+ onFileUploadError?: (fileId: string, error: string) => void;
28
+ onAllFileUploadsComplete?: () => void;
29
+ }
30
+
31
+ export interface UseFileUploadUserCallbacks {
32
+ onFilesAdded?: (files: File[]) => void;
33
+ onFileRemoved?: (fileId: string) => void;
34
+ onFileDeleted?: (fileId: string) => void;
35
+ onFileUploadCanceled?: (fileId: string) => void;
36
+ onFileUploadRetried?: (fileId: string) => void;
37
+ }
38
+
39
+ export interface UseFileUploadReturn {
40
+ files: UploadedFile[];
41
+ acceptedFiles: UploadedFile[];
42
+ addFiles: (newFiles: File[]) => UploadedFile[];
43
+ addRejectedFiles: (filesWithErrors: { file: File; errors: FileError[] }[]) => void;
44
+ removeFile: (fileId: string) => void;
45
+ deleteFile: (fileId: string) => Promise<void>;
46
+ uploadFile: (fileId: string) => Promise<void>;
47
+ uploadAll: () => Promise<void>;
48
+ cancelUpload: (fileId: string) => Promise<void>;
49
+ retryUpload: (fileId: string) => Promise<void>;
50
+ clearFiles: () => void;
51
+ /**
52
+ * Get props for FileUpload component with callback composition
53
+ * @param userCallbacks Optional user callbacks to compose with internal logic
54
+ * @returns Props ready to spread onto FileUpload component
55
+ */
56
+ getProps: (
57
+ userCallbacks?: UseFileUploadUserCallbacks,
58
+ ) => Pick<
59
+ FileUploadProps,
60
+ | 'files'
61
+ | 'acceptedFiles'
62
+ | 'onFileAccept'
63
+ | 'onFileReject'
64
+ | 'onFileRemove'
65
+ | 'onFileDelete'
66
+ | 'onFileCancel'
67
+ | 'onFileRetry'
68
+ >;
69
+ }
70
+
71
+ /**
72
+ * File upload state management hook
73
+ */
74
+ export function useFileUpload({
75
+ adapter,
76
+ autoUpload = true,
77
+ maxConcurrent = 3,
78
+ maxFiles,
79
+ metadata,
80
+ onFileUploadComplete,
81
+ onFileUploadError,
82
+ onAllFileUploadsComplete,
83
+ }: UseFileUploadConfig): UseFileUploadReturn {
84
+ const [abortControllers] = useState(() => new Map<string, AbortController>());
85
+ const [files, setFiles] = useState<UploadedFile[]>([]);
86
+ const acceptedFiles = files.filter((file) => file.status !== 'error');
87
+
88
+ if (files.length && !files.some((f) => f.status === 'uploading')) {
89
+ onAllFileUploadsComplete?.();
90
+ }
91
+
92
+ const getAddedFiles = (newFiles: File[]) =>
93
+ newFiles.filter((file) => !acceptedFiles.some((existing) => isFileEqual(existing, file)));
94
+
95
+ const getDuplicatedFiles = (newFiles: File[]) =>
96
+ newFiles.filter((file) => {
97
+ const uploadedFile = file as UploadedFile;
98
+ if (uploadedFile.id) {
99
+ return false;
100
+ }
101
+ const found = files.filter((existing) => isFileEqual(existing, file));
102
+ if (found.length) {
103
+ const alreadyAdded = found.some((existing) => existing.errors?.includes('FILE_EXISTS'));
104
+ return !alreadyAdded;
105
+ }
106
+ return false;
107
+ });
108
+
109
+ const markDuplicates = (duplicates: File[]) => {
110
+ const duplicateFilesWithErrors = duplicates.map((file) => ({
111
+ file,
112
+ errors: ['FILE_EXISTS'],
113
+ }));
114
+ if (duplicateFilesWithErrors.length) {
115
+ addRejectedFiles(duplicateFilesWithErrors);
116
+ }
117
+ };
118
+
119
+ const markMaxFiles = (files: File[]): File[] => {
120
+ if (maxFiles === undefined) {
121
+ return files;
122
+ }
123
+
124
+ const currentAcceptedCount = acceptedFiles.length;
125
+ const availableSlots = maxFiles - currentAcceptedCount;
126
+
127
+ let filesToAdd: File[];
128
+ let rejectedFiles: File[];
129
+
130
+ if (availableSlots <= 0) {
131
+ filesToAdd = [];
132
+ rejectedFiles = files;
133
+ } else if (files.length > availableSlots) {
134
+ filesToAdd = files.slice(0, availableSlots);
135
+ rejectedFiles = files.slice(availableSlots);
136
+ } else {
137
+ return files;
138
+ }
139
+
140
+ const rejectedFilesWithErrors = rejectedFiles.map((file) => ({
141
+ file,
142
+ errors: ['TOO_MANY_FILES'],
143
+ }));
144
+ addRejectedFiles(rejectedFilesWithErrors);
145
+
146
+ return filesToAdd;
147
+ };
148
+
149
+ const addNewFiles = (filesToUpload: File[]): UploadedFile[] => {
150
+ const uploadedFiles = filesToUpload.map((file) => createUploadedFile(file, 'pending'));
151
+ setFiles((prev) => [...prev, ...uploadedFiles]);
152
+ return uploadedFiles;
153
+ };
154
+
155
+ const uploadNewFiles = (uploadedFiles: UploadedFile[]) => {
156
+ if (autoUpload) {
157
+ uploadedFiles.forEach((file) => {
158
+ void uploadSingleFile(file);
159
+ });
160
+ }
161
+ };
162
+
163
+ const addFiles = (newFiles: File[]): UploadedFile[] => {
164
+ const addedFiles = getAddedFiles(newFiles);
165
+ const duplicateFiles = getDuplicatedFiles(newFiles);
166
+ markDuplicates(duplicateFiles);
167
+ const filesToAdd = markMaxFiles(addedFiles);
168
+ const uploadedFiles = addNewFiles(filesToAdd);
169
+ uploadNewFiles(uploadedFiles);
170
+ return uploadedFiles;
171
+ };
172
+
173
+ const addRejectedFiles = (filesWithErrors: { file: File; errors: FileError[] }[]) => {
174
+ const newFileStates = filesWithErrors.map(({ file, errors }) =>
175
+ createUploadedFile(file, 'error', errors),
176
+ );
177
+ setFiles((prev) => [...prev, ...newFileStates]);
178
+ };
179
+
180
+ const updateFileProgress = (fileId: string, progress: number) => {
181
+ setFiles((prev) => prev.map((file) => (file.id === fileId ? { ...file, progress } : file)));
182
+ };
183
+
184
+ const updateFileStatus = (fileId: string, status: UploadFileStatus, error?: string) => {
185
+ setFiles((prev) =>
186
+ prev.map((file) =>
187
+ file.id === fileId
188
+ ? {
189
+ ...file,
190
+ status,
191
+ errors: error ? [...(file.errors || []), error] : file.errors,
192
+ }
193
+ : file,
194
+ ),
195
+ );
196
+ };
197
+
198
+ const uploadFile = async (fileId: string) => {
199
+ const file = acceptedFiles.find((f) => f.id === fileId);
200
+ if (file) {
201
+ await uploadSingleFile(file);
202
+ }
203
+ };
204
+
205
+ const uploadSingleFile = async (file: UploadedFile) => {
206
+ const fileId = file.id;
207
+ const abortController = new AbortController();
208
+ abortControllers.set(fileId, abortController);
209
+
210
+ updateFileStatus(file.id, 'uploading');
211
+
212
+ try {
213
+ const result = await adapter.upload({
214
+ file,
215
+ fileId,
216
+ metadata,
217
+ signal: abortController.signal,
218
+ onProgress: (progress) => {
219
+ updateFileProgress(fileId, progress);
220
+ },
221
+ });
222
+
223
+ updateFileStatus(fileId, 'completed');
224
+ updateFileProgress(fileId, 100);
225
+ onFileUploadComplete?.(fileId, result);
226
+ } catch (error) {
227
+ const errorMessage = error instanceof Error ? error.message : 'Upload failed';
228
+ // Determine status based on error type
229
+ // Unknown errors are treated as retryable (network errors, etc.)
230
+ let status: UploadFileStatus = 'interrupted';
231
+ if (error instanceof FileUploadError) {
232
+ status = error.isRetryable ? 'interrupted' : 'error';
233
+ }
234
+ updateFileStatus(fileId, status, errorMessage);
235
+ onFileUploadError?.(fileId, errorMessage);
236
+ } finally {
237
+ abortControllers.delete(fileId);
238
+ }
239
+ };
240
+
241
+ const uploadAll = async () => {
242
+ const pendingFiles = files.filter((f) => f.status === 'pending');
243
+
244
+ for (let i = 0; i < pendingFiles.length; i += maxConcurrent) {
245
+ const batch = pendingFiles.slice(i, i + maxConcurrent);
246
+ await Promise.allSettled(batch.map((f) => uploadFile(f.id)));
247
+ }
248
+ };
249
+
250
+ const cancelUpload = async (fileId: string) => {
251
+ abortControllers.get(fileId)?.abort();
252
+ abortControllers.delete(fileId);
253
+
254
+ if (adapter.cancel) {
255
+ await adapter.cancel(fileId);
256
+ }
257
+
258
+ updateFileStatus(fileId, 'cancelled');
259
+ };
260
+
261
+ const retryUpload = async (fileId: string) => {
262
+ updateFileStatus(fileId, 'pending');
263
+ updateFileProgress(fileId, 0);
264
+ await uploadFile(fileId);
265
+ };
266
+
267
+ const removeFile = (fileId: string) => {
268
+ abortControllers.get(fileId)?.abort();
269
+ abortControllers.delete(fileId);
270
+
271
+ setFiles((prev) => prev.filter((file) => file.id !== fileId));
272
+ };
273
+
274
+ const deleteFile = async (fileId: string) => {
275
+ if (adapter.delete) {
276
+ await adapter.delete(fileId);
277
+ }
278
+
279
+ removeFile(fileId);
280
+ };
281
+
282
+ const clearFiles = () => {
283
+ abortControllers.forEach((controller) => controller.abort());
284
+ abortControllers.clear();
285
+
286
+ setFiles([]);
287
+ };
288
+
289
+ const getProps = (userCallbacks?: UseFileUploadUserCallbacks) => {
290
+ const handleFileAccept = (details: FileUploadFileAcceptDetails) => {
291
+ const addedFiles = addFiles(details.files);
292
+ userCallbacks?.onFilesAdded?.(addedFiles);
293
+ };
294
+
295
+ const handleFileReject = (details: FileUploadFileRejectDetails) => {
296
+ addRejectedFiles(
297
+ details.files.map((f: FileUploadFileRejection) => ({
298
+ file: f.file,
299
+ errors: f.errors,
300
+ })),
301
+ );
302
+ };
303
+
304
+ const handleFileRemove = (fileId: string) => {
305
+ removeFile(fileId);
306
+ userCallbacks?.onFileRemoved?.(fileId);
307
+ };
308
+
309
+ const handleFileDelete = async (fileId: string) => {
310
+ await deleteFile(fileId);
311
+ userCallbacks?.onFileDeleted?.(fileId);
312
+ };
313
+
314
+ const handleFileUploadCanceled = async (fileId: string) => {
315
+ await cancelUpload(fileId);
316
+ userCallbacks?.onFileUploadCanceled?.(fileId);
317
+ };
318
+
319
+ const handleFileUploadRetried = async (fileId: string) => {
320
+ await retryUpload(fileId);
321
+ userCallbacks?.onFileUploadRetried?.(fileId);
322
+ };
323
+
324
+ return {
325
+ files,
326
+ acceptedFiles,
327
+ onFileAccept: handleFileAccept,
328
+ onFileReject: handleFileReject,
329
+ onFileRemove: handleFileRemove,
330
+ onFileDelete: handleFileDelete,
331
+ onFileCancel: handleFileUploadCanceled,
332
+ onFileRetry: handleFileUploadRetried,
333
+ };
334
+ };
335
+
336
+ return {
337
+ files,
338
+ acceptedFiles,
339
+ addFiles,
340
+ addRejectedFiles,
341
+ removeFile,
342
+ deleteFile,
343
+ uploadFile,
344
+ uploadAll,
345
+ cancelUpload,
346
+ retryUpload,
347
+ clearFiles,
348
+ getProps,
349
+ };
350
+ }
@@ -0,0 +1,7 @@
1
+ export { default as DsFileUpload } from './ds-file-upload';
2
+ export { FileUpload } from './components/file-upload';
3
+ export * from './ds-file-upload.types';
4
+ export type { FileUploadAdapter, FileUploadOptions, FileUploadResult } from './ds-file-upload-api.types';
5
+ export * from './hooks';
6
+ export { FileUploadError, RetryableFileUploadError, FatalFileUploadError } from './errors/file-upload-errors';
7
+ export type { AcceptedFileType, FileExtension } from './types/accept-types';