Enhancements to FileChooser#4385
Conversation
# Conflicts: # CHANGELOG.md # yarn.lock
…solidate display config props, add validate method
Updates FileChooser to the new react-dropzone API:
- `accept` prop now takes the `Accept` object form keyed by MIME type
(e.g. `{'application/pdf': ['.pdf']}`) instead of an extension string/array.
- Drops use of removed `draggedFiles` render-prop field; drag-active message no
longer shows a count.
- Types `onDrop` rejected files as `FileRejection[]`.
Closes #4378.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@claude can you catch this branch up with the latest changes on |
|
Claude was sick today - it needed his token settings adjusted and will be feeling better soon. Still, turns out it wouldn't have been able to do what I asked anyway. Another Claude is working on it now. |
Resolved conflicts in FileChooser.ts and FileChooserModel.ts in favor of the branch's redesign (develop carried only the prior design + react-dropzone 15 bump via #4381, both already incorporated here). Reconciled CHANGELOG: removed duplicate FileChooser/react-dropzone entries from the merge, merged duplicate New Features headers, and corrected the FileChooser breaking-change and feature entries to match the shipping API (accept takes extension strings; maxCount, not maxFiles). Also fixed a filesize v11 named-import in DefaultFileDisplayModel (default import no longer valid; was breaking tsc).
|
Had to run out of the office and forgot to push my work, but I did get this updated locally and reconciled the changelog. Happy to push that tomorrow morning. @jskupsik these changes seem very relevant to your current project we should have a look tomorrow. |
The redesign no longer imports this stylesheet; the empty file failed stylelint's no-empty-source rule.
- Default empty display now shows an upload icon, a single drag-and-drop prompt with an inline "click to browse" link (replacing the standalone button), and an auto-derived hint summarizing accepted types and size/file limits. Long extension lists collapse to a few entries plus a "+N more" suffix (never "+1 more"). - Compose the default prompt and hint as private, model-bound hoistCmp factories; drop the redundant vframe wrapper around the placeholder. - Replace the `emptyDisplayBrowseButton` config with `emptyDisplayHint`. - Rename `maxCount` -> `maxFiles` (tracks the underlying react-dropzone prop) and `FileChooserConf` -> `FileChooserConfig`. - Update CHANGELOG to match.
- In single-file mode (`maxFiles: 1`), a selected file now renders as a compact card (type-specific icon, name, size) that doubles as a click/drop target to replace it, with a Remove action - instead of a one-row grid. - Dropping a file in single-file mode replaces the current selection rather than being rejected as over-limit. - Empty state is now click-anywhere-to-browse: the whole surface is the target (plain prompt text, no separate link) with a pointer cursor. - Renamed the `emptyDisplayText` config to `emptyDisplayPrompt`. - Empty-state hint is parenthesized, spaced from the prompt, and now genuinely smaller (the size limit hint drops the redundant single-file phrasing). - Fixed `onDropAsync` treating an explicit `maxFiles: null` as a zero limit, and fixed hint font-size using the unitless `--xh-font-size-small` instead of the `-px` length variant.
… fixes - Multi-file selections now keep a persistent drop target alongside the grid so users can continue adding files. Placement is set via the new `dropTargetPlacement` prop (`left` | `top` | `hidden`), and the target disables with a clear message once `maxFiles` is reached. - In multi-file mode the dropzone root wraps only the target rail, so the grid no longer participates in drag / click. - Corrected the class JSDoc, which described a removed children/`items` customization, and clarified that `fileDisplay` renders alongside the framework-owned multi-file target. - CHANGELOG updated.
Convert the descriptive line comments above the impl display factories (multiDropTarget, singleFileDisplay, emptyHint) to JSDoc doc comments.
| @@ -1,35 +1,21 @@ | |||
| /* | |||
There was a problem hiding this comment.
We removed the copyright blurb from this file -- do we care about that?
|
Toolbox change: xh/toolbox#860 |
|
Looks really great. Minor nit on the toolbox display side -- think it would benefit from having the controls to modify it displayed as options on the right. So just super obvious that they are not part of the control, which would not typically have a toolbar. Not a showstopper, but when I was interacting with the toolbar, I would click off of the picker or select controls to dismiss, and then the file chooser would capture that click and come up. That seems like non-optimal behavior. In most configurations that less likely to be an issue, but with the toolbar above it certainly is. I think we could make the clickable surface limited to just the placeholder icon/text area in center or provide an explicit link. |
…4408) Follow-up to the FileChooser redesign (#4385), addressing code-review feedback. Targets `upgradeDropzone` so the diff is just these refinements. ### Changes - **Reject-message crash fix** — use `file.name` instead of the optional `file.handle.name`. `handle` is only populated on the drag-drop path in secure-context Chromium; it's `undefined` on click-to-browse and in Firefox/Safari, where the default reject toast would otherwise throw. - **Dropped `validateFilesAsync`** — and reverted the async `onDropAsync` back to a synchronous `onDrop` (the validation hook was its only `await`). Also corrected the `rejected` param type from `Partial<FileRejection[]>` to `FileRejection[]`. - **`addFilesInternal` shared add path** — de-dupes by name, then enforces `maxFiles` on the *de-duped* result, so re-adding an already-selected file isn't double-counted. Public `addFiles` now respects the limit (and is documented as not enforcing `accept`/file-size, which only the dropzone applies). - **Simplified `accept`** — removed the `mime` dependency and the extension→MIME mapping (the source of the same-MIME-collapse and `'null'`-key bugs). The model stores extensions directly; the react-dropzone `{type: [exts]}` map is built on the fly in the component. Documented as extensions-only. - **Import cycle** — impl files now import `FileChooserModel` relatively instead of via the package barrel. - Updated class doc and CHANGELOG to match. ### Notes - `accept` is intentionally extensions-only; MIME/wildcard support can be added later as a non-breaking change if needed. - `tsc` clean; pre-commit prettier/eslint/tsc hooks pass.
Companion to the `FileChooser` redesign in xh/hoist-react#4385 - updates the Toolbox examples to the new `FileChooserModel` config API and demonstrates the new display options. - Updated the **FileChooser** example (`Other › FileChooser`) and the **File Manager** example to the redesigned API (model-config `accept` / `maxFiles` / `maxFileSize`, `onDropAsync`, and the `emptyDisplay` / `fileDisplay` content props). - Added live controls to the FileChooser example for accepted types (picker), max files, max file size, and the multi-file drop-target placement (`dropTargetPlacement`) - re-creating the chooser on config change. - Added a second single-image example using a custom `fileDisplay` that previews the selected PNG in place, with a replace / clear footer. Should be merged in coordination with xh/hoist-react#4385, as the examples consume the new API. --------- Co-authored-by: petedarinzo <peter.darinzo@gmail.com> Co-authored-by: lbwexler <lbwexler@xh.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Jakub Kupsik <jskupsik@gmail.com>

Comprehensive redesign of
FileChooser, building on the react-dropzone15.xupgrade already ondevelop(via #4381).Companion Toolbox PR: xh/toolbox#860
Highlights
FileChooserModel—accept, file-size limits, and other options are now set on the model constructor rather than as component props.maxFiles: 1) a selected file renders as a compact card (type-specific icon, name, size) that doubles as a replace target plus a remove action, rather than a one-row grid.dropTargetPlacementprop (left/top/hidden), and it disables clearly at themaxFileslimit.emptyDisplay/fileDisplaycontent props replace the formertargetText/showFileGrid. DefaultfileDisplayis a file grid, or the single-file card whenmaxFilesis 1.maxFileslimit (FileChoosershould accept amaxFilesprop #3570),onFileAccepted/onFileRejectedcallbacks, configurable rejection toasts,maskOnDrag/maskOnDisabled, and a programmaticopenFileBrowser().maxSize/minSize→maxFileSize/minFileSize; removedenableMulti/enableAddMulti(usemaxFiles);Also caught the branch up with
developand fixed afilesizev11 named-import bug. See CHANGELOG for breaking-change details.