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.
116 lines
3.2 KiB
TypeScript
116 lines
3.2 KiB
TypeScript
import * as React from 'react';
|
|
import { Knob } from '../components/Knob';
|
|
import { Arc } from '../primitives/Arc';
|
|
import { GlowFilter } from '../primitives/GlowFilter';
|
|
import { Pointer } from '../primitives/Pointer';
|
|
import { KnobLabel, KnobValue } from '../primitives/Text';
|
|
import { useKnobTheme } from '../core/theme';
|
|
import type { SkinProps } from './shared';
|
|
|
|
export interface RubberKnobProps extends SkinProps {
|
|
/** Glow/accent color. */
|
|
color?: string;
|
|
trackColor?: string;
|
|
textColor?: string;
|
|
labelColor?: string;
|
|
arcFrom?: 'min' | 'center';
|
|
}
|
|
|
|
/** Soft-touch rubber knob with a glowing halo — modern synth hardware. */
|
|
export const RubberKnob = React.forwardRef<HTMLDivElement, RubberKnobProps>(function RubberKnob({
|
|
size = 90,
|
|
color: colorProp,
|
|
trackColor,
|
|
textColor,
|
|
labelColor,
|
|
arcFrom = 'min',
|
|
label,
|
|
sublabel,
|
|
labelSize,
|
|
showValue = false,
|
|
unit,
|
|
format,
|
|
focusRing,
|
|
className,
|
|
style,
|
|
...core
|
|
}, ref) {
|
|
const theme = useKnobTheme();
|
|
const color = colorProp ?? theme.accent ?? '#ff9640';
|
|
const id = React.useId();
|
|
const bodyId = `${id}-body`;
|
|
const glowId = `${id}-glow`;
|
|
const c = size / 2;
|
|
const bodyR = size / 2 - size * 0.17;
|
|
|
|
return (
|
|
<Knob ref={ref} size={size} className={className} style={style} focusRing={focusRing} {...core} getAriaValueText={core.getAriaValueText ?? (format ? (v: number) => format(v) : unit ? (v: number) => `${v}${unit}` : undefined)}>
|
|
<defs>
|
|
<radialGradient id={bodyId} cx="0.38" cy="0.3" r="1">
|
|
<stop offset="0" stopColor="#3a3b41" />
|
|
<stop offset="0.55" stopColor="#232428" />
|
|
<stop offset="1" stopColor="#0e0f12" />
|
|
</radialGradient>
|
|
<GlowFilter id={glowId} blur={size * 0.02} />
|
|
</defs>
|
|
|
|
<Arc
|
|
radius={size / 2 - size * 0.05}
|
|
thickness={Math.max(3, size * 0.045)}
|
|
color={color}
|
|
trackColor={trackColor ?? theme.track}
|
|
from={arcFrom}
|
|
arcProps={{ filter: `url(#${glowId})` }}
|
|
/>
|
|
|
|
<circle cx={c} cy={c} r={bodyR} fill={`url(#${bodyId})`} stroke="#000" strokeWidth="1" />
|
|
{/* rubber grip */}
|
|
<circle
|
|
cx={c}
|
|
cy={c}
|
|
r={bodyR - size * 0.03}
|
|
fill="none"
|
|
stroke="rgba(0,0,0,0.5)"
|
|
strokeWidth={size * 0.035}
|
|
strokeDasharray={`${size * 0.02} ${size * 0.03}`}
|
|
/>
|
|
{/* top sheen */}
|
|
<ellipse
|
|
cx={c}
|
|
cy={c - bodyR * 0.45}
|
|
rx={bodyR * 0.6}
|
|
ry={bodyR * 0.28}
|
|
fill="rgba(255,255,255,0.07)"
|
|
/>
|
|
|
|
<Pointer
|
|
radius={bodyR - size * 0.045}
|
|
length={bodyR * 0.5}
|
|
width={Math.max(2.5, size * 0.04)}
|
|
color={color}
|
|
shapeProps={{ filter: `url(#${glowId})` }}
|
|
/>
|
|
{showValue && (
|
|
<KnobValue
|
|
color={textColor ?? theme.text}
|
|
unit={unit}
|
|
format={format}
|
|
fontSize={size * 0.15}
|
|
fontFamily={theme.fontMono}
|
|
dy={size * 0.02}
|
|
/>
|
|
)}
|
|
{label && (
|
|
<KnobLabel
|
|
color={labelColor ?? theme.label}
|
|
fontFamily={theme.fontUI}
|
|
dy={size * 0.44}
|
|
fontSize={labelSize ?? size * 0.1}
|
|
sublabel={sublabel}
|
|
>
|
|
{label}
|
|
</KnobLabel>
|
|
)}
|
|
</Knob>
|
|
);
|
|
});
|