AniUI

File Picker

File upload UI with dashed border, file preview, and remove button.

Installation#

npx @aniui/cli add file-picker
Web preview — components render natively on iOS & Android
import { FilePicker, type FileInfo } from "@/components/ui/file-picker";
import * as DocumentPicker from "expo-document-picker";

export function MyScreen() {
  const [file, setFile] = useState<FileInfo | undefined>();

  const pickFile = async () => {
    const result = await DocumentPicker.getDocumentAsync({});
    if (result.assets?.[0]) {
      const asset = result.assets[0];
      setFile({ name: asset.name, size: asset.size, uri: asset.uri });
    }
  };

  return (
    <FilePicker
      file={file}
      onPress={pickFile}
      onRemove={() => setFile(undefined)}
      label="Tap to upload a document"
    />
  );
}

Usage#

app/index.tsx
import { FilePicker, type FileInfo } from "@/components/ui/file-picker";
import * as DocumentPicker from "expo-document-picker";

export function MyScreen() {
  const [file, setFile] = useState<FileInfo | undefined>();

  const pickFile = async () => {
    const result = await DocumentPicker.getDocumentAsync({});
    if (result.assets?.[0]) {
      const asset = result.assets[0];
      setFile({ name: asset.name, size: asset.size, uri: asset.uri });
    }
  };

  return (
    <FilePicker
      file={file}
      onPress={pickFile}
      onRemove={() => setFile(undefined)}
      label="Tap to upload a document"
    />
  );
}

FileInfo Type#

PropTypeDefault
name
string
-
size
number
-
type
string
-
uri
string
-

Props#

PropTypeDefault
file
FileInfo
-
onPress
() => void
-
onRemove
() => void
-
label
string
"Tap to select a file"
className
string
-

Also accepts all View props.

Accessibility#

  • File upload UI with dashed border and preview.
  • Remove action has accessibilityRole="button" with accessibilityLabel.

Source#

components/ui/file-picker.tsx
import React from "react";
import { View, Pressable, Text } from "react-native";
import { cn } from "@/lib/utils";

export interface FileInfo {
  name: string;
  size?: number;
  type?: string;
  uri?: string;
}

export interface FilePickerProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  file?: FileInfo;
  onPress: () => void;
  onRemove?: () => void;
  label?: string;
  accept?: string;
}

function formatSize(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

export function FilePicker({
  className,
  file,
  onPress,
  onRemove,
  label = "Tap to select a file",
  ...props
}: FilePickerProps) {
  return (
    <View className={cn("", className)} {...props}>
      {!file ? (
        <Pressable
          onPress={onPress}
          accessible={true}
          accessibilityRole="button"
          accessibilityLabel={label}
          className="min-h-24 items-center justify-center rounded-lg border-2 border-dashed border-input bg-background px-4 py-6"
        >
          <Text className="text-2xl text-muted-foreground mb-2">↑</Text>
          <Text className="text-sm text-muted-foreground text-center">{label}</Text>
        </Pressable>
      ) : (
        <View className="flex-row items-center rounded-lg border border-border bg-card px-4 py-3">
          <View className="flex-1 mr-3">
            <Text className="text-sm font-medium text-foreground" numberOfLines={1}>{file.name}</Text>
            {file.size !== undefined && (
              <Text className="text-xs text-muted-foreground mt-0.5">{formatSize(file.size)}</Text>
            )}
          </View>
          {onRemove && (
            <Pressable
              onPress={onRemove}
              accessible={true}
              accessibilityRole="button"
              accessibilityLabel="Remove file"
              className="min-h-8 min-w-8 items-center justify-center"
            >
              <Text className="text-destructive text-lg">×</Text>
            </Pressable>
          )}
        </View>
      )}
    </View>
  );
}