التخطّي إلى المحتوى
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
البدءWebReactSwiftUIJetpack ComposeFlutterReact Nativeالإعدادات الجاهزةنظام التصميم الخاص بكاستخدام Tanrei مع وكلاء الذكاء الاصطناعيالعلامة التجاريةرموز التصميمأنظمة التصميم والتخصيصTheme Studioاللونالطباعةالأيقوناتالشكلالارتفاعالحركةحالات التفاعلالسلوكالتخطيطإمكانية الوصولالكلماترموز الألوانرموز السلّم الطباعيرموز الشكلرموز الارتفاعرموز الحركةرموز الحالةرموز التباعد والقياسالمكوّناتالزرزر الإجراء الرئيسيالزر المقسَّممجموعة الأزرارزر مع قائمةشريط الأدواتزر النسخخانة الاختيارزر الاختيارمفتاح التبديلالرقاقةقائمة الخياراتشريط التمريرحقل شريط التمريرالتقويمDateTimePickerمجموعة الحقولمجموعة أزرار الاختيارمجموعة خانات الاختيارعنصر الاختيارحقل الاختيارشريط القوائمرفع الملفاتمنتقي الملفات متقدّمساحة الإفلاتحقل إدخال النص متقدّمحقل النصحقل الرقاقاتالحقلمربع التحرير والسرد متقدّمحقل التحرير والسردحقل إدخال العدد متقدّمحقل العددحقل إدخال اللون متقدّمحقل اللونحقل إدخال الرمز لمرة واحدة متقدّمحقل الرمز لمرة واحدةحقل إدخال النص المدمج متقدّممربع كتابة الرسائلالبطاقةالفاصلالطبقة النمطية متقدّممربع الحوارالقائمة (list)صف القائمةاللوحة السفليةاللوحة الجانبيةالعرض الدوّاركتلة الشيفرةالجدولاللوحة المنبثقةالخطواتالأكورديونقائمة الوصفالشكل التوضيحيهيكل التطبيقرأس الصفحةالعرض الشجريالخط الزمنيمعاينة الصورمقبض تغيير الحجمالإطار القابل لتغيير الحجممنطقة التمريرالشارةمؤشر التقدمالعدّادعارض الأرقام المتحرّكمؤشر التحميلشريط الإشعار (Snackbar)التلميحاختصار لوحة المفاتيحالوسمالشيفرة المضمَّنةالتنويهالصورة الرمزيةالحالة الفارغةمجموعة الوسومهيكل التحميلعنصر الملفمربع اللونالنص المتحركعلامات التبويبعرض التبويباتشريط التطبيقشريط التنقلدرج التنقلالبحثالرابطالتنقل بين الصفحاتروابط السابق والتاليمؤشر الخطواتمسار التنقلجدول المحتوياتبطاقة الرابطقائمة FABالحالةكيف يُبنى Tanreiالمساهمةالترخيص والإسناد
الرئيسيةالمكوّناتمربع الحوار

مربع الحوار

يوقف الشخص من أجل قرار أو بضعة تفاصيل لا تستطيع المهمة أن تستمر من دونها، في سطح يعتّم كل ما خلفه ويحجبه إلى أن يُجاب عنه أو يُغلق.

content_copyعرض بتنسيق Markdown
الفئة
الاحتواء
الرموز
comp.dialog
المستوى
مركّب
مبني من
Modal layer

نظرة عامة

Web
tanrei-dialog
schedule
SwiftUI
tanreiDialog
schedule
Jetpack Compose
TanreiDialog
schedule
Flutter
TanreiDialog
schedule
React Native
Dialog
schedule

مولَّد من docs/status/status.json, specs/dialog.spec.json

مواصفة معدَّلة، لم تصل بعد إلى كل منصة

تصف هذه الصفحة مربع الحوار كما هو في المواصفة الآن: كل مربع حوار يعرض إجراءً واحدًا على الأقل، والإجراءات صف بعرض كامل من أزرار من الطراز tonal تُوضع بحسب مهمتها، والأيقونة تقع بجانب عنوان رئيسي محاذى إلى البداية، وفي نافذة مضغوطة (Compact) يقع مربع الحوار في الأسفل. مكوّن الويب ينفّذ المراجعة، والعروض في هذه الصفحة هي مكوّن الويب كما يُقدَّم اليوم؛ ويبقى معلَّمًا أعلاه بأنه متأخر إلى أن تُدقَّق المراجعة وتُسجَّل صورها المرجعية. أما SwiftUI و Compose و Flutter و React Native فتقدّم المراجعة السابقة إلى أن تلحق: الأيقونة فوق عنوان رئيسي موسَّط، وإجراءات من الطراز text في صف عند النهاية بالترتيب المعطى، وإجراءات اختيارية، ومربع حوار موسَّط في كل نافذة.

تأكيد: افتحه بالزر

تجاهل المسودةإلغاءتجاهل

الاستخدام

مربع الحوار مُتحكَّم به (controlled). تفتحه بضبط الخاصية open، وتغلقه بإعادتها إلى القيمة false، من معالج إجراء أو استجابةً للحدث dismiss. الإجراء لا يغلق مربع الحوار من تلقاء نفسه أبدًا، فتستطيع أن تتحقق من المدخلات أولًا، أو أن تعرض التقدم، أو أن تبقيه مفتوحًا عند الخطأ.

لمربع الحوار أيقونة اختيارية، وعنوان رئيسي، ونص مساعد اختياري، ومحتوى اختياري من عندك، وإجراءاته. مربع الحوار حوار: يعرض دائمًا جوابًا واحدًا على الأقل. الإجراءات أزرار Tanrei، كل منها معلَّم بالعلامة confirming (يُتمّ المهمة: «حفظ»، «حذف»، «سماح») أو بالعلامة dismissive (يتراجع: «إلغاء»، «ليس الآن»). يضعها مربع الحوار في مواضعها بحسب هاتين العلامتين، فتعطيها بأي ترتيب، ويرسم الإجراء الذي لم تضبط له variant بالطراز tonal.

عيّن width إلى small أو medium أو large ليأخذ مربع الحوار عرضًا ثابتًا بدل عرض محتواه. وليس لمربع الحوار زر إغلاق: طريقه للخروج من دون جواب إجراء تراجع، مثل «إلغاء».

علِّم العنصر الذي ينبغي أن ينال التركيز عند فتح مربع الحوار بعلامة المنصة نفسها: autofocus على الويب، و .defaultFocus أو @AccessibilityFocusState على SwiftUI، و FocusRequester يُمرَّر في initialFocus على Compose، و autofocus: true على Flutter، و initialFocusRef على React Native. من دون علامة يذهب التركيز إلى مربع الحوار نفسه، فيُقرأ عنوانه الرئيسي أولًا.

تعرض عيّنتا Web و React المواصفة الحالية. أما SwiftUI و Compose و Flutter و React Native فمتأخرة: ما زالت تقدّم المراجعة السابقة من مربع الحوار، وليس فيها علامتا confirming و dismissive ولا الطراز tonal الافتراضي، ولذلك تعطي عيّناتها كل إجراء الطراز text وتضع إجراء التأكيد أخيرًا، وتعرض تلك المراجعة إلى أن تلحق بالمواصفة.

<tanrei-button id="discard-draft" variant="text">تجاهل المسودة</tanrei-button>

<!-- closedby="none" هي dismissible مضبوطة على false. -->
<tanrei-dialog headline="تجاهل المسودة؟"
               supporting-text="ستُحذف من هذا الجهاز الرسالة التي كنت تكتبها.">
  <!-- dismissive يذهب إلى البداية، و confirming إلى النهاية. من دون variant، كلاهما tonal. -->
  <!-- autofocus تعلّم التركيز الأولي: الإجراء الأقل ضررًا. -->
  <tanrei-button slot="actions" dismissive autofocus>إلغاء</tanrei-button>
  <tanrei-button slot="actions" confirming>تجاهل</tanrei-button>
</tanrei-dialog>

<!-- أيقونة بجانب العنوان الرئيسي، وإجراء واحد له variant خاص به. -->
<tanrei-dialog headline="حذف المشروع؟" alert>
  <span slot="icon">delete</span>
  <tanrei-button slot="actions" dismissive variant="text">إلغاء</tanrei-button>
  <tanrei-button slot="actions" confirming variant="filled" destructive>حذف</tanrei-button>
</tanrei-dialog>

<!-- نموذج: «إلغاء» بجانب «حفظ»، وعرض ثابت. ليس لمربع الحوار زر إغلاق. -->
<tanrei-dialog headline="تعديل الاسم الظاهر" width="small">
  <tanrei-text-input label="الاسم الظاهر" autofocus></tanrei-text-input>
  <tanrei-button slot="actions" dismissive>إلغاء</tanrei-button>
  <tanrei-button slot="actions" confirming>حفظ</tanrei-button>
</tanrei-dialog>

<script type="module">
  import '@tanrei/web/components/dialog';
  const dialog = document.querySelector('tanrei-dialog');
  const cancel = dialog.querySelector('[dismissive]');
  const discard = dialog.querySelector('[confirming]');
  document.querySelector('#discard-draft').addEventListener('click', () => (dialog.open = true));
  // الإجراء لا يغلق مربع الحوار أبدًا: أنت تغلقه، من معالجه.
  cancel.addEventListener('click', () => (dialog.open = false));
  discard.addEventListener('click', () => { discardDraft(); dialog.open = false; });
  // المفتاح Escape والضغط على الخلفية يُصدران cancel، كما في <dialog> الأصلي؛
  // ويبيّن e.detail.reason أيّها كان: escape أو back أو scrim.
  // ثم يغلق مربع الحوار نفسه، ما لم تُلغِ الحدث.
  dialog.addEventListener('cancel', (e) => { if (unsaved()) e.preventDefault(); });
  // close يتبع كل إغلاق.
  dialog.addEventListener('close', onDialogClosed);
