fix(frontend): 카탈로그 배지 테마 분리 + 단속 조치 가독성 수정

변경:
- badgeVariants 8 intent 모두 라이트/다크 팔레트 분리
  - 다크: 밝은 솔리드 배경(-400) + slate-900 글자 + 강한 보더(-600)
  - 라이트: 파스텔 배경(-100) + 진한 글자(-900) + 소프트 보더(-300)
  - base에서 text-on-bright 제거 (intent별로 관리)
- classes 기반 카탈로그 4종에 dark: 변형 추가로 라이트 모드 대응:
  - eventStatuses: bg-red-100 text-red-800 dark:bg-red-500/20 dark:text-red-400
  - enforcementResults: 동일 패턴 (red/purple/yellow/green)
  - patrolStatuses: border 포함 (7 상태)
  - enforcementActions: classes 필드 신규 추가 (기존에 없어서 fallback grey로 떨어져
    라이트 모드에서 글자 안 보이던 원인)
- CatalogSection fallback classes도 dark: 변형 추가 (안전장치)
- enforcementActions에 getEnforcementActionClasses() 헬퍼 신규

빌드 검증:
- tsc , vite build 
- CSS 확인: .dark\:bg-red-400:is(.dark *) 컴파일 정상
This commit is contained in:
htlee 2026-04-08 11:23:38 +09:00
부모 e0b51efc54
커밋 f589cb0f94
6개의 변경된 파일58개의 추가작업 그리고 32개의 파일을 삭제

파일 보기

