diff --git a/src/components/common/Badge.module.css b/src/components/common/Badge.module.css new file mode 100644 index 000000000..216c9c2ae --- /dev/null +++ b/src/components/common/Badge.module.css @@ -0,0 +1,82 @@ +.badge { + display: inline-flex; + align-items: center; + align-self: flex-start; + gap: 6px; + padding: 2px 8px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + line-height: 1.4; + white-space: nowrap; +} + +.dot { + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; +} + +.good { + color: #15803d; + background: #dcfce7; +} + +.good .dot { + background: #16a34a; +} + +.warning { + color: #a16207; + background: #fef9c3; +} + +.warning .dot { + background: #ca8a04; +} + +.danger { + color: #b91c1c; + background: #fee2e2; +} + +.danger .dot { + background: #dc2626; +} + +.gray { + color: var(--text-muted); + background: var(--surface-sunken); +} + +.gray .dot { + background: var(--text-muted); +} + +:global([data-theme='dark']) .good { + color: #86efac; + background: rgba(22, 163, 74, 0.15); +} + +:global([data-theme='dark']) .good .dot { + background: #4ade80; +} + +:global([data-theme='dark']) .warning { + color: #fde047; + background: rgba(202, 138, 4, 0.15); +} + +:global([data-theme='dark']) .warning .dot { + background: #facc15; +} + +:global([data-theme='dark']) .danger { + color: #fca5a5; + background: rgba(220, 38, 38, 0.15); +} + +:global([data-theme='dark']) .danger .dot { + background: #f87171; +} diff --git a/src/components/common/Badge.tsx b/src/components/common/Badge.tsx new file mode 100644 index 000000000..44becfe73 --- /dev/null +++ b/src/components/common/Badge.tsx @@ -0,0 +1,16 @@ +import styles from './Badge.module.css'; + +export interface BadgeProps { + variant: 'good' | 'warning' | 'danger' | 'gray'; + children: React.ReactNode; + dot?: boolean; +} + +export function Badge({ variant, children, dot = true }: BadgeProps) { + return ( + + {dot && } + {children} + + ); +} diff --git a/src/components/metrics/PerformanceCard.module.css b/src/components/metrics/PerformanceCard.module.css index 27b566c2d..9e0d1f296 100644 --- a/src/components/metrics/PerformanceCard.module.css +++ b/src/components/metrics/PerformanceCard.module.css @@ -3,21 +3,10 @@ } .card:hover { - box-shadow: 0 0 0 1px var(--primary); + box-shadow: 0 0 0 1px var(--text-primary); } .card.selected { - box-shadow: 0 0 0 1px var(--primary); + box-shadow: 0 0 0 1px var(--text-primary); } -.good .rating { - color: #0cce6b; -} - -.needs-improvement .rating { - color: #ffa400; -} - -.poor .rating { - color: #ff4e42; -} diff --git a/src/components/metrics/PerformanceCard.tsx b/src/components/metrics/PerformanceCard.tsx index be745d47b..6a6345aaa 100644 --- a/src/components/metrics/PerformanceCard.tsx +++ b/src/components/metrics/PerformanceCard.tsx @@ -2,6 +2,7 @@ import { config, useSpring } from '@react-spring/web'; import { Column, Text } from '@umami/react-zen'; import { useRef } from 'react'; import { AnimatedDiv } from '@/components/common/AnimatedDiv'; +import { Badge } from '@/components/common/Badge'; import { useMessages } from '@/components/hooks'; import { WEB_VITALS_THRESHOLDS } from '@/lib/constants'; import { formatNumber } from '@/lib/format'; @@ -16,6 +17,12 @@ export interface PerformanceCardProps { selected?: boolean; } +const RATING_VARIANTS = { + good: 'good', + 'needs-improvement': 'warning', + poor: 'danger', +} as const; + function getRating(metric: string, value: number): 'good' | 'needs-improvement' | 'poor' { const threshold = WEB_VITALS_THRESHOLDS[metric as keyof typeof WEB_VITALS_THRESHOLDS]; if (!threshold || value <= 0) return 'good'; @@ -45,7 +52,7 @@ export const PerformanceCard = ({ return ( {spring.value.to(n => formatValue(n))} - + {t(labels[rating === 'needs-improvement' ? 'needsImprovement' : rating])} - + ); };