Add Badge component and use it for performance page ratings

This commit is contained in:
Mike Cao
2026-03-15 20:44:25 -07:00
parent 4aa5f8411e
commit 31f322630d
4 changed files with 110 additions and 16 deletions
+82
View File
@@ -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;
}
+16
View File
@@ -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 (
<span className={`${styles.badge} ${styles[variant]}`}>
{dot && <span className={styles.dot} />}
{children}
</span>
);
}
@@ -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;
}
+10 -3
View File
@@ -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 (
<Column
className={`${styles.card} ${styles[rating]} ${selected ? styles.selected : ''}`}
className={`${styles.card} ${selected ? styles.selected : ''}`}
justifyContent="center"
paddingX="6"
paddingY="4"
@@ -62,9 +69,9 @@ export const PerformanceCard = ({
<Text size="4xl" weight="bold" wrap="nowrap">
<AnimatedDiv>{spring.value.to(n => formatValue(n))}</AnimatedDiv>
</Text>
<Text size="sm" className={styles.rating}>
<Badge variant={RATING_VARIANTS[rating]}>
{t(labels[rating === 'needs-improvement' ? 'needsImprovement' : rating])}
</Text>
</Badge>
</Column>
);
};