Number Box
A value shown as text that you drag up and down, or type into, like the tempo field of a DAW.
Anatomy
import { NumberBox } from '@addstack/daw-ui/react';
<NumberBox.Root>
<NumberBox.Label />
<NumberBox.Field />
</NumberBox.Root>Interaction
| Input | Effect |
|---|---|
| Drag up / down | Changes the value: two pixels per step by default. Shift is 10 times finer. |
| Double-click, Enter | Edits the value as text. |
Typing a digit, ., ,, + or - | Starts editing with that character, as in Ableton Live. |
| Enter, leaving the field | Applies the typed value, parsed by format. Text that does not parse keeps the value. |
| Escape | Discards the typed value. |
| Arrow keys, Page Up / Page Down, Home / End, Delete | As on the knob. |
Typed values go through the format's parse: formats.frequency() reads "1k" as 1000, formats.decibel() reads "-inf". Either decimal separator works, whatever the locale.
Segments
With NumberBox.Segments in place of Field, the value shows as fields that change one at a time, as the tempo, position and timecode fields of a DAW do. The fields come from the format: the whole part and decimals of formats.number(), the bars, beats and sixteenths of formats.position(), the hours to frames of formats.timecode().
<NumberBox.Root min={0} max={999 * 4} format={formats.position()}>
<NumberBox.Label>Position</NumberBox.Label>
<NumberBox.Segments labels={{ bars: 'Bar', beats: 'Beat', divisions: 'Sixteenth' }} />
</NumberBox.Root>Every field is a view of the one value, so stepping the beats past 4 moves to the next bar. The group is labelled by Label, and each field is a spinbutton named by labels, which come from your app in its language.
| Input on a field | Effect |
|---|---|
| Drag up / down | One step of the field per 4 px (Shift: per 40 px). |
| ↑ / ↓, wheel | One step of the field. |
| ← / →, Tab | The previous or next field. |
| Enter, double-click, typing a digit | Edits the whole value as text: 12 goes to bar 12, 1500 to 15 seconds of timecode. |
| Page Up / Page Down, Home / End, Delete | On the whole value, as on the field. |
To style fields and the text between them apart, render each segment yourself. The function runs when the format changes, not when the value does; the fields write their text without rendering.
<NumberBox.Segments labels={labels}>
{(segment) => <NumberBox.Segment segment={segment} className="focus:bg-orange-500 data-literal:opacity-50" />}
</NumberBox.Segments>Your own format can have segments too: see values and formats.
Ends
An endless number box has no minimum or maximum, for values such as a transposition or an offset: it drags two pixels per step and ignores Home and End. With wrap, the value comes around past max, as on the knob.
API reference
Root
Holds the value. Renders a <div>.
Prop
Type
| Data attribute | |
|---|---|
data-dragging | While the value is being dragged. |
data-editing | While the value is edited as text. |
data-disabled | When disabled. |
data-zone | The zone of zones the value is in, e.g. zones={{ fast: 160 }} for a tempo field. |
Field
The value: a <span> with role="spinbutton", replaced by an <input> while editing. Both have dir="auto" and the root's data attributes.
Prop
Type
Segments
The value as fields: a <div> with role="group" and dir="ltr", replaced by an <input> while editing, with the root's data attributes. The format must have segments.
Prop
Type
Segment
One segment: a field is a <span> with role="spinbutton", data-segment set to the field's name (bars, frames) and the root's data attributes; the text between fields is a <span> with data-literal and aria-hidden.
Prop
Type
Label
Names the field; clicking it focuses the field. Renders a <span>.
Prop
Type