Components
Stepper
Stepper shows a number between a − and a + button. Each press changes the number by step, within min and max.
Holding a button keeps stepping. The timer for the repeat runs in the core, so JS only receives onChange with each
new value.
// Stepper: its own value, a controlled one with a fractional step, and its ref.
import { mount, signal, Text, View } from "nsh";
import { Button, Group, Stepper, type StepperRef } from "nsh/ui/mobile";
function Row(props: { label: string; children: any }) {
return (
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between" }}>
<Text>{props.label}</Text>
{props.children}
</View>
);
}
function App() {
const quantity = signal(1.5);
const asked = signal("");
let own!: StepperRef;
return (
<View style={{ flex: 1, backgroundColor: "#f5f5f5", padding: 16, paddingTop: 24, gap: 20 }}>
<Text style={{ fontSize: 22, fontWeight: "700" }}>Stepper</Text>
<Group footer={<Text>Hold − or + to keep stepping.</Text>}>
<Row label="Own value (0–10)">
<Stepper ref={own} defaultValue={3} min={0} max={10} />
</Row>
<Row label={`Controlled: ${quantity()}`}>
<Stepper value={quantity()} min={0} max={5} step={0.5} onChange={(v) => quantity.set(v)} />
</Row>
<Row label="Disabled">
<Stepper defaultValue={2} disabled />
</Row>
</Group>
<Button
variant="secondary"
title="Read the first, then set it to 7"
onPress={async () => {
const was = await own.value();
own.setValue(7);
asked.set(`It was ${was}`);
}}
/>
<Text style={{ color: "#737373" }}>{asked()}</Text>
</View>
);
}
mount(App);import { Stepper, type StepperRef } from "nsh/ui/mobile";
#Usage
import { mount, signal, Text, View } from "nsh";
import { Stepper } from "nsh/ui/mobile";
function App() {
const guests = signal(2);
return (
<View style={{ flex: 1, padding: 16, paddingTop: 48, gap: 12 }}>
<Text>{`${guests()} guests`}</Text>
<Stepper value={guests()} min={1} max={12} onChange={(v) => guests.set(v)} />
</View>
);
}
mount(App);
#Controlled and uncontrolled
- Controlled: pass
valueand update it inonChange. The stepper showsvalue. A press reports the next value withonChange, and the number changes when you set the newvalue. - Uncontrolled: leave out
value. The stepper keeps its own value, starting atdefaultValue, and reports each change withonChange.
<Stepper defaultValue={5} min={0} max={50} onChange={(v) => console.log("now", v)} />
#Range, step and decimals
- The value always stays within
minandmax. Ifminis larger thanmax, the two are swapped. A negativestepis used as positive. - The number is shown with as many decimals as
stephas:step={0.25}shows1.75,step={1}shows2. - Values are rounded to that number of decimals, which avoids floating-point drift such as
0.30000000000000004. - When
min,maxorstepchange, the value is moved back into the new range.
<Stepper value={weight()} min={0} max={5} step={0.5} onChange={(v) => weight.set(v)} />
#Holding a button
A press steps once immediately. If the button is still held after 400 ms, the stepper steps again every 80 ms until the
button is released or the value reaches min or max. At a bound, that button is dimmed and disabled.
#Ref methods
Pass a ref to get a StepperRef, which reads and sets the value from your code:
import { mount, View } from "nsh";
import { Button, Stepper, type StepperRef } from "nsh/ui/mobile";
function App() {
let stepper!: StepperRef;
return (
<View style={{ flex: 1, padding: 16, paddingTop: 48, gap: 12 }}>
<Stepper ref={stepper} defaultValue={3} max={10} />
<Button
title="Reset"
onPress={async () => {
console.log("was", await stepper.value());
stepper.setValue(0);
}}
/>
</View>
);
}
mount(App);
| Member | Description |
|---|---|
setValue(value: number): void | Sets the value, rounded to the step’s decimals and kept within the range, and reports it with onChange as a press would. On a controlled stepper, the number changes when you update value. |
value(): Promise<number> | The value shown. It is a promise because the value lives in the core, not in JS. |
node: YNode | The stepper’s host node. |
#Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | null | none (uncontrolled) | The value, when the app controls it. |
defaultValue | number | 0 | The first value, when the app does not control it. |
min | number | 0 | The smallest value. |
max | number | 100 | The largest value. |
step | number | 1 | How much a press changes the value. |
disabled | boolean | false | No presses; both buttons dimmed. |
ref | StepperRef | ((ref: StepperRef) => void) | none | Receives the ref. |
class, classList, style, testID, onLayout | Applied to the stepper’s root view (the row). |
#Events
| Event | Payload | When |
|---|---|---|
onChange | number: the new value | A press, a held button’s repeat or setValue changed the value. Not sent when the value would not change (at a bound). |
#Theming
| Part | Theme key |
|---|---|
| Buttons | secondary.background, secondary.active.background while pressed, corner radius radius |
| − and + | secondary.foreground, font.size + 4 |
| Number | foreground, font.size + 1 |
The buttons are 40 × 40 pt. A disabled button, or one at a bound, is shown at 40% opacity. See Theming.
#Accessibility
The buttons have the button role and the labels “Decrease” and “Increase”. A button at a bound, or on a disabled
stepper, is reported as disabled. The number is a text that screen readers read as it is.