@faststore/components 3.98.0-dev.1 → 3.98.0-dev.8

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 (198) hide show
  1. package/dist/cjs/atoms/Link/Link.d.ts +1 -1
  2. package/dist/cjs/atoms/Link/Link.d.ts.map +1 -1
  3. package/dist/cjs/atoms/Link/Link.js.map +1 -1
  4. package/dist/cjs/atoms/List/List.d.ts.map +1 -1
  5. package/dist/cjs/atoms/List/List.js.map +1 -1
  6. package/dist/cjs/hooks/index.d.ts +7 -3
  7. package/dist/cjs/hooks/index.d.ts.map +1 -1
  8. package/dist/cjs/hooks/index.js +12 -6
  9. package/dist/cjs/hooks/index.js.map +1 -1
  10. package/dist/cjs/hooks/useCSVParser.d.ts +43 -0
  11. package/dist/cjs/hooks/useCSVParser.d.ts.map +1 -0
  12. package/dist/cjs/hooks/useCSVParser.js +264 -0
  13. package/dist/cjs/hooks/useCSVParser.js.map +1 -0
  14. package/dist/cjs/hooks/useFileUpload.d.ts +26 -0
  15. package/dist/cjs/hooks/useFileUpload.d.ts.map +1 -0
  16. package/dist/cjs/hooks/useFileUpload.js +68 -0
  17. package/dist/cjs/hooks/useFileUpload.js.map +1 -0
  18. package/dist/cjs/index.d.ts +58 -50
  19. package/dist/cjs/index.d.ts.map +1 -1
  20. package/dist/cjs/index.js +47 -32
  21. package/dist/cjs/index.js.map +1 -1
  22. package/dist/cjs/molecules/Accordion/AccordionItem.d.ts.map +1 -1
  23. package/dist/cjs/molecules/Accordion/AccordionItem.js.map +1 -1
  24. package/dist/cjs/molecules/Dropzone/Dropzone.d.ts +87 -0
  25. package/dist/cjs/molecules/Dropzone/Dropzone.d.ts.map +1 -0
  26. package/dist/cjs/molecules/Dropzone/Dropzone.js +85 -0
  27. package/dist/cjs/molecules/Dropzone/Dropzone.js.map +1 -0
  28. package/dist/cjs/molecules/Dropzone/index.d.ts +3 -0
  29. package/dist/cjs/molecules/Dropzone/index.d.ts.map +1 -0
  30. package/dist/cjs/molecules/Dropzone/index.js +9 -0
  31. package/dist/cjs/molecules/Dropzone/index.js.map +1 -0
  32. package/dist/cjs/molecules/FileUploadCard/FileUploadCard.d.ts +113 -0
  33. package/dist/cjs/molecules/FileUploadCard/FileUploadCard.d.ts.map +1 -0
  34. package/dist/cjs/molecules/FileUploadCard/FileUploadCard.js +169 -0
  35. package/dist/cjs/molecules/FileUploadCard/FileUploadCard.js.map +1 -0
  36. package/dist/cjs/molecules/FileUploadCard/index.d.ts +3 -0
  37. package/dist/cjs/molecules/FileUploadCard/index.d.ts.map +1 -0
  38. package/dist/cjs/molecules/FileUploadCard/index.js +9 -0
  39. package/dist/cjs/molecules/FileUploadCard/index.js.map +1 -0
  40. package/dist/cjs/molecules/FileUploadStatus/FileUploadStatus.d.ts +93 -0
  41. package/dist/cjs/molecules/FileUploadStatus/FileUploadStatus.d.ts.map +1 -0
  42. package/dist/cjs/molecules/FileUploadStatus/FileUploadStatus.js +77 -0
  43. package/dist/cjs/molecules/FileUploadStatus/FileUploadStatus.js.map +1 -0
  44. package/dist/cjs/molecules/FileUploadStatus/index.d.ts +3 -0
  45. package/dist/cjs/molecules/FileUploadStatus/index.d.ts.map +1 -0
  46. package/dist/cjs/molecules/FileUploadStatus/index.js +11 -0
  47. package/dist/cjs/molecules/FileUploadStatus/index.js.map +1 -0
  48. package/dist/cjs/molecules/SearchInputField/SearchInputField.d.ts +23 -1
  49. package/dist/cjs/molecules/SearchInputField/SearchInputField.d.ts.map +1 -1
  50. package/dist/cjs/molecules/SearchInputField/SearchInputField.js +9 -4
  51. package/dist/cjs/molecules/SearchInputField/SearchInputField.js.map +1 -1
  52. package/dist/cjs/molecules/SearchProducts/SearchProductItem.d.ts +1 -1
  53. package/dist/cjs/molecules/SearchProducts/SearchProductItem.d.ts.map +1 -1
  54. package/dist/cjs/molecules/SearchProducts/SearchProductItem.js +2 -2
  55. package/dist/cjs/molecules/SearchProducts/SearchProductItem.js.map +1 -1
  56. package/dist/cjs/molecules/SearchProvider/SearchProvider.d.ts +5 -1
  57. package/dist/cjs/molecules/SearchProvider/SearchProvider.d.ts.map +1 -1
  58. package/dist/cjs/molecules/SearchProvider/SearchProvider.js +2 -2
  59. package/dist/cjs/molecules/SearchProvider/SearchProvider.js.map +1 -1
  60. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawer.d.ts +24 -0
  61. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawer.d.ts.map +1 -0
  62. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawer.js +14 -0
  63. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawer.js.map +1 -0
  64. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.d.ts +16 -0
  65. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.d.ts.map +1 -0
  66. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.js +37 -0
  67. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.js.map +1 -0
  68. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.d.ts +9 -0
  69. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.d.ts.map +1 -0
  70. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.js +24 -0
  71. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.js.map +1 -0
  72. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.d.ts +35 -0
  73. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.d.ts.map +1 -0
  74. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.js +91 -0
  75. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.js.map +1 -0
  76. package/dist/cjs/organisms/QuickOrderDrawer/index.d.ts +11 -0
  77. package/dist/cjs/organisms/QuickOrderDrawer/index.d.ts.map +1 -0
  78. package/dist/cjs/organisms/QuickOrderDrawer/index.js +18 -0
  79. package/dist/cjs/organisms/QuickOrderDrawer/index.js.map +1 -0
  80. package/dist/cjs/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.d.ts +61 -0
  81. package/dist/cjs/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.d.ts.map +1 -0
  82. package/dist/cjs/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.js +86 -0
  83. package/dist/cjs/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.js.map +1 -0
  84. package/dist/cjs/organisms/SearchInput/SearchInput.d.ts +8 -0
  85. package/dist/cjs/organisms/SearchInput/SearchInput.d.ts.map +1 -1
  86. package/dist/cjs/organisms/SearchInput/SearchInput.js +2 -2
  87. package/dist/cjs/organisms/SearchInput/SearchInput.js.map +1 -1
  88. package/dist/esm/atoms/Link/Link.d.ts +1 -1
  89. package/dist/esm/atoms/Link/Link.d.ts.map +1 -1
  90. package/dist/esm/atoms/Link/Link.js.map +1 -1
  91. package/dist/esm/atoms/List/List.d.ts.map +1 -1
  92. package/dist/esm/atoms/List/List.js.map +1 -1
  93. package/dist/esm/hooks/index.d.ts +7 -3
  94. package/dist/esm/hooks/index.d.ts.map +1 -1
  95. package/dist/esm/hooks/index.js +6 -3
  96. package/dist/esm/hooks/index.js.map +1 -1
  97. package/dist/esm/hooks/useCSVParser.d.ts +43 -0
  98. package/dist/esm/hooks/useCSVParser.d.ts.map +1 -0
  99. package/dist/esm/hooks/useCSVParser.js +259 -0
  100. package/dist/esm/hooks/useCSVParser.js.map +1 -0
  101. package/dist/esm/hooks/useFileUpload.d.ts +26 -0
  102. package/dist/esm/hooks/useFileUpload.d.ts.map +1 -0
  103. package/dist/esm/hooks/useFileUpload.js +64 -0
  104. package/dist/esm/hooks/useFileUpload.js.map +1 -0
  105. package/dist/esm/index.d.ts +58 -50
  106. package/dist/esm/index.d.ts.map +1 -1
  107. package/dist/esm/index.js +24 -20
  108. package/dist/esm/index.js.map +1 -1
  109. package/dist/esm/molecules/Accordion/AccordionItem.d.ts.map +1 -1
  110. package/dist/esm/molecules/Accordion/AccordionItem.js.map +1 -1
  111. package/dist/esm/molecules/Dropzone/Dropzone.d.ts +87 -0
  112. package/dist/esm/molecules/Dropzone/Dropzone.d.ts.map +1 -0
  113. package/dist/esm/molecules/Dropzone/Dropzone.js +82 -0
  114. package/dist/esm/molecules/Dropzone/Dropzone.js.map +1 -0
  115. package/dist/esm/molecules/Dropzone/index.d.ts +3 -0
  116. package/dist/esm/molecules/Dropzone/index.d.ts.map +1 -0
  117. package/dist/esm/molecules/Dropzone/index.js +2 -0
  118. package/dist/esm/molecules/Dropzone/index.js.map +1 -0
  119. package/dist/esm/molecules/FileUploadCard/FileUploadCard.d.ts +113 -0
  120. package/dist/esm/molecules/FileUploadCard/FileUploadCard.d.ts.map +1 -0
  121. package/dist/esm/molecules/FileUploadCard/FileUploadCard.js +166 -0
  122. package/dist/esm/molecules/FileUploadCard/FileUploadCard.js.map +1 -0
  123. package/dist/esm/molecules/FileUploadCard/index.d.ts +3 -0
  124. package/dist/esm/molecules/FileUploadCard/index.d.ts.map +1 -0
  125. package/dist/esm/molecules/FileUploadCard/index.js +2 -0
  126. package/dist/esm/molecules/FileUploadCard/index.js.map +1 -0
  127. package/dist/esm/molecules/FileUploadStatus/FileUploadStatus.d.ts +93 -0
  128. package/dist/esm/molecules/FileUploadStatus/FileUploadStatus.d.ts.map +1 -0
  129. package/dist/esm/molecules/FileUploadStatus/FileUploadStatus.js +73 -0
  130. package/dist/esm/molecules/FileUploadStatus/FileUploadStatus.js.map +1 -0
  131. package/dist/esm/molecules/FileUploadStatus/index.d.ts +3 -0
  132. package/dist/esm/molecules/FileUploadStatus/index.d.ts.map +1 -0
  133. package/dist/esm/molecules/FileUploadStatus/index.js +2 -0
  134. package/dist/esm/molecules/FileUploadStatus/index.js.map +1 -0
  135. package/dist/esm/molecules/SearchInputField/SearchInputField.d.ts +23 -1
  136. package/dist/esm/molecules/SearchInputField/SearchInputField.d.ts.map +1 -1
  137. package/dist/esm/molecules/SearchInputField/SearchInputField.js +9 -4
  138. package/dist/esm/molecules/SearchInputField/SearchInputField.js.map +1 -1
  139. package/dist/esm/molecules/SearchProducts/SearchProductItem.d.ts +1 -1
  140. package/dist/esm/molecules/SearchProducts/SearchProductItem.d.ts.map +1 -1
  141. package/dist/esm/molecules/SearchProducts/SearchProductItem.js +2 -2
  142. package/dist/esm/molecules/SearchProducts/SearchProductItem.js.map +1 -1
  143. package/dist/esm/molecules/SearchProvider/SearchProvider.d.ts +5 -1
  144. package/dist/esm/molecules/SearchProvider/SearchProvider.d.ts.map +1 -1
  145. package/dist/esm/molecules/SearchProvider/SearchProvider.js +2 -2
  146. package/dist/esm/molecules/SearchProvider/SearchProvider.js.map +1 -1
  147. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawer.d.ts +24 -0
  148. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawer.d.ts.map +1 -0
  149. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawer.js +11 -0
  150. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawer.js.map +1 -0
  151. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.d.ts +16 -0
  152. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.d.ts.map +1 -0
  153. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.js +34 -0
  154. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.js.map +1 -0
  155. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.d.ts +9 -0
  156. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.d.ts.map +1 -0
  157. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.js +21 -0
  158. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.js.map +1 -0
  159. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.d.ts +35 -0
  160. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.d.ts.map +1 -0
  161. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.js +88 -0
  162. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.js.map +1 -0
  163. package/dist/esm/organisms/QuickOrderDrawer/index.d.ts +11 -0
  164. package/dist/esm/organisms/QuickOrderDrawer/index.d.ts.map +1 -0
  165. package/dist/esm/organisms/QuickOrderDrawer/index.js +6 -0
  166. package/dist/esm/organisms/QuickOrderDrawer/index.js.map +1 -0
  167. package/dist/esm/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.d.ts +61 -0
  168. package/dist/esm/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.d.ts.map +1 -0
  169. package/dist/esm/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.js +80 -0
  170. package/dist/esm/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.js.map +1 -0
  171. package/dist/esm/organisms/SearchInput/SearchInput.d.ts +8 -0
  172. package/dist/esm/organisms/SearchInput/SearchInput.d.ts.map +1 -1
  173. package/dist/esm/organisms/SearchInput/SearchInput.js +2 -2
  174. package/dist/esm/organisms/SearchInput/SearchInput.js.map +1 -1
  175. package/package.json +5 -2
  176. package/src/atoms/Link/Link.tsx +4 -1
  177. package/src/atoms/List/List.tsx +18 -16
  178. package/src/hooks/index.ts +11 -3
  179. package/src/hooks/useCSVParser.ts +367 -0
  180. package/src/hooks/useFileUpload.ts +88 -0
  181. package/src/index.ts +97 -66
  182. package/src/molecules/Accordion/AccordionItem.tsx +4 -3
  183. package/src/molecules/Dropzone/Dropzone.tsx +248 -0
  184. package/src/molecules/Dropzone/index.ts +2 -0
  185. package/src/molecules/FileUploadCard/FileUploadCard.tsx +406 -0
  186. package/src/molecules/FileUploadCard/index.tsx +2 -0
  187. package/src/molecules/FileUploadStatus/FileUploadStatus.tsx +258 -0
  188. package/src/molecules/FileUploadStatus/index.tsx +6 -0
  189. package/src/molecules/SearchInputField/SearchInputField.tsx +72 -23
  190. package/src/molecules/SearchProducts/SearchProductItem.tsx +8 -3
  191. package/src/molecules/SearchProvider/SearchProvider.tsx +6 -1
  192. package/src/organisms/QuickOrderDrawer/QuickOrderDrawer.tsx +58 -0
  193. package/src/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.tsx +72 -0
  194. package/src/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.tsx +43 -0
  195. package/src/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.tsx +323 -0
  196. package/src/organisms/QuickOrderDrawer/index.ts +19 -0
  197. package/src/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.tsx +205 -0
  198. package/src/organisms/SearchInput/SearchInput.tsx +6 -0
