v1.1.1: second integration-feedback release
Readout legibility (the #1 ask): - bold own-cell decimal point + colon in seven/fourteen-seg (not hairlines) - weight prop thickens strokes; gap controls inter-cell kerning Display colors: - SegmentDisplay defaults to theme.ledGreen, AlphaDisplay to theme.ledAmber - valueColor fn + value-space zones on SegmentDisplay (self-coloring readouts) - charset: colon in both, $ and % in 14-seg; disabled + data-disabled Knob captions: sublabel (second line) + labelSize (independent of dial size). New components: - ToggleSwitch (rocker/slider boolean, role=switch) - Gauge (read-only radial arc meter, round sibling of Meter) - TransportButton (play/stop/record/pause/panic, record blinks) - LampRow (console-print lamp bank), LabeledField, Rack (panel chrome) Responsiveness: fill on Meter + Fader (stretch to container). Niceties: PushButton leadingIcon; theme ledGreen/ledAmber/panel tokens. 54 -> 63 exports. 38 unit tests. Browser-verified.
This commit is contained in:
parent
d8b7b55505
commit
5b97091902
32 changed files with 1535 additions and 133 deletions
|
|
@ -128,24 +128,32 @@ export const ApiDocs: React.FC = () => (
|
|||
<Row name="<LEDKnob />" type="digital" desc="Segmented LED ring + seven-segment readout. color, offColor, segments, digits, displayDecimals (any precision), unit label under the digits." />
|
||||
<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' — 'end' gives one-line inspector rows)." />
|
||||
<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="<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, ...] for multi-channel), zones (defaults to theme green/amber/red), peakHold (ms), peakColor, orientation, unit, seven-segment readout. Clip LED: showClip (default on), clipThreshold (default max), clipHold (ms or 'latch', click to clear), clipColor, onClip." />
|
||||
<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' (default) or 'dot' — the corner dot scales down for small icon-only toggles." />
|
||||
<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="<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, chars padding, align, glow, skew." />
|
||||
<Row name="<SegmentDisplay />" type="digital" desc="Standalone seven-segment display. digits, decimals, color, glow, skew, ghostOpacity." />
|
||||
<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." />
|
||||
<Row name="<SegmentDisplay />" type="digital" desc="Standalone seven-segment display. digits, decimals, weight (thicker strokes), gap (kerning), valueColor / zones (color by value), bold own-cell decimal + colon, disabled. Defaults to theme.ledGreen." />
|
||||
<Row name="<Gauge />" type="display" desc="Read-only radial arc meter — the round sibling of Meter. value/min/max/taper, zones or valueColor, needle, center readout, unit. For CPU/RAM/load." />
|
||||
<Row name="<ToggleSwitch />" type="switch" desc="Rocker/slider boolean (role='switch'): the knob slides OFF↔ON. label, showStateLabel, ←/→ keys, controlled/uncontrolled, name." />
|
||||
<Row name="<TransportButton />" type="switch" desc="play / pause / stop / record / rewind / forward / panic, styled to match the controls beside it. active lights it; record blinks (reduced-motion aware)." />
|
||||
<Row name="<LampRow />" type="display" desc="Row of small indicator lamps with console-print captions (● CUE ● MUTE). Read-only status, or pass onToggle to make them pressable." />
|
||||
<Row name="<LabeledField />" type="layout" desc="One aligned inspector row: label · control · unit. Share labelWidth across a stack and the controls line up." />
|
||||
<Row name="<Rack />" type="layout" desc="Channel-strip / rack container owning panel chrome + spacing: orientation row|column, title, nestable. Declarative console sections." />
|
||||
</Table>
|
||||
<p className="api-note">
|
||||
Every knob skin and fader also takes <code>label</code>, <code>showValue</code>,{' '}
|
||||
<code>unit</code>, <code>format</code>, <code>className</code>,{' '}
|
||||
<code>style</code>, <code>name</code> plus every core prop above, and forwards a
|
||||
ref to its root element (<code>VintageKnob</code> has no readout, so it omits{' '}
|
||||
Every knob skin and fader also takes <code>label</code>, <code>sublabel</code>{' '}
|
||||
(a second caption line — <code>SEED</code> over <code>world #7</code>),{' '}
|
||||
<code>labelSize</code> (caption size independent of the dial diameter),{' '}
|
||||
<code>showValue</code>, <code>unit</code>, <code>format</code>,{' '}
|
||||
<code>className</code>, <code>style</code>, <code>name</code> plus every core prop
|
||||
above, and forwards a ref to its root element (<code>VintageKnob</code> has no readout, so it omits{' '}
|
||||
<code>showValue</code>/<code>unit</code>/<code>format</code>; the display
|
||||
components <code>Meter</code>/<code>SegmentDisplay</code>/<code>AlphaDisplay</code>{' '}
|
||||
have their own smaller prop sets). Primitive-drawn parts carry{' '}
|
||||
|
|
@ -180,8 +188,11 @@ export const ApiDocs: React.FC = () => (
|
|||
label: 'rgba(255,255,255,0.5)',
|
||||
fontMono: '"IBM Plex Mono", monospace',
|
||||
zoneGood: '#3df2ad', // meter zones: green / amber / red
|
||||
zoneWarn: '#ffd23e', // used by Meter, MeterBridge and
|
||||
zoneHot: '#ff4d6b', // LEDFader when no zones are given
|
||||
zoneWarn: '#ffd23e', // used by Meter, MeterBridge, Gauge
|
||||
zoneHot: '#ff4d6b', // and LEDFader when no zones given
|
||||
ledGreen: '#3df2ad', // SegmentDisplay default lit color
|
||||
ledAmber: '#ffb84d', // AlphaDisplay default lit color
|
||||
panel: 'rgba(255,255,255,0.03)', // Rack / panel chrome
|
||||
}}
|
||||
>
|
||||
<FlatKnob ... /> <Fader ... /> <LEDKnob ... />
|
||||
|
|
@ -234,6 +245,18 @@ function MyKnob() {
|
|||
}`}
|
||||
/>
|
||||
|
||||
<h3 className="api-h">Small readouts</h3>
|
||||
<p className="api-note">
|
||||
Segment displays keep their proportions down to about{' '}
|
||||
<strong>11 px</strong> tall; below that, raise <code>weight</code> to thicken
|
||||
the strokes rather than shrinking further. The decimal point and colon render as
|
||||
bold cells with their own width (not hairlines between digits), so{' '}
|
||||
<code>17.5</code> and <code>1:23:45</code> stay readable in a status bar. Widen{' '}
|
||||
<code>gap</code> when packed tokens like <code>18.1/24</code> crowd. Colons and{' '}
|
||||
<code>$</code>/<code>%</code> are in the charset, so a whole token can live in one
|
||||
display instead of text beside it.
|
||||
</p>
|
||||
|
||||
<h3 className="api-h">Recipes</h3>
|
||||
<p className="api-note">
|
||||
The prop combinations we reach for in real consoles and inspectors.
|
||||
|
|
@ -265,7 +288,34 @@ function MyKnob() {
|
|||
editable unit="px" aria-label="Radius" />
|
||||
|
||||
// Stereo meter — one component, two channels, shared clip LED
|
||||
<Meter value={[left, right]} min={-60} max={6} clipThreshold={0} />`}
|
||||
<Meter value={[left, right]} min={-60} max={6} clipThreshold={0} />
|
||||
|
||||
// Resource gauge — colors itself green → amber → red as load climbs
|
||||
<Gauge value={cpu} min={0} max={100} unit="%" label="CPU" size={96}
|
||||
zones={[{upTo: 0.75, color: '#3df2ad'},
|
||||
{upTo: 0.9, color: '#ffd23e'},
|
||||
{upTo: 1, color: '#ff4d6b'}]} />
|
||||
|
||||
// Self-coloring resource readout — no app-side color math
|
||||
<SegmentDisplay value={cpu} digits={3} unit="%" weight={1.4}
|
||||
zones={[{upTo: 74, color: '#3df2ad'},
|
||||
{upTo: 89, color: '#ffd23e'},
|
||||
{upTo: Infinity, color: '#ff4d6b'}]} />
|
||||
|
||||
// Aligned inspector rows — share labelWidth so controls line up
|
||||
<LabeledField label="Radius" unit="px" labelWidth={64}>
|
||||
<ScrubField defaultValue={12} min={0} max={200} />
|
||||
</LabeledField>
|
||||
<LabeledField label="Fresh seed" labelWidth={64}>
|
||||
<ToggleSwitch defaultOn showStateLabel />
|
||||
</LabeledField>
|
||||
|
||||
// A declarative channel strip
|
||||
<Rack orientation="column" title="Channel 1">
|
||||
<FlatKnob label="Gain" sublabel="dB" />
|
||||
<Fader orientation="horizontal" fill valuePosition="end" />
|
||||
<Meter value={level} min={-60} max={6} fill orientation="horizontal" />
|
||||
</Rack>`}
|
||||
/>
|
||||
|
||||
<h3 className="api-h">Precision & number handling</h3>
|
||||
|
|
|
|||
|
|
@ -5,10 +5,13 @@ import {
|
|||
DreamknobProvider,
|
||||
Fader,
|
||||
FlatKnob,
|
||||
Gauge,
|
||||
ImageKnob,
|
||||
Knob,
|
||||
KnobValue,
|
||||
IndicatorLamp,
|
||||
LabeledField,
|
||||
LampRow,
|
||||
LEDFader,
|
||||
LEDKnob,
|
||||
MetalKnob,
|
||||
|
|
@ -17,12 +20,15 @@ import {
|
|||
NeonKnob,
|
||||
Pointer,
|
||||
PushButton,
|
||||
Rack,
|
||||
RubberKnob,
|
||||
ScrubField,
|
||||
SegmentSwitch,
|
||||
SegmentDisplay,
|
||||
SteppedKnob,
|
||||
Ticks,
|
||||
ToggleSwitch,
|
||||
TransportButton,
|
||||
VintageKnob,
|
||||
XYPad,
|
||||
logTaper,
|
||||
|
|
@ -197,6 +203,95 @@ const CommitModeDemo: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const RESOURCE_ZONES = [
|
||||
{ upTo: 0.75, color: '#3df2ad' },
|
||||
{ upTo: 0.9, color: '#ffd23e' },
|
||||
{ upTo: 1, color: '#ff4d6b' },
|
||||
];
|
||||
|
||||
const GaugeDemo: React.FC = () => {
|
||||
const [load, setLoad] = React.useState<[number, number]>([38, 61]);
|
||||
React.useEffect(() => {
|
||||
let t = 0;
|
||||
const id = setInterval(() => {
|
||||
t += 0.12;
|
||||
setLoad([
|
||||
Math.max(2, Math.min(100, 45 + Math.sin(t) * 30 + Math.random() * 12)),
|
||||
Math.max(2, Math.min(100, 62 + Math.sin(t * 0.7 + 2) * 26 + Math.random() * 10)),
|
||||
]);
|
||||
}, 140);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 18, alignItems: 'center' }}>
|
||||
<Gauge value={load[0]} min={0} max={100} size={104} unit="%" label="CPU" zones={RESOURCE_ZONES} />
|
||||
<Gauge value={load[1]} min={0} max={100} size={104} unit="%" label="GPU" zones={RESOURCE_ZONES} needle={false} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SmallReadoutDemo: React.FC = () => {
|
||||
const [t, setT] = React.useState(0);
|
||||
React.useEffect(() => {
|
||||
const id = setInterval(() => setT(v => v + 1), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
const hh = String(Math.floor(t / 3600) % 100).padStart(2, '0');
|
||||
const mm = String(Math.floor(t / 60) % 60).padStart(2, '0');
|
||||
const ss = String(t % 60).padStart(2, '0');
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14, alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
|
||||
<SegmentDisplay value={17.5} digits={3} decimals={1} height={13} weight={1.5} />
|
||||
<SegmentDisplay value={`${hh}:${mm}:${ss}`} height={13} weight={1.4} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
|
||||
<SegmentDisplay value={82} digits={3} height={16} weight={1.4} zones={[
|
||||
{ upTo: 74, color: '#3df2ad' },
|
||||
{ upTo: 89, color: '#ffd23e' },
|
||||
{ upTo: Infinity, color: '#ff4d6b' },
|
||||
]} />
|
||||
<AlphaDisplay value="$4.20" height={15} weight={1.3} />
|
||||
<AlphaDisplay value="47%" height={15} weight={1.3} color="#3df2ad" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PanelDemo: React.FC = () => {
|
||||
const [fresh, setFresh] = React.useState(true);
|
||||
const [mode, setMode] = React.useState(0);
|
||||
const [playing, setPlaying] = React.useState(false);
|
||||
const [armed, setArmed] = React.useState(false);
|
||||
return (
|
||||
<Rack orientation="column" title="Performer" accentColor="#c9a6ff">
|
||||
<div style={{ display: 'flex', gap: 16, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<FlatKnob size={62} defaultValue={7} min={0} max={64} step={1} color="#c9a6ff"
|
||||
label="Seed" sublabel="world #7" labelSize={9} aria-label="Seed" />
|
||||
<FlatKnob size={62} defaultValue={40} min={0} max={100} color="#c9a6ff"
|
||||
label="Refresh" sublabel="every 40" labelSize={9} aria-label="Refresh" />
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||
<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} />
|
||||
<TransportButton kind="panic" onClick={() => { setPlaying(false); setArmed(false); }} size={34} />
|
||||
</div>
|
||||
<LabeledField label="Fresh seed" labelWidth={78}>
|
||||
<ToggleSwitch on={fresh} onChange={setFresh} color="#c9a6ff" showStateLabel aria-label="Fresh seed" />
|
||||
</LabeledField>
|
||||
<SegmentSwitch options={['SOLID', 'GRAD', 'NOISE']} value={mode} onChange={setMode} color="#c9a6ff" aria-label="Fill mode" />
|
||||
<LampRow
|
||||
lamps={[
|
||||
{ label: 'Cue', on: playing, color: '#3df2ad' },
|
||||
{ label: 'Rec', on: armed, color: '#ff4d6b' },
|
||||
{ label: 'Seed', on: fresh, color: '#c9a6ff' },
|
||||
]}
|
||||
/>
|
||||
</Rack>
|
||||
);
|
||||
};
|
||||
|
||||
const ImageKnobDemo: React.FC = () => {
|
||||
const src = useGeneratedStrip();
|
||||
if (!src) return null;
|
||||
|
|
@ -429,6 +524,18 @@ export const Gallery: React.FC = () => (
|
|||
<CommitModeDemo />
|
||||
</Card>
|
||||
|
||||
<Card title="Gauge" desc="Read-only radial arc meter — the round sibling of Meter. Colors itself green→amber→red as load climbs." tag="<Gauge />">
|
||||
<GaugeDemo />
|
||||
</Card>
|
||||
|
||||
<Card title="Small readouts" desc="Bold own-cell decimal + colon, weight to thicken strokes, and value-colored zones — legible at status-bar sizes. Colons and $/% are in the charset." tag="weight · zones · : $ %">
|
||||
<SmallReadoutDemo />
|
||||
</Card>
|
||||
|
||||
<Card title="Panel: strip + switches" desc="Rack chrome, ToggleSwitch, TransportButton, SegmentSwitch and a LampRow — a console section, declarative." tag="<Rack /> · <ToggleSwitch /> · <TransportButton />">
|
||||
<PanelDemo />
|
||||
</Card>
|
||||
|
||||
<Card title="Gradient arc" desc="Position-anchored gradient — the red zone stays at the top of travel." tag="<Arc gradient={...} />">
|
||||
<Knob size={96} min={-60} max={6} step={0.5} defaultValue={-8} aria-label="Gradient arc demo">
|
||||
<Arc
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@ export const GettingStarted: React.FC = () => (
|
|||
|
||||
<Step n={1} title="Install">
|
||||
<CodeBlock
|
||||
code={`pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.0/dreamknob-1.1.0.tgz\n# (or npm install <same url>) · peer deps: react >= 18, react-dom >= 18`}
|
||||
code={`pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.1/dreamknob-1.1.1.tgz\n# (or npm install <same url>) · peer deps: react >= 18, react-dom >= 18`}
|
||||
/>
|
||||
<p className="api-note" style={{ marginTop: 10 }}>
|
||||
Installs straight from the{' '}
|
||||
<a href="https://git.dreamodus.software/Dreamodus/DreamKnob/releases">
|
||||
v1.1.0 release
|
||||
v1.1.1 release
|
||||
</a>{' '}
|
||||
on our Forgejo. Zero runtime dependencies, ESM + CJS, full TypeScript types. No
|
||||
CSS file to import — everything is SVG and inline styles.
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ export const Hero: React.FC = () => {
|
|||
range, any decimal precision, and colors you fully control.
|
||||
</p>
|
||||
<div className="install">
|
||||
<span>pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.0/dreamknob-1.1.0.tgz</span>
|
||||
<span>pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.1/dreamknob-1.1.1.tgz</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText('pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.0/dreamknob-1.1.0.tgz');
|
||||
navigator.clipboard.writeText('pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.1/dreamknob-1.1.1.tgz');
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1400);
|
||||
}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue