4.1 KiB
<DigitalDisplay>
(no submodules)
Purpose
Read-only 7-segment / LCD-style readout for the control surface. The
component is a typed information sink: it accepts a discriminated
mode (numeric | text | scrolling) and renders the appropriate
visualisation. Although <DigitalDisplay> implements the same
BoundControlProps contract as input controls (so it can drop into
the same schema slots), it never emits onChange — the display is
pure output.
Features
- Three modes (
numericwith precision + optional units,text,scrollingwith CSS-keyframe overflow scroll). - Scrolling mode honours
prefers-reduced-motionby disabling the animation via a@mediablock injected alongside the keyframes. role="status"witharia-live="polite"so screen readers receive numeric updates without taking focus.- A hidden screen-reader-only span echoes the numeric / text content outside the clipped visual area, so SR users get the full value even when the visible LCD is too short.
- CSS-variable theming:
--cs-display-font,--cs-display-color,--cs-display-bg. - NaN / Infinity render as
—(em-dash).
Usage: Basic
import { DigitalDisplay } from "@suaveplan/react-control-surface";
export function TempoReadout({ bpm }: { bpm: number }) {
return (
<DigitalDisplay
mode={{ kind: "numeric", precision: 1, units: "BPM" }}
value={bpm}
min={20}
max={300}
onChange={() => undefined}
label="Tempo"
/>
);
}
Usage: Advanced
A patch-name readout that scrolls when the patch name is longer than the LCD window:
import { DigitalDisplay } from "@suaveplan/react-control-surface";
export function PatchName({ name }: { name: string }) {
return (
<DigitalDisplay
mode={{
kind: "scrolling",
text: name,
windowChars: 8,
speedPxPerSec: 40,
}}
value={0}
min={0}
max={1}
onChange={() => undefined}
widthPx={160}
heightPx={28}
label="Patch Name"
/>
);
}
API Reference
| Prop | Type | Default |
|---|---|---|
mode |
{kind: "numeric" | "text" | "scrolling", ...} |
required |
value |
number |
required |
min/max |
number |
required |
onChange |
(v: number) => void |
required |
disabled |
boolean |
false |
label |
string |
— |
ariaLabel |
string |
— |
widthPx |
number |
120 |
heightPx |
number |
32 |
className |
string |
— |
numeric mode adds: precision?: number (default 0), units?: string.
text mode adds: text: string.
scrolling mode adds: text: string, speedPxPerSec?: number
(default 30), windowChars?: number (default 8).
Implementation Notes
Scrolling mode injects an inline <style> block with a unique keyframe
name (cs-display-scroll); the keyframe duration is derived from
text length × per-glyph width / configured speed, so longer strings
scroll at a constant linear-px-per-second rate regardless of length.
The @media (prefers-reduced-motion: reduce) rule short-circuits the
animation to none, satisfying WAI-ARIA APG guidance for moving
content. The component intentionally does not use requestAnimationFrame
or React state ticks — CSS handles the animation so the React render
graph stays static.