// A thin horizontal bar showing one value against a maximum — a table cell's "how busy is this // row" beside the number, or a quota. Decorative by default: with no `label` it is hidden from // the accessibility tree, because the number beside it is the fact. With one it is a `meter` role // with the value spoken. // // An SVG with a `width` ATTRIBUTE rather than a styled box: it paints identically on the server // copy and the hydrated one and asks nothing of the CSP's `style-src-attr`. import type { JSX } from 'solid-js'; import { cx } from '../cx'; import styles from './Meter.module.scss'; import { meterAria, meterShare, meterWidth } from './meter-view'; import type { Tone } from './variants'; export interface MeterProps { value: number; max: number; /** The accessible name. Omit for a purely decorative bar beside a visible number. */ label?: string | undefined; tone?: Tone | undefined; class?: string | undefined; } export function Meter(props: MeterProps): JSX.Element { const share = (): number => meterShare(props.value, props.max); const classes = (): string => cx(styles['meter'], styles[`tone-${props.tone ?? 'accent'}`], props.class); const bars = (): JSX.Element => [ , , ]; // Two literal elements rather than one with conditional ARIA: the `meter` role's value // attributes are only valid ON that role, and a lint that reads roles statically has to see it. if (props.label === undefined) { return ( ); } return ( // A native paints its fill through a vendor pseudo-element per browser, so it cannot // be drawn with tokens or identically everywhere; the role goes on the SVG that draws it. // biome-ignore lint/a11y/useSemanticElements: cannot be styled from tokens {bars()} ); }