snp-connection-monitoring/docs/design/components.md
HYOJIN 88e25abe14 feat(frontend): 디자인 시스템 적용 및 전체 UI 개선 (#42)
- 디자인 시스템 CSS 변수 토큰 적용 (success/warning/danger/info)
- PeriodFilter 공통 컴포넌트 생성 및 통계 페이지 적용
- SERVICE_BADGE_VARIANTS 공통 상수 추출
- 통계/요청로그/키관리/관리자 페이지 레퍼런스 디자인 반영
- 테이블 규격 통일 (h-8/h-7, px-3 py-1, text-xs, Button xs)
- 타이틀 아이콘 전체 페이지 통일
- 카드 테두리 디자인 통일 (border + rounded-xl)
- FHD 1920x1080 최적화
2026-04-17 14:45:27 +09:00

7.6 KiB

컴포넌트 스펙

모든 컴포넌트는 cn() 유틸리티(src/utils/cn.ts)를 사용하고, CSS 변수 토큰을 통해 색상을 지정한다.


Button

변형 (Variant)

Variant 배경 텍스트 테두리 용도
primary --color-primary white none 주요 CTA
secondary --color-secondary --color-secondary-text none 보조 액션
accent --color-accent --color-accent-text none 포인트 액션
outline transparent --color-primary --color-primary 조용한 강조
ghost transparent --color-text-secondary none 최소 강조
danger --color-danger white none 삭제, 경고

사이즈

Size padding font-size height border-radius
sm px-3 py-1.5 text-sm 32px rounded-md
md px-4 py-2 text-base 40px rounded-lg
lg px-6 py-3 text-lg 48px rounded-lg

상태

  • disabled: opacity-50 cursor-not-allowed pointer-events-none
  • loading: 스피너 아이콘 표시, 텍스트 유지, disabled 처리
  • hover: 각 variant에 맞는 -hover 토큰 적용
  • active: 각 variant에 맞는 -active 토큰 적용
  • focus-visible: ring-2 ring-[var(--color-primary)] ring-offset-2

구현 예시

const buttonVariants = {
  primary: 'bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-hover)] active:bg-[var(--color-primary-active)]',
  secondary: 'bg-[var(--color-secondary)] text-[var(--color-secondary-text)] hover:bg-[var(--color-secondary-hover)]',
  accent: 'bg-[var(--color-accent)] text-[var(--color-accent-text)] hover:bg-[var(--color-accent-hover)]',
  outline: 'border border-[var(--color-primary)] text-[var(--color-primary)] hover:bg-[var(--color-primary-subtle)]',
  ghost: 'text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] hover:text-[var(--color-text-primary)]',
  danger: 'bg-[var(--color-danger)] text-white hover:opacity-90',
};

Badge

상태, 카테고리, 태그 표시에 사용한다. Shape에 따라 Pill과 Filled로 구분한다.

Pill 변형 (rounded-full) — 상태 표시용

Variant 용도 Light 배경 Dark 배경
default 기본 --color-bg-base --color-bg-base
success 성공/활성 green-50 green-500/15
warning 경고/대기 amber-50 amber-500/15
danger 에러/비활성 red-50 red-500/15
info 정보/식별자 blue-50 blue-500/15

Filled 변형 (rounded-md) — 카테고리/서비스 라벨용

Chart Palette와 1:1 매핑. className="rounded-md"를 추가하여 Filled shape 적용.

Variant Chart # Light 배경 Light 텍스트 Dark 배경 Dark 텍스트
blue 1 primary-100 primary-900 primary-900 primary-300
gold 2 accent-100 accent-900 accent-900 accent-300
rose 3 #fce4ec #B5607D rgba(212,131,155,0.12) #D4839B
teal 4 #e0f2f1 #2E7D6E rgba(91,181,166,0.12) #5BB5A6
lavender 5 #f3e8ff #7B5DA5 rgba(183,159,212,0.12) #B79FD4
coral 6 #fff3e0 #A86440 rgba(212,149,107,0.12) #D4956B

주의: success, warning, danger, info는 Pill 전용. Filled에 사용 금지.

스펙

  • Pill: rounded-full, px-2 py-0.5, text-xs font-medium
  • Filled: rounded-md, px-3 py-1, text-xs font-medium
  • 사이즈: sm (px-1.5 py-0.5 text-[10px]), md (px-2 py-0.5 text-xs)

Card

콘텐츠를 담는 기본 컨테이너.

구조

<div class="rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-surface)] shadow-sm">
  <!-- Card Header -->
  <div class="border-b border-[var(--color-border)] px-6 py-4">
    <h3 class="text-base font-semibold text-[var(--color-text-primary)]">제목</h3>
    <p class="text-sm text-[var(--color-text-secondary)]">부제목</p>
  </div>

  <!-- Card Body -->
  <div class="p-6">
    <!-- 콘텐츠 -->
  </div>

  <!-- Card Footer (선택) -->
  <div class="border-t border-[var(--color-border)] px-6 py-4">
    <!-- 액션 버튼 등 -->
  </div>
</div>

변형

Variant 설명
default border + shadow-sm
elevated shadow-md, 모달 내부 카드
flat border만, shadow 없음
interactive hover 시 shadow-md + scale-[1.01]

Input

기본 스펙

<div class="flex flex-col gap-1.5">
  <label class="text-sm font-medium text-[var(--color-text-primary)]">레이블</label>
  <input
    class="
      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
    "
    placeholder="입력하세요"
  />
  <p class="text-xs text-[var(--color-text-tertiary)]">도움말 텍스트</p>
</div>

상태

상태 테두리 색상
기본 --color-border
포커스 --color-primary (ring)
오류 --color-danger
성공 --color-success
비활성 --color-border + opacity-50

사이즈

Size padding font-size height
sm px-3 py-1.5 text-sm 32px
md px-3 py-2 text-base 40px
lg px-4 py-3 text-lg 48px

Modal

구조

<!-- Backdrop -->
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
  <!-- Dialog -->
  <div class="
    relative w-full max-w-md mx-4 rounded-2xl
    bg-[var(--color-bg-elevated)] shadow-xl
  ">
    <!-- Header -->
    <div class="flex items-center justify-between border-b border-[var(--color-border)] px-6 py-4">
      <h2 class="text-lg font-semibold text-[var(--color-text-primary)]">제목</h2>
      <button class="..."></button>
    </div>

    <!-- Body -->
    <div class="px-6 py-6">...</div>

    <!-- Footer -->
    <div class="flex justify-end gap-3 border-t border-[var(--color-border)] px-6 py-4">
      <Button variant="ghost">취소</Button>
      <Button variant="primary">확인</Button>
    </div>
  </div>
</div>

사이즈

Size max-width
sm max-w-sm (384px)
md max-w-md (448px)
lg max-w-lg (512px)
xl max-w-xl (576px)

접근성

  • role="dialog", aria-modal="true", aria-labelledby 필수
  • 열릴 때 첫 번째 포커스 가능 요소로 포커스 이동
  • Escape 키로 닫기
  • Backdrop 클릭 시 닫기 (선택적)
  • 열린 동안 body 스크롤 잠금

Toast

위치

fixed bottom-4 right-4 z-[60] (Modal보다 위)

변형

Variant 아이콘 색상
success CheckCircle --color-success
warning AlertTriangle --color-warning
error XCircle --color-danger
info Info --color-info

구조

<div class="
  flex items-start gap-3 w-80 rounded-xl p-4
  bg-[var(--color-bg-elevated)] shadow-lg
  border border-[var(--color-border)]
">
  <!-- 아이콘 -->
  <!-- 텍스트 -->
  <!-- 닫기 버튼 -->
</div>

동작

  • 기본 자동 닫힘: 4000ms
  • 진입: translate-y-2 opacity-0translate-y-0 opacity-100
  • 퇴장: translate-y-0 opacity-100translate-y-2 opacity-0
  • 트랜지션: duration-200 ease-out