feat: create FileOrUrlField component
This commit is contained in:
parent
e04fc22bb8
commit
8b0c699d76
120
app/components/FileOrUrlField.tsx
Normal file
120
app/components/FileOrUrlField.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
import { Upload, Link, X } from 'lucide-react';
|
||||
|
||||
export type InputMode = 'url' | 'file';
|
||||
|
||||
export interface FileOrUrlFieldProps {
|
||||
label: string;
|
||||
required?: boolean;
|
||||
mode: InputMode;
|
||||
onModeChange: (m: InputMode) => void;
|
||||
accept: string;
|
||||
file: File | null;
|
||||
onFileChange: (f: File | null) => void;
|
||||
inputRef: React.RefObject<HTMLInputElement | null>;
|
||||
url: string;
|
||||
onUrlChange: (u: string) => void;
|
||||
urlPlaceholder: string;
|
||||
}
|
||||
|
||||
export function FileOrUrlField({
|
||||
label, required, mode, onModeChange,
|
||||
accept, file, onFileChange, inputRef,
|
||||
url, onUrlChange, urlPlaceholder,
|
||||
}: FileOrUrlFieldProps) {
|
||||
return (
|
||||
<div className='space-y-1.5'>
|
||||
{/* Label + toggle */}
|
||||
<div className='flex items-center justify-between ml-1'>
|
||||
<label className='text-xs font-bold text-neutral-400 uppercase tracking-wider'>
|
||||
{label}{required && <span className='text-orange-500 ml-0.5'>*</span>}
|
||||
</label>
|
||||
<div className='flex items-center gap-1 rounded-xl bg-[#181818] border border-neutral-800 p-1 text-xs'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => onModeChange('file')}
|
||||
className={`flex items-center gap-1 rounded-lg px-3 py-1.5 transition-all font-bold cursor-pointer ${
|
||||
mode === 'file'
|
||||
? 'bg-orange-500 text-black shadow-md'
|
||||
: 'text-neutral-500 hover:text-neutral-300'
|
||||
}`}
|
||||
>
|
||||
<Upload size={11} />
|
||||
Upload
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => onModeChange('url')}
|
||||
className={`flex items-center gap-1 rounded-lg px-3 py-1.5 transition-all font-bold cursor-pointer ${
|
||||
mode === 'url'
|
||||
? 'bg-orange-500 text-black shadow-md'
|
||||
: 'text-neutral-500 hover:text-neutral-300'
|
||||
}`}
|
||||
>
|
||||
<Link size={11} />
|
||||
URL
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{mode === 'file' ? (
|
||||
<div
|
||||
onClick={() => inputRef.current?.click()}
|
||||
className={`flex cursor-pointer items-center gap-3 rounded-xl border-2 border-dashed px-4 py-4 transition-all duration-200 ${
|
||||
file
|
||||
? 'border-orange-500/50 bg-[#161616]'
|
||||
: 'border-neutral-800 hover:border-neutral-700 bg-[#161616]/40 hover:bg-[#161616]/70'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type='file'
|
||||
accept={accept}
|
||||
className='hidden'
|
||||
onChange={(e) => onFileChange(e.target.files?.[0] ?? null)}
|
||||
/>
|
||||
{file ? (
|
||||
<>
|
||||
<div className='flex flex-1 items-center gap-3.5 min-w-0'>
|
||||
<div className='w-8 h-8 rounded-lg bg-orange-500/10 border border-orange-500/20 flex items-center justify-center shrink-0'>
|
||||
<Upload size={14} className='text-orange-500' />
|
||||
</div>
|
||||
<div className="flex flex-col min-w-0">
|
||||
<span className='text-sm text-white font-semibold truncate'>{file.name}</span>
|
||||
<span className='text-[10px] text-neutral-500 font-medium mt-0.5'>
|
||||
{(file.size / 1024 / 1024).toFixed(2)} MB
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type='button'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onFileChange(null);
|
||||
if (inputRef.current) inputRef.current.value = '';
|
||||
}}
|
||||
className='shrink-0 text-neutral-500 hover:text-white p-1 hover:bg-neutral-800 rounded-lg transition-colors cursor-pointer'
|
||||
>
|
||||
<X size={15} />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div className='flex flex-1 items-center gap-3 text-neutral-500 py-1'>
|
||||
<Upload size={16} className="text-neutral-600" />
|
||||
<span className='text-sm font-medium'>
|
||||
Choose a file <span className='text-neutral-600 font-normal'>({accept})</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<input
|
||||
type='url'
|
||||
className='w-full bg-[#181818] border border-neutral-800 rounded-xl px-4 py-3 text-sm text-white placeholder-neutral-700 focus:border-orange-500 focus:outline-none transition-colors duration-200'
|
||||
placeholder={urlPlaceholder}
|
||||
value={url}
|
||||
onChange={(e) => onUrlChange(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user