Phase 9d · Tier B

RN-universal FileUpload pilot

Scaffolded

Drag-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 onNativePickRequest callback.
  • 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 onNativePickRequest hook so consumers wire their own picker.

Semantics

Aspectshadcn (Web File API)RNNotes
Web pickernative <input type="file">native <input type="file"> via castSame browser path; consistent UX
Drag-and-dropDOM events (DragEvent)DOM events on web; no-op on nativeWeb-primary feature
Native pickern/aonNativePickRequest hookConsumer wires expo-document-picker etc.
Validationsize + accept (MIME / extension)size + accept (MIME / extension)Same logic
Image previewURL.createObjectURLURL.createObjectURL on web; uri on nativeNative consumers pass uri in WebFileLike
Uploadcaller-supplied onUploadcaller-supplied onUploadBest-effort progress tick to 90%; complete on resolve
Retryyes (per-entry)yes (per-entry)Same UI