!loading && onClose()}
- />
-
-
-
-
-
-
- {title}
-
-
{message}
-
-
-
-
-
-
-
- )}
-
- );
-}
diff --git a/src/admin/components/FormModal.tsx b/src/admin/components/FormModal.tsx
deleted file mode 100644
index 4c0d8df..0000000
--- a/src/admin/components/FormModal.tsx
+++ /dev/null
@@ -1,201 +0,0 @@
-import {
- useEffect,
- type ReactNode,
- type FormEvent,
- Children,
- isValidElement,
- cloneElement,
-} from "react";
-import { motion, AnimatePresence } from "framer-motion";
-import useModalLock from "../hooks/useModalLock";
-import useReducedMotion from "../hooks/useReducedMotion";
-
-export interface FormModalProps {
- isOpen: boolean;
- onClose: () => void;
- onSubmit?: () => void; // called when user clicks primary button (only if provided)
- title: string;
- /** Optional muted line shown under the title in the header. */
- subtitle?: ReactNode;
- children: ReactNode; // modal body (form fields)
- submitLabel?: string; // primary button text
- cancelLabel?: string; // cancel button text
- loading?: boolean;
- /** Disable the primary (submit) button — e.g. until the form is valid.
- * Keeps the button visibly greyed out instead of clickable-but-inert. */
- submitDisabled?: boolean;
- hideFooter?: boolean; // for "view-only" modals
- size?: "sm" | "md" | "lg"; // optional
- closeOnBackdrop?: boolean; // default true
- closeOnEsc?: boolean; // default true
- /** Slot for footer-left actions (e.g. delete). Rendered to the LEFT of
- * the standard cancel/submit group in the modal footer. */
- footerLeft?: ReactNode;
- /** Hide the close × in the header. Default false. */
- hideCloseButton?: boolean;
-}
-
-export default function FormModal({
- isOpen,
- onClose,
- onSubmit,
- title,
- subtitle,
- children,
- submitLabel = "Uložit",
- cancelLabel = "Zrušit",
- loading = false,
- submitDisabled = false,
- hideFooter = false,
- size = "md",
- closeOnBackdrop = true,
- closeOnEsc = true,
- footerLeft,
- hideCloseButton = false,
-}: FormModalProps) {
- useModalLock(isOpen);
- const reducedMotion = useReducedMotion();
-
- useEffect(() => {
- if (!isOpen || !closeOnEsc) return;
-
- function handleKeyDown(e: KeyboardEvent) {
- if (e.key === "Escape" && !loading) {
- onClose();
- }
- }
-
- window.addEventListener("keydown", handleKeyDown);
- return () => window.removeEventListener("keydown", handleKeyDown);
- }, [isOpen, closeOnEsc, loading, onClose]);
-
- const handleBackdropClick = () => {
- if (closeOnBackdrop && !loading) onClose();
- };
-
- const handlePrimaryClick = (e: FormEvent) => {
- e.preventDefault();
- if (onSubmit) onSubmit();
- };
-
- const titleId = "form-modal-title";
- const modalClassName =
- size === "lg" ? "admin-modal admin-modal-lg" : "admin-modal";
-
- // Snappier ease curve for modal entry — matches the "industrial/refined"
- // tone. Roughly equivalent to the cubic-bezier(0.16, 1, 0.3, 1) used by
- // a lot of design systems for "expo out" feel.
- const ease = [0.16, 1, 0.3, 1] as const;
- const duration = reducedMotion ? 0 : 0.22;
- // Stagger the body children slightly for a "drawing-in" reveal. We cap
- // the cascade so long forms don't drag on forever.
- const childrenArray = Children.toArray(children).filter(isValidElement);
- const stagger = reducedMotion ? 0 : Math.min(childrenArray.length, 5) * 0.03;
-
- return (
-
- {isOpen && (
-
-
-
-
-
-
- {title}
-
- {subtitle &&
{subtitle}
}
-
- {!hideCloseButton && (
-
- )}
-
-
-
- {reducedMotion || stagger === 0
- ? children
- : Children.map(children, (child, i) => {
- if (!isValidElement(child)) return child;
- return (
-
- {cloneElement(child)}
-
- );
- })}
-
-
- {!hideFooter && (
-
-
{footerLeft}
-
-
- {onSubmit && (
-
- )}
-
-
- )}
-
-
- )}
-
- );
-}