"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 (
);
}