Skip to content

Commit 94635d6

Browse files
committed
fix(ffe-file-upload-react): håndter iOS bildenavn og forbedre tilgjengelighet
- Legg til unik navngenerering for iOS kamerabilder som alltid får navnet 'image.jpg' - Fiks ESLint feil med no-shadow ved å endre parameternavn - Forbedre tilgjengelighet med tastatur og skjermleser støtte - Legg til korrekt ARIA-roller og tastaturfunksjonalitet
1 parent 7b65209 commit 94635d6

File tree

1 file changed

+87
-4
lines changed

1 file changed

+87
-4
lines changed

packages/ffe-file-upload-react/src/FileUpload.tsx

+87-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@ import classNames from 'classnames';
44
import { SecondaryButton } from '@sb1/ffe-buttons-react';
55
import { Icon } from '@sb1/ffe-icons-react';
66

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+
729
export interface FileUploadProps<Document> {
830
/** ID for the input field. The ID is used as a base for the label ID as well. */
931
id: string;
@@ -54,6 +76,45 @@ export interface FileUploadProps<Document> {
5476
accept?: string;
5577
}
5678

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+
*/
57118
export function FileUpload<Document>({
58119
id,
59120
label,
@@ -80,23 +141,42 @@ export function FileUpload<Document>({
80141
const handleFilesDropped = (event: React.DragEvent<HTMLDivElement>) => {
81142
event.preventDefault();
82143
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;
84157
};
85158

86159
const handleFileDeleted = (event: React.MouseEvent<HTMLButtonElement>) => {
87160
onFileDeleted(files[event.currentTarget.id]);
88161
};
89162

163+
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
164+
if (event.key === 'Enter' || event.key === ' ') {
165+
event.preventDefault();
166+
triggerUploadFileNativeHandler();
167+
}
168+
};
169+
90170
const triggerUploadFileNativeHandler = () => {
91-
// clear file input to trigger onChange when uploading same filename
92171
if (fileInputElement.current) {
93172
fileInputElement.current.value = '';
94173
fileInputElement.current.click();
95174
}
96175
};
97176

98177
const handleFileSelected = (event: React.ChangeEvent<HTMLInputElement>) => {
99-
onFilesSelected(event.target.files);
178+
const processedFiles = processFiles(event.target.files);
179+
onFilesSelected(processedFiles);
100180
};
101181

102182
return (
@@ -124,7 +204,6 @@ export function FileUpload<Document>({
124204
</div>
125205
</div>
126206
)}
127-
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
128207
<div
129208
className="ffe-file-upload__upload-section"
130209
onDrop={handleFilesDropped}
@@ -133,6 +212,10 @@ export function FileUpload<Document>({
133212
setIsHover(true);
134213
}}
135214
onDragLeave={() => setIsHover(false)}
215+
role="button"
216+
tabIndex={0}
217+
onKeyDown={handleKeyDown}
218+
aria-label={label}
136219
>
137220
<div
138221
className={classNames(

0 commit comments

Comments
 (0)