Phase 9d · Tier B
RN-universal FileUpload pilot
ScaffoldedDrag-and-drop file picker with image previews, per-file progress, MIME and size validation, and retry. Web-primary — drag-and-drop uses standard DOM events; native uses onNativePickRequest to wire consumer-supplied pickers (no expo-document-picker peer dep).
Side-by-side: single PNG/JPEG, max 5 MB
shadcn (Web File API)
Click to upload or drag and drop
PNG, JPEG up to 5.0 MB
PNG/JPEG up to 5 MB. Drag a file or click to browse.
RN (web-primary)
ID document
PNG/JPEG up to 5 MB. Drag a file or click to browse.
Multi-file mode
Upload several files at once. Each entry tracks its own progress and error state independently.
Supporting documents
PNG/JPEG/PDF up to 10 MB each. The fake upload fails ~25% of the time to demo retry.
Library decision
Per Phase 9 Q2, every Tier B primitive must record its library choice. RnFileUpload ships with no new runtime dependency.
- expo-document-picker@55.0.13 (MIT, ~904 k/wk) — evaluated and rejected. Adding a peer dep that's only useful for Expo consumers contradicts the Phase 9d pattern. Consumers who need the native picker import expo-document-picker themselves and wire results through the
onNativePickRequestcallback. - react-native-document-picker (~480 k/wk, MIT) — same blocker.
- react-native-image-picker (~770 k/wk, MIT) — image-only; narrower than this primitive.
- This implementation — web-primary; drag-and- drop via standard DOM events; native exposes the
onNativePickRequesthook so consumers wire their own picker.
Semantics
| Aspect | shadcn (Web File API) | RN | Notes |
|---|---|---|---|
| Web picker | native <input type="file"> | native <input type="file"> via cast | Same browser path; consistent UX |
| Drag-and-drop | DOM events (DragEvent) | DOM events on web; no-op on native | Web-primary feature |
| Native picker | n/a | onNativePickRequest hook | Consumer wires expo-document-picker etc. |
| Validation | size + accept (MIME / extension) | size + accept (MIME / extension) | Same logic |
| Image preview | URL.createObjectURL | URL.createObjectURL on web; uri on native | Native consumers pass uri in WebFileLike |
| Upload | caller-supplied onUpload | caller-supplied onUpload | Best-effort progress tick to 90%; complete on resolve |
| Retry | yes (per-entry) | yes (per-entry) | Same UI |