쇼케이스 (/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+ 페이지 마이그레이션 진행 예정.
131 lines
6.3 KiB
TypeScript
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"><PageContainer fullBleed></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">
|
|
<Badge intent="info" className="w-full justify-center">
|
|
</code>
|
|
</p>
|
|
<p className="text-xs text-label">
|
|
<strong className="text-red-400">금지:</strong>{' '}
|
|
<code className="font-mono"><Badge className="bg-red-500 text-white"></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"><button className="bg-blue-600 ..."></code> — Button 컴포넌트 사용 필수</li>
|
|
<li>❌ <code className="font-mono"><input className="bg-surface ..."></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>
|
|
</>
|
|
);
|
|
}
|