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";