Files
invyone/frontend/lib/registry/components/title/InvTitleConfigPanel.tsx
T
DDD1542 a8ded6455d refactor: ConfigPanel Inv 네이밍 통합 + legacy 패널 분리 + input cp 마이그
11 패널 일괄 Inv* prefix 통일:
- 통합 (lib/registry/components/X/): button / container / divider / search /
  stats / table / title / input → Inv*ConfigPanel
- frontend/components/v2/config-panels/V2FieldConfigPanel → InvFieldConfigPanel
- 옛 v2-* hidden 호환 → InvLegacy{Divider,Text,Button}ConfigPanel

input 통합 컴포넌트 cp 톤 신규 작성 (InvInputConfigPanel):
- 277줄 옛 디자인 → CPVisualGrid 10칸 type 카드 + 타입별 옵션 + FeatureChipGrid

getComponentConfigPanel.tsx 버그 수정 (Codex 검토):
- "stats" key 중복 제거 (옛 StatsCardConfigPanel 이 통합 stats 덮던 silent bug)
- ALIAS 에서 v2-button-primary/v2-divider-line/v2-text-display 제외
  (옵션 B 일관성 — 옛 hidden 컴포넌트는 InvLegacy 패널 사용)
- MAP 의 해당 키를 InvLegacy* 로 직접 매핑

호출처 일괄 갱신:
- 각 통합 컴포넌트의 index.ts 7개 (import / config_panel / re-export)
- v2-input/v2-select/v2-divider-line/v2-text-display/v2-button-primary
  의 index.ts (config_panel 매핑)
- V2PropertiesPanel.tsx 의 require pattern (v2-input/v2-select)

검증: tsc 우리 영역 0건 / V2FieldConfigPanel 잔재 0건 / 기존 path 잔재 0건

다음 세션: useDbTables hook 추출 + 잔여 V2* cp 마이그 + dead code 정리

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:57:57 +09:00

205 lines
6.9 KiB
TypeScript

