Components
Group
Group shows rows in a rounded box with a hairline between them, like a section of a settings screen. Each JSX child
becomes one row. You can add a short caption above the box and an explanation below it.
// Group: settings-style rows; each child becomes a row with a divider.
import { mount, signal, Text, View } from "nsh";
import { Group, Stepper } from "nsh/ui/mobile";
function Row(props: { label: string; value: string; onPress?: () => void }) {
return (
<View onPress={props.onPress} style={{ flexDirection: "row", justifyContent: "space-between" }}>
<Text>{props.label}</Text>
<Text style={{ color: "#737373" }}>{props.value}</Text>
</View>
);
}
function App() {
const notifications = signal(true);
const size = signal(16);
return (
<View style={{ flex: 1, backgroundColor: "#f5f5f5", padding: 16, paddingTop: 24, gap: 24 }}>
<Group header={<Text>ACCOUNT</Text>}>
<Row label="Name" value="Ada Lovelace" />
<Row label="E-mail" value="ada@example.com" />
</Group>
<Group
header={<Text>PREFERENCES</Text>}
footer={<Text>Tap the first row to toggle it. Rows are the app's JSX; the group adds the box and the dividers.</Text>}
>
<Row
label="Notifications"
value={notifications() ? "On" : "Off"}
onPress={() => notifications.set(!notifications())}
/>
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between" }}>
<Text>{`Text size ${size()}`}</Text>
<Stepper value={size()} min={12} max={24} onChange={(v) => size.set(v)} />
</View>
</Group>
</View>
);
}
mount(App);import { Group } from "nsh/ui/mobile";
#Usage
import { mount, signal, Text, View } from "nsh";
import { Group } from "nsh/ui/mobile";
function App() {
const sounds = signal(true);
return (
<View style={{ flex: 1, backgroundColor: "#f5f5f5", padding: 16, paddingTop: 48 }}>
<Group
header={<Text>NOTIFICATIONS</Text>}
footer={<Text>Sounds play for new messages only.</Text>}
>
<Text>Show previews</Text>
<View
onPress={() => sounds.set(!sounds())}
style={{ flexDirection: "row", justifyContent: "space-between" }}
>
<Text>Sounds</Text>
<Text>{sounds() ? "On" : "Off"}</Text>
</View>
</Group>
</View>
);
}
mount(App);
#Rows
Group wraps each child in a row with 16 pt horizontal and 12 pt vertical padding and a minimum height of 44 pt, the smallest comfortable touch target. The content is centered vertically. Every row except the first has a 1 pt divider at its top.
A row is whatever you pass, so put a <View> with flexDirection: "row" in it to place a label and a value, and an
onPress on that view to make the row pressable. When a child is added or removed, for example with
<If> or <Each>, the group updates the rows and dividers.
<Group header={<Text>DEVICES</Text>}>
<Each of={devices()}>
{(device) => (
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
<Text>{device().name}</Text>
<Text style={{ color: "#737373" }}>{device().status}</Text>
</View>
)}
</Each>
</Group>
#Header and footer
The header is placed 16 pt in from the left, in small (text-sm) muted text. The footer is in extra-small
(text-xs) muted text. There is an 8 pt gap between the header, the box and the footer. Both are optional.
#Props
| Prop | Type | Default | Description |
|---|---|---|---|
header | JSX.Element | none | Above the box: a short caption. |
footer | JSX.Element | none | Below the box: an explanation. |
children | JSX.Element | none | The rows, one per child. |
class, classList, style, testID, onLayout, ref | Applied to the group’s root view (the column holding the header, box and footer). |
Group has no events.
#Theming
| Part | Theme key |
|---|---|
| Box | group_box.background, corner radius radius.lg |
| Row text color | group_box.foreground |
| Dividers | border |
| Header and footer text | muted.foreground |
See Theming.
#Accessibility
Group adds no roles or labels. Each row’s content is read as you wrote it. If a row is pressable, give its view an
accessibilityRole (for example "button") and, if needed, an accessibilityLabel. See
Accessibility.