Components
Loading preview...
A file upload component for React.
npx shadcn@latest add https://21st.dev/r/sean0205/file-upload'use client';
import { useFileUpload } from '@/components/ui/file-upload';
import { Button } from '@/components/ui/button-1';
import { CircleUserRoundIcon } from 'lucide-react';
function useCopyToClipboard() {
const [copied, setCopied] = useState(false);
const copy = async (text: string) => {
if (!navigator?.clipboard) return false;
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
return true;
} catch (error) {
console.error('Failed to copy:', error);
setCopied(false);
return false;
}
};
return { copy, copied };
}
export default function Component() {
const [{ files }, { removeFile, openFileDialog, getInputProps }] = useFileUpload({
accept: 'image/*',
});
const previewUrl = files[0]?.preview || null;
const fileName = files[0]?.file.name || null;
return (
<div className="flex flex-col items-center gap-2">
<div className="inline-flex items-center gap-2 align-top">
<div
className="border-input relative flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-md border"
aria-label={previewUrl ? 'Preview of uploaded image' : 'Default user avatar'}
>
{previewUrl ? (
<img
className="size-full object-cover"
src={previewUrl}
alt="Preview of uploaded image"
width={32}
height={32}
/>
) : (
<div aria-hidden="true">
<CircleUserRoundIcon className="opacity-60" size={16} />
</div>
)}
</div>
<div className="relative inline-block">
<Button onClick={openFileDialog} aria-haspopup="dialog">
{fileName ? 'Change image' : 'Upload image'}
</Button>
<input {...getInputProps()} className="sr-only" aria-label="Upload image file" tabIndex={-1} />
</div>
</div>
{fileName ? (
<div className="inline-flex gap-2 text-xs">
<p className="text-muted-foreground truncate" aria-live="polite">
{fileName}
</p>{' '}
<button
onClick={() => removeFile(files[0]?.id)}
className="cursor-pointer text-destructive font-medium hover:underline"
aria-label={`Remove ${fileName}`}
>
Remove
</button>
</div>
) : (
<div className="inline-flex gap-2 text-xs">
<p className="text-muted-foreground truncate" aria-live="polite">
No image attached
</p>
</div>
)}
</div>
);
}