kcg-ai-monitoring/frontend/src/design-system/lib/Trk.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

72 lines
1.9 KiB
TypeScript

import { type ReactNode, type CSSProperties, type MouseEvent } from 'react';
import { useTrk } from './TrkContext';
/**
* 쇼케이스 추적 ID 시스템
* 각 쇼케이스 항목에 고유 ID를 부여하여:
* 1. 호버 시 툴팁으로 ID 노출
* 2. "ID 복사 모드"에서 클릭 시 클립보드 복사
* 3. URL hash 딥링크 (#trk=TRK-BADGE-critical-sm) 지원
*/
interface TrkProps {
id: string;
children: ReactNode;
className?: string;
style?: CSSProperties;
/** 인라인 요소로 렌더할지 여부 (기본: block) */
inline?: boolean;
}
export function Trk({ id, children, className = '', style, inline = false }: TrkProps) {
const { copyMode, activeId } = useTrk();
const isActive = activeId === id;
const handleClick = async (e: MouseEvent) => {
if (!copyMode) return;
e.preventDefault();
e.stopPropagation();
try {
await navigator.clipboard.writeText(id);
const el = e.currentTarget as HTMLElement;
el.dataset.copied = 'true';
setTimeout(() => delete el.dataset.copied, 800);
} catch {
// clipboard API 미지원 시 무시
}
};
const Wrapper = inline ? 'span' : 'div';
return (
<Wrapper
data-trk={id}
className={`trk-item ${isActive ? 'trk-active' : ''} ${copyMode ? 'trk-copyable' : ''} ${className}`}
style={style}
title={id}
onClick={handleClick}
>
{children}
</Wrapper>
);
}
export function TrkSectionHeader({
id,
title,
description,
}: {
id: string;
title: string;
description?: string;
}) {
return (
<div data-trk={id} className="mb-4">
<div className="flex items-baseline gap-2">
<h2 className="text-xl font-bold text-heading">{title}</h2>
<code className="text-[10px] text-hint font-mono">{id}</code>
</div>
{description && <p className="text-xs text-hint mt-1">{description}</p>}
</div>
);
}