The FlexRadio panel, AmpCard and TunerCard each carried their own copy of the MeterBar (LED-bar) component. Even small drift between the copies made the tuner meters look slightly off in height/LED size vs the others. Extracted one shared components/MeterBar.tsx (segments, bar height, padding) and imported it in all three, so every meter renders at exactly the same size. No behaviour change.
47 lines
2.7 KiB
TypeScript
47 lines
2.7 KiB
TypeScript
import { cn } from '@/lib/utils';
|
|
|
|
// MeterBar is the ONE LED-bar meter used across the FlexRadio panel, the amplifier
|
|
// cards and the Tuner Genius card, so every meter renders at exactly the same size
|
|
// (segment count, bar height, padding). Keep it the single source of truth — don't
|
|
// re-declare a local copy in a panel, or the meters drift out of sync.
|
|
export const METER_SEGMENTS = 26;
|
|
|
|
export function MeterBar({ label, value, unit, lo, hi, accent = '#16a34a', extra, display, segColor, onClick, title, compact }: {
|
|
label: string; value: number; unit?: string; lo: number; hi: number; accent?: string; extra?: string; display?: string;
|
|
segColor?: (frac: number) => string; onClick?: () => void; title?: string; compact?: boolean;
|
|
}) {
|
|
const span = hi - lo;
|
|
const pct = span > 0 ? Math.max(0, Math.min(100, ((value - lo) / span) * 100)) : 0;
|
|
const lit = Math.round((pct / 100) * METER_SEGMENTS);
|
|
return (
|
|
<div onClick={onClick} title={title}
|
|
className={cn('rounded-lg border border-border/70 bg-gradient-to-b from-card to-muted/40 shadow-sm min-w-0',
|
|
compact ? 'px-2 py-1' : 'px-2.5 py-2',
|
|
onClick && 'cursor-pointer hover:border-primary/60 hover:from-muted/40')}>
|
|
<div className={cn('flex items-baseline justify-between gap-1', compact ? 'mb-1' : 'mb-1.5')}>
|
|
<span className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground truncate">{label}</span>
|
|
<span className={cn('font-mono font-bold tabular-nums whitespace-nowrap text-foreground/90', compact ? 'text-xs' : 'text-sm')}>
|
|
{display !== undefined ? display : (
|
|
<>{Math.abs(value) >= 100 ? value.toFixed(0) : value.toFixed(1)}<span className="text-muted-foreground text-[10px] ml-0.5">{unit}</span></>
|
|
)}
|
|
</span>
|
|
</div>
|
|
{/* LED bar — recessed track + gradient segments for a cleaner instrument look. */}
|
|
<div className={cn('flex gap-[2px] items-stretch rounded-[3px] bg-black/10 p-[2px]', compact ? 'h-2' : 'h-3')}>
|
|
{Array.from({ length: METER_SEGMENTS }).map((_, i) => {
|
|
const on = i < lit;
|
|
const frac = i / METER_SEGMENTS;
|
|
const col = segColor ? segColor(frac) : (frac > 0.82 ? '#dc2626' : accent);
|
|
return (
|
|
<div key={i} className="flex-1 rounded-[2px] transition-colors duration-100"
|
|
style={on
|
|
? { background: `linear-gradient(to bottom, ${col}, ${col}cc)`, boxShadow: `0 0 4px ${col}88` }
|
|
: { background: '#cfc6ad', opacity: 0.35 }} />
|
|
);
|
|
})}
|
|
</div>
|
|
{extra && !compact && <div className="text-[10px] text-muted-foreground/70 mt-1 text-right font-mono">{extra}</div>}
|
|
</div>
|
|
);
|
|
}
|