@@ -4,6 +4,28 @@ import classNames from 'classnames';
4
4
import { SecondaryButton } from '@sb1/ffe-buttons-react' ;
5
5
import { Icon } from '@sb1/ffe-icons-react' ;
6
6
7
+ /**
8
+ * Generates a unique filename for iOS camera uploads, ensuring the name isn't too long
9
+ * @param originalFile - Original File object
10
+ * @returns A new File object with a unique, length-constrained name
11
+ */
12
+ const generateUniqueFileName = ( originalFile : File ) : File => {
13
+ // Only handle 'image.jpg' from iOS camera
14
+ if ( originalFile . name !== 'image.jpg' ) {
15
+ return originalFile ;
16
+ }
17
+
18
+ // Use timestamp for uniqueness, no need for additional randomness
19
+ const timestamp = new Date ( ) . getTime ( ) ;
20
+ const newName = `image_${ timestamp } .jpg` ;
21
+
22
+ // Create a new File object with the unique name
23
+ return new File ( [ originalFile ] , newName , {
24
+ type : originalFile . type ,
25
+ lastModified : originalFile . lastModified ,
26
+ } ) ;
27
+ } ;
28
+
7
29
export interface FileUploadProps < Document > {
8
30
/** ID for the input field. The ID is used as a base for the label ID as well. */
9
31
id : string ;
@@ -54,6 +76,45 @@ export interface FileUploadProps<Document> {
54
76
accept ?: string ;
55
77
}
56
78
79
+ /**
80
+ * En komponent for opplasting av filer, som kan brukes til å håndtere dokumentopplasting i ulike formater.
81
+ *
82
+ * Filer kan lastes opp på to måter:
83
+ * 1. Via den native filopplastingsdialogen i nettleseren
84
+ * 2. Ved å dra og slippe filer direkte i opplastingssonen
85
+ *
86
+ * Filhåndteringsflyt:
87
+ * 1. Brukeren velger filer via opplastingsdialog eller drag-and-drop
88
+ * 2. Nettleseren sender event-callback med info om valgte filer
89
+ * 3. Konsumenten må selv hente filinnholdet fra brukerens filsystem
90
+ * 4. Konsumenten oppretter et objekt med filinformasjon som sendes til komponenten
91
+ * 5. Validering av filstørrelse og type må håndteres av konsumenten
92
+ *
93
+ * Files-objektet er indeksert på filnavn og har følgende struktur:
94
+ * @example
95
+ * ```typescript
96
+ * const files = {
97
+ * fileBeingUploaded: {
98
+ * name: 'fileBeingUploaded',
99
+ * },
100
+ * fileWithError: {
101
+ * name: 'fileWithError',
102
+ * error: 'Feil filtype',
103
+ * },
104
+ * fileUploaded: {
105
+ * name: 'fileUploaded',
106
+ * document: {
107
+ * content: '(data)',
108
+ * },
109
+ * },
110
+ * };
111
+ * ```
112
+ *
113
+ * Merk:
114
+ * - Komponenten er tilstandsløs - all filhåndtering må gjøres i foreldrekomponenten
115
+ * - Duplikate filnavn støttes ikke
116
+ * - Ved opplasting fra iOS-kamera omdøpes filer automatisk med timestamp
117
+ */
57
118
export function FileUpload < Document > ( {
58
119
id,
59
120
label,
@@ -80,23 +141,42 @@ export function FileUpload<Document>({
80
141
const handleFilesDropped = ( event : React . DragEvent < HTMLDivElement > ) => {
81
142
event . preventDefault ( ) ;
82
143
setIsHover ( false ) ;
83
- onFilesDropped ( event . dataTransfer . files ) ;
144
+ const processedFiles = processFiles ( event . dataTransfer . files ) ;
145
+ onFilesDropped ( processedFiles ) ;
146
+ } ;
147
+
148
+ const processFiles = ( fileList : FileList | null ) : FileList | null => {
149
+ if ( ! fileList ) return null ;
150
+
151
+ const dataTransfer = new DataTransfer ( ) ;
152
+ Array . from ( fileList ) . forEach ( file => {
153
+ const processedFile = generateUniqueFileName ( file ) ;
154
+ dataTransfer . items . add ( processedFile ) ;
155
+ } ) ;
156
+ return dataTransfer . files ;
84
157
} ;
85
158
86
159
const handleFileDeleted = ( event : React . MouseEvent < HTMLButtonElement > ) => {
87
160
onFileDeleted ( files [ event . currentTarget . id ] ) ;
88
161
} ;
89
162
163
+ const handleKeyDown = ( event : React . KeyboardEvent < HTMLDivElement > ) => {
164
+ if ( event . key === 'Enter' || event . key === ' ' ) {
165
+ event . preventDefault ( ) ;
166
+ triggerUploadFileNativeHandler ( ) ;
167
+ }
168
+ } ;
169
+
90
170
const triggerUploadFileNativeHandler = ( ) => {
91
- // clear file input to trigger onChange when uploading same filename
92
171
if ( fileInputElement . current ) {
93
172
fileInputElement . current . value = '' ;
94
173
fileInputElement . current . click ( ) ;
95
174
}
96
175
} ;
97
176
98
177
const handleFileSelected = ( event : React . ChangeEvent < HTMLInputElement > ) => {
99
- onFilesSelected ( event . target . files ) ;
178
+ const processedFiles = processFiles ( event . target . files ) ;
179
+ onFilesSelected ( processedFiles ) ;
100
180
} ;
101
181
102
182
return (
@@ -124,7 +204,6 @@ export function FileUpload<Document>({
124
204
</ div >
125
205
</ div >
126
206
) }
127
- { /* eslint-disable-next-line jsx-a11y/no-static-element-interactions */ }
128
207
< div
129
208
className = "ffe-file-upload__upload-section"
130
209
onDrop = { handleFilesDropped }
@@ -133,6 +212,10 @@ export function FileUpload<Document>({
133
212
setIsHover ( true ) ;
134
213
} }
135
214
onDragLeave = { ( ) => setIsHover ( false ) }
215
+ role = "button"
216
+ tabIndex = { 0 }
217
+ onKeyDown = { handleKeyDown }
218
+ aria-label = { label }
136
219
>
137
220
< div
138
221
className = { classNames (
0 commit comments