From bd658cd00c674c050f93c8a0b96b07e3d96a5dc7 Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 6 Jun 2026 20:44:16 +0200 Subject: [PATCH] =?UTF-8?q?feat(mui):=20data/form=20kit=20=E2=80=94=20Moda?= =?UTF-8?q?l,=20ConfirmDialog,=20DataTable,=20Field/SwitchField?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/ui/ConfirmDialog.tsx | 57 +++++++++++++++++++++++ src/admin/ui/DataTable.tsx | 82 ++++++++++++++++++++++++++++++++++ src/admin/ui/Field.tsx | 75 +++++++++++++++++++++++++++++++ src/admin/ui/Modal.tsx | 68 ++++++++++++++++++++++++++++ src/admin/ui/index.ts | 5 +++ 5 files changed, 287 insertions(+) create mode 100644 src/admin/ui/ConfirmDialog.tsx create mode 100644 src/admin/ui/DataTable.tsx create mode 100644 src/admin/ui/Field.tsx create mode 100644 src/admin/ui/Modal.tsx diff --git a/src/admin/ui/ConfirmDialog.tsx b/src/admin/ui/ConfirmDialog.tsx new file mode 100644 index 0000000..bd0f935 --- /dev/null +++ b/src/admin/ui/ConfirmDialog.tsx @@ -0,0 +1,57 @@ +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogActions from "@mui/material/DialogActions"; +import MuiButton from "@mui/material/Button"; + +export interface ConfirmDialogProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + title: string; + message: string; + confirmText?: string; + cancelText?: string; + confirmVariant?: "primary" | "danger"; + loading?: boolean; +} + +/** Confirmation dialog over MUI Dialog. Preserves the legacy ConfirmModal prop shape. */ +export default function ConfirmDialog({ + isOpen, + onClose, + onConfirm, + title, + message, + confirmText = "Potvrdit", + cancelText = "Zrušit", + confirmVariant = "primary", + loading = false, +}: ConfirmDialogProps) { + return ( + + {title} + + {message} + + + + {cancelText} + + + {loading ? "Pracuji…" : confirmText} + + + + ); +} diff --git a/src/admin/ui/DataTable.tsx b/src/admin/ui/DataTable.tsx new file mode 100644 index 0000000..b4126c9 --- /dev/null +++ b/src/admin/ui/DataTable.tsx @@ -0,0 +1,82 @@ +import type { ReactNode } from "react"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; + +export interface DataColumn { + key: string; + header: string; + align?: "left" | "right"; + /** Render the cell with the DM Mono numeric face. */ + mono?: boolean; + render: (row: T) => ReactNode; +} + +export interface DataTableProps { + columns: DataColumn[]; + rows: T[]; + rowKey: (row: T) => string | number; + rowInactive?: (row: T) => boolean; + empty?: ReactNode; +} + +/** Dense, Soft-SaaS-styled data table. Styling only — never re-formats values. */ +export default function DataTable({ + columns, + rows, + rowKey, + rowInactive, + empty, +}: DataTableProps) { + if (rows.length === 0 && empty) return <>{empty}; + return ( + + + + + {columns.map((c) => ( + + {c.header} + + ))} + + + + {rows.map((row) => ( + + {columns.map((c) => ( + + {c.render(row)} + + ))} + + ))} + +
+
+ ); +} diff --git a/src/admin/ui/Field.tsx b/src/admin/ui/Field.tsx new file mode 100644 index 0000000..e2d29d7 --- /dev/null +++ b/src/admin/ui/Field.tsx @@ -0,0 +1,75 @@ +import type { ReactNode } from "react"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; + +/** Labeled field wrapper: label (+required) + the input + an error/hint line. */ +export function Field({ + label, + required, + error, + hint, + children, +}: { + label: string; + required?: boolean; + error?: string; + hint?: string; + children: ReactNode; +}) { + return ( + + + {label} + {required && ( + + * + + )} + + {children} + {error ? ( + + {error} + + ) : hint ? ( + + {hint} + + ) : null} + + ); +} + +/** Boolean switch with a label. */ +export function SwitchField({ + label, + checked, + onChange, +}: { + label: string; + checked: boolean; + onChange: (v: boolean) => void; +}) { + return ( + onChange(e.target.checked)} + /> + } + label={label} + /> + ); +} diff --git a/src/admin/ui/Modal.tsx b/src/admin/ui/Modal.tsx new file mode 100644 index 0000000..d720a0b --- /dev/null +++ b/src/admin/ui/Modal.tsx @@ -0,0 +1,68 @@ +import type { ReactNode } from "react"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import DialogActions from "@mui/material/DialogActions"; +import Typography from "@mui/material/Typography"; +import MuiButton from "@mui/material/Button"; + +export interface ModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: () => void; + title: string; + subtitle?: string; + children: ReactNode; + loading?: boolean; + submitDisabled?: boolean; + submitText?: string; + cancelText?: string; + maxWidth?: "xs" | "sm" | "md" | "lg"; +} + +/** Form modal over MUI Dialog. Preserves the legacy FormModal prop shape. */ +export default function Modal({ + isOpen, + onClose, + onSubmit, + title, + subtitle, + children, + loading = false, + submitDisabled = false, + submitText = "Uložit", + cancelText = "Zrušit", + maxWidth = "sm", +}: ModalProps) { + return ( + + + {title} + {subtitle && ( + + {subtitle} + + )} + + {children} + + + {cancelText} + + + {loading ? "Ukládám…" : submitText} + + + + ); +} diff --git a/src/admin/ui/index.ts b/src/admin/ui/index.ts index d5002f3..d904161 100644 --- a/src/admin/ui/index.ts +++ b/src/admin/ui/index.ts @@ -2,3 +2,8 @@ export { default as MuiProvider } from "./MuiProvider"; export { default as Button } from "./Button"; export { default as Card } from "./Card"; export { default as TextField } from "./TextField"; +export { default as Modal } from "./Modal"; +export { default as ConfirmDialog } from "./ConfirmDialog"; +export { default as DataTable } from "./DataTable"; +export type { DataColumn } from "./DataTable"; +export { Field, SwitchField } from "./Field";