diff --git a/src/admin/ui/FileUpload.tsx b/src/admin/ui/FileUpload.tsx new file mode 100644 index 0000000..62ec68d --- /dev/null +++ b/src/admin/ui/FileUpload.tsx @@ -0,0 +1,117 @@ +import { useRef, type ChangeEvent } from "react"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import IconButton from "@mui/material/IconButton"; +import Button from "./Button"; + +function formatSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} kB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; +} + +export interface FileUploadProps { + /** Selected files. Single-select mode keeps 0–1 items. */ + files: File[]; + onFilesChange: (files: File[]) => void; + /** e.g. "application/pdf" */ + accept?: string; + /** Allow multiple files (appends on pick). Default false = single (replaces). */ + multiple?: boolean; + /** Picker button label. */ + buttonLabel?: string; + disabled?: boolean; +} + +/** + * File picker: a button wrapping a hidden ``, with selected-file + * chips (name + size + remove). Single mode (default) replaces the selection; + * `multiple` appends. Value is always `File[]` so callers handle both uniformly. + * Validation (size/count/type beyond `accept`) stays with the caller. + */ +export default function FileUpload({ + files, + onFilesChange, + accept, + multiple = false, + buttonLabel = "Vybrat soubor", + disabled, +}: FileUploadProps) { + const inputRef = useRef(null); + + const handleChange = (e: ChangeEvent) => { + const picked = Array.from(e.target.files ?? []); + if (picked.length === 0) return; + onFilesChange(multiple ? [...files, ...picked] : picked.slice(0, 1)); + // Reset so picking the same file again (after removal) still fires onChange. + if (inputRef.current) inputRef.current.value = ""; + }; + + const remove = (idx: number) => + onFilesChange(files.filter((_, i) => i !== idx)); + + return ( + + + {files.length > 0 && ( + + {files.map((f, i) => ( + + + {f.name} + + + ({formatSize(f.size)}) + + remove(i)} + aria-label="Odebrat soubor" + disabled={disabled} + sx={{ p: 0.25, fontSize: "1rem", lineHeight: 1 }} + > + × + + + ))} + + )} + + ); +} diff --git a/src/admin/ui/index.ts b/src/admin/ui/index.ts index 8fb006f..9299a89 100644 --- a/src/admin/ui/index.ts +++ b/src/admin/ui/index.ts @@ -23,3 +23,4 @@ export { default as LoadingState } from "./LoadingState"; export { default as FilterBar } from "./FilterBar"; export { default as StatCard } from "./StatCard"; export type { StatCardColor } from "./StatCard"; +export { default as FileUpload } from "./FileUpload";