554 lines
30 KiB
TypeScript
554 lines
30 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useState } from "react";
|
|
import { useEditorStore } from "@/features/editor/state/editorStore";
|
|
import {
|
|
FilePlus2,
|
|
ListChecks,
|
|
Type,
|
|
MousePointerClick,
|
|
Image as ImageIcon,
|
|
Video,
|
|
Minus,
|
|
List,
|
|
LayoutTemplate,
|
|
Pencil,
|
|
FileText,
|
|
ListFilter,
|
|
CircleDot,
|
|
SquareCheck,
|
|
} from "lucide-react";
|
|
|
|
// 좌측 블록/폼/템플릿 추가 사이드바를 분리한 컴포넌트
|
|
export function BlocksSidebar() {
|
|
const addTextBlock = useEditorStore((state) => state.addTextBlock);
|
|
const addButtonBlock = useEditorStore((state) => state.addButtonBlock);
|
|
const addImageBlock = useEditorStore((state) => state.addImageBlock);
|
|
const addVideoBlock = useEditorStore((state) => (state as any).addVideoBlock);
|
|
const addDividerBlock = useEditorStore((state) => state.addDividerBlock);
|
|
const addListBlock = useEditorStore((state) => state.addListBlock);
|
|
const addSectionBlock = useEditorStore((state) => state.addSectionBlock);
|
|
|
|
const addFormBlock = useEditorStore((state) => state.addFormBlock);
|
|
const addFormInputBlock = useEditorStore((state) => (state as any).addFormInputBlock);
|
|
const addFormSelectBlock = useEditorStore((state) => (state as any).addFormSelectBlock);
|
|
const addFormCheckboxBlock = useEditorStore((state) => (state as any).addFormCheckboxBlock);
|
|
const addFormRadioBlock = useEditorStore((state) => (state as any).addFormRadioBlock);
|
|
|
|
const addHeroTemplateSection = useEditorStore((state) => state.addHeroTemplateSection);
|
|
const addFeaturesTemplateSection = useEditorStore((state) => state.addFeaturesTemplateSection);
|
|
const addCtaTemplateSection = useEditorStore((state) => state.addCtaTemplateSection);
|
|
const addFaqTemplateSection = useEditorStore((state) => state.addFaqTemplateSection);
|
|
const addPricingTemplateSection = useEditorStore((state) => state.addPricingTemplateSection);
|
|
const addTestimonialsTemplateSection = useEditorStore((state) => state.addTestimonialsTemplateSection);
|
|
const addBlogTemplateSection = useEditorStore((state) => state.addBlogTemplateSection);
|
|
const addTeamTemplateSection = useEditorStore((state) => state.addTeamTemplateSection);
|
|
const addFooterTemplateSection = useEditorStore((state) => state.addFooterTemplateSection);
|
|
|
|
// 버튼 핸들러를 useCallback으로 래핑해 불필요한 재생성을 줄인다.
|
|
const handleAddText = useCallback(() => addTextBlock(), [addTextBlock]);
|
|
const handleAddButton = useCallback(() => addButtonBlock(), [addButtonBlock]);
|
|
const handleAddImage = useCallback(() => addImageBlock(), [addImageBlock]);
|
|
const handleAddVideo = useCallback(() => addVideoBlock(), [addVideoBlock]);
|
|
const handleAddDivider = useCallback(() => addDividerBlock(), [addDividerBlock]);
|
|
const handleAddList = useCallback(() => addListBlock(), [addListBlock]);
|
|
const handleAddSection = useCallback(() => addSectionBlock(), [addSectionBlock]);
|
|
|
|
const handleAddFormBlock = useCallback(() => addFormBlock(), [addFormBlock]);
|
|
const handleAddFormInput = useCallback(() => addFormInputBlock(), [addFormInputBlock]);
|
|
const handleAddFormSelect = useCallback(() => addFormSelectBlock(), [addFormSelectBlock]);
|
|
const handleAddFormRadio = useCallback(() => addFormRadioBlock(), [addFormRadioBlock]);
|
|
const handleAddFormCheckbox = useCallback(() => addFormCheckboxBlock(), [addFormCheckboxBlock]);
|
|
|
|
const handleAddHeroTemplate = useCallback(
|
|
() => addHeroTemplateSection(),
|
|
[addHeroTemplateSection],
|
|
);
|
|
const handleAddFeaturesTemplate = useCallback(
|
|
() => addFeaturesTemplateSection(),
|
|
[addFeaturesTemplateSection],
|
|
);
|
|
const handleAddCtaTemplate = useCallback(() => addCtaTemplateSection(), [addCtaTemplateSection]);
|
|
const handleAddFaqTemplate = useCallback(() => addFaqTemplateSection(), [addFaqTemplateSection]);
|
|
const handleAddPricingTemplate = useCallback(
|
|
() => addPricingTemplateSection(),
|
|
[addPricingTemplateSection],
|
|
);
|
|
const handleAddTestimonialsTemplate = useCallback(
|
|
() => addTestimonialsTemplateSection(),
|
|
[addTestimonialsTemplateSection],
|
|
);
|
|
const handleAddBlogTemplate = useCallback(() => addBlogTemplateSection(), [addBlogTemplateSection]);
|
|
const handleAddTeamTemplate = useCallback(() => addTeamTemplateSection(), [addTeamTemplateSection]);
|
|
const handleAddFooterTemplate = useCallback(
|
|
() => addFooterTemplateSection(),
|
|
[addFooterTemplateSection],
|
|
);
|
|
|
|
const [isBlocksOpen, setIsBlocksOpen] = useState(true);
|
|
const [isFormsOpen, setIsFormsOpen] = useState(true);
|
|
const [isTemplatesOpen, setIsTemplatesOpen] = useState(true);
|
|
|
|
return (
|
|
<aside
|
|
data-testid="blocks-sidebar"
|
|
className="w-60 border-r border-slate-200 bg-white p-4 text-sm space-y-3 overflow-y-auto pb-scroll dark:border-slate-800 dark:bg-slate-950/40"
|
|
>
|
|
<h2 className="font-medium flex items-center gap-2 text-slate-900 dark:text-slate-100">
|
|
<button
|
|
type="button"
|
|
className="flex items-center justify-between w-full text-left"
|
|
aria-expanded={isBlocksOpen}
|
|
onClick={() => setIsBlocksOpen((prev) => !prev)}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<Pencil className="w-4 h-4 text-sky-400" aria-hidden="true" />
|
|
<span>블록</span>
|
|
</span>
|
|
</button>
|
|
</h2>
|
|
{isBlocksOpen && (
|
|
<>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddText}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<Type className="w-3 h-3" aria-hidden="true" />
|
|
<span>텍스트</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddButton}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<MousePointerClick className="w-3 h-3" aria-hidden="true" />
|
|
<span>버튼</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddImage}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<ImageIcon className="w-3 h-3" aria-hidden="true" />
|
|
<span>이미지</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddVideo}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<Video className="w-3 h-3" aria-hidden="true" />
|
|
<span>비디오</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddDivider}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<Minus className="w-3 h-3" aria-hidden="true" />
|
|
<span>구분선</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddList}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<List className="w-3 h-3" aria-hidden="true" />
|
|
<span>리스트</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddSection}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<LayoutTemplate className="w-3 h-3" aria-hidden="true" />
|
|
<span>섹션</span>
|
|
</span>
|
|
</button>
|
|
</>
|
|
)}
|
|
|
|
<div className="pt-3 border-t border-slate-200 mt-3 space-y-2 dark:border-slate-800">
|
|
<h3 className="text-[11px] font-medium text-slate-800 flex items-center gap-2 dark:text-slate-200">
|
|
<button
|
|
type="button"
|
|
className="flex items-center justify-between w-full text-left"
|
|
aria-expanded={isFormsOpen}
|
|
onClick={() => setIsFormsOpen((prev) => !prev)}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<ListChecks className="w-3 h-3" aria-hidden="true" />
|
|
<span>폼 요소</span>
|
|
</span>
|
|
</button>
|
|
</h3>
|
|
{isFormsOpen && (
|
|
<>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-emerald-200 bg-emerald-50 px-3 py-2 text-left text-xs text-emerald-900 hover:bg-emerald-100 dark:border-emerald-700 dark:bg-emerald-950 dark:text-emerald-100 dark:hover:bg-emerald-900"
|
|
onClick={handleAddFormBlock}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<FilePlus2 className="w-3 h-3" aria-hidden="true" />
|
|
<span>폼 컨트롤러</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddFormInput}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<FileText className="w-3 h-3" aria-hidden="true" />
|
|
<span>입력(Input)</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddFormSelect}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<ListFilter className="w-3 h-3" aria-hidden="true" />
|
|
<span>셀렉트(Select)</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddFormRadio}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<CircleDot className="w-3 h-3" aria-hidden="true" />
|
|
<span>단일 선택(Radio)</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="w-full rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddFormCheckbox}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<SquareCheck className="w-3 h-3" aria-hidden="true" />
|
|
<span>다중 선택(Checkbox)</span>
|
|
</span>
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
<div className="pt-3 border-t border-slate-200 mt-3 space-y-3 dark:border-slate-800">
|
|
<h3 className="text-[11px] font-medium text-slate-800 flex items-center gap-2 dark:text-slate-200">
|
|
<button
|
|
type="button"
|
|
className="flex items-center justify-between w-full text-left"
|
|
aria-expanded={isTemplatesOpen}
|
|
onClick={() => setIsTemplatesOpen((prev) => !prev)}
|
|
>
|
|
<span className="inline-flex items-center gap-2">
|
|
<FilePlus2 className="w-3 h-3" aria-hidden="true" />
|
|
<span>템플릿</span>
|
|
</span>
|
|
</button>
|
|
</h3>
|
|
|
|
{isTemplatesOpen && (
|
|
<div className="space-y-3">
|
|
<div className="space-y-2">
|
|
<p className="text-[10px] font-semibold text-slate-600 dark:text-slate-400">히어로 · CTA</p>
|
|
<div className="space-y-2">
|
|
<div className="rounded border border-slate-200 bg-slate-50 p-2 space-y-1 dark:border-slate-800 dark:bg-slate-950/50">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddHeroTemplate}
|
|
>
|
|
Hero 템플릿
|
|
</button>
|
|
<div
|
|
data-testid="template-preview-hero"
|
|
className="shrink-0 flex h-6 w-10 rounded border border-slate-300 bg-slate-100 p-[2px] dark:border-slate-700 dark:bg-slate-900"
|
|
>
|
|
<div className="flex h-full w-full flex-col items-center justify-center gap-[2px]">
|
|
<div className="h-[2px] w-3/4 bg-slate-700/70" />
|
|
<div className="h-[2px] w-1/2 bg-slate-700/50" />
|
|
<div className="h-2 w-4 rounded-[1px] bg-slate-700/70 mt-[1px]" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 leading-snug">
|
|
페이지 상단 Hero 섹션 (큰 제목 + 서브텍스트 + 버튼)
|
|
</p>
|
|
</div>
|
|
|
|
<div className="rounded border border-slate-200 bg-slate-50 p-2 space-y-1 dark:border-slate-800 dark:bg-slate-950/50">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddCtaTemplate}
|
|
>
|
|
CTA 템플릿
|
|
</button>
|
|
<div
|
|
data-testid="template-preview-cta"
|
|
className="shrink-0 flex h-6 w-10 rounded border border-slate-300 bg-slate-100 p-[2px] dark:border-slate-700 dark:bg-slate-900"
|
|
>
|
|
<div className="flex h-full w-full items-center gap-[2px]">
|
|
<div className="flex-1 flex flex-col justify-center gap-[1px]">
|
|
<div className="h-[2px] w-full bg-slate-700/70" />
|
|
<div className="h-[2px] w-2/3 bg-slate-700/50" />
|
|
</div>
|
|
<div className="w-3 h-2 rounded-[1px] bg-slate-700/70" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 leading-snug">콜투액션(CTA) 섹션</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<p className="text-[10px] font-semibold text-slate-600 dark:text-slate-400">콘텐츠 섹션</p>
|
|
<div className="space-y-2">
|
|
<div className="rounded border border-slate-200 bg-slate-50 p-2 space-y-1 dark:border-slate-800 dark:bg-slate-950/50">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddFeaturesTemplate}
|
|
>
|
|
기능 템플릿
|
|
</button>
|
|
<div
|
|
data-testid="template-preview-features"
|
|
className="shrink-0 flex h-6 w-10 items-stretch gap-[2px] rounded border border-slate-300 bg-slate-100 p-[2px] dark:border-slate-700 dark:bg-slate-900"
|
|
>
|
|
<div className="flex-1 flex flex-col gap-[1px]">
|
|
<div className="h-[2px] w-full bg-slate-700/70" />
|
|
<div className="h-[2px] w-full bg-slate-700/50" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col gap-[1px]">
|
|
<div className="h-[2px] w-full bg-slate-700/70" />
|
|
<div className="h-[2px] w-full bg-slate-700/50" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col gap-[1px]">
|
|
<div className="h-[2px] w-full bg-slate-700/70" />
|
|
<div className="h-[2px] w-full bg-slate-700/50" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 leading-snug">3컬럼 기능 소개 섹션</p>
|
|
</div>
|
|
|
|
<div className="rounded border border-slate-200 bg-slate-50 p-2 space-y-1 dark:border-slate-800 dark:bg-slate-950/50">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddFaqTemplate}
|
|
>
|
|
FAQ 템플릿
|
|
</button>
|
|
<div
|
|
data-testid="template-preview-faq"
|
|
className="shrink-0 flex h-6 w-10 items-start gap-[2px] rounded border border-slate-300 bg-slate-100 p-[2px] dark:border-slate-700 dark:bg-slate-900"
|
|
>
|
|
<div className="w-2 flex flex-col gap-[1px]">
|
|
<div className="h-[2px] w-full bg-slate-700/70" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col gap-[2px]">
|
|
<div className="h-[1px] w-full bg-slate-700/50" />
|
|
<div className="h-[1px] w-full bg-slate-700/50" />
|
|
<div className="h-[1px] w-full bg-slate-700/50" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 leading-snug">자주 묻는 질문(FAQ) 섹션</p>
|
|
</div>
|
|
|
|
<div className="rounded border border-slate-200 bg-slate-50 p-2 space-y-1 dark:border-slate-800 dark:bg-slate-950/50">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddPricingTemplate}
|
|
>
|
|
상품 템플릿
|
|
</button>
|
|
<div
|
|
data-testid="template-preview-pricing"
|
|
className="shrink-0 flex h-6 w-10 items-stretch gap-[2px] rounded border border-slate-300 bg-slate-100 p-[2px] dark:border-slate-700 dark:bg-slate-900"
|
|
>
|
|
<div className="flex-1 flex flex-col items-center justify-end gap-[1px] border border-slate-700/30 rounded-[1px]">
|
|
<div className="h-[1px] w-1/2 bg-slate-700/70" />
|
|
<div className="h-[1px] w-3/4 bg-slate-700/50" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col items-center justify-end gap-[1px] border border-sky-700/50 bg-sky-900/20 rounded-[1px]">
|
|
<div className="h-[1px] w-1/2 bg-sky-500/70" />
|
|
<div className="h-[1px] w-3/4 bg-sky-500/50" />
|
|
<div className="h-[2px] w-3/4 bg-sky-500/70 mt-[1px] rounded-[1px]" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col items-center justify-end gap-[1px] border border-slate-700/30 rounded-[1px]">
|
|
<div className="h-[1px] w-1/2 bg-slate-700/70" />
|
|
<div className="h-[1px] w-3/4 bg-slate-700/50" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 leading-snug">요금제/플랜 소개 섹션</p>
|
|
</div>
|
|
|
|
<div className="rounded border border-slate-200 bg-slate-50 p-2 space-y-1 dark:border-slate-800 dark:bg-slate-950/50">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddBlogTemplate}
|
|
>
|
|
블로그 템플릿
|
|
</button>
|
|
<div
|
|
data-testid="template-preview-blog"
|
|
className="shrink-0 flex h-6 w-10 items-stretch gap-[2px] rounded border border-slate-300 bg-slate-100 p-[2px] dark:border-slate-700 dark:bg-slate-900"
|
|
>
|
|
<div className="flex-1 flex flex-col gap-[1px]">
|
|
<div className="h-2 w-full bg-slate-600/50 rounded-[1px]" />
|
|
<div className="h-[1px] w-full bg-slate-700/70" />
|
|
<div className="h-[1px] w-2/3 bg-slate-700/50" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col gap-[1px]">
|
|
<div className="h-2 w-full bg-slate-600/50 rounded-[1px]" />
|
|
<div className="h-[1px] w-full bg-slate-700/70" />
|
|
<div className="h-[1px] w-2/3 bg-slate-700/50" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col gap-[1px]">
|
|
<div className="h-2 w-full bg-slate-600/50 rounded-[1px]" />
|
|
<div className="h-[1px] w-full bg-slate-700/70" />
|
|
<div className="h-[1px] w-2/3 bg-slate-700/50" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 leading-snug">블로그 포스트 목록 섹션</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<p className="text-[10px] font-semibold text-slate-600 dark:text-slate-400">신뢰/소개</p>
|
|
<div className="space-y-2">
|
|
<div className="rounded border border-slate-200 bg-slate-50 p-2 space-y-1 dark:border-slate-800 dark:bg-slate-950/50">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddTestimonialsTemplate}
|
|
>
|
|
후기 템플릿
|
|
</button>
|
|
<div
|
|
data-testid="template-preview-testimonials"
|
|
className="shrink-0 flex h-6 w-10 items-stretch gap-[2px] rounded border border-slate-300 bg-slate-100 p-[2px] dark:border-slate-700 dark:bg-slate-900"
|
|
>
|
|
<div className="flex-1 flex flex-col justify-between border border-slate-700/30 rounded-[1px] p-[1px]">
|
|
<div className="h-[1px] w-full bg-slate-700/50" />
|
|
<div className="h-[1px] w-1/2 bg-slate-700/70" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col justify-between border border-slate-700/30 rounded-[1px] p-[1px]">
|
|
<div className="h-[1px] w-full bg-slate-700/50" />
|
|
<div className="h-[1px] w-1/2 bg-slate-700/70" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col justify-between border border-slate-700/30 rounded-[1px] p-[1px]">
|
|
<div className="h-[1px] w-full bg-slate-700/50" />
|
|
<div className="h-[1px] w-1/2 bg-slate-700/70" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 leading-snug">고객 후기(Testimonials) 섹션</p>
|
|
</div>
|
|
|
|
<div className="rounded border border-slate-200 bg-slate-50 p-2 space-y-1 dark:border-slate-800 dark:bg-slate-950/50">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddTeamTemplate}
|
|
>
|
|
Team 템플릿
|
|
</button>
|
|
<div
|
|
data-testid="template-preview-team"
|
|
className="shrink-0 flex h-6 w-10 items-stretch gap-[2px] rounded border border-slate-300 bg-slate-100 p-[2px] dark:border-slate-700 dark:bg-slate-900"
|
|
>
|
|
<div className="flex-1 flex flex-col items-center gap-[1px]">
|
|
<div className="h-2 w-2 rounded-full bg-slate-600/80" />
|
|
<div className="h-[1px] w-full bg-slate-700/70" />
|
|
<div className="h-[1px] w-2/3 bg-slate-700/50" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col items-center gap-[1px]">
|
|
<div className="h-2 w-2 rounded-full bg-slate-600/80" />
|
|
<div className="h-[1px] w-full bg-slate-700/70" />
|
|
<div className="h-[1px] w-2/3 bg-slate-700/50" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col items-center gap-[1px]">
|
|
<div className="h-2 w-2 rounded-full bg-slate-600/80" />
|
|
<div className="h-[1px] w-full bg-slate-700/70" />
|
|
<div className="h-[1px] w-2/3 bg-slate-700/50" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 leading-snug">팀 소개 섹션</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<p className="text-[10px] font-semibold text-slate-600 dark:text-slate-400">푸터/기타</p>
|
|
<div className="space-y-2">
|
|
<div className="rounded border border-slate-200 bg-slate-50 p-2 space-y-1 dark:border-slate-800 dark:bg-slate-950/50">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded border border-slate-200 bg-slate-50 px-3 py-2 text-left text-xs text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
|
onClick={handleAddFooterTemplate}
|
|
>
|
|
Footer 템플릿
|
|
</button>
|
|
<div
|
|
data-testid="template-preview-footer"
|
|
className="shrink-0 flex h-6 w-10 items-center justify-between gap-[2px] rounded border border-slate-300 bg-slate-100 p-[2px] dark:border-slate-700 dark:bg-slate-900"
|
|
>
|
|
<div className="flex-1 h-[2px] bg-slate-700/70" />
|
|
<div className="flex-1 flex flex-col gap-[1px]">
|
|
<div className="h-[1px] w-full bg-slate-700/50" />
|
|
<div className="h-[1px] w-full bg-slate-700/50" />
|
|
</div>
|
|
<div className="flex-1 h-[1px] bg-slate-700/40" />
|
|
</div>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 leading-snug">페이지 푸터 섹션</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|