krishnasales_mobile/src/components/forms/fields/FileInput.tsx
Bhanu Prakash Sai Potteri 73850d6e0a Krishna Sales mobile PWA
Mobile PWA port of the desktop console: bottom-sheet modals, card-list
record views, bottom tab nav, vite-plugin-pwa (manifest, service worker,
icons). API layer, workflow UIDs, and design tokens reused verbatim.
2026-07-08 11:27:35 +05:30

90 lines
2.9 KiB
TypeScript

import { useState, useEffect } from 'react';
export function ImagePreview({ file }: { file: File }) {
const [url, setUrl] = useState<string | null>(null);
useEffect(() => {
const objectUrl = URL.createObjectURL(file);
setUrl(objectUrl);
return () => URL.revokeObjectURL(objectUrl);
}, [file]);
if (!url) return null;
return <img src={url} alt="Preview" className="object-cover w-full h-full" />;
}
export function FileInput({
label,
type,
required,
value,
onChange,
}: {
label: string;
type: 'image' | 'file' | string;
required?: boolean;
value: File[] | undefined | null | unknown;
onChange: (files: File[]) => void;
}) {
const files = Array.isArray(value) ? value : (value instanceof File ? [value] : []);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
const newFiles = Array.from(e.target.files);
onChange([...files, ...newFiles]);
}
// reset input so the same file can be selected again if needed
e.target.value = '';
};
const removeFile = (index: number) => {
const newFiles = [...files];
newFiles.splice(index, 1);
onChange(newFiles);
};
return (
<div className="flex flex-col gap-1.5 font-sans">
<span className="text-sm font-medium text-muted">
{label}
{required && <span className="text-ruby-600"> *</span>}
</span>
<div className="flex flex-col gap-3">
<input
type="file"
multiple
required={required && files.length === 0}
accept={type === 'image' ? 'image/*' : undefined}
onChange={handleFileChange}
className="w-full text-sm text-strong file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-slate-100 file:text-navy-700 hover:file:bg-slate-200"
/>
{files.length > 0 && (
<div className="flex flex-wrap gap-3">
{files.map((file, i) => (
<div key={i} className="relative w-24 h-24 rounded-md overflow-hidden border border-border-default shadow-sm bg-slate-50 flex-shrink-0 group">
{type === 'image' ? (
<ImagePreview file={file} />
) : (
<div className="w-full h-full flex items-center justify-center p-2 text-xs text-center break-all overflow-hidden text-muted">
{file.name}
</div>
)}
<button
type="button"
onClick={() => removeFile(i)}
className="absolute top-1 right-1 bg-ruby-600/90 hover:bg-ruby-700 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-opacity"
title="Remove file"
>
</button>
</div>
))}
</div>
)}
</div>
</div>
);
}