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
134
packages/dreamknob/src/skins/LampRow.tsx
Normal file
134
packages/dreamknob/src/skins/LampRow.tsx
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import * as React from 'react';
|
||||
import { useKnobTheme } from '../core/theme';
|
||||
|
||||
export interface LampRowItem {
|
||||
label: string;
|
||||
on: boolean;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface LampRowProps {
|
||||
lamps: readonly LampRowItem[];
|
||||
/** Lamp diameter in px. Default: 9. */
|
||||
size?: number;
|
||||
/** Caption placement relative to each lamp. Default: 'right'. */
|
||||
labelPosition?: 'right' | 'below';
|
||||
/** Make lamps clickable (a compact indicator toggle bank). */
|
||||
onToggle?: (index: number, on: boolean) => void;
|
||||
/** Spacing between lamp cells in px. Default: 14. */
|
||||
gap?: number;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
'aria-label'?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A row of small indicator lamps with console-print captions — the
|
||||
* `● CUE ● MUTE` bank hardware panels use, standardized. Read-only status by
|
||||
* default; pass `onToggle` to make the lamps pressable.
|
||||
*/
|
||||
export const LampRow: React.FC<LampRowProps> = ({
|
||||
lamps,
|
||||
size = 9,
|
||||
labelPosition = 'right',
|
||||
onToggle,
|
||||
gap = 14,
|
||||
disabled = false,
|
||||
className,
|
||||
style,
|
||||
...aria
|
||||
}) => {
|
||||
const theme = useKnobTheme();
|
||||
const below = labelPosition === 'below';
|
||||
const interactive = !!onToggle;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
role="group"
|
||||
aria-label={aria['aria-label']}
|
||||
data-disabled={disabled ? '' : undefined}
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap,
|
||||
opacity: disabled ? 0.45 : 1,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{lamps.map((lamp, i) => {
|
||||
const accent = lamp.color ?? theme.accent ?? theme.zoneGood;
|
||||
const dot = (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
flex: 'none',
|
||||
borderRadius: '50%',
|
||||
background: lamp.on ? accent : 'rgba(255,255,255,0.1)',
|
||||
border: '1px solid rgba(0,0,0,0.5)',
|
||||
boxShadow: lamp.on
|
||||
? `0 0 ${size * 0.8}px ${accent}, inset 0 0 ${size * 0.25}px rgba(255,255,255,0.5)`
|
||||
: 'inset 0 1px 2px rgba(0,0,0,0.6)',
|
||||
transition: 'background 80ms, box-shadow 80ms',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const caption = (
|
||||
<span
|
||||
style={{
|
||||
fontFamily: theme.fontUI,
|
||||
fontSize: 9.5,
|
||||
fontWeight: 600,
|
||||
letterSpacing: '0.12em',
|
||||
textTransform: 'uppercase',
|
||||
color: lamp.on ? theme.text : theme.label,
|
||||
whiteSpace: 'nowrap',
|
||||
transition: 'color 80ms',
|
||||
}}
|
||||
>
|
||||
{lamp.label}
|
||||
</span>
|
||||
);
|
||||
const inner = (
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
flexDirection: below ? 'column' : 'row',
|
||||
alignItems: 'center',
|
||||
gap: below ? 4 : 6,
|
||||
}}
|
||||
>
|
||||
{dot}
|
||||
{caption}
|
||||
</span>
|
||||
);
|
||||
|
||||
return interactive ? (
|
||||
<button
|
||||
key={i}
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={lamp.on}
|
||||
aria-label={lamp.label}
|
||||
disabled={disabled}
|
||||
onClick={() => onToggle!(i, !lamp.on)}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
padding: 0,
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
font: 'inherit',
|
||||
}}
|
||||
>
|
||||
{inner}
|
||||
</button>
|
||||
) : (
|
||||
<React.Fragment key={i}>{inner}</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue