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,72 @@
import * as React from 'react';
import { KnobContextProvider, type KnobRenderContext } from '../core/context';
import type { KnobCoreProps } from '../core/types';
import { useKnob } from '../hooks/useKnob';
export interface KnobProps extends KnobCoreProps {
/** Square canvas size in px. Default: 80. */
size?: number;
className?: string;
style?: React.CSSProperties;
/** SVG children (Arc, Pointer, Ticks, ...) or a render function receiving the live state. */
children?: React.ReactNode | ((ctx: KnobRenderContext) => React.ReactNode);
/** Extra content rendered inside the wrapper but outside the SVG (e.g. HTML labels). */
overlay?: React.ReactNode | ((ctx: KnobRenderContext) => React.ReactNode);
}
/**
* Headless-but-visual knob container: wires up the full interaction model
* (rotary/relative/track drag, wheel, keyboard, double-click reset, a11y)
* and provides a render context for composable SVG primitives.
*/
export const Knob: React.FC<KnobProps> = ({
size = 80,
className,
style,
children,
overlay,
...core
}) => {
const knob = useKnob(core);
const ctx: KnobRenderContext = {
value: knob.value,
normalized: knob.normalized,
angle: knob.angle,
isDragging: knob.isDragging,
min: knob.min,
max: knob.max,
decimals: knob.decimals,
angleOffset: knob.angleOffset,
angleRange: knob.angleRange,
size,
center: size / 2,
};
return (
<KnobContextProvider value={ctx}>
<div
{...knob.bind}
className={className}
style={{
display: 'inline-flex',
position: 'relative',
width: size,
height: size,
...knob.bind.style,
...style,
}}
>
<svg
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
style={{ display: 'block', overflow: 'visible' }}
aria-hidden="true"
>
{typeof children === 'function' ? children(ctx) : children}
</svg>
{typeof overlay === 'function' ? overlay(ctx) : overlay}
</div>
</KnobContextProvider>
);
};