Record attachments panel with pick-and-upload from files, photos or the camera, per-file progress with retry, list and grid views, and delete confirmation.
import { FileAttachmentPanel, type DocyrusFile, type NativeFile} from '@/components/docyrus-native/file-attachment-panel';async function uploadFile(file: NativeFile, options?: { onProgress: (progress: number) => void }): Promise<DocyrusFile> { const form = new FormData(); form.append('file', { uri: file.uri, name: file.name, type: file.type } as never); // Use XMLHttpRequest (or expo-file-system uploadAsync) to report real progress. return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('POST', `${apiUrl}/v1/apps/base/data-sources/contact/items/${recordId}/files`); xhr.setRequestHeader('Authorization', `Bearer ${token}`); xhr.upload.onprogress = event => options?.onProgress((event.loaded / event.total) * 100); xhr.onload = () => (xhr.status < 300 ? resolve(JSON.parse(xhr.responseText).data) : reject(new Error('Upload failed'))); xhr.onerror = () => reject(new Error('Network error')); xhr.send(form); });}<FileAttachmentPanel files={files} maxFiles={10} maxFileSize={20 * 1024 * 1024} accept={['image/*', 'application/pdf']} onUploadFile={uploadFile} onDeleteFile={id => deleteFile(id)} isDeletePending={deleteMutation.isPending} onValidationError={message => toast.error(message)} />
Behaviour:
Add → source sheet → picker → every file is validated (maxFileSize, accept, remaining maxFiles slots; rejections go to onValidationError) → onUploadFile is called per file in parallel.
Each upload gets a progress row. Without onProgress reports the bar sits at an indeterminate 50% (like web); a finished upload shows a check for 1.5 s. A rejected promise turns the row into an error row with Retry (re-runs onUploadFile with the same file) and Dismiss.
Tapping a file calls onFileOpen, or Linking.openURL(signed_url) when onFileOpen is omitted.
Delete asks through DeleteConfirmDialog (objectName="file"); the dialog closes when onDeleteFile resolves and shows a spinner while isDeletePending.
Uncontrolled view mode starts in grid when any file is an image, else list.
The component moved to a folder; import path @/components/docyrus-native/file-attachment-panel is unchanged.
title default 'Files' → 'Attachments' (web).
maxFileSize now defaults to 50 MB (was unlimited).
uploadingFiles entries changed from { id, name, progress: 0–1 } to the web UploadingFile shape { id, file: NativeFile, progress: 0–100, status, error? }.
Upload failures now render as an error row with Retry / Dismiss and are no longer reported through onValidationError (which now only covers rejected picks).
onDeleteFile is confirmed through DeleteConfirmDialog instead of Alert.alert; the dialog waits for the returned promise.
Tapping a file without onFileOpen now opens signed_url (was a no-op).
Uncontrolled view mode defaults to grid when images are present (was always list).
The Add button now opens a source sheet (files / photos / camera, depending on installed pickers) instead of the document picker directly; it is disabled (not hidden) when maxFiles is reached.
FileAttachmentPanelViewMode and FileAttachmentPickedFile are deprecated aliases of ViewMode / NativeFile.