Introduction
Components
- Accordion
- Action Sheet
- Alert
- Audio Player
- Audio Recorder
- Audio Waveform
- Avatar
- Badge
- BottomSheet
- Button
- Camera
- Camera Preview
- Card
- Carousel
- Checkbox
- Collapsible
- Color Picker
- Combobox
- Date Picker
- File Picker
- Gallery
- Hello Wave
- Icon
- Image
- Input
- Input OTP
- Link
- MediaPicker
- Mode Toggle
- Onboarding
- ParallaxScrollView
- Picker
- Popover
- Progress
- Radio
- ScrollView
- SearchBar
- Separator
- Share
- Sheet
- Skeleton
- Spinner
- Switch
- Table
- Tabs
- Text
- Toast
- Toggle
- Video
- View
Charts
import { FilePicker } from '@/components/ui/file-picker';
import React from 'react';
export function FilePickerDemo() {
return (
<FilePicker
onFilesSelected={(files) => console.log('Selected files:', files)}
onError={(error) => console.error('Error:', error)}
fileType='all'
multiple={true}
maxFiles={5}
placeholder='Select your files'
showFileInfo={true}
/>
);
}
Installation
pnpm dlx bna-ui add file-picker
Usage
import { FilePicker } from '@/components/ui/file-picker';
<FilePicker
onFilesSelected={(files) => console.log('Selected files:', files)}
onError={(error) => console.error('Error:', error)}
fileType='all'
multiple={true}
maxFiles={5}
placeholder='Select your files'
/>
Examples
Default
import { FilePicker } from '@/components/ui/file-picker';
import React from 'react';
export function FilePickerDemo() {
return (
<FilePicker
onFilesSelected={(files) => console.log('Selected files:', files)}
onError={(error) => console.error('Error:', error)}
fileType='all'
multiple={true}
maxFiles={5}
placeholder='Select your files'
showFileInfo={true}
/>
);
}
Image Only
import { FilePicker, SelectedFile } from '@/components/ui/file-picker';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';
export function FilePickerImages() {
const [selectedFiles, setSelectedFiles] = useState<SelectedFile[]>([]);
return (
<View style={{ gap: 12 }}>
<FilePicker
onFilesSelected={setSelectedFiles}
onError={(error) => console.error('Error:', error)}
fileType='image'
multiple={true}
maxFiles={3}
maxSizeBytes={5 * 1024 * 1024} // 5MB
allowedExtensions={['jpg', 'jpeg', 'png', 'gif', 'webp']}
placeholder='Select images (max 3)'
showFileInfo={true}
/>
{selectedFiles.length > 0 && (
<Text style={{ fontSize: 14, opacity: 0.7 }}>
{selectedFiles.length} image{selectedFiles.length > 1 ? 's' : ''}{' '}
selected
</Text>
)}
</View>
);
}
Single File
import { FilePicker, SelectedFile } from '@/components/ui/file-picker';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';
export function FilePickerSingle() {
const [selectedFile, setSelectedFile] = useState<SelectedFile[]>([]);
return (
<View style={{ gap: 12 }}>
<FilePicker
onFilesSelected={setSelectedFile}
onError={(error) => console.error('Error:', error)}
fileType='document'
multiple={false}
maxFiles={1}
maxSizeBytes={2 * 1024 * 1024} // 2MB
placeholder='Select a document'
showFileInfo={true}
/>
{selectedFile.length > 0 && (
<View style={{ padding: 12, borderRadius: 8 }}>
<Text style={{ fontWeight: '500' }}>Selected File:</Text>
<Text style={{ fontSize: 14 }}>{selectedFile[0].name}</Text>
{selectedFile[0].size && (
<Text style={{ fontSize: 12, opacity: 0.7 }}>
{(selectedFile[0].size / 1024).toFixed(1)} KB
</Text>
)}
</View>
)}
</View>
);
}
With Validation
import { FilePicker, SelectedFile } from '@/components/ui/file-picker';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';
export function FilePickerValidation() {
const [error, setError] = useState('');
const [files, setFiles] = useState<SelectedFile[]>([]);
return (
<View style={{ gap: 12 }}>
<FilePicker
onFilesSelected={(files) => {
setFiles(files);
setError('');
}}
onError={setError}
fileType='all'
multiple={true}
maxFiles={2}
maxSizeBytes={1 * 1024 * 1024} // 1MB limit
allowedExtensions={['pdf', 'doc', 'docx', 'txt']}
placeholder='Select (PDF, DOC, DOCX, TXT only)'
showFileInfo={true}
/>
{error && (
<View
style={{
padding: 12,
backgroundColor: '#ffeaea',
borderRadius: 8,
borderWidth: 1,
borderColor: '#ffcaca',
}}
>
<Text style={{ color: '#d32f2f', fontSize: 14 }}>{error}</Text>
</View>
)}
{files.length > 0 && !error && (
<View
style={{
padding: 12,
backgroundColor: '#e8f5e8',
borderRadius: 8,
borderWidth: 1,
borderColor: '#c8e6c9',
}}
>
<Text style={{ color: '#2e7d32', fontSize: 14, fontWeight: '500' }}>
✓ Files validated successfully
</Text>
<Text style={{ color: '#2e7d32', fontSize: 12, marginTop: 4 }}>
{files.length} file{files.length > 1 ? 's' : ''} ready for upload
</Text>
</View>
)}
</View>
);
}
Custom Styling
import { FilePicker } from '@/components/ui/file-picker';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';
export function FilePickerStyled() {
return (
<View style={{ gap: 16 }}>
{/* Primary Style */}
<View>
<Text style={{ marginBottom: 8, fontWeight: '500' }}>
Primary Style
</Text>
<FilePicker
variant='ghost'
onFilesSelected={(files) => console.log('Primary files:', files)}
onError={(error) => console.error('Error:', error)}
fileType='all'
multiple={true}
maxFiles={3}
placeholder='Upload files'
style={{
borderWidth: 2,
borderColor: '#007AFF',
borderRadius: 12,
// backgroundColor: '#f0f8ff',
}}
/>
</View>
{/* Minimal Style */}
<View>
<Text style={{ marginBottom: 8, fontWeight: '500' }}>
Minimal Style
</Text>
<FilePicker
variant='ghost'
onFilesSelected={(files) => console.log('Minimal files:', files)}
onError={(error) => console.error('Error:', error)}
fileType='image'
multiple={false}
maxFiles={1}
placeholder='Choose image'
style={{
borderWidth: 1,
borderStyle: 'dashed',
borderColor: '#ccc',
borderRadius: 8,
backgroundColor: 'transparent',
}}
/>
</View>
{/* Success Style */}
<View>
<Text style={{ marginBottom: 8, fontWeight: '500' }}>
Success Style
</Text>
<FilePicker
variant='ghost'
onFilesSelected={(files) => console.log('Success files:', files)}
onError={(error) => console.error('Error:', error)}
fileType='document'
multiple={true}
maxFiles={5}
placeholder='Select documents'
style={{
borderWidth: 2,
borderColor: '#34C759',
borderRadius: 16,
// backgroundColor: '#f0fff4',
}}
/>
</View>
</View>
);
}
Controlled
import { Button } from '@/components/ui/button';
import { useFilePicker } from '@/components/ui/file-picker';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';
export function FilePickerControlled() {
const {
files,
addFiles,
removeFile,
clearFiles,
totalSize,
isValid,
errors,
} = useFilePicker({
maxFiles: 3,
maxSizeBytes: 2 * 1024 * 1024, // 2MB
allowedExtensions: ['pdf', 'jpg', 'png', 'doc'],
onError: (error) => console.error('Validation error:', error),
});
const formatSize = (bytes: number) => {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
};
// Simulate adding files (in real app, this would come from file picker)
const simulateAddFiles = () => {
const mockFiles = [
{ uri: 'file://test1.pdf', name: 'test1.pdf', size: 150000 },
{ uri: 'file://test2.jpg', name: 'test2.jpg', size: 250000 },
];
addFiles(mockFiles);
};
return (
<View style={{ gap: 16 }}>
<View style={{ flexDirection: 'row', gap: 8 }}>
<Button
onPress={simulateAddFiles}
style={{ flex: 1 }}
variant='outline'
>
<Text>Add Mock Files</Text>
</Button>
<Button
onPress={clearFiles}
style={{ flex: 1 }}
variant='outline'
disabled={files.length === 0}
>
<Text>Clear All</Text>
</Button>
</View>
{/* Status Info */}
<View
style={{
padding: 12,
borderRadius: 8,
}}
>
<Text style={{ fontWeight: '500' }}>Status</Text>
<Text style={{ fontSize: 14 }}>Files: {files.length}/3</Text>
<Text style={{ fontSize: 14 }}>
Total Size: {formatSize(totalSize)}
</Text>
<Text style={{ fontSize: 14 }}>Valid: {isValid ? '✓' : '✗'}</Text>
</View>
{/* Errors */}
{errors.length > 0 && (
<View
style={{
padding: 12,
backgroundColor: '#ffeaea',
borderRadius: 8,
}}
>
<Text style={{ color: '#d32f2f', fontWeight: '500' }}>Errors:</Text>
{errors.map((error, index) => (
<Text key={index} style={{ color: '#d32f2f', fontSize: 14 }}>
• {error}
</Text>
))}
</View>
)}
{/* Files List */}
{files.length > 0 && (
<View>
<Text style={{ fontWeight: '500', marginBottom: 8 }}>
Selected Files:
</Text>
{files.map((file, index) => (
<View
key={index}
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 8,
borderRadius: 6,
marginBottom: 4,
borderWidth: 1,
borderColor: '#e0e0e0',
}}
>
<View style={{ flex: 1 }}>
<Text style={{ fontSize: 14, fontWeight: '500' }}>
{file.name}
</Text>
{file.size && (
<Text style={{ fontSize: 12, opacity: 0.7 }}>
{formatSize(file.size)}
</Text>
)}
</View>
<Button
onPress={() => removeFile(index)}
variant='ghost'
style={{ padding: 4 }}
>
<Text style={{ color: '#d32f2f' }}>Remove</Text>
</Button>
</View>
))}
</View>
)}
</View>
);
}
With File Info
import { FilePicker, SelectedFile } from '@/components/ui/file-picker';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useThemeColor } from '@/hooks/useThemeColor';
import React, { useState } from 'react';
export function FilePickerInfo() {
const card = useThemeColor({}, 'card');
const [files, setFiles] = useState<SelectedFile[]>([]);
const [uploadProgress, setUploadProgress] = useState<any>({});
const handleFilesSelected = (selectedFiles: SelectedFile[]) => {
setFiles(selectedFiles);
// Simulate upload progress
selectedFiles.forEach((file, index) => {
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 20;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
}
setUploadProgress((prev: any) => ({
...prev,
[index]: Math.min(progress, 100),
}));
}, 200);
});
};
const formatFileSize = (bytes: number) => {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
};
const getFileTypeIcon = (fileName: string) => {
const ext = fileName.split('.').pop()?.toLowerCase() || '';
const typeMap: Record<string, string> = {
pdf: '📄',
doc: '📝',
docx: '📝',
txt: '📄',
jpg: '🖼️',
jpeg: '🖼️',
png: '🖼️',
gif: '🖼️',
zip: '📦',
rar: '📦',
mp4: '🎥',
mp3: '🎵',
};
return typeMap[ext] || '📎';
};
return (
<View style={{ gap: 16 }}>
<FilePicker
onFilesSelected={handleFilesSelected}
onError={(error) => console.error('Error:', error)}
fileType='all'
multiple={true}
maxFiles={4}
maxSizeBytes={5 * 1024 * 1024} // 5MB
placeholder='Select files for detailed preview'
showFileInfo={true}
/>
{files.length > 0 && (
<View>
<Text style={{ fontSize: 16, fontWeight: '600', marginBottom: 12 }}>
File Details
</Text>
{files.map((file, index) => (
<View
key={index}
style={{
padding: 16,
backgroundColor: card,
borderRadius: 12,
marginBottom: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 2,
}}
>
<View
style={{
flexDirection: 'row',
alignItems: 'flex-start',
gap: 12,
}}
>
<Text style={{ fontSize: 24 }}>
{getFileTypeIcon(file.name)}
</Text>
<View style={{ flex: 1 }}>
<Text
style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}
>
{file.name}
</Text>
<View style={{ gap: 2 }}>
{file.size && (
<Text variant='caption' style={{ fontSize: 14 }}>
Size: {formatFileSize(file.size)}
</Text>
)}
{file.mimeType && (
<Text variant='caption' style={{ fontSize: 14 }}>
Type: {file.mimeType}
</Text>
)}
<Text variant='caption' style={{ fontSize: 14 }}>
Status:{' '}
{uploadProgress[index] >= 100
? 'Uploaded'
: 'Uploading...'}
</Text>
</View>
{/* Progress Bar */}
{uploadProgress[index] !== undefined && (
<View style={{ marginTop: 8 }}>
<View
style={{
height: 4,
backgroundColor: '#e0e0e0',
borderRadius: 2,
overflow: 'hidden',
}}
>
<View
style={{
height: '100%',
width: `${uploadProgress[index] || 0}%`,
backgroundColor:
uploadProgress[index] >= 100
? '#4CAF50'
: '#2196F3',
borderRadius: 2,
}}
/>
</View>
<Text
variant='caption'
style={{ fontSize: 12, marginTop: 4 }}
>
{Math.round(uploadProgress[index] || 0)}%
</Text>
</View>
)}
</View>
</View>
</View>
))}
{/* Summary */}
<View
style={{
padding: 12,
backgroundColor: '#f0f8ff',
borderRadius: 8,
borderWidth: 1,
borderColor: '#e3f2fd',
}}
>
<Text style={{ fontSize: 14, fontWeight: '500', color: '#1976d2' }}>
Summary
</Text>
<Text style={{ fontSize: 14, color: '#1976d2' }}>
{files.length} file{files.length > 1 ? 's' : ''} • Total size:{' '}
{formatFileSize(
files.reduce((sum, file) => sum + (file.size || 0), 0)
)}
</Text>
</View>
</View>
)}
</View>
);
}
API Reference
FilePicker
The main file picker component.
Prop | Type | Default | Description |
---|---|---|---|
onFilesSelected | (files: SelectedFile[]) => void | - | Callback when files are selected. |
onError? | (error: string) => void | - | Callback when an error occurs. |
fileType? | 'image' | 'document' | 'all' | 'all' | Type of files to allow. |
multiple? | boolean | false | Whether to allow multiple file selection. |
maxFiles? | number | 10 | Maximum number of files to select. |
maxSizeBytes? | number | 10MB | Maximum file size in bytes. |
allowedExtensions? | string[] | - | Array of allowed file extensions. |
placeholder? | string | 'Select files' | Placeholder text for the picker button. |
disabled? | boolean | false | Whether the picker is disabled. |
style? | ViewStyle | - | Additional styles for the container. |
showFileInfo? | boolean | true | Whether to show file size information. |
accessibilityLabel? | string | - | Accessibility label for the picker button. |
accessibilityHint? | string | - | Accessibility hint for the picker button. |
SelectedFile
The file object structure returned by the component.
Property | Type | Description |
---|---|---|
uri | string | The file URI. |
name | string | The file name. |
type? | string | The file MIME type. |
size? | number | The file size in bytes. |
mimeType? | string | The file MIME type. |
useFilePicker Hook
A hook for managing file picker state programmatically.
const { files, addFiles, removeFile, clearFiles, totalSize, isValid, errors } =
useFilePicker({
maxFiles: 5,
maxSizeBytes: 5 * 1024 * 1024, // 5MB
allowedExtensions: ['pdf', 'doc', 'docx'],
onError: (error) => console.error(error),
});
Options
Property | Type | Default | Description |
---|---|---|---|
maxFiles? | number | 10 | Maximum number of files. |
maxSizeBytes? | number | 10MB | Maximum file size in bytes. |
allowedExtensions? | string[] | - | Array of allowed file extensions. |
onError? | (error: string) => void | - | Callback when an error occurs. |
Return Value
Property | Type | Description |
---|---|---|
files | SelectedFile[] | Array of selected files. |
addFiles | (files: SelectedFile[]) => void | Function to add files. |
removeFile | (index: number) => void | Function to remove a file by index. |
clearFiles | () => void | Function to clear all files. |
totalSize | number | Total size of all files in bytes. |
isValid | boolean | Whether the current state is valid. |
errors | string[] | Array of validation errors. |
Utility Functions
createFileFromUri
const file = await createFileFromUri(uri, 'custom-name.pdf');
validateFiles
const { valid, errors } = validateFiles(files, {
maxSize: 5 * 1024 * 1024,
allowedExtensions: ['pdf', 'doc'],
maxFiles: 3,
});
File Types
The component supports three file type modes:
'all'
- All file types (default)'image'
- Images only (jpg, jpeg, png, gif, webp, etc.)'document'
- All file types with document picker
Validation
The FilePicker includes built-in validation for:
- File size - Configurable maximum size per file
- File extensions - Whitelist of allowed extensions
- File count - Maximum number of files
- MIME types - Automatic validation based on file type
Accessibility
The FilePicker component is built with accessibility in mind:
- Proper accessibility labels and hints
- Screen reader support for file information
- Keyboard navigation support
- Clear error messaging
- Semantic button structure
Best Practices
- Set appropriate file size limits to prevent memory issues
- Use specific file type restrictions when possible
- Provide clear error messages to guide users
- Show file previews when relevant
- Handle loading states for better UX
- Validate files on both client and server side