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:
parent
d8b7b55505
commit
5b97091902
32 changed files with 1535 additions and 133 deletions
91
packages/dreamknob/src/layout/LabeledField.tsx
Normal file
91
packages/dreamknob/src/layout/LabeledField.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
96
packages/dreamknob/src/layout/Rack.tsx
Normal file
96
packages/dreamknob/src/layout/Rack.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue