Initial Commit
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# `<DigitalDisplay>`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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
|
||||
|
||||
```tsx
|
||||
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:
|
||||
|
||||
```tsx
|
||||
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.
|
||||
Reference in New Issue
Block a user