</script>
import { Button, Dialog } from '@tanrei/react';

// المفتاح Escape والخلفية يغلقان مربع الحوار بنفسيهما (onCancel ثم onClose): أبقِ open متوافقة معه من onClose.
<Dialog
  open={open}
  headline="تجاهل المسودة؟"
  supportingText="ستُحذف من هذا الجهاز الرسالة التي كنت تكتبها."
  onClose={() => setOpen(false)}
>
  <Button slot="actions" dismissive onClick={() => setOpen(false)}>إلغاء</Button>
  <Button slot="actions" confirming onClick={discard}>تجاهل</Button>
</Dialog>
import Tanrei

// مُعدِّل، مثل .alert و .sheet. الإغلاق يكتب false في الربط (binding).
TanreiButton("تجاهل المسودة", variant: .text) { showDiscard = true }
    .tanreiDialog("تجاهل المسودة؟", isPresented: $showDiscard,
                  supportingText: "ستُحذف من هذا الجهاز الرسالة التي كنت تكتبها.",
                  actions: {
                      TanreiButton("إلغاء", variant: .text) { showDiscard = false }
                      TanreiButton("تجاهل", variant: .text) { discardDraft(); showDiscard = false }
                  })
import io.github.chnotchy.tanrei.components.TanreiButton
import io.github.chnotchy.tanrei.components.TanreiDialog
import io.github.chnotchy.tanrei.tokens.TanreiButtonVariant

// visible بدل استدعائه وهو مفتوح فقط، لكي تعمل حركة الخروج.
val cancel = remember { FocusRequester() }
TanreiDialog(
    visible = open,
    onDismissRequest = { open = false },
    headline = "تجاهل المسودة؟",
    supportingText = "ستُحذف من هذا الجهاز الرسالة التي كنت تكتبها.",
    initialFocus = cancel,
    actions = {
        TanreiButton(text = "إلغاء", onClick = { open = false }, modifier = Modifier.focusRequester(cancel), variant = TanreiButtonVariant.Text)
        TanreiButton(text = "تجاهل", onClick = { discardDraft(); open = false }, variant = TanreiButtonVariant.Text)
    },
)
import 'package:tanrei/tanrei.dart';

// showTanreiDialog يدفع مسارًا نمطيًّا (modal route). Navigator.pop يغلقه؛ والإغلاق بلا إجراء يُكمل future بالقيمة null.
final bool? discard = await showTanreiDialog<bool>(
  context: context,
  barrierDismissible: true, // هي dismissible في المواصفة: Escape والرجوع والحجاب
  builder: (BuildContext context) => TanreiDialog(
    headline: 'تجاهل المسودة؟',
    supportingText: 'ستُحذف من هذا الجهاز الرسالة التي كنت تكتبها.',
    actions: <Widget>[
      TanreiButton(
        variant: TanreiButtonVariant.text,
        autofocus: true,
        onPressed: () => Navigator.pop(context, false),
        child: const Text('إلغاء'),
      ),
      TanreiButton(
        variant: TanreiButtonVariant.text,
        onPressed: () => Navigator.pop(context, true),
        child: const Text('تجاهل'),
      ),
    ],
  ),
);
import { useRef, useState } from 'react';
import type { View } from 'react-native';
import { Button, Dialog } from '@tanrei/react-native';

const cancel = useRef<View>(null);
const [open, setOpen] = useState(false);

// الأسماء كما في Modal من React Native: يُتحكَّم به عبر visible، و onRequestClose هو dismiss في المواصفة.
<Dialog
  visible={open}
  headline="تجاهل المسودة؟"
  supportingText="ستُحذف من هذا الجهاز الرسالة التي كنت تكتبها."
  onRequestClose={() => setOpen(false)}
  initialFocusRef={cancel}
  actions={
    <>
      <Button ref={cancel} variant="text" onPress={() => setOpen(false)}>إلغاء</Button>
      <Button variant="text" onPress={discard}>تجاهل</Button>
    </>
  }
/>

المواصفات

مربع حوار تنبيهي: تأكيد لإجراء لا رجعة فيه يُقرأ لحظة فتحه

حذف المشروعdeleteإلغاءحذف

البنية

الجزء الوصف
1 scrim The modal layer's scrim over everything behind the dialog (MDL-1). It blocks presses and, when the dialog is dismissible, dismisses it when pressed.
2 container The raised surface that holds everything else: centred in the window, or, in a compact window, with the bottom sheet's appearance at its bottom edge (DLG-17). It is a dialog in both places: the bottom sheet's look, not its drag. Its width follows its widest part, or the size width names (DLG-27).
3 header The row at the top: the icon and the headline, and nothing else (DLG-26, DLG-28). By default the icon sits at the headline's start and both are aligned to the start; a system may put the icon above a centred headline instead (DLG-9).
4 icon · اختياري A glyph that marks the kind of decision (a warning, a deletion), at the headline's start.
5 headline The question or statement the dialog is about, aligned to the start. It is the dialog's accessible name.
6 scroll · اختياري The area between the header and the actions that holds the supporting text and the content, and scrolls them when the dialog does not fit (DLG-11). It reaches the container's inline edges whatever inline padding the container has, so the dividers cross the whole container and nothing overflows it; inside, it insets its parts by that same padding.
7 supporting-text · اختياري What will happen, or what the person needs to know to decide. It is the dialog's accessible description.
8 content · اختياري Whatever else the decision needs: a list to choose from, a field. The dialog insets it and hands it a content color; it does not lay it out.
9 divider · اختياري Hairlines between the header and the scrolling content and between the content and the actions, drawn only while the content scrolls: for as long as it does in the default system, or only while content lies beyond each line under dividerVisibility scroll.
10 actions The answers: one or more Tanrei Buttons, each marked confirming or dismissive, laid out as a full-width Button group (a row that the actions share equally, or a column when they do not fit). A Button whose variant the product did not set is tonal in the default system. Required: a dialog asks something, so it always offers an answer.

الخصائص

الخاصية النوع القيمة الافتراضية الوصف
open boolean false Whether the dialog is presented. The product opens it, and closes it from an action's handler or in answer to dismiss.
headline string "" The question or statement ("Discard draft?"). Required: it is the accessible name.
supportingText string "" What will happen, or what the person needs to know. It is the accessible description.
alert boolean false Marks a dialog that interrupts with something urgent (a destructive confirmation, a lost connection): assistive technology reads its headline and supporting text as soon as it opens, and the web exposes it as an alertdialog.
dismissible boolean true Whether Escape, the platform's back and a press on the scrim ask to close the dialog (dismiss). Set it to false only when the person must choose one of the actions; give such a dialog a dismissive action. A dialog has no close button (DLG-28): content that needs a way out and nothing more gets a dismissive action (Cancel, or Close where there is nothing to cancel), or belongs in a side sheet or a bottom sheet.
width content | small | medium | large "content" What decides the container's width while the dialog is centred (DLG-7). content: as wide as its widest part, between comp.dialog.container.min-width and comp.dialog.container.max-width (DLG-7). small, medium, large: as wide as comp.dialog.width.small, comp.dialog.width.medium or comp.dialog.width.large (400, 560 and 800 in the default system) whatever it holds, for a form or content whose width should not follow its text (DLG-27). Never wider than the window less its margins; as a sheet in a compact window every value is placed as DLG-17 says. A structural choice drawn by code, not a token axis.
compactPresentation sheet | own "sheet" How the dialog is presented in a compact window, one narrower than comp.dialog.compact.window-width. sheet: with the bottom sheet's appearance at the window's bottom edge (DLG-17, DLG-18), as every dialog and every popover of Tanrei is there; it stays a dialog. own: centred in every window, as DLG-7 places it, for a dialog whose look must not change with the window (a short alert that should read as one). A structural choice drawn by code, not a token axis.
dividerVisibility auto | always | scroll "auto" When the dividers below the header and above the actions are drawn while the supporting text and the content scroll between them. always: both, for as long as that part scrolls, wherever it is scrolled to. scroll: each only while content lies beyond it — below the header while the part is not at its top, above the actions while more of it lies below. auto takes comp.dialog.divider.visibility, the design system's answer (always in the default system). Under every choice a dialog whose parts all fit draws none.

المنافذ

المنفذ الوصف
default The custom content, below the supporting text.
icon A glyph at the headline's start (DLG-9).
actions The action items: Tanrei Buttons, one at least (DLG-19), each marked confirming or dismissive. The dialog places them (DLG-10) and gives those without a variant its own (DLG-20). A fixture fills it through its items.

الأحداث

الحدث الوصف
dismiss The person asked to close the dialog without an action: Escape, the platform's back or a press on the scrim. Carries which of these it was, named escape, back or scrim (in the platform's casing). Emitted only while dismissible is true.

العناصر

أنواع العناصر التي يكررها المكوّن. يسرد كل نموذج اختبار عناصره بالترتيب، وتسمّي كل منصة كل نوع بأسلوبها.

action

One answer at the foot of the dialog: a Tanrei Button, placed by the dialog as a full-width Button group places its items and given the dialog's variant unless the product set one.

