diff --git a/frontend/components/dash/TemplateLibraryModal.tsx b/frontend/components/dash/TemplateLibraryModal.tsx
index 436d730d..194f7b42 100644
--- a/frontend/components/dash/TemplateLibraryModal.tsx
+++ b/frontend/components/dash/TemplateLibraryModal.tsx
@@ -3,7 +3,7 @@
import { useState, useEffect } from 'react';
import { Search, X } from 'lucide-react';
import { getTemplateList } from '@/lib/api/template';
-import { TemplateThumbnail } from './TemplateThumbnail';
+import { TemplateMiniPreview } from './TemplateMiniPreview';
interface TemplateLibraryModalProps {
open: boolean;
@@ -143,7 +143,7 @@ export function TemplateLibraryModal({ open, onClose, onSelectTemplate }: Templa
className="dash-lib-card"
onClick={() => onSelectTemplate(t)}
>
-
+
{name}
{desc && {desc}
}
diff --git a/frontend/components/dash/TemplateMiniPreview.tsx b/frontend/components/dash/TemplateMiniPreview.tsx
new file mode 100644
index 00000000..b6448525
--- /dev/null
+++ b/frontend/components/dash/TemplateMiniPreview.tsx
@@ -0,0 +1,97 @@
+'use client';
+
+/**
+ * 라이브러리 카드용 라이브 미니 프리뷰.
+ *
+ * 실제 TemplateRenderer 를 mock empty context 로 띄운 뒤 transform: scale 로
+ * 카드 썸네일 사이즈에 맞춤. 사용자가 빌더에서 그린 그대로의 레이아웃이
+ * 읽힘 (테이블/폼/버튼/구분선 등 실제 컴포넌트 모양). 데이터는 비어있으니
+ * 추가 API 호출이나 회사 전환 부담 없음.
+ *
+ * - pointer-events: none — 카드 클릭 영역은 부모가 처리
+ * - overflow: hidden — scale 후 박스 밖 픽셀 클립
+ * - ResizeObserver 로 카드 폭에 따라 scale 자동 조정
+ *
+ * 빈 템플릿이면 TemplateThumbnail 폴백.
+ */
+
+import { useLayoutEffect, useRef, useState, useMemo } from 'react';
+import { TemplateRenderer, type TemplateRenderContext } from './TemplateRenderer';
+import { TemplateThumbnail } from './TemplateThumbnail';
+
+interface TemplateMiniPreviewProps {
+ template: any;
+}
+
+const EMPTY_CTX: TemplateRenderContext = {
+ fields: [],
+ data: [],
+ loading: false,
+ selectedRow: null,
+ totalCount: 0,
+ page: 1,
+ pageSize: 20,
+ searchParams: {},
+ onSearch: () => {},
+ onRowSelect: () => {},
+ onPageChange: () => {},
+ onAdd: () => {},
+ onEdit: () => {},
+ onDelete: () => {},
+};
+
+const BASE_WIDTH = 1200;
+const BASE_HEIGHT = 750; // 16:10
+
+function hasContent(template: any): boolean {
+ const v = template?.views ?? template?.VIEWS;
+ if (!v) return false;
+ const lst = v?.list;
+ if (Array.isArray(lst) && lst.length > 0) return true;
+ if (Array.isArray(lst?.blocks) && lst.blocks.length > 0) return true;
+ if (Array.isArray(lst?.components) && lst.components.length > 0) return true;
+ if (Array.isArray(lst?.layers) &&
+ lst.layers.some((l: any) => Array.isArray(l?.components) && l.components.length > 0)) {
+ return true;
+ }
+ return false;
+}
+
+export function TemplateMiniPreview({ template }: TemplateMiniPreviewProps) {
+ const wrapRef = useRef
(null);
+ const [scale, setScale] = useState(0.15);
+
+ const empty = useMemo(() => !hasContent(template), [template]);
+
+ useLayoutEffect(() => {
+ if (empty || !wrapRef.current) return;
+ const el = wrapRef.current;
+ const update = () => {
+ const w = el.clientWidth;
+ if (w > 0) setScale(w / BASE_WIDTH);
+ };
+ update();
+ const ro = new ResizeObserver(update);
+ ro.observe(el);
+ return () => ro.disconnect();
+ }, [empty]);
+
+ if (empty) {
+ return ;
+ }
+
+ return (
+
+ );
+}
diff --git a/frontend/styles/dashboard.css b/frontend/styles/dashboard.css
index 7b68fa29..119e2e4f 100644
--- a/frontend/styles/dashboard.css
+++ b/frontend/styles/dashboard.css
@@ -487,6 +487,15 @@
.dash-lib-card-thumb--empty {
display: flex; align-items: center; justify-content: center;
}
+.dash-lib-card-thumb--live {
+ padding: 0;
+ background: var(--v5-surface-solid);
+}
+.dash-lib-card-thumb-stage {
+ transform-origin: top left;
+ pointer-events: none;
+ user-select: none;
+}
.dash-lib-card-thumb-canvas {
position: relative; width: 100%; height: 100%;
}