v1.1.2: action button, responsive faders, label scaling
Third integration-feedback release from building the streaming studio.
- New `Button` — a click-to-fire command button in the panel style, the
non-latching sibling of PushButton. Implemented as PushButton's new
`mode="action"` (never latches, press-down + brief LED flash, `onClick`);
led="dot" (default) matches the corner-dot look, led={false} is plain.
- `fill` on LEDFader (parity with Fader) — faders stretch to fill their
container in resizable panels instead of a fixed pixel length.
- `labelSize` on IndicatorLamp (and LampRow) — shrink the caption below its
10px floor to match dense header rows.
- Docs: a dedicated "Buttons & switches" section with a decision guide
(Button/PushButton/TransportButton/ToggleSwitch/SegmentSwitch) + live demos.
Library + docs typecheck and build; 38 tests pass.
This commit is contained in:
parent
89c7a7f603
commit
a6348d5c46
13 changed files with 372 additions and 27 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { AdvancedGuide } from './sections/AdvancedGuide';
|
||||
import { ApiDocs } from './sections/ApiDocs';
|
||||
import { Buttons } from './sections/Buttons';
|
||||
import { Console } from './sections/Console';
|
||||
import { Gallery } from './sections/Gallery';
|
||||
import { GettingStarted } from './sections/GettingStarted';
|
||||
|
|
@ -26,6 +27,7 @@ export const App: React.FC = () => (
|
|||
<div className="nav-links">
|
||||
<a href="#start">Start</a>
|
||||
<a href="#gallery">Gallery</a>
|
||||
<a href="#buttons">Buttons</a>
|
||||
<a href="#synth">Synth</a>
|
||||
<a href="#playground">Playground</a>
|
||||
<a href="#guide">Guide</a>
|
||||
|
|
@ -36,6 +38,7 @@ export const App: React.FC = () => (
|
|||
<Hero />
|
||||
<GettingStarted />
|
||||
<Gallery />
|
||||
<Buttons />
|
||||
<Console />
|
||||
<Synth />
|
||||
<Playground />
|
||||
|
|
|
|||
|
|
@ -129,13 +129,14 @@ export const ApiDocs: React.FC = () => (
|
|||
<Row name="<NeonKnob />" type="2D" desc="Glowing arc + dot pointer. color, trackColor, arcFrom." />
|
||||
<Row name="<SteppedKnob />" type="selector" desc="Detented switch. positions (named) or steps (numeric), color, faceColor." />
|
||||
<Row name="<Fader />" type="linear" desc="Channel fader. orientation, length, breadth, color, tickCount, showFill, unit, format, valuePosition ('top' | 'end' | 'none'), fill (stretch to container)." />
|
||||
<Row name="<LEDFader />" type="linear · digital" desc="Segmented LED meter-fader. orientation, length, segments, color or zones ([{upTo, color}] — defaults to the theme's green/amber/red), glow, digits, unit." />
|
||||
<Row name="<LEDFader />" type="linear · digital" desc="Segmented LED meter-fader. orientation, length, segments, color or zones ([{upTo, color}] — defaults to the theme's green/amber/red), glow, digits, unit, fill (stretch to container)." />
|
||||
<Row name="<ImageKnob />" type="sprite" desc="Film-strip knob (KnobMan-style PNG strips): src, frames, stripOrientation. Photoreal skins with zero drawing code." />
|
||||
<Row name="<Meter />" type="display" desc="Read-only LED level meter: value (number or [l, r, ...]), zones (theme green/amber/red), peakHold, peakColor, orientation, unit, fill (stretch to container), disabled, seven-segment readout. Clip LED: showClip, clipThreshold, clipHold (ms or 'latch'), clipColor, onClip." />
|
||||
<Row name="<MeterBridge />" type="display" desc="Labeled multi-strip meter block: channels ([{label, value, clipValue?}]), per-channel peak-hold text, shared clip LED — the mixer meter row, prebuilt." />
|
||||
<Row name="<XYPad />" type="2D control" desc="Two-parameter pad: x/y axis configs (each with min/max/step/invert/detents), drag + arrow keys (arrows follow the visual direction), grid, crosshair, Escape cancel, double-click reset." />
|
||||
<Row name="<PushButton />" type="switch" desc="Panel button: toggle or momentary, controlled/uncontrolled, aria-pressed. led: 'strip' | 'dot'. leadingIcon slot tinted to the LED." />
|
||||
<Row name="<IndicatorLamp />" type="switch · display" desc="Panel LED lamp: pressable toggle (role='switch') or read-only status light, blink, momentary, label either side — the checkbox, in hardware form." />
|
||||
<Row name="<Button />" type="action" desc="Click-to-fire command button in the panel style (Regenerate, Add, Apply). PushButton locked to mode='action' — never latches, press-down + LED flash, onClick. led: 'dot' (default) | 'strip' | false." />
|
||||
<Row name="<PushButton />" type="switch" desc="Panel button: mode 'toggle' | 'momentary' | 'action', controlled/uncontrolled, aria-pressed. led: 'strip' | 'dot'. leadingIcon slot tinted to the LED." />
|
||||
<Row name="<IndicatorLamp />" type="switch · display" desc="Panel LED lamp: pressable toggle (role='switch') or read-only status light, blink, momentary, label either side (labelSize to shrink the caption below its 10px floor) — the checkbox, in hardware form." />
|
||||
<Row name="<SegmentSwitch />" type="selector" desc="Segmented option switch (radiogroup): options list, LED tick on the active cell, arrow/Home/End keys, controlled/uncontrolled index." />
|
||||
<Row name="<ScrubField />" type="numeric field" desc="After-Effects-style number field: drag the label to scrub (Shift = fine), click to type (Enter commits, Esc reverts), arrows step, wheel nudges. role='spinbutton'." />
|
||||
<Row name="<AlphaDisplay />" type="digital" desc="Fourteen-segment alphanumeric LED: A–Z 0–9 and symbols (now incl. : $ %), chars padding, align, weight, gap, glow, skew. Defaults to theme.ledAmber." />
|
||||
|
|
|
|||
156
apps/docs/src/sections/Buttons.tsx
Normal file
156
apps/docs/src/sections/Buttons.tsx
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
PushButton,
|
||||
TransportButton,
|
||||
ToggleSwitch,
|
||||
SegmentSwitch,
|
||||
} from 'dreamknob';
|
||||
|
||||
/** Small glyph for a button's leadingIcon (the docs app has no icon dep). */
|
||||
const Glyph: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<span aria-hidden="true" style={{ fontSize: 14, lineHeight: 1 }}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
/** The button/switch family, side by side, with a "which do I use?" guide. */
|
||||
export const Buttons: React.FC = () => {
|
||||
const [playing, setPlaying] = React.useState(false);
|
||||
const [armed, setArmed] = React.useState(false);
|
||||
const [bypass, setBypass] = React.useState(false);
|
||||
const [fresh, setFresh] = React.useState(true);
|
||||
const [mode, setMode] = React.useState(0);
|
||||
const [fired, setFired] = React.useState(0);
|
||||
|
||||
return (
|
||||
<section className="block" id="buttons">
|
||||
<div className="container">
|
||||
<div className="section-kicker">Buttons & switches</div>
|
||||
<h2 className="section-title">Press, latch, or pick</h2>
|
||||
<p className="section-sub">
|
||||
Five components cover every panel control that isn't a dial. The
|
||||
only question is what the press <em>means</em>:
|
||||
</p>
|
||||
|
||||
<div className="guide-grid">
|
||||
<GuideRow
|
||||
code="<Button>"
|
||||
when="Fire a one-off action"
|
||||
eg="Regenerate · Add · Apply · Refresh"
|
||||
/>
|
||||
<GuideRow
|
||||
code="<PushButton>"
|
||||
when="Latch a boolean state (or hold it)"
|
||||
eg="Mute · Solo · Bypass · push-to-talk"
|
||||
/>
|
||||
<GuideRow
|
||||
code="<TransportButton>"
|
||||
when="Transport / deck control"
|
||||
eg="Play · Stop · Record · Panic"
|
||||
/>
|
||||
<GuideRow
|
||||
code="<ToggleSwitch>"
|
||||
when="An on/off setting that reads as a switch"
|
||||
eg="Fresh seed · Monitor · Loop"
|
||||
/>
|
||||
<GuideRow
|
||||
code="<SegmentSwitch>"
|
||||
when="Pick one of a few modes"
|
||||
eg="Linear · Radial · Conic"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="btn-demo">
|
||||
<Demo label={`<Button> — action, fired ${fired}×`}>
|
||||
<Button
|
||||
color="#4cc2ff"
|
||||
leadingIcon={<Glyph>↻</Glyph>}
|
||||
onClick={() => setFired(n => n + 1)}
|
||||
aria-label="Regenerate"
|
||||
>
|
||||
Regenerate
|
||||
</Button>
|
||||
<Button
|
||||
color="#3df2ad"
|
||||
led="dot"
|
||||
leadingIcon={<Glyph>✦</Glyph>}
|
||||
onClick={() => setFired(n => n + 1)}
|
||||
aria-label="Fresh"
|
||||
>
|
||||
Fresh
|
||||
</Button>
|
||||
<Button
|
||||
color="#ff4d6b"
|
||||
led={false}
|
||||
leadingIcon={<Glyph>✕</Glyph>}
|
||||
onClick={() => setFired(n => n + 1)}
|
||||
aria-label="Clear"
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</Demo>
|
||||
|
||||
<Demo label="<PushButton> — latch / hold">
|
||||
<PushButton
|
||||
pressed={bypass}
|
||||
onChange={setBypass}
|
||||
color="#ffd23e"
|
||||
aria-label="Bypass"
|
||||
>
|
||||
Bypass
|
||||
</PushButton>
|
||||
<PushButton
|
||||
mode="momentary"
|
||||
color="#4cc2ff"
|
||||
led="dot"
|
||||
size={30}
|
||||
aria-label="Talk"
|
||||
>
|
||||
Talk
|
||||
</PushButton>
|
||||
</Demo>
|
||||
|
||||
<Demo label="<TransportButton>">
|
||||
<TransportButton kind="play" active={playing} onClick={() => setPlaying(p => !p)} size={34} />
|
||||
<TransportButton kind="stop" onClick={() => setPlaying(false)} size={34} />
|
||||
<TransportButton kind="record" active={armed} onClick={() => setArmed(a => !a)} size={34} />
|
||||
</Demo>
|
||||
|
||||
<Demo label="<ToggleSwitch> · <SegmentSwitch>">
|
||||
<ToggleSwitch on={fresh} onChange={setFresh} color="#c9a6ff" showStateLabel aria-label="Fresh seed" />
|
||||
<SegmentSwitch
|
||||
options={['LINEAR', 'RADIAL', 'CONIC']}
|
||||
value={mode}
|
||||
onChange={setMode}
|
||||
color="#4cc2ff"
|
||||
aria-label="Gradient kind"
|
||||
/>
|
||||
</Demo>
|
||||
</div>
|
||||
|
||||
<p className="section-sub" style={{ marginTop: 20 }}>
|
||||
<code><Button></code> is <code><PushButton mode="action"></code> —
|
||||
it never latches, shows a press-down and a brief LED flash, and calls{' '}
|
||||
<code>onClick</code>. <code>led="dot"</code> gives the corner-dot
|
||||
look; <code>led={false}</code> a plain button.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const GuideRow: React.FC<{ code: string; when: string; eg: string }> = ({ code, when, eg }) => (
|
||||
<div className="guide-row">
|
||||
<code className="guide-code">{code}</code>
|
||||
<span className="guide-when">{when}</span>
|
||||
<span className="guide-eg">{eg}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Demo: React.FC<{ label: string; children: React.ReactNode }> = ({ label, children }) => (
|
||||
<div className="btn-demo-cell">
|
||||
<div className="btn-demo-label">{label}</div>
|
||||
<div className="btn-demo-row">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -353,6 +353,76 @@ h2.section-title {
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Buttons section: decision guide + live demo grid */
|
||||
.guide-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
margin: 8px 0 28px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--line);
|
||||
}
|
||||
.guide-row {
|
||||
display: grid;
|
||||
grid-template-columns: 190px 1fr;
|
||||
gap: 4px 18px;
|
||||
align-items: baseline;
|
||||
padding: 12px 16px;
|
||||
background: #101219;
|
||||
}
|
||||
.guide-code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
color: #4cc2ff;
|
||||
}
|
||||
.guide-when {
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
}
|
||||
.guide-eg {
|
||||
grid-column: 2;
|
||||
font-size: 12.5px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.btn-demo {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.btn-demo-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 18px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(180deg, #16181f, #0e0f14);
|
||||
border: 1px solid var(--line-strong);
|
||||
}
|
||||
.btn-demo-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-dim);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.btn-demo-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
min-height: 40px;
|
||||
}
|
||||
@media (max-width: 560px) {
|
||||
.guide-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.guide-eg {
|
||||
grid-column: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- synth panel ---- */
|
||||
.synth-rows {
|
||||
display: flex;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue