Tier 3: bubbles, animation, wrap mode, XYPad, AlphaDisplay, PushButton
- valueBubble/bubbleFormat: floating readout above knobs while dragging
(Knob-based skins + ImageKnob)
- animateChanges: rAF ease-out tween of the pointer on programmatic
value changes; user gestures snap; prefers-reduced-motion disables
- wrap: endless encoder mode — values and relative drags roll around
min↔max; pairs with angleOffset={0} angleRange={360}
- XYPad: two-parameter pad with absolute drag, arrow keys (Shift
coarse), Escape cancel, double-click reset, grid/crosshair, focus
ring, hidden form inputs, change meta
- AlphaDisplay: fourteen-segment alphanumeric LED (A-Z, 0-9, symbols,
decimal points, chars padding/align, glow/skew)
- PushButton: studio panel button with LED strip — toggle or momentary,
controlled/uncontrolled, aria-pressed, hidden form input
- Docs: five new gallery cards incl. animated preset demo, API rows,
README
This commit is contained in:
parent
0aee48b749
commit
e9e52e43ed
12 changed files with 936 additions and 8 deletions
|
|
@ -52,6 +52,9 @@ export const ApiDocs: React.FC = () => (
|
|||
<Row name="taper" type="Taper" def="linearTaper" desc="Travel curve: linearTaper, logTaper (frequencies), powTaper(n) (gain), or your own." />
|
||||
<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="wrap" type="boolean" def="false" desc="Endless encoder: values roll around min↔max. Pair with angleOffset={0} angleRange={360}." />
|
||||
<Row name="animateChanges" type="boolean | {duration}" def="false" desc="Tween the pointer on programmatic changes (preset loads). Gestures never animate; respects prefers-reduced-motion." />
|
||||
<Row name="valueBubble / bubbleFormat" type="boolean / fn" def="false" desc="Floating readout above the control while dragging." />
|
||||
<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)." />
|
||||
|
|
@ -95,6 +98,9 @@ export const ApiDocs: React.FC = () => (
|
|||
<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="<XYPad />" type="2D control" desc="Two-parameter pad: x/y axis configs, drag + arrow keys, grid, crosshair, Escape cancel, double-click reset." />
|
||||
<Row name="<PushButton />" type="switch" desc="Panel button with LED strip: toggle or momentary, controlled/uncontrolled, aria-pressed." />
|
||||
<Row name="<AlphaDisplay />" type="digital" desc="Fourteen-segment alphanumeric LED: A–Z 0–9 and symbols, chars padding, align, glow, skew." />
|
||||
<Row name="<SegmentDisplay />" type="digital" desc="Standalone seven-segment display. digits, decimals, color, glow, skew, ghostOpacity." />
|
||||
</Table>
|
||||
<p className="api-note">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
AlphaDisplay,
|
||||
Arc,
|
||||
DreamknobProvider,
|
||||
Fader,
|
||||
|
|
@ -13,14 +14,57 @@ import {
|
|||
Meter,
|
||||
NeonKnob,
|
||||
Pointer,
|
||||
PushButton,
|
||||
RubberKnob,
|
||||
SegmentDisplay,
|
||||
SteppedKnob,
|
||||
Ticks,
|
||||
VintageKnob,
|
||||
XYPad,
|
||||
logTaper,
|
||||
} from 'dreamknob';
|
||||
|
||||
const PRESETS = [
|
||||
{ name: 'INIT PATCH', cutoff: 50 },
|
||||
{ name: 'WARM PAD', cutoff: 22 },
|
||||
{ name: 'ACID 303', cutoff: 84 },
|
||||
];
|
||||
|
||||
const PresetDemo: React.FC = () => {
|
||||
const [preset, setPreset] = React.useState(0);
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 14 }}>
|
||||
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
|
||||
<FlatKnob
|
||||
size={84}
|
||||
value={PRESETS[preset].cutoff}
|
||||
onChange={() => undefined}
|
||||
readOnly
|
||||
animateChanges={{ duration: 350 }}
|
||||
color="#3df2ad"
|
||||
label="Cutoff"
|
||||
aria-label="Preset cutoff"
|
||||
/>
|
||||
<AlphaDisplay value={PRESETS[preset].name} chars={10} height={16} color="#3df2ad" />
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
{PRESETS.map((p, i) => (
|
||||
<PushButton
|
||||
key={p.name}
|
||||
pressed={preset === i}
|
||||
onChange={on => on && setPreset(i)}
|
||||
color="#3df2ad"
|
||||
size={34}
|
||||
aria-label={`Preset ${p.name}`}
|
||||
>
|
||||
{String.fromCharCode(65 + i)}
|
||||
</PushButton>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/** Draw a film-strip of a simple hardware knob at runtime (stand-in for a KnobMan PNG). */
|
||||
const useGeneratedStrip = (frames = 31, fs = 120): string | null => {
|
||||
const [src, setSrc] = React.useState<string | null>(null);
|
||||
|
|
@ -340,6 +384,59 @@ export const Gallery: React.FC = () => (
|
|||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="Animated presets + buttons" desc="animateChanges tweens programmatic changes; PushButton latches or is momentary." tag="animateChanges · <PushButton />">
|
||||
<PresetDemo />
|
||||
</Card>
|
||||
|
||||
<Card title="XY pad" desc="Two parameters at once — cutoff/resonance, vector mixing, FX morphing." tag="<XYPad />">
|
||||
<XYPad
|
||||
width={190}
|
||||
height={140}
|
||||
x={{ min: 20, max: 20000, step: 1, defaultValue: 800 }}
|
||||
y={{ min: 0, max: 100, step: 0.5, defaultValue: 30 }}
|
||||
formatX={v => (v >= 1000 ? `${(v / 1000).toFixed(1)}k` : `${Math.round(v)}`)}
|
||||
color="#e44cff"
|
||||
label="Filter"
|
||||
aria-label="Filter XY pad"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="Endless encoder" desc="wrap makes the value roll around min↔max — full-turn phase and offset dials." tag="wrap · angleRange={360}">
|
||||
<NeonKnob
|
||||
size={92}
|
||||
min={0}
|
||||
max={360}
|
||||
step={1}
|
||||
defaultValue={90}
|
||||
wrap
|
||||
angleOffset={0}
|
||||
angleRange={360}
|
||||
unit="°"
|
||||
color="#4cc2ff"
|
||||
label="Phase"
|
||||
aria-label="Phase encoder"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="Drag bubble" desc="A floating readout follows the gesture — keep the knob face clean." tag="valueBubble">
|
||||
<MetalKnob
|
||||
size={92}
|
||||
defaultValue={64}
|
||||
step={1}
|
||||
valueBubble
|
||||
bubbleFormat={v => `${v} %`}
|
||||
label="Blend"
|
||||
aria-label="Bubble demo"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="Alpha display" desc="Fourteen-segment alphanumeric LED — preset names, modes, messages." tag="<AlphaDisplay />">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, alignItems: 'center' }}>
|
||||
<AlphaDisplay value="DREAMKNOB" height={20} />
|
||||
<AlphaDisplay value="LP-24 SAT+2" height={16} color="#4cc2ff" />
|
||||
</div>
|
||||
</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