DreamKnob/apps/docs/src/sections/GettingStarted.tsx
Dreamodus 54189ad07a 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
2026-07-12 18:27:06 -07:00

155 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 Hz20 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 &gt; 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>&lt;form&gt;</code> post.
</p>
</Step>
</div>
</section>
);