kcg-ai-monitoring/frontend/src/design-system/sections/GuideSection.tsx
htlee e0b51efc54 feat(frontend): 디자인 시스템 쇼케이스 페이지 + 신규 공통 컴포넌트
쇼케이스 (/design-system.html):
- 별도 Vite entry (System Flow 패턴 재사용, 메인 SPA 분리)
- 10개 섹션: Intro / Token / Typography / Badge / Button / Form /
  Card / Layout / Catalog (19+) / Guide
- 추적 ID 체계 (TRK-CATEGORY-SLUG):
  - hover 시 툴팁 + "ID 복사 모드"에서 클릭 시 클립보드 복사
  - URL hash 딥링크 (#trk=TRK-BADGE-critical-sm) 스크롤+하이라이트
  - 산출문서/논의에서 특정 변형 정확히 참조 가능
- Dark/Light 테마 토글로 양쪽 시각 검증

신규 공통 컴포넌트:
- Button (@shared/components/ui/button.tsx)
  - 5 variant × 3 size = 15 변형
  - primary/secondary/ghost/outline/destructive × sm/md/lg
- Input / Select / Textarea / Checkbox / Radio
  - Input · Select 공통 inputVariants 공유 (sm/md/lg × default/error/success)
- PageContainer / PageHeader / Section (shared/components/layout/)
  - PageContainer: size sm/md/lg + fullBleed (지도/풀화면 예외)
  - PageHeader: title + description + icon + demo 배지 + actions 슬롯
  - Section: Card + CardHeader + CardTitle + CardContent 단축

variants.ts 확장:
- buttonVariants / inputVariants / pageContainerVariants CVA 정의
- Button/Input/Select는 variants.ts에서 import하여 fast-refresh 경고 회피

빌드 검증 완료:
- TypeScript 타입 체크 통과
- ESLint 통과 (경고 0)
- vite build: designSystem-*.js 54KB (메인 SPA와 분리)

이 쇼케이스가 확정된 후 실제 40+ 페이지 마이그레이션 진행 예정.
2026-04-08 11:09:36 +09:00

131 lines
6.3 KiB
TypeScript

import { TrkSectionHeader, Trk } from '../lib/Trk';
export function GuideSection() {
return (
<>
<TrkSectionHeader
id="TRK-SEC-guide"
title="예외 처리 · 가이드"
description="공통 패턴에서 벗어날 때의 명시적 규칙"
/>
<Trk id="TRK-GUIDE-fullbleed" className="ds-sample">
<h3 className="text-sm font-semibold text-heading mb-2"> fullBleed를 ?</h3>
<ul className="text-xs text-label space-y-1.5 list-disc list-inside leading-relaxed">
<li>
<strong className="text-heading"> </strong> (LiveMapView,
VesselDetail)
</li>
<li>
<strong className="text-heading">3 </strong> + +
padding이
</li>
<li>
<strong className="text-heading"> 100% </strong>
</li>
</ul>
<p className="text-xs text-hint mt-2">
: <code className="font-mono">&lt;PageContainer fullBleed&gt;</code> .{' '}
<code className="font-mono">-m-4</code>, <code className="font-mono">-mx-4</code> negative margin .
</p>
</Trk>
<Trk id="TRK-GUIDE-classname-override" className="ds-sample mt-3">
<h3 className="text-sm font-semibold text-heading mb-2"> className override를 ?</h3>
<ul className="text-xs text-label space-y-1.5 list-disc list-inside leading-relaxed">
<li>
<strong className="text-heading"> </strong> width/margin (
<code className="font-mono">w-48</code>, <code className="font-mono">flex-1</code> )
</li>
<li>
<strong className="text-heading"> </strong> sm/md/lg
</li>
</ul>
<p className="text-xs text-label mt-2">
<strong className="text-green-400">:</strong>{' '}
<code className="font-mono">
&lt;Badge intent="info" className="w-full justify-center"&gt;
</code>
</p>
<p className="text-xs text-label">
<strong className="text-red-400">:</strong>{' '}
<code className="font-mono">&lt;Badge className="bg-red-500 text-white"&gt;</code>{' '}
<span className="text-hint">intent prop을 </span>
</p>
</Trk>
<Trk id="TRK-GUIDE-dynamic-color" className="ds-sample mt-3">
<h3 className="text-sm font-semibold text-heading mb-2"> hex </h3>
<ul className="text-xs text-label space-y-1.5 list-disc list-inside leading-relaxed">
<li>
DB에서 (: Role.colorHex) <code className="font-mono">style={`{{ background: role.colorHex }}`}</code>{' '}
</li>
<li>
<code className="font-mono">getAlertLevelHex(level)</code> API에서 hex
</li>
<li>
deck.gl RGB , hex
</li>
</ul>
</Trk>
<Trk id="TRK-GUIDE-anti-patterns" className="ds-sample mt-3 border border-red-500/30">
<h3 className="text-sm font-semibold text-red-400 mb-2"> </h3>
<ul className="text-xs text-label space-y-1.5 leading-relaxed">
<li> <code className="font-mono">!important</code> prefix (<code className="font-mono">!bg-red-500</code>)</li>
<li> <code className="font-mono">className="bg-X text-Y"</code> Badge </li>
<li> <code className="font-mono">&lt;button className="bg-blue-600 ..."&gt;</code> Button </li>
<li> <code className="font-mono">&lt;input className="bg-surface ..."&gt;</code> Input </li>
<li> <code className="font-mono">p-4 space-y-5</code> padding PageContainer size </li>
<li> <code className="font-mono">-m-4</code>, <code className="font-mono">-mx-4</code> negative margin fullBleed </li>
<li> <code className="font-mono">const STATUS_COLORS = {`{...}`}</code> shared/constants </li>
<li> <code className="font-mono">date.toLocaleString('ko-KR', ...)</code> <code className="font-mono">formatDateTime</code> </li>
</ul>
</Trk>
<Trk id="TRK-GUIDE-new-page" className="ds-sample mt-3">
<h3 className="text-sm font-semibold text-heading mb-2"> 릿</h3>
<code className="ds-code">
{`import { PageContainer, PageHeader, Section } from '@shared/components/layout';
import { Button } from '@shared/components/ui/button';
import { Input } from '@shared/components/ui/input';
import { Badge } from '@shared/components/ui/badge';
import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels';
import { formatDateTime } from '@shared/utils/dateFormat';
import { Shield, Plus } from 'lucide-react';
import { useTranslation } from 'react-i18next';
export function MyNewPage() {
const { t, i18n } = useTranslation('common');
const lang = i18n.language as 'ko' | 'en';
return (
<PageContainer>
<PageHeader
icon={Shield}
iconColor="text-blue-400"
title="새 페이지"
description="페이지 설명"
actions={
<Button variant="primary" icon={<Plus className="w-4 h-4" />}>
추가
</Button>
}
/>
<Section title="데이터 목록">
<Badge intent={getAlertLevelIntent('HIGH')} size="sm">
{getAlertLevelLabel('HIGH', t, lang)}
</Badge>
<span className="text-xs text-hint">{formatDateTime(row.createdAt)}</span>
</Section>
</PageContainer>
);
}`}
</code>
</Trk>
</>
);
}