الخاصية النوع القيمة الافتراضية الوصف
variant string "" The Button's variant (filled, tonal, elevated, outlined, text, or one a system added). Empty, the default: the dialog gives it the variant comp.dialog.actions.variant names, tonal in the default system (DLG-20). Set it to give one action another look (a filled Delete beside a text Cancel); a Delete that destroys something also sets the Button's own destructive, which the dialog leaves as set (BTN-40).
disabled boolean false As the Button's disabled. A disabled action keeps its place and its share of the width.
confirming boolean false The action completes the task (OK, Save, Delete, Allow). Placed at the end of a row and at the top of a column (BGR-13, BGR-14). It decides the place only, never the look (BGR-19).
dismissive boolean false The action backs out of the task (Cancel, Not now, Keep editing). Placed at the start of a row and at the bottom of a column (BGR-13, BGR-14). It decides the place only, never the look (BGR-19).
المنفذ الوصف
label The Button's label, as its default slot. A fixture item's content is this label.
الحدث الوصف
click The action was activated, as the Button's click. The dialog does not close by itself (DLG-6).
المواصفة Web SwiftUI Jetpack Compose Flutter React Native
العنصر tanrei-button TanreiButton TanreiButton TanreiButton Button
variant · الخاصية variant variant variant variant variant
disabled · الخاصية disabled disabled enabled onPressed disabled
confirming · الخاصية confirming tanreiGroupPlacement(.confirming) Modifier.confirming() TanreiButtonGroupItem.placement confirming
dismissive · الخاصية dismissive tanreiGroupPlacement(.dismissive) Modifier.dismissive() TanreiButtonGroupItem.placement dismissive
click · الحدث click action onClick onPressed onPress
ملاحظات A tanrei-button child with slot="actions": its label is its content. confirming and dismissive are boolean attributes that the dialog reads, as the button group does. A Button without a variant attribute takes the dialog's variant through the internal controller the group already gives its items (DLG-20); the attribute, once written, wins. A TanreiButton in the actions: builder. The flags are the button group's .tanreiGroupPlacement(_:) modifier (one enum: both cannot be set). A Button created with variant: nil reads the dialog's variant from an environment value the dialog sets (internal to the package). A TanreiButton in the actions lambda, whose receiver is the button group's scope, so the flags are its Modifier.confirming() and Modifier.dismissive(). enabled is disabled's inverse. variant: TanreiButtonVariant? = null takes the dialog's from a CompositionLocal (internal to the library). A TanreiButton in actions:; a null onPressed disables it. The flags are the button group's TanreiButtonGroupItem(placement:, child:) (one enum: both cannot be set). A Button with variant: null takes the dialog's from an InheritedWidget (internal to the package). A Button in the actions prop (an element or a fragment of them). confirming and dismissive are props the dialog reads from its actions, as the button group reads them. A Button with no variant takes the dialog's from a React context (internal to the package).

الحالات

السلوك

متطلبات بصيغة EARS. يُختبر تنفيذ كل منصة مقابل هذه المعرّفات. يُعرض نص المتطلبات كما ورد في ملف المواصفة، بالإنجليزية.