@ -70,7 +70,10 @@ function CatalogBadges({ catalog, idPrefix }: { catalog: AnyCatalog; idPrefix: s
</Badge>
) : (
<span
className={`inline-flex items-center px-2 py-0.5 rounded-md border text-[12px] font-semibold ${getFallbackClasses(meta) ?? 'bg-slate-500/20 text-slate-300 border-slate-500/30'}`}
className={`inline-flex items-center px-2 py-0.5 rounded-md border text-[12px] font-semibold ${
getFallbackClasses(meta) ??
'bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-500/20 dark:text-slate-300 dark:border-slate-500/30'
}`}
>
{label}
</span>

파일 보기

@ -19,29 +19,34 @@ export const cardVariants = cva('rounded-xl border border-border', {
/** / 150+
*
* :
* - 배경: 색상별 (-400)
* - 텍스트: 시맨틱 text-on-bright (theme.css = #0f172a)
* - 보더: 같은 -600 ( )
* ( ):
* - ** **: (-400) + (slate-900) + (-600)
*
* - ** **: (-100) + (-900) + (-300)
* ,
* -
* - 크기: rem (root font-size )
* - 크기: rem
*
* className override는 cn(tailwind-merge) (text-color/font-size/bg)
* !important override만 .
* className override는 cn(tailwind-merge) .
*/
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',
'inline-flex items-center justify-center whitespace-nowrap rounded-md border px-2 py-0.5 font-semibold transition-colors text-center',
{
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',
critical:
'bg-red-100 text-red-900 border-red-300 dark:bg-red-400 dark:text-slate-900 dark:border-red-600',
high: 'bg-orange-100 text-orange-900 border-orange-300 dark:bg-orange-400 dark:text-slate-900 dark:border-orange-600',
warning:
'bg-yellow-100 text-yellow-900 border-yellow-300 dark:bg-yellow-400 dark:text-slate-900 dark:border-yellow-600',
info: 'bg-blue-100 text-blue-900 border-blue-300 dark:bg-blue-400 dark:text-slate-900 dark:border-blue-600',
success:
'bg-green-100 text-green-900 border-green-300 dark:bg-green-400 dark:text-slate-900 dark:border-green-600',
muted:
'bg-slate-100 text-slate-800 border-slate-300 dark:bg-slate-400 dark:text-slate-900 dark:border-slate-600',
purple:
'bg-purple-100 text-purple-900 border-purple-300 dark:bg-purple-400 dark:text-slate-900 dark:border-purple-600',
cyan: 'bg-cyan-100 text-cyan-900 border-cyan-300 dark:bg-cyan-400 dark:text-slate-900 dark:border-cyan-600',
},
// rem 기반 — root font-size 대비 비율, 화면 비율 변경 시 자동 조정
// xs ≈ 11px, sm ≈ 12px, md ≈ 13px, lg ≈ 14px (root 14px 기준)

파일 보기

@ -19,6 +19,7 @@ export interface EnforcementActionMeta {
code: EnforcementAction;
i18nKey: string;
fallback: { ko: string; en: string };
classes: string;
hex: string;
order: number;
}
@ -28,6 +29,7 @@ export const ENFORCEMENT_ACTIONS: Record<EnforcementAction, EnforcementActionMet
code: 'CAPTURE',
i18nKey: 'enforcementAction.CAPTURE',
fallback: { ko: '나포', en: 'Capture' },
classes: 'bg-red-100 text-red-800 dark:bg-red-500/20 dark:text-red-400',
hex: '#ef4444',
order: 1,
},
@ -35,6 +37,7 @@ export const ENFORCEMENT_ACTIONS: Record<EnforcementAction, EnforcementActionMet
code: 'INSPECT',
i18nKey: 'enforcementAction.INSPECT',
fallback: { ko: '검문', en: 'Inspect' },
classes: 'bg-amber-100 text-amber-800 dark:bg-amber-500/20 dark:text-amber-400',
hex: '#f59e0b',
order: 2,
},
@ -42,6 +45,7 @@ export const ENFORCEMENT_ACTIONS: Record<EnforcementAction, EnforcementActionMet
code: 'WARN',
i18nKey: 'enforcementAction.WARN',
fallback: { ko: '경고', en: 'Warn' },
classes: 'bg-blue-100 text-blue-800 dark:bg-blue-500/20 dark:text-blue-400',
hex: '#3b82f6',
order: 3,
},
@ -49,6 +53,7 @@ export const ENFORCEMENT_ACTIONS: Record<EnforcementAction, EnforcementActionMet
code: 'DISPERSE',
i18nKey: 'enforcementAction.DISPERSE',
fallback: { ko: '퇴거', en: 'Disperse' },
classes: 'bg-violet-100 text-violet-800 dark:bg-violet-500/20 dark:text-violet-400',
hex: '#8b5cf6',
order: 4,
},
@ -56,6 +61,7 @@ export const ENFORCEMENT_ACTIONS: Record<EnforcementAction, EnforcementActionMet
code: 'TRACK',
i18nKey: 'enforcementAction.TRACK',
fallback: { ko: '추적', en: 'Track' },
classes: 'bg-cyan-100 text-cyan-800 dark:bg-cyan-500/20 dark:text-cyan-400',
hex: '#06b6d4',
order: 5,
},
@ -63,11 +69,16 @@ export const ENFORCEMENT_ACTIONS: Record<EnforcementAction, EnforcementActionMet
code: 'EVIDENCE',
i18nKey: 'enforcementAction.EVIDENCE',
fallback: { ko: '증거수집', en: 'Evidence' },
classes: 'bg-slate-100 text-slate-700 dark:bg-slate-500/20 dark:text-slate-300',
hex: '#64748b',
order: 6,
},
};
export function getEnforcementActionClasses(action: string): string {
return getEnforcementActionMeta(action)?.classes ?? 'bg-muted text-muted-foreground';
}
export function getEnforcementActionMeta(action: string): EnforcementActionMeta | undefined {
return ENFORCEMENT_ACTIONS[action as EnforcementAction];
}

파일 보기

@ -27,28 +27,28 @@ export const ENFORCEMENT_RESULTS: Record<EnforcementResult, EnforcementResultMet
code: 'PUNISHED',
i18nKey: 'enforcementResult.PUNISHED',
fallback: { ko: '처벌', en: 'Punished' },
classes: 'bg-red-500/20 text-red-400',
classes: 'bg-red-100 text-red-800 dark:bg-red-500/20 dark:text-red-400',
order: 1,
},
REFERRED: {
code: 'REFERRED',
i18nKey: 'enforcementResult.REFERRED',
fallback: { ko: '수사의뢰', en: 'Referred' },
classes: 'bg-purple-500/20 text-purple-400',
classes: 'bg-purple-100 text-purple-800 dark:bg-purple-500/20 dark:text-purple-400',
order: 2,
},
WARNED: {
code: 'WARNED',
i18nKey: 'enforcementResult.WARNED',
fallback: { ko: '경고', en: 'Warned' },
classes: 'bg-yellow-500/20 text-yellow-400',
classes: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-500/20 dark:text-yellow-400',
order: 3,
},
RELEASED: {
code: 'RELEASED',
i18nKey: 'enforcementResult.RELEASED',
fallback: { ko: '훈방', en: 'Released' },
classes: 'bg-green-500/20 text-green-400',
classes: 'bg-green-100 text-green-800 dark:bg-green-500/20 dark:text-green-400',
order: 4,
},
FALSE_POSITIVE: {

파일 보기

@ -27,28 +27,28 @@ export const EVENT_STATUSES: Record<EventStatus, EventStatusMeta> = {
code: 'NEW',
i18nKey: 'eventStatus.NEW',
fallback: { ko: '신규', en: 'New' },
classes: 'bg-red-500/20 text-red-400',
classes: 'bg-red-100 text-red-800 dark:bg-red-500/20 dark:text-red-400',
order: 1,
},
ACK: {
code: 'ACK',
i18nKey: 'eventStatus.ACK',
fallback: { ko: '확인', en: 'Acknowledged' },
classes: 'bg-orange-500/20 text-orange-400',
classes: 'bg-orange-100 text-orange-800 dark:bg-orange-500/20 dark:text-orange-400',
order: 2,
},
IN_PROGRESS: {
code: 'IN_PROGRESS',
i18nKey: 'eventStatus.IN_PROGRESS',
fallback: { ko: '처리중', en: 'In Progress' },
classes: 'bg-blue-500/20 text-blue-400',
classes: 'bg-blue-100 text-blue-800 dark:bg-blue-500/20 dark:text-blue-400',
order: 3,
},
RESOLVED: {
code: 'RESOLVED',
i18nKey: 'eventStatus.RESOLVED',
fallback: { ko: '완료', en: 'Resolved' },
classes: 'bg-green-500/20 text-green-400',
classes: 'bg-green-100 text-green-800 dark:bg-green-500/20 dark:text-green-400',
order: 4,
},
FALSE_POSITIVE: {

파일 보기

@ -29,49 +29,56 @@ export const PATROL_STATUSES: Record<PatrolStatus, PatrolStatusMeta> = {
code: 'IN_PURSUIT',
i18nKey: 'patrolStatus.IN_PURSUIT',
fallback: { ko: '추적중', en: 'In Pursuit' },
classes: 'bg-red-500/20 text-red-400 border-red-500/30',
classes:
'bg-red-100 text-red-800 border-red-300 dark:bg-red-500/20 dark:text-red-400 dark:border-red-500/30',
order: 1,
},
INSPECTING: {
code: 'INSPECTING',
i18nKey: 'patrolStatus.INSPECTING',
fallback: { ko: '검문중', en: 'Inspecting' },
classes: 'bg-orange-500/20 text-orange-400 border-orange-500/30',
classes:
'bg-orange-100 text-orange-800 border-orange-300 dark:bg-orange-500/20 dark:text-orange-400 dark:border-orange-500/30',
order: 2,
},
ON_PATROL: {
code: 'ON_PATROL',
i18nKey: 'patrolStatus.ON_PATROL',
fallback: { ko: '초계중', en: 'On Patrol' },
classes: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
classes:
'bg-blue-100 text-blue-800 border-blue-300 dark:bg-blue-500/20 dark:text-blue-400 dark:border-blue-500/30',
order: 3,
},
RETURNING: {
code: 'RETURNING',
i18nKey: 'patrolStatus.RETURNING',
fallback: { ko: '귀항중', en: 'Returning' },
classes: 'bg-purple-500/20 text-purple-400 border-purple-500/30',
classes:
'bg-purple-100 text-purple-800 border-purple-300 dark:bg-purple-500/20 dark:text-purple-400 dark:border-purple-500/30',
order: 4,
},
AVAILABLE: {
code: 'AVAILABLE',
i18nKey: 'patrolStatus.AVAILABLE',
fallback: { ko: '가용', en: 'Available' },
classes: 'bg-green-500/20 text-green-400 border-green-500/30',
classes:
'bg-green-100 text-green-800 border-green-300 dark:bg-green-500/20 dark:text-green-400 dark:border-green-500/30',
order: 5,
},
STANDBY: {
code: 'STANDBY',
i18nKey: 'patrolStatus.STANDBY',
fallback: { ko: '대기', en: 'Standby' },
classes: 'bg-slate-500/20 text-slate-400 border-slate-500/30',
classes:
'bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-500/20 dark:text-slate-400 dark:border-slate-500/30',
order: 6,
},
MAINTENANCE: {
code: 'MAINTENANCE',
i18nKey: 'patrolStatus.MAINTENANCE',
fallback: { ko: '정비중', en: 'Maintenance' },
classes: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
classes:
'bg-yellow-100 text-yellow-800 border-yellow-300 dark:bg-yellow-500/20 dark:text-yellow-400 dark:border-yellow-500/30',
order: 7,
},
};