تُرجمت هذه الصفحة آليًا وقد تحتوي على أخطاء. اقرأ الأصل الإنجليزي

العناصر

AlertDialog

يعرض AlertDialog التنبيه أو ورقة الإجراءات الأصلية الخاصة بالمنصة: UIAlertController على iOS وMac Catalyst، وAlertDialog (أو مربع حوار بقائمة لورقة الإجراءات) على Android، وتنبيهًا بمظهر مربعات الحوار الخاص بالمنصة على المنصات الأخرى. تعيد AlertDialog.show (.show() الخاصة بـ alert_dialog() في Rust) وعدًا (future في Rust) يكتمل بالقيمة { action }: أي id الإجراء الذي اختاره المستخدم. تأتي كل التسميات من التطبيق، لذلك يقول التنبيه ما يقوله تطبيقك، وبلغته.

el-alert-dialog.tsx
el-alert-dialog.tsxLive
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))
}

للحوارات ذات المظهر الخاص بك (بطاقة، أو ورقة، أو مسار بملء الشاشة)، استخدم 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.
        }
    });
}

#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 | nullfalse للتنبيه، true لورقة الإجراءاتيغلقه زر Escape وزر الرجوع في Android دون اختيار إجراء.
closeOnBackdropPress.close_on_backdrop_press(..)boolean | nullfalse للتنبيه، true لورقة الإجراءاتيغلقه الضغط خارجه دون اختيار إجراء.

#AlertDialogResult

الحقلRustالنوعالوصف
actionactionstring | 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 التنبيه أو ورقة الإجراءات بمظهر مربعات الحوار الخاص بالمنصة.