المعرّف المتطلب
DLG-1 WHEN open becomes true THE dialog SHALL present its container on its modal layer (MDL-1, MDL-2): above everything in its window, with the layer's scrim in comp.dialog.scrim.color and comp.dialog.scrim.blur over the whole window behind it, everything outside it inert and the content behind it kept from scrolling, until it closes.
DLG-2 WHEN the dialog opens THE dialog SHALL move focus as its modal layer does (MDL-3): to the element in it that the product marked for initial focus (the platform's own mark, named in the platform notes) or, without one, to its container, so that assistive technology reads the headline first and the next Tab reaches the first focusable element.
DLG-3 WHILE the dialog is open THE dialog SHALL keep focus inside it as its modal layer does (MDL-4): Tab and Shift+Tab wrap between its last and first focusable elements, and screen-reader navigation does not leave it.
DLG-4 WHEN the dialog closes THE dialog SHALL return focus and restore the content behind it as its modal layer does (MDL-2, MDL-5).
DLG-5 WHILE dismissible is true, WHEN the user presses Escape, uses the platform's back or presses the scrim, as its modal layer answers them (MDL-6), THE dialog SHALL emit dismiss once with the reason escape, back or scrim; the product closes it by setting open to false, except on the web, where the dialog closes itself unless dismiss is cancelled, as a native dialog does. WHILE dismissible is false THE dialog SHALL ignore these inputs and emit nothing.
DLG-6 WHEN an action is activated THE dialog SHALL NOT close by itself; the product closes it from the action's handler by setting open to false.
DLG-7 WHILE the dialog is not presented as a sheet (DLG-17) — in a window at least comp.dialog.compact.window-width wide, or while compactPresentation is own — THE dialog SHALL centre its container in the window's safe area, at least comp.dialog.container.margin from every edge and from the on-screen keyboard, as wide as width names (DLG-27) — WHILE width is content, as wide as its widest part (its text, its content, or its actions in one row at their own widths) between comp.dialog.container.min-width and comp.dialog.container.max-width — but never wider than the window less the margins. There THE dialog SHALL draw its container in comp.dialog.container.color with comp.dialog.container.elevation and comp.dialog.container.shape corners eased by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0).
DLG-8 THE dialog SHALL inset its parts by comp.dialog.container.padding on every side and stack the header (the icon and the headline, DLG-9), the supporting text and the content comp.dialog.container.gap apart, leaving out a part that is empty, with the actions comp.dialog.actions.margin-block-start below the last of them. It SHALL draw the headline in comp.dialog.headline.typescale and comp.dialog.headline.color, wrapping onto more lines rather than truncating, and the supporting text in comp.dialog.supporting-text.typescale and comp.dialog.supporting-text.color, and SHALL hand the content comp.dialog.content.color as the platform's inherited foreground (the web's inherited color, SwiftUI's foreground style, Compose's content color, Flutter's default text and icon style, React Native's Tanrei text context); plain text given as content, where the platform must style it, SHALL take sys.typescale.body. THE dialog SHALL set the headline's letter-spacing by the script of its text, as every title is set (PRS-20): no negative letter-spacing WHERE its language is Chinese, Japanese or Korean, and none WHERE its script joins its letters or stacks marks on them.
DLG-9 WHILE the icon slot is filled THE dialog SHALL draw the icon at comp.dialog.icon.size in comp.dialog.icon.color, with the family and fill of an unselected icon, sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, and place it as comp.dialog.icon.placement says: WHILE it is inline, at the start of the header, comp.dialog.header.gap before the headline and centred on the headline's first line, so that a headline that wraps keeps the icon beside its first line; WHILE it is above, above the headline, comp.dialog.container.gap apart, with the icon and the headline centred. The supporting text and the content SHALL be aligned to the start in either placement, and WHILE the icon slot is empty the headline SHALL be aligned to the start whatever the placement.
DLG-10 THE dialog SHALL lay out its actions as a standard Button group lays out its items (BGR-12 to BGR-19), comp.dialog.actions.gap apart, in the order the group gives them — dismissive at the start and confirming at the end of a row, confirming on top and dismissive at the bottom of a column — with focus and reading order following that order (BGR-15). WHILE comp.dialog.actions.arrangement is fill it SHALL make them a full-width group across the container's inner width, the actions sharing a row equally (BGR-17) or each as wide as the column; WHILE it is end it SHALL keep them at their own widths, the row aligned to the end, and a column as wide as its widest action and aligned to the end. It SHALL place them in a row WHILE every action's label fits its share on one line (fill) or the row at its own widths fits the inner width (end), and otherwise in a column, re-deciding when the dialog's width or the text size changes.
DLG-11 WHEN the dialog's parts do not fit in the height its placement leaves THE dialog SHALL keep the header and the actions in place and scroll the supporting text and the content between them, and SHALL draw a divider of comp.dialog.divider.width in comp.dialog.divider.color across the container below the header and another above the actions: WHILE its divider visibility (DLG-24) is always, both for as long as that part scrolls, wherever it is scrolled to, at its top included; WHILE it is scroll, the one below the header only WHILE the scrolled part is not at its top and the one above the actions only WHILE more of it lies below. WHILE everything fits THE dialog SHALL draw no divider.
DLG-12 WHEN the header and the actions alone leave no room for the scrolled part (large text on a short window) THE dialog SHALL scroll all of its parts together instead, so that nothing is cut off or unreachable.
DLG-13 WHEN the dialog opens centred in the window (DLG-7) THE dialog SHALL fade its scrim in with its container, as its modal layer says (MDL-7), and make its container appear as every modal surface appears: from sys.motion.appear.opacity, sys.motion.appear.scale, sys.motion.appear.distance and sys.motion.appear.blur to whole, grown about its centre and travelling up into place, over comp.dialog.motion.enter-duration with comp.dialog.motion.enter-easing; WHEN it closes there it SHALL take the container back the same way to where it started while fading the scrim out, over comp.dialog.motion.exit-duration with comp.dialog.motion.exit-easing. A motion turned round SHALL go on from the opacity and the size it has reached. WHERE sys.behavior.appearance is unfold the scale SHALL apply on both axes, from the centre: a centred container has no anchored edge. WHERE motion is reduced it SHALL only fade the container in and out, over sys.motion.role.reduced, with no scale, distance or blur, or appear and disappear at once on a platform whose reduced-motion setting removes every animation (Android's). WHEN it is mounted with open true THE dialog SHALL appear without its motion while the document has not yet been first painted, and with it when it is mounted after that. Focus SHALL move (DLG-2, DLG-4) when the dialog opens or closes, not when the motion ends.
DLG-14 THE dialog SHALL expose its container as its modal layer's modal dialog (MDL-10) — an alert dialog WHILE alert is true — named by headline and described by supportingText; WHEN alert is true and the dialog opens it SHALL have assistive technology read the headline and the supporting text at once; WHILE headline is empty it SHALL still open, and in debug builds SHALL report that it has no accessible name.
DLG-15 WHEN a dialog opens while another is open THE dialog SHALL stack above it as modal layers stack (MDL-8): with its own scrim, the first one inert as well, focus returned on closing to the element inside the first dialog that had it, and Escape dismissing only the topmost dialog.
DLG-16 WHEN the layout direction is right-to-left THE dialog SHALL mirror its layout — the icon at the right of the headline, the headline and the supporting text aligned to the right, and its actions placed as a Button group places them right to left (BGR-8: dismissive at the right, confirming at the left of a row; a column's order unchanged) — and SHALL NOT mirror its content itself: the content follows the layout direction.
DLG-17 WHILE the window is narrower than comp.dialog.compact.window-width (a compact window) and compactPresentation is sheet THE dialog SHALL present its container with the bottom sheet's appearance (BSH-7): at the window's bottom edge, as wide as the window but no wider than comp.bottom-sheet.container.max-width and centred in it, whatever width names, filled with comp.bottom-sheet.container.color, raised by comp.bottom-sheet.container.elevation, its top corners rounded by comp.bottom-sheet.container.shape eased by sys.shape.corner-smoothing and its bottom corners square, its color reaching under the bottom system inset and its parts above it, sitting on the on-screen keyboard while it is shown, and never taller than the window below its top system inset less comp.bottom-sheet.container.margin-block-start. Its parts SHALL keep their own tokens and layout (DLG-8 to DLG-12, DLG-20), inside comp.dialog.container.padding. It SHALL stay a dialog there — DLG-1 to DLG-6, DLG-8 to DLG-12 and DLG-14 to DLG-16 hold unchanged, its scrim the dialog's own — and SHALL draw no drag handle, follow no drag and close on no swipe, because it has one height and its exits are its actions, Escape, back and the scrim. WHILE compactPresentation is own THE dialog SHALL be centred in every window as DLG-7 says. WHEN the window's width crosses comp.dialog.compact.window-width, or compactPresentation changes, while the dialog is open THE dialog SHALL take the other presentation at once, without motion, keeping focus where it is. WHERE comp.dialog.compact.window-width is 0 no window is compact. WHEN compactPresentation names a value the dialog does not define THE dialog SHALL treat it as sheet and in debug builds report the unknown value.
DLG-18 WHEN the dialog opens as a sheet in a compact window (DLG-17) THE dialog SHALL fade its scrim in (MDL-7) while sliding its container up from below the window's bottom edge into its place, as a modal bottom sheet opens (BSH-1), over comp.bottom-sheet.motion.enter-duration with comp.bottom-sheet.motion.enter-easing; WHEN it closes there it SHALL fade the scrim out while sliding the container down out of view, over comp.bottom-sheet.motion.exit-duration with comp.bottom-sheet.motion.exit-easing. WHERE motion is reduced it SHALL fade in and out in place without sliding, or appear and disappear at once on a platform whose reduced-motion setting removes every animation (Android's). Focus SHALL move as DLG-13 says.
DLG-19 IF the dialog opens with no action THE dialog SHALL still open and lay out its other parts, and in debug builds SHALL report as an error that a dialog must offer at least one action; a platform whose API can require an action (a required, non-empty list or builder of actions) SHALL require it, and one that can count its actions only when it opens SHALL count them then. It SHALL report likewise a dialog whose actions are all disabled while dismissible is false, which leaves the person no way out.
DLG-20 WHILE an action's variant is empty THE dialog SHALL give that Button the variant comp.dialog.actions.variant names, through the channel a Button reads from the component that places it, so that the Button renders from that variant's tokens and states as it would with the variant set; WHILE an action's variant is set THE dialog SHALL leave it as set. THE dialog SHALL NOT change an action's size or tokens, and SHALL NOT change its look or emphasis for its confirming or dismissive (BGR-19).
DLG-21 THE dialog SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the dialog is shown THE dialog SHALL draw itself for the new direction at once, without a motion of its own, keeping its open state and its focus.
DLG-22 WHERE the layout direction is right-to-left THE dialog SHALL draw the glyph of its icon slot mirrored left to right exactly when the system's icon set marks that glyph's name as mirrored or the product forces mirroring for that use, and unmirrored when the product forbids it: the icon helper mirrors the glyph inside its own box, about the box's vertical centre line, changing neither its place, size, color and fill nor the order DLG-16 gives. THE dialog SHALL NOT mirror a glyph itself, and SHALL NOT mirror content the product gives as an image or a view of its own rather than by a glyph's name.
DLG-23 THE dialog SHALL lay out its headline and its supporting text as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word, a number with its sign or unit, a telephone number and an address keep their own order in a right-to-left layout and reorder nothing beside them; THE dialog SHALL still place each and align its lines by the layout direction, not by the text's own.
DLG-24 THE dialog SHALL draw its dividers (DLG-11) as its dividerVisibility names: WHILE dividerVisibility is auto, as comp.dialog.divider.visibility names, which follows sys.behavior.divider-visibility unless a design system sets the dialog apart; WHILE dividerVisibility is always or scroll, as that value names, whatever the tokens say. WHEN dividerVisibility, or the token it follows, changes THE dialog SHALL draw the other form at once, moving no part and keeping the scroll position and focus; WHEN dividerVisibility names a value the dialog does not define THE dialog SHALL treat it as auto and in debug builds report the unknown value. A platform that cannot draw a choice SHALL fall back to the first value, always, and state that in its README.
DLG-26 THE dialog SHALL draw its header as one row that holds the icon and the headline as DLG-9 places them, across the container's inner width, and nothing at its end; WHILE headline is empty and the icon slot is empty THE dialog SHALL draw no header, leaving it out as DLG-8 leaves out an empty part.
DLG-27 WHILE the dialog is centred (DLG-7) and width is small, medium or large THE dialog SHALL make its container as wide as comp.dialog.width.small, comp.dialog.width.medium or comp.dialog.width.large, whatever its parts would ask and not held between comp.dialog.container.min-width and comp.dialog.container.max-width, but never wider than the window's safe area less comp.dialog.container.margin on each side; its headline and supporting text SHALL wrap to that width, its content SHALL take it, and its actions SHALL be placed in a row or a column as DLG-10 decides at that width. WHILE width is content THE dialog SHALL be as wide as DLG-7 says. WHILE it is a sheet THE dialog SHALL place its container as DLG-17 says, whatever width names, because a narrow window leaves no room for a choice of width. WHEN width changes while the dialog is open THE dialog SHALL take the new width at once, without a motion of its own, keeping its scroll position and focus; WHEN width names a value the dialog does not define THE dialog SHALL treat it as content and in debug builds report the unknown value.
DLG-28 THE dialog SHALL draw no close button in its header: a dialog asks for a decision, and its way out without one is its dismissive action and, WHILE dismissible is true, Escape, the platform's back and the scrim (DLG-5). THE dialog SHALL have no prop that draws one; WHERE a platform can be given a property the dialog does not define and the product gives one that asks for a close button (the web's closable and close-label attributes), THE dialog SHALL draw nothing for it and in debug builds SHALL report that a dialog has no close button.
DLG-29 WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on the host, by the attribute or by the ARIA property that reflects it, THE dialog SHALL move it to the data-aria- attribute of the same name, so that the host is no second node with its name, description or state, and SHALL expose it on its dialog (DLG-14) — an id reference by the elements it names in the host's tree or a tree around it, a description after its own. WHILE headline is not empty it is the dialog's name, over a name given so; WHILE headline is empty, a name given so is the dialog's name and SHALL NOT be reported as having none in debug builds. WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE dialog SHALL take it off that element.

مطابقة الأسماء بين المنصات

التكافؤ لا يعني واجهات برمجية متطابقة. تعبّر كل منصة عن العقد نفسه بأسلوبها.