@@ -0,0 +1,248 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react'
2
+ import React, { forwardRef, useCallback } from 'react'
3
+ import { useDropzone, type Accept, type FileRejection } from 'react-dropzone'
4
+
5
+ /**
6
+ * Merges multiple refs (object refs and callback refs) into a single callback ref.
7
+ */
8
+ function mergeRefs<T>(
9
+ ...refs: Array<React.Ref<T> | undefined>
10
+ ): React.RefCallback<T> {
11
+ return (instance: T | null) => {
12
+ for (const ref of refs) {
13
+ if (typeof ref === 'function') {
14
+ ref(instance)
15
+ } else if (ref && typeof ref === 'object') {
16
+ ;(ref as React.MutableRefObject<T | null>).current = instance
17
+ }
18
+ }
19
+ }
20
+ }
21
+
22
+ export interface DropzoneProps extends HTMLAttributes<HTMLDivElement> {
23
+ /**
24
+ * ID to find this component in testing tools (e.g.: cypress,
25
+ * testing-library, and jest).
26
+ */
27
+ testId?: string
28
+ /**
29
+ * A React component that will be rendered as an icon.
30
+ */
31
+ icon?: ReactNode
32
+ /**
33
+ * Callback function called when files are accepted.
34
+ */
35
+ onFilesAccepted?: (files: File[]) => void
36
+ /**
37
+ * Callback function called when files are rejected.
38
+ */
39
+ onFilesRejected?: (fileRejections: FileRejection[]) => void
40
+ /**
41
+ * Maximum number of files that can be selected.
42
+ */
43
+ maxFiles?: number
44
+ /**
45
+ * Maximum file size in bytes.
46
+ */
47
+ maxSize?: number
48
+ /**
49
+ * Minimum file size in bytes.
50
+ */
51
+ minSize?: number
52
+ /**
53
+ * Accepted file types.
54
+ */
55
+ accept?: Accept
56
+ /**
57
+ * Whether multiple files can be selected.
58
+ */
59
+ multiple?: boolean
60
+ /**
61
+ * Whether the dropzone is disabled.
62
+ */
63
+ disabled?: boolean
64
+ /**
65
+ * Custom text to display when dragging files over the dropzone.
66
+ */
67
+ dragActiveText?: string
68
+ /**
69
+ * Custom text to display in the dropzone.
70
+ */
71
+ text?: string
72
+ /**
73
+ * Whether to disable opening the file dialog on click.
74
+ */
75
+ noClick?: boolean
76
+ /**
77
+ * Whether to disable opening the file dialog via keyboard.
78
+ */
79
+ noKeyboard?: boolean
80
+ /**
81
+ * Whether to disable drag-and-drop on the drop zone.
82
+ */
83
+ noDrag?: boolean
84
+ /**
85
+ * Custom content for the select files button.
86
+ */
87
+ selectFilesButton?: ReactNode
88
+ /**
89
+ * Accessible label for the dropzone root element.
90
+ * When disabled, `aria-disabled` is also set so assistive tech
91
+ * can still identify the dropzone and its state.
92
+ */
93
+ ariaLabel?: string
94
+ }
95
+
96
+ export interface DropzoneState {
97
+ acceptedFiles: File[]
98
+ fileRejections: FileRejection[]
99
+ }
100
+
101
+ export interface DropzoneState {
102
+ acceptedFiles: File[]
103
+ fileRejections: FileRejection[]
104
+ }
105
+
106
+ const Dropzone = forwardRef<HTMLDivElement, DropzoneProps>(function Dropzone(
107
+ {
108
+ icon,
109
+ testId = 'fs-dropzone',
110
+ children,
111
+ onFilesAccepted,
112
+ onFilesRejected,
113
+ maxFiles = 0,
114
+ maxSize,
115
+ minSize,
116
+ accept,
117
+ multiple = true,
118
+ disabled = false,
119
+ dragActiveText,
120
+ text,
121
+ noClick = false,
122
+ noKeyboard = false,
123
+ noDrag = false,
124
+ selectFilesButton = null,
125
+ ariaLabel,
126
+ ...otherProps
127
+ },
128
+ ref
129
+ ) {
130
+ const onDrop = useCallback(
131
+ (acceptedFiles: File[]) => {
132
+ if (onFilesAccepted && acceptedFiles.length > 0) {
133
+ onFilesAccepted(acceptedFiles)
134
+ }
135
+ },
136
+ [onFilesAccepted]
137
+ )
138
+
139
+ const onDropRejected = useCallback(
140
+ (fileRejections: FileRejection[]) => {
141
+ if (onFilesRejected && fileRejections.length > 0) {
142
+ onFilesRejected(fileRejections)
143
+ }
144
+ },
145
+ [onFilesRejected]
146
+ )
147
+
148
+ const {
149
+ getRootProps,
150
+ getInputProps,
151
+ isDragActive,
152
+ isDragAccept,
153
+ isDragReject,
154
+ acceptedFiles,
155
+ fileRejections,
156
+ } = useDropzone({
157
+ onDrop,
158
+ onDropRejected,
159
+ accept,
160
+ maxFiles,
161
+ maxSize,
162
+ minSize,
163
+ multiple,
164
+ disabled,
165
+ noClick,
166
+ noKeyboard,
167
+ noDrag,
168
+ })
169
+
170
+ const rootProps = getRootProps()
171
+ const mergedRef = mergeRefs(ref, rootProps.ref as React.Ref<HTMLDivElement>)
172
+
173
+ const accessibilityProps = ariaLabel
174
+ ? disabled
175
+ ? {
176
+ role: 'button' as const,
177
+ 'aria-label': ariaLabel,
178
+ 'aria-disabled': true as const,
179
+ }
180
+ : {
181
+ role: 'button' as const,
182
+ 'aria-label': ariaLabel,
183
+ }
184
+ : {}
185
+
186
+ return (
187
+ <div
188
+ data-fs-dropzone
189
+ data-fs-dropzone-drag-active={isDragActive}
190
+ data-fs-dropzone-drag-accept={isDragAccept}
191
+ data-fs-dropzone-drag-reject={isDragReject}
192
+ data-fs-dropzone-disabled={disabled}
193
+ data-testid={testId}
194
+ {...rootProps}
195
+ {...accessibilityProps}
196
+ {...otherProps}
197
+ ref={mergedRef}
198
+ >
199
+ <input {...getInputProps()} />
200
+
201
+ {icon && <div data-fs-dropzone-icon>{icon}</div>}
202
+
203
+ <div data-fs-dropzone-content>
204
+ {isDragActive ? (
205
+ <p data-fs-dropzone-text>{dragActiveText}</p>
206
+ ) : (
207
+ <>{children || <p data-fs-dropzone-text>{text}</p>}</>
208
+ )}
209
+
210
+ {selectFilesButton && !noClick && !disabled && selectFilesButton}
211
+ </div>
212
+
213
+ {acceptedFiles.length > 0 && (
214
+ <div data-fs-dropzone-files>
215
+ <ul>
216
+ {acceptedFiles.map((file: File, index: number) => (
217
+ <li key={index} data-fs-dropzone-file>
218
+ {file.name} - {file.size} bytes
219
+ </li>
220
+ ))}
221
+ </ul>
222
+ </div>
223
+ )}
224
+
225
+ {fileRejections.length > 0 && (
226
+ <div data-fs-dropzone-errors>
227
+ <ul>
228
+ {fileRejections.map(
229
+ (fileRejection: FileRejection, index: number) => {
230
+ const { file, errors } = fileRejection
231
+ return (
232
+ <li key={index} data-fs-dropzone-error>
233
+ {file.name} -{' '}
234
+ {errors
235
+ .map((e: { message: string }) => e.message)
236
+ .join(', ')}
237
+ </li>
238
+ )
239
+ }
240
+ )}
241
+ </ul>
242
+ </div>
243
+ )}
244
+ </div>
245
+ )
246
+ })
247
+
248
+ export default Dropzone
@@ -0,0 +1,2 @@
1
+ export { default } from './Dropzone'
2
+ export type { DropzoneProps, DropzoneState } from './Dropzone'
@@ -0,0 +1,406 @@
1
+ import type { ChangeEvent, DragEvent, HTMLAttributes } from 'react'
2
+ import React, { useEffect, useRef, useState } from 'react'
3
+
4
+ import { Button, Card, Icon, Input } from '../..'
5
+ import { useOnClickOutside } from '../../hooks'
6
+ import FileUploadStatus, {
7
+ FileUploadErrorType,
8
+ FileUploadState,
9
+ } from '../FileUploadStatus/FileUploadStatus'
10
+
11
+ export interface FileUploadCardProps
12
+ extends Omit<HTMLAttributes<HTMLDivElement>, 'onDrop'> {
13
+ /**
14
+ * ID to find this component in testing tools (e.g.: cypress, testing library, and jest).
15
+ */
16
+ testId?: string
17
+ /**
18
+ * Controls whether the component is visible.
19
+ */
20
+ isOpen: boolean
21
+ /**
22
+ * Callback function when the component should be closed.
23
+ */
24
+ onDismiss?: () => void
25
+ /**
26
+ * Callback function when a file is selected.
27
+ */
28
+ onFileSelect?: (files: File[]) => void
29
+ /**
30
+ * Callback function when download template is clicked.
31
+ */
32
+ onDownloadTemplate?: () => void
33
+ /**
34
+ * Callback function when search button is clicked after file upload.
35
+ */
36
+ onSearch?: (file: File) => void
37
+ /**
38
+ * Accepted file types.
39
+ * @default '.csv'
40
+ */
41
+ accept?: string
42
+ /**
43
+ * Allow multiple file selection.
44
+ * @default false
45
+ */
46
+ multiple?: boolean
47
+ /**
48
+ * Card title (e.g. from CMS).
49
+ */
50
+ title: string
51
+ /**
52
+ * Aria-label for the file input (e.g. from CMS).
53
+ */
54
+ fileInputAriaLabel: string
55
+ /**
56
+ * Aria-label for the dropzone region (e.g. from CMS).
57
+ */
58
+ dropzoneAriaLabel: string
59
+ /**
60
+ * Dropzone title text (e.g. from CMS).
61
+ */
62
+ dropzoneTitle: string
63
+ /**
64
+ * Label for the select file button (e.g. from CMS).
65
+ */
66
+ selectFileButtonLabel: string
67
+ /**
68
+ * Label for the download template button (e.g. from CMS).
69
+ */
70
+ downloadTemplateButtonLabel: string
71
+ /**
72
+ * Aria-label for the remove button in FileUploadStatus (e.g. from CMS).
73
+ */
74
+ removeButtonAriaLabel: string
75
+ /**
76
+ * Label for the search button in FileUploadStatus (e.g. from CMS).
77
+ */
78
+ searchButtonLabel: string
79
+ /**
80
+ * Status text when uploading in FileUploadStatus (e.g. from CMS).
81
+ */
82
+ uploadingStatusText: string
83
+ /**
84
+ * Status text when completed in FileUploadStatus (e.g. from CMS). Receives file size in bytes.
85
+ */
86
+ getCompletedStatusText: (fileSize: number) => string
87
+ /**
88
+ * Error messages per error type for FileUploadStatus (e.g. from CMS).
89
+ */
90
+ errorMessages: Partial<
91
+ Record<FileUploadErrorType, { title: string; description: string }>
92
+ >
93
+ /**
94
+ * Formatter for file size display.
95
+ */
96
+ formatterFileSize?: (size: number) => string
97
+ /**
98
+ * Formatter for file name display.
99
+ */
100
+ formatterFileName?: (name: string) => string
101
+ /**
102
+ * Indicates if the file is being uploaded.
103
+ */
104
+ isUploading?: boolean
105
+ /**
106
+ * Indicates if there was an error during file upload.
107
+ */
108
+ hasError?: boolean
109
+ /**
110
+ * Type of error when hasError is true.
111
+ */
112
+ errorType?: FileUploadErrorType
113
+ /**
114
+ * Custom error message to display when hasError is true.
115
+ */
116
+ errorMessage?: string
117
+ }
118
+
119
+ const FileUploadCard = ({
120
+ testId = 'fs-file-upload-card',
121
+ isOpen,
122
+ onDismiss,
123
+ onFileSelect,
124
+ onDownloadTemplate,
125
+ onSearch,
126
+ accept = '.csv',
127
+ multiple = false,
128
+ title,
129
+ fileInputAriaLabel,
130
+ dropzoneAriaLabel,
131
+ dropzoneTitle,
132
+ selectFileButtonLabel,
133
+ downloadTemplateButtonLabel,
134
+ removeButtonAriaLabel,
135
+ searchButtonLabel,
136
+ uploadingStatusText,
137
+ getCompletedStatusText,
138
+ errorMessages,
139
+ formatterFileSize,
140
+ formatterFileName,
141
+ isUploading = false,
142
+ hasError = false,
143
+ errorType: errorTypeProp,
144
+ errorMessage,
145
+ ...otherProps
146
+ }: FileUploadCardProps) => {
147
+ const fileInputRef = useRef<HTMLInputElement>(null)
148
+ const containerRef = useRef<HTMLDivElement>(null)
149
+ const [dragActive, setDragActive] = useState(false)
150
+ const [selectedFile, setSelectedFile] = useState<File | null>(null)
151
+ const [uploadState, setUploadState] = useState<FileUploadState>(
152
+ FileUploadState.Uploading
153
+ )
154
+ const [errorType, setErrorType] = useState<FileUploadErrorType | undefined>(
155
+ undefined
156
+ )
157
+
158
+ useOnClickOutside(isOpen ? containerRef : undefined, () => {
159
+ if (isOpen) {
160
+ onDismiss?.()
161
+ }
162
+ })
163
+
164
+ useEffect(() => {
165
+ const handleEscape = (e: KeyboardEvent) => {
166
+ if (e.key === 'Escape' && isOpen) {
167
+ onDismiss?.()
168
+ }
169
+ }
170
+
171
+ window.addEventListener('keydown', handleEscape)
172
+ return () => window.removeEventListener('keydown', handleEscape)
173
+ }, [isOpen, onDismiss])
174
+
175
+ useEffect(() => {
176
+ if (!selectedFile) return
177
+
178
+ if (hasError) {
179
+ setUploadState(FileUploadState.Error)
180
+ setErrorType(errorTypeProp ?? FileUploadErrorType.InvalidStructure)
181
+ return
182
+ }
183
+
184
+ if (isUploading) {
185
+ setUploadState(FileUploadState.Uploading)
186
+ setErrorType(undefined)
187
+ return
188
+ }
189
+
190
+ setUploadState(FileUploadState.Completed)
191
+ setErrorType(undefined)
192
+ }, [hasError, selectedFile, isUploading, errorTypeProp])
193
+
194
+ const isValidFileType = (file: File): boolean => {
195
+ const fileName = file.name.toLowerCase()
196
+ const acceptedTypes = accept
197
+ .split(',')
198
+ .map((value) => value.trim().toLowerCase())
199
+ .filter(Boolean)
200
+
201
+ if (acceptedTypes.length === 0) {
202
+ return fileName.endsWith('.csv')
203
+ }
204
+
205
+ return acceptedTypes.some((value) =>
206
+ value.startsWith('.')
207
+ ? fileName.endsWith(value)
208
+ : file.type.toLowerCase() === value
209
+ )
210
+ }
211
+
212
+ const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
213
+ const files = Array.from(e.target.files || [])
214
+ if (files.length > 0) {
215
+ const file = files[0]
216
+ setSelectedFile(file)
217
+
218
+ if (!isValidFileType(file)) {
219
+ setUploadState(FileUploadState.Error)
220
+ setErrorType(FileUploadErrorType.Unsupported)
221
+ return
222
+ }
223
+
224
+ setErrorType(undefined)
225
+
226
+ if (isUploading) {
227
+ setUploadState(FileUploadState.Uploading)
228
+ } else {
229
+ setUploadState(FileUploadState.Completed)
230
+ }
231
+
232
+ if (onFileSelect) {
233
+ onFileSelect(files)
234
+ }
235
+ }
236
+ }
237
+
238
+ const handleDrag = (e: DragEvent<HTMLDivElement>) => {
239
+ if (!isOpen) return
240
+ e.preventDefault()
241
+ e.stopPropagation()
242
+ if (e.type === 'dragenter' || e.type === 'dragover') {
243
+ setDragActive(true)
244
+ } else if (e.type === 'dragleave') {
245
+ setDragActive(false)
246
+ }
247
+ }
248
+
249
+ const handleDrop = (e: DragEvent<HTMLDivElement>) => {
250
+ if (!isOpen) return
251
+ e.preventDefault()
252
+ e.stopPropagation()
253
+ setDragActive(false)
254
+
255
+ if (e.dataTransfer.files && e.dataTransfer.files[0]) {
256
+ const files = Array.from(e.dataTransfer.files)
257
+ const file = files[0]
258
+ setSelectedFile(file)
259
+
260
+ if (!isValidFileType(file)) {
261
+ setUploadState(FileUploadState.Error)
262
+ setErrorType(FileUploadErrorType.Unsupported)
263
+ return
264
+ }
265
+
266
+ setErrorType(undefined)
267
+
268
+ if (isUploading) {
269
+ setUploadState(FileUploadState.Uploading)
270
+ } else {
271
+ setUploadState(FileUploadState.Completed)
272
+ }
273
+
274
+ if (onFileSelect) {
275
+ onFileSelect(files)
276
+ }
277
+ }
278
+ }
279
+
280
+ const triggerFileInput = () => {
281
+ if (fileInputRef.current) {
282
+ fileInputRef.current.value = ''
283
+ }
284
+ fileInputRef.current?.click()
285
+ }
286
+
287
+ const handleDownloadTemplate = () => {
288
+ if (onDownloadTemplate) {
289
+ onDownloadTemplate()
290
+ } else {
291
+ const csvContent = 'SKU,Quantity\nAB001,2\nAB100,5\nAB999,49'
292
+ const blob = new Blob([csvContent], { type: 'text/csv' })
293
+ const url = window.URL.createObjectURL(blob)
294
+ const a = document.createElement('a')
295
+ a.href = url
296
+ a.download = 'template.csv'
297
+ a.click()
298
+ window.URL.revokeObjectURL(url)
299
+ }
300
+ }
301
+
302
+ const handleRemoveFile = () => {
303
+ setSelectedFile(null)
304
+ setUploadState(FileUploadState.Uploading)
305
+ setErrorType(undefined)
306
+ if (fileInputRef.current) {
307
+ fileInputRef.current.value = ''
308
+ }
309
+ }
310
+
311
+ const handleSearch = () => {
312
+ if (selectedFile && onSearch) {
313
+ onSearch(selectedFile)
314
+ }
315
+ }
316
+
317
+ return (
318
+ <Card
319
+ ref={containerRef}
320
+ title={title}
321
+ data-fs-file-upload-card
322
+ data-fs-file-upload-card-open={isOpen}
323
+ data-fs-file-upload-card-has-file={selectedFile !== null}
324
+ testId={testId}
325
+ aria-hidden={!isOpen}
326
+ {...otherProps}
327
+ >
328
+ <Input
329
+ ref={fileInputRef}
330
+ type="file"
331
+ onChange={handleFileChange}
332
+ accept={accept}
333
+ multiple={multiple}
334
+ style={{ display: 'none' }}
335
+ aria-label={fileInputAriaLabel}
336
+ />
337
+
338
+ {selectedFile ? (
339
+ <FileUploadStatus
340
+ file={selectedFile}
341
+ state={uploadState}
342
+ errorType={errorTypeProp ?? errorType}
343
+ errorMessages={errorMessages}
344
+ errorMessage={errorMessage}
345
+ onRemove={handleRemoveFile}
346
+ onSearch={handleSearch}
347
+ onDownloadTemplate={handleDownloadTemplate}
348
+ onSelectFile={triggerFileInput}
349
+ removeButtonAriaLabel={removeButtonAriaLabel}
350
+ searchButtonLabel={searchButtonLabel}
351
+ downloadTemplateButtonLabel={downloadTemplateButtonLabel}
352
+ selectFileButtonLabel={selectFileButtonLabel}
353
+ uploadingStatusText={uploadingStatusText}
354
+ completedStatusText={getCompletedStatusText(selectedFile.size)}
355
+ fileName={
356
+ formatterFileName
357
+ ? formatterFileName(selectedFile.name)
358
+ : selectedFile.name
359
+ }
360
+ />
361
+ ) : (
362
+ <div
363
+ data-fs-file-upload-card-dropzone
364
+ data-fs-file-upload-card-dragging={dragActive}
365
+ onDragEnter={handleDrag}
366
+ onDragLeave={handleDrag}
367
+ onDragOver={handleDrag}
368
+ onDrop={handleDrop}
369
+ role="region"
370
+ aria-label={dropzoneAriaLabel}
371
+ >
372
+ <div data-fs-file-upload-card-icon>
373
+ <div data-fs-file-upload-card-icon-shadow />
374
+ <div data-fs-file-upload-card-icon-wrapper>
375
+ <Icon name="Paperclip" width={24} height={32} />
376
+ </div>
377
+ <div data-fs-file-upload-card-icon-badge>
378
+ <Icon name="Plus" width={16} height={16} />
379
+ </div>
380
+ </div>
381
+
382
+ <p data-fs-file-upload-card-title>{dropzoneTitle}</p>
383
+
384
+ <Button
385
+ variant="secondary"
386
+ size="regular"
387
+ onClick={triggerFileInput}
388
+ data-fs-file-upload-card-select-button
389
+ >
390
+ {selectFileButtonLabel}
391
+ </Button>
392
+
393
+ <Button
394
+ type="button"
395
+ onClick={handleDownloadTemplate}
396
+ data-fs-file-upload-card-template-link
397
+ >
398
+ {downloadTemplateButtonLabel}
399
+ </Button>
400
+ </div>
401
+ )}
402
+ </Card>
403
+ )
404
+ }
405
+
406
+ export default FileUploadCard
@@ -0,0 +1,2 @@
1
+ export { default } from './FileUploadCard'
2
+ export type { FileUploadCardProps } from './FileUploadCard'