Skip to content

Enhancements to FileChooser#4385

Merged
lbwexler merged 33 commits into
developfrom
upgradeDropzone
Jun 4, 2026
Merged

Enhancements to FileChooser#4385
lbwexler merged 33 commits into
developfrom
upgradeDropzone

Conversation

@lbwexler

@lbwexler lbwexler commented May 20, 2026

Copy link
Copy Markdown
Member

Comprehensive redesign of FileChooser, building on the react-dropzone 15.x upgrade already on develop (via #4381).

Companion Toolbox PR: xh/toolbox#860

Highlights

  • Configuration moved to FileChooserModelaccept, file-size limits, and other options are now set on the model constructor rather than as component props.
  • Empty & single-file states — the empty target is click-anywhere-to-browse, with a prompt and an auto-derived hint summarizing accepted types / size limits. In single-file mode (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.
  • Multi-file state — once files are selected, a persistent drop target sits alongside the grid so users can keep adding files; placement is set via the dropTargetPlacement prop (left / top / hidden), and it disables clearly at the maxFiles limit.
  • Customizable display API — new emptyDisplay / fileDisplay content props replace the former targetText / showFileGrid. Default fileDisplay is a file grid, or the single-file card when maxFiles is 1.
  • New capabilitiesmaxFiles limit (FileChooser should accept a maxFiles prop #3570), onFileAccepted / onFileRejected callbacks, configurable rejection toasts, maskOnDrag / maskOnDisabled, and a programmatic openFileBrowser().
  • API changes — renamed maxSize/minSizemaxFileSize/minFileSize; removed enableMulti/enableAddMulti (use maxFiles);

Also caught the branch up with develop and fixed a filesize v11 named-import bug. See CHANGELOG for breaking-change details.

PeteDarinzo and others added 25 commits November 26, 2024 11:09
# 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>
Base automatically changed from deps/react-dropzone-15 to develop May 20, 2026 19:49
@lbwexler lbwexler changed the title Additional enhancements to upgrade Dropzone Additional enhancements to FileChooser May 20, 2026
@lbwexler lbwexler changed the title Additional enhancements to FileChooser Enhancements to FileChooser May 20, 2026
@amcclain

amcclain commented Jun 2, 2026

Copy link
Copy Markdown
Member

@claude can you catch this branch up with the latest changes on develop, ensure the CHANGELOG and other merge conflicts are reconciled appropriately, then update this pull request with a description of what changed?

@amcclain

amcclain commented Jun 2, 2026

Copy link
Copy Markdown
Member

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).
@amcclain

amcclain commented Jun 3, 2026

Copy link
Copy Markdown
Member

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.
amcclain added 2 commits June 3, 2026 12:29
- 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.
@amcclain

amcclain commented Jun 3, 2026

Copy link
Copy Markdown
Member
CleanShot 2026-06-03 at 15 10 06@2x

amcclain added 2 commits June 3, 2026 15:16
Convert the descriptive line comments above the impl display factories
(multiDropTarget, singleFileDisplay, emptyHint) to JSDoc doc comments.
@@ -1,35 +1,21 @@
/*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We removed the copyright blurb from this file -- do we care about that?

@jskupsik

jskupsik commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Toolbox change: xh/toolbox#860

@lbwexler

lbwexler commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

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.

Comment thread package.json Outdated
Comment thread desktop/cmp/filechooser/FileChooserModel.ts
…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.
@lbwexler lbwexler merged commit b5206a1 into develop Jun 4, 2026
3 checks passed
@lbwexler lbwexler deleted the upgradeDropzone branch June 4, 2026 18:41
lbwexler added a commit to xh/toolbox that referenced this pull request Jun 4, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FileChooser should accept a maxFiles prop

4 participants