initial commit after split

This commit is contained in:
2026-06-24 01:43:10 +02:00
parent b1d6c0150f
commit 30c11a6dcf
173 changed files with 25380 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
import { useEffect, useId, type ReactNode } from "react";
export type DialogProps = {
open: boolean;
title: ReactNode;
children: ReactNode;
footer?: ReactNode;
role?: "dialog" | "alertdialog";
ariaDescribedBy?: string;
closeLabel?: string;
closeOnBackdrop?: boolean;
showCloseButton?: boolean;
closeDisabled?: boolean;
onClose?: () => void;
className?: string;
backdropClassName?: string;
headerClassName?: string;
titleClassName?: string;
bodyClassName?: string;
footerClassName?: string;
};
function joinClasses(...classes: Array<string | undefined | false>) {
return classes.filter(Boolean).join(" ");
}
export default function Dialog({
open,
title,
children,
footer,
role = "dialog",
ariaDescribedBy,
closeLabel = "Close",
closeOnBackdrop = true,
showCloseButton = true,
closeDisabled = false,
onClose,
className = "",
backdropClassName = "",
headerClassName = "",
titleClassName = "",
bodyClassName = "",
footerClassName = ""
}: DialogProps) {
const titleId = useId();
const canClose = Boolean(onClose) && !closeDisabled;
useEffect(() => {
if (!open || !canClose) return undefined;
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") onClose?.();
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [canClose, onClose, open]);
if (!open) return null;
return (
<div
className={joinClasses("dialog-backdrop", backdropClassName)}
role="presentation"
onMouseDown={(event) => {
if (closeOnBackdrop && canClose && event.target === event.currentTarget) onClose?.();
}}
>
<section
className={joinClasses("dialog-panel", className)}
role={role}
aria-modal="true"
aria-labelledby={titleId}
aria-describedby={ariaDescribedBy}
>
<div className={joinClasses("dialog-header", headerClassName)}>
<h2 id={titleId} className={joinClasses("dialog-title", titleClassName)}>{title}</h2>
{showCloseButton && onClose && (
<button
type="button"
className="dialog-close"
onClick={onClose}
aria-label={closeLabel}
disabled={closeDisabled}
>
×
</button>
)}
</div>
<div className={joinClasses("dialog-body", bodyClassName)}>{children}</div>
{footer && <div className={joinClasses("dialog-footer", footerClassName)}>{footer}</div>}
</section>
</div>
);
}