ERP-node/frontend/components/report/designer/modals/shared.tsx

204 lines
5.5 KiB
TypeScript

"use client";
/**
* shared.tsx — 모달 내부 공통 UI 헬퍼 컴포넌트
*
* Section, TabContent, Field, FieldGroup, Grid, InfoBox, PreviewPanel 등
* 모든 컴포넌트 설정 모달에서 일관된 UI를 유지하기 위한 빌딩 블록.
*/
import React, { type ReactNode } from "react";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label";
// ─── 섹션 컨테이너 ────────────────────────────────────────────────────────────
interface SectionProps {
emphasis?: boolean;
icon?: ReactNode;
title?: string;
children: ReactNode;
className?: string;
}
export function Section({
emphasis = false,
icon,
title,
children,
className,
}: SectionProps) {
return (
<div
className={cn(
"rounded-xl border p-4",
emphasis
? "border-blue-200 bg-blue-50/50"
: "border-gray-200 bg-white",
className,
)}
>
{(icon || title) && (
<div className="mb-3 flex items-center gap-2">
{icon && (
<span
className={cn(
"shrink-0",
emphasis ? "text-blue-600" : "text-gray-500",
)}
>
{icon}
</span>
)}
{title && (
<p
className={cn(
"text-xs font-medium",
emphasis ? "text-blue-700" : "text-foreground",
)}
>
{title}
</p>
)}
</div>
)}
{children}
</div>
);
}
// ─── 탭 콘텐츠 래퍼 ──────────────────────────────────────────────────────────
interface TabContentProps {
children: ReactNode;
className?: string;
}
export function TabContent({ children, className }: TabContentProps) {
return (
<div className={cn("space-y-4 px-6 py-5", className)}>{children}</div>
);
}
// ─── 폼 필드 래퍼 ────────────────────────────────────────────────────────────
interface FieldProps {
label: string;
help?: string;
children: ReactNode;
className?: string;
htmlFor?: string;
}
export function Field({ label, help, children, className, htmlFor }: FieldProps) {
return (
<div className={cn("space-y-1.5", className)}>
<Label
htmlFor={htmlFor}
className="block text-xs font-medium text-foreground"
>
{label}
</Label>
{children}
{help && (
<p className="mt-1 text-[10px] text-muted-foreground">{help}</p>
)}
</div>
);
}
// ─── 2/3열 그리드 ─────────────────────────────────────────────────────────────
interface GridProps {
children: ReactNode;
cols?: 2 | 3;
className?: string;
}
export function Grid({ children, cols = 2, className }: GridProps) {
return (
<div
className={cn(
"grid gap-3",
cols === 2 ? "grid-cols-2" : "grid-cols-3",
className,
)}
>
{children}
</div>
);
}
// ─── 필드 그룹 (space-y-3) ───────────────────────────────────────────────────
interface FieldGroupProps {
children: ReactNode;
className?: string;
}
export function FieldGroup({ children, className }: FieldGroupProps) {
return (
<div className={cn("space-y-3", className)}>{children}</div>
);
}
// ─── 인라인 정보 박스 ─────────────────────────────────────────────────────────
interface InfoBoxProps {
children: ReactNode;
variant?: "blue" | "gray";
}
export function InfoBox({ children, variant = "blue" }: InfoBoxProps) {
return (
<div
className={cn(
"rounded-lg border px-3 py-2 text-xs",
variant === "blue"
? "border-blue-200 bg-blue-50 text-blue-800"
: "border-gray-200 bg-gray-50 text-gray-600",
)}
>
{children}
</div>
);
}
// ─── 미리보기 패널 ────────────────────────────────────────────────────────────
interface PreviewPanelProps {
children?: ReactNode;
label?: string;
height?: string;
}
export function PreviewPanel({
children,
label = "미리보기",
height = "h-48",
}: PreviewPanelProps) {
return (
<div className="flex flex-col items-center justify-center gap-3">
{children ? (
<div
className={cn(
"flex w-full items-center justify-center overflow-hidden rounded-xl border border-gray-200 bg-white",
height,
)}
>
{children}
</div>
) : (
<div
className={cn(
"flex w-full items-center justify-center rounded-xl border-2 border-dashed border-gray-200 bg-white",
height,
)}
>
<p className="text-xs text-muted-foreground">{label}</p>
</div>
)}
</div>
);
}