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
155
apps/docs/src/sections/GettingStarted.tsx
Normal file
155
apps/docs/src/sections/GettingStarted.tsx
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import React from 'react';
|
||||
import { FlatKnob, logTaper } from 'dreamknob';
|
||||
import { CodeBlock } from '../components/CodeBlock';
|
||||
|
||||
const Step: React.FC<{ n: number; title: string; children: React.ReactNode }> = ({
|
||||
n,
|
||||
title,
|
||||
children,
|
||||
}) => (
|
||||
<div style={{ marginBottom: 34 }}>
|
||||
<h3 className="api-h" style={{ marginTop: 0 }}>
|
||||
<span style={{ color: 'var(--accent)', fontFamily: 'var(--font-mono)', marginRight: 10 }}>
|
||||
{String(n).padStart(2, '0')}
|
||||
</span>
|
||||
{title}
|
||||
</h3>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const GettingStarted: React.FC = () => (
|
||||
<section className="block" id="start">
|
||||
<div className="container prose">
|
||||
<div className="section-kicker">Getting started</div>
|
||||
<h2 className="section-title">Your first knob in five minutes</h2>
|
||||
<p className="section-sub">
|
||||
Everything below is copy-pasteable. The only rule to remember: every control needs
|
||||
an <code>aria-label</code> (or a visible label wired via <code>aria-labelledby</code>).
|
||||
</p>
|
||||
|
||||
<Step n={1} title="Install">
|
||||
<CodeBlock code={`npm install dreamknob\n# peer deps: react >= 18, react-dom >= 18`} />
|
||||
<p className="api-note" style={{ marginTop: 10 }}>
|
||||
Zero runtime dependencies, ESM + CJS, full TypeScript types. No CSS file to
|
||||
import — everything is SVG and inline styles.
|
||||
</p>
|
||||
</Step>
|
||||
|
||||
<Step n={2} title="Drop in a knob (uncontrolled)">
|
||||
<p className="api-note" style={{ marginBottom: 10 }}>
|
||||
Pass <code>defaultValue</code> and the knob manages its own state.{' '}
|
||||
<code>defaultValue</code> is also the double-click reset target.
|
||||
</p>
|
||||
<CodeBlock
|
||||
code={`import { FlatKnob } from 'dreamknob'
|
||||
|
||||
<FlatKnob
|
||||
size={90}
|
||||
min={0} max={100} step={1}
|
||||
defaultValue={50}
|
||||
label="Level" unit="%"
|
||||
aria-label="Level"
|
||||
/>`}
|
||||
/>
|
||||
<div className="preview" style={{ minHeight: 150, marginTop: 12 }}>
|
||||
<FlatKnob size={90} min={0} max={100} step={1} defaultValue={50} label="Level" unit="%" aria-label="Level" />
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
<Step n={3} title="Read the value (controlled)">
|
||||
<p className="api-note" style={{ marginBottom: 10 }}>
|
||||
For app state, pass <code>value</code> + <code>onChange</code>. The second
|
||||
argument tells you what caused the change — useful later for undo/automation.
|
||||
</p>
|
||||
<CodeBlock
|
||||
code={`const [gain, setGain] = useState(-6)
|
||||
|
||||
<FlatKnob
|
||||
value={gain}
|
||||
onChange={(v, meta) => setGain(v)} // meta.source: 'drag' | 'wheel' | 'keyboard' | 'reset' | 'api'
|
||||
min={-60} max={12} step={0.5}
|
||||
unit=" dB"
|
||||
aria-label="Gain"
|
||||
/>`}
|
||||
/>
|
||||
</Step>
|
||||
|
||||
<Step n={4} title="Pick the feel">
|
||||
<p className="api-note" style={{ marginBottom: 10 }}>
|
||||
The default is grab-and-rotate (the pointer angle drives the value). Prefer the
|
||||
up/down plugin feel, or MIDI-style pickup? One prop each. Users always get
|
||||
scroll-wheel, arrow keys, Shift-fine, Escape-cancel and double-click reset for
|
||||
free.
|
||||
</p>
|
||||
<CodeBlock
|
||||
code={`<FlatKnob interaction="vertical" ... /> // drag up/down like a plugin
|
||||
<FlatKnob rotaryMode="pickup" ... /> // never jumps when grabbed off-position
|
||||
<FlatKnob rotaryMode="relative" ... /> // angle deltas, no jump either`}
|
||||
/>
|
||||
</Step>
|
||||
|
||||
<Step n={5} title="Get the numbers right">
|
||||
<p className="api-note" style={{ marginBottom: 10 }}>
|
||||
<code>step</code> snaps and sets display precision (float-safe — no{' '}
|
||||
<code>0.30000000000000004</code>). For audio ranges, use a taper:{' '}
|
||||
<code>logTaper</code> gives equal ratios per turn, so 20 Hz–20 kHz feels right.
|
||||
<code>format</code> controls display without touching the emitted value.
|
||||
</p>
|
||||
<CodeBlock
|
||||
code={`import { FlatKnob, logTaper } from 'dreamknob'
|
||||
|
||||
<FlatKnob
|
||||
min={20} max={20000}
|
||||
taper={logTaper} // min must be > 0 for log
|
||||
defaultValue={440}
|
||||
decimals={0}
|
||||
format={v => v >= 1000 ? \`\${(v / 1000).toFixed(1)}k\` : \`\${Math.round(v)}\`}
|
||||
label="Freq"
|
||||
aria-label="Frequency"
|
||||
/>`}
|
||||
/>
|
||||
<div className="preview" style={{ minHeight: 150, marginTop: 12 }}>
|
||||
<FlatKnob
|
||||
size={90}
|
||||
min={20}
|
||||
max={20000}
|
||||
taper={logTaper}
|
||||
defaultValue={440}
|
||||
decimals={0}
|
||||
color="#3df2ad"
|
||||
format={v => (v >= 1000 ? `${(v / 1000).toFixed(1)}k` : `${Math.round(v)}`)}
|
||||
label="Freq"
|
||||
aria-label="Frequency example"
|
||||
/>
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
<Step n={6} title="Theme once, restyle everything">
|
||||
<CodeBlock
|
||||
code={`import { DreamknobProvider } from 'dreamknob'
|
||||
|
||||
<DreamknobProvider base="dark" theme={{ accent: '#ff4d6b' }}>
|
||||
{/* every knob/fader/meter below inherits the tokens */}
|
||||
</DreamknobProvider>`}
|
||||
/>
|
||||
<p className="api-note" style={{ marginTop: 10 }}>
|
||||
Use <code>base="light"</code> on light backgrounds. Per-instance color props
|
||||
always win over the theme.
|
||||
</p>
|
||||
</Step>
|
||||
|
||||
<Step n={7} title="Gotchas checklist">
|
||||
<p className="api-note">
|
||||
① Always pass <code>aria-label</code>. ② <code>logTaper</code> needs{' '}
|
||||
<code>min > 0</code> (it falls back to linear otherwise). ③ Controlled mode
|
||||
means <em>you</em> own the value — if you don't call <code>setState</code> in{' '}
|
||||
<code>onChange</code>, the knob won't move. ④ Hovering captures the scroll
|
||||
wheel by default; pass <code>wheelRequiresFocus</code> if your UI scrolls. ⑤
|
||||
The knob is a <code>div</code>, not an input — use <code>name="…"</code> if you
|
||||
need it in a plain <code><form></code> post.
|
||||
</p>
|
||||
</Step>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue