Tier 1: origin anchor, detents, theming, type-in editing, ImageKnob
- origin: bipolar anchor value — Arc from=center, Fader fill and LEDFader segments draw from origin instead of min/mid-travel - detents + detentSize: magnetic snap while dragging (normalized space, wheel/keyboard unaffected); applyDetents exported - DreamknobProvider + KnobTheme: accent/track/face/text/label/ticks/ focusRing/fonts tokens with dark and light bases; every skin resolves defaults from the theme, instance props win - editable + parseValue: double-click knobs (or click fader readouts) to type exact values; Enter/blur commits, Esc cancels, 1.2k parses; ValueInput + defaultParseValue exported; setValue added to context - ImageKnob: film-strip sprite knob (KnobMan-style strips), vertical or horizontal, with focus ring, editing, value/label - Docs: LED gallery cards for film-strip/bipolar/editing/theming, playground editable toggle, theming API section, README updates
This commit is contained in:
parent
d8992408fa
commit
804a85cca6
25 changed files with 999 additions and 162 deletions
|
|
@ -1,8 +1,10 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Arc,
|
||||
DreamknobProvider,
|
||||
Fader,
|
||||
FlatKnob,
|
||||
ImageKnob,
|
||||
Knob,
|
||||
KnobValue,
|
||||
LEDFader,
|
||||
|
|
@ -18,6 +20,60 @@ import {
|
|||
logTaper,
|
||||
} from 'dreamknob';
|
||||
|
||||
/** 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);
|
||||
React.useEffect(() => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = fs;
|
||||
canvas.height = fs * frames;
|
||||
const g = canvas.getContext('2d');
|
||||
if (!g) return;
|
||||
for (let i = 0; i < frames; i++) {
|
||||
const cx = fs / 2;
|
||||
const cy = i * fs + fs / 2;
|
||||
const angle = ((225 + (i / (frames - 1)) * 270) * Math.PI) / 180;
|
||||
const grad = g.createRadialGradient(cx - fs * 0.15, cy - fs * 0.18, fs * 0.05, cx, cy, fs * 0.45);
|
||||
grad.addColorStop(0, '#5c5f69');
|
||||
grad.addColorStop(0.65, '#26282e');
|
||||
grad.addColorStop(1, '#0e0f12');
|
||||
g.fillStyle = grad;
|
||||
g.beginPath();
|
||||
g.arc(cx, cy, fs * 0.4, 0, Math.PI * 2);
|
||||
g.fill();
|
||||
g.strokeStyle = 'rgba(0,0,0,0.65)';
|
||||
g.lineWidth = fs * 0.025;
|
||||
g.stroke();
|
||||
g.strokeStyle = '#ffd23e';
|
||||
g.lineWidth = fs * 0.05;
|
||||
g.lineCap = 'round';
|
||||
g.beginPath();
|
||||
g.moveTo(cx + Math.sin(angle) * fs * 0.16, cy - Math.cos(angle) * fs * 0.16);
|
||||
g.lineTo(cx + Math.sin(angle) * fs * 0.33, cy - Math.cos(angle) * fs * 0.33);
|
||||
g.stroke();
|
||||
}
|
||||
setSrc(canvas.toDataURL('image/png'));
|
||||
}, [frames, fs]);
|
||||
return src;
|
||||
};
|
||||
|
||||
const ImageKnobDemo: React.FC = () => {
|
||||
const src = useGeneratedStrip();
|
||||
if (!src) return null;
|
||||
return (
|
||||
<ImageKnob
|
||||
src={src}
|
||||
frames={31}
|
||||
size={92}
|
||||
defaultValue={40}
|
||||
step={1}
|
||||
showValue
|
||||
label="Sprite"
|
||||
aria-label="Film-strip demo"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Card: React.FC<{
|
||||
title: string;
|
||||
desc: string;
|
||||
|
|
@ -131,6 +187,88 @@ export const Gallery: React.FC = () => (
|
|||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="Film-strip" desc="Photoreal sprite knobs — any KnobMan-style PNG strip works. This one is drawn at runtime." tag="<ImageKnob />">
|
||||
<ImageKnobDemo />
|
||||
</Card>
|
||||
|
||||
<Card title="Bipolar origin + detent" desc="Fill anchors at value 0 (not mid-travel) and the drag snaps magnetically at 0." tag="origin={0} detents={[0]}">
|
||||
<FlatKnob
|
||||
size={92}
|
||||
min={-60}
|
||||
max={12}
|
||||
step={0.5}
|
||||
defaultValue={0}
|
||||
origin={0}
|
||||
detents={[0]}
|
||||
arcFrom="center"
|
||||
color="#3df2ad"
|
||||
format={v => `${v > 0 ? '+' : ''}${v.toFixed(1)}`}
|
||||
label="Gain"
|
||||
aria-label="Bipolar gain demo"
|
||||
/>
|
||||
<Fader
|
||||
length={130}
|
||||
min={-60}
|
||||
max={12}
|
||||
step={0.5}
|
||||
defaultValue={0}
|
||||
origin={0}
|
||||
detents={[0]}
|
||||
color="#3df2ad"
|
||||
format={v => `${v > 0 ? '+' : ''}${v.toFixed(1)} dB`}
|
||||
label="Trim"
|
||||
aria-label="Bipolar fader demo"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="Type-in editing" desc="Double-click the knob (or click a fader readout) and type the exact value — '1.2k' works too." tag="editable">
|
||||
<FlatKnob
|
||||
size={92}
|
||||
min={20}
|
||||
max={20000}
|
||||
taper={logTaper}
|
||||
decimals={0}
|
||||
defaultValue={440}
|
||||
editable
|
||||
color="#4cc2ff"
|
||||
format={v => (v >= 1000 ? `${(v / 1000).toFixed(1)}k` : `${Math.round(v)}`)}
|
||||
label="Freq"
|
||||
aria-label="Editable demo"
|
||||
/>
|
||||
<Fader
|
||||
length={130}
|
||||
min={-60}
|
||||
max={12}
|
||||
step={0.5}
|
||||
defaultValue={-6}
|
||||
editable
|
||||
unit=" dB"
|
||||
label="Out"
|
||||
aria-label="Editable fader demo"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="Theming" desc="One provider restyles every control — accent, tracks, text, fonts. Light backgrounds too." tag="<DreamknobProvider />">
|
||||
<DreamknobProvider theme={{ accent: '#ff4d6b' }}>
|
||||
<FlatKnob size={70} defaultValue={62} label="A" aria-label="Themed A" />
|
||||
<RubberKnob size={70} defaultValue={35} label="B" aria-label="Themed B" />
|
||||
</DreamknobProvider>
|
||||
<div
|
||||
style={{
|
||||
background: '#eceef2',
|
||||
borderRadius: 10,
|
||||
padding: '12px 16px',
|
||||
display: 'flex',
|
||||
gap: 14,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<DreamknobProvider base="light" theme={{ accent: '#0868c8' }}>
|
||||
<FlatKnob size={70} defaultValue={62} label="Light" aria-label="Light themed" />
|
||||
</DreamknobProvider>
|
||||
</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