ERP-node/frontend/lib/meta-components/Layout/LayoutRenderer.tsx

179 lines
4.6 KiB
TypeScript
Raw Normal View History

2026-03-01 03:39:00 +09:00
"use client";
/**
* Layout
* - config.mode에 children :
* - columns: CSS Grid (grid-cols-{n})
* - rows: flex flex-col
* - tabs: shadcn Tabs
* - card: shadcn Card
* - children은 props.children으로 (React.ReactNode)
* - config.gap으로
* - 모드: 점선 border +
*/
import React from "react";
import { cn } from "@/lib/utils";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
interface LayoutArea {
id: string;
label?: string;
size?: string; // "1fr", "2fr", "auto", "200px"
}
interface LayoutTab {
id: string;
label: string;
}
interface LayoutRendererProps {
id: string;
config: {
mode: "columns" | "rows" | "tabs" | "card";
areas?: LayoutArea[];
tabs?: LayoutTab[];
gap?: number;
title?: string;
};
children?: React.ReactNode; // 자식 컴포넌트들
isDesignMode?: boolean;
className?: string;
}
export function LayoutRenderer({
id,
config,
children,
isDesignMode = false,
className,
}: LayoutRendererProps) {
const { mode, areas = [], tabs = [], gap = 4, title } = config;
// columns 모드: CSS Grid 가로 분할
if (mode === "columns") {
const gridCols = areas.length || 2;
const gridTemplateColumns = areas
.map((a) => a.size || "1fr")
.join(" ");
return (
<div
className={cn(
"grid w-full",
isDesignMode && "rounded-md border-2 border-dashed border-primary/30",
className
)}
style={{
gridTemplateColumns,
gap: `${gap * 4}px`,
}}
>
{isDesignMode && areas.length === 0 ? (
<div className="col-span-full flex h-20 items-center justify-center text-sm text-muted-foreground">
Columns ( )
</div>
) : (
children
)}
</div>
);
}
// rows 모드: flex 세로 분할
if (mode === "rows") {
return (
<div
className={cn(
"flex w-full flex-col",
isDesignMode && "rounded-md border-2 border-dashed border-primary/30",
className
)}
style={{ gap: `${gap * 4}px` }}
>
{isDesignMode && areas.length === 0 ? (
<div className="flex h-20 items-center justify-center text-sm text-muted-foreground">
Rows ( )
</div>
) : (
children
)}
</div>
);
}
// tabs 모드: shadcn Tabs
if (mode === "tabs") {
if (tabs.length === 0) {
return (
<div
className={cn(
"flex h-20 items-center justify-center rounded-md border-2 border-dashed border-primary/30 text-sm text-muted-foreground",
className
)}
>
Tabs ( )
</div>
);
}
return (
<div className={cn("w-full", className)}>
{title && <h2 className="mb-2 text-lg font-semibold">{title}</h2>}
<Tabs defaultValue={tabs[0]?.id}>
<TabsList>
{tabs.map((tab) => (
<TabsTrigger key={tab.id} value={tab.id}>
{tab.label}
</TabsTrigger>
))}
</TabsList>
{tabs.map((tab) => (
<TabsContent key={tab.id} value={tab.id} className="mt-4">
{isDesignMode ? (
<div className="flex h-20 items-center justify-center rounded-md border border-dashed text-sm text-muted-foreground">
{tab.label}
</div>
) : (
children
)}
</TabsContent>
))}
</Tabs>
</div>
);
}
// card 모드: shadcn Card
if (mode === "card") {
return (
<Card className={cn("w-full", className)}>
{title && (
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
)}
<CardContent
className="space-y-4"
style={{ gap: `${gap * 4}px` }}
>
{isDesignMode && !children ? (
<div className="flex h-20 items-center justify-center text-sm text-muted-foreground">
Card ( )
</div>
) : (
children
)}
</CardContent>
</Card>
);
}
return (
<div className="text-destructive">
Unknown layout mode: {mode}
</div>
);
}