daw-ui

Number Box

A value shown as text that you drag up and down, or type into, like the tempo field of a DAW.

Tempo120.00 BPM

Anatomy

import { NumberBox } from '@addstack/daw-ui/react';

<NumberBox.Root>
  <NumberBox.Label />
  <NumberBox.Field />
</NumberBox.Root>

Interaction

InputEffect
Drag up / downChanges the value: two pixels per step by default. Shift is 10 times finer.
Double-click, EnterEdits the value as text.
Typing a digit, ., ,, + or -Starts editing with that character, as in Ableton Live.
Enter, leaving the fieldApplies the typed value, parsed by format. Text that does not parse keeps the value.
EscapeDiscards the typed value.
Arrow keys, Page Up / Page Down, Home / End, DeleteAs 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().

Tempo
12000
Position
111
Timecode
00000000
<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 fieldEffect
Drag up / downOne step of the field per 4 px (Shift: per 40 px).
↑ / ↓, wheelOne step of the field.
← / →, TabThe previous or next field.
Enter, double-click, typing a digitEdits the whole value as text: 12 goes to bar 12, 1500 to 15 seconds of timecode.
Page Up / Page Down, Home / End, DeleteOn 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-draggingWhile the value is being dragged.
data-editingWhile the value is edited as text.
data-disabledWhen disabled.
data-zoneThe 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

On this page