المواصفة Web SwiftUI Jetpack Compose Flutter React Native
المكوّن tanrei-dialog tanreiDialog TanreiDialog TanreiDialog Dialog
open · الخاصية open isPresented visible showTanreiDialog visible
headline · الخاصية headline headline headline headline headline
supportingText · الخاصية supportingText supportingText supportingText supportingText supportingText
alert · الخاصية alert alert alert alert alert
dismissible · الخاصية closedby dismissible dismissible barrierDismissible dismissible
width · الخاصية width width width width width
compactPresentation · الخاصية compactPresentation compactPresentation compactPresentation compactPresentation compactPresentation
dividerVisibility · الخاصية dividerVisibility dividerVisibility dividerVisibility dividerVisibility dividerVisibility
dismiss · الحدث cancel isPresented onDismissRequest showTanreiDialog onRequestClose
ملاحظات supportingText is the supporting-text attribute. dismissible is HTML's own closedby attribute: any (the default: Escape and a press on the backdrop) or none. cancel is a CustomEvent whose detail.reason is escape, back or scrim, cancelable as on a native <dialog>; close follows every close. There is no closable or close-label attribute (DLG-28): given one, the dialog draws nothing for it, and debug builds report it. width is the width attribute (content, small, medium, large), and compactPresentation the compact-presentation attribute (sheet, own). Slots: default, icon, actions. The actions are laid out by the button group's placement (BGR-13 to BGR-17) inside the shadow root: the dialog's shadow root uses manual slot assignment, assigns its icon and content children to their slots, and assigns each action to one of three action slots in the order of the orientation, so focus and the accessibility tree follow the placed order (BGR-15); CSS order is not used. Compact (DLG-17): a media query cannot read a custom property, so the dialog reads the resolved comp.dialog.compact.window-width through a probe and matches it with matchMedia('(width < …px)'), re-placing at once on change; the sheet's appearance is the shared one the popover draws too (src/primitives/compact-sheet.ts), read from comp.bottom-sheet. Debug builds (import.meta.env.DEV or the package's dev export) report a dialog opened with no assigned action (DLG-19). Snapshot harness: a 640 × 640 viewport, and 360 × 640 for the compact- fixtures, with the dialog opened by open; the screenshot is the viewport, since the dialog and its backdrop are in the top layer. dividerVisibility is the divider-visibility attribute (auto, always, scroll); auto reads --tanrei-comp-dialog-divider-visibility from the cascade with a style query, and where the browser has none the dialog writes the name it read on its <dialog> when it lays out. Lagging: ships the earlier revision — the icon above a centred headline, text actions in a row at the end, optional actions, centred in every window, the 480 × 640 harness. The target: a view modifier, as .alert and .sheet are: .tanreiDialog(_ headline:, isPresented: Binding<Bool>, supportingText:, alert:, dismissible:, icon:, actions:, content:). Dismissal writes false to the binding, SwiftUI's idiom. It is presented with .fullScreenCover, with a clear presentation background and the system transition off (Tanrei draws the scrim and the motion); that presentation makes the rest of the app inaccessible to VoiceOver, as accessibilityViewIsModal does. Initial focus is .defaultFocus or an @AccessibilityFocusState the product passes; the iOS escape gesture is .accessibilityAction(.escape). actions: is a required builder; the dialog counts its subviews through a variadic view and reports an empty one in debug builds (DLG-19), and lays them out with the button group's Layout. Compact is the window's width (a GeometryReader at the presentation's root) against the token, not the horizontal size class, so every platform switches at the same width. Snapshot harness: TanreiDialogSurface in place in a 640 × 640 frame, and 360 × 640 for the compact- fixtures. Not yet: the headline's letter-spacing by the script of its text (DLG-8, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; DLG-24) — the dividers still follow the scroll position, as scroll does. Not built on this platform yet: width (DLG-27), and the reason dismiss carries. Not built on this platform yet: DLG-17 and DLG-18 as they are written now — the bottom sheet's appearance and motion in a compact window — and compactPresentation. Lagging: ships the earlier revision — the icon above a centred headline, text actions in a row at the end, optional actions, centred in every window. The target: TanreiDialog(visible, onDismissRequest, headline, supportingText, alert, dismissible, initialFocus: FocusRequester?, icon, actions, content) with actions a required parameter; the group's Layout sees its measurables and reports none in debug builds (DLG-19). visible rather than calling it only while open, so that the exit motion can run before the window goes. Presented in a Dialog window with usePlatformDefaultWidth = false and the window's own dim off (Tanrei draws the scrim). dismissible sets dismissOnBackPress and dismissOnClickOutside. The headline is the window's paneTitle. Compact is LocalWindowInfo.current.containerSize against the token, not a window size class, so every platform switches at the same width. Snapshot harness: renders TanreiDialogContent (the surface and the scrim) in place in a 640 × 640 dp box, and 360 × 640 for the compact- fixtures; the window itself is covered by instrumented tests. Not yet: the headline's letter-spacing by the script of its text (DLG-8, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; DLG-24) — the dividers still follow the scroll position, as scroll does. Not built on this platform yet: width (DLG-27), and the reason dismiss carries. Not built on this platform yet: DLG-17 and DLG-18 as they are written now — the bottom sheet's appearance and motion in a compact window — and compactPresentation. Lagging: ships the earlier revision — the icon above a centred headline, text actions in a row at the end, optional actions, centred in every window, the 480 × 640 golden. The target: showTanreiDialog<T>(context:, barrierDismissible:, builder:) pushes a modal route that draws the scrim as its barrier, and returns a Future<T?>; Navigator.pop(context, value) from an action closes it, and a dismissal completes the future with null. TanreiDialog(headline:, supportingText:, alert:, icon:, required actions:, child:) is the surface the builder returns; actions is asserted non-empty (DLG-19). Initial focus is autofocus: true on a focusable widget. The route is marked scopesRoute and namesRoute, so assistive technology stays inside it. Compact is MediaQuery.sizeOf(context).width against the token. Snapshot harness: a golden of the whole 640 × 640 surface after showTanreiDialog, and 360 × 640 for the compact- fixtures, as the overlay is part of the widget tree. Not yet: the headline's letter-spacing by the script of its text (DLG-8, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; DLG-24) — the dividers still follow the scroll position, as scroll does. Not built on this platform yet: width (DLG-27), and the reason dismiss carries. Not built on this platform yet: DLG-17 and DLG-18 as they are written now — the bottom sheet's appearance and motion in a compact window — and compactPresentation. Lagging: ships the earlier revision — the icon above a centred headline, text actions in a row at the end, optional actions, centred in every window, the 480 × 640 harness. The target: named as React Native's Modal: visible and onRequestClose (Android's back and, here, Escape on a hardware keyboard and a press on the scrim). Presented in a transparent Modal with animationType="none" (Tanrei draws the scrim and the motion), statusBarTranslucent and navigationBarTranslucent. Initial focus is an initialFocusRef the product passes, moved with AccessibilityInfo.setAccessibilityFocus. actions is required in the types, and development builds report it empty when the dialog becomes visible (DLG-19). Compact is useWindowDimensions().width against the token. Snapshot harness: renders DialogSurface (the surface and the scrim) in place in a 640 × 640 view, and 360 × 640 for the compact- fixtures, since a Modal is a native window the snapshot does not include. Not yet: the headline's letter-spacing by the script of its text (DLG-8, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; DLG-24) — the dividers still follow the scroll position, as scroll does. Not built on this platform yet: width (DLG-27), and the reason dismiss carries. Not built on this platform yet: DLG-17 and DLG-18 as they are written now — the bottom sheet's appearance and motion in a compact window — and compactPresentation.

الرموز

الرمز يشير إلى القيمة المحلولة (فاتح)
--tanrei-comp-dialog-actions-arrangement — 0
--tanrei-comp-dialog-actions-gap sys.space.space200 16px
--tanrei-comp-dialog-actions-margin-block-start sys.space.space300 24px
--tanrei-comp-dialog-actions-variant — 1
--tanrei-comp-dialog-compact-window-width — 600px
--tanrei-comp-dialog-container-color sys.color.surface-bright #ffffff
--tanrei-comp-dialog-container-elevation sys.elevation.level3 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 1px 0px rgb(0 0 0 / 0.04), 0px 4px 8px -4px rgb(0 0 0 / 0.04), 0px 16px 24px -8px rgb(0 0 0 / 0.06)
--tanrei-comp-dialog-container-gap sys.space.space200 16px
--tanrei-comp-dialog-container-margin sys.space.space200 16px
--tanrei-comp-dialog-container-max-width — 560px
--tanrei-comp-dialog-container-min-width — 280px
--tanrei-comp-dialog-container-padding sys.space.space300 24px
--tanrei-comp-dialog-container-shape sys.shape.corner.large 12px
--tanrei-comp-dialog-content-color sys.color.on-surface #1b1b1b
--tanrei-comp-dialog-divider-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-dialog-divider-visibility sys.behavior.divider-visibility 0
--tanrei-comp-dialog-divider-width sys.border.hairline 1px
--tanrei-comp-dialog-header-gap sys.space.space150 12px
--tanrei-comp-dialog-headline-color sys.color.on-surface #1b1b1b
--tanrei-comp-dialog-icon-color sys.color.secondary #525252
--tanrei-comp-dialog-icon-placement — 0
--tanrei-comp-dialog-icon-size sys.icon.medium 24px
--tanrei-comp-dialog-motion-enter-duration comp.modal-layer.motion.enter-duration 350ms
--tanrei-comp-dialog-motion-enter-easing comp.modal-layer.motion.enter-easing cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-dialog-motion-exit-duration comp.modal-layer.motion.exit-duration 300ms
--tanrei-comp-dialog-motion-exit-easing comp.modal-layer.motion.exit-easing cubic-bezier(0.3, 0, 1, 1)
--tanrei-comp-dialog-scrim-blur comp.modal-layer.scrim.blur 0px
--tanrei-comp-dialog-scrim-color comp.modal-layer.scrim.color #000000 · 40%
--tanrei-comp-dialog-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-dialog-width-large — 800px
--tanrei-comp-dialog-width-medium — 560px
--tanrei-comp-dialog-width-small — 400px
--tanrei-comp-dialog-headline-typescale sys.typescale.heading-5 500 16px/1.5
--tanrei-comp-dialog-supporting-text-typescale sys.typescale.body 400 14px/1.5714

نماذج الاختبار

عروض مرجعية. تلتقط كل المنصات لقطة لكل نموذج بالمعرّف نفسه حتى يمكن مقارنة النتائج.

مولَّد من specs/dialog.spec.json, tokens/comp/dialog.tokens.json, design/tokens/

الإرشادات

متى يُستخدم

استخدم مربع الحوار حين لا تستطيع المهمة أن تستمر من دون جواب: تأكيد شيء لا يمكن التراجع عنه، أو طلب إذن، أو جمع تفصيلين أو ثلاثة من دون مغادرة الصفحة. هو يوقف الشخص، فلا تستخدمه إلا حين يستحق الأمر إيقافه.

استخدم شريط الإشعار لتبلغ عمّا حدث للتو، مع «تراجع» اختياري، بدل أن تسأل «هل أنت متأكد؟» عن شيء يمكن التراجع عنه. استخدم قائمة الخيارات لاختيار قصير لا يحجب الشاشة. وضع النماذج الأطول والمهام المعقدة في صفحة خاصة بها.

الأجزاء

الجزء يُضبط بـ ملاحظات
الأيقونة منفذ الأيقونة (icon) اختيارية. رسم أيقونة (glyph) يعلّم نوع القرار، مثل delete. يقع في بداية السطر الأول من العنوان الرئيسي، قبله بمقدار 12، وتبقى بجانب ذلك السطر حين يلتف العنوان.
العنوان الرئيسي headline مطلوب. السؤال أو العبارة، مثل «تجاهل المسودة؟». محاذى دائمًا إلى البداية. وهو الاسم المتاح للتقنيات المساعدة لمربع الحوار.
النص المساعد supportingText ما الذي سيحدث، أو ما يحتاج الشخص إلى معرفته ليقرر. محاذى دائمًا إلى البداية. وهو وصف مربع الحوار.
المحتوى المنفذ الافتراضي كل ما يحتاج إليه القرار غير ذلك: قائمة للاختيار منها، حقل. يزيحه مربع الحوار عن الحواف إلى الداخل ويعطيه لون محتوى؛ ولا يرتّب تخطيطه.
الإجراءات منفذ الإجراءات (actions) مطلوبة: زر Tanrei واحد على الأقل، وكل زر معلَّم بالعلامة confirming أو dismissive. يضعها مربع الحوار في مجموعة أزرار بعرض كامل ويرسم ما لا طراز له منها بالطراز tonal.

حين لا تتسع النافذة للأجزاء، تبقى الترويسة والإجراءات في مكانها ويُمرَّر النص المساعد والمحتوى بينهما. ويفصل خط رفيع الترويسة عنهما، وآخر الإجراءات، ما داما يُمرَّران، حتى وهما في أعلاهما. اضبط dividerVisibility على scroll (divider-visibility على الويب) ليُرسم كل فاصل فقط ما دام شيء خلفه قد خرج بالتمرير عن مجال الرؤية؛ ويختار cupertino و m3-like ذلك لكل مربعات الحوار. وحين لا يترك النص الكبير حيّزًا حتى لذلك، يُمرَّر مربع الحوار كله، بلا فاصل.

على الويب، الجزء الذي يُمرَّر هو ::part(scroll). ومهما كان الحشو الأفقي الذي تعطيه لـ ::part(container)، يمتد هذا الجزء إلى حافتي الحاوية ويحتفظ بالحشو نفسه في داخله، فتبقى فواصله ممتدة عبر مربع الحوار كله ولا يفيض منه شيء. وإن كان محتواك يثبّت صفًّا في أسفله بـ position: sticky، فأخبر الجزء الذي يُمرَّر بارتفاع ذلك الصف، كي لا يختبئ تحته حقلٌ مُرِّر إلى مجال الرؤية حين تلقّى التركيز:

tanrei-dialog::part(scroll) { scroll-padding-block-end: 56px; }

يجوز عنوان رئيسي بلا نص مساعد: سؤال في العنوان مع أجوبته حوار. أما ما لا يكونه مربع الحوار فعبارة ليس لها ما يُجاب به.

الإجراءات

تتقاسم الإجراءات العرض الداخلي للحاوية بالتساوي، وبينها 16.

  • الموضع بحسب المهمة. في الصف، يكون إجراء التراجع (dismissive) في البداية وإجراء التأكيد (confirming) في النهاية. في العمود، يكون إجراء التأكيد في الأعلى وإجراء التراجع في الأسفل. والإجراء الذي لا يحمل أيًّا من العلامتين يحتفظ بموضعه المعطى بين الاثنين. التركيز وقارئات الشاشة تتبع ترتيب المواضع، فلا ترتّب الأزرار بيدك أبدًا.
  • صف أو عمود بحسب الاتساع. تبقى الإجراءات في صف ما دامت كل تسمية تتسع في حصتها على سطر واحد. وحين تكاد إحداها تلتف، تُرصّ في عمود، كل منها بعرض مربع الحوار. يقرر مربع الحوار من جديد حين يتغير عرضه أو حجم النص.
  • الطراز tonal افتراضيًّا. الإجراء الذي لم تضبط له variant يُرسم بالطراز tonal. العلامتان تحددان الموضع فقط، لا المظهر أبدًا.
  • مظهر آخر. اضبط variant لزر واحد لتعطي ذلك الإجراء مظهرًا آخر، مثل «إلغاء» من الطراز text بجانب «حذف» من الطراز filled. واضبط destructive على الإجراء الذي يحذف شيئًا أو يتخلص منه، مثل ذلك «حذف»: يحتفظ بطرازه ويأخذ لون الخطأ. مربع الحوار لا يستبدل أبدًا طرازًا ضبطته أنت، ولا destructive. والمنتج الذي يريد مظهرًا آخر لكل مربعات الحوار يضبطه مرة واحدة في طبقته (actions.variant، و actions.arrangement لإجراءات بعروضها الخاصة محاذاة إلى النهاية)، لا على كل زر.
  • واحد على الأقل. مربع الحوار الذي لا إجراء له يُفتح مع ذلك، وتبلّغ عنه بنيات التنقيح (debug) خطأً. وحيث تستطيع واجهة المنصة البرمجية أن تفرض وجود الإجراءات، تقول المواصفة إنها تفرضه: الباني actions: في SwiftUI، والمعامل actions في Compose، والقائمة actions في Flutter، والخاصية actions في React Native. ومن الأربعة، لا يُفرض اليوم إلا ما في SwiftUI؛ أما البقية فما زالت تقبل مربع حوار بلا إجراءات إلى أن تلحق. وتبلّغ بنيات التنقيح كذلك عن مربع حوار إجراءاته كلها معطَّلة والخاصية dismissible قيمتها false، لأن ذلك لا يترك مخرجًا.

لا زر إغلاق

نموذج فيه «إلغاء» بجانب «حفظ»، بعرض small

تعديل الاسم الظاهرإلغاءحفظ

لا يرسم مربع الحوار زر إغلاق في ترويسته. فهو يطلب قرارًا، وطريقه للخروج من دون قرار هو إجراء التراجع فيه، وما دامت قيمة dismissible هي true، المفتاح Escape والرجوع في المنصة والضغط على الحجاب.

  • نموذج أو اختيار. ضع إجراء تراجع، مثل «إلغاء»، بجانب إجراء التأكيد، كما في المثال أعلاه.
  • شيء للقراءة. أعطِ مربع الحوار إجراءً واحدًا عنوانه «إغلاق»، أو «إلغاء» حين يكون هناك ما يُلغى، وعلِّمه بالعلامة dismissive. وإن كان المحتوى لا يحتاج إلى جواب بل إلى طريق للخروج فقط، فاعرضه بدل ذلك في لوحة جانبية، ولها زر إغلاق، أو في لوحة سفلية يستطيع الشخص إبعادها بالسحب.
  • مربع حوار طويل. ما دام محتواه يتمرّر، تبقى الإجراءات في مكانها أسفله، فيبقى طريق الخروج ظاهرًا.
  • على الويب. مربع الحوار الذي يُعطى closable أو close-label لا يرسم شيئًا لأجلهما، وتُبلغ إصدارات التصحيح بأن مربع الحوار ليس له زر إغلاق.

العرض

افتراضيًا (width هي content)، يكون مربع الحوار بعرض أعرض أجزائه، بين 280 و560. فيتبع عرضه نصّه، وهذا يناسب التأكيد. عيّن width ليأخذ عرضًا ثابتًا بدلًا من ذلك، فيحفظ النموذج عرضه مهما تغيّرت حقوله ورسائله:

width العرض لأجل
content أعرض أجزائه، من 280 إلى 560 تأكيد أو سؤال قصير (الافتراضي)
small 400 حقل أو حقلين
medium 560 نموذج، قائمة خيارات
large 800 محتوى يحتاج إلى مساحة، كجدول أو معاينة

لا يزيد العرض الثابت أبدًا على عرض النافذة ناقص 16 من كل جانب، فمربع الحوار large في نافذة عرضها 640 يكون عرضه 608. وفي نافذة مضغوطة (أضيق من 600) يوضع كل عرض كما يوضع الافتراضي: لوحةً سفلية في الأسفل، بعرض النافذة حتى 640. وتغيير width ومربع الحوار مفتوح يُرسم في الحال. ويغيّر المنتج الأعراض الثلاثة لكل مربعات حواره في طبقته (width.small وwidth.medium وwidth.large).

في نافذة مضغوطة

حين يكون عرض النافذة أقل من 600، يأخذ مربع الحوار مظهر اللوحة السفلية بدل أن يقف في الوسط. هذه قاعدة Tanrei لكل مربع حوار ولكل لوحة منبثقة في نافذة مضغوطة. فيستقر مربع الحوار عند الحافة السفلية للنافذة (أو فوق لوحة المفاتيح الظاهرة على الشاشة)، بعرض النافذة حتى comp.bottom-sheet.container.max-width (640)، بلون اللوحة السفلية وارتفاعها، وبزاويتيها العلويتين المدوَّرتين وزاويتين سفليتين قائمتين. ويمتد لونه تحت الهامش السفلي للنظام، وتحتفظ أجزاؤه بحشوة مربع الحوار نفسه وأنماط نصوصه. العرض وحده هو المعتبر، فالهاتف في الوضع الأفقي ليس من الفئة المضغوطة ومربع حواره موسَّط.

وهو هناك يبدو لوحة سفلية، لكنه ما زال مربع حوار. له ارتفاع واحد، ومخارجه إجراءاته والمفتاح Escape والرجوع والحجاب، لذلك ليس له مقبض سحب، ولا يتبع السحب، ولا يُغلق بحركة سحب سريعة (swipe). دوره وتركيزه وخمول ما حوله وحجابه وإغلاقه هي ما لأي مربع حوار. وحين تعبر النافذة العرض 600 ومربع الحوار مفتوح، ينتقل إلى الموضع الآخر فورًا، بلا حركة، ويبقى التركيز حيث هو.

النظام الذي يُبقي مربعات الحوار موسَّطة في كل مكان يضبط عرض الفئة المضغوطة على 0. الإعدادان الجاهزان m3-like و cupertino يفعلان ذلك، لأن الأصلين كليهما يوسّطان مربعات الحوار. ويحتفظان كذلك بإجراءات الطراز text التي في أصليهما. ولإبقاء مربع حوار واحد موسَّطًا في كل نافذة، اضبط compactPresentation فيه على own (compact-presentation="own" على الويب).

الفتح والتركيز والإغلاق

مربع الحوار تراكب نمطي (modal). وهو ينقل التركيز إلى داخله، ويُبقيه فيه، ويعيده حين يُغلق.

  • الفتح. حين تصير قيمة open هي true، يُعرض مربع الحوار فوق كل ما في نافذته، موسَّطًا (أو في أسفل نافذة مضغوطة)، فوق حجاب (scrim) يعتّم النافذة كلها. كل ما هو خارجه يصير خاملًا (inert): لا يمكن التركيز عليه ولا ضغطه، ولا تستطيع التقنيات المساعدة الوصول إليه. والمحتوى الذي خلفه يتوقف عن التمرير.
  • دخول التركيز. ينتقل التركيز إلى العنصر الذي علّمته، أو إلى مربع الحوار نفسه، فتقرأ التقنيات المساعدة العنوان الرئيسي أولًا ويصل الضغط التالي على المفتاح Tab إلى أول عنصر يقبل التركيز. ينتقل التركيز حين يُفتح مربع الحوار، لا حين تنتهي حركته.
  • حصر التركيز. المفتاح Tab من آخر عنصر يقبل التركيز يذهب إلى الأول، و Shift+Tab من الأول يذهب إلى الأخير. وتنقّل قارئ الشاشة لا يغادر مربع الحوار.
  • الإغلاق. ما دامت قيمة dismissible هي true، فإن المفتاح Escape، والرجوع في المنصة (زر الرجوع أو إيماءته في Android، وإيماءة الخروج لإمكانية الوصول في iOS)، والضغط على الحجاب تُصدر الحدث dismiss مرة واحدة، وأنت تغلق مربع الحوار. على الويب يغلق مربع الحوار نفسه ما لم تُلغِ الحدث cancel، كما يفعل <dialog> الأصلي. وما دامت قيمة dismissible هي false فهذه لا تفعل شيئًا، ولا يغلق مربع الحوار إلا إجراء. ومربع الحوار في أسفل نافذة مضغوطة لا يُغلق بحركة سحب سريعة. ويحمل dismiss سببه: escape أو back أو scrim.
  • عودة التركيز. حين يُغلق، يعود التركيز إلى العنصر الذي كان عليه حين فُتح مربع الحوار. ويُستعاد المحتوى الذي خلفه كما كان.
  • التكدّس. مربع الحوار المفتوح من مربع حوار يتكدّس فوقه بحجاب خاص به ويجعل الأول خاملًا هو الآخر. المفتاح Escape يغلق الأعلى فقط، وإغلاقه يعيد التركيز إلى العنصر الذي كان عليه داخل مربع الحوار الأول.
  • الإعلان. يُعلَن مربع الحوار مربعَ حوار، اسمه عنوانه الرئيسي ووصفه نصه المساعد. ومع alert هو مربع حوار تنبيهي، وتقرأ التقنيات المساعدة العنوان الرئيسي والنص المساعد فور فتحه.
  • الحركة. يظهر الحجاب تدريجيًّا. الحاوية الموسَّطة تظهر تدريجيًّا وهي تكبر من حجم أصغر قليلًا من حجمها. وفي النافذة المضغوطة تصعد الحاوية من تحت الحافة السفلية بحركة اللوحة السفلية، من دون أن تكبر، وتنزل عائدة حين تُغلق. وحيث تُقلَّل الحركة تظهر تدريجيًّا من دون أن تكبر أو تتحرك؛ وعلى Android، الذي يزيل إعدادُه كل حركة، تظهر فورًا.

كيف تعرضه كل منصة: <dialog> أصلي يُفتح بالدالة showModal() على الويب، و .fullScreenCover بخلفية شفافة على SwiftUI في iOS (وعلى macOS، الذي ليس فيه غطاء بملء الشاشة، تراكب على العرض الذي أُلحق به المُعدِّل)، ونافذة Dialog على Compose، ومسار نمطي على Flutter، و Modal شفاف على React Native. كل منها يوقف تعتيمه وانتقاله الخاصين، فترسم كل المنصات الحجاب نفسه والحركة نفسها من الرموز (tokens).

شريط الإشعار الذي لم يُعرض بعد ينتظر ما دام مربع حوار من Tanrei مفتوحًا، والشريط المعروض أصلًا يوقف مهلته مؤقتًا. والتلميح داخل مربع الحوار يُرسم فوقه.

افعل

  • اكتب العنوان الرئيسي على أنه السؤال أو العبارة نفسها: «تجاهل المسودة؟»، «حذف المشروع؟». تجنّب «هل أنت متأكد؟».
  • سمِّ الإجراءات بما تفعله: «تجاهل»، «حذف»، «تسجيل الخروج». تجنّب «نعم» و«لا».
  • علِّم كل إجراء بالعلامة confirming أو dismissive، ودع مربع الحوار يضعها في مواضعها.
  • أعطِ كل مربع حوار جوابًا. مربع الحوار الذي يُعلِم فقط هو شريط إشعار أو سطر في الصفحة.
  • أعطِ مربع الحوار الذي يعرض شيئًا للقراءة إجراءً عنوانه «إغلاق» أو «إلغاء»، أو اعرض ذلك المحتوى في لوحة جانبية.
  • في تأكيد إجراء لا رجعة فيه، علِّم «إلغاء» للتركيز الأولي. في نموذج، علِّم الحقل الأول. وفي مربع حوار يجب أن يُقرأ نصه أولًا، لا تعلِّم شيئًا.
  • اضبط alert للمقاطعات العاجلة فقط: تأكيد إجراء لا رجعة فيه، انقطاع الاتصال.

لا تفعل

  • لا تفتح مربع حوار لم يطلبه الشخص، إلا إذا كانت المهمة حقًّا لا تستطيع أن تستمر.
  • لا تُضف زر إغلاق من عندك إلى الترويسة أو المحتوى: ليس لمربع الحوار زر إغلاق، وطريقه للخروج إجراء تراجع.
  • لا تضبط dismissible على false إلا إذا وجب على الشخص أن يختار أحد الإجراءات. مستخدمو أجهزة التبديل (switch) وقارئات الشاشة يعتمدون على المفتاح Escape وعلى الرجوع. ومربع الحوار الذي لا يمكن إغلاقه يحتاج مع ذلك إلى مخرج مرئي، مثل «إلغاء».
  • لا تضع في مربع الحوار أكثر من بضعة تفاصيل. ضع النموذج الطويل في صفحة.
  • لا ترتّب الإجراءات بيدك لتضع إجراء التأكيد أخيرًا، ولا تضبط طرازًا على كل زر لتحصل على الطراز tonal: مربع الحوار يفعل الأمرين.
  • لا تغيّر مظهر الأزرار بما يتجاوز variant، ولا تضف عناصر تحكم إلى إطار مربع الحوار.
  • لا تعامل مربع الحوار في أسفل نافذة مضغوطة على أنه لوحة سفلية. استخدم اللوحة السفلية حين ينبغي أن يستطيع الشخص إبعادها بحركة سحب سريعة.

إمكانية الوصول

الدور
dialog
التسمية
The headline is the accessible name and the supporting text the description. The role is dialog, or alertdialog while alert is true (platforms without that distinction read the headline and supporting text on opening). Everything outside the dialog is hidden from assistive technology while it is open. Each action is named by its own label; confirming and dismissive are not announced, and the actions are read in the order they are placed (BGR-15).
الحد الأدنى للهدف
تستوفي مساحة اللمس الحد الأدنى لحجم الهدف حتى لو كان الحجم المرئي أصغر. minimum 32px · pointer 44px · spacing 8px · touch 48px

لوحة المفاتيح

المفاتيح الإجراء
Tab / Shift+Tab Move focus through the dialog's focusable elements, wrapping from the last to the first and back; focus never leaves the dialog.
Escape Dismisses the dialog (emits dismiss with the reason escape) while dismissible is true; with nested dialogs, only the topmost.
Enter / Space Activate the focused button (an action: its click). The dialog adds no default action to Enter.

ملاحظات

  • The dialog has no target of its own; its actions are Tanrei Buttons, which meet the minimum target themselves (BTN-5). comp.dialog.actions.gap keeps each action's whole target in a column in the default system.
  • Initial focus: mark the least destructive action (Cancel) in a destructive confirmation, the first field in a form, and nothing in a dialog whose text must be read first. Without a mark, focus goes to the container (DLG-2).
  • Every dialog offers an action (DLG-19). Use dismissible false sparingly: a person using a switch or a screen reader relies on Escape and back. A dialog that must not be dismissed still needs a visible way out, a dismissive action.
  • A dialog has no close button (DLG-28). Its visible way out is a dismissive action, which a person meets in the reading order of the actions; Escape, back and the scrim dismiss it as well while it is dismissible.
  • Placement is by task, not by code order: in a row the dismissive action is at the start and the confirming one at the end; in a column the confirming one is on top. Focus and screen readers follow the placed order, so a sighted and a screen-reader user meet the actions in the same order.
  • In a compact window the dialog takes the bottom sheet's appearance (DLG-17), as every dialog and every popover of Tanrei does, and is still a modal dialog: the same role, focus, inertness and dismissal, and no drag. A person who expects a bottom sheet's swipe to close it uses Escape, back or the scrim, or a dismissive action. compactPresentation own keeps one dialog centred in every window.
  • Text size: at 200% the dialog grows up to the window; its actions stack when a label no longer fits its share (DLG-10), its supporting text and content scroll (DLG-11), and on a very short window everything scrolls together (DLG-12).
  • Web: a native `<dialog>` opened with `showModal()` inside the shadow root, so the rest of the page is inert and the dialog is in the top layer. `::backdrop` draws the scrim. The `autofocus` attribute on a slotted element marks initial focus. `form method="dialog"` closes it natively, and the native `close` event follows every close.
  • Where a platform presents the dialog in a window of its own (Compose's `Dialog`, React Native's `Modal`), the platform's own window dim is switched off and Tanrei draws the scrim, so every platform draws the same color.

مولَّد من specs/dialog.spec.json, tokens/sys/measure.tokens.json

ما يتكفل به المكوّن

  • يُعرَّف مربع الحوار للتقنيات المساعدة مربعَ حوار نمطيًّا، أو مربع حوار تنبيهيًّا ما دامت قيمة alert هي true، اسمه عنوانه الرئيسي ووصفه نصه المساعد. ومع alert يُقرأ العنوان الرئيسي والنص المساعد فور فتحه.
  • كل ما هو خارجه خامل ومخفي عن التقنيات المساعدة ما دام مفتوحًا.
  • الفتح ينقل التركيز إلى العنصر الذي علّمته، أو إلى مربع الحوار نفسه. المفتاحان Tab و Shift+Tab يدوران داخله. والإغلاق يعيد التركيز إلى العنصر الذي كان عليه حين فُتح.
  • المفتاح Escape والرجوع في المنصة والضغط على الحجاب تُصدر الحدث dismiss ما دامت قيمة dismissible هي true. ومع مربعات الحوار المتكدّسة، لا يُغلق إلا الأعلى.
  • المفتاحان Enter و Space يفعّلان الزر الذي عليه التركيز. مربع الحوار لا يضيف إجراءً افتراضيًّا إلى المفتاح Enter.
  • الإجراءات أزرار Tanrei، وهي تستوفي الحد الأدنى للهدف بنفسها. والمسافة 16 بين الإجراءات المرصوصة تُبقي كل هدف كاملًا.
  • التركيز وقارئات الشاشة تلقى الإجراءات بترتيب مواضعها، وهو الترتيب نفسه الذي يراه الشخص المبصر.
  • في أسفل نافذة مضغوطة يحتفظ مربع الحوار بالدور نفسه والتركيز نفسه والخمول نفسه والإغلاق نفسه. والشخص الذي يتوقع حركة السحب السريعة التي للوحة السفلية يغلقه بالمفتاح Escape أو بالرجوع أو بالحجاب أو بإجراء تراجع.
  • عند حجم نص 200% يكبر مربع الحوار حتى حدود النافذة، وتُرصّ إجراءاته حين لا تعود تسمية تتسع في حصتها، ويُمرَّر النص المساعد والمحتوى، وفي النافذة القصيرة جدًّا يُمرَّر كل شيء معًا، فلا يُقتطع شيء ولا يتعذر الوصول إلى شيء.
  • مربع الحوار الذي لا عنوان رئيسي له أو لا إجراء له يُفتح مع ذلك، وتبلّغ بنيات التنقيح عن الاسم الناقص أو الجواب الناقص. وتبلّغ كذلك عن مربع حوار إجراءاته كلها معطَّلة والخاصية dismissible قيمتها false.

فجوات معروفة في المنصات:

  • على SwiftUI و Compose و Flutter و React Native: الخاصية dividerVisibility تنتظر أن تلحق تلك المنصات. ففواصلها تظهر فقط ما دام شيء خلفها قد خرج بالتمرير عن مجال الرؤية، كما تفعل scroll.
  • SwiftUI وCompose وFlutter وReact Native: width والسبب الذي يحمله dismiss تنتظر لحاق هذه المنصات.
  • الويب: لا يستطيع Firefox أن يُبقي مربع حوار يُغلق في الطبقة العليا (top layer) وهو يختفي تدريجيًّا، ولذلك يختفي مربع الحوار هناك فورًا حين يُغلق، من دون اختفاء تدريجي.
  • الويب: الابتعاد عن لوحة المفاتيح الظاهرة على الشاشة لم يُفحص بعد على جهاز فيه لوحة كهذه.
  • ليس في SwiftUI دور لمربع الحوار ولا لمربع الحوار التنبيهي. تحمل الحاوية الصفة النمطية (modal trait)، واسمها العنوان الرئيسي ووصفها النص المساعد في صورة تلميح لها (hint)، والعنوان الرئيسي يحمل صفة عنوان القسم (heading trait). ومع alert يقرأ VoiceOver العنوان الرئيسي والنص المساعد لحظة فتح مربع الحوار.
  • ليس في SwiftUI على macOS غطاء بملء الشاشة: يغطي مربع الحوار العرض الذي أُلحق به المُعدِّل فقط، فألحقه بجذر النافذة. ذلك العرض يُعطَّل ما دام مربع الحوار مفتوحًا، وترسم عناصر التحكم فيه حالة تعطيلها تحت الحجاب.
  • لا يستطيع SwiftUI أن ينقل تركيز لوحة المفاتيح إلى مربع الحوار نفسه. من دون علامة يبدأ VoiceOver عند العنوان الرئيسي. وعند الإغلاق يعيد iOS تركيز VoiceOver إلى العنصر الذي عرض مربع الحوار، لكن تركيز لوحة المفاتيح لا يُعاد لا على iOS ولا على macOS.
  • ليس في Compose دور لمربع الحوار التنبيهي. النافذة مربع حوار اسمه العنوان الرئيسي، ومربع الحوار الذي يحمل alert يُعلِن عنوانه الرئيسي ونصه المساعد لحظة فتحه، عبر واجهة برمجية من Android جعلها Android 16 مهمَلة.
  • على Compose: نافذة مربع الحوار الحقيقية، ودوران المفتاح Tab، وعودة التركيز إلى العنصر الذي فتحه، وإيماءة الرجوع، و TalkBack اختُبرت على Robolectric فقط، لا على جهاز بعد.
  • على Flutter: حين لا يترك حتى العنوان الرئيسي والإجراءات حيّزًا ويُمرَّر مربع الحوار كله معًا، يُتخذ قرار ذلك التحول بعد عملية تخطيط ويظهر بعد إطار واحد. ليس في Flutter خاصية وصف، فالنص المساعد هو تلميح الحاوية (hint). وليس للحجاب تسمية لإمكانية الوصول ما لم تمرّر barrierLabel.
  • لا يستطيع React Native أن يعرف أي عنصر كان عليه التركيز، فالإغلاق لا يعيد التركيز إلى العنصر الذي فتح مربع الحوار: يذهب التركيز حيث يضعه iOS و Android لنافذة أُغلقت. ومن دون initialFocusRef لا يتحرك إلا قارئ الشاشة (إلى العنوان الرئيسي)؛ أما تركيز لوحة المفاتيح الفعلية فيُترك حيث تضعه المنصة في النافذة الجديدة.
  • على React Native: دوران المفتاح Tab داخل مربع الحوار أو عدمه أمر تقرره المنصة. وهذا، وتركيز قارئ الشاشة، والرجوع، وإيماءة الخروج في iOS، ومربعات الحوار المتداخلة لم تُفحص بعد على جهاز.
  • على React Native: لا يبقى مربع الحوار داخل المنطقة الآمنة إلا حين تمرّر هوامش النافذة (insets) إلى TanreiProvider.

ما عليك فعله

  • أعطِ كل مربع حوار عنوانًا رئيسيًّا. هو اسم مربع الحوار.
  • أعطِ كل مربع حوار إجراءً واحدًا على الأقل، وعلِّم كل إجراء بالعلامة confirming أو dismissive. وعلى المنصات التي ما زالت تقدّم المراجعة السابقة، ضع إجراء التأكيد أخيرًا بنفسك.
  • أغلق مربع الحوار بنفسك: من معالج كل إجراء، واستجابةً للحدث dismiss (أي onRequestClose أو onDismissRequest أو الربط أو المسار، بحسب المنصة).
  • علِّم التركيز الأولي حيث يفيد: «إلغاء» في تأكيد إجراء لا رجعة فيه، والحقل الأول في نموذج.
  • أعطِ كل مربع حوار مخرجًا مرئيًّا، إجراء تراجع مثل «إلغاء»، حتى حين تكون قيمة dismissible هي false.
  • على React Native، مرِّر initialFocusRef. وبعد أن يُغلق مربع الحوار، أعد التركيز إلى العنصر الذي فتحه بنفسك إن لم تفعل المنصة ذلك.
  • على SwiftUI في macOS، ألحق .tanreiDialog بالعرض الجذر للنافذة، ليغطي الحجاب النافذة كلها.
  • على الويب، إن كان محتواك يثبّت صفًّا في أسفله، فاضبط scroll-padding-block-end على ::part(scroll) بارتفاع ذلك الصف، كي لا يختبئ العنصر الذي عليه التركيز تحته أبدًا.