एलिमेंट
Sheet
Sheet स्क्रीन के किनारे पर एक पैनल है, जो एक बैकड्रॉप के ऊपर दिखता है और बाकी स्क्रीन को ढक देता है: फ़िल्टर, शेयर पैनल, साइड ड्रॉअर। बैकड्रॉप पर प्रेस, Escape, Android का back बटन और VoiceOver का escape जेस्चर इसे बंद कर देते हैं। यह अपनी विंडो में सबसे ऊपर दिखता है, हर डिवाइस पर एक जैसा, और अपना कुछ भी नहीं बनाता: पैनल और बैकड्रॉप ऐप के होते हैं।
import { mount, signal, Text, View } from "nsh";
import { Sheet } from "nsh/base";
import { button, buttonText, desktop, Inspector, Log, Panel, size, Stage, ui, wide } from "./palette";
type Side = "bottom" | "top" | "left" | "right";
/** The panel's corners and size across, by the edge it is on. */
function panel(side: Side) {
const r = 20;
const across = side === "left" || side === "right" ? { width: wide() ? 320 : 280 } : {};
const corners = {
bottom: { borderTopLeftRadius: r, borderTopRightRadius: r, paddingBottom: 32 },
top: { borderBottomLeftRadius: r, borderBottomRightRadius: r, paddingTop: 32 },
left: { borderTopRightRadius: r, borderBottomRightRadius: r },
right: { borderTopLeftRadius: r, borderBottomLeftRadius: r },
}[side];
return { ...ui().card, padding: 20, gap: 12, ...across, ...corners };
}
function App() {
const open = signal(false);
const side = signal<Side>("bottom");
const events = signal<string[]>([]);
const log = (e: string) => events.set([e, ...events()].slice(0, 6));
const show = (s: Side) => {
side.set(s);
open.set(true);
log(`open · side ${s}`);
};
/** A button that opens the sheet at side `s`. */
const opener = (s: Side) => (
<View onPress={() => show(s)} {...button("secondary", { on: () => s === side() })}>
<Text style={buttonText("secondary", { on: () => s === side() })}>side="{s}"</Text>
</View>
);
const changes = () => <Log title="onOpenChange({ open, reason }) · onCancel" lines={events()} minHeight={130} />;
return (
<View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>sheet</Text>
<Text style={{ color: ui().muted }}>
{desktop ? "Open a side; click the backdrop or press Escape to close it" : "Open a side; tap the backdrop or Android's back to close it"}
</Text>
{() =>
wide() ? (
<View style={{ flex: 1, flexDirection: "row", gap: 16 }}>
{/* Each button at the edge its sheet comes from. */}
<Stage width={360} gap={12}>
<View style={{ alignItems: "center" }}>{opener("top")}</View>
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between" }}>
{opener("left")}
{opener("right")}
</View>
<View style={{ alignItems: "center" }}>{opener("bottom")}</View>
</Stage>
<Inspector>
<Panel title="open · side">
<Text style={{ fontSize: 13, color: ui().text }}>
{open() ? "open" : "closed"} · {side()}
</Text>
</Panel>
{changes()}
</Inspector>
</View>
) : (
<>
<View style={{ flex: 1, minHeight: 200, alignItems: "center", justifyContent: "center" }}>
<View style={{ width: 240, gap: 8 }}>
<Each of={["top", "left", "right", "bottom"] as Side[]}>{(s) => opener(s())}</Each>
</View>
</View>
{changes()}
</>
)
}
<Sheet
open={open()}
closeOnSwipe
side={side()}
onOpenChange={({ open: next, reason }) => {
open.set(next);
log(`onOpenChange · ${next ? "opened" : "closed"} · ${reason}`);
}}
onCancel={() => log("onCancel")}
popupStyle={panel(side())}
backdropStyle={{ backgroundColor: "rgba(0,0,0,0.45)" }}
>
<Text style={{ fontSize: 17, fontWeight: "700", color: ui().text }}>Filters</Text>
<Text style={{ fontSize: 14, color: ui().muted }}>
A panel at the {side()} edge. {desktop ? "The backdrop and Escape close it." : "The backdrop and Android's back close it."}
</Text>
<View
onPress={() => {
open.set(false);
log("Done pressed");
}}
{...button("primary", { align: "flex-end" })}
>
<Text style={buttonText("primary")}>Done</Text>
</View>
</Sheet>
</View>
);
}
mount(App);use nsh_base::{SheetEvent, sheet};
use nsh_core::prelude::*;
fn filters(open: Signal<bool>) -> impl IntoElement {
sheet()
.open(move || Some(open.get()))
.on_open_change(move |e: &SheetEvent| {
if let SheetEvent::OpenChange(next, _) = e {
open.set(*next);
}
})
.child(
view()
.gap_3()
.p_6()
.rounded_t(px(16.0))
.bg(white())
.child(text("Filters").font_weight(FontWeight::SEMIBOLD))
.child(view().on_press(move || open.set(false)).child(text("Done"))),
)
}import { signal, Text, View } from "nsh";
import { Sheet } from "nsh/base";
function Filters() {
const open = signal(false);
return (
<>
<View onPress={() => open.set(true)}><Text>Filters</Text></View>
<Sheet
open={open()}
onOpenChange={(e) => open.set(e.open)}
popupClass="gap-3 rounded-t-2xl bg-white p-6"
backdropClass="bg-black/40"
>
<Text class="font-semibold">Filters</Text>
<View onPress={() => open.set(false)}><Text>Done</Text></View>
</Sheet>
</>
);
}Sheet nsh/base से आता है। इसका कंटेंट हर बार खुलने पर बनाया जाता है। side इसे स्क्रीन के "bottom" (डिफ़ॉल्ट) या "top" पर रखता है, जहाँ यह स्क्रीन जितना चौड़ा होता है, या "left" या "right" पर, जहाँ यह स्क्रीन जितना ऊँचा होता है; आड़े में इसका आकार पैनल का अपना होता है, जो उसके कंटेंट या popupClass से तय होता है।
इसकी स्लाइड पैनल के मापे गए आकार का अनुसरण करती है, भले ही एंट्रेंस के दौरान कंटेंट बने या बढ़े। Lazy कंटेंट और एसिंक्रोनस आकार अपडेट एक ही एनिमेशन प्रोग्रेस साझा करते हैं; पैनल अपने मौजूदा आकार पर आकर ठहरता है।
#खुली स्थिति
open के बिना, sheet अपनी स्थिति खुद रखती है, जो defaultOpen से शुरू होती है। open के साथ, स्थिति ऐप के पास होती है: इसे onOpenChange({ open, reason }) में बदलें, जो बंद करने के हर अनुरोध को उसके कारण ("backdropPress", "cancel", "swipe") के साथ बताता है। onOpen और onClose इसके खुलने और बंद होने के साथ चलते हैं, चाहे इसे किसी ने भी खोला या बंद किया हो।
closeOnEscape={false} इसे Escape और back पर खुला रखता है, closeOnBackdropPress={false} बैकड्रॉप पर प्रेस के लिए यही करता है; onCancel फिर भी इन्हें सुनता है, ताकि पहले पूछ सके।
#Bottom sheet को खींचना
closeOnSwipe सेट करें ताकि नीचे का पैनल नीचे की ओर खींचने पर साथ चले। दूरी के साथ इसका बैकड्रॉप फीका पड़ता है; पैनल की ऊँचाई के 30% से आगे छोड़ने पर, या 800 पॉइंट प्रति सेकंड की गति से नीचे की ओर छोड़ने पर, कारण "swipe" के साथ बंद करने का अनुरोध होता है। इससे छोटा ड्रैग पैनल को सहजता से वापस लौटा देता है। नियंत्रित (controlled) मालिक बंद करने के अनुरोध के बाद पैनल को वापस लाने के लिए open को true रख सकता है। Reduced motion में, छोड़ना और प्रोग्रामैटिक रूप से खोलना/बंद करना तुरंत पूरा हो जाता है; सीधा ड्रैग फिर भी उंगली का अनुसरण करता है।
पैनल अपने कंटेंट के अनुसार बैठता है, छोटे मेनू समेत; ज़्यादा ऊँचे कंटेंट को सीमित करने के लिए popupStyle या किसी child की ऊँचाई का उपयोग करें। हैंडल ऐप खुद बनाता है, जो पैनल के अंदर कोई भी view हो सकता है। iOS पर, भीतरी वर्टिकल Scroll या List अपने शीर्ष तक ड्रैग को खुद लेता है और उसी जेस्चर के भीतर बची हुई नीचे की गति पैनल को सौंप देता है। Android, डेस्कटॉप और वेब पर, स्क्रॉल के बाहर हैंडल या कंटेंट को खींचना उनके नेटिव pan सपोर्ट का उपयोग करता है; भीतरी स्क्रॉल अपने प्लेटफ़ॉर्म का सामान्य जेस्चर निर्धारण बनाए रखते हैं। closeOnSwipe को false सेट करने पर पैनल स्थिर रहता है और माउंट किया हुआ कंटेंट तथा स्क्रॉल पोज़िशन बनी रहती है। अन्य किनारे अपना मौजूदा व्यवहार बनाए रखते हैं।
#Native sheets
Sheet को NSH ऐप की विंडो में खुद बनाता है, और उसकी स्टाइल ऐप तय करता है। प्लेटफ़ॉर्म की अपनी प्रस्तुत की गई sheet (iOS पर page sheet) के लिए, presentation="pageSheet" के साथ Dialog का उपयोग करें।
#Props
| Prop | Rust | Type | Default | विवरण |
|---|---|---|---|---|
open | .open(..) | boolean | none | खुला है, ऐप द्वारा नियंत्रित। |
defaultOpen | .default_open(..) | boolean | false | शुरू में खुला, open के बिना। |
side | .side(..) | "top" | "bottom" | "left" | "right" | "bottom" | वह किनारा जहाँ यह है। |
closeOnEscape | .close_on_escape(..) | boolean | true | Escape और Android का back बटन इसे बंद करते हैं। |
closeOnBackdropPress | .close_on_backdrop_press(..) | boolean | true | बैकड्रॉप पर प्रेस इसे बंद करता है। |
closeOnSwipe | .close_on_swipe(..) | boolean | false | नीचे का पैनल नीचे की ओर ड्रैग का अनुसरण करता है और पर्याप्त रिलीज़ पर बंद होता है। |
popupClass, popupStyle | .popup_class(..), .popup_style(..) | string, StyleProp<ViewStyle> | none | पैनल का रूप। |
backdropClass, backdropStyle | .backdrop_class(..), .backdrop_style(..) | string, StyleProp<ViewStyle> | none | बैकड्रॉप का रूप, जैसे पारभासी रंग। |
children | .child(..) | JSX.Element | none | कंटेंट। |
#Events
| Event | Rust | विवरण |
|---|---|---|
onOpenChange | .on_open_change(|e| ..) | बंद करने का अनुरोध, कारण के साथ: "backdropPress", "cancel" या "swipe"। |
onOpen | .on_open(|| ..) | यह आ गई: पूरी तरह मौजूद है। |
onClose | .on_close(|| ..) | यह चली गई: पूरी तरह गायब है। ऐप द्वारा बंद किए जाने पर भी। |
onCancel | .on_cancel(|| ..) | उपयोगकर्ता ने इसे बंद करने की कोशिश की (Escape या back, बैकड्रॉप पर प्रेस, सक्षम स्वाइप), चाहे यह बंद हुई हो या नहीं। |
वेब पर, dialogs: "viewport" के साथ एम्बेड करने पर sheet और उसका बैकड्रॉप होस्ट पेज के ऊपर दिखते हैं, same-origin iframe समेत। Cross-origin फ़्रेम hostPlayerFrame का उपयोग करते हैं।