Files
2026-05-27 23:47:53 +01:00

4.1 KiB
Raw Permalink Blame History

<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 (numeric with precision + optional units, text, scrolling with CSS-keyframe overflow scroll).
  • Scrolling mode honours prefers-reduced-motion by disabling the animation via a @media block injected alongside the keyframes.
  • role="status" with aria-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.