"use client";
/**
* InvTitleConfigPanel — 통합 "제목/텍스트" (id: title) cp 톤 설정 패널
*
* 흐름:
* ① 텍스트
* ② 시맨틱 프리셋 (CPVisualGrid 9칸 — 실제 폰트 사이즈로 Aa 미리보기)
* ③ 폰트 (두께 CPSegment + 크기 override CPText)
* ④ 정렬 + 색
* ▾ 고급 — 말줄임 (FeatureChipGrid)
*
* Reference: notes/gbpark/2026-04-28-cp-panel-standard.md
*/
import React from "react";
import { AlignLeft, AlignCenter, AlignRight, AlignJustify } from "lucide-react";
import {
CPSection,
CPRow,
CPGroup,
CPText,
CPColor,
CPSegment,
CPVisualGrid,
FeatureChipGrid,
Hint,
} from "@/components/v2/config-panels/_shared/cp";
import type { TitleConfig } from "./types";
export interface InvTitleConfigPanelProps {
config?: TitleConfig;
onChange?: (config: TitleConfig) => void;
onUpdateProperty?: (componentId: string, path: string, value: unknown) => void;
selectedComponent?: { id: string; config?: TitleConfig; [k: string]: any };
}
export const InvTitleConfigPanel: React.FC<InvTitleConfigPanelProps> = ({
config,
onChange,
onUpdateProperty,
selectedComponent,
}) => {
const current: TitleConfig =
(config as TitleConfig) || (selectedComponent?.config as TitleConfig) || {};
const patch = (p: Partial<TitleConfig>) => {
const next = { ...current, ...p };
onChange?.(next);
if (selectedComponent?.id) {
Object.entries(p).forEach(([key, value]) => {
onUpdateProperty?.(selectedComponent.id, `config.${key}`, value);
});
}
};
const variant = current.variant || "";
const fontWeight = current.fontWeight || "normal";
const textAlign = current.textAlign || "left";
const color = current.color || "#212121";
return (
<div style={{ fontFamily: "var(--v5-font-sans)", color: "var(--cp-text)", padding: "0 12px" }}>
{/* ── ① 텍스트 ─────────────────────────── */}
<CPSection title="① 텍스트">
<CPRow label="텍스트">
<CPText
value={current.text || ""}
onChange={(v) => patch({ text: v })}
placeholder="텍스트를 입력하세요"
/>
</CPRow>
</CPSection>
{/* ── ② 시맨틱 프리셋 ─────────────────────────── */}
<CPSection title="② 시맨틱 프리셋" desc="용도별 사이즈 + 두께 자동">
<CPVisualGrid
cols={3}
cardHeight={56}
value={variant}
onChange={(v) => patch({ variant: (v || undefined) as TitleConfig["variant"] })}
options={[
{
value: "",
label: "없음",
preview: (
<span style={{ fontSize: 13, color: "var(--cp-text-muted)" }}></span>
),
},
{
value: "h1",
label: "H1",
preview: <span style={{ fontSize: 22, fontWeight: 700 }}>Aa</span>,
},
{
value: "h2",
label: "H2",
preview: <span style={{ fontSize: 19, fontWeight: 700 }}>Aa</span>,
},
{
value: "h3",
label: "H3",
preview: <span style={{ fontSize: 16, fontWeight: 700 }}>Aa</span>,
},
{
value: "h4",
label: "H4",
preview: <span style={{ fontSize: 14, fontWeight: 600 }}>Aa</span>,
},
{
value: "h5",
label: "H5",
preview: <span style={{ fontSize: 13, fontWeight: 600 }}>Aa</span>,
},
{
value: "h6",
label: "H6",
preview: <span style={{ fontSize: 12, fontWeight: 500 }}>Aa</span>,
},
{
value: "body",
label: "본문",
preview: <span style={{ fontSize: 13 }}>Aa</span>,
},
{
value: "caption",
label: "캡션",
preview: (
<span style={{ fontSize: 10, color: "var(--cp-text-muted)" }}>Aa</span>
),
},
]}
/>
</CPSection>
{/* ── ③ 폰트 ─────────────────────────── */}
<CPSection title="③ 폰트" desc="두께와 크기 (override)">
<CPRow label="두께">
<CPSegment
value={fontWeight}
onChange={(v) => patch({ fontWeight: v })}
options={[
{ value: "normal", label: "기본" },
{ value: "500", label: "500" },
{ value: "600", label: "600" },
{ value: "700", label: "700" },
{ value: "800", label: "800" },
]}
/>
</CPRow>
<CPRow label="크기" help="예: 14px / 1.2rem (비우면 프리셋 사용)">
<CPText
value={current.fontSize || ""}
onChange={(v) => patch({ fontSize: v || undefined })}
placeholder="14px"
/>
</CPRow>
{variant && current.fontSize && (
<Hint tone="warn">
.
</Hint>
)}
</CPSection>
{/* ── ④ 정렬 + 색 ─────────────────────────── */}
<CPSection title="④ 정렬 / 색">
<CPRow label="정렬">
<CPSegment
value={textAlign}
onChange={(v) => patch({ textAlign: v as TitleConfig["textAlign"] })}
options={[
{ value: "left", label: <AlignLeft size={12} /> },
{ value: "center", label: <AlignCenter size={12} /> },
{ value: "right", label: <AlignRight size={12} /> },
{ value: "justify", label: <AlignJustify size={12} /> },
]}
/>
</CPRow>
<CPRow label="색">
<CPColor value={color} onChange={(v) => patch({ color: v })} />
</CPRow>
</CPSection>
{/* ── ▾ 고급 ─────────────────────────── */}
<CPGroup title="고급 설정" defaultOpen={false}>
<FeatureChipGrid
items={[
{
key: "truncate",
label: "말줄임",
desc: "텍스트가 길면 한 줄로 자르고 끝에 … 을 표시합니다.\n부모 컨테이너 너비를 기준으로 잘려요.\nsticky/카드 헤더처럼 한 줄 유지가 중요한 곳에 적합.",
},
]}
source={current as any}
onToggle={(k, v) => patch({ [k]: v } as Partial<TitleConfig>)}
/>
</CPGroup>
</div>
);
};
InvTitleConfigPanel.displayName = "InvTitleConfigPanel";
export default InvTitleConfigPanel;