kcg-ai-monitoring/frontend/src/lib/theme/variants.ts
htlee 5812d9dea3 feat(frontend): UI 공통 인프라 + 19개 분류 카탈로그 구축
- cn() 유틸 신규 (clsx + tailwind-merge, 시맨틱 토큰 classGroup 등록)
- theme.css @layer utilities로 직접 정의 (Tailwind v4 복합 이름 매핑 실패 대응):
  text-heading/label/hint/on-vivid/on-bright, bg-surface-raised/overlay
- badgeVariants (CVA) 재구축: 8 intent x 4 size, rem 기반, !important 제거
- Badge 컴포넌트가 cn(badgeVariants, className)로 override 허용
- DataTable width 의미 변경: 고정 -> 선호 최소 너비 (minWidth), truncate + title 툴팁
- dateFormat.ts sv-SE 로케일로 YYYY-MM-DD HH:mm:ss 일관된 KST 출력
- ColorPicker 신규 (팔레트 + native color + hex 입력)
- shared/constants/ 19개 카탈로그: violation/alert/event/enforcement/patrol/
  engine/userRole/device/parentResolution/modelDeployment/gearGroup/darkVessel/
  httpStatus/userAccount/loginResult/permission/vesselAnalysis/connection/trainingZone
  + kpiUiMap. 백엔드 enum/code_master 기반 SSOT
- i18n ko/en common.json에 카테고리 섹션 추가
- adminApi.fetchRoles()가 updateRoleColorCache 자동 호출
- 공통 컴포넌트 (ExcelExport/NotificationBanner/Pagination/SaveButton) 시맨틱 토큰 적용
2026-04-08 10:53:40 +09:00

81 lines
2.9 KiB
TypeScript

/**
* CVA (Class Variance Authority) 변형 모듈
* 프로젝트 전체에서 반복되는 Tailwind 패턴을 변형으로 통합
*/
import { cva } from 'class-variance-authority';
/** 카드 변형 — CSS 변수 기반으로 테마 반응 */
export const cardVariants = cva('rounded-xl border border-border', {
variants: {
variant: {
default: 'bg-surface-raised',
elevated: 'bg-card shadow-lg shadow-black/20',
inner: 'bg-surface-overlay',
transparent: 'bg-transparent border-transparent',
},
},
defaultVariants: { variant: 'default' },
});
/** 뱃지 변형 — 위험도/상태별 150회+ 반복 패턴 통합
*
* 가독성 정책:
* - 배경: 색상별 진한 솔리드(-400) — 명확한 분류 식별
* - 텍스트: 시맨틱 토큰 text-on-bright (theme.css = #0f172a) — 테마 무관 일관 가독성
* - 보더: 같은 색상 계열 -600 (배경 강조)
* - 가운데 정렬
* - 폰트 크기: rem 기반 (root font-size 대비 비율) — 화면 비율에 따라 자동 조정
*
* className override는 cn(tailwind-merge) 덕분에 같은 그룹(text-color/font-size/bg) 충돌 시
* 마지막 명시값이 적용 — !important 없이 의도된 override만 허용.
*/
export const badgeVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md border px-2 py-0.5 font-semibold transition-colors text-center text-on-bright',
{
variants: {
intent: {
critical: 'bg-red-400 border-red-600',
high: 'bg-orange-400 border-orange-600',
warning: 'bg-yellow-400 border-yellow-600',
info: 'bg-blue-400 border-blue-600',
success: 'bg-green-400 border-green-600',
muted: 'bg-slate-400 border-slate-600',
purple: 'bg-purple-400 border-purple-600',
cyan: 'bg-cyan-400 border-cyan-600',
},
// rem 기반 — root font-size 대비 비율, 화면 비율 변경 시 자동 조정
// xs ≈ 11px, sm ≈ 12px, md ≈ 13px, lg ≈ 14px (root 14px 기준)
size: {
xs: 'text-[0.6875rem] leading-tight',
sm: 'text-[0.75rem] leading-tight',
md: 'text-[0.8125rem] leading-tight',
lg: 'text-[0.875rem] leading-tight',
},
},
defaultVariants: { intent: 'info', size: 'sm' },
},
);
/** 상태 점(dot) 변형 */
export const statusDotVariants = cva('rounded-full', {
variants: {
status: {
online: 'bg-green-500',
warning: 'bg-yellow-500',
danger: 'bg-red-500',
offline: 'bg-slate-500',
},
size: {
sm: 'w-1.5 h-1.5',
md: 'w-2 h-2',
lg: 'w-2.5 h-2.5',
},
},
defaultVariants: { status: 'online', size: 'md' },
});
export type CardVariant = 'default' | 'elevated' | 'inner' | 'transparent';
export type BadgeIntent = 'critical' | 'high' | 'warning' | 'info' | 'success' | 'muted' | 'purple' | 'cyan';
export type BadgeSize = 'xs' | 'sm' | 'md' | 'lg';
export type StatusDotStatus = 'online' | 'warning' | 'danger' | 'offline';