generated from gc/template-java-maven
- 디자인 시스템 가이드 문서 11개 생성 (docs/design/) - CSS 변수 토큰 시스템 (@theme + :root/.dark 전환) - cn() 유틸리티 (clsx + tailwind-merge) - Button/Badge 공통 컴포넌트 (variant/size, 다크모드 대응) - 하드코딩 Tailwind 색상 → CSS 변수 토큰 리팩토링 (30개 파일) - 차트 팔레트 다크모드 색상 업데이트 (CHART_COLORS_HEX) - 버튼 다크모드 채도/대비 강화 (primary-600 기반) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.8 KiB
5.8 KiB
코드 컨벤션
프론트엔드 디자인 시스템 적용을 위한 코드 작성 규칙.
파일 네이밍
| 유형 | 규칙 | 예시 |
|---|---|---|
| 컴포넌트 | PascalCase | Button.tsx, UserCard.tsx |
| 훅 | camelCase, use 접두사 |
useModal.ts, useTheme.ts |
| 유틸리티 | camelCase | cn.ts, formatDate.ts |
| 상수 | camelCase | chart.ts, routes.ts |
| 타입 | camelCase | user.ts, apihub.ts |
| 스타일 | camelCase | index.css |
컴포넌트 파일 당 하나의 export default 컴포넌트.
Tailwind 클래스 순서
가독성을 위해 다음 순서로 클래스를 작성한다. Prettier + prettier-plugin-tailwindcss로 자동 정렬 권장.
- 레이아웃 —
flex,grid,block,hidden,relative,fixed,z-* - 크기 —
w-*,h-*,min-w-*,max-w-*,size-* - 여백 —
m-*,mx-*,my-*,p-*,px-*,py-*,gap-* - 테두리 —
border*,rounded-*,ring-*,outline-* - 배경 —
bg-*,shadow-*,backdrop-* - 텍스트 —
text-*,font-*,leading-*,tracking-*,truncate - 색상 —
text-[var(*)],bg-[var(*)] - 상호작용 —
cursor-*,select-*,pointer-events-* - 트랜지션 —
transition-*,duration-*,ease-*,animate-* - 반응형 —
sm:*,md:*,lg:*,xl:* - 다크모드 —
dark:* - 상태 —
hover:*,focus:*,active:*,disabled:*,aria-*:
// 올바른 예
<div className="flex items-center justify-between w-full p-4 border border-[var(--color-border)] rounded-lg bg-[var(--color-bg-surface)] text-sm text-[var(--color-text-primary)] transition-colors hover:bg-[var(--color-bg-elevated)]">
cn() 유틸리티
clsx + tailwind-merge 조합. 조건부 클래스와 클래스 충돌 해결에 사용한다.
// src/utils/cn.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
사용 패턴
// 기본 사용
<div className={cn('p-4 rounded-lg', className)} />
// 조건부 클래스
<button
className={cn(
'px-4 py-2 rounded-lg font-medium transition-colors',
variant === 'primary' && 'bg-[var(--color-primary)] text-white',
variant === 'ghost' && 'text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)]',
disabled && 'opacity-50 cursor-not-allowed',
className,
)}
/>
// 클래스 충돌 해결 (twMerge)
cn('p-4', 'p-6') // → 'p-6'
cn('text-sm', 'text-base') // → 'text-base'
규칙
- 모든 컴포넌트의 루트 엘리먼트에
classNameprop을 전달할 수 있도록cn()으로 병합. - variant별 클래스는 객체 맵으로 분리.
const variants = {
primary: 'bg-[var(--color-primary)] text-white',
ghost: 'text-[var(--color-text-secondary)]',
} as const;
type Variant = keyof typeof variants;
<div className={cn(baseClass, variants[variant], className)} />
Import 순서
ESLint import/order 규칙 기준.
// 1. React
import { useState, useCallback } from 'react';
// 2. 외부 라이브러리
import { ChevronRight } from 'lucide-react';
// 3. 내부 모듈 (절대 경로)
import { cn } from '@/utils/cn';
import { CHART_COLORS } from '@/constants/chart';
import type { ButtonProps } from '@/types/ui';
// 4. 상대 경로 컴포넌트
import Badge from './Badge';
// 5. 스타일 (필요 시)
import styles from './Button.module.css';
forwardRef
외부에서 DOM 요소에 직접 접근이 필요한 컴포넌트(Input, Button 등)에 적용.
import { forwardRef } from 'react';
import { cn } from '@/utils/cn';
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
}
const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, error, className, ...props }, ref) => {
return (
<div className="flex flex-col gap-1.5">
{label && (
<label className="text-sm font-medium text-[var(--color-text-primary)]">
{label}
</label>
)}
<input
ref={ref}
className={cn(
'w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-bg-surface)]',
'px-3 py-2 text-base text-[var(--color-text-primary)]',
'placeholder:text-[var(--color-text-tertiary)]',
'focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:border-transparent',
'disabled:opacity-50 disabled:cursor-not-allowed',
error && 'border-[var(--color-danger)] focus:ring-[var(--color-danger)]',
className,
)}
{...props}
/>
{error && (
<p className="text-xs text-[var(--color-danger)]">{error}</p>
)}
</div>
);
},
);
Input.displayName = 'Input';
export default Input;
Props 타입 정의
- Props 타입은
interface로 정의. 이름은{ComponentName}Props. - HTML 속성 확장 시
extends React.HTMLAttributes<HTMLElement>사용. - 선택적
classNameprop 항상 포함.
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
title: string;
description?: string;
variant?: 'default' | 'elevated' | 'flat';
}
CSS 변수 사용
// DO: CSS 변수 토큰 사용
<div className="bg-[var(--color-bg-surface)] text-[var(--color-text-primary)]" />
// DO NOT: HEX 직접 사용
<div className="bg-white text-gray-900" />
// DO NOT: Tailwind 기본 색상으로 브랜드 컬러 표현
<div className="bg-blue-500 text-blue-900" />
컴포넌트 파일 구조
// 1. imports
// 2. 타입 정의
// 3. 상수 (variants 맵 등)
// 4. 컴포넌트 함수
// 5. export default