एलिमेंट
Select
Select सूची में से एक मान चुनता है: कोई सेटिंग, फ़िल्टर, देश, टाइम ज़ोन। ऐप का ट्रिगर सूची खोलता है, जिसे हर डिवाइस की अपेक्षा के अनुसार दिखाया जाता है:
import { type JSX, mount, signal, Text, View } from "nsh";
import { Button, type OpenChangeReason, Select, type SelectEntry, type SelectGroup, Switch } from "nsh/base";
import { button, buttonText, CheckMark, desktop, focusRing, hoverFill, Icon, Log, Panel, size, Split, ui } from "./palette";
// Two selects in a settings card: a plain one (typeahead jumps by letter)
// and a searchable one with sections, a disabled item and an empty state.
// The trigger is the shared button look (hover, pressed, focus ring,
// disabled) with the chosen title and a chevron that turns while the list
// is open; the list is a popup at the trigger with a pointer, a sheet on a
// touch screen. The highlighted row (the keys or the pointer) is lit with
// the accent tint, the chosen one is bolder with a check in its own lane.
const LANGUAGES = [
{ value: "en", title: "English" },
{ value: "de", title: "Deutsch" },
{ value: "es", title: "Español" },
{ value: "fr", title: "Français" },
{ value: "it", title: "Italiano" },
{ value: "nl", title: "Nederlands" },
{ value: "pl", title: "Polski" },
{ value: "pt", title: "Português" },
{ value: "sv", title: "Svenska" },
{ value: "tr", title: "Türkçe" },
{ value: "ja", title: "日本語" },
{ value: "ko", title: "한국어" },
{ value: "zh", title: "中文" },
];
const REGIONS = [
{ value: "us-east-1", title: "US East (Virginia)", group: "Americas" },
{ value: "us-west-2", title: "US West (Oregon)", group: "Americas" },
{ value: "ca-central-1", title: "Canada (Montréal)", group: "Americas" },
{ value: "sa-east-1", title: "South America (São Paulo)", group: "Americas" },
{ value: "eu-west-1", title: "Europe (Ireland)", group: "Europe" },
{ value: "eu-central-1", title: "Europe (Frankfurt)", group: "Europe" },
{ value: "eu-north-1", title: "Europe (Stockholm)", group: "Europe" },
{ value: "ap-northeast-1", title: "Asia Pacific (Tokyo)", group: "Asia Pacific" },
{ value: "ap-northeast-2", title: "Asia Pacific (Seoul)", group: "Asia Pacific", disabled: true },
{ value: "ap-southeast-1", title: "Asia Pacific (Singapore)", group: "Asia Pacific" },
{ value: "ap-east-1", title: "Asia Pacific (Hong Kong)", group: "Asia Pacific" },
{ value: "ap-southeast-2", title: "Asia Pacific (Sydney)", group: "Asia Pacific" },
];
const titleOf = (items: { value: string; title: string }[], value: string | null) => items.find((i) => i.value === value)?.title;
/** The text of a control: 13 with a pointer, the touch label size otherwise. */
const controlText = desktop ? 13 : size.label;
/** A popup at the trigger with a pointer, a sheet at the bottom on a touch screen. */
const surface = () =>
desktop
? { ...ui().card, borderRadius: 12, padding: 4 }
: { ...ui().card, borderTopLeftRadius: 20, borderTopRightRadius: 20, paddingHorizontal: 10, paddingTop: 10, paddingBottom: 30, maxHeight: "70%" as const };
const backdrop = { backgroundColor: "rgba(0,0,0,0.45)" };
/** The trigger: the shared button's look, the chosen title at the start and a chevron in a lane as wide as it. */
function Trigger(props: { title?: string; placeholder: string; open: boolean; disabled?: boolean; clearable?: boolean }) {
const look = button("secondary", {
style: () => ({
flexDirection: "row",
justifyContent: "space-between",
gap: 8,
...(desktop ? { width: 260 } : {}),
// Open: the border stays lit while the list is up; disabled, dimmed.
...(props.open ? { borderColor: ui().faint } : {}),
...(props.disabled ? { opacity: 0.4 } : {}),
}),
});
return (
// No hover while disabled.
<View {...look} hoverStyle={props.disabled ? undefined : look.hoverStyle}>
<Text style={{ numberOfLines: 1, flex: 1, fontSize: controlText, color: props.title ? ui().text : ui().muted }}>
{props.title ?? props.placeholder}
</Text>
{/* With a clear button over the end (`cleanable`), an empty lane as wide as the button in place of the chevron. */}
<View
class="transition-transform duration-150 motion-reduce:transition-none"
style={{
width: props.clearable ? 20 : 10,
height: 20,
alignItems: "center",
justifyContent: "center",
opacity: props.clearable ? 0 : 1,
transform: [{ rotate: props.open ? "270deg" : "90deg" }],
}}
>
<Text style={{ fontSize: 18, color: ui().muted }}>›</Text>
</View>
</View>
);
}
/**
* A row's look, on the row itself (`itemClass`): the accent tint while
* highlighted (the keys or the pointer, as the menus' rows), dimmed while
* disabled. The states are the row's classes (`group`), so the list draws a
* row's content once per item and the highlight moves without drawing.
*/
const rowClass =
"group rounded-lg border border-transparent transition-colors duration-100 motion-reduce:transition-none " +
"focus:border-brand/30 focus:bg-brand-tint active:opacity-70 disabled:opacity-40";
/**
* A row's content: the title (bolder while chosen), a quieter detail at the
* end, the check in its own lane (`group-selected:`).
*/
function Row(props: { item: SelectEntry; detail?: string }) {
return (
// The row's 1-point border on each side.
<View style={{ height: size.rowSm - 2, flexDirection: "row", alignItems: "center", gap: 8, paddingHorizontal: 7 }}>
<Text class="font-normal group-selected:font-semibold" style={{ numberOfLines: 1, flex: 1, fontSize: controlText, color: ui().text }}>
{props.item.title}
</Text>
<If when={props.detail}>
<Text style={{ fontSize: 12, color: ui().faint }}>{props.detail}</Text>
</If>
<View class="opacity-0 group-selected:opacity-100" style={{ width: 16 }}>
<CheckMark size={16} tint={ui().accent} />
</View>
</View>
);
}
function Header(props: { group: SelectGroup }) {
return (
<Text style={{ fontSize: 12, fontWeight: "500", color: ui().muted, paddingHorizontal: 9, paddingTop: 8, paddingBottom: 4 }}>
{props.group.title}
</Text>
);
}
/** A label over its control, a quieter line under the label. */
function Field(props: { label: string; detail: string; children: JSX.Element }) {
return (
<View style={{ gap: 8 }}>
<View style={{ gap: 2 }}>
<Text style={{ fontSize: size.label, fontWeight: "500", color: ui().text }}>{props.label}</Text>
<Text style={{ fontSize: size.detail, color: ui().muted }}>{props.detail}</Text>
</View>
{props.children}
</View>
);
}
function App() {
const language = signal<string | null>("en");
const region = signal<string | null>(null);
const languageOpen = signal(false);
const regionOpen = signal(false);
const locked = signal(false);
const events = signal<string[]>([]);
const log = (line: string) => events.set([line, ...events()].slice(0, 8));
const opened = (name: string, open: (next: boolean) => void) => ({ open: next, reason }: { open: boolean; reason: OpenChangeReason }) => {
open(next);
log(`${name} ${next ? "opened" : "closed"} · ${reason}`);
};
return (
<View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>select</Text>
<Text style={{ color: ui().muted }}>
{desktop ? "Click a select, or Tab to it and press Down; type a letter to jump" : "Tap a select to choose from a sheet"}
</Text>
<Split
gap={14}
inspector={
<>
<Panel title="value">
<Text style={{ fontSize: 13, color: ui().text, fontVariant: ["tabular-nums"] }}>{`language ${language() ?? "null"}`}</Text>
<Text style={{ fontSize: 13, color: ui().text, fontVariant: ["tabular-nums"] }}>{`region ${region() ?? "null"}`}</Text>
<Button
disabled={region() === null}
onPress={() => region.set(null)}
{...button("secondary", { size: "sm", style: { marginTop: 6 } })}
>
<Text style={buttonText("secondary", { size: "sm" })}>Clear region</Text>
</Button>
<Switch
checked={locked()}
onChange={({ checked }) => locked.set(checked)}
class={`-mx-2 mt-1 flex-row-reverse items-center justify-between gap-3 rounded-lg px-2 ${desktop ? "py-1" : "py-2"} active:opacity-70`}
hoverStyle={hoverFill()}
focusStyle={focusRing()}
trackClass={`${desktop ? "w-9" : "w-12"} rounded-full p-0.5 transition-colors motion-reduce:transition-none`}
trackStyle={locked() ? ui().accentFill : ui().inset}
thumbClass={desktop ? "size-4 rounded-full transition-transform motion-reduce:transition-none checked:translate-x-4" : "size-5.5 rounded-full transition-transform motion-reduce:transition-none checked:translate-x-5"}
thumbStyle={locked() ? { backgroundColor: ui().onAccent } : ui().raised}
>
<Text style={{ flex: 1, fontSize: size.label, color: ui().text }}>Region disabled</Text>
</Switch>
</Panel>
<Log title="onChange · onOpenChange" lines={events()} minHeight={120} />
</>
}
>
<View style={{ ...ui().card, borderRadius: 16, padding: 16, gap: 20 }}>
<Field label="Language" detail={desktop ? "Typing a letter on the trigger chooses by title" : "Thirteen languages in a sheet"}>
<Select
items={LANGUAGES}
value={language()}
onChange={({ value: next }) => {
language.set(next);
log(`Language → ${titleOf(LANGUAGES, next)}`);
}}
onOpenChange={opened("Language", (next) => languageOpen.set(next))}
accessibilityLabel="Language"
offset={4}
margin={8}
popupStyle={surface()}
backdropStyle={backdrop}
itemClass={rowClass}
trigger={<Trigger title={titleOf(LANGUAGES, language())} placeholder="Choose a language" open={languageOpen()} />}
renderItem={(item) => <Row item={item} />}
/>
</Field>
<Field label="Region" detail="Search by name; one region is at capacity">
<Select
items={REGIONS}
value={region()}
onChange={({ value: next }) => {
region.set(next);
log(next === null ? "Region cleared" : `Region → ${titleOf(REGIONS, next)}`);
}}
onOpenChange={opened("Region", (next) => regionOpen.set(next))}
disabled={locked()}
accessibilityLabel="Region"
searchable
searchPlaceholder="Search regions"
searchAccessibilityLabel="Search regions"
searchClass={`${desktop ? "text-sm" : "text-base"} placeholder:text-[${ui().faint}] text-[${ui().text}]`}
searchStyle={{ ...ui().inset, borderRadius: 8, height: size.control, paddingHorizontal: 8, marginBottom: 4 }}
empty={<Text style={{ paddingVertical: 20, textAlign: "center", fontSize: 13, color: ui().muted }}>No region matches</Text>}
offset={4}
margin={8}
// Wider than the trigger on the desktops: the titles and their codes side by side.
popupStyle={desktop ? { ...surface(), width: 340 } : surface()}
backdropStyle={backdrop}
itemClass={rowClass}
// As wide as the trigger, so the clear button lands over its end.
style={desktop ? { width: 260 } : undefined}
cleanable
clearButtonAccessibilityLabel="Clear the region"
clearButtonClass="absolute items-center justify-center rounded-full enabled:hover:opacity-70"
clearButtonStyle={{ top: (size.control - 20) / 2, right: 10, width: 20, height: 20 }}
clearButton={<Icon name="close" size={14} tint={ui().muted} />}
trigger={<Trigger title={titleOf(REGIONS, region())} placeholder="Choose a region" open={regionOpen()} disabled={locked()} clearable={region() !== null} />}
renderItem={(item) => <Row item={item} detail={item.disabled ? "At capacity" : item.value} />}
renderGroup={(group) => <Header group={group} />}
/>
</Field>
</View>
</Split>
</View>
);
}
mount(App);- डेस्कटॉप और डेस्कटॉप ब्राउज़र पर, ट्रिगर के ऊपर एक पॉपअप खुलता है, जिसमें चुनी हुई पंक्ति ट्रिगर के ऊपर ही रखी जाती है, ताकि चुना हुआ शीर्षक अपनी जगह पर बना रहे (shadcn/ui की
item-alignedस्थिति)। पॉपअप ट्रिगर जितना चौड़ा होता है (ऐप कीpopupStyle/popupClassचौड़ाई कोई और चौड़ाई तय कर सकती है, जैसेgpui-componentकीmenu_width) और अपनी पंक्तियों जितना ऊँचा होता है, स्क्रीन पर उपलब्ध जगह तक। कुछ भी चुना न हो तो यह ट्रिगर के नीचे खुलता है। कीबोर्ड आइटमों के बीच चलता है और किसी एक को चुनता है। - फ़ोन और टैबलेट पर, स्क्रीन के नीचे बैकड्रॉप के ऊपर एक शीट खुलती है।
Select व्यवहार को संभालता है: मान (ऐप का अपना, या Select का अपना), खुली/बंद स्थिति, कीबोर्ड, टाइपअहेड, खोज, सेक्शन, हाइलाइट की गई पंक्ति और स्क्रीन रीडर क्या बोलते हैं। रूप-रंग ऐप का होता है: ऐप ट्रिगर, पॉपअप, सूची, पंक्तियों, सेक्शन के हेडर और खोज फ़ील्ड को स्टाइल करता है, या पंक्तियाँ खुद बनाता है।
use nsh_base::{SelectEntry, SelectItem, select, theme};
use nsh_core::prelude::*;
const FRUITS: [(&str, &str); 5] = [
("apple", "Apple"),
("banana", "Banana"),
("blueberry", "Blueberry"),
("grapes", "Grapes"),
("pineapple", "Pineapple"),
];
fn fruit_select() -> impl IntoElement {
let fruit = signal(None::<SharedString>);
let title = move || {
let chosen = fruit.get();
FRUITS
.iter()
.find(|(value, _)| Some(*value) == chosen.as_deref())
.map_or("Pick a fruit", |(_, title)| *title)
.to_string()
};
select()
.items(
FRUITS
.iter()
.map(|&(value, title)| SelectItem::new(value, title).disabled(value == "grapes"))
.collect::<Vec<_>>(),
)
.value(move || fruit.get())
.on_change(move |next: &Option<SharedString>| fruit.set(next.clone()))
.accessibility_label("Fruit")
.offset(px(4.0))
.popup_style(|s| {
s.p_1()
.rounded_lg()
.border_1()
.border_color(move || theme().border)
.bg(move || theme().popover)
.text_color(move || theme().popover_foreground)
.shadow_md()
})
.item_class("rounded-md focus:bg-accent selected:font-medium disabled:opacity-50")
.trigger(
view()
.h_9()
.w_48()
.flex_row()
.items_center()
.justify_between()
.px_3()
.rounded_md()
.border_1()
.border_color(move || theme().input)
.hover(|s| s.bg(move || theme().accent))
.child(text(title))
.child(text("▾").text_color(move || theme().muted_foreground)),
)
.render_item(|item: &SelectEntry| {
view()
.flex_row()
.items_center()
.px_2()
.py_1p5()
.child(text(item.title.clone()).text_sm())
})
}import { mount, signal, Text, View } from "nsh";
import { Select } from "nsh/base";
const FRUITS = [
{ value: "apple", title: "Apple" },
{ value: "banana", title: "Banana" },
{ value: "blueberry", title: "Blueberry" },
{ value: "grapes", title: "Grapes", disabled: true },
{ value: "pineapple", title: "Pineapple" },
];
function App() {
const fruit = signal<string | null>(null);
const title = () => FRUITS.find((f) => f.value === fruit())?.title;
return (
<Select
items={FRUITS}
value={fruit()}
onChange={({ value }) => fruit.set(value)}
accessibilityLabel="Fruit"
offset={4}
popupClass="rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-md"
backdropClass="bg-black/40"
itemClass="rounded-md px-2 py-1.5 text-sm focus:bg-accent focus:text-accent-foreground disabled:opacity-50"
trigger={
<View class="h-9 w-48 flex-row items-center justify-between rounded-md border border-input px-3 hover:bg-accent active:bg-accent focus-visible:ring-2 focus-visible:ring-ring">
<Text class={fruit() ? "text-foreground" : "text-muted-foreground"}>{title() ?? "Pick a fruit"}</Text>
<Text class="text-muted-foreground">▾</Text>
</View>
}
/>
);
}
mount(App);Select Rust के nsh-base क्रेट nsh/base से आता है। Rust में select() हर प्रॉप के लिए एक सेटर (.items(..), .value(..), .item_style(..)) और हर इवेंट के लिए एक लिसनर (.on_change(..)) के साथ एक Select बनाता है; SelectItem::new(value, title) .disabled(..) और .group(..) के साथ एक आइटम बनाता है।
#आइटम
items डेटा के रूप में सूची है: हर आइटम में एक value होता है (आइटमों में अद्वितीय: उसे चुनने पर यही सेट होता है), एक title (उसकी पंक्ति में जो दिखता है, जिससे खोज और टाइपअहेड मिलान करते हैं और जो स्क्रीन रीडर बोलते हैं), और वैकल्पिक रूप से disabled और group। एक ही group वाले आसन्न आइटम उस शीर्षक वाले हेडर के नीचे एक सेक्शन बनाते हैं:
<Select
items={[
{ value: "lisbon", title: "Lisbon", group: "Europe" },
{ value: "london", title: "London", group: "Europe" },
{ value: "tokyo", title: "Tokyo", group: "Asia" },
{ value: "seoul", title: "Seoul", group: "Asia", disabled: true },
]}
groupClass="px-2 pb-1 pt-2 text-xs text-muted-foreground"
trigger={trigger}
/>
पंक्तियाँ पॉपअप के स्क्रॉल व्यू में एक वर्चुअलाइज़्ड सूची होती हैं: हज़ारों आइटमों की सूची में भी केवल दिख रही पंक्तियाँ बनती हैं, और जब पंक्तियाँ उपलब्ध जगह से ऊँची हों तो पॉपअप स्क्रॉल होता है।
#मान
value के साथ, चुना हुआ आइटम ऐप के पास रहता है: Select उस मान का आइटम दिखाता है और चुनाव की सूचना onChange({ value }) से देता है; ऐप उसका अनुसरण करने के लिए value सेट करता है, या अपना मान रखता है। value={null} का अर्थ है कुछ भी नहीं चुना गया। इसे न दिया जाए तो Select अपना मान खुद रखता है, शुरुआत में defaultValue से। onChange बदलावों की सूचना देता है: चुने हुए आइटम को दोबारा चुनने पर सूची बंद हो जाती है।
cleanable आइटम चुने होने पर एक क्लियर बटन जोड़ता है: दबाने पर मान साफ़ हो जाता है, जिसकी सूचना onChange({ value: null }) से मिलती है, और सूची बंद रहती है। बटन Select के व्यू में ट्रिगर के बाद आता है, उसके अंदर नहीं, इसलिए उसे दबाने से सूची कभी नहीं खुलती; उसे ऐप रखता है (absolute के साथ ट्रिगर के सिरे पर, जहाँ शेवरॉन होता) और भरता है:
<Select
items={fonts}
value={font()}
onChange={({ value }) => font.set(value)}
cleanable
clearButtonAccessibilityLabel="Clear the font"
clearButtonClass="absolute right-2 top-2 size-5 items-center justify-center rounded-full hover:bg-accent"
clearButton={<Text class="text-xs text-muted-foreground">✕</Text>}
trigger={trigger}
/>
ट्रिगर ऐप का एलिमेंट है, इसलिए चुने हुए आइटम के लिए वह जो चाहे दिखा सकता है: उसका शीर्षक, एक आइकन, या कुछ न चुना हो तब प्लेसहोल्डर। TSX में यह वही एलिमेंट है जो आप पास करते हैं: वही कॉम्बोबॉक्स है और प्रेस, कीबोर्ड फ़ोकस, विस्तारित स्थिति और disabled खुद लेता है, इसलिए उसके अपने active:, focus: और disabled: क्लासेस Select का अनुसरण करते हैं, उसका अपना onPress भी चलता है, और सूची उसी के सापेक्ष रखी जाती है और उसी जितनी चौड़ी होती है।
#खुली स्थिति
open के बिना, Select अपनी खुली स्थिति खुद रखता है (defaultOpen से)। open के साथ, ऐप उसे रखता है और onOpenChange({ open, reason }) में बदलता है: "triggerPress" (ट्रिगर का प्रेस, या उसकी कोई कुंजी), "select" (एक चुनाव), "pressOutside", "backdropPress" या "cancel" (Escape या बैक)। onOpen और onClose बताते हैं कि सूची कब दिखाई गई और कब हटी (शीट के लिए: आ जाने के बाद और चली जाने के बाद), onCancel बताता है कि उपयोगकर्ता ने बिना चुने सूची बंद कर दी।
#खोज
searchable आइटमों के ऊपर एक खोज फ़ील्ड लगाता है: टाइप करने पर आइटम शीर्षक के आधार पर, अक्षरों के केस की अनदेखी करते हुए, फ़िल्टर होते हैं, और जिस सेक्शन में कोई मिलान न हो वह अपने हेडर के साथ हट जाता है। हाइलाइट पहले मिलान पर चला जाता है, फ़ील्ड के पास कीबोर्ड रहते हुए भी कुंजियाँ उसे चलाती रहती हैं, और Return उसे चुनता है। empty वह है जो सूची तब दिखाती है जब कुछ भी मेल नहीं खाता:
<Select
items={zones}
searchable
searchPlaceholder="Search time zones"
searchAccessibilityLabel="Search time zones"
searchClass="mx-1 mb-1 rounded-md border border-input px-2 py-1.5 text-sm"
empty={<Text class="px-2 py-6 text-center text-sm text-muted-foreground">No time zone found</Text>}
trigger={trigger}
/>
डेस्कटॉप पर सूची खुलते ही खोज फ़ील्ड कीबोर्ड ले लेता है; टच स्क्रीन पर फ़ील्ड दबाने पर कीबोर्ड ऊपर आता है। हर बार खुलने पर खोज खाली से शुरू होती है।
#ऐप द्वारा बनाई गई पंक्तियाँ
itemClass हर आइटम की पंक्ति को स्टाइल करता है, जो आइटम का शीर्षक तब तक दिखाती है जब तक renderItem उसे न बनाए: focus: हाइलाइट की गई पंक्ति पर लागू होता है (जहाँ कुंजियाँ या पॉइंटर हैं), selected: चुनी हुई पर, और disabled: अक्षम पर। पंक्ति बनाने के लिए renderItem दें: उसे मिलता है कि आइटम क्या है (SelectEntry) और वह पंक्ति की सामग्री लौटाता है, दिख रही पंक्तियों के लिए। उसे पंक्ति की स्थिति कभी नहीं मिलती: itemClass में group के साथ, सामग्री पंक्ति की स्थितियों का अनुसरण group-focus:, group-selected: और group-disabled: क्लासेस से करती है, इसलिए ड्रॉइंग हर आइटम के लिए एक बार होती है और हाइलाइट का हिलना या कोई चुनाव दोबारा कुछ नहीं बनाता। renderGroup सेक्शन का हेडर उसी तरह बनाता है:
<Select
items={fonts}
itemClass="group rounded-md focus:bg-accent disabled:opacity-50"
renderItem={(item) => (
<View class="flex-row items-center gap-2 px-2 py-1.5">
<Text class="flex-1 text-sm" style={{ fontFamily: item.value }}>{item.title}</Text>
<Text class="w-4 text-right text-sm opacity-0 group-selected:opacity-100">✓</Text>
</View>
)}
renderGroup={(group) => <Text class="px-2 pt-2 text-xs font-medium text-muted-foreground">{group.title}</Text>}
trigger={trigger}
/>
#Props
| प्रॉप | Rust | प्रकार | डिफ़ॉल्ट | विवरण |
|---|---|---|---|---|
items | .items(..) | SelectItem[] | [] | आइटम, क्रम में। |
value | .value(..) | string | null | कोई नहीं | चुने हुए आइटम का value, जो ऐप के पास रहता है; कोई न हो तो null। न दिया जाए तो Select अपना रखता है। |
defaultValue | .default_value(..) | string | कोई नहीं | शुरुआत में मान, value के बिना। |
open | .open(..) | boolean | कोई नहीं | खुली स्थिति, ऐप के पास। |
defaultOpen | .default_open(..) | boolean | false | शुरुआत में खुला, open के बिना। |
searchable | .searchable(..) | boolean | false | आइटमों के ऊपर एक खोज फ़ील्ड उन्हें शीर्षक के आधार पर फ़िल्टर करता है। |
searchPlaceholder | .search_placeholder(..) | string | कोई नहीं | खोज फ़ील्ड का प्लेसहोल्डर। |
searchAccessibilityLabel | .search_accessibility_label(..) | string | कोई नहीं | स्क्रीन रीडर खोज फ़ील्ड को क्या कहते हैं: वह क्या खोजता है। |
cleanable | .cleanable(..) | boolean | false | आइटम चुने होने पर एक क्लियर बटन; दबाने से मान साफ़ हो जाता है (onChange({ value: null }))। |
disabled | .disabled(..) | boolean | false | यह बंद रहता है: ट्रिगर न प्रेस लेता है और न कीबोर्ड फ़ोकस, और उस पर disabled: स्टाइल लागू होते हैं। |
accessibilityLabel | .accessibility_label(..) | string | कोई नहीं | स्क्रीन रीडर इसे क्या कहते हैं: चुनाव किस बारे में है। |
placement | .placement(..) | "top" | "bottom" | "left" | "right" | "bottom" | डेस्कटॉप: ट्रिगर का वह किनारा जिस ओर सूची जाती है; यदि केवल दूसरी ओर जगह हो तो दूसरी ओर। |
align | .align(..) | "start" | "center" | "end" | "start" | डेस्कटॉप: यह ट्रिगर के साथ कैसे संरेखित होती है। |
offset | .offset(..) | number | 0 | डेस्कटॉप: ट्रिगर और सूची के बीच का अंतर। |
margin | .margin(..) | number | 0 | डेस्कटॉप: सेफ़ एरिया के किनारों से यह कितनी दूरी रखती है। |
popupClass, popupStyle | .popup_class(..), .popup_style(..) | string, StyleProp<ViewStyle> | कोई नहीं | वह पॉपअप (डेस्कटॉप) या शीट (टच स्क्रीन) जिसमें सूची होती है। |
backdropClass, backdropStyle | .backdrop_class(..), .backdrop_style(..) | string, StyleProp<ViewStyle> | कोई नहीं | टच स्क्रीन: शीट के पीछे का बैकड्रॉप। |
listClass, listStyle | .list_class(..), .list_style(..) | string, StyleProp<ViewStyle> | कोई नहीं | सूची, यानी पंक्तियों का स्क्रॉल व्यू। |
itemClass, itemStyle | .item_class(..), .item_style(..) | string, StyleProp<TextStyle> | कोई नहीं | हर आइटम की पंक्ति, जिसके टेक्स्ट स्टाइल उसका शीर्षक विरासत में लेता है: focus: हाइलाइट होने पर, selected: चुने होने पर, disabled: जब वह चुना न जा सके। |
groupClass, groupStyle | .group_class(..), .group_style(..) | string, StyleProp<ViewStyle> | कोई नहीं | हर सेक्शन का हेडर। |
searchClass, searchStyle | .search_class(..), .search_style(..) | string, StyleProp<InputStyle> | कोई नहीं | खोज फ़ील्ड: उसका बॉक्स और उसका टेक्स्ट (color, fontSize, placeholderColor)। |
clearButtonClass, clearButtonStyle | .clear_button_class(..), .clear_button_style(..) | string, StyleProp<ViewStyle> | कोई नहीं | क्लियर बटन (cleanable); Select के साथ disabled:। |
clearButton | .clear_button(..) | JSX.Element | कोई नहीं | क्लियर बटन की सामग्री: एक क्रॉस, एक आइकन। |
clearButtonAccessibilityLabel | .clear_button_accessibility_label(..) | string | कोई नहीं | स्क्रीन रीडर क्लियर बटन को क्या कहते हैं। |
trigger | .trigger(..) | JSX.Element | कोई नहीं | जो दबाने पर सूची खोलता है; सूची उसी के सापेक्ष रखी जाती है। |
empty | .empty(..) | JSX.Element | कोई नहीं | जब कोई आइटम नहीं दिखता (मिलान रहित खोज) तब सूची क्या दिखाती है। |
renderItem | .render_item(..) | (item: SelectEntry) => JSX.Element | कोई नहीं | किसी आइटम की पंक्ति, जिसे ऐप इस आधार पर बनाता है कि आइटम क्या है (उसकी स्थितियाँ पंक्ति के क्लासेस हैं); अन्यथा आइटम का शीर्षक। |
renderGroup | .render_group(..) | (group: SelectGroup) => JSX.Element | कोई नहीं | किसी सेक्शन का हेडर, जिसे ऐप बनाता है; अन्यथा उसका शीर्षक। |
class, style | स्टाइल मेथड | string, StyleProp<ViewStyle> | कोई नहीं | ट्रिगर के चारों ओर का व्यू। |
#इवेंट
| इवेंट | Rust | विवरण |
|---|---|---|
onChange | .on_change(|value| ..) | उपयोगकर्ता ने कोई दूसरा आइटम चुना (प्रेस, Return या Space, ट्रिगर पर टाइपअहेड): उसका value; क्लियर बटन से साफ़ होने पर null। उसका अनुसरण करने के लिए value सेट करें। |
onOpenChange | .on_open_change(|e| ..) | सूची खुली या बंद हुई, कारण के साथ। उसका अनुसरण करने के लिए open सेट करें। |
onOpen | .on_open(|| ..) | सूची दिखाई गई; शीट के लिए, उसके आ जाने के बाद। |
onClose | .on_close(|| ..) | सूची हट गई; शीट के लिए, उसके चले जाने के बाद। ऐप द्वारा बंद करने पर भी। |
onCancel | .on_cancel(|| ..) | उपयोगकर्ता ने बिना चुने सूची बंद की (Escape या बैक, बाहर या बैकड्रॉप पर प्रेस)। |
ये इस क्रम में आते हैं: चुनाव के लिए onChange, या बिना चुनाव के बंद करने के लिए onCancel, फिर onOpenChange, फिर सूची के दिखाए जाने या हटने पर onOpen या onClose।
#प्रकार
| प्रकार | सदस्य | विवरण |
|---|---|---|
SelectItem | value: string, title: string, disabled?: boolean, group?: string | items का एक आइटम। |
SelectEntry | value, title, disabled, group? (आइटम का), index: number | renderItem को क्या मिलता है: आइटम और items में उसका इंडेक्स, उसकी स्थिति कभी नहीं (पंक्ति के focus:, selected: और disabled:)। |
SelectGroup | title: string | renderGroup को क्या मिलता है: सेक्शन का group। |
Rust में ये nsh_base::SelectItem, SelectEntry और SelectGroup हैं; value एक SelectValue है (मान के लिए Controlled(Some(..)), null के लिए Controlled(None)), जिसे .value(..) किसी Option<SharedString> या स्ट्रिंग से बनाता है।
#पार्ट्स
| पार्ट | कहाँ | स्टाइल कैसे करें |
|---|---|---|
| ट्रिगर | Select का व्यू | ऐप का अपना एलिमेंट (trigger) |
| क्लियर बटन | Select का व्यू, ट्रिगर के बाद (cleanable, आइटम चुने होने पर) | clearButtonClass, clearButtonStyle; उसकी सामग्री clearButton |
| पॉपअप या शीट | सबके ऊपर, ट्रिगर पर (डेस्कटॉप) या नीचे (टच स्क्रीन) | popupClass, popupStyle |
| बैकड्रॉप | शीट के पीछे (टच स्क्रीन) | backdropClass, backdropStyle |
| खोज फ़ील्ड | पॉपअप, सबसे पहले (searchable) | searchClass, searchStyle, searchPlaceholder, searchAccessibilityLabel |
| सूची | पॉपअप, खोज फ़ील्ड के बाद | listClass, listStyle |
| सेक्शन का हेडर | सूची, अपने आइटमों से पहले | groupClass, groupStyle, renderGroup |
| आइटम की पंक्ति | सूची | itemClass, itemStyle, renderItem (group-focus:, group-selected:, group-disabled: अंदर, itemClass में group के अंतर्गत) |
| खाली स्थिति | पॉपअप, जब कोई आइटम न दिखे | उसका अपना एलिमेंट (empty) |
पॉपअप एक कॉलम है: उसकी ऊँचाई खोज फ़ील्ड और पंक्तियों के अनुसार चलती है, इसलिए popupClass पर max-h-* उसे सीमित कर देता है और सूची उसके भीतर स्क्रॉल होती है।
#कीबोर्ड
डेस्कटॉप पर, Tab ऐसे Select के ट्रिगर तक पहुँचता है जो disabled नहीं है। सूची बंद होने पर, Down, Up, Return और Space उसे चुने हुए आइटम पर खोलते हैं (कुछ भी चुना न हो तो Up आख़िरी सक्षम आइटम पर और बाकी पहले पर), और अक्षर व अंक उस आइटम को चुनते हैं जिसका शीर्षक टाइप किए गए से शुरू होता है, जैसा नेटिव select करता है। सूची खुली होने पर:
| कुंजी | क्रिया |
|---|---|
| Down, Up | अगला या पिछला सक्षम आइटम, सिरों के आर-पार घूमते हुए। |
| Home, End | पहला या आख़िरी सक्षम आइटम (जब कीबोर्ड खोज फ़ील्ड के पास हो तब फ़ील्ड का अपना)। |
| Page Down, Page Up | एक पेज जितनी पंक्तियाँ आगे या पीछे। |
| Return | हाइलाइट किया हुआ आइटम चुनता है और सूची बंद करता है। |
| Space | हाइलाइट किया हुआ आइटम चुनता है (खोज फ़ील्ड के बिना)। |
| अक्षर, अंक | अगला आइटम जिसका शीर्षक पिछले एक सेकंड में टाइप किए गए से शुरू होता है (खोज फ़ील्ड के बिना)। |
| Escape | बिना चुने सूची बंद करता है। |
सूची बंद होने पर कीबोर्ड फ़ोकस ट्रिगर पर लौट आता है, जब तक कि बाहर का कोई प्रेस उसे कहीं और न ले गया हो। पॉइंटर जिस पंक्ति पर चलता है उसे हाइलाइट करता है; कुंजियों के उस तक पहुँचने पर हाइलाइट की गई पंक्ति दिखाई दे जाती है।
#हर डिवाइस पर
| डेस्कटॉप और डेस्कटॉप ब्राउज़र | फ़ोन और टैबलेट | |
|---|---|---|
| सूची | ट्रिगर के ऊपर एक पॉपअप, जिसमें चुनी हुई पंक्ति उसी पर होती है (कोई न चुनी हो तो उसके नीचे), ट्रिगर जितना चौड़ा जब तक popupStyle कोई चौड़ाई तय न करे, अपनी पंक्तियों जितना ऊँचा, स्क्रीन पर उपलब्ध जगह तक | नीचे बैकड्रॉप के ऊपर एक शीट, अपनी पंक्तियों जितनी ऊँची, स्टेटस बार तक |
| बंद करना | एक चुनाव, Escape, बाहर का प्रेस (जो पॉइंटर के नीचे जो है उस तक जाता है) | एक चुनाव, बैकड्रॉप, Android का बैक बटन |
| खोज | सूची खुलते ही फ़ील्ड कीबोर्ड ले लेता है | फ़ील्ड दबाने पर कीबोर्ड ऊपर आता है |
| चुनाव | नीचे जाता प्रेस ही चुन लेता है, इसलिए खोज फ़ील्ड के पास कीबोर्ड बना रहता है | एक टैप |
popupClass डेस्कटॉप पर पॉपअप को और फ़ोन पर शीट को स्टाइल करता है; जो स्टाइल दोनों में अलग हो, वह platform.idiom (nsh/device) की तुलना "desktop" से कर सकता है।
#Accessibility
- स्क्रीन रीडर के लिए ट्रिगर एक combobox है: इसका लेबल
accessibilityLabelहै, इसका मान चुने गए आइटम का शीर्षक है, और यह बताता है कि सूची विस्तारित (expanded) है या नहीं।disabledइसे धुंधला कर देता है। - क्लियर बटन एक button है जिसका नाम
clearButtonAccessibilityLabelसे तय होता है; यह Tab का पड़ाव नहीं है। - सर्च फ़ील्ड एक text field है जिसका नाम
searchAccessibilityLabelसे तय होता है; इसका प्लेसहोल्डर एक संकेत है जो फ़ील्ड के खाली होने पर दिखता है। - सूची एक listbox है और हर पंक्ति एक option है, जिसका नाम उसका शीर्षक है; चुना गया option selected होता है और अक्षम (disabled) option धुंधला दिखता है। सेक्शनों के हेडर headers होते हैं।
- जैसे-जैसे कुंजियाँ हाइलाइट को आगे बढ़ाती हैं, स्क्रीन रीडर का फ़ोकस हाइलाइट की गई पंक्ति पर चला जाता है; जब सर्च फ़ील्ड के पास कीबोर्ड होता है, तब इसके बजाय हाइलाइट किए गए आइटम का शीर्षक बोलकर बताया जाता है, ताकि टाइपिंग जारी रहे।
सामान्य रूप से लेबल, भूमिकाओं (roles) और घोषणाओं के लिए Accessibility देखें।