Meter
A level against a scale, read at a glance: how full it is, and where a target or a limit sits on the same scale. Loudness against a target, storage against a quota, a budget against its limit.
import { Meter } from '@basmilius/desktop-ui';import { Meter, useNow } from '@basmilius/desktop-ui';
import { formatDecimal, formatPercent } from '@basmilius/desktop-ui/format';
const FLOOR = -40;
const TOP = -4;
const TARGET = -14;
// A loudness that swells and dips, as a song under the playhead would.
const loudnessAt = (seconds: number, speed: number): number => -20 + 7 * Math.sin(seconds * speed) + 3 * Math.sin(seconds * speed * 3.7);
export default function MeterDemo() {
const now = useNow(100);
const seconds = now / 1000;
const rows = [
{ letter: 'M', name: 'Momentary loudness', loudness: loudnessAt(seconds, 2.3) },
{ letter: 'S', name: 'Short-term loudness', loudness: loudnessAt(seconds, 0.6) }
];
return (
<div className="flex w-full max-w-xs flex-col gap-6">
<div role="group" aria-label={`Loudness against ${TARGET} LUFS`} className="flex flex-col gap-0.5">
{rows.map((row) => (
<div key={row.letter} className="flex items-center gap-1.5 text-xs">
<span aria-hidden className="w-2.5 text-text-faint">
{row.letter}
</span>
<Meter
value={row.loudness}
min={FLOOR}
max={TOP}
marks={[TARGET]}
label={row.name}
valueText={`${formatDecimal(row.loudness)} LUFS`}
className="flex-1"
/>
<span aria-hidden className="w-8 text-right text-text-muted tabular-nums">
{formatDecimal(row.loudness)}
</span>
</div>
))}
</div>
<div className="flex flex-col gap-1.5 text-xs">
<div className="flex justify-between text-text-muted">
<span>Storage</span>
<span className="tabular-nums">{formatPercent(62)}</span>
</div>
<Meter value={62} marks={[80]} label="Storage" valueText={`${formatPercent(62)} used`} />
</div>
</div>
);
}value sits between min and max, 0 and 100 unless you set them, and a level past either end is held to it. null draws the track without a fill, for a level not measured yet. marks draws a line across the track at each value, such as a target or a limit.
<Meter value={loudness} min={-40} max={-4} marks={[-14]} label="Momentary loudness" valueText={`${formatDecimal(loudness)} LUFS`} />It is a thin bar of 6 pixels and has no width of its own: className gives it one, or a flex row stretches it. It draws only the bar, so the name and the value beside it are yours to lay out, and two meters stack in a toolbar row. The fill has no transition, so a level that updates many times a second follows without lag.
A screen reader hears it as a meter named by label. valueText is what it hears as the value, such as "-18.2 LUFS" or "Measuring"; without it, it hears the number.
Props
| Prop | Type | Default | |
|---|---|---|---|
value | number | null | Required. null draws an empty track. | |
label | string | Required. The name a screen reader reads. | |
min | number | 0 | |
max | number | 100 | |
marks | readonly number[] | [] | Values drawn as lines across the track. |
valueText | string | The value in words for a screen reader. | |
className | string | Its width. | |
ref | Ref<HTMLDivElement> |
MeterProps is an exported type.