एलिमेंट
Toolbar
टूलबार नियंत्रणों की एक पट्टी है: टेक्स्ट एडिटर की फ़ॉर्मैटिंग, मीडिया प्लेयर के ट्रांसपोर्ट नियंत्रण, या टूल्स का वर्टिकल पैलेट।
Toolbar इसका कीबोर्ड और सिमैंटिक्स संभालता है: स्क्रीन रीडर टूलबार की घोषणा करते हैं (इसे accessibilityLabel से नाम मिलता है), और कीबोर्ड एक ही स्टॉप में इस तक पहुँचता है और इसके axis की ऐरो कीज़ से इसके आइटमों के बीच चलता है। इसके आइटम वे Button, Toggle और Link हैं जो इसके भीतर, किसी भी गहराई पर हों। ToolbarGroup संबंधित आइटमों का एक नामित समूह है (undo और redo, अलाइनमेंट), और ToolbarSeparator वह रेखा है जिसे ऐप समूहों के बीच खींचता है। रूप-रंग ऐप का अपना है: टूलबार के अपने कोई रंग, पैडिंग या गैप नहीं होते, और हर नियंत्रण अपनी स्थिति खुद रखता है।
import { mount, signal, Text, View } from "nsh";
import { Button, Toggle, ToggleGroup, Toolbar, ToolbarGroup, ToolbarSeparator } from "nsh/base";
import { desktop, focusRing, hoverFill, Log, Split, ui } from "./palette";
type Mark = "bold" | "italic" | "underline";
type Align = "left" | "center" | "right";
type Tool = "select" | "pen" | "shape" | "text";
const marks: { id: Mark; label: string; glyph: string }[] = [
{ id: "bold", label: "Bold", glyph: "B" },
{ id: "italic", label: "Italic", glyph: "I" },
{ id: "underline", label: "Underline", glyph: "U" },
];
const aligns: { id: Align; label: string; glyph: string }[] = [
{ id: "left", label: "Align left", glyph: "⇤" },
{ id: "center", label: "Align center", glyph: "↔" },
{ id: "right", label: "Align right", glyph: "⇥" },
];
const tools: { id: Tool; label: string; glyph: string }[] = [
{ id: "select", label: "Select", glyph: "↖" },
{ id: "pen", label: "Pen", glyph: "✎" },
{ id: "shape", label: "Rectangle", glyph: "▢" },
{ id: "text", label: "Text", glyph: "T" },
];
const side = desktop ? 28 : 34;
function App() {
const on = signal<Mark[]>(["bold"]);
const align = signal<Align>("left");
const tool = signal<Tool>("select");
const history = signal<string[]>([]);
const events = signal<string[]>([]);
const log = (line: string) => events.set([line, ...events()].slice(0, 3));
const edit = (what: string) => {
history.set([...history(), what]);
log(what);
};
const has = (m: Mark) => on().includes(m);
// A tool: bare while off, raised while pressed, a hover fill, the focus ring.
const look = (pressed: boolean) => ({
style: { minWidth: side, height: side, paddingHorizontal: 6, borderRadius: 7, alignItems: "center", justifyContent: "center" } as const,
class: "transition-colors active:opacity-70 disabled:opacity-35 motion-reduce:transition-none",
pressedStyle: ui().raised,
hoverStyle: pressed ? undefined : hoverFill(),
focusStyle: focusRing(),
});
const glyph = (text: string, active: boolean) => <Text style={{ fontSize: 14, fontWeight: "600", color: active ? ui().text : ui().muted }}>{text}</Text>;
const rule = (vertical = false) => (
<ToolbarSeparator style={vertical ? { height: 1, alignSelf: "stretch", marginVertical: 4, backgroundColor: ui().line } : { width: 1, height: 16, marginHorizontal: 4, backgroundColor: ui().line }} />
);
const bar = { alignItems: "center", gap: 2, padding: 3, borderRadius: 10, ...ui().inset } as const;
return (
<View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>toolbar</Text>
<Text style={{ color: ui().muted }}>
{desktop ? "Tab into a toolbar, then move with the arrow keys; Home and End jump to its ends" : "Tap the tools; a screen reader hears each bar as a toolbar"}
</Text>
<Split gap={14} inspector={<Log title={`${history().length} edits · ${on().join(", ") || "no marks"}, ${align()} · ${tool()}`} lines={events()} minHeight={76} />}>
{/* A formatting bar: history, a toggle group of marks (a group of the toolbar), the alignment. */}
<Toolbar accessibilityLabel="Formatting" style={{ ...bar, alignSelf: "flex-start" }}>
<ToolbarGroup accessibilityLabel="History">
<Button accessibilityLabel="Undo" disabled={history().length === 0} onPress={() => {
history.set(history().slice(0, -1));
log("Undo");
}} {...look(false)}>
{glyph("↶", true)}
</Button>
</ToolbarGroup>
{rule()}
<ToggleGroup style={{ flexDirection: "row", gap: 2 }}>
<Each of={marks}>
{(m) => (
<Toggle
pressed={has(m().id)}
accessibilityLabel={m().label}
onChange={({ pressed }) => {
on.set(pressed ? [...on(), m().id] : on().filter((x) => x !== m().id));
edit(`${m().label}: ${pressed}`);
}}
{...look(has(m().id))}
>
{glyph(m().glyph, has(m().id))}
</Toggle>
)}
</Each>
</ToggleGroup>
{rule()}
<ToolbarGroup accessibilityLabel="Alignment" style={{ gap: 2 }}>
<Each of={aligns}>
{(a) => (
<Toggle
pressed={align() === a().id}
accessibilityLabel={a().label}
onChange={({ pressed }) => {
// Exactly one: the pressed alignment stays pressed.
if (pressed) align.set(a().id);
edit(`${a().label}`);
}}
{...look(align() === a().id)}
>
{glyph(a().glyph, align() === a().id)}
</Toggle>
)}
</Each>
</ToolbarGroup>
</Toolbar>
<View style={{ flexDirection: "row", gap: 14, alignItems: "stretch" }}>
{/* A vertical palette: Up and Down move along it. */}
<Toolbar axis="vertical" accessibilityLabel="Tools" style={{ ...bar, alignSelf: "flex-start" }}>
<Each of={tools}>
{(t) => (
<Toggle
pressed={tool() === t().id}
accessibilityLabel={t().label}
onChange={({ pressed }) => {
if (!pressed) return;
tool.set(t().id);
log(`Tool: ${t().label}`);
}}
{...look(tool() === t().id)}
>
{glyph(t().glyph, tool() === t().id)}
</Toggle>
)}
</Each>
{rule(true)}
<Button accessibilityLabel="Clear canvas" onPress={() => log("Clear canvas")} {...look(false)}>
{glyph("⌫", false)}
</Button>
</Toolbar>
<View style={{ flex: 1, ...ui().card, borderRadius: 14, padding: 14, gap: 6 }}>
<Text
style={{
fontSize: 15,
lineHeight: 22,
color: ui().text,
fontWeight: has("bold") ? "700" : "400",
fontStyle: has("italic") ? "italic" : "normal",
textDecorationLine: has("underline") ? "underline" : "none",
textAlign: align(),
}}
>
One stop of Tab per toolbar; its arrows move between the tools.
</Text>
<Text style={{ fontSize: 13, color: ui().muted }}>{`Tool: ${tools.find((t) => t.id === tool())!.label}`}</Text>
</View>
</View>
</Split>
</View>
);
}
mount(App);use nsh_base::{button, theme, toggle, toolbar, toolbar_group, toolbar_separator};
use nsh_core::prelude::*;
fn formatting() -> impl IntoElement {
let bold = signal(false);
toolbar()
.accessibility_label("Formatting")
.items_center()
.gap_1()
.child(
toolbar_group()
.accessibility_label("History")
.child(button().accessibility_label("Undo").size_8().child(text("↶")))
.child(button().accessibility_label("Redo").size_8().child(text("↷"))),
)
.child(toolbar_separator().w(px(1.0)).h_5().bg(move || theme().border))
.child(
toggle()
.pressed(bold)
.accessibility_label("Bold")
.on_change(move |pressed: &bool| bold.set(*pressed))
.size_8()
.items_center()
.justify_center()
.pressed_style(|s| s.bg(move || theme().accent))
.child(text("B")),
)
}import { mount, signal, Text } from "nsh";
import { Button, Toggle, Toolbar, ToolbarGroup, ToolbarSeparator } from "nsh/base";
function App() {
const bold = signal(false);
return (
<Toolbar accessibilityLabel="Formatting" class="items-center gap-1 self-start rounded-lg bg-muted p-1">
<ToolbarGroup accessibilityLabel="History">
<Button accessibilityLabel="Undo" class="size-8 items-center justify-center rounded-md hover:bg-accent">
<Text>↶</Text>
</Button>
<Button accessibilityLabel="Redo" class="size-8 items-center justify-center rounded-md hover:bg-accent">
<Text>↷</Text>
</Button>
</ToolbarGroup>
<ToolbarSeparator class="h-5 w-px bg-border" />
<Toggle
pressed={bold()}
accessibilityLabel="Bold"
onChange={({ pressed }) => bold.set(pressed)}
class="size-8 items-center justify-center rounded-md aria-pressed:bg-accent"
>
<Text>B</Text>
</Toggle>
</Toolbar>
);
}
mount(App);Toolbar, ToolbarGroup और ToolbarSeparator nsh/base से आते हैं, जो Rust में nsh-base crate है। टूलबार कोई मान नहीं रखता: इसके भीतर के toggle को बताया जाता है कि वह दबा हुआ है या नहीं और वह onChange रिपोर्ट करता है, बटन onPress रिपोर्ट करता है, और स्थिति ऐप रखता है। Rust में toolbar(), toolbar_group() और toolbar_separator() इन्हें बनाते हैं, हर prop के लिए एक setter (.axis(..), .disabled(..)), टाइप्ड स्टाइल मेथड और इनकी सामग्री के लिए .child(..) के साथ।
#Props
#Toolbar
| Prop | Rust | Type | Default | Description |
|---|---|---|---|---|
axis | .axis(..) | "horizontal" | "vertical" | "horizontal" | वह दिशा जिसमें इसके आइटम चलते हैं: उसके अनुसार चलने वाली ऐरो कीज़ (Left और Right, या Up और Down) और वह दिशा जिसमें यह अपने चाइल्ड लेआउट करता है (पंक्ति, या स्तंभ)। इसे टूलबार बनते समय पढ़ा जाता है। |
disabled | .disabled(..) | boolean | false | टूलबार का हर आइटम disabled हो जाता है, चाहे उसका अपना disabled कुछ भी हो: कोई प्रेस नहीं, कीबोर्ड फ़ोकस का कोई स्टॉप नहीं, और स्क्रीन रीडर के लिए धुँधला (dimmed)। |
accessibilityLabel | .accessibility_label(..) | string | कोई नहीं | स्क्रीन रीडर टूलबार को किस नाम से पुकारते हैं (“Formatting”), ऐसे पेज के लिए जिसमें एक से अधिक टूलबार हों। |
children | .child(..) | JSX.Element | कोई नहीं | आइटम, उनके समूह और विभाजक, और बाकी सब कुछ जिसे ऐप उनके बीच लेआउट करता है (शीर्षक, स्पेसर, इनपुट)। |
class, style | style methods | string, StyleProp<ViewStyle> | कोई नहीं | पट्टी का लेआउट और रूप: अलाइनमेंट, गैप, पैडिंग, इसकी सतह। |
#ToolbarGroup
| Prop | Rust | Type | Default | Description |
|---|---|---|---|---|
disabled | .disabled(..) | boolean | false | समूह का हर आइटम disabled हो जाता है, चाहे उसका अपना disabled कुछ भी हो। |
accessibilityLabel | .accessibility_label(..) | string | कोई नहीं | स्क्रीन रीडर समूह को किस नाम से पुकारते हैं (“History”, “Alignment”)। |
children | .child(..) | JSX.Element | कोई नहीं | समूह के आइटम, और बाकी सब कुछ जिसे ऐप उनके बीच लेआउट करता है। |
class, style | style methods | string, StyleProp<ViewStyle> | कोई नहीं | समूह का लेआउट और रूप। यह टूलबार की धुरी (axis) के साथ चलता है। |
#ToolbarSeparator
| Prop | Rust | Type | Default | Description |
|---|---|---|---|---|
class, style | style methods | string, StyleProp<ViewStyle> | कोई नहीं | रेखा: पंक्ति (h-5 w-px bg-border) या स्तंभ (h-px self-stretch bg-border) के आर-पार एक बारीक रेखा। |
#Items
टूलबार के आइटम इसके भीतर के Button, Toggle और Link हैं, चाहे वे इसके समूहों में हों या इसके भीतर के ToggleGroup में। Menu या Popover का ट्रिगर भी एक आइटम है: उसके trigger स्लॉट का व्यू, जिसे popup अपने प्रेस, अपना फ़ोकस और अपनी expanded स्थिति देता है। जो टूल ऐप खुद बनाता है, वह ऐप की अपनी क्लासेज़ वाला Button है: Button का अपना कोई रूप नहीं होता, इसलिए वह वैसा ही दिखता है जैसा ऐप उसे स्टाइल करता है, और वह एक आइटम है (onPress वाला View अपना अलग स्टॉप बना रहता है, ऐरो कीज़ से बाहर)। टूलबार के भीतर का ToggleGroup उसके समूहों में से एक है: उसके toggle टूलबार के आइटम हैं, स्क्रीन रीडर एक समूह सुनाते हैं, और उसका disabled फिर भी उसके अपने toggle को disable करता है। अन्य नियंत्रण (Input, Select, Slider) अपने अलग स्टॉप बने रहते हैं और अपनी ऐरो कीज़ रखते हैं; उन्हें पट्टी के अंत में रखें। किसी आइटम द्वारा खोला गया popup जो कुछ दिखाता है (popover के फ़ील्ड और बटन, menu के आइटम, hover card के लिंक) वह टूलबार का हिस्सा नहीं है। ऐरो कीज़ आइटमों से उसी क्रम में गुज़रती हैं जिसमें पट्टी उन्हें लेआउट करती है, चाहे हर आइटम कितनी भी गहराई पर हो, और जिस आइटम को ऐप बाद में दिखाता है वह उनके बीच अपनी जगह ले लेता है।
#Parts
| Part | Inside | Styled with | States |
|---|---|---|---|
| पट्टी | Toolbar के class, style | ||
| समूह | पट्टी | ToolbarGroup के class, style | |
| विभाजक | पट्टी, समूह | ToolbarSeparator के class, style | |
| आइटम | पट्टी, समूह | आइटम के अपने class, style | इसकी अपनी: aria-pressed:, disabled:, hover:, focus:, active: |
जिस आइटम पर कीबोर्ड है उसे focus: स्टाइल से दिखाएँ, और दबे हुए toggle को उसकी अपनी सतह से (देखें Design guides)।
#Vertical
टूल्स का पैलेट एक स्तंभ है: axis="vertical" आइटमों को ऊपर से नीचे लेआउट करता है, और Up और Down उनके साथ चलते हैं।
<Toolbar axis="vertical" accessibilityLabel="Tools" class="gap-1 self-start rounded-lg bg-muted p-1">
<Each of={["select", "pen", "shape", "text"]}>
{(t) => (
<Toggle
pressed={tool() === t()}
accessibilityLabel={t()}
onChange={({ pressed }) => pressed && tool.set(t())}
class="size-8 items-center justify-center rounded-md aria-pressed:bg-accent"
>
<Text>{t()[0].toUpperCase()}</Text>
</Toggle>
)}
</Each>
</Toolbar>
#Disabled
टूलबार पर disabled उसके हर आइटम को disable कर देता है, यानी ऐसी पट्टी जिसे ऐप लॉक करता है; समूह पर यह उस समूह के आइटमों को disable करता है। disabled आइटम कोई प्रेस नहीं लेता, ऐरो कीज़ उसे छोड़कर आगे बढ़ जाती हैं, और स्क्रीन रीडर उसे धुँधला (dimmed) बताते हैं:
<Toolbar disabled={readonly()} accessibilityLabel="Review" class="flex-row gap-1">
<Button onPress={approve} class="h-8 px-3 disabled:opacity-50">
<Text>Approve</Text>
</Button>
</Toolbar>
#Accessibility
Toolbarकी भूमिका (role)"toolbar"है, जिसेaccessibilityLabelनाम देता है; इसके आइटम अपने अलग एलिमेंट बने रहते हैं। iOS पर टूलबार एक सिमैंटिक समूह है जिसमें VoiceOver प्रवेश करता है।ToolbarGroupकी भूमिका"group"है, जिसे इसकाaccessibilityLabelनाम देता है, ताकि आइटमों की एक शृंखला एक इकाई की तरह पढ़ी जाए। टूलबार के भीतर काToggleGroupभी एक समूह है।ToolbarSeparatorस्क्रीन रीडर के लिए कोई एलिमेंट नहीं है।disabledकी घोषणा वैसे ही होती है जैसे प्लेटफ़ॉर्म उसकी घोषणा करता है (VoiceOver, TalkBack, UI Automation, ब्राउज़र का accessibility tree)।
लेबल, भूमिकाओं और घोषणाओं की सामान्य जानकारी के लिए Accessibility देखें।
#Keyboard
macOS और वेब पर, जहाँ Tab उन व्यू तक पहुँचता है जो प्रेस लेते हैं, Toolbar WAI-ARIA toolbar pattern का पालन करता है:
| Key | What it does |
|---|---|
| Tab, Shift+Tab | टूलबार में उस आइटम पर प्रवेश करता है जिसे फ़ोकस ने आख़िरी बार छोड़ा था (शुरू में, पहला आइटम जो disabled नहीं है), और एक ही चरण में इससे बाहर निकल जाता है। |
Right, Left (axis="horizontal") | फ़ोकस को अगले या पिछले आइटम पर ले जाता है, आख़िरी से पहले पर और वापस। |
Down, Up (axis="vertical") | फ़ोकस को अगले या पिछले आइटम पर ले जाता है, आख़िरी से पहले पर और वापस। |
| Home, End | फ़ोकस को पहले या आख़िरी आइटम पर ले जाता है। |
| Space, Return | फ़ोकस वाले आइटम को दबाता है। |
ऐरो कीज़ disabled आइटमों को छोड़ देती हैं और कुछ नहीं दबातीं: toggle तभी बदलता है जब उसे दबाया जाए। ऐरो की दूसरी जोड़ी आइटमों के लिए छोड़ दी जाती है (menu का ट्रिगर Down से अपना menu खोलता है)। right-to-left लेआउट में क्षैतिज टूलबार दाईं ओर से चलता है, इसलिए Left अगले आइटम पर और Right पिछले आइटम पर जाता है; वर्टिकल टूलबार में Down और Up वैसे ही रहते हैं।
#Platform notes
- iOS, Android और Mac Catalyst: हर आइटम टैप से दबाया जाता है; टूलबार एक समूह है जिसकी घोषणा स्क्रीन रीडर करते हैं।
- डेस्कटॉप (macOS, Windows, Linux, GPUI) और वेब: प्रेस एक क्लिक है। macOS और वेब पर टूलबार Tab का एक ही स्टॉप है, और ऐरो कीज़, Home और End इसके आइटमों के बीच चलते हैं (Keyboard)।
- हर प्लेटफ़ॉर्म वही बनाता है जो ऐप स्टाइल करता है: पट्टी, उसके समूह, उसके विभाजक और उसके आइटम।