import React from 'react';
import {
AlphaDisplay,
Arc,
DreamknobProvider,
Fader,
FlatKnob,
Gauge,
ImageKnob,
Knob,
KnobValue,
IndicatorLamp,
LabeledField,
LampRow,
LEDFader,
LEDKnob,
MetalKnob,
Meter,
MeterBridge,
NeonKnob,
Pointer,
PushButton,
Rack,
RubberKnob,
ScrubField,
SegmentSwitch,
SegmentDisplay,
SteppedKnob,
Ticks,
ToggleSwitch,
TransportButton,
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 (
undefined}
readOnly
animateChanges={{ duration: 350 }}
color="#3df2ad"
label="Cutoff"
aria-label="Preset cutoff"
/>
{PRESETS.map((p, i) => (
on && setPreset(i)}
color="#3df2ad"
size={34}
aria-label={`Preset ${p.name}`}
>
{String.fromCharCode(65 + i)}
))}
);
};
/** 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(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 MeterDemo: React.FC = () => {
const [levels, setLevels] = React.useState<[number, number]>([-18, -20]);
React.useEffect(() => {
let t = 0;
const id = setInterval(() => {
t += 0.09;
// Occasional hot transient so the clip LED gets to do its job.
const spike = Math.random() < 0.04 ? 9 : 0;
setLevels([
-13 + Math.sin(t) * 6 + Math.random() * 7 + spike,
-14 + Math.sin(t * 1.3 + 1) * 6 + Math.random() * 7 + spike,
]);
}, 90);
return () => clearInterval(id);
}, []);
return (
);
};
const LampSwitchDemo: React.FC = () => {
const [mode, setMode] = React.useState(1);
const [armed, setArmed] = React.useState(true);
return (
);
};
const CommitModeDemo: React.FC = () => {
const [commits, setCommits] = React.useState(0);
return (
setCommits(n => n + 1)}
aria-label="Release commit demo"
/>
onChange calls
);
};
const RESOURCE_ZONES = [
{ upTo: 0.75, color: '#3df2ad' },
{ upTo: 0.9, color: '#ffd23e' },
{ upTo: 1, color: '#ff4d6b' },
];
const GaugeDemo: React.FC = () => {
const [load, setLoad] = React.useState<[number, number]>([38, 61]);
React.useEffect(() => {
let t = 0;
const id = setInterval(() => {
t += 0.12;
setLoad([
Math.max(2, Math.min(100, 45 + Math.sin(t) * 30 + Math.random() * 12)),
Math.max(2, Math.min(100, 62 + Math.sin(t * 0.7 + 2) * 26 + Math.random() * 10)),
]);
}, 140);
return () => clearInterval(id);
}, []);
return (
);
};
const SmallReadoutDemo: React.FC = () => {
const [t, setT] = React.useState(0);
React.useEffect(() => {
const id = setInterval(() => setT(v => v + 1), 1000);
return () => clearInterval(id);
}, []);
const hh = String(Math.floor(t / 3600) % 100).padStart(2, '0');
const mm = String(Math.floor(t / 60) % 60).padStart(2, '0');
const ss = String(t % 60).padStart(2, '0');
return (
);
};
const PanelDemo: React.FC = () => {
const [fresh, setFresh] = React.useState(true);
const [mode, setMode] = React.useState(0);
const [playing, setPlaying] = React.useState(false);
const [armed, setArmed] = React.useState(false);
return (
setPlaying(p => !p)} size={34} />
setPlaying(false)} size={34} />
setArmed(a => !a)} size={34} />
{ setPlaying(false); setArmed(false); }} size={34} />
);
};
const ImageKnobDemo: React.FC = () => {
const src = useGeneratedStrip();
if (!src) return null;
return (
);
};
const Card: React.FC<{
title: string;
desc: string;
tag: string;
children: React.ReactNode;
}> = ({ title, desc, tag, children }) => (
{title}
{desc}
{children}
{tag}
);
export const Gallery: React.FC = () => (
Gallery
Every style in the studio
A full rack of prebuilt controls — every color themeable per instance. All of
them share the same interaction core: rotary or drag adjustment, scroll-wheel
nudge, keyboard, fine control and double-click reset.
(v >= 1000 ? `${(v / 1000).toFixed(1)}k` : `${Math.round(v)}`)}
label="Freq"
aria-label="Frequency demo"
/>
`${v > 0 ? '+' : ''}${v.toFixed(1)}`}
label="Gain"
aria-label="Bipolar gain demo"
/>
`${v > 0 ? '+' : ''}${v.toFixed(1)} dB`}
label="Trim"
aria-label="Bipolar fader demo"
/>
(v >= 1000 ? `${(v / 1000).toFixed(1)}k` : `${Math.round(v)}`)}
label="Freq"
aria-label="Editable demo"
/>
(v >= 1000 ? `${(v / 1000).toFixed(1)}k` : `${Math.round(v)}`)}
color="#e44cff"
label="Filter"
aria-label="Filter XY pad"
/>
`${v} %`}
label="Blend"
aria-label="Bubble demo"
/>
);