इस पेज का अनुवाद मशीन द्वारा किया गया है और इसमें त्रुटियाँ हो सकती हैं। मूल अंग्रेज़ी पढ़ें

एलिमेंट

Toolbar

टूलबार नियंत्रणों की एक पट्टी है: टेक्स्ट एडिटर की फ़ॉर्मैटिंग, मीडिया प्लेयर के ट्रांसपोर्ट नियंत्रण, या टूल्स का वर्टिकल पैलेट। Toolbar इसका कीबोर्ड और सिमैंटिक्स संभालता है: स्क्रीन रीडर टूलबार की घोषणा करते हैं (इसे accessibilityLabel से नाम मिलता है), और कीबोर्ड एक ही स्टॉप में इस तक पहुँचता है और इसके axis की ऐरो कीज़ से इसके आइटमों के बीच चलता है। इसके आइटम वे Button, Toggle और Link हैं जो इसके भीतर, किसी भी गहराई पर हों। ToolbarGroup संबंधित आइटमों का एक नामित समूह है (undo और redo, अलाइनमेंट), और ToolbarSeparator वह रेखा है जिसे ऐप समूहों के बीच खींचता है। रूप-रंग ऐप का अपना है: टूलबार के अपने कोई रंग, पैडिंग या गैप नहीं होते, और हर नियंत्रण अपनी स्थिति खुद रखता है।

el-toolbar.tsx
el-toolbar.tsxLive
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")),
        )
}

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

PropRustTypeDefaultDescription
axis.axis(..)"horizontal" | "vertical""horizontal"वह दिशा जिसमें इसके आइटम चलते हैं: उसके अनुसार चलने वाली ऐरो कीज़ (Left और Right, या Up और Down) और वह दिशा जिसमें यह अपने चाइल्ड लेआउट करता है (पंक्ति, या स्तंभ)। इसे टूलबार बनते समय पढ़ा जाता है।
disabled.disabled(..)booleanfalseटूलबार का हर आइटम disabled हो जाता है, चाहे उसका अपना disabled कुछ भी हो: कोई प्रेस नहीं, कीबोर्ड फ़ोकस का कोई स्टॉप नहीं, और स्क्रीन रीडर के लिए धुँधला (dimmed)।
accessibilityLabel.accessibility_label(..)stringकोई नहींस्क्रीन रीडर टूलबार को किस नाम से पुकारते हैं (“Formatting”), ऐसे पेज के लिए जिसमें एक से अधिक टूलबार हों।
children.child(..)JSX.Elementकोई नहींआइटम, उनके समूह और विभाजक, और बाकी सब कुछ जिसे ऐप उनके बीच लेआउट करता है (शीर्षक, स्पेसर, इनपुट)।
class, stylestyle methodsstring, StyleProp<ViewStyle>कोई नहींपट्टी का लेआउट और रूप: अलाइनमेंट, गैप, पैडिंग, इसकी सतह।

#ToolbarGroup

PropRustTypeDefaultDescription
disabled.disabled(..)booleanfalseसमूह का हर आइटम disabled हो जाता है, चाहे उसका अपना disabled कुछ भी हो।
accessibilityLabel.accessibility_label(..)stringकोई नहींस्क्रीन रीडर समूह को किस नाम से पुकारते हैं (“History”, “Alignment”)।
children.child(..)JSX.Elementकोई नहींसमूह के आइटम, और बाकी सब कुछ जिसे ऐप उनके बीच लेआउट करता है।
class, stylestyle methodsstring, StyleProp<ViewStyle>कोई नहींसमूह का लेआउट और रूप। यह टूलबार की धुरी (axis) के साथ चलता है।

#ToolbarSeparator

PropRustTypeDefaultDescription
class, stylestyle methodsstring, 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

PartInsideStyled withStates
पट्टी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 का पालन करता है:

KeyWhat 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)।
  • हर प्लेटफ़ॉर्म वही बनाता है जो ऐप स्टाइल करता है: पट्टी, उसके समूह, उसके विभाजक और उसके आइटम।