쇼케이스 (/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+ 페이지 마이그레이션 진행 예정.
72 lines
1.9 KiB
TypeScript
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>
|
|
);
|
|
}
|