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.
122 lines
3.6 KiB
TypeScript
122 lines
3.6 KiB
TypeScript
import * as React from 'react';
|
|
import { Knob } from '../components/Knob';
|
|
import { Pointer } from '../primitives/Pointer';
|
|
import { Ticks } from '../primitives/Ticks';
|
|
import { KnobLabel } from '../primitives/Text';
|
|
import { useKnobTheme } from '../core/theme';
|
|
import type { SkinProps } from './shared';
|
|
|
|
export interface SteppedKnobProps extends SkinProps {
|
|
/** Position names, e.g. ['LP','BP','HP']. Sets the number of detents. */
|
|
positions?: readonly string[];
|
|
/** Number of detents when `positions` is not given. */
|
|
steps?: number;
|
|
color?: string;
|
|
tickColor?: string;
|
|
faceColor?: string;
|
|
textColor?: string;
|
|
labelColor?: string;
|
|
}
|
|
|
|
/** Detented selector knob — mode/range switches with hard stops. */
|
|
export const SteppedKnob = React.forwardRef<HTMLDivElement, SteppedKnobProps>(function SteppedKnob({
|
|
size = 90,
|
|
positions,
|
|
steps,
|
|
color: colorProp,
|
|
tickColor,
|
|
faceColor = '#1c1d22',
|
|
textColor,
|
|
labelColor,
|
|
label,
|
|
sublabel,
|
|
labelSize,
|
|
showValue = true,
|
|
format,
|
|
focusRing,
|
|
className,
|
|
style,
|
|
...core
|
|
}, ref) {
|
|
const theme = useKnobTheme();
|
|
const color = colorProp ?? theme.accent ?? '#ffd23e';
|
|
const detents = positions ? positions.length : steps ?? 5;
|
|
const min = core.min ?? 0;
|
|
const max = core.max ?? (positions ? positions.length - 1 : 100);
|
|
|
|
return (
|
|
<Knob
|
|
ref={ref}
|
|
size={size}
|
|
className={className}
|
|
style={style}
|
|
focusRing={focusRing}
|
|
{...core}
|
|
min={min}
|
|
max={max}
|
|
steps={detents}
|
|
getAriaValueText={core.getAriaValueText ?? (positions ? (v: number) => positions[Math.round(((v - min) / (max - min || 1)) * (detents - 1))] ?? String(v) : format ? (v: number) => format(v) : undefined)}
|
|
>
|
|
{ctx => {
|
|
const index = Math.round(ctx.normalized * (detents - 1));
|
|
return (
|
|
<>
|
|
<Ticks
|
|
count={detents}
|
|
radius={size / 2 - 1}
|
|
length={size * 0.07}
|
|
width={Math.max(1.5, size * 0.022)}
|
|
color={tickColor ?? theme.ticks}
|
|
getTickProps={i => (i === index ? { stroke: color } : undefined)}
|
|
/>
|
|
<circle
|
|
cx={ctx.center}
|
|
cy={ctx.center}
|
|
r={size / 2 - size * 0.14}
|
|
fill={faceColor}
|
|
stroke="rgba(255,255,255,0.1)"
|
|
strokeWidth="1"
|
|
/>
|
|
<Pointer
|
|
type="triangle"
|
|
radius={size / 2 - size * 0.15}
|
|
length={size * 0.14}
|
|
width={size * 0.09}
|
|
color={color}
|
|
/>
|
|
{showValue && (
|
|
<text
|
|
x={ctx.center}
|
|
y={ctx.center}
|
|
textAnchor="middle"
|
|
dominantBaseline="central"
|
|
fontSize={size * (positions ? 0.15 : 0.17)}
|
|
fill={textColor ?? theme.text}
|
|
fontFamily={theme.fontMono}
|
|
fontWeight={600}
|
|
style={{ pointerEvents: 'none' }}
|
|
>
|
|
{positions
|
|
? positions[index]
|
|
: format
|
|
? format(ctx.value)
|
|
: ctx.value.toFixed(ctx.decimals)}
|
|
</text>
|
|
)}
|
|
{label && (
|
|
<KnobLabel
|
|
color={labelColor ?? theme.label}
|
|
fontFamily={theme.fontUI}
|
|
dy={size * 0.45}
|
|
fontSize={labelSize ?? size * 0.095}
|
|
sublabel={sublabel}
|
|
>
|
|
{label}
|
|
</KnobLabel>
|
|
)}
|
|
</>
|
|
);
|
|
}}
|
|
</Knob>
|
|
);
|
|
});
|