@@ -54,6 +54,66 @@ export interface FileUploadProps<Document> {
54
54
accept ?: string ;
55
55
}
56
56
57
+ /**
58
+ * En komponent for opplasting av filer, som kan brukes til å håndtere dokumentopplasting i ulike formater.
59
+ *
60
+ * Filer kan lastes opp på to måter:
61
+ * 1. Via den native filopplastingsdialogen i nettleseren
62
+ * 2. Ved å dra og slippe filer direkte i opplastingssonen
63
+ *
64
+ * Filhåndteringsflyt:
65
+ * 1. Brukeren velger filer via opplastingsdialog eller drag-and-drop
66
+ * 2. Nettleseren sender event-callback med info om valgte filer
67
+ * 3. Konsumenten må selv hente filinnholdet fra brukerens filsystem
68
+ * 4. Konsumenten oppretter et objekt med filinformasjon som sendes til komponenten
69
+ * 5. Validering av filstørrelse og type må håndteres av konsumenten
70
+ *
71
+ * Files-objektet er indeksert på filnavn og har følgende struktur:
72
+ * @example
73
+ * ```typescript
74
+ * const files = {
75
+ * fileBeingUploaded: {
76
+ * name: 'fileBeingUploaded',
77
+ * },
78
+ * fileWithError: {
79
+ * name: 'fileWithError',
80
+ * error: 'Feil filtype',
81
+ * },
82
+ * fileUploaded: {
83
+ * name: 'fileUploaded',
84
+ * document: {
85
+ * content: '(data)',
86
+ * },
87
+ * },
88
+ * };
89
+ * ```
90
+ *
91
+ * Merk:
92
+ * - Komponenten er tilstandsløs - all filhåndtering må gjøres i foreldrekomponenten
93
+ * - Duplikate filnavn støttes ikke
94
+ * - Ved opplasting fra iOS-kamera må filnavnhåndtering implementeres i konsumenten
95
+ *
96
+ * @template Document - Type for dokumentobjektet som kan legges til i files-objektet
97
+ * @param {Object } props - Komponentens props
98
+ * @param {string } props.id - ID for input-feltet
99
+ * @param {string } props.label - Tekst på knappen for filopplasting
100
+ * @param {Record<string, FileItemProps<Document>['file']> } props.files - Objekt med filer indeksert på filnavn
101
+ * @param {string } [props.cancelText] - Tekst på avbryt-knappen
102
+ * @param {string } [props.deleteText] - Tekst på slett-knappen
103
+ * @param {boolean } [props.multiple] - Om flere filer kan lastes opp samtidig
104
+ * @param {string } props.title - Tittel på modulen
105
+ * @param {string } props.infoText - Tekst i info-seksjonen
106
+ * @param {string } [props.infoSubText] - Undertekst i info-seksjonen
107
+ * @param {string } props.uploadTitle - Tittel på opplastingsseksjonen
108
+ * @param {string } props.uploadMicroText - Mikrotekst i opplastingsseksjonen
109
+ * @param {string } props.uploadSubText - Undertekst i opplastingsseksjonen
110
+ * @param {string } [props.accept] - Filtypespesifikasjon (f.eks. ".pdf")
111
+ * @param {(fileList: FileList | null) => void } props.onFilesDropped - Callback når filer droppes
112
+ * @param {(file: FileItemProps<Document>['file']) => void } props.onFileDeleted - Callback når en fil slettes
113
+ * @param {(fileList: FileList | null) => void } props.onFilesSelected - Callback når filer velges
114
+ *
115
+ * @returns {JSX.Element } FileUpload-komponenten
116
+ */
57
117
export function FileUpload < Document > ( {
58
118
id,
59
119
label,
@@ -77,6 +137,20 @@ export function FileUpload<Document>({
77
137
const downloadIcon =
78
138
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgLTk2MCA5NjAgOTYwIiB3aWR0aD0iMjQiPjxwYXRoIGQ9Ik00ODAtMzQzLjUzOXEtNy4yMzEgMC0xMy40NjEtMi4zMDgtNi4yMzEtMi4zMDctMTEuODQ2LTcuOTIzTDMzMC4zMDktNDc4LjE1M3EtOC45MjMtOC45MjMtOC44MDctMjAuODg0LjExNS0xMS45NjEgOC44MDctMjEuMjY5IDkuMzA4LTkuMzA3IDIxLjM4NC05LjYxNSAxMi4wNzctLjMwOCAyMS4zODUgOWw3Ni45MjMgNzYuOTIzdi0zMDYuMDAxcTAtMTIuNzY5IDguNjE1LTIxLjM4NCA4LjYxNS04LjYxNiAyMS4zODQtOC42MTZ0MjEuMzg0IDguNjE2cTguNjE1IDguNjE1IDguNjE1IDIxLjM4NHYzMDYuMDAxbDc2LjkyMy03Ni45MjNxOC45MjMtOC45MjMgMjEuMTkyLTguODA4IDEyLjI2OS4xMTYgMjEuNTc3IDkuNDIzIDguNjkyIDkuMzA4IDguOTk5IDIxLjA3Ny4zMDggMTEuNzY5LTguOTk5IDIxLjA3Nkw1MDUuMzA3LTM1My43N3EtNS42MTUgNS42MTYtMTEuODQ2IDcuOTIzLTYuMjMgMi4zMDgtMTMuNDYxIDIuMzA4Wk0yNTIuMzA5LTE4MC4wMDFxLTMwLjMwOCAwLTUxLjMwOC0yMXQtMjEtNTEuMzA4di03OC40NjFxMC0xMi43NjkgOC42MTYtMjEuMzg0IDguNjE1LTguNjE1IDIxLjM4NC04LjYxNXQyMS4zODQgOC42MTVRMjQwLTM0My41MzkgMjQwLTMzMC43N3Y3OC40NjFxMCA0LjYxNiAzLjg0NiA4LjQ2MyAzLjg0NyAzLjg0NiA4LjQ2MyAzLjg0Nmg0NTUuMzgycTQuNjE2IDAgOC40NjMtMy44NDYgMy44NDYtMy44NDcgMy44NDYtOC40NjN2LTc4LjQ2MXEwLTEyLjc2OSA4LjYxNS0yMS4zODR0MjEuMzg0LTguNjE1cTEyLjc2OSAwIDIxLjM4NCA4LjYxNSA4LjYxNiA4LjYxNSA4LjYxNiAyMS4zODR2NzguNDYxcTAgMzAuMzA4LTIxIDUxLjMwOHQtNTEuMzA4IDIxSDI1Mi4zMDlaIi8+PC9zdmc+' ;
79
139
140
+ const triggerUploadFileNativeHandler = ( ) => {
141
+ if ( fileInputElement . current ) {
142
+ fileInputElement . current . value = '' ;
143
+ fileInputElement . current . click ( ) ;
144
+ }
145
+ } ;
146
+
147
+ const handleKeyDown = ( event : React . KeyboardEvent < HTMLDivElement > ) => {
148
+ if ( event . key === 'Enter' || event . key === ' ' ) {
149
+ event . preventDefault ( ) ;
150
+ triggerUploadFileNativeHandler ( ) ;
151
+ }
152
+ } ;
153
+
80
154
const handleFilesDropped = ( event : React . DragEvent < HTMLDivElement > ) => {
81
155
event . preventDefault ( ) ;
82
156
setIsHover ( false ) ;
@@ -87,14 +161,6 @@ export function FileUpload<Document>({
87
161
onFileDeleted ( files [ event . currentTarget . id ] ) ;
88
162
} ;
89
163
90
- const triggerUploadFileNativeHandler = ( ) => {
91
- // clear file input to trigger onChange when uploading same filename
92
- if ( fileInputElement . current ) {
93
- fileInputElement . current . value = '' ;
94
- fileInputElement . current . click ( ) ;
95
- }
96
- } ;
97
-
98
164
const handleFileSelected = ( event : React . ChangeEvent < HTMLInputElement > ) => {
99
165
onFilesSelected ( event . target . files ) ;
100
166
} ;
@@ -124,7 +190,6 @@ export function FileUpload<Document>({
124
190
</ div >
125
191
</ div >
126
192
) }
127
- { /* eslint-disable-next-line jsx-a11y/no-static-element-interactions */ }
128
193
< div
129
194
className = "ffe-file-upload__upload-section"
130
195
onDrop = { handleFilesDropped }
@@ -133,6 +198,10 @@ export function FileUpload<Document>({
133
198
setIsHover ( true ) ;
134
199
} }
135
200
onDragLeave = { ( ) => setIsHover ( false ) }
201
+ role = "button"
202
+ tabIndex = { 0 }
203
+ onKeyDown = { handleKeyDown }
204
+ aria-label = { label }
136
205
>
137
206
< div
138
207
className = { classNames (
0 commit comments