Docs accuracy pass, guides, and a playable Web Audio synth demo
Docs/playground accuracy (from a full docs-vs-source audit): - Playground: drag-feel select covers pickup/relative rotary modes, is disabled for faders, and generated code no longer emits props the target component does not accept - API/README claims corrected: Shift-fine wheel scope, wheelStep and decimals defaults, trackInset row, data-part and ref-forwarding scope, VintageKnob readout exception, Meter peakHold tag, stale component lists, aria-valuetext wording, also-exported list API consistency fixes surfaced by the audit and the tutorial: - Keyboard gestures now fire onChangeStart (bracket parity with drag/ wheel - fixes gesture-scoped undo patterns) - name (hidden form input) added to Fader, LEDFader, ImageKnob - PushButton gets a themed keyboard-only focus ring + data-focus-visible New docs content: - Getting Started: 7-step guide with live examples and a gotchas list - Advanced guide: 8-step channel-strip tutorial ending in a live, themed, gesture-undoable strip with meter (plus headless demo) - Synth section: playable Web Audio synth - osc/filter/env/LFO/master all dreamknob controls, momentary-pad keyboard, analyser-driven Meter
This commit is contained in:
parent
e9e52e43ed
commit
54189ad07a
15 changed files with 1069 additions and 53 deletions
|
|
@ -477,6 +477,16 @@ export function useKnob(props: KnobCoreProps): UseKnobResult {
|
|||
onChangeEndRef.current?.(s.startValue, { source: 'drag' });
|
||||
return;
|
||||
}
|
||||
// A key gesture begins on the first adjusting keydown — bracket it with
|
||||
// onChangeStart (before the value moves) like drags and wheel bursts.
|
||||
const ADJUST_KEYS = [
|
||||
'ArrowUp', 'ArrowRight', 'ArrowDown', 'ArrowLeft',
|
||||
'PageUp', 'PageDown', 'Home', 'End',
|
||||
];
|
||||
if (ADJUST_KEYS.includes(e.key) && !keyAdjusted.current) {
|
||||
keyAdjusted.current = true;
|
||||
onChangeStartRef.current?.(valueRef.current, { source: 'keyboard' });
|
||||
}
|
||||
const span = max - min;
|
||||
let handled = true;
|
||||
switch (e.key) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ export interface FaderProps extends Omit<KnobCoreProps, 'interaction'> {
|
|||
editable?: boolean;
|
||||
/** Custom parser for typed input. */
|
||||
parseValue?: (text: string) => number | null;
|
||||
/** Render a hidden form input carrying the current value. */
|
||||
name?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
|
@ -57,6 +59,7 @@ export const Fader = React.forwardRef<HTMLDivElement, FaderProps>(function Fader
|
|||
labelColor,
|
||||
editable = false,
|
||||
parseValue,
|
||||
name,
|
||||
className,
|
||||
style,
|
||||
...core
|
||||
|
|
@ -256,6 +259,7 @@ export const Fader = React.forwardRef<HTMLDivElement, FaderProps>(function Fader
|
|||
</>
|
||||
)}
|
||||
</svg>
|
||||
{name && <input type="hidden" name={name} value={knob.value} readOnly />}
|
||||
</div>
|
||||
{label && (
|
||||
<div style={{ width: '100%', display: 'flex', justifyContent: 'center' }}>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ export interface ImageKnobProps extends KnobCoreProps {
|
|||
/** Show a floating value bubble above the knob while dragging. */
|
||||
valueBubble?: boolean;
|
||||
bubbleFormat?: (value: number) => string;
|
||||
/** Render a hidden form input carrying the current value. */
|
||||
name?: string;
|
||||
/** Extra styles for the image layer (e.g. filter, borderRadius). */
|
||||
imageStyle?: React.CSSProperties;
|
||||
className?: string;
|
||||
|
|
@ -53,6 +55,7 @@ export const ImageKnob = React.forwardRef<HTMLDivElement, ImageKnobProps>(functi
|
|||
parseValue,
|
||||
valueBubble = false,
|
||||
bubbleFormat,
|
||||
name,
|
||||
imageStyle,
|
||||
className,
|
||||
style,
|
||||
|
|
@ -158,6 +161,7 @@ export const ImageKnob = React.forwardRef<HTMLDivElement, ImageKnobProps>(functi
|
|||
{(bubbleFormat ?? (() => text))(knob.value)}
|
||||
</div>
|
||||
)}
|
||||
{name && <input type="hidden" name={name} value={knob.value} readOnly />}
|
||||
</div>
|
||||
{showValue && (
|
||||
<div style={{ width: '100%', display: 'flex', justifyContent: 'center' }}>
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ export interface LEDFaderProps extends Omit<KnobCoreProps, 'interaction'> {
|
|||
editable?: boolean;
|
||||
/** Custom parser for typed input. */
|
||||
parseValue?: (text: string) => number | null;
|
||||
/** Render a hidden form input carrying the current value. */
|
||||
name?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
|
@ -68,6 +70,7 @@ export const LEDFader = React.forwardRef<HTMLDivElement, LEDFaderProps>(function
|
|||
labelColor,
|
||||
editable = false,
|
||||
parseValue,
|
||||
name,
|
||||
className,
|
||||
style,
|
||||
...core
|
||||
|
|
@ -231,6 +234,7 @@ export const LEDFader = React.forwardRef<HTMLDivElement, LEDFaderProps>(function
|
|||
/>
|
||||
{leds}
|
||||
</svg>
|
||||
{name && <input type="hidden" name={name} value={knob.value} readOnly />}
|
||||
</div>
|
||||
{label && (
|
||||
<div style={{ width: '100%', display: 'flex', justifyContent: 'center' }}>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export const PushButton = React.forwardRef<HTMLButtonElement, PushButtonProps>(
|
|||
const accent = color ?? theme.accent ?? '#4cc2ff';
|
||||
const isControlled = pressed !== undefined;
|
||||
const [internal, setInternal] = React.useState(defaultPressed);
|
||||
const [focusVisible, setFocusVisible] = React.useState(false);
|
||||
const on = isControlled ? (pressed as boolean) : internal;
|
||||
|
||||
const set = (next: boolean) => {
|
||||
|
|
@ -85,7 +86,16 @@ export const PushButton = React.forwardRef<HTMLButtonElement, PushButtonProps>(
|
|||
aria-pressed={on}
|
||||
aria-label={aria['aria-label']}
|
||||
data-pressed={on ? '' : undefined}
|
||||
data-focus-visible={focusVisible ? '' : undefined}
|
||||
className={className}
|
||||
onFocus={e => {
|
||||
try {
|
||||
setFocusVisible(e.currentTarget.matches(':focus-visible'));
|
||||
} catch {
|
||||
setFocusVisible(true);
|
||||
}
|
||||
}}
|
||||
onBlur={() => setFocusVisible(false)}
|
||||
{...momentaryProps}
|
||||
style={{
|
||||
position: 'relative',
|
||||
|
|
@ -103,9 +113,12 @@ export const PushButton = React.forwardRef<HTMLButtonElement, PushButtonProps>(
|
|||
background: on
|
||||
? 'linear-gradient(180deg, #17181d, #232429)'
|
||||
: 'linear-gradient(180deg, #35363d, #1d1e24)',
|
||||
boxShadow: on
|
||||
? 'inset 0 2px 5px rgba(0,0,0,0.65)'
|
||||
: 'inset 0 1px 0 rgba(255,255,255,0.09), 0 2px 4px rgba(0,0,0,0.45)',
|
||||
outline: 'none',
|
||||
boxShadow: `${
|
||||
on
|
||||
? 'inset 0 2px 5px rgba(0,0,0,0.65)'
|
||||
: 'inset 0 1px 0 rgba(255,255,255,0.09), 0 2px 4px rgba(0,0,0,0.45)'
|
||||
}${focusVisible ? `, 0 0 0 2px ${theme.focusRing}` : ''}`,
|
||||
color: on ? theme.text : theme.label,
|
||||
fontFamily: theme.fontUI,
|
||||
fontSize: Math.max(10, size * 0.28),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue