"use client"; import { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { X } from "lucide-react"; /** * 회사 관리 모달 공용 셸 — v5 토큰/글로우 따름. 반투명/blur 금지. * * ⚠ createPortal 로 document.body 에 렌더링. * 이유: accordion 의 부모에 transform 이 걸려 있으면 position:fixed 가 viewport 가 아니라 * 그 transform 부모 기준으로 포지셔닝됨 (CSS containing-block 규칙). Portal 로 탈출. */ export default function ModalShell({ title, subtitle, onClose, width = 520, children, footer, }: { title: React.ReactNode; subtitle?: React.ReactNode; onClose: () => void; width?: number; children: React.ReactNode; footer?: React.ReactNode; }) { const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); if (!mounted) return null; // 헤더는 항상 테마 primary 색의 아주 연한 그라데이션 (variant 구분 없음). const headerBg = "linear-gradient(90deg, rgba(var(--v5-primary-rgb), 0.05), transparent 70%)"; const shell = (
{ if (e.target === e.currentTarget) onClose(); }} style={{ position: "fixed", inset: 0, zIndex: 90, background: "rgba(6, 5, 14, 0.55)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24, animation: "modalOverlayIn 0.18s ease-out", }} >
{title}
{subtitle && (
{subtitle}
)}
{children}
{footer && (
{footer}
)}
); return createPortal(shell, document.body); } export function ModalBtn({ children, onClick, variant = "secondary", disabled, icon, }: { children: React.ReactNode; onClick?: () => void; variant?: "primary" | "secondary" | "ghost" | "danger"; disabled?: boolean; icon?: React.ReactNode; }) { const map: Record = { primary: { background: "var(--v5-primary)", color: "#fff", borderColor: "var(--v5-primary)" }, danger: { background: "var(--v5-red)", color: "#fff", borderColor: "var(--v5-red)" }, secondary: { background: "var(--v5-surface-solid)", color: "var(--v5-text)", borderColor: "var(--v5-border)", }, ghost: { background: "transparent", color: "var(--v5-text-sec)", borderColor: "transparent" }, }; return ( ); }