- Remove tw-animate-css import + dead src/components/ui shadcn tree - Convert 7 orphaned components (moderation, analysis, guild-selector, voice/activity-timeline, shared/empty+error) to new primitives - Add missing moderation/view.tsx; analysis uses SearchPanel directly - globals.css now uses new signal-driven ops-console tokens - tsc --noEmit clean, next build green (11 routes), local smoke 200
76 lines
2.0 KiB
TypeScript
76 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
import { type ReactNode, useEffect, useRef } from "react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export interface DialogProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
children: ReactNode;
|
|
className?: string;
|
|
labelledBy?: string;
|
|
}
|
|
|
|
export function Dialog({
|
|
open,
|
|
onClose,
|
|
children,
|
|
className,
|
|
labelledBy,
|
|
}: DialogProps) {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const reduce = useReducedMotion();
|
|
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
const onKey = (e: KeyboardEvent) => {
|
|
if (e.key === "Escape") onClose();
|
|
};
|
|
document.addEventListener("keydown", onKey);
|
|
document.body.style.overflow = "hidden";
|
|
return () => {
|
|
document.removeEventListener("keydown", onKey);
|
|
document.body.style.overflow = "";
|
|
};
|
|
}, [open, onClose]);
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{open && (
|
|
<motion.div
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.18 }}
|
|
>
|
|
<div
|
|
className="absolute inset-0 bg-black/55 backdrop-blur-sm"
|
|
onClick={onClose}
|
|
aria-hidden
|
|
/>
|
|
<motion.div
|
|
ref={ref}
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby={labelledBy}
|
|
className={cn(
|
|
"relative z-10 w-full max-w-lg surface-2 shadow-2xl",
|
|
className,
|
|
)}
|
|
initial={
|
|
reduce ? { opacity: 0 } : { opacity: 0, scale: 0.96, y: 12 }
|
|
}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
exit={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.96, y: 12 }}
|
|
transition={{ type: "spring", stiffness: 320, damping: 28 }}
|
|
>
|
|
{children}
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|