Add Badge component and use it for performance page ratings
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user