91 lines
2.7 KiB
TypeScript
91 lines
2.7 KiB
TypeScript
|
|
import React, { memo } from 'react';
|
||
|
|
import { cn } from '../../lib/utils';
|
||
|
|
|
||
|
|
interface ResourceBarProps {
|
||
|
|
label: string;
|
||
|
|
value: number | null | undefined;
|
||
|
|
className?: string;
|
||
|
|
/** Slightly taller track for GPU cards. */
|
||
|
|
size?: 'sm' | 'md';
|
||
|
|
/** Accent color tone override */
|
||
|
|
tone?: 'primary' | 'sky' | 'emerald' | 'amber' | 'rose' | 'auto';
|
||
|
|
}
|
||
|
|
|
||
|
|
const toneClasses: Record<string, string> = {
|
||
|
|
primary: 'bg-primary',
|
||
|
|
sky: 'bg-sky-500',
|
||
|
|
emerald: 'bg-emerald-500',
|
||
|
|
amber: 'bg-amber-500',
|
||
|
|
rose: 'bg-rose-500',
|
||
|
|
auto: '',
|
||
|
|
};
|
||
|
|
|
||
|
|
function barToneClass(clamped: number): string {
|
||
|
|
if (clamped > 85) return 'bg-destructive';
|
||
|
|
if (clamped > 60) return 'bg-amber-500';
|
||
|
|
return 'bg-primary';
|
||
|
|
}
|
||
|
|
|
||
|
|
function barGlowStyle(clamped: number): React.CSSProperties {
|
||
|
|
if (clamped <= 60) return {};
|
||
|
|
const intensity = Math.min(1, (clamped - 60) / 40);
|
||
|
|
const color = clamped > 85
|
||
|
|
? 'hsl(var(--destructive) / 0.4)'
|
||
|
|
: 'rgba(245, 158, 11, 0.4)';
|
||
|
|
return {
|
||
|
|
boxShadow: `0 0 ${4 + intensity * 6}px ${color}`,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export const ResourceBar = memo(function ResourceBar({
|
||
|
|
label,
|
||
|
|
value,
|
||
|
|
className,
|
||
|
|
size = 'sm',
|
||
|
|
tone = 'auto',
|
||
|
|
}: ResourceBarProps) {
|
||
|
|
const finite = typeof value === 'number' && Number.isFinite(value);
|
||
|
|
const clamped = finite ? Math.max(0, Math.min(100, value)) : 0;
|
||
|
|
const fillClass = tone === 'auto' ? barToneClass(clamped) : toneClasses[tone];
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={cn('flex items-center gap-2 min-w-0', className)}>
|
||
|
|
{label ? (
|
||
|
|
<span className="text-[10px] text-muted-foreground w-7 shrink-0">{label}</span>
|
||
|
|
) : null}
|
||
|
|
<div
|
||
|
|
className={cn(
|
||
|
|
'flex-1 rounded-full overflow-hidden min-w-[48px]',
|
||
|
|
'bg-muted/60',
|
||
|
|
'shadow-inner',
|
||
|
|
size === 'md' ? 'h-2' : 'h-1.5',
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
<div
|
||
|
|
className={cn(
|
||
|
|
'h-full rounded-full transition-[width,background-color] duration-500 ease-out motion-reduce:transition-none relative',
|
||
|
|
finite ? fillClass : 'opacity-0',
|
||
|
|
)}
|
||
|
|
style={{
|
||
|
|
width: `${clamped}%`,
|
||
|
|
...(finite && clamped > 60 ? barGlowStyle(clamped) : {}),
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{/* Subtle highlight sheen */}
|
||
|
|
{finite && clamped > 5 && (
|
||
|
|
<div
|
||
|
|
className="absolute top-0 left-0 right-0 h-1/2 rounded-t-full opacity-30"
|
||
|
|
style={{
|
||
|
|
background: 'linear-gradient(to bottom, rgba(255,255,255,0.5), transparent)',
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<span className="text-[10px] tabular-nums text-muted-foreground w-10 text-right shrink-0">
|
||
|
|
{finite ? `${value.toFixed(1)}%` : '--'}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
});
|