kcg-ai-monitoring/frontend/src/design-system/sections/BadgeSection.tsx
htlee 52749638ef refactor(frontend): 쇼케이스 SSOT 구조 — 카탈로그 레지스트리 + variant 메타
Phase A: 쇼케이스의 카탈로그/variant 정보를 중앙 상수로 끌어올림

- shared/constants/catalogRegistry.ts 신규
  - 19+ 카탈로그의 id/showcaseId/titleKo/titleEn/description/source/items를
    단일 레지스트리로 통합 관리
  - 새 카탈로그 추가 = 레지스트리에 1줄 추가로 쇼케이스 자동 노출
  - CATALOG_REGISTRY + getCatalogById()
- lib/theme/variantMeta.ts 신규
  - BADGE_INTENT_META: 8 intent의 titleKo/titleEn/description
  - BUTTON_VARIANT_META: 5 variant의 titleKo/titleEn/description
  - BADGE_INTENT_ORDER/SIZE_ORDER, BUTTON_VARIANT_ORDER/SIZE_ORDER
- 쇼케이스 섹션 리팩토링 — 하드코딩 제거
  - CatalogSection: CATALOG_REGISTRY 자동 열거 (CATALOGS 배열 삭제)
  - BadgeSection: BADGE_INTENT_META에서 의미 가이드 + titleKo 참조
  - ButtonSection: BUTTON_VARIANT_META에서 의미 가이드 + titleKo 참조

효과:
- 카탈로그의 라벨/색상/intent 변경 시 쇼케이스와 실 페이지 동시 반영
- Badge/Button의 variant 의미가 variantMeta 한 곳에서 관리됨
- 쇼케이스 섹션에 분산돼 있던 하드코딩 제거 (INTENT_USAGE, VARIANT_USAGE 등)

다음 단계: 실 페이지를 PageContainer/PageHeader/Button/Input으로 마이그레이션
2026-04-08 11:42:43 +09:00

110 lines
4.1 KiB
TypeScript
Raw Blame 히스토리

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { TrkSectionHeader, Trk } from '../lib/Trk';
import { Badge } from '@shared/components/ui/badge';
import {
BADGE_INTENT_META,
BADGE_INTENT_ORDER,
BADGE_SIZE_ORDER,
} from '@lib/theme/variantMeta';
export function BadgeSection() {
return (
<>
<TrkSectionHeader
id="TRK-SEC-badge"
title="Badge"
description={`${BADGE_INTENT_ORDER.length} intent × ${BADGE_SIZE_ORDER.length} size = ${BADGE_INTENT_ORDER.length * BADGE_SIZE_ORDER.length} 변형. CVA + cn()로 className override 허용, !important 없음.`}
/>
{/* 변형 그리드 */}
<h3 className="text-sm font-semibold text-heading mb-2 mt-2"> </h3>
<Trk id="TRK-BADGE-matrix" className="ds-sample">
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr>
<th className="text-left text-[10px] text-hint font-mono pb-2 pr-3">
intent / size
</th>
{BADGE_SIZE_ORDER.map((size) => (
<th key={size} className="text-left text-[10px] text-hint font-mono pb-2 px-2">
{size}
</th>
))}
</tr>
</thead>
<tbody>
{BADGE_INTENT_ORDER.map((intent) => (
<tr key={intent}>
<td className="text-[11px] text-label font-mono pr-3 py-1.5">{intent}</td>
{BADGE_SIZE_ORDER.map((size) => (
<td key={size} className="px-2 py-1.5">
<Trk id={`TRK-BADGE-${intent}-${size}`} inline>
<Badge intent={intent} size={size}>
{intent.toUpperCase()}
</Badge>
</Trk>
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</Trk>
{/* intent 의미 가이드 (variantMeta에서 자동 열거) */}
<h3 className="text-sm font-semibold text-heading mb-2 mt-6">Intent </h3>
<div className="ds-grid ds-grid-2">
{BADGE_INTENT_ORDER.map((intent) => {
const meta = BADGE_INTENT_META[intent];
return (
<Trk key={intent} id={`TRK-BADGE-usage-${intent}`} className="ds-sample">
<div className="flex items-center gap-3">
<Badge intent={intent} size="md">
{meta.titleKo}
</Badge>
<span className="text-xs text-label flex-1">{meta.description}</span>
</div>
</Trk>
);
})}
</div>
{/* 사용 예시 코드 */}
<h3 className="text-sm font-semibold text-heading mb-2 mt-6"> </h3>
<Trk id="TRK-BADGE-usage-code" className="ds-sample">
<code className="ds-code">
{`import { Badge } from '@shared/components/ui/badge';
import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels';
// 카탈로그 API와 결합 — 라벨/색상 변경은 카탈로그 파일에서만
<Badge intent={getAlertLevelIntent('CRITICAL')} size="sm">
{getAlertLevelLabel('CRITICAL', t, lang)}
</Badge>
// className override (tailwind-merge가 같은 그룹 충돌 감지)
<Badge intent="info" size="md" className="rounded-full px-3">
커스텀 둥근 배지
</Badge>`}
</code>
</Trk>
{/* 금지 패턴 */}
<h3 className="text-sm font-semibold text-heading mb-2 mt-6"> </h3>
<Trk id="TRK-BADGE-antipattern" className="ds-sample border-red-500/30">
<div className="text-xs text-red-400 mb-2"> </div>
<code className="ds-code">
{`// ❌ className 직접 작성 (intent prop 무시)
<Badge className="bg-red-400 text-white text-[11px]">...</Badge>
// ❌ !important 사용
<Badge className="!bg-red-400 !text-slate-900">...</Badge>
// ❌ <div className="bg-red-400 ..."> → Badge 컴포넌트 사용 필수
<div className="inline-flex bg-red-400 text-white rounded px-2">위험</div>`}
</code>
</Trk>
</>
);
}