From 5b970919027e61fb019db8dee34cfd8300539884 Mon Sep 17 00:00:00 2001 From: Dreamodus Date: Mon, 13 Jul 2026 19:20:23 -0700 Subject: [PATCH 1/6] 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. --- README.md | 7 +- apps/docs/src/sections/ApiDocs.tsx | 74 +++++-- apps/docs/src/sections/Gallery.tsx | 107 ++++++++++ apps/docs/src/sections/GettingStarted.tsx | 4 +- apps/docs/src/sections/Hero.tsx | 4 +- packages/dreamknob/CHANGELOG.md | 40 ++++ packages/dreamknob/README.md | 21 +- packages/dreamknob/package.json | 2 +- packages/dreamknob/src/core/theme.tsx | 12 ++ .../dreamknob/src/digital/AlphaDisplay.tsx | 113 +++++++--- .../dreamknob/src/digital/SegmentDisplay.tsx | 49 ++++- .../dreamknob/src/digital/segments.test.ts | 46 +++++ packages/dreamknob/src/digital/segments.tsx | 113 ++++++---- packages/dreamknob/src/index.ts | 13 ++ .../dreamknob/src/layout/LabeledField.tsx | 91 ++++++++ packages/dreamknob/src/layout/Rack.tsx | 96 +++++++++ packages/dreamknob/src/primitives/Text.tsx | 66 ++++-- packages/dreamknob/src/skins/Fader.tsx | 28 ++- packages/dreamknob/src/skins/FlatKnob.tsx | 5 +- packages/dreamknob/src/skins/Gauge.tsx | 179 ++++++++++++++++ packages/dreamknob/src/skins/LEDKnob.tsx | 5 +- packages/dreamknob/src/skins/LampRow.tsx | 134 ++++++++++++ packages/dreamknob/src/skins/MetalKnob.tsx | 5 +- packages/dreamknob/src/skins/Meter.tsx | 26 ++- packages/dreamknob/src/skins/NeonKnob.tsx | 5 +- packages/dreamknob/src/skins/PushButton.tsx | 21 +- packages/dreamknob/src/skins/RubberKnob.tsx | 5 +- packages/dreamknob/src/skins/SteppedKnob.tsx | 5 +- packages/dreamknob/src/skins/ToggleSwitch.tsx | 195 ++++++++++++++++++ .../dreamknob/src/skins/TransportButton.tsx | 185 +++++++++++++++++ packages/dreamknob/src/skins/VintageKnob.tsx | 5 +- packages/dreamknob/src/skins/shared.ts | 7 + 32 files changed, 1535 insertions(+), 133 deletions(-) create mode 100644 packages/dreamknob/src/digital/segments.test.ts create mode 100644 packages/dreamknob/src/layout/LabeledField.tsx create mode 100644 packages/dreamknob/src/layout/Rack.tsx create mode 100644 packages/dreamknob/src/skins/Gauge.tsx create mode 100644 packages/dreamknob/src/skins/LampRow.tsx create mode 100644 packages/dreamknob/src/skins/ToggleSwitch.tsx create mode 100644 packages/dreamknob/src/skins/TransportButton.tsx diff --git a/README.md b/README.md index d40beb9..546f3ec 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,9 @@ import { MetalKnob, LEDKnob, Fader, logTaper } from 'dreamknob' power / custom tapers. - **Styles**: `FlatKnob`, `MetalKnob`, `RubberKnob`, `VintageKnob`, `LEDKnob`, `NeonKnob`, `SteppedKnob`, `ImageKnob` (film-strips), `Fader`, `LEDFader`, `Meter`, `MeterBridge`, - `XYPad`, `PushButton`, `IndicatorLamp`, `SegmentSwitch`, `ScrubField`, - `SegmentDisplay`, `AlphaDisplay` — every color themeable per instance. + `Gauge`, `XYPad`, `PushButton`, `IndicatorLamp`, `LampRow`, `ToggleSwitch`, + `SegmentSwitch`, `TransportButton`, `ScrubField`, `SegmentDisplay`, `AlphaDisplay`, + plus `LabeledField`/`Rack` layout — every color themeable per instance. - **Composable**: `` + primitives (`Arc`, `Pointer`, `Ticks`, `TickLabels`, `Face`, `KnobValue`, `KnobLabel`, `GlowFilter`) for custom designs, or go fully headless with `useKnob`. @@ -49,7 +50,7 @@ See `packages/dreamknob/README.md` and the docs app for the full API. The easiest way is the published release on our Forgejo: ```bash -pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.0/dreamknob-1.1.0.tgz +pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.1/dreamknob-1.1.1.tgz ``` Or build locally — the package builds to `packages/dreamknob/dist` (ESM + CJS + types): diff --git a/apps/docs/src/sections/ApiDocs.tsx b/apps/docs/src/sections/ApiDocs.tsx index 20ff93c..2d4f086 100644 --- a/apps/docs/src/sections/ApiDocs.tsx +++ b/apps/docs/src/sections/ApiDocs.tsx @@ -128,24 +128,32 @@ export const ApiDocs: React.FC = () => ( - + - + - + - - + + + + + + + +

- Every knob skin and fader also takes label, showValue,{' '} - unit, format, className,{' '} - style, name plus every core prop above, and forwards a - ref to its root element (VintageKnob has no readout, so it omits{' '} + Every knob skin and fader also takes label, sublabel{' '} + (a second caption line — SEED over world #7),{' '} + labelSize (caption size independent of the dial diameter),{' '} + showValue, unit, format,{' '} + className, style, name plus every core prop + above, and forwards a ref to its root element (VintageKnob has no readout, so it omits{' '} showValue/unit/format; the display components Meter/SegmentDisplay/AlphaDisplay{' '} 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 }} > @@ -234,6 +245,18 @@ function MyKnob() { }`} /> +

Small readouts

+

+ Segment displays keep their proportions down to about{' '} + 11 px tall; below that, raise weight 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{' '} + 17.5 and 1:23:45 stay readable in a status bar. Widen{' '} + gap when packed tokens like 18.1/24 crowd. Colons and{' '} + $/% are in the charset, so a whole token can live in one + display instead of text beside it. +

+

Recipes

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 -`} + + +// Resource gauge — colors itself green → amber → red as load climbs + + +// Self-coloring resource readout — no app-side color math + + +// Aligned inspector rows — share labelWidth so controls line up + + + + + + + +// A declarative channel strip + + + + +`} />

Precision & number handling

diff --git a/apps/docs/src/sections/Gallery.tsx b/apps/docs/src/sections/Gallery.tsx index eebd097..050b87f 100644 --- a/apps/docs/src/sections/Gallery.tsx +++ b/apps/docs/src/sections/Gallery.tsx @@ -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 ( +
+ + +
+ ); +}; + +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 ( +
+
+ + +
+
+ + + +
+
+ ); +}; + +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 ( + +
+ + +
+
+ setPlaying(p => !p)} size={34} /> + setPlaying(false)} size={34} /> + setArmed(a => !a)} size={34} /> + { setPlaying(false); setArmed(false); }} size={34} /> +
+ + + + + +
+ ); +}; + const ImageKnobDemo: React.FC = () => { const src = useGeneratedStrip(); if (!src) return null; @@ -429,6 +524,18 @@ export const Gallery: React.FC = () => ( + + + + + + + + + + + + ( ) · 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 ) · peer deps: react >= 18, react-dom >= 18`} />

Installs straight from the{' '} - v1.1.0 release + v1.1.1 release {' '} on our Forgejo. Zero runtime dependencies, ESM + CJS, full TypeScript types. No CSS file to import — everything is SVG and inline styles. diff --git a/apps/docs/src/sections/Hero.tsx b/apps/docs/src/sections/Hero.tsx index bac156d..f71020d 100644 --- a/apps/docs/src/sections/Hero.tsx +++ b/apps/docs/src/sections/Hero.tsx @@ -25,10 +25,10 @@ export const Hero: React.FC = () => { range, any decimal precision, and colors you fully control.

- pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.0/dreamknob-1.1.0.tgz + pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.1/dreamknob-1.1.1.tgz + ) : ( + {inner} + ); + })} +
+ ); +}; diff --git a/packages/dreamknob/src/skins/MetalKnob.tsx b/packages/dreamknob/src/skins/MetalKnob.tsx index 26690e0..e45afa9 100644 --- a/packages/dreamknob/src/skins/MetalKnob.tsx +++ b/packages/dreamknob/src/skins/MetalKnob.tsx @@ -33,6 +33,8 @@ export const MetalKnob = React.forwardRef(functi showArc = true, tickCount = 21, label, + sublabel, + labelSize, showValue = false, unit, format, @@ -137,7 +139,8 @@ export const MetalKnob = React.forwardRef(functi color={labelColor ?? theme.label} fontFamily={theme.fontUI} dy={size * 0.44} - fontSize={size * 0.1} + fontSize={labelSize ?? size * 0.1} + sublabel={sublabel} > {label} diff --git a/packages/dreamknob/src/skins/Meter.tsx b/packages/dreamknob/src/skins/Meter.tsx index d088b48..ac9fb3b 100644 --- a/packages/dreamknob/src/skins/Meter.tsx +++ b/packages/dreamknob/src/skins/Meter.tsx @@ -17,6 +17,14 @@ export interface MeterProps { orientation?: 'vertical' | 'horizontal'; /** Travel length in px. Default: 160. */ length?: number; + /** + * Scale to fill the container instead of a fixed px footprint. The SVG keeps + * its `length`×`breadth` aspect ratio; a horizontal meter fills width, a + * vertical one fills height. Great for resizable dock panels. + */ + fill?: boolean; + /** Dim the meter uniformly (matches disabled controls). */ + disabled?: boolean; /** Cross-axis size in px PER CHANNEL. Minimum useful: ~6. Default: 16. */ breadth?: number; /** Number of LED segments. Default: 28. */ @@ -81,6 +89,8 @@ export const Meter: React.FC = ({ taper = linearTaper, orientation = 'vertical', length = 160, + fill = false, + disabled = false, breadth = 16, segments = 28, color, @@ -225,12 +235,15 @@ export const Meter: React.FC = ({ aria-valuenow={hottestShown} aria-label={aria['aria-label']} data-clipped={clipped ? '' : undefined} + data-disabled={disabled ? '' : undefined} style={{ - display: 'inline-flex', + display: fill ? 'flex' : 'inline-flex', flexDirection: 'column', alignItems: 'center', gap: 5, - width: w, + width: fill && !vertical ? '100%' : w, + height: fill && vertical ? '100%' : undefined, + opacity: disabled ? 0.45 : undefined, ...style, }} > @@ -252,7 +265,14 @@ export const Meter: React.FC = ({ )} )} - + {leadingIcon && ( + + )} + {children} + + )} {name && } ); diff --git a/packages/dreamknob/src/skins/RubberKnob.tsx b/packages/dreamknob/src/skins/RubberKnob.tsx index 92b2cfa..1f1cc09 100644 --- a/packages/dreamknob/src/skins/RubberKnob.tsx +++ b/packages/dreamknob/src/skins/RubberKnob.tsx @@ -25,6 +25,8 @@ export const RubberKnob = React.forwardRef(func labelColor, arcFrom = 'min', label, + sublabel, + labelSize, showValue = false, unit, format, @@ -103,7 +105,8 @@ export const RubberKnob = React.forwardRef(func color={labelColor ?? theme.label} fontFamily={theme.fontUI} dy={size * 0.44} - fontSize={size * 0.1} + fontSize={labelSize ?? size * 0.1} + sublabel={sublabel} > {label} diff --git a/packages/dreamknob/src/skins/SteppedKnob.tsx b/packages/dreamknob/src/skins/SteppedKnob.tsx index b07fed0..91dfa50 100644 --- a/packages/dreamknob/src/skins/SteppedKnob.tsx +++ b/packages/dreamknob/src/skins/SteppedKnob.tsx @@ -29,6 +29,8 @@ export const SteppedKnob = React.forwardRef(fu textColor, labelColor, label, + sublabel, + labelSize, showValue = true, format, focusRing, @@ -106,7 +108,8 @@ export const SteppedKnob = React.forwardRef(fu color={labelColor ?? theme.label} fontFamily={theme.fontUI} dy={size * 0.45} - fontSize={size * 0.095} + fontSize={labelSize ?? size * 0.095} + sublabel={sublabel} > {label} diff --git a/packages/dreamknob/src/skins/ToggleSwitch.tsx b/packages/dreamknob/src/skins/ToggleSwitch.tsx new file mode 100644 index 0000000..e6ffa8d --- /dev/null +++ b/packages/dreamknob/src/skins/ToggleSwitch.tsx @@ -0,0 +1,195 @@ +import * as React from 'react'; +import { useKnobTheme } from '../core/theme'; + +export interface ToggleSwitchProps { + /** Controlled on-state. Pair with `onChange`. */ + on?: boolean; + defaultOn?: boolean; + onChange?: (on: boolean) => void; + /** Accent color when on. Defaults to the theme accent. */ + color?: string; + /** Track height in px (the knob is sized from it). Default: 22. */ + size?: number; + /** Caption beside the switch. */ + label?: string; + labelPosition?: 'right' | 'left'; + /** Tiny OFF/ON legend printed on the track. Default: false. */ + showStateLabel?: boolean; + /** Text for the on/off legend when `showStateLabel`. Default: 'ON'/'OFF'. */ + onLabel?: string; + offLabel?: string; + disabled?: boolean; + /** Render a hidden form input carrying "on"/"off". */ + name?: string; + className?: string; + style?: React.CSSProperties; + 'aria-label'?: string; +} + +/** + * A rocker/slider switch — the most literal boolean idiom. Renders + * `role="switch"`; the knob slides between OFF and ON. Space/Enter toggles, + * ←/→ set off/on. + */ +export const ToggleSwitch = React.forwardRef( + function ToggleSwitch( + { + on, + defaultOn = false, + onChange, + color, + size = 22, + label, + labelPosition = 'right', + showStateLabel = false, + onLabel = 'ON', + offLabel = 'OFF', + disabled = false, + name, + className, + style, + ...aria + }, + ref, + ) { + const theme = useKnobTheme(); + const accent = color ?? theme.accent ?? '#4cc2ff'; + const isControlled = on !== undefined; + const [internal, setInternal] = React.useState(defaultOn); + const [focusVisible, setFocusVisible] = React.useState(false); + const lit = isControlled ? (on as boolean) : internal; + + const set = (next: boolean) => { + if (next === lit) return; + if (!isControlled) setInternal(next); + onChange?.(next); + }; + + const trackW = size * 1.85; + const trackH = size; + const knobD = size - 6; + const pad = 3; + const knobX = lit ? trackW - knobD - pad : pad; + + const legend = (side: 'on' | 'off') => ( + + ); + + return ( + + ); + }, +); diff --git a/packages/dreamknob/src/skins/TransportButton.tsx b/packages/dreamknob/src/skins/TransportButton.tsx new file mode 100644 index 0000000..9ce6917 --- /dev/null +++ b/packages/dreamknob/src/skins/TransportButton.tsx @@ -0,0 +1,185 @@ +import * as React from 'react'; +import { useKnobTheme } from '../core/theme'; + +export type TransportKind = + | 'play' + | 'pause' + | 'stop' + | 'record' + | 'rewind' + | 'forward' + | 'panic'; + +export interface TransportButtonProps { + kind: TransportKind; + /** Engaged/lit state (playing, armed, recording). Reflects app state. */ + active?: boolean; + onClick?: () => void; + /** Pulse the glyph while active (auto-on for `record`). Reduced-motion aware. */ + blink?: boolean; + /** Override the glyph/LED color. Defaults per kind (play green, record red…). */ + color?: string; + /** Button size in px (square). Default: 38. */ + size?: number; + disabled?: boolean; + className?: string; + style?: React.CSSProperties; + 'aria-label'?: string; +} + +const LABEL: Record = { + play: 'Play', + pause: 'Pause', + stop: 'Stop', + record: 'Record', + rewind: 'Rewind', + forward: 'Forward', + panic: 'Panic — all notes off', +}; + +/** Glyph in a 24×24 box, filled with `currentColor`. */ +const Glyph: React.FC<{ kind: TransportKind }> = ({ kind }) => { + switch (kind) { + case 'play': + return ; + case 'pause': + return ( + <> + + + + ); + case 'stop': + return ; + case 'record': + return ; + case 'rewind': + return ( + <> + + + + ); + case 'forward': + return ( + <> + + + + ); + case 'panic': + return ( + <> + + + + ); + } +}; + +/** + * A record / play / stop / pause / panic transport button, styled to sit + * alongside dreamknob controls. `active` lights it; `record` blinks while + * armed. Reduced-motion aware. + */ +export const TransportButton = React.forwardRef( + function TransportButton( + { kind, active = false, onClick, blink, color, size = 38, disabled = false, className, style, ...aria }, + ref, + ) { + const theme = useKnobTheme(); + const [focusVisible, setFocusVisible] = React.useState(false); + const glyphRef = React.useRef(null); + + const accent = + color ?? + (kind === 'record' || kind === 'panic' + ? theme.zoneHot + : kind === 'play' + ? theme.zoneGood + : kind === 'pause' + ? theme.zoneWarn + : theme.text); + const lit = active ? accent : theme.label; + const shouldBlink = (blink ?? kind === 'record') && active; + + React.useEffect(() => { + const el = glyphRef.current; + if (!el || !shouldBlink) return; + if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) + return; + const anim = el.animate([{ opacity: 1 }, { opacity: 0.3 }], { + duration: 560, + iterations: Infinity, + direction: 'alternate', + easing: 'ease-in-out', + }); + return () => anim.cancel(); + }, [shouldBlink]); + + return ( + + ); + }, +); diff --git a/packages/dreamknob/src/skins/VintageKnob.tsx b/packages/dreamknob/src/skins/VintageKnob.tsx index 123a1f4..8c97c64 100644 --- a/packages/dreamknob/src/skins/VintageKnob.tsx +++ b/packages/dreamknob/src/skins/VintageKnob.tsx @@ -103,6 +103,8 @@ export const VintageKnob = React.forwardRef(fu scaleLabels, indicatorColor, label, + sublabel, + labelSize, focusRing, className, style, @@ -132,7 +134,8 @@ export const VintageKnob = React.forwardRef(fu color={labelColor ?? theme.label} fontFamily={theme.fontUI} dy={size * 0.46} - fontSize={size * 0.095} + fontSize={labelSize ?? size * 0.095} + sublabel={sublabel} > {label} diff --git a/packages/dreamknob/src/skins/shared.ts b/packages/dreamknob/src/skins/shared.ts index ddadc36..5eb989a 100644 --- a/packages/dreamknob/src/skins/shared.ts +++ b/packages/dreamknob/src/skins/shared.ts @@ -9,6 +9,13 @@ export interface SkinProps extends KnobCoreProps { size?: number; /** Caption drawn in the travel gap at the bottom of the knob. */ label?: string; + /** + * Secondary caption line under the label — a unit or live descriptor + * (`SEED` over `world #7`). Rendered smaller and un-uppercased. + */ + sublabel?: React.ReactNode; + /** Label font size in px, independent of `size` (small knobs, readable labels). */ + labelSize?: number; /** Show the numeric readout. Default varies per skin. */ showValue?: boolean; /** Unit suffix for the readout, e.g. "dB", "Hz", "%". */ From 89c7a7f603d8e251af74c576e8557cc9d4909688 Mon Sep 17 00:00:00 2001 From: Dreamodus Date: Mon, 13 Jul 2026 19:36:28 -0700 Subject: [PATCH 2/6] Gauge: short rim pointer so the needle never covers the center readout The full center-to-rim needle crossed the digital readout at low/mid values. Draw it as a short marker riding just inside the arc (with a dark outline for contrast) and recenter the readout. --- packages/dreamknob/src/skins/Gauge.tsx | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/dreamknob/src/skins/Gauge.tsx b/packages/dreamknob/src/skins/Gauge.tsx index b7444dc..7fa212d 100644 --- a/packages/dreamknob/src/skins/Gauge.tsx +++ b/packages/dreamknob/src/skins/Gauge.tsx @@ -91,8 +91,10 @@ export const Gauge: React.FC = ({ const valueAngle = angleFromNormalized(n, angleOffset, angleRange); const trackPath = describeArc(c, c, r, angleOffset, angleOffset + angleRange); const valuePath = n > 0.001 ? describeArc(c, c, r, angleOffset, valueAngle) : ''; - const tip = polarToCartesian(c, c, r - t * 0.1, valueAngle); - const tail = polarToCartesian(c, c, t * 0.4, valueAngle); + // A short pointer riding the rim — it never crosses the center, so it can't + // cover the digital readout that sits there. + const tip = polarToCartesian(c, c, r + t * 0.15, valueAngle); + const tail = polarToCartesian(c, c, r - t * 1.2, valueAngle); return (
= ({ {valuePath && ( )} - {needle && ( + {needle && n > 0.001 && ( + )} + {needle && n > 0.001 && ( + )} - {needle && } {showValue && (
= ({ position: 'absolute', left: 0, right: 0, - top: needle ? '58%' : '50%', + top: '50%', transform: 'translateY(-50%)', display: 'flex', flexDirection: 'column', From a6348d5c46d176a3db3a73b953923716503573b3 Mon Sep 17 00:00:00 2001 From: Dreamodus Date: Tue, 14 Jul 2026 06:11:26 -0700 Subject: [PATCH 3/6] v1.1.2: action button, responsive faders, label scaling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- README.md | 2 +- apps/docs/src/App.tsx | 3 + apps/docs/src/sections/ApiDocs.tsx | 7 +- apps/docs/src/sections/Buttons.tsx | 156 ++++++++++++++++++ apps/docs/src/styles.css | 70 ++++++++ packages/dreamknob/CHANGELOG.md | 32 ++++ packages/dreamknob/README.md | 2 +- packages/dreamknob/package.json | 2 +- packages/dreamknob/src/index.ts | 2 +- .../dreamknob/src/skins/IndicatorLamp.tsx | 9 +- packages/dreamknob/src/skins/LEDFader.tsx | 32 +++- packages/dreamknob/src/skins/LampRow.tsx | 5 +- packages/dreamknob/src/skins/PushButton.tsx | 77 +++++++-- 13 files changed, 372 insertions(+), 27 deletions(-) create mode 100644 apps/docs/src/sections/Buttons.tsx diff --git a/README.md b/README.md index 546f3ec..04cf677 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ See `packages/dreamknob/README.md` and the docs app for the full API. The easiest way is the published release on our Forgejo: ```bash -pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.1/dreamknob-1.1.1.tgz +pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.2/dreamknob-1.1.2.tgz ``` Or build locally — the package builds to `packages/dreamknob/dist` (ESM + CJS + types): diff --git a/apps/docs/src/App.tsx b/apps/docs/src/App.tsx index 09bca6d..1d02c03 100644 --- a/apps/docs/src/App.tsx +++ b/apps/docs/src/App.tsx @@ -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 = () => (
Start Gallery + Buttons Synth Playground Guide @@ -36,6 +38,7 @@ export const App: React.FC = () => ( + diff --git a/apps/docs/src/sections/ApiDocs.tsx b/apps/docs/src/sections/ApiDocs.tsx index 2d4f086..ac44fa2 100644 --- a/apps/docs/src/sections/ApiDocs.tsx +++ b/apps/docs/src/sections/ApiDocs.tsx @@ -129,13 +129,14 @@ export const ApiDocs: React.FC = () => ( - + - - + + + diff --git a/apps/docs/src/sections/Buttons.tsx b/apps/docs/src/sections/Buttons.tsx new file mode 100644 index 0000000..02676f4 --- /dev/null +++ b/apps/docs/src/sections/Buttons.tsx @@ -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 }) => ( + +); + +/** 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 ( +
+
+
Buttons & switches
+

Press, latch, or pick

+

+ Five components cover every panel control that isn't a dial. The + only question is what the press means: +

+ +
+ + + + + +
+ +
+ — action, fired ${fired}×`}> + + + + + + + + Bypass + + + Talk + + + + + setPlaying(p => !p)} size={34} /> + setPlaying(false)} size={34} /> + setArmed(a => !a)} size={34} /> + + + + + + +
+ +

+ <Button> is <PushButton mode="action"> — + it never latches, shows a press-down and a brief LED flash, and calls{' '} + onClick. led="dot" gives the corner-dot + look; led={false} a plain button. +

+
+
+ ); +}; + +const GuideRow: React.FC<{ code: string; when: string; eg: string }> = ({ code, when, eg }) => ( +
+ {code} + {when} + {eg} +
+); + +const Demo: React.FC<{ label: string; children: React.ReactNode }> = ({ label, children }) => ( +
+
{label}
+
{children}
+
+); diff --git a/apps/docs/src/styles.css b/apps/docs/src/styles.css index 1ea24c2..b17d254 100644 --- a/apps/docs/src/styles.css +++ b/apps/docs/src/styles.css @@ -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; diff --git a/packages/dreamknob/CHANGELOG.md b/packages/dreamknob/CHANGELOG.md index eb9e2ab..07ab660 100644 --- a/packages/dreamknob/CHANGELOG.md +++ b/packages/dreamknob/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## 1.1.2 — 2026-07-14 + +Third integration-feedback release — a general action button, responsive +faders everywhere, and the label-scaling gap, all surfaced building a +resizable streaming studio. + +### New components +- `Button` — a normal **click-to-fire** command button in the panel style + (Regenerate, Add, Apply, Refresh), the non-latching sibling of `PushButton`. + It's `PushButton` locked to the new `mode="action"`: never latches, shows a + press-down + a brief LED flash, calls `onClick`. `led="dot"` (default) gives + the corner-dot look; `led={false}` a plain button. + +### Buttons +- `PushButton` gains **`mode="action"`** (alongside `toggle`/`momentary`) and + an `onClick` prop — the stateless command-button behavior `Button` wraps. + +### Responsive faders +- **`fill`** on `LEDFader` (matching `Fader`) — the fader stretches along its + travel axis to fill the container instead of a fixed `length`, so faders in + resizable dock panels size themselves with no wrapper measurement. + +### Label scaling +- **`labelSize`** on `IndicatorLamp` — the caption otherwise tracks `size` but + is floored at 10px; set this to shrink it with a small lamp (e.g. matching a + dense header row). Also on `LampRow` for parity. + +### Docs +- A dedicated **Buttons & switches** section with a "which do I use?" guide + (Button = action · PushButton = latch · TransportButton = transport · + ToggleSwitch = boolean · SegmentSwitch = mode) and a live demo of each. + ## 1.1.1 — 2026-07-13 Second integration-feedback release — the friction that showed up once the diff --git a/packages/dreamknob/README.md b/packages/dreamknob/README.md index 4687401..0a03e50 100644 --- a/packages/dreamknob/README.md +++ b/packages/dreamknob/README.md @@ -5,7 +5,7 @@ headless core, any number range, any decimal precision, and colors you fully con Zero runtime dependencies. ``` -pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.1/dreamknob-1.1.1.tgz +pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.2/dreamknob-1.1.2.tgz ``` (Latest release tarballs: https://git.dreamodus.software/Dreamodus/DreamKnob/releases) diff --git a/packages/dreamknob/package.json b/packages/dreamknob/package.json index be13c56..b587886 100644 --- a/packages/dreamknob/package.json +++ b/packages/dreamknob/package.json @@ -1,6 +1,6 @@ { "name": "dreamknob", - "version": "1.1.1", + "version": "1.1.2", "description": "A feature-full, studio-grade React knob & fader library. Rotary, linear, 2D, 3D, digital and analog styles with a headless core.", "license": "MIT", "author": "Dreamodus Software Inc.", diff --git a/packages/dreamknob/src/index.ts b/packages/dreamknob/src/index.ts index 7f58d1c..f030813 100644 --- a/packages/dreamknob/src/index.ts +++ b/packages/dreamknob/src/index.ts @@ -70,7 +70,7 @@ export { ImageKnob, type ImageKnobProps } from './skins/ImageKnob'; export { Meter, type MeterProps } from './skins/Meter'; export { MeterBridge, type MeterBridgeProps, type MeterBridgeChannel } from './skins/MeterBridge'; export { Gauge, type GaugeProps } from './skins/Gauge'; -export { PushButton, type PushButtonProps } from './skins/PushButton'; +export { PushButton, type PushButtonProps, Button, type ButtonProps } from './skins/PushButton'; export { IndicatorLamp, type IndicatorLampProps } from './skins/IndicatorLamp'; export { LampRow, type LampRowProps, type LampRowItem } from './skins/LampRow'; export { ToggleSwitch, type ToggleSwitchProps } from './skins/ToggleSwitch'; diff --git a/packages/dreamknob/src/skins/IndicatorLamp.tsx b/packages/dreamknob/src/skins/IndicatorLamp.tsx index 8154410..f02f48c 100644 --- a/packages/dreamknob/src/skins/IndicatorLamp.tsx +++ b/packages/dreamknob/src/skins/IndicatorLamp.tsx @@ -19,6 +19,12 @@ export interface IndicatorLampProps { readOnly?: boolean; /** Caption beside the lamp. */ label?: string; + /** + * Caption font size in px, independent of the lamp diameter. Without it the + * label tracks `size` but is floored at 10px; set this to shrink the caption + * with a small lamp (e.g. matching a dense header row's height). + */ + labelSize?: number; labelPosition?: 'right' | 'left'; disabled?: boolean; /** Render a hidden form input carrying "on"/"off". */ @@ -45,6 +51,7 @@ export const IndicatorLamp = React.forwardRef { orientation?: 'vertical' | 'horizontal'; /** Travel length in px. Default: 160. */ length?: number; + /** + * Stretch along the travel axis to fill the container (horizontal fills + * width, vertical fills height) instead of a fixed `length` — the same + * responsive behavior as `Fader`, so faders in resizable dock panels size + * themselves instead of needing wrapper measurement. + */ + fill?: boolean; /** Cross-axis size in px. Default: 34. */ breadth?: number; /** Number of LED segments. Default: 24. */ @@ -57,6 +64,7 @@ export interface LEDFaderProps extends Omit { export const LEDFader = React.forwardRef(function LEDFader({ orientation = 'vertical', length = 160, + fill = false, breadth = 34, segments = 24, color: colorProp, @@ -95,6 +103,12 @@ export const LEDFader = React.forwardRef(function const w = vertical ? breadth : length; const h = vertical ? length : breadth; + // Fill stretches the travel axis (via preserveAspectRatio='none'); the cross + // axis stays at `breadth`. The LED geometry is computed in the fixed viewBox + // and scaled to the element — pointer mapping measures the real element, so + // interaction stays correct at any rendered width. + const ctrlW = fill && !vertical ? '100%' : w; + const ctrlH = fill && vertical ? '100%' : h; const zoneList: readonly LEDFaderZone[] = React.useMemo(() => { if (zones) return [...zones].sort((a, b) => a.upTo - b.upTo); if (color) return [{ upTo: 1, color }]; @@ -157,7 +171,8 @@ export const LEDFader = React.forwardRef(function flexDirection: 'column', alignItems: 'center', gap: 6, - width: w, + width: fill && !vertical ? '100%' : w, + height: fill && vertical ? '100%' : undefined, opacity: core.disabled ? 0.45 : undefined, ...style, }} @@ -218,14 +233,21 @@ export const LEDFader = React.forwardRef(function style={{ ...knob.bind.style, opacity: undefined, // the outer wrapper owns disabled dimming - width: w, - height: h, - display: 'inline-flex', + width: ctrlW, + height: ctrlH, + display: fill ? 'flex' : 'inline-flex', borderRadius: 6, ...(knob.isFocusVisible && ring ? { boxShadow: `0 0 0 2px ${ring}` } : undefined), }} > - void; - /** 'toggle' latches; 'momentary' is only on while held. Default: 'toggle'. */ - mode?: 'toggle' | 'momentary'; + /** + * 'toggle' latches; 'momentary' is on only while held; 'action' is a normal + * click-to-fire command button — it never latches, shows a press-down + a + * brief LED flash, and calls `onClick`. Default: 'toggle'. + */ + mode?: 'toggle' | 'momentary' | 'action'; + /** Fired on activation in `action` mode (click, Enter, or Space). */ + onClick?: () => void; /** LED / active color. */ color?: string; /** @@ -39,6 +45,7 @@ export const PushButton = React.forwardRef( pressed, defaultPressed = false, onChange, + onClick, mode = 'toggle', color, led = true, @@ -59,16 +66,42 @@ export const PushButton = React.forwardRef( const isControlled = pressed !== undefined; const [internal, setInternal] = React.useState(defaultPressed); const [focusVisible, setFocusVisible] = React.useState(false); - const on = isControlled ? (pressed as boolean) : internal; + // Action mode is stateless: a transient `held` (pointer down) drives the + // press-down look and a short `flash` lights the LED on activation. + const [held, setHeld] = React.useState(false); + const [flash, setFlash] = React.useState(false); + const flashTimer = React.useRef>(); + React.useEffect(() => () => clearTimeout(flashTimer.current), []); + const latched = isControlled ? (pressed as boolean) : internal; + const on = mode === 'action' ? held : latched; // press-down / background inset + const ledOn = mode === 'action' ? held || flash : latched; const set = (next: boolean) => { - if (next === on) return; + if (next === latched) return; if (!isControlled) setInternal(next); onChange?.(next); }; + const fire = () => { + onClick?.(); + setFlash(true); + clearTimeout(flashTimer.current); + flashTimer.current = setTimeout(() => setFlash(false), 160); + }; + const momentaryProps = - mode === 'momentary' + mode === 'action' + ? { + onClick: fire, + onPointerDown: (e: React.PointerEvent) => { + e.currentTarget.setPointerCapture(e.pointerId); + setHeld(true); + }, + onPointerUp: () => setHeld(false), + onPointerCancel: () => setHeld(false), + onPointerLeave: () => setHeld(false), + } + : mode === 'momentary' ? { onPointerDown: (e: React.PointerEvent) => { e.currentTarget.setPointerCapture(e.pointerId); @@ -83,14 +116,14 @@ export const PushButton = React.forwardRef( if (e.key === ' ' || e.key === 'Enter') set(false); }, } - : { onClick: () => set(!on) }; + : { onClick: () => set(!latched) }; return ( ); }, ); + +export interface ButtonProps + extends Omit { + onClick?: () => void; +} + +/** + * A normal click-to-fire command button in the studio-panel style — the + * non-latching sibling of `PushButton`. Use it for actions (Regenerate, + * Add, Apply, Refresh). It's `PushButton` locked to `mode="action"`; pass + * `led="dot"` for the corner-dot look (like a Fresh Seed button) or + * `led={false}` for a plain button. + */ +export const Button = React.forwardRef( + function Button({ led = 'dot', ...rest }, ref) { + return ; + }, +); From 605c38af51216a4565605c3164c4e5959f119b51 Mon Sep 17 00:00:00 2001 From: Dreamodus Date: Tue, 14 Jul 2026 06:36:39 -0700 Subject: [PATCH 4/6] docs: point the site install command at the v1.1.2 release tarball The READMEs were bumped for 1.1.2 but the docs Hero/GettingStarted still showed the v1.1.1 URL. --- apps/docs/src/sections/GettingStarted.tsx | 4 ++-- apps/docs/src/sections/Hero.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/src/sections/GettingStarted.tsx b/apps/docs/src/sections/GettingStarted.tsx index f973723..895817d 100644 --- a/apps/docs/src/sections/GettingStarted.tsx +++ b/apps/docs/src/sections/GettingStarted.tsx @@ -30,12 +30,12 @@ export const GettingStarted: React.FC = () => ( ) · peer deps: react >= 18, react-dom >= 18`} + code={`pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.2/dreamknob-1.1.2.tgz\n# (or npm install ) · peer deps: react >= 18, react-dom >= 18`} />

Installs straight from the{' '} - v1.1.1 release + v1.1.2 release {' '} on our Forgejo. Zero runtime dependencies, ESM + CJS, full TypeScript types. No CSS file to import — everything is SVG and inline styles. diff --git a/apps/docs/src/sections/Hero.tsx b/apps/docs/src/sections/Hero.tsx index f71020d..f66c4b4 100644 --- a/apps/docs/src/sections/Hero.tsx +++ b/apps/docs/src/sections/Hero.tsx @@ -25,10 +25,10 @@ export const Hero: React.FC = () => { range, any decimal precision, and colors you fully control.

- pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.1/dreamknob-1.1.1.tgz + pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.2/dreamknob-1.1.2.tgz + + {/* … */} +`} />

Precision & number handling

diff --git a/apps/docs/src/sections/Buttons.tsx b/apps/docs/src/sections/Buttons.tsx index 02676f4..1cf6753 100644 --- a/apps/docs/src/sections/Buttons.tsx +++ b/apps/docs/src/sections/Buttons.tsx @@ -82,7 +82,7 @@ export const Buttons: React.FC = () => {
diff --git a/apps/docs/src/sections/Gallery.tsx b/apps/docs/src/sections/Gallery.tsx index 050b87f..aefbe68 100644 --- a/apps/docs/src/sections/Gallery.tsx +++ b/apps/docs/src/sections/Gallery.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { AlphaDisplay, Arc, + Button, DreamknobProvider, Fader, FlatKnob, @@ -18,6 +19,7 @@ import { Meter, MeterBridge, NeonKnob, + PanKnob, Pointer, PushButton, Rack, @@ -280,7 +282,10 @@ const PanelDemo: React.FC = () => { - +
+ + +
{ ); }; +const DbMeterDemo: React.FC = () => { + // Drive with linear amplitude; the meter converts to dBFS internally. + const [amp, setAmp] = React.useState<[number, number]>([0.3, 0.28]); + React.useEffect(() => { + let t = 0; + const id = setInterval(() => { + t += 0.11; + const spike = Math.random() < 0.05 ? 0.4 : 0; + setAmp([ + Math.max(0.001, Math.min(1.2, 0.34 + Math.sin(t) * 0.22 + Math.random() * 0.16 + spike)), + Math.max(0.001, Math.min(1.2, 0.32 + Math.sin(t * 1.25 + 1) * 0.22 + Math.random() * 0.16 + spike)), + ]); + }, 90); + return () => clearInterval(id); + }, []); + return ( +
+ + +
+ ); +}; + +const PanDemo: React.FC = () => ( +
+ + +
+); + const ImageKnobDemo: React.FC = () => { const src = useGeneratedStrip(); if (!src) return null; @@ -509,6 +569,10 @@ export const Gallery: React.FC = () => ( + + + + @@ -536,6 +600,10 @@ export const Gallery: React.FC = () => ( + + + + ( ) · peer deps: react >= 18, react-dom >= 18`} + code={`pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.3/dreamknob-1.1.3.tgz\n# (or npm install ) · peer deps: react >= 18, react-dom >= 18`} />

Installs straight from the{' '} - v1.1.2 release + v1.1.3 release {' '} on our Forgejo. Zero runtime dependencies, ESM + CJS, full TypeScript types. No CSS file to import — everything is SVG and inline styles. diff --git a/apps/docs/src/sections/Hero.tsx b/apps/docs/src/sections/Hero.tsx index f66c4b4..2808d9c 100644 --- a/apps/docs/src/sections/Hero.tsx +++ b/apps/docs/src/sections/Hero.tsx @@ -25,10 +25,10 @@ export const Hero: React.FC = () => { range, any decimal precision, and colors you fully control.

- pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.2/dreamknob-1.1.2.tgz + pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.3/dreamknob-1.1.3.tgz
); +const LightThemeDemo: React.FC = () => { + const [play, setPlay] = React.useState(false); + const [mute, setMute] = React.useState(true); + return ( + +
+
+ + + +
+
+ + Mute + + + setPlay(p => !p)} size={32} /> + +
+
+
+ ); +}; + const ImageKnobDemo: React.FC = () => { const src = useGeneratedStrip(); if (!src) return null; @@ -565,6 +601,10 @@ export const Gallery: React.FC = () => (
+ + + + diff --git a/apps/docs/src/sections/GettingStarted.tsx b/apps/docs/src/sections/GettingStarted.tsx index 526962c..32dbbc9 100644 --- a/apps/docs/src/sections/GettingStarted.tsx +++ b/apps/docs/src/sections/GettingStarted.tsx @@ -30,12 +30,12 @@ export const GettingStarted: React.FC = () => ( ) · peer deps: react >= 18, react-dom >= 18`} + code={`pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.4/dreamknob-1.1.4.tgz\n# (or npm install ) · peer deps: react >= 18, react-dom >= 18`} />

Installs straight from the{' '} - v1.1.3 release + v1.1.4 release {' '} on our Forgejo. Zero runtime dependencies, ESM + CJS, full TypeScript types. No CSS file to import — everything is SVG and inline styles. diff --git a/apps/docs/src/sections/Hero.tsx b/apps/docs/src/sections/Hero.tsx index 2808d9c..4669d40 100644 --- a/apps/docs/src/sections/Hero.tsx +++ b/apps/docs/src/sections/Hero.tsx @@ -25,10 +25,10 @@ export const Hero: React.FC = () => { range, any decimal precision, and colors you fully control.

- pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.3/dreamknob-1.1.3.tgz + pnpm add https://git.dreamodus.software/Dreamodus/DreamKnob/releases/download/v1.1.4/dreamknob-1.1.4.tgz