Repository navigation
[FileUpload, FileDropzone]: add validator, hint, error and success props and report every rejection reason #999
Description
Problem
A form field can show at most one hint and one validation message at the same time. The validation message is either an error or a success, and it comes first, then the hint. The two must not cancel each other out.
FileUpload and FileDropzone don't follow this:
- FileUpload:
- When the component rejects a file, the restrictions hint disappears and only the error remains.
- When a hint is passed through
helper, it renders before the component's error. helperholds a single feedback text, so a hint and an error can't be passed together.- An error passed through
helperturns the border red but doesn't setaria-invalid.
- FileDropzone:
- When the component rejects a file, an error passed through
helperis hidden until the rejection clears. helperholds a single feedback text, as in FileUpload.
- When the component rejects a file, an error passed through
They also only enforce two restrictions: accept and maxSize. Consumers can't add a rule of their own, such as a minimum size, rejecting empty files, or a file naming convention. The only workaround is controlled mode, where the consumer sets isValid: false on files in onChange. Two things are still missing that way:
- The rejection reason doesn't reach the error under the field or the screen-reader announcement. The user sees a danger tag but not why.
- The consumer has to repeat the duplicate and single-file handling that
useFileUploadalready does.
Their messages also lag behind Angular FileUpload:
- Only the first reason is reported. A file that fails both the type and the size check is reported for one of them.
- The texts use "Fail(id)" / "File(s)" for every case, so a single file reads ungrammatically ("Fail(id) 'a.pdf' on liiga suured").
- The screen-reader text for an invalid file is "Faili üleslaadimine ebaõnnestus". That's misleading for a file that was rejected rather than failed to upload.
FileUpload's layout and stories also differ from Figma and Angular FileUpload:
- In the default size, the add button's "Lisa manus" text is 16px. It should be 14px, as in the small size.
- The gap between the clear button and the separator is too large. It should be 8px.
- The stories don't match Angular's. The Disabled, ValidationFailed and ValidationSuccess states are separate stories, and the list differs.
Solution
-
Take the hint, error and success as separate props:
hint: guidance shown after the automatic allowed-types and size hint, in one hint.error: an error of your own, such as a server error, combined with the component's own rejection message into one error.success: shown in the error's place while there is no error.
Keep the restrictions hint visible when there's an error. Render the validation message before the hint. Set
aria-invalidwhenever an error is shown, whatever its source. Decide whetherhelperstays as a deprecated alias forhint. -
Add an optional
validatorprop toUseFileUploadProps, so both components get it:export type FileUploadValidator = (file: File) => string | null | undefined;
It returns the reason a file is rejected, or nothing to accept it.
- It runs inside
useFileUploadon every selected file, including onesacceptormaxSizerejected, so it may receive a file of the wrong type or size. - A file it rejects is handled like a built-in rejection. It gets
isValid: falseand is kept or discarded according tokeepRejectedFiles. - The restrictions hint can't describe the rule, so consumers describe it in
hint. - It only checks files picked after it is set, like
accept. - It is synchronous. Server-side checks stay with the consumer, who sets
isLoadingandisValidon the file. FileDropzoneshould not pass it to react-dropzone's ownvalidatoroption. Running it in the hook sends picked and dropped files through one path.
- It runs inside
-
Report every reason per file in one error:
- Built-in reasons come first, grouped by reason.
- Then each validator reason, followed by the names of the files it rejected. Files failing the same rule are grouped.
- A trailing full stop on a validator reason is trimmed, because the messages are joined with
.. - With
keepRejectedFiles, build the error from the rejected files still listed, so earlier rejections survive later selections.
-
Give the file messages singular and plural forms. Pass the file count to
file-upload.size-rejected,file-upload.extension-rejected,file-upload.duplicates-skippedandfile-upload.added, and pick the form from it. -
Change the
file-upload.failedtexts to "Sobimatu fail" / "Invalid file" / "Недопустимый файл". Keep the key name. -
Fix the FileUpload layout:
- Use the small add button whenever the buttons sit beside the files, whatever
sizeis, so its text is 14px. - Set an 8px gap between the clear button and the separator, with the separator's own spacing removed.
- Use the small add button whenever the buttons sit beside the files, whatever
-
Align the FileUpload stories with Angular:
- Combine Disabled, ValidationFailed and ValidationSuccess into one States story.
- Match the rest of the list and the wording to Angular FileUpload's stories.
Tests should cover:
hint,errorandsuccessrender in the right order, andsuccessis hidden while there's an error.- A file failing several rules gets every reason in the one error.
- Files rejected by the validator are kept, or discarded when
keepRejectedFilesis off. - Files failing the same validator rule are grouped in the error.
- A validator returning
null,undefinedor''accepts the file.
Also add stories for each component and document the new props.
DoD
- The component is developed using best practices, conventions, and modern frontend trends
- The component follows BEM naming conventions (if applicable)
- The component implementation matches the Figma design, including all defined states and interactions
- The component supports responsiveness and scales across breakpoints (if applicable)
- The component does not introduce runtime errors or warnings
- Linter passes
- The component is exported from its tier barrel
- The component has unit tests with at least 80% code coverage (enforced by the global Jest
coverageThresholdoversrc/tedi/components, not per component) - CodeCov passes its threshold (80% on the changed code, with 10% tolerance — a separate gate from the Jest one above)
- The component has passed code review
- The component has passed Chromatic visual review
- Storybook stories are created:
- Cover all Figma variations
- Include edge cases, empty/error states
- Component’s API (props, slots, events) is documented
- ZeroHeight is updated with design/development guidelines
- External dependencies used in the component are documented in Storybook and Zeroheight, with rationale
- Semantic commit format
type(scope): Subject text #issue-refis used (forsemantic-release), with the issue reference required - see Commit convention - External libraries comply with validated open source licenses
- Component is versioned correctly and ready for release if required
- Update the React development status on the statuses page in Zeroheight
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsBacklog
Activity