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:
부모
e0b51efc54
커밋
f589cb0f94
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user