snp-connection-monitoring/docs/design/code-conventions.md
HYOJIN c2a71c1b77 feat(design): 디자인 시스템 적용 (CSS 토큰, Button/Badge, 차트, 다크모드) (#48)
- 디자인 시스템 가이드 문서 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>
2026-04-15 16:38:00 +09:00

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로 자동 정렬 권장.

  1. 레이아웃flex, grid, block, hidden, relative, fixed, z-*
  2. 크기w-*, h-*, min-w-*, max-w-*, size-*
  3. 여백m-*, mx-*, my-*, p-*, px-*, py-*, gap-*
  4. 테두리border*, rounded-*, ring-*, outline-*
  5. 배경bg-*, shadow-*, backdrop-*
  6. 텍스트text-*, font-*, leading-*, tracking-*, truncate
  7. 색상text-[var(*)], bg-[var(*)]
  8. 상호작용cursor-*, select-*, pointer-events-*
  9. 트랜지션transition-*, duration-*, ease-*, animate-*
  10. 반응형sm:*, md:*, lg:*, xl:*
  11. 다크모드dark:*
  12. 상태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'

규칙

  • 모든 컴포넌트의 루트 엘리먼트에 className prop을 전달할 수 있도록 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> 사용.
  • 선택적 className prop 항상 포함.
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