v1.1.1: second integration-feedback release

Readout legibility (the #1 ask):
- bold own-cell decimal point + colon in seven/fourteen-seg (not hairlines)
- weight prop thickens strokes; gap controls inter-cell kerning

Display colors:
- SegmentDisplay defaults to theme.ledGreen, AlphaDisplay to theme.ledAmber
- valueColor fn + value-space zones on SegmentDisplay (self-coloring readouts)
- charset: colon in both, $ and % in 14-seg; disabled + data-disabled

Knob captions: sublabel (second line) + labelSize (independent of dial size).

New components:
- ToggleSwitch (rocker/slider boolean, role=switch)
- Gauge (read-only radial arc meter, round sibling of Meter)
- TransportButton (play/stop/record/pause/panic, record blinks)
- LampRow (console-print lamp bank), LabeledField, Rack (panel chrome)

Responsiveness: fill on Meter + Fader (stretch to container).
Niceties: PushButton leadingIcon; theme ledGreen/ledAmber/panel tokens.

54 -> 63 exports. 38 unit tests. Browser-verified.
This commit is contained in:
Dreamodus 2026-07-13 19:20:23 -07:00
parent d8b7b55505
commit 5b97091902
32 changed files with 1535 additions and 133 deletions

View file

@ -0,0 +1,91 @@
import * as React from 'react';
import { useKnobTheme } from '../core/theme';
export interface LabeledFieldProps {
/** Field name printed beside the control. */
label: string;
/** The control (a knob, fader, ScrubField, ToggleSwitch, …). */
children: React.ReactNode;
/** Unit printed after the control, e.g. "px", "dB". */
unit?: string;
/**
* Label column width set the same value on a stack of fields so their
* controls line up. Number = px. Default: auto.
*/
labelWidth?: number | string;
/** 'row' (label · control · unit, default) or 'column' (label above). */
orientation?: 'row' | 'column';
/** Push the control to the right edge of the row. Default: false. */
spread?: boolean;
gap?: number;
labelColor?: string;
className?: string;
style?: React.CSSProperties;
}
/**
* One aligned inspector row: a label, a control and an optional unit. Give a
* stack of fields the same `labelWidth` and their controls line up the
* boilerplate every properties panel rewrites, owned in one place.
*/
export const LabeledField: React.FC<LabeledFieldProps> = ({
label,
children,
unit,
labelWidth,
orientation = 'row',
spread = false,
gap = 10,
labelColor,
className,
style,
}) => {
const theme = useKnobTheme();
const column = orientation === 'column';
const labelEl = (
<span
style={{
flex: 'none',
width: column ? undefined : labelWidth,
fontFamily: theme.fontUI,
fontSize: 11,
letterSpacing: '0.05em',
textTransform: 'uppercase',
color: labelColor ?? theme.label,
whiteSpace: 'nowrap',
}}
>
{label}
</span>
);
return (
<div
className={className}
style={{
display: 'flex',
flexDirection: column ? 'column' : 'row',
alignItems: column ? 'flex-start' : 'center',
gap: column ? 4 : gap,
...style,
}}
>
{labelEl}
<div
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 6,
marginLeft: spread && !column ? 'auto' : undefined,
}}
>
{children}
{unit && (
<span style={{ fontFamily: theme.fontMono, fontSize: 11, color: theme.label, whiteSpace: 'nowrap' }}>
{unit}
</span>
)}
</div>
</div>
);
};

View file

@ -0,0 +1,96 @@
import * as React from 'react';
import { useKnobTheme } from '../core/theme';
export interface RackProps {
/** 'row' lays out strips side by side (a rack); 'column' stacks a strip. Default: 'row'. */
orientation?: 'row' | 'column';
/** Panel caption printed at the top of the chrome. */
title?: string;
/** Spacing between children in px. Default: 14. */
gap?: number;
/** Inner padding in px. Default: 14. */
padding?: number;
/** Draw the panel chrome (border + background). Default: true. */
chrome?: boolean;
/** Cross-axis alignment. Default: 'stretch' for column, 'flex-start' for row. */
align?: React.CSSProperties['alignItems'];
accentColor?: string;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
}
/**
* A rack / channel-strip container that owns the panel chrome and spacing, so a
* console section is declarative: nest `Rack`s (a row of column strips) and the
* borders, background and gaps come for free.
*/
export const Rack: React.FC<RackProps> = ({
orientation = 'row',
title,
gap = 14,
padding = 14,
chrome = true,
align,
accentColor,
className,
style,
children,
}) => {
const theme = useKnobTheme();
const column = orientation === 'column';
return (
<section
className={className}
style={{
display: 'inline-flex',
flexDirection: 'column',
gap: title ? 10 : 0,
padding: chrome ? padding : 0,
borderRadius: 10,
background: chrome ? theme.panel : undefined,
border: chrome ? '1px solid rgba(128,128,128,0.18)' : undefined,
boxShadow: chrome ? 'inset 0 1px 0 rgba(255,255,255,0.04)' : undefined,
...style,
}}
>
{title && (
<header
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
fontFamily: theme.fontUI,
fontSize: 10.5,
fontWeight: 600,
letterSpacing: '0.14em',
textTransform: 'uppercase',
color: theme.label,
}}
>
<span
aria-hidden="true"
style={{
width: 6,
height: 6,
borderRadius: '50%',
background: accentColor ?? theme.accent ?? theme.zoneGood,
flex: 'none',
}}
/>
{title}
</header>
)}
<div
style={{
display: 'flex',
flexDirection: column ? 'column' : 'row',
alignItems: align ?? (column ? 'stretch' : 'flex-start'),
gap,
}}
>
{children}
</div>
</section>
);
};