العناصر
AlertDialog
يعرض AlertDialog التنبيه أو ورقة الإجراءات الأصلية الخاصة بالمنصة: UIAlertController على iOS وMac Catalyst، وAlertDialog (أو مربع حوار بقائمة لورقة الإجراءات) على Android، وتنبيهًا بمظهر مربعات الحوار الخاص بالمنصة على المنصات الأخرى. تعيد AlertDialog.show (.show() الخاصة بـ alert_dialog() في Rust) وعدًا (future في Rust) يكتمل بالقيمة { action }: أي id الإجراء الذي اختاره المستخدم.
تأتي كل التسميات من التطبيق، لذلك يقول التنبيه ما يقوله تطبيقك، وبلغته.
import { mount, signal, Text, View } from "nsh";
import { AlertDialog, Button } from "nsh/base";
import { button, buttonText, desktop, focusRing, hoverFill, Inspector, Panel, size, Stage, ui, wide } from "./palette";
type Options = Parameters<typeof AlertDialog.show>[0];
// What each button shows, and what it is about.
const alerts: { label: string; detail: string; danger?: boolean; options: Options }[] = [
{
label: "Delete…",
detail: "cancel + destructive roles",
danger: true,
options: {
title: "Delete the photo?",
description: "This cannot be undone.",
actions: [
{ label: "Cancel", role: "cancel", id: "cancel" },
{ label: "Delete", role: "destructive", id: "delete" },
],
},
},
{
label: "Save changes…",
detail: "three actions, closeOnEscape",
options: {
title: "Save changes?",
description: "Your edits to “Trip notes” are not saved yet.",
actions: [
{ label: "Don't save", role: "destructive", id: "discard" },
{ label: "Cancel", role: "cancel", id: "cancel" },
{ label: "Save", id: "save" },
],
closeOnEscape: true,
},
},
{
label: "Share…",
detail: 'presentation "actionSheet"',
options: {
title: "Share the photo",
presentation: "actionSheet",
actions: [
{ label: "Copy link", id: "copy" },
{ label: "Save", id: "save" },
{ label: "Delete", role: "destructive", id: "delete" },
{ label: "Cancel", role: "cancel", id: "cancel" },
],
},
},
];
function App() {
const answer = signal("–");
const shown = signal(0);
const show = async (options: Options) => {
shown.update((n) => n + 1);
const { action } = await AlertDialog.show(options);
answer.set(action === null ? "{ action: null } (closed without an action)" : `{ action: ${JSON.stringify(action)} }`);
};
const row = () => ({ ...ui().row, borderRadius: 12, padding: 14, gap: 2 });
const hint = desktop ? "Escape or a click outside the action sheet closes it with { action: null }." : "A tap outside the action sheet closes it with { action: null }.";
return (
<View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 12 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>alert-dialog</Text>
<Text style={{ color: ui().muted }}>The platform's own alert; every label comes from the app.</Text>
{() =>
wide() ? (
<View style={{ flex: 1, flexDirection: "row", gap: 16 }}>
{/* A row per alert: what it shows, and a button as wide as its label at the end. */}
<Stage width={400}>
<View style={{ ...ui().card, borderRadius: 14, overflow: "hidden" }}>
<Each of={alerts}>
{(a, i) => (
<>
{i() > 0 ? <View style={{ height: 1, marginLeft: 12, backgroundColor: ui().line }} /> : null}
<View style={{ flexDirection: "row", alignItems: "center", gap: 12, height: size.row + 4, paddingHorizontal: 12 }}>
<Text style={{ flex: 1, fontSize: 13, color: ui().muted }}>{a().detail}</Text>
<Button onPress={() => show(a().options)} {...button()}>
<Text style={{ ...buttonText(), color: a().danger ? ui().down : ui().text }}>{a().label}</Text>
</Button>
</View>
</>
)}
</Each>
</View>
</Stage>
<Inspector>
<Panel title={`AlertDialog.show resolved with (${shown()} shown)`}>
<Text style={{ color: ui().accent, fontSize: 14, fontWeight: "600" }}>{answer()}</Text>
</Panel>
<Text style={{ color: ui().faint, fontSize: 12 }}>{hint}</Text>
</Inspector>
</View>
) : (
<View style={{ gap: 10 }}>
<Each of={alerts}>
{(a) => (
<Button onPress={() => show(a().options)} style={row()} activeStyle={{ opacity: 0.7 }} hoverStyle={hoverFill()} focusStyle={focusRing()}>
<Text style={{ color: a().danger ? ui().down : ui().text, fontSize: 16, fontWeight: "600" }}>{a().label}</Text>
<Text style={{ color: ui().muted, fontSize: 13 }}>{a().detail}</Text>
</Button>
)}
</Each>
<View style={{ ...ui().inset, borderRadius: 12, padding: 14, gap: 4, marginTop: 4 }}>
<Text style={{ color: ui().muted, fontSize: 13 }}>AlertDialog.show resolved with ({shown()} shown)</Text>
<Text style={{ color: ui().accent, fontSize: 16, fontWeight: "600" }}>{answer()}</Text>
</View>
<Text style={{ color: ui().faint, fontSize: 13 }}>{hint}</Text>
</View>
)
}
</View>
);
}
mount(App);use nsh_base::{alert_dialog, button};
use nsh_core::prelude::*;
fn app() -> impl IntoElement {
let answer = signal(SharedString::from("–"));
let confirm = move || {
spawn(async move {
let choice = alert_dialog()
.title("Delete the photo?")
.description("This cannot be undone.")
.cancel("cancel", "Cancel")
.destructive("delete", "Delete")
.show()
.await;
let deleted = matches!(choice, Ok(r) if r.action.as_deref() == Some("delete"));
answer.set(if deleted { "deleted" } else { "kept" }.into());
});
};
view()
.flex_1()
.p_4()
.pt(px(48.0))
.gap_3()
.child(
button()
.on_press(confirm)
.rounded_lg()
.bg(rgb(0xdc2626))
.px_4()
.py_3()
.child(text("Delete…").text_color(white()).font_weight(FontWeight::SEMIBOLD)),
)
.child(text(answer))
}import { mount, signal, Text, View } from "nsh";
import { AlertDialog, Button } from "nsh/base";
function App() {
const answer = signal("–");
const confirm = async () => {
const { action } = await AlertDialog.show({
title: "Delete the photo?",
description: "This cannot be undone.",
actions: [
{ label: "Cancel", role: "cancel", id: "cancel" },
{ label: "Delete", role: "destructive", id: "delete" },
],
});
answer.set(action === "delete" ? "deleted" : "kept");
};
return (
<View style={{ flex: 1, padding: 16, paddingTop: 48, gap: 12 }}>
<Button onPress={confirm} class="rounded-lg bg-red-600 px-4 py-3 active:bg-red-700">
<Text class="text-center font-semibold text-white">Delete…</Text>
</Button>
<Text>{answer()}</Text>
</View>
);
}
mount(App);للحوارات ذات المظهر الخاص بك (بطاقة، أو ورقة، أو مسار بملء الشاشة)، استخدم Dialog.
#AlertDialog.show
AlertDialog.show(options: AlertDialogOptions): Promise<AlertDialogResult>
alert_dialog().title(..).description(..).action(..).cancel(..).destructive(..).show()
-> impl Future<Output = Result<AlertDialogResult, String>>
النتيجة هي { action } (AlertDialogResult): أي id الإجراء المختار، أو null (None) عند إغلاق الحوار
دون اختيار إجراء: بالضغط خارجه، أو بزر Escape، أو بزر الرجوع، حيثما يسمح بذلك
closeOnBackdropPress وcloseOnEscape. وبدون إجراءات يُرفض الوعد بالخطأ TypeError (وهو Err
في Rust). في Rust، يُعدّ nsh_base::alert_dialog() الحوار بطريقة (method) لكل خيار ولكل إجراء، بالترتيب
الذي تظهر به الإجراءات، ويعرضه .show()؛ وانتظر إجابته داخل مهمة (spawn).
#Action sheets
تقبل ورقة الإجراءات الخيارات نفسها مع نمط العرض "actionSheet":
use nsh_base::alert_dialog;
use nsh_core::prelude::*;
fn share() {
spawn(async move {
let choice = alert_dialog()
.title("Share the photo")
.action_sheet()
.action("copy", "Copy link")
.action("save", "Save")
.destructive("delete", "Delete")
.cancel("cancel", "Cancel")
.show()
.await;
match choice.ok().and_then(|r| r.action).as_deref() {
Some("copy") => { /* … */ }
Some("save") => { /* … */ }
Some("delete") => { /* … */ }
_ => {} // "cancel", or closed without an action.
}
});
}const { action } = await AlertDialog.show({
title: "Share the photo",
presentation: "actionSheet",
actions: [
{ label: "Copy link", id: "copy" },
{ label: "Save", id: "save" },
{ label: "Delete", role: "destructive", id: "delete" },
{ label: "Cancel", role: "cancel", id: "cancel" },
],
});#AlertDialogOptions
| الحقل | Rust | النوع | الافتراضي | الوصف |
|---|---|---|---|---|
title | .title(..) | string | null | لا يوجد | يظهر فوق الوصف. |
description | .description(..) | string | null | لا يوجد | يظهر تحت العنوان. |
actions | .action(..)، .cancel(..)، .destructive(..)، واحدة لكل إجراء | AlertDialogAction[] | مطلوب | الإجراءات بالترتيب (إجراء واحد على الأقل؛ وثلاثة كحد أقصى في تنبيه Android). |
presentation | .action_sheet() | "alert" | "actionSheet" | "alert" | تنبيه في المنتصف، أو ورقة إجراءات من الأسفل (مربع حوار بقائمة على Android). |
closeOnEscape | .close_on_escape(..) | boolean | null | false للتنبيه، true لورقة الإجراءات | يغلقه زر Escape وزر الرجوع في Android دون اختيار إجراء. |
closeOnBackdropPress | .close_on_backdrop_press(..) | boolean | null | false للتنبيه، true لورقة الإجراءات | يغلقه الضغط خارجه دون اختيار إجراء. |
#AlertDialogResult
| الحقل | Rust | النوع | الوصف |
|---|---|---|---|
action | action | string | null (Option<SharedString>) | id الإجراء المختار، أو null عند إغلاق الحوار دون اختيار إجراء. |
#AlertDialogAction
| الحقل | Rust | النوع | الافتراضي | الوصف |
|---|---|---|---|---|
id | الوسيط الأول للطريقة | string | مطلوب | ما يعيده show في action عند اختياره: اسم التطبيق لهذا الإجراء، ويبقى كما هو أيًّا كانت لغة التسميات. |
label | الوسيط الثاني للطريقة | string | مطلوب | النص الذي يعرضه. |
role | .action(..)، .cancel(..)، .destructive(..) | "default" | "cancel" | "destructive" | "default" | معناه: تحدد المنصة موضعه وشكله حسب دوره (الإجراء المدمّر يظهر باللون الأحمر على iOS). |
#ملاحظات المنصات
- iOS: يتولى
UIAlertControllerترتيب الإجراءات. يوضع الإجراء ذو الدور"cancel"حيث يضع iOS إجراءات الإلغاء، وتعرضه ورقة الإجراءات منفصلًا في أسفلها. على iPad وMac Catalyst تكون ورقة الإجراءات نافذة منبثقة (popover) مع إجراء إلغاء بلا عنوان: يغلقها الضغط خارجها، وتكون قيمةactionهيnull. - Android: يعرض
AlertDialogحتى ثلاثة إجراءات، تُربط حسب الموضع: الإجراء الأخير هو الزر الإيجابي، والذي قبله هو الزر السلبي، والذي قبله هو الزر المحايد. وورقة الإجراءات هي قائمة بالإجراءات داخل حوار، ويحل الضغط خارجها أو زر الرجوع محل إجراء الإلغاء (وتكون قيمةactionعندئذٍnull). يعرض حوار القائمةtitle، أوdescriptionعندما لا يكون للورقة عنوان. - Windows وLinux وGPUI والويب: يرسم NSH التنبيه أو ورقة الإجراءات بمظهر مربعات الحوار الخاص بالمنصة.