Initial release: dreamknob library + showcase

React + TypeScript knob/fader library with a headless interaction core
(rotary, relative-drag and track modes, wheel, keyboard, a11y), SVG
primitives, nine prebuilt skins, seven-segment display engine, and a
Vite showcase with gallery, live playground and API docs.
This commit is contained in:
Dreamodus 2026-07-12 14:26:52 -07:00
commit 60a02cb1db
46 changed files with 6186 additions and 0 deletions

View file

@ -0,0 +1,94 @@
import * as React from 'react';
import { Knob } from '../components/Knob';
import { Pointer } from '../primitives/Pointer';
import { Ticks } from '../primitives/Ticks';
import { KnobLabel } from '../primitives/Text';
import { MONO_FONT, UI_FONT, type SkinProps } from './shared';
export interface SteppedKnobProps extends SkinProps {
/** Position names, e.g. ['LP','BP','HP']. Sets the number of detents. */
positions?: readonly string[];
/** Number of detents when `positions` is not given. */
steps?: number;
color?: string;
tickColor?: string;
faceColor?: string;
textColor?: string;
labelColor?: string;
}
/** Detented selector knob — mode/range switches with hard stops. */
export const SteppedKnob: React.FC<SteppedKnobProps> = ({
size = 90,
positions,
steps,
color = '#ffd23e',
tickColor = 'rgba(255,255,255,0.18)',
faceColor = '#1c1d22',
textColor = 'rgba(255,255,255,0.92)',
labelColor = 'rgba(255,255,255,0.45)',
label,
showValue = true,
className,
style,
...core
}) => {
const detents = positions ? positions.length : steps ?? 5;
const min = core.min ?? 0;
const max = core.max ?? (positions ? positions.length - 1 : 100);
return (
<Knob size={size} className={className} style={style} {...core} min={min} max={max} steps={detents}>
{ctx => {
const index = Math.round(ctx.normalized * (detents - 1));
return (
<>
<Ticks
count={detents}
radius={size / 2 - 1}
length={size * 0.07}
width={Math.max(1.5, size * 0.022)}
color={tickColor}
getTickProps={i => (i === index ? { stroke: color } : undefined)}
/>
<circle
cx={ctx.center}
cy={ctx.center}
r={size / 2 - size * 0.14}
fill={faceColor}
stroke="rgba(255,255,255,0.1)"
strokeWidth="1"
/>
<Pointer
type="triangle"
radius={size / 2 - size * 0.15}
length={size * 0.14}
width={size * 0.09}
color={color}
/>
{showValue && (
<text
x={ctx.center}
y={ctx.center}
textAnchor="middle"
dominantBaseline="central"
fontSize={size * (positions ? 0.15 : 0.17)}
fill={textColor}
fontFamily={MONO_FONT}
fontWeight={600}
style={{ pointerEvents: 'none' }}
>
{positions ? positions[index] : ctx.value.toFixed(ctx.decimals)}
</text>
)}
{label && (
<KnobLabel color={labelColor} fontFamily={UI_FONT} dy={size * 0.45} fontSize={size * 0.095}>
{label}
</KnobLabel>
)}
</>
);
}}
</Knob>
);
};