Tier 2: drag feel, wheel overhaul, refs/meta/forms, Meter, gradients
- rotaryMode: 'absolute' | 'relative' (angle deltas, no grab jump) |
'pickup' (MIDI-style: engages when the pointer sweeps past the value)
- dragAcceleration: velocity gain for relative drags (Shift bypasses);
hideCursorOnDrag hides the cursor mid-gesture
- Wheel: trackpad delta accumulation (flicks can't rocket the value,
one notch per wheel click), wheelStep override, wheelRequiresFocus
opt-out of the hover scroll-trap
- onChange/onChangeStart/onChangeEnd now receive { source: 'drag' |
'wheel' | 'keyboard' | 'reset' | 'api' }
- Every component forwards a ref to its root; name prop renders a
hidden form input; SVG primitives carry data-part attributes
- Arc gradient: position-anchored color stops rendered as sliced arcs
(mixColors/sampleGradient exported, unit-tested)
- New read-only Meter: LED level meter with peak hold, zones, readout
- Fader capColor/capLength for custom handle styling
- Docs: Meter/gradient/drag-feel gallery cards, white-cap fader demo,
API rows, README
This commit is contained in:
parent
804a85cca6
commit
0aee48b749
27 changed files with 756 additions and 134 deletions
|
|
@ -53,13 +53,19 @@ export const ApiDocs: React.FC = () => (
|
|||
<Row name="origin" type="number" desc="Bipolar anchor: arcs and fader fills draw from this value instead of the minimum (origin={0} for pan/gain)." />
|
||||
<Row name="detents / detentSize" type="number[] / number" def="— / 0.025" desc="Magnetic snap points: drags stick to these values within the capture radius. Wheel/keyboard unaffected." />
|
||||
<Row name="interaction" type="InteractionMode" def="'rotary'" desc="'rotary' (grab & turn), 'vertical', 'horizontal', 'both', or absolute 'track-*' modes used by faders." />
|
||||
<Row name="rotaryMode" type="RotaryMode" def="'absolute'" desc="'absolute' tracks the pointer angle; 'relative' applies angle deltas (no grab jump); 'pickup' engages once the pointer sweeps past the value (MIDI pickup)." />
|
||||
<Row name="dragSensitivity" type="number" def="200" desc="Pixels of relative drag for full travel (vertical/horizontal modes)." />
|
||||
<Row name="dragAcceleration" type="number" def="0" desc="Velocity gain for relative drags: fast flicks cover more range, slow drags stay precise. Shift bypasses it." />
|
||||
<Row name="hideCursorOnDrag" type="boolean" def="false" desc="Hide the mouse cursor for the duration of a drag." />
|
||||
<Row name="fineMultiplier" type="number" def="0.1" desc="Speed multiplier while Shift is held during drags and wheel." />
|
||||
<Row name="angleOffset / angleRange" type="number" def="225 / 270" desc="Where travel starts and how far it sweeps, in degrees clockwise from 12 o'clock." />
|
||||
<Row name="enableWheel" type="boolean" def="true" desc="Scroll-wheel nudging (one step per notch)." />
|
||||
<Row name="enableWheel" type="boolean" def="true" desc="Scroll-wheel nudging. Trackpad deltas accumulate into notches so a flick can't rocket the value." />
|
||||
<Row name="wheelStep" type="number" def="step" desc="Value change per wheel notch, independent of the snapping step." />
|
||||
<Row name="wheelRequiresFocus" type="boolean" def="false" desc="Only respond to the wheel while focused — hovering no longer captures page scroll." />
|
||||
<Row name="doubleClickReset" type="boolean" def="auto" desc="Double-click resets to defaultValue." />
|
||||
<Row name="onChange" type="(v: number) => void" desc="Fires for every distinct snapped value." />
|
||||
<Row name="onChangeStart / onChangeEnd" type="(v: number) => void" desc="Gesture boundaries — ideal for undo history or parameter automation." />
|
||||
<Row name="onChange" type="(v, meta) => void" desc="Fires for every distinct snapped value. meta.source tells you what caused it: 'drag' | 'wheel' | 'keyboard' | 'reset' | 'api'." />
|
||||
<Row name="onChangeStart / onChangeEnd" type="(v, meta) => void" desc="Gesture boundaries (drag, wheel burst, key press, reset) — ideal for undo history or parameter automation." />
|
||||
<Row name="name" type="string" desc="Renders a hidden form input carrying the value — plain <form> posts and form libraries just work." />
|
||||
<Row name="disabled / readOnly" type="boolean" def="false" desc="Disable interaction (readOnly stays focusable)." />
|
||||
<Row name="editable / parseValue" type="boolean / fn" def="false" desc="Type-in editing: double-click a knob (or click a fader readout), type a value, Enter commits, Esc cancels. '1.2k' → 1200." />
|
||||
<Row name="focusRing" type="string | false" def="theme" desc="Keyboard focus ring color. Pointer grabs never show it." />
|
||||
|
|
@ -88,12 +94,19 @@ export const ApiDocs: React.FC = () => (
|
|||
<Row name="<Fader />" type="linear" desc="Channel fader. orientation, length, breadth, color, tickCount, showFill, unit, format." />
|
||||
<Row name="<LEDFader />" type="linear · digital" desc="Segmented LED meter-fader. orientation, length, segments, color or zones ([{upTo, color}]), glow, digits." />
|
||||
<Row name="<ImageKnob />" type="sprite" desc="Film-strip knob (KnobMan-style PNG strips): src, frames, stripOrientation. Photoreal skins with zero drawing code." />
|
||||
<Row name="<Meter />" type="display" desc="Read-only LED level meter: value, zones, peakHold (ms), peakColor, orientation, seven-segment readout." />
|
||||
<Row name="<SegmentDisplay />" type="digital" desc="Standalone seven-segment display. digits, decimals, color, glow, skew, ghostOpacity." />
|
||||
</Table>
|
||||
<p className="api-note">
|
||||
All skins also take <code>label</code>, <code>showValue</code>, <code>unit</code>,{' '}
|
||||
<code>format</code>, <code>className</code>, <code>style</code> plus every core prop
|
||||
above.
|
||||
<code>format</code>, <code>className</code>, <code>style</code>, <code>name</code>{' '}
|
||||
plus every core prop above, and forward a ref to their root element. Inside the
|
||||
SVG, parts carry <code>data-part</code> attributes (<code>arc</code>,{' '}
|
||||
<code>track</code>, <code>pointer</code>, <code>ticks</code>, <code>face</code>,{' '}
|
||||
<code>value</code>, <code>label</code>) and the root exposes{' '}
|
||||
<code>data-dragging</code>/<code>data-disabled</code>/<code>data-focus-visible</code>{' '}
|
||||
— style any state or part with plain CSS. <code><Arc></code> additionally
|
||||
accepts <code>gradient</code> (position-anchored color stops).
|
||||
</p>
|
||||
|
||||
<h3 className="api-h">Theming</h3>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
LEDFader,
|
||||
LEDKnob,
|
||||
MetalKnob,
|
||||
Meter,
|
||||
NeonKnob,
|
||||
Pointer,
|
||||
RubberKnob,
|
||||
|
|
@ -57,6 +58,32 @@ const useGeneratedStrip = (frames = 31, fs = 120): string | null => {
|
|||
return src;
|
||||
};
|
||||
|
||||
const MeterDemo: React.FC = () => {
|
||||
const [levels, setLevels] = React.useState<[number, number]>([-18, -20]);
|
||||
React.useEffect(() => {
|
||||
let t = 0;
|
||||
const id = setInterval(() => {
|
||||
t += 0.09;
|
||||
setLevels([
|
||||
-13 + Math.sin(t) * 6 + Math.random() * 7,
|
||||
-14 + Math.sin(t * 1.3 + 1) * 6 + Math.random() * 7,
|
||||
]);
|
||||
}, 90);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
const zones = [
|
||||
{ upTo: 0.72, color: '#3df2ad' },
|
||||
{ upTo: 0.9, color: '#ffd23e' },
|
||||
{ upTo: 1, color: '#ff4d6b' },
|
||||
];
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<Meter value={levels[0]} min={-60} max={6} length={132} zones={zones} peakColor="#fff" label="L" aria-label="Left level" />
|
||||
<Meter value={levels[1]} min={-60} max={6} length={132} zones={zones} peakColor="#fff" label="R" aria-label="Right level" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ImageKnobDemo: React.FC = () => {
|
||||
const src = useGeneratedStrip();
|
||||
if (!src) return null;
|
||||
|
|
@ -136,8 +163,9 @@ export const Gallery: React.FC = () => (
|
|||
<SteppedKnob size={92} min={-24} max={24} steps={9} color="#4cc2ff" defaultValue={0} label="Semi" aria-label="Semitones demo" />
|
||||
</Card>
|
||||
|
||||
<Card title="Fader" desc="Absolute-position linear control, both orientations." tag="<Fader />">
|
||||
<Card title="Fader" desc="Absolute-position linear control, both orientations, custom caps." tag="<Fader />">
|
||||
<Fader length={130} defaultValue={-6} min={-60} max={12} step={0.5} unit=" dB" label="Main" aria-label="Fader demo" />
|
||||
<Fader length={130} defaultValue={20} step={1} capColor="#e8e8ec" capLength={26} color="#3df2ad" label="Aux" aria-label="White cap fader demo" />
|
||||
<Fader orientation="horizontal" length={150} defaultValue={35} step={1} color="#e44cff" label="X-Fade" aria-label="Crossfade demo" />
|
||||
</Card>
|
||||
|
||||
|
|
@ -269,6 +297,49 @@ export const Gallery: React.FC = () => (
|
|||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="Meter" desc="Read-only LED metering with peak hold — the display sibling of LEDFader." tag="<Meter peakHold />">
|
||||
<MeterDemo />
|
||||
</Card>
|
||||
|
||||
<Card title="Gradient arc" desc="Position-anchored gradient — the red zone stays at the top of travel." tag="<Arc gradient={...} />">
|
||||
<Knob size={96} min={-60} max={6} step={0.5} defaultValue={-8} aria-label="Gradient arc demo">
|
||||
<Arc
|
||||
thickness={7}
|
||||
trackColor="rgba(255,255,255,0.08)"
|
||||
gradient={[
|
||||
{ at: 0, color: '#3df2ad' },
|
||||
{ at: 0.72, color: '#3df2ad' },
|
||||
{ at: 0.9, color: '#ffd23e' },
|
||||
{ at: 1, color: '#ff4d6b' },
|
||||
]}
|
||||
/>
|
||||
<Pointer type="line" radius={33} length={12} width={3} color="#e8eaf0" />
|
||||
<KnobValue unit=" dB" color="#e8eaf0" fontSize={14} />
|
||||
</Knob>
|
||||
</Card>
|
||||
|
||||
<Card title="Drag feel options" desc="Pickup mode never jumps on grab; acceleration makes fast vertical drags cover more range." tag="rotaryMode · dragAcceleration">
|
||||
<FlatKnob
|
||||
size={88}
|
||||
defaultValue={70}
|
||||
rotaryMode="pickup"
|
||||
color="#ffd23e"
|
||||
label="Pickup"
|
||||
aria-label="Pickup mode demo"
|
||||
/>
|
||||
<RubberKnob
|
||||
size={88}
|
||||
defaultValue={40}
|
||||
interaction="vertical"
|
||||
dragAcceleration={2}
|
||||
hideCursorOnDrag
|
||||
showValue
|
||||
color="#4cc2ff"
|
||||
label="Accel"
|
||||
aria-label="Acceleration demo"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="Compose your own" desc="Primitives + headless core = any design you want." tag="<Knob> + primitives">
|
||||
<Knob size={96} defaultValue={42} aria-label="Composed demo">
|
||||
<Ticks count={28} radius={47} length={5} width={1.5} color="rgba(255,255,255,0.12)" activeColor="#ffd23e" />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue