204 lines
5.5 KiB
TypeScript
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>
|
|
);
|
|
}
|