@faststore/components 3.98.0-dev.3 → 3.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) 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/organisms/QuickOrderDrawer/QuickOrderDrawer.d.ts +24 -0
  53. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawer.d.ts.map +1 -0
  54. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawer.js +14 -0
  55. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawer.js.map +1 -0
  56. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.d.ts +16 -0
  57. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.d.ts.map +1 -0
  58. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.js +37 -0
  59. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.js.map +1 -0
  60. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.d.ts +9 -0
  61. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.d.ts.map +1 -0
  62. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.js +24 -0
  63. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.js.map +1 -0
  64. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.d.ts +35 -0
  65. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.d.ts.map +1 -0
  66. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.js +91 -0
  67. package/dist/cjs/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.js.map +1 -0
  68. package/dist/cjs/organisms/QuickOrderDrawer/index.d.ts +11 -0
  69. package/dist/cjs/organisms/QuickOrderDrawer/index.d.ts.map +1 -0
  70. package/dist/cjs/organisms/QuickOrderDrawer/index.js +18 -0
  71. package/dist/cjs/organisms/QuickOrderDrawer/index.js.map +1 -0
  72. package/dist/cjs/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.d.ts +61 -0
  73. package/dist/cjs/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.d.ts.map +1 -0
  74. package/dist/cjs/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.js +86 -0
  75. package/dist/cjs/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.js.map +1 -0
  76. package/dist/esm/atoms/Link/Link.d.ts +1 -1
  77. package/dist/esm/atoms/Link/Link.d.ts.map +1 -1
  78. package/dist/esm/atoms/Link/Link.js.map +1 -1
  79. package/dist/esm/atoms/List/List.d.ts.map +1 -1
  80. package/dist/esm/atoms/List/List.js.map +1 -1
  81. package/dist/esm/hooks/index.d.ts +7 -3
  82. package/dist/esm/hooks/index.d.ts.map +1 -1
  83. package/dist/esm/hooks/index.js +6 -3
  84. package/dist/esm/hooks/index.js.map +1 -1
  85. package/dist/esm/hooks/useCSVParser.d.ts +43 -0
  86. package/dist/esm/hooks/useCSVParser.d.ts.map +1 -0
  87. package/dist/esm/hooks/useCSVParser.js +259 -0
  88. package/dist/esm/hooks/useCSVParser.js.map +1 -0
  89. package/dist/esm/hooks/useFileUpload.d.ts +26 -0
  90. package/dist/esm/hooks/useFileUpload.d.ts.map +1 -0
  91. package/dist/esm/hooks/useFileUpload.js +64 -0
  92. package/dist/esm/hooks/useFileUpload.js.map +1 -0
  93. package/dist/esm/index.d.ts +58 -50
  94. package/dist/esm/index.d.ts.map +1 -1
  95. package/dist/esm/index.js +24 -20
  96. package/dist/esm/index.js.map +1 -1
  97. package/dist/esm/molecules/Accordion/AccordionItem.d.ts.map +1 -1
  98. package/dist/esm/molecules/Accordion/AccordionItem.js.map +1 -1
  99. package/dist/esm/molecules/Dropzone/Dropzone.d.ts +87 -0
  100. package/dist/esm/molecules/Dropzone/Dropzone.d.ts.map +1 -0
  101. package/dist/esm/molecules/Dropzone/Dropzone.js +82 -0
  102. package/dist/esm/molecules/Dropzone/Dropzone.js.map +1 -0
  103. package/dist/esm/molecules/Dropzone/index.d.ts +3 -0
  104. package/dist/esm/molecules/Dropzone/index.d.ts.map +1 -0
  105. package/dist/esm/molecules/Dropzone/index.js +2 -0
  106. package/dist/esm/molecules/Dropzone/index.js.map +1 -0
  107. package/dist/esm/molecules/FileUploadCard/FileUploadCard.d.ts +113 -0
  108. package/dist/esm/molecules/FileUploadCard/FileUploadCard.d.ts.map +1 -0
  109. package/dist/esm/molecules/FileUploadCard/FileUploadCard.js +166 -0
  110. package/dist/esm/molecules/FileUploadCard/FileUploadCard.js.map +1 -0
  111. package/dist/esm/molecules/FileUploadCard/index.d.ts +3 -0
  112. package/dist/esm/molecules/FileUploadCard/index.d.ts.map +1 -0
  113. package/dist/esm/molecules/FileUploadCard/index.js +2 -0
  114. package/dist/esm/molecules/FileUploadCard/index.js.map +1 -0
  115. package/dist/esm/molecules/FileUploadStatus/FileUploadStatus.d.ts +93 -0
  116. package/dist/esm/molecules/FileUploadStatus/FileUploadStatus.d.ts.map +1 -0
  117. package/dist/esm/molecules/FileUploadStatus/FileUploadStatus.js +73 -0
  118. package/dist/esm/molecules/FileUploadStatus/FileUploadStatus.js.map +1 -0
  119. package/dist/esm/molecules/FileUploadStatus/index.d.ts +3 -0
  120. package/dist/esm/molecules/FileUploadStatus/index.d.ts.map +1 -0
  121. package/dist/esm/molecules/FileUploadStatus/index.js +2 -0
  122. package/dist/esm/molecules/FileUploadStatus/index.js.map +1 -0
  123. package/dist/esm/molecules/SearchInputField/SearchInputField.d.ts +23 -1
  124. package/dist/esm/molecules/SearchInputField/SearchInputField.d.ts.map +1 -1
  125. package/dist/esm/molecules/SearchInputField/SearchInputField.js +9 -4
  126. package/dist/esm/molecules/SearchInputField/SearchInputField.js.map +1 -1
  127. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawer.d.ts +24 -0
  128. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawer.d.ts.map +1 -0
  129. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawer.js +11 -0
  130. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawer.js.map +1 -0
  131. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.d.ts +16 -0
  132. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.d.ts.map +1 -0
  133. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.js +34 -0
  134. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.js.map +1 -0
  135. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.d.ts +9 -0
  136. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.d.ts.map +1 -0
  137. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.js +21 -0
  138. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.js.map +1 -0
  139. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.d.ts +35 -0
  140. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.d.ts.map +1 -0
  141. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.js +88 -0
  142. package/dist/esm/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.js.map +1 -0
  143. package/dist/esm/organisms/QuickOrderDrawer/index.d.ts +11 -0
  144. package/dist/esm/organisms/QuickOrderDrawer/index.d.ts.map +1 -0
  145. package/dist/esm/organisms/QuickOrderDrawer/index.js +6 -0
  146. package/dist/esm/organisms/QuickOrderDrawer/index.js.map +1 -0
  147. package/dist/esm/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.d.ts +61 -0
  148. package/dist/esm/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.d.ts.map +1 -0
  149. package/dist/esm/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.js +80 -0
  150. package/dist/esm/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.js.map +1 -0
  151. package/package.json +5 -2
  152. package/src/atoms/Link/Link.tsx +4 -1
  153. package/src/atoms/List/List.tsx +18 -16
  154. package/src/hooks/index.ts +11 -3
  155. package/src/hooks/useCSVParser.ts +367 -0
  156. package/src/hooks/useFileUpload.ts +88 -0
  157. package/src/index.ts +97 -66
  158. package/src/molecules/Accordion/AccordionItem.tsx +4 -3
  159. package/src/molecules/Dropzone/Dropzone.tsx +248 -0
  160. package/src/molecules/Dropzone/index.ts +2 -0
  161. package/src/molecules/FileUploadCard/FileUploadCard.tsx +406 -0
  162. package/src/molecules/FileUploadCard/index.tsx +2 -0
  163. package/src/molecules/FileUploadStatus/FileUploadStatus.tsx +258 -0
  164. package/src/molecules/FileUploadStatus/index.tsx +6 -0
  165. package/src/molecules/SearchInputField/SearchInputField.tsx +72 -23
  166. package/src/organisms/QuickOrderDrawer/QuickOrderDrawer.tsx +58 -0
  167. package/src/organisms/QuickOrderDrawer/QuickOrderDrawerFooter.tsx +72 -0
  168. package/src/organisms/QuickOrderDrawer/QuickOrderDrawerHeader.tsx +43 -0
  169. package/src/organisms/QuickOrderDrawer/QuickOrderDrawerProducts.tsx +323 -0
  170. package/src/organisms/QuickOrderDrawer/index.ts +19 -0
  171. package/src/organisms/QuickOrderDrawer/provider/QuickOrderDrawerProvider.tsx +205 -0
@@ -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'
@@ -0,0 +1,258 @@
1
+ import type { HTMLAttributes } from 'react'
2
+ import React from 'react'
3
+
4
+ import { Button, Icon } from '../..'
5
+
6
+ export enum FileUploadState {
7
+ Uploading = 'uploading',
8
+ Completed = 'completed',
9
+ Error = 'error',
10
+ }
11
+
12
+ export enum FileUploadErrorType {
13
+ Unexpected = 'unexpected',
14
+ Unsupported = 'unsupported',
15
+ Unreadable = 'unreadable',
16
+ InvalidStructure = 'invalid-structure',
17
+ Empty = 'empty',
18
+ TooLarge = 'too-large',
19
+ NoProductsFound = 'no-products-found',
20
+ }
21
+
22
+ export interface FileUploadStatusProps extends HTMLAttributes<HTMLDivElement> {
23
+ /**
24
+ * ID to find this component in testing tools (e.g.: cypress, testing library, and jest).
25
+ */
26
+ testId?: string
27
+ /**
28
+ * The file being uploaded.
29
+ */
30
+ file: File
31
+ /**
32
+ * Current upload state.
33
+ * @default 'uploading'
34
+ */
35
+ state?: FileUploadState
36
+ /**
37
+ * Type of error when state is 'error'.
38
+ */
39
+ errorType?: FileUploadErrorType
40
+ /**
41
+ * Custom error message. If provided, overrides the default error message for the errorType.
42
+ */
43
+ errorMessage?: string
44
+ /**
45
+ * Callback when the remove/cancel button is clicked.
46
+ */
47
+ onRemove?: () => void
48
+ /**
49
+ * Callback when the search button is clicked (only shown when state is 'completed').
50
+ */
51
+ onSearch?: () => void
52
+ /**
53
+ * Callback when download template is clicked (only shown when state is 'error').
54
+ */
55
+ onDownloadTemplate?: () => void
56
+ /**
57
+ * Callback when select file is clicked (only shown when state is 'error').
58
+ */
59
+ onSelectFile?: () => void
60
+ /**
61
+ * Aria-label for the remove button (e.g. from CMS).
62
+ */
63
+ removeButtonAriaLabel: string
64
+ /**
65
+ * Label for the search button when state is 'completed' (e.g. from CMS).
66
+ */
67
+ searchButtonLabel: string
68
+ /**
69
+ * Label for the download template button (e.g. from CMS).
70
+ */
71
+ downloadTemplateButtonLabel: string
72
+ /**
73
+ * Label for the select file button (e.g. from CMS).
74
+ */
75
+ selectFileButtonLabel: string
76
+ /**
77
+ * Error messages per error type (e.g. from CMS). Required when state is Error to show messages.
78
+ */
79
+ errorMessages: Partial<
80
+ Record<FileUploadErrorType, { title: string; description: string }>
81
+ >
82
+ /**
83
+ * Status text when state is Uploading (e.g. from CMS).
84
+ */
85
+ uploadingStatusText: string
86
+ /**
87
+ * Status text when state is Completed (e.g. from CMS). May include file size.
88
+ */
89
+ completedStatusText: string
90
+ /**
91
+ * Custom file name display (optional).
92
+ */
93
+ fileName?: string
94
+ }
95
+
96
+ const FileUploadStatus = ({
97
+ testId = 'fs-file-upload-status',
98
+ file,
99
+ state = FileUploadState.Uploading,
100
+ errorType,
101
+ errorMessage,
102
+ onRemove,
103
+ onSearch,
104
+ onDownloadTemplate,
105
+ onSelectFile,
106
+ removeButtonAriaLabel,
107
+ searchButtonLabel,
108
+ downloadTemplateButtonLabel,
109
+ selectFileButtonLabel,
110
+ errorMessages,
111
+ uploadingStatusText,
112
+ completedStatusText,
113
+ fileName,
114
+ ...otherProps
115
+ }: FileUploadStatusProps) => {
116
+ const getErrorMessage = (): { title: string; description: string } => {
117
+ if (errorMessage) {
118
+ return { title: errorMessage, description: '' }
119
+ }
120
+
121
+ if (errorType && errorMessages?.[errorType]) {
122
+ return errorMessages[errorType]!
123
+ }
124
+
125
+ return (
126
+ errorMessages?.[FileUploadErrorType.Unexpected] ?? {
127
+ title: '',
128
+ description: '',
129
+ }
130
+ )
131
+ }
132
+
133
+ const getStatusText = (): string => {
134
+ switch (state) {
135
+ case FileUploadState.Uploading:
136
+ return uploadingStatusText
137
+ case FileUploadState.Completed:
138
+ return completedStatusText
139
+ default:
140
+ return ''
141
+ }
142
+ }
143
+
144
+ const getIcon = () => {
145
+ switch (state) {
146
+ case FileUploadState.Uploading:
147
+ return (
148
+ <div data-fs-file-upload-status-icon-loading>
149
+ <Icon name="CircleNotch" width={24} height={24} strokeWidth={5} />
150
+ </div>
151
+ )
152
+ case FileUploadState.Completed:
153
+ return (
154
+ <div data-fs-file-upload-status-icon-completed>
155
+ <Icon name="Table" width={24} height={24} strokeWidth={5} />
156
+ </div>
157
+ )
158
+ case FileUploadState.Error:
159
+ return (
160
+ <div data-fs-file-upload-status-icon-error>
161
+ <Icon
162
+ name="WarningOctagon"
163
+ width={24}
164
+ height={24}
165
+ strokeWidth={5}
166
+ />
167
+ </div>
168
+ )
169
+ default:
170
+ return null
171
+ }
172
+ }
173
+
174
+ return (
175
+ <div
176
+ data-fs-file-upload-status
177
+ data-fs-file-upload-status-state={state}
178
+ data-testid={testId}
179
+ {...otherProps}
180
+ >
181
+ <div
182
+ data-fs-file-upload-status-file-info
183
+ data-fs-file-upload-state={state}
184
+ >
185
+ <div data-fs-file-upload-status-icon>{getIcon()}</div>
186
+
187
+ <div data-fs-file-upload-status-details>
188
+ {state === FileUploadState.Error ? (
189
+ <>
190
+ <p data-fs-file-upload-status-text-error>
191
+ {getErrorMessage().title}
192
+ </p>
193
+ <p data-fs-file-upload-status-text-error>
194
+ {getErrorMessage().description}
195
+ </p>
196
+ </>
197
+ ) : (
198
+ <>
199
+ <p data-fs-file-upload-status-filename>{fileName ?? file.name}</p>
200
+ <p data-fs-file-upload-status-text>{getStatusText()}</p>
201
+ </>
202
+ )}
203
+ </div>
204
+
205
+ {onRemove && (
206
+ <Button
207
+ type="button"
208
+ onClick={onRemove}
209
+ data-fs-file-upload-status-remove
210
+ aria-label={removeButtonAriaLabel}
211
+ >
212
+ <Icon name="X" width={16} height={16} />
213
+ </Button>
214
+ )}
215
+ </div>
216
+ {state === FileUploadState.Completed && onSearch && (
217
+ <Button
218
+ type="button"
219
+ variant="primary"
220
+ size="regular"
221
+ onClick={onSearch}
222
+ data-fs-file-upload-status-search-button
223
+ >
224
+ {searchButtonLabel}
225
+ </Button>
226
+ )}
227
+
228
+ {state === FileUploadState.Error && (
229
+ <div data-fs-file-upload-status-error-actions>
230
+ {onDownloadTemplate && (
231
+ <Button
232
+ type="button"
233
+ variant="secondary"
234
+ size="regular"
235
+ onClick={onDownloadTemplate}
236
+ data-fs-file-upload-status-download-button
237
+ >
238
+ {downloadTemplateButtonLabel}
239
+ </Button>
240
+ )}
241
+ {onSelectFile && (
242
+ <Button
243
+ type="button"
244
+ variant="primary"
245
+ size="regular"
246
+ onClick={onSelectFile}
247
+ data-fs-file-upload-status-select-button
248
+ >
249
+ {selectFileButtonLabel}
250
+ </Button>
251
+ )}
252
+ </div>
253
+ )}
254
+ </div>
255
+ )
256
+ }
257
+
258
+ export default FileUploadStatus
@@ -0,0 +1,6 @@
1
+ export {
2
+ default,
3
+ FileUploadState,
4
+ FileUploadErrorType,
5
+ } from './FileUploadStatus'
6
+ export type { FileUploadStatusProps } from './FileUploadStatus'