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

شريط التنقل

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

content_copyعرض بتنسيق Markdown
الفئة
التنقل
الرموز
comp.navigation-bar
المستوى
أساسي

نظرة عامة

Web
tanrei-navigation-bar
schedule
SwiftUI
TanreiNavigationBar
schedule
Jetpack Compose
TanreiNavigationBar
schedule
Flutter
TanreiNavigationBar
schedule
React Native
NavigationBar
schedule
أشكال غير متوفرة في
cupertino: filled · m3-expressive-like: filled · m3-like: filled

مولَّد من docs/status/status.json, specs/navigation-bar.spec.json

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

تصف هذه الصفحة شريط التنقل كما هو في المواصفة الآن، على مثال علامات التبويب. المؤشر النشط هو الحصة المتساوية الكاملة للوجهة الحالية من الشريط، مُزاحةً إلى الداخل عن حواف الشريط، لا حبّة تلتف حول الأيقونة والتسمية. ينزلق إلى الوجهة الجديدة بحجمه الكامل حين يتغيّر التحديد، والسحب على امتداد الشريط يحمله مع مؤشر الإدخال. ويُرسم التحويم والضغط والتركيز في ذلك الصندوق نفسه. تختار الخاصية variant بين tonal (الافتراضي) و filled. وكل الخصائص والأحداث الأخرى هي نفسها في المراجعتين. المنصات الموسومة أعلاه بأنها متأخرة تقدّم المراجعة الأولى إلى أن تلحق: بلا variant، وبلا سحب، وبمؤشر خلف الأيقونة وحدها يكبر من مركزه، وتعرض عيّناتها تلك المراجعة. العروض الحيّة في هذه الصفحة هي مكوّن الويب كما يُقدَّم اليوم.

أربع وجهات، على إحداها عدد

الطراز filled: اضغط واسحب على امتداد الشريط لتحمل المؤشر

شريط من الروابط: الصفحة تسمّي الوجهة الحالية بعنوانها، و«المساعدة» تفتح تبويبًا جديدًا

الاستخدام

لشريط التنقل نوع واحد من العناصر، هو destination: أيقونة، وتسمية تحتها، وشارة اختيارية على الأيقونة. تأخذ كل منصة الوجهات كما تأخذ أي عناصر فرعية متكررة: عناصر فرعية <tanrei-navigation-bar-item> على الويب، وعروض TanreiNavigationBarItem في باني العرض على SwiftUI، واستدعاءات TanreiNavigationBarItem في دالة المحتوى على Compose، وكائنات TanreiNavigationDestination في القائمة destinations: على Flutter، ومصفوفة destinations من كائنات بسيطة على React Native.

التحديد ملك للشريط، بقيمة الوجهة. تسمّي الخاصية value الوجهة الحالية، ويحمل الحدث change الوجهة التي اختارها الشخص، ويصدر الحدث reselect حين يختار الوجهة الحالية مرة أخرى، لتعيد تلك الشاشة إلى أعلاها. والقيمة الفارغة لا تحدّد شيئًا، لشاشة ليست من وجهات الشريط. يكتب SwiftUI القيمة الجديدة في الربط selection. وتحتفظ منصتان بأسلوبهما، كما تفعل أشرطة التنقل الخاصة بهما. ليس في Compose قيمة على مستوى الشريط: تعلّم العنصر الحالي بالخاصية selected، وتقوم onClick فيه مقام change و reselect معًا. ويحدّد Flutter بالفهرس: selectedIndex، و onDestinationSelected التي يستدعيها للوجهة الحالية أيضًا.

تضبط الخاصية variant مدى قوة تمييز الوجهة الحالية: القيمة tonal (الافتراضية) تملأ مؤشرها بلطف، والقيمة filled تملؤه بقوة، مع الأيقونة والتسمية معكوستَي اللون عليه. وهي على الويب اليوم.

الوجهة التي لها href رابط: هي رابط المنصة نفسه، تُرسم كما تُرسم الوجهة التي هي زر تمامًا. يفعّلها المفتاح Enter ولا يفعّلها المفتاح Space. ولا يحدّد الشريط الرابط عند الضغط: يُصدر الحدث change حاملًا قيمة الوجهة وقيمة href فيها، ثم يُتبع الرابط. والصفحة التي تصل هي التي تسمّي الوجهة الحالية بالخاصية value، كما في درج التنقل، والرابط الذي بلا value يأخذ قيمة href قيمةً له. والخاصيتان target و targetLabel هما خاصيتا الزر. الويب ينفّذ الوجهات التي هي روابط؛ أما SwiftUI و Compose و Flutter و React Native فليست فيها بعد.

قيمة icon في الوجهة هي اسم رسم في خط الأيقونات الخاص بنظام التصميم. يرسمه الشريط مفرَّغًا ما دامت الوجهة غير محدَّدة ومعبَّأً حين تكون محدَّدة. وترسم الخصائص badge و badgeCount و badgeLabel شارة من Tanrei على الأيقونة: نقطة حين لا يوجد عدد، والرقم في غير ذلك. يأخذها SwiftUI و Compose و Flutter قيمة واحدة من النوع TanreiNavigationBadge، ويأخذها React Native كائنًا badge.

الشريط منطقة من التخطيط، لا تراكب. يصل لونه إلى ما تحت مؤشر الشاشة الرئيسية أو شريط الإيماءات أسفله، وتقع وجهاته فوقهما وداخل الهوامش الجانبية (insets). على React Native تأتي الهوامش من الخصائص وحدها، بلا اعتماد على حزمة للمنطقة الآمنة: الخاصية insets على TanreiProvider، أو insets على الشريط نفسه، وجوانبها تستبدل جوانب المزوِّد.

<!-- العنصر المضيف مَعلم تنقل (navigation landmark) تسمّيه label. -->
<!-- variant="filled" يعكس ألوان الوجهة الحالية، لشريط فوق شاشة مزدحمة. والافتراضي tonal. -->
<tanrei-navigation-bar label="الرئيسي" value="inbox">
  <tanrei-navigation-bar-item value="inbox" label="الوارد" icon="inbox"
                              badge badge-count="3" badge-label="3 غير مقروءة"></tanrei-navigation-bar-item>
  <tanrei-navigation-bar-item value="chat" label="المحادثة" icon="chat"
                              badge badge-label="رسائل جديدة"></tanrei-navigation-bar-item>
  <tanrei-navigation-bar-item value="starred" label="المميَّزة" icon="star"></tanrei-navigation-bar-item>
  <tanrei-navigation-bar-item value="settings" label="الإعدادات" icon="settings" disabled></tanrei-navigation-bar-item>
</tanrei-navigation-bar>

<script type="module">
  import '@tanrei/web/components/navigation-bar';
  const bar = document.querySelector('tanrei-navigation-bar');
  // يضبط الشريط قيمته بنفسه قبل أن يُصدر change الذي يحملها.
  bar.addEventListener('change', (e) => showScreen(e.detail.value));
  bar.addEventListener('reselect', () => scrollTo({ top: 0 }));
</script>

<!-- شريط من الروابط: كل صفحة تضبط value على عنوانها هي. والرابط الذي بلا value يأخذ قيمة href. -->
<tanrei-navigation-bar label="الرئيسي" value="/search/">
  <tanrei-navigation-bar-item href="/" label="الرئيسية" icon="home"></tanrei-navigation-bar-item>
  <tanrei-navigation-bar-item href="/search/" label="البحث" icon="search"></tanrei-navigation-bar-item>
  <tanrei-navigation-bar-item href="/library/" label="المكتبة" icon="video_library"></tanrei-navigation-bar-item>
  <tanrei-navigation-bar-item href="https://example.com/help" label="المساعدة" icon="help"
                              target="new" target-label="يُفتح في تبويب جديد"></tanrei-navigation-bar-item>
</tanrei-navigation-bar>

<script type="module">
  // الموجِّه يتولى التنقل: ألغِ change، واذهب إلى detail.href، وقل أين صار المنتج الآن.
  bar.addEventListener('change', (e) => {
    if (!e.detail.href) return;
    e.preventDefault();
    router.push(e.detail.href);
    bar.value = e.detail.value;
  });
</script>
import { NavigationBar, NavigationBarItem } from '@tanrei/react';

// variant="filled" لشريط فوق شاشة مزدحمة؛ والافتراضي tonal.
<NavigationBar label="الرئيسي" value={screen} onChange={(e) => setScreen(e.detail.value)} onReselect={scrollToTop}>
  <NavigationBarItem value="inbox" label="الوارد" icon="inbox" badge badgeCount={3} badgeLabel="3 غير مقروءة" />
  <NavigationBarItem value="chat" label="المحادثة" icon="chat" />
  <NavigationBarItem value="starred" label="المميَّزة" icon="star" />
</NavigationBar>
import Tanrei

// شريط مستقل في الهامش الآمن السفلي. اختيار وجهة يكتب في الربط (change).
ScreenView(screen)
    .safeAreaInset(edge: .bottom, spacing: 0) {
        TanreiNavigationBar(selection: $screen, accessibilityLabel: "الرئيسي", onReselect: { _ in scrollToTop() }) {
            TanreiNavigationBarItem("الوارد", icon: "inbox", value: "inbox",
                                    badge: TanreiNavigationBadge(count: 3, label: "3 غير مقروءة"))
            TanreiNavigationBarItem("المحادثة", icon: "chat", value: "chat")
            TanreiNavigationBarItem("المميَّزة", icon: "star", value: "starred")
            // disabled مُعدِّل من SwiftUI نفسه.
            TanreiNavigationBarItem("الإعدادات", icon: "settings", value: "settings").disabled(true)
        }
    }
import io.github.chnotchy.tanrei.components.TanreiNavigationBadge
import io.github.chnotchy.tanrei.components.TanreiNavigationBar
import io.github.chnotchy.tanrei.components.TanreiNavigationBarItem

// مثل NavigationBar في Material: لا قيمة على مستوى الشريط. تعلّم العنصر الحالي بـ selected،
// و onClick واحدة هي change و reselect معًا.
var screen by rememberSaveable { mutableStateOf("inbox") }
TanreiNavigationBar(contentDescription = "الرئيسي") {
    TanreiNavigationBarItem(
        selected = screen == "inbox",
        onClick = { if (screen == "inbox") { scrollToTop() } else { screen = "inbox" } },
        icon = "inbox",
        label = "الوارد",
        badge = TanreiNavigationBadge(count = 3, contentDescription = "3 غير مقروءة"),
    )
    TanreiNavigationBarItem(selected = screen == "chat", onClick = { screen = "chat" }, icon = "chat", label = "المحادثة")
    // enabled عكس disabled في المواصفة.
    TanreiNavigationBarItem(selected = screen == "settings", onClick = { screen = "settings" }, icon = "settings", label = "الإعدادات", enabled = false)
}
import 'package:tanrei/tanrei.dart';

// مثل NavigationBar: قيمة الوجهة فهرسها. لا يحتفظ الشريط بتحديد خاص به.
TanreiNavigationBar(
  selectedIndex: current, // القيمة null لا تحدّد شيئًا
  onDestinationSelected: (int index) {
    if (index == current) {
      scrollToTop(); // reselect
    } else {
      setState(() => current = index); // change
    }
  },
  semanticLabel: 'الرئيسي',
  destinations: const <TanreiNavigationDestination>[
    TanreiNavigationDestination(icon: 'inbox', label: 'الوارد', badge: TanreiNavigationBadge(count: 3, semanticLabel: '3 غير مقروءة')),
    TanreiNavigationDestination(icon: 'chat', label: 'المحادثة'),
    TanreiNavigationDestination(icon: 'settings', label: 'الإعدادات', enabled: false),
  ],
),
import { useState } from 'react';
import { NavigationBar } from '@tanrei/react-native';

const [screen, setScreen] = useState('inbox');

// متحكَّم به: حرِّك value من onValueChange.
<NavigationBar
  accessibilityLabel="الرئيسي"
  value={screen}
  onValueChange={setScreen}
  onReselect={scrollToTop}
  destinations={[
    { value: 'inbox', label: 'الوارد', icon: 'inbox', badge: { count: 3, label: '3 غير مقروءة' } },
    { value: 'chat', label: 'المحادثة', icon: 'chat', badge: { label: 'رسائل جديدة' } },
    { value: 'settings', label: 'الإعدادات', icon: 'settings', disabled: true },
  ]}
/>

المواصفات

تسمية الوجهة المحدَّدة وحدها، وبلا تسميات

البنية

الجزء الوصف
1 container The bar along the bottom edge, and the ground the active indicator moves on: one constant color, whichever destination is selected. Its color reaches under the system inset below it (home indicator, gesture bar).
2 outline The bar's top edge, which sets it apart from the content above.
3 item One destination (an item of the destination kind): an equal share of the bar's width, less the bar's inset at its two ends, and its full height. The whole cell is the hit area.
4 item.container The destination's box: its cell, held in from the bar's top and bottom edges by the bar's inset. It is as wide as the destination's share of the bar, not as wide as its icon and label, so every destination of a bar has the same box. It carries hover, focus and press for every destination, which are therefore drawn where the active indicator is or would be, and it is filled as the active indicator while the destination is selected. When the selection changes the filled box slides from the previous destination to the new one, and it follows a finger dragged across the bar.
5 item.icon-indicator The box behind the icon alone, the icon centered in it. The default system leaves it transparent; a system that marks the selection behind the glyph only (Material's navigation bar) fills it and leaves the item container clear. It grows in from its center rather than sliding.
6 item.icon The destination's glyph (its icon). Outlined when unselected, filled when selected.
7 item.label · اختياري The destination's name under the icon indicator, inside the item container, on one line. Always its accessible name, drawn or not.
8 item.covered-content A second, decorative copy of the icon and label in the covered colors, clipped to the active indicator wherever it is drawn, so that the content inverts where the indicator covers it and the inversion travels with the indicator, in a slide and under a finger alike. Hidden from assistive technology.
9 item.badge · اختياري A Tanrei badge anchored to the icon, drawn from the destination's badge, badgeCount and badgeLabel: a dot or a count (the badge component). It is drawn above both copies of the icon.
10 focus-ring Gap-then-ring indicator around the item container, concentric with it and in its shape, held inside the bar, shown for keyboard focus.

الخصائص

الخاصية النوع القيمة الافتراضية الوصف
value string "" The value of the selected destination (selection is the bar's, by a destination's value). Empty selects none (the current screen is not one of the bar's destinations).
variant tonal | filled "tonal" How the active indicator is filled. Tonal: a soft container with the content in that container's own color. Filled: the strong color, with the content inverted on it, for a bar that must stand out from a busy screen. Tonal destinations are drawn from the unselected and selected conditions, filled ones from filled-unselected and filled-selected. The indicator's size and place are the same in both.
labelVisibility always | selected | never "always" Which destinations draw their label: all of them, only the selected one, or none. A label that is not drawn is still the destination's accessible name, and no item container changes size with it.
label string "" Names the bar itself for assistive technology ("Main"); on the web it names the navigation landmark.

الأحداث

الحدث الوصف
change The user activated a destination that is not the selected one, or released a drag of the active indicator on one; carries its value and, for a link, its href. For a link it comes before the link is followed, only for an activation that follows it in place (NVB-31), and a product that cancels it follows the link itself.
reselect The user activated the destination that is already selected (a product may scroll that screen to its top); carries its value and, for a link, its href, as change does. A drag that returns the indicator to it does not emit it.

العناصر

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

destination

One top-level destination of the bar: an icon, a label under the icon, and optionally a badge on the icon.

الخاصية النوع القيمة الافتراضية الوصف
value string "" What identifies the destination. The bar's value selects the destination whose value equals it; change and reselect carry it. Unique within the bar. Empty on a link, its href stands as its value.
href string "" The address the destination leads to. Not empty, the destination is a link and not a button (NVB-30): it is followed as the platform follows a link, and the bar does not select it by itself (NVB-31). Empty, the destination is a button and the product moves on change. As the Button's href (a control with a destination is a link).
target same | new "same" Where a link opens, as the Button's target. same: where the person is. new: a new context — a new tab or window on the web, outside the current screen elsewhere — which a bar's cell has no place to mark and says through targetLabel (NVB-32). A link that opens elsewhere is never selected by a press (NVB-31). Ignored without href.
targetLabel string "" What assistive technology reads after the link's name while target is new ("opens in a new tab"), as the Button's targetLabel: Tanrei ships no words of its own. Required while target is new; ignored otherwise.
label string "" Its name, drawn under the icon (see the bar's labelVisibility) and always its accessible name. Required.
icon string "" The name of its glyph in the system's icon set, drawn in the destination's own icon style: outlined while unselected, filled while selected. Required.
disabled boolean false Makes it unavailable. It keeps its place, draws its disabled look, cannot be activated and leaves the focus order.
badge boolean false Draws a Tanrei badge (the badge component) on the icon, from badgeCount and badgeLabel.
badgeCount number null The badge's count, as the badge component's count: null draws the dot, 0 draws nothing, above 0 the number (above 999 as 999+). Ignored while badge is false.
badgeLabel string "" The badge's label, as the badge component's: what assistive technology reads after the destination's name and state ("3 unread"). A dot needs one. Ignored while badge is false.
المواصفة Web SwiftUI Jetpack Compose Flutter React Native
العنصر tanrei-navigation-bar-item TanreiNavigationBarItem TanreiNavigationBarItem TanreiNavigationDestination NavigationBarDestination
value · الخاصية value value selected destinations value
href · الخاصية href destination href href href
target · الخاصية target target target target target
targetLabel · الخاصية targetLabel targetLabel targetLabel targetSemanticLabel targetLabel
label · الخاصية label title label label label
icon · الخاصية icon icon icon icon icon
disabled · الخاصية disabled disabled enabled enabled disabled
badge · الخاصية badge badge badge badge badge
badgeCount · الخاصية badgeCount count count count count
badgeLabel · الخاصية badgeLabel label contentDescription semanticLabel label
ملاحظات icon is the glyph's name, as <tanrei-tab>'s. badgeCount and badgeLabel are the badge-count and badge-label attributes; the element draws a <tanrei-badge> around the glyph from them. href is the spec's own prop: the cell is an <a href> in the item's shadow root. target is the target attribute with the values same and new (the HTML spellings _self and _blank are read as those two), targetLabel the target-label attribute, and rel (to which new adds noopener) and download are web-only and forwarded to the anchor, all as <tanrei-button>'s. Space is not handled on it. With no value attribute the item's value property reads its href attribute as written (not the resolved URL). TanreiNavigationBarItem(_ title: String, icon: String, value: Value, badge: TanreiNavigationBadge? = nil), where icon is the glyph's name and TanreiNavigationBadge(count: Int? = nil, label: String = "") is drawn with .tanreiBadge(…) on the glyph (no badge is nil). disabled follows .disabled(_:) on the item. destination: URL? = nil makes it a link (the link trait beside the item's selected trait), opened by the environment's openURL after the selection callback, which an app that routes addresses itself answers with its own OpenURLAction; the binding is not written for it. target: TanreiLinkTarget = .same and targetLabel (the accessibility hint), as the Button's. TanreiNavigationBarItem(selected, onClick, icon = "inbox", label = "…", enabled = true, badge = null) in the bar's RowScope content lambda. enabled is the inverse flag. badge: TanreiNavigationBadge? with TanreiNavigationBadge(count: Int? = null, contentDescription: String = ""); the item draws a TanreiBadge around its icon from it. icon is the glyph's name; the item draws it in the selection's icon style. href: String? = null makes it a link: opened through LocalUriHandler.current after onClick (an app that routes addresses itself provides its own UriHandler); the item keeps Role.Tab, which Compose cannot join with a link role. target: TanreiLinkTarget = TanreiLinkTarget.Same, and targetLabel is appended to the state description, as the Button's. TanreiNavigationDestination(icon:, label:, enabled: true, badge: null) in the bar's destinations: list; its value is its index there. icon is the glyph's name. badge: TanreiNavigationBadge(count:, semanticLabel:), drawn as a TanreiBadge around the icon. enabled: false disables it. href: Uri? = null makes it a link (Semantics(link: true) with the address as its linkUrl, beside the tab semantics), opened by the nearest TanreiLinkOpener after onDestinationSelected; target is a TanreiLinkTarget and targetSemanticLabel the semantics hint, as the Button's. An entry of destinations: NavigationBarDestination[]: { value, label, icon, disabled?, badge? }, where icon is the glyph's name and badge?: { count?: number | null; label?: string } (absent is no badge) is drawn as a Badge around the icon. href?: string makes it a link: the destination keeps the tab role (React Native gives an element one role), onValueChange receives the value and the href, and Linking.openURL(href) follows it unless the product handles it; target is "same" | "new" and targetLabel the accessibilityHint, as the Button's.

الحالات

السلوك

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

المعرّف المتطلب
NVB-1 WHEN the user activates a destination by pointer, touch, Enter or Space (Enter only for a destination that is a link, and for a link only an activation that NVB-31 counts) THE navigation bar SHALL emit change exactly once with the destination's value WHILE it is not the selected destination, and reselect exactly once with its value WHILE it is.
NVB-2 WHILE a destination's disabled is true THE navigation bar SHALL NOT emit change or reselect for it, SHALL NOT make it focusable and SHALL expose it as disabled to assistive technology.
NVB-3 THE navigation bar SHALL draw the destination whose value equals the bar's value from its variant's selected condition and every other destination from its variant's unselected condition: WHILE variant is tonal the selected condition is selected (comp.navigation-bar.selected.item.container.color, comp.navigation-bar.selected.item.icon-indicator.color, comp.navigation-bar.selected.item.icon.color, comp.navigation-bar.selected.item.label.color, comp.navigation-bar.selected.item.covered-icon.color, comp.navigation-bar.selected.item.covered-label.color) and the unselected condition is unselected; WHILE variant is filled they are filled-selected (comp.navigation-bar.filled-selected.item.container.color and the same parts) and filled-unselected. IF no destination's value equals a value that is not empty THE navigation bar SHALL draw every destination from its variant's unselected condition, and in debug builds report the value.
NVB-4 WHILE a destination is hovered, keyboard-focused or pressed THE navigation bar SHALL render its item container, icon indicator, icon, label, covered icon and label, and opacity from the component tokens of its condition in the highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state.
NVB-5 WHEN a destination receives keyboard focus THE navigation bar SHALL show the focus indicator (the ring or the glow, NVB-10) around its item container (NVB-12), concentric with it and in its shape, with its inner edge sys.focus.offset outside the container's edge, and SHALL hold it inside the bar: WHERE the space between the item container and the bar's edges (comp.navigation-bar.container.padding) is less than the indicator reaches, it SHALL be drawn further in by the difference, on every side alike, keeping the container's shape. THE indicator SHALL be drawn above the neighbouring destinations and the active indicator, without animation; focus gained by pointer or touch SHALL NOT show it.
NVB-6 THE navigation bar SHALL make each destination's hit area its whole cell, the bar's full height above the system inset and its share of the bar's width (NVB-11), the first and the last cell reaching the bar's start and end edges, and SHALL NOT let a cell's hit area fall below sys.target.touch in either direction; hover and press over any part of the cell SHALL answer on the item container. WHERE an equal share of the bar's width is narrower than sys.target.touch (more destinations than the width holds), the cells SHALL still share the bar's width and narrow below the target rather than make the bar wider than the space it is given.
NVB-7 WHILE a destination's disabled is true THE navigation bar SHALL render its item container, icon indicator, icon, label, covered icon and label, and opacity from the disabled component tokens of its condition.
NVB-8 WHEN a destination is pressed by pointer, touch, Enter or Space THE navigation bar SHALL show its pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner.
NVB-9 WHILE a destination is pressed THE navigation bar SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole item container at once; ripple grows the pressed state layer, clipped to the item container, from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole destination and draws no state layer; none draws no answer to the press. Under ripple and dim only the item container color stays with the state under the press; icon indicator, icon, label, covered icon and label, and opacity still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.navigation-bar.motion.feedback-duration with comp.navigation-bar.motion.state-easing, fades over comp.navigation-bar.motion.state-duration with comp.navigation-bar.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
NVB-10 THE navigation bar SHALL ease its item container and icon indicator corners 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) and show its keyboard focus as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README.
NVB-11 THE navigation bar SHALL span the full width of its parent, at least comp.navigation-bar.container.height high above the system inset, filled with comp.navigation-bar.container.color, raised by comp.navigation-bar.container.elevation and edged at the top by comp.navigation-bar.outline.width of comp.navigation-bar.outline.color, and SHALL divide its width, less comp.navigation-bar.container.padding at its start and at its end, into equal cells, one per destination, in reading order from the start edge; right-to-left layouts SHALL mirror the order. THE bar's container SHALL be the same whichever destination is selected, whatever state a destination is in and while the active indicator moves: only the active indicator marks the selection.
NVB-12 THE navigation bar SHALL draw each destination's item container as wide as its cell and as tall as the bar above the system inset less comp.navigation-bar.container.padding above and below, the same size for every destination of the bar whatever each holds, with its four corners rounded by comp.navigation-bar.item.container.shape, each at most half its height, and SHALL NOT size it to the destination's content. Inside it THE navigation bar SHALL lay out the destination as a column centered in the item container: the icon indicator, comp.navigation-bar.item.icon-indicator.width wide and comp.navigation-bar.item.icon-indicator.height high with corners comp.navigation-bar.item.icon-indicator.shape, its icon centered in it; and WHILE the label is drawn, the label below it, comp.navigation-bar.item.gap away, centered, on one line in comp.navigation-bar.item.label.typescale. THE column SHALL keep comp.navigation-bar.item.container.padding-block between itself and the item container's top and bottom edges, the bar growing in height to keep it (NVB-21), and a label wider than the item container less comp.navigation-bar.item.container.padding-inline on each side SHALL be truncated at its end with an ellipsis. THE navigation bar SHALL NOT draw hover, focus or press on the part of the cell outside the item container. Every part is laid out in every system; a transparent color hides a part without changing the layout.
NVB-13 WHILE labelVisibility is always THE navigation bar SHALL draw every destination's label; WHILE it is selected it SHALL draw only the selected destination's label; WHILE it is never it SHALL draw none. A destination that draws no label SHALL center its icon indicator in its item container, whose size does not change with the label, and every destination's label SHALL remain its accessible name.
NVB-14 THE navigation bar SHALL draw each icon at comp.navigation-bar.item.icon.size with the family and fill of its condition (selected for the selected destination, unselected for every other), sys.icon.weight, and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max.
NVB-15 WHEN the selected destination changes while no drag is followed (NVB-27) THE navigation bar SHALL slide the active indicator (the fill of the selected condition's item container) from the previous destination's item container to the new one's, at its full size throughout and drawn in the new destination's colors, every frame of the slide being the in-between look of NVB-27 (the indicator as a drag would draw it at that place, each icon and label in its covered color exactly where it is covered); SHALL grow the new destination's icon indicator in width from its center to its full width, at its full height throughout, and fade the previous one out; and SHALL change icon and label colors and icon fill; all over comp.navigation-bar.motion.selection-duration with comp.navigation-bar.motion.selection-easing. WHERE no destination was selected before, the active indicator SHALL fade in on the new item container at its full size, and WHERE none is selected after, it SHALL fade out, over the same time; it SHALL NOT grow or shrink in width to appear or disappear. WHILE labelVisibility is selected the labels SHALL appear and disappear over the same time, without resizing or moving any item container. WHERE motion is reduced it SHALL show the new selection at once. WHEN the bar first appears, or the selected destination changes size or place without a new selection (a font arriving, the text size changing, the bar resizing), THE navigation bar SHALL put the active indicator on it at once, without a slide.
NVB-16 WHILE a destination's badge is true THE navigation bar SHALL draw a Tanrei badge anchored to its icon as the badge component draws one whose count is the destination's badgeCount and whose label is its badgeLabel, SHALL keep it while the destination is disabled, and SHALL expose the badge's description (badgeLabel, or the displayed count when it is empty) as part of the destination's own description, after its name and state.
NVB-17 IF the navigation bar holds fewer than three or more than five destinations THE navigation bar SHALL still render them, and in debug builds report the count.
NVB-18 THE navigation bar SHALL extend its container under the bottom system inset (home indicator, gesture bar, taskbar) and SHALL place its destinations above that inset and inside the left and right insets.
NVB-19 THE navigation bar SHALL expose each destination as one element named by its label, with its selected state and its position in the set (for example 2 of 4), and its icon hidden: a link WHILE its href is not empty (NVB-30) and a button otherwise. On the web it is an anchor or a button inside a navigation landmark named by the bar's label, the selected destination carrying aria-current="page"; on Apple platforms, Android, Flutter and React Native a tab of a tab bar, with the selected state, a link destination included: there the tab is what carries the position and the selection a bar is read by, and a link destination adds the link trait to it where the platform lets one element carry both. The selected destination is the place the product is showing, in a product of one screen and in a site of many pages alike: THE navigation bar SHALL have no second state and no second mark for a current page, and SHALL expose exactly one destination, or none, as current.
NVB-20 WHILE a destination's label is not empty THE navigation bar SHALL expose it as the destination's accessible name, over a name the product gives the destination on the element (aria-label or aria-labelledby); WHILE a destination's label is empty and the product gives the destination a name of its own on the element — aria-label, or aria-labelledby naming an element — THE navigation bar SHALL expose that name as its accessible name; WHILE a destination's label is empty and the product gives it none THE navigation bar SHALL still render it, and in debug builds report that it has no accessible name.
NVB-21 WHILE the text size is enlarged THE navigation bar SHALL grow its height to hold its labels on one line inside their item containers (NVB-12), every item container growing with the bar alike, and SHALL NOT scale its icon indicators or icons; WHERE the platform offers a large-content viewer for bar items (iOS) a long press SHALL show the destination's icon and label enlarged.
NVB-22 IF two destinations have the same value THE navigation bar SHALL treat the first of them as the one the bar's value selects, and in debug builds report the repeated value.
NVB-23 THE navigation bar SHALL draw each destination's icon and label a second time, over the first, in the covered colors of its condition (comp.navigation-bar.unselected.item.covered-icon.color and comp.navigation-bar.unselected.item.covered-label.color for a tonal destination that is not selected, comp.navigation-bar.selected.item.covered-icon.color and comp.navigation-bar.selected.item.covered-label.color for the tonal selected one, comp.navigation-bar.filled-unselected.item.covered-icon.color, comp.navigation-bar.filled-unselected.item.covered-label.color, comp.navigation-bar.filled-selected.item.covered-icon.color and comp.navigation-bar.filled-selected.item.covered-label.color for a filled bar), clipped to the active indicator's current shape, its corners included, wherever the indicator is drawn; WHILE the indicator slides (NVB-15) or follows a drag (NVB-27) the clip SHALL move with it in the same frame, so that a destination it passes over changes where it is covered and the newly selected destination's icon and label are wholly in their covered colors when it lands, neither before nor after. THE navigation bar SHALL NOT approximate the covered colors with a blend mode, SHALL draw the badge above both copies, and SHALL expose each destination to assistive technology once.
NVB-24 THE navigation bar 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 navigation bar is shown THE navigation bar SHALL draw itself for the new direction at once, without a motion of its own, keeping the selected destination and its focus; a drag (NVB-27) SHALL carry the active indicator to where the pointer is in either direction.
NVB-25 WHERE the layout direction is right-to-left THE navigation bar SHALL draw a destination's icon, in both of its copies (NVB-23) 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 NVB-11 gives. THE navigation bar 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.
NVB-26 THE navigation bar SHALL lay out a destination's label, in both of its copies (NVB-23) 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 navigation bar SHALL still place each label and align its lines by the layout direction, not by the text's own, and SHALL cut a label that does not fit (NVB-12) at the end of its own direction, so that a left-to-right label in a right-to-left layout loses its right end and never its beginning.
NVB-27 WHEN the user presses the bar with touch, a pen or a mouse and drags past the platform's touch slop (8 points where the platform names none), further sideways than up or down, WHILE a destination is selected, THE navigation bar SHALL take the drag: the press SHALL activate no destination when it ends (NVB-1), the destination under it SHALL leave its pressed state as it does when a press ends (NVB-8), and from then until the drag ends THE navigation bar SHALL draw the active indicator where the pointer is along the bar — on an enabled destination's item container while the pointer is at that cell's center, halfway between two neighbouring enabled destinations while the pointer is where their cells meet (the middle of any disabled destinations between them), at the linear interpolation in between, and on the first or last enabled destination while the pointer is before or beyond its center — at its full size, in the selected destination's colors. Everything that differs between the indicator on the one destination and on the other SHALL change continuously with the pointer, with no threshold: its place, and the color of each icon and label, which is its covered color exactly where the indicator covers it and its own color elsewhere, the clip of NVB-23 moving with the indicator in the same frame. The icon indicator, each icon's fill and, WHILE labelVisibility is selected, the label that is drawn SHALL stay with the selected destination until the release (NVB-28). THE navigation bar SHALL place the indicator in the frame the pointer moves, without a motion of its own; WHEN the drag is taken with the pointer away from where the indicator rests THE navigation bar SHALL bring it to the pointer's place over comp.navigation-bar.motion.state-duration with comp.navigation-bar.motion.state-easing instead of at once, following the pointer meanwhile. A drag that is further up or down than sideways when it passes the slop SHALL be left to the page and to the system's gestures at the bottom edge for its whole length, a wheel or a trackpad SHALL move nothing, and WHILE no destination is selected THE navigation bar SHALL take no drag.
NVB-28 WHEN the user releases a drag the navigation bar has taken THE navigation bar SHALL choose one enabled destination: the one the active indicator is nearer to of the two it lies between (the one after it from halfway on), or, WHEN the release is a fling along the bar, the one of those two that the fling is toward; a fling is a release faster than the platform's minimum fling velocity (Android's ViewConfiguration.getScaledMinimumFlingVelocity, Flutter's kMinFlingVelocity) or, where the platform names none (the web, SwiftUI, React Native), 0.5 points per millisecond. A release's velocity is the pointer's over its last moves: WHERE the pointer has not moved for longer than the time after which the platform's velocity tracker takes a pointer as stopped (40 milliseconds in Android's VelocityTracker, Jetpack Compose's VelocityTracker and Flutter's VelocityTracker) or, where the platform names none (the web, SwiftUI, React Native), 40 milliseconds before the release THE navigation bar SHALL take the release as having no velocity, so that a drag that stops where it is wanted and is let go there is no fling, however fast it moved before it stopped. WHERE the chosen destination is not the selected one and is not a link THE navigation bar SHALL activate it as NVB-1 does — emit change exactly once with its value — and SHALL slide the active indicator from where the release left it onto that destination over comp.navigation-bar.motion.selection-duration with comp.navigation-bar.motion.selection-easing, as NVB-15 does from a destination; WHERE the chosen destination is not the selected one and is a link THE navigation bar SHALL treat the release as a plain press on that link — emitting change and following the link as NVB-31 says, or only opening it where NVB-31 does not count it — SHALL NOT write value, and SHALL return the active indicator to the destination that value selects as NVB-29 returns it, the indicator going on to the chosen destination when the product sets value (NVB-15); WHERE it is the selected destination THE navigation bar SHALL return the indicator to it over the same motion and SHALL emit neither change nor reselect. The place where two cells meet decides only what a release chooses; nothing drawn changes at it. The release SHALL NOT also activate the destination under the pointer.
NVB-29 WHEN a drag the navigation bar has taken is cancelled — the platform cancels the pointer (a system gesture, a lost capture), Escape is pressed while it lasts, or the product sets value or removes or disables a destination the indicator lies between — THE navigation bar SHALL return the active indicator to the selected destination (the one the product has just set, where it set one) over comp.navigation-bar.motion.selection-duration with comp.navigation-bar.motion.selection-easing and emit nothing. WHILE a drag lasts THE navigation bar SHALL NOT change value, keyboard focus, or the selected state that each destination exposes to assistive technology, whatever the indicator covers, and SHALL announce nothing: the selection is made, and announced, by the release alone (NVB-28). WHERE motion is reduced THE navigation bar SHALL still draw the active indicator where the pointer is, which the person's own finger moves, taking the drag without the catch-up motion of NVB-27, and SHALL put it on its destination at once when the drag is released or cancelled.
NVB-30 WHILE a destination's href is not empty THE navigation bar SHALL make it a link to that address and not a button. On the web it SHALL be an anchor element with that address, whose behaviours are the browser's own — following it on a plain click, a touch or Enter; opening it in another tab or window by a modified click or a middle click; the link's context menu; copying its address; showing its address on hover — and THE navigation bar SHALL NOT prevent, replace or imitate any of them, with one exception: a sideways drag that begins on a link destination moves the active indicator (NVB-27) and does not drag the link's address, because one gesture cannot do both and the drag is the bar's own way of moving its selection; Space SHALL NOT activate a link. On other platforms it SHALL be the platform's own link (SwiftUI's Link; on Compose, Flutter and React Native an element with the platform's link semantics whose address is opened by the platform's opener), as BTN-22 says for a link button. THE navigation bar SHALL draw a link exactly as it draws a button destination, from the same tokens in every state and both variants (NVB-3, NVB-4, NVB-7 to NVB-9, NVB-12, NVB-23), SHALL draw no look of its own for a link the user has visited, and SHALL give its hit area (NVB-6) to the link. WHILE a link's value is empty THE navigation bar SHALL take its href as its value, for value, change, reselect and NVB-22. WHILE a link's disabled is true it SHALL keep the link role with the disabled state and no destination, and SHALL open nothing by any means the platform gives a link (NVB-2; on the web the anchor carries no address).
NVB-31 WHEN the user activates a link destination THE navigation bar SHALL count it as an activation (NVB-1) only WHILE it follows the link in the place where the product is shown: a click with the primary button and no modifier key, a touch, Enter, or the default action of assistive technology, on a link whose target is same and that asks for no download. It SHALL then emit change or reselect exactly once, carrying the destination's value and its href, before the link is followed, and SHALL NOT hold the link back for sys.motion.duration.press-hold; SHALL NOT write value itself on any platform, because where the product is, is the product's to say, and the page or the route shown next sets value; SHALL keep the active indicator on the destination that value selects, moving it as NVB-15 says only when value changes; and SHALL let the link be followed unless the product cancels the event (preventDefault on the web), as a product does that moves between its pages without loading a document. For every other activation of a link — a modified or a middle click, a link whose target is new, a download — THE navigation bar SHALL emit neither change nor reselect, and SHALL keep value and the drawn selection as they are.
NVB-32 WHILE a destination is a link and its target is new THE navigation bar SHALL open its href in a new context, as BTN-24 says for a link button (on the web a new tab or window that is given no handle to the page that opened it), and SHALL draw no mark for it: a destination's cell is an icon over a label and has no trailing glyph place for a mark to stand in, as an icon-only button has none, and a second glyph beside the icon would be read as its badge. It SHALL expose targetLabel as part of the destination's description, after its badge's description (NVB-16), and WHILE targetLabel is empty SHALL still render and in debug builds report that a link that opens a new context does not say so. WHILE target is same THE navigation bar SHALL ignore targetLabel.
NVB-33 WHEN a destination's target names a value the navigation bar does not define THE navigation bar SHALL treat it as same, and in debug builds report the unknown value. IF a destination's href is only white space, or names a script to run rather than a place to go (the web's javascript: scheme), or cannot be read as an address by the platform, THE navigation bar SHALL make the destination a button without a destination, and in the last two cases report it in debug builds, as BTN-26 says for a button. WHILE a destination's href is empty THE navigation bar SHALL ignore its target and targetLabel.
NVB-34 WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on a destination's host, by the attribute or by the ARIA property that reflects it, THE navigation bar 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 the element that carries the destination's role (its link or its button) — an id reference by the elements it names in the host's tree or a tree around it, an error message as part of the description while the destination is invalid — except those a destination owns (aria-current, aria-disabled), which it SHALL move and not expose. 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 navigation bar SHALL take it off that element. WHEN the product sets an aria- attribute a destination owns THE navigation bar SHALL report it in debug builds. The bar's host carries its navigation landmark itself (NVB-19), so what the product writes on it is not moved, but for a name: WHILE the bar's label is not empty THE navigation bar SHALL hold the product's aria-label and aria-labelledby on that host as their data-aria- twins, so that label is the landmark's whole name although the platform gives aria-labelledby the precedence, and WHEN label is emptied SHALL give them back as they were written, so that a name the product gives names the landmark; WHEN the product removes one, or its data-aria- twin, or sets the ARIA property that reflects it to null, THE navigation bar SHALL take it away.

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

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

المواصفة Web SwiftUI Jetpack Compose Flutter React Native
المكوّن tanrei-navigation-bar TanreiNavigationBar TanreiNavigationBar TanreiNavigationBar NavigationBar
value · الخاصية value selection selected selectedIndex value
variant · الخاصية variant variant variant variant variant
labelVisibility · الخاصية labelVisibility labelVisibility labelVisibility labelBehavior labelVisibility
label · الخاصية label accessibilityLabel contentDescription semanticLabel accessibilityLabel
change · الحدث change selection onClick onDestinationSelected onValueChange
reselect · الحدث reselect onReselect onClick onDestinationSelected onReselect
ملاحظات Destinations are <tanrei-navigation-bar-item> children, in order. The bar selects the activated destination itself (sets value) before firing change, as a native control does, unless the destination is a link. For a link destination change and reselect are cancelable, carry detail.href beside detail.value, and are fired from the anchor's click only when event.button is 0, no modifier key is down, the click was not already cancelled, and the item's target is same and it has no download; cancelling one calls preventDefault() on that click. The bar does not set value for a link. variant is the variant attribute (tonal, filled) and labelVisibility the label-visibility attribute. The host is a navigation landmark (role="navigation", aria-label from label); the selected destination has aria-current="page". The active indicator is one layer of the bar that slides between the destinations' item containers; the covered copy of every icon and label (NVB-23) is an aria-hidden layer clipped with clip-path to the indicator's box, animated with it. A drag (NVB-27 to NVB-29) is followed with Pointer Events, as tanrei-tabs follows its own: pointerdown on the bar, setPointerCapture once the drag is taken, pointermove placing the indicator through the same custom properties a slide transitions, pointerup choosing the destination, and pointercancel, lostpointercapture or Escape cancelling; the click that follows a taken drag is swallowed, aria-current is written only on the release, a destination that is a link is not draggable as a link (draggable="false", the one behaviour of a link the bar takes, NVB-30) and is followed with its own click() when a release chooses it, the indicator then returning to the destination value selects (NVB-28), and touch-action: pan-y leaves vertical drags to the page. The bottom inset is env(safe-area-inset-bottom). It does not yet follow NVB-30 to NVB-33, nor the parts of NVB-1, NVB-19 and NVB-28 that concern links: a pressed link destination is selected like a button, change is not cancelable and carries no href, and the destination writes the link rules itself instead of using the shared link primitive (packages/web/src/primitives/link.ts). TanreiNavigationBar(selection: Binding<Value>, variant:, labelVisibility:, onReselect:) { TanreiNavigationBarItem(…) … }, placed with .safeAreaInset(edge: .bottom). A bar of its own rather than a restyled TabView, whose bar the tokens cannot reach (indicator, geometry, colors). Activation writes the binding (change); activating the selected item calls onReselect. The bar is exposed as a tab bar and each item as a button with the selected trait; each item supports the large-content viewer. It lags the spec: it has no variant, and draws the active indicator behind the icon alone (the item container at its least size), growing in from its center instead of sliding, with the label outside it. Not built on this platform yet: the item container as wide as the destination's cell and held in by container.padding, hover, focus and press drawn in that box, and the drag of the active indicator (NVB-27 to NVB-29): a DragGesture(minimumDistance:) beside each item's tap, predictedEndTranslation for the fling, the binding written in onEnded. Link destinations are not built yet either (NVB-30 to NVB-33 and the parts of NVB-1, NVB-19 and NVB-28 that concern links, with the fixtures links, links-rtl and links-disabled). Compose idiom (as NavigationBar): TanreiNavigationBar(variant, labelVisibility, contentDescription) { TanreiNavigationBarItem(…) … }. There is no bar-level value: the caller marks the selected item with selected = (current == value), so the bar's value and a destination's value both map to the item's selected, and one onClick per item covers change and reselect (the caller knows which item is selected). The bar draws the sliding active indicator behind its items, from the bounds of the selected item. Insets follow WindowInsets.navigationBars plus the horizontal display cutout, as a windowInsets parameter. Items have Role.Tab in a selectableGroup(). It lags the spec: it has no variant, and draws the active indicator behind the icon alone (the item container at its least size), growing in from its center instead of sliding, with the label outside it. Not built on this platform yet: the item container as wide as the destination's cell and held in by container.padding, hover, focus and press drawn in that box, and the drag of the active indicator (NVB-27 to NVB-29): pointerInput with detectHorizontalDragGestures on the bar and a VelocityTracker for the fling; the release calls the chosen item's onClick. Link destinations are not built yet either (NVB-30 to NVB-33 and the parts of NVB-1, NVB-19 and NVB-28 that concern links, with the fixtures links, links-rtl and links-disabled). Flutter idiom (as NavigationBar): TanreiNavigationBar(selectedIndex:, onDestinationSelected:, variant:, labelBehavior:, destinations: [TanreiNavigationDestination(…), …]). A destination's value is its index in destinations; a selectedIndex of null selects none. onDestinationSelected is called for the selected destination too (reselect); the caller compares. variant is TanreiNavigationBarVariant.tonal or .filled; labelBehavior is alwaysShow, onlyShowSelected or alwaysHide. Bottom inset from MediaQuery.padding via SafeArea. It lags the spec: it has no variant, and draws the active indicator behind the icon alone (the item container at its least size), growing in from its center instead of sliding, with the label outside it. Not built on this platform yet: the item container as wide as the destination's cell and held in by container.padding, hover, focus and press drawn in that box, and the drag of the active indicator (NVB-27 to NVB-29): a GestureDetector's horizontal drag callbacks on the bar, DragEndDetails.primaryVelocity for the fling; the release calls onDestinationSelected. Link destinations are not built yet either (NVB-30 to NVB-33 and the parts of NVB-1, NVB-19 and NVB-28 that concern links, with the fixtures links, links-rtl and links-disabled). Controlled, in React Native's value / onValueChange idiom, with a destinations data array; variant is 'tonal' or 'filled'. The bar has the tablist role and each destination the tab role with accessibilityState.selected; the covered copy is hidden with importantForAccessibility="no-hide-descendants" and accessibilityElementsHidden. Insets come from the bar's own insets prop, side by side, falling back to TanreiProvider's insets; the package takes no safe-area dependency, and a product that uses react-native-safe-area-context passes its useSafeAreaInsets() to either. It lags the spec: it has no variant, and draws the active indicator behind the icon alone (the item container at its least size), growing in from its center instead of sliding, with the label outside it. Not built on this platform yet: the item container as wide as the destination's cell and held in by container.padding, hover, focus and press drawn in that box, and the drag of the active indicator (NVB-27 to NVB-29): a PanResponder on the bar (or React Native Gesture Handler's Gesture.Pan() where the product has it), the release's vx for the fling; the release calls onValueChange. Link destinations are not built yet either (NVB-30 to NVB-33 and the parts of NVB-1, NVB-19 and NVB-28 that concern links, with the fixtures links, links-rtl and links-disabled).

الرموز

الرمز يشير إلى القيمة المحلولة (فاتح)
--tanrei-comp-navigation-bar-container-color sys.color.surface #ffffff
--tanrei-comp-navigation-bar-container-elevation sys.elevation.level0
--tanrei-comp-navigation-bar-container-height — 64px
--tanrei-comp-navigation-bar-container-padding sys.space.space50 4px
--tanrei-comp-navigation-bar-disabled-opacity — 1
--tanrei-comp-navigation-bar-filled-selected-disabled-item-container-color — #1b1b1b · 12%
--tanrei-comp-navigation-bar-filled-selected-disabled-item-covered-icon-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-filled-selected-disabled-item-covered-label-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-filled-selected-disabled-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-filled-selected-disabled-item-icon-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-filled-selected-disabled-item-label-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-filled-selected-disabled-item-state-layer-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-filled-selected-focused-item-container-color — #323232
--tanrei-comp-navigation-bar-filled-selected-focused-item-covered-icon-color — #ffffff
--tanrei-comp-navigation-bar-filled-selected-focused-item-covered-label-color — #ffffff
--tanrei-comp-navigation-bar-filled-selected-focused-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-filled-selected-focused-item-icon-color — #262626
--tanrei-comp-navigation-bar-filled-selected-focused-item-label-color — #1b1b1b
--tanrei-comp-navigation-bar-filled-selected-focused-item-state-layer-color — #ffffff
--tanrei-comp-navigation-bar-filled-selected-hovered-item-container-color — #2d2d2d
--tanrei-comp-navigation-bar-filled-selected-hovered-item-covered-icon-color — #ffffff
--tanrei-comp-navigation-bar-filled-selected-hovered-item-covered-label-color — #ffffff
--tanrei-comp-navigation-bar-filled-selected-hovered-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-filled-selected-hovered-item-icon-color — #262626
--tanrei-comp-navigation-bar-filled-selected-hovered-item-label-color — #1b1b1b
--tanrei-comp-navigation-bar-filled-selected-hovered-item-state-layer-color — #ffffff
--tanrei-comp-navigation-bar-filled-selected-item-container-color sys.color.primary #1b1b1b
--tanrei-comp-navigation-bar-filled-selected-item-covered-icon-color sys.color.on-primary #ffffff
--tanrei-comp-navigation-bar-filled-selected-item-covered-label-color sys.color.on-primary #ffffff
--tanrei-comp-navigation-bar-filled-selected-item-icon-indicator-color comp.navigation-bar.selected.item.icon-indicator.color #000000 · 0%
--tanrei-comp-navigation-bar-filled-selected-item-icon-color comp.navigation-bar.selected.item.icon.color #262626
--tanrei-comp-navigation-bar-filled-selected-item-label-color comp.navigation-bar.selected.item.label.color #1b1b1b
--tanrei-comp-navigation-bar-filled-selected-item-state-layer-color comp.navigation-bar.filled-selected.item.covered-icon.color #ffffff
--tanrei-comp-navigation-bar-filled-selected-pressed-item-container-color — #323232
--tanrei-comp-navigation-bar-filled-selected-pressed-item-covered-icon-color — #ffffff
--tanrei-comp-navigation-bar-filled-selected-pressed-item-covered-label-color — #ffffff
--tanrei-comp-navigation-bar-filled-selected-pressed-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-filled-selected-pressed-item-icon-color — #262626
--tanrei-comp-navigation-bar-filled-selected-pressed-item-label-color — #1b1b1b
--tanrei-comp-navigation-bar-filled-selected-pressed-item-state-layer-color — #ffffff
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-container-color — #000000 · 0%
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-covered-icon-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-covered-label-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-icon-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-label-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-filled-unselected-disabled-item-state-layer-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-filled-unselected-focused-item-container-color — #4b4b4b · 10%
--tanrei-comp-navigation-bar-filled-unselected-focused-item-covered-icon-color — #ffffff
--tanrei-comp-navigation-bar-filled-unselected-focused-item-covered-label-color — #ffffff
--tanrei-comp-navigation-bar-filled-unselected-focused-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-filled-unselected-focused-item-icon-color — #4b4b4b
--tanrei-comp-navigation-bar-filled-unselected-focused-item-label-color — #4b4b4b
--tanrei-comp-navigation-bar-filled-unselected-focused-item-state-layer-color — #4b4b4b
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-container-color — #4b4b4b · 8%
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-covered-icon-color — #ffffff
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-covered-label-color — #ffffff
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-icon-color — #4b4b4b
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-label-color — #4b4b4b
--tanrei-comp-navigation-bar-filled-unselected-hovered-item-state-layer-color — #4b4b4b
--tanrei-comp-navigation-bar-filled-unselected-item-container-color comp.navigation-bar.unselected.item.container.color #000000 · 0%
--tanrei-comp-navigation-bar-filled-unselected-item-covered-icon-color sys.color.on-primary #ffffff
--tanrei-comp-navigation-bar-filled-unselected-item-covered-label-color sys.color.on-primary #ffffff
--tanrei-comp-navigation-bar-filled-unselected-item-icon-indicator-color comp.navigation-bar.unselected.item.icon-indicator.color #000000 · 0%
--tanrei-comp-navigation-bar-filled-unselected-item-icon-color comp.navigation-bar.unselected.item.icon.color #4b4b4b
--tanrei-comp-navigation-bar-filled-unselected-item-label-color comp.navigation-bar.unselected.item.label.color #4b4b4b
--tanrei-comp-navigation-bar-filled-unselected-item-state-layer-color comp.navigation-bar.filled-unselected.item.icon.color #4b4b4b
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-container-color — #4b4b4b · 10%
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-covered-icon-color — #ffffff
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-covered-label-color — #ffffff
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-icon-color — #4b4b4b
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-label-color — #4b4b4b
--tanrei-comp-navigation-bar-filled-unselected-pressed-item-state-layer-color — #4b4b4b
--tanrei-comp-navigation-bar-focused-opacity — 1
--tanrei-comp-navigation-bar-hovered-opacity — 1
--tanrei-comp-navigation-bar-item-container-padding-block sys.space.space50 4px
--tanrei-comp-navigation-bar-item-container-padding-inline sys.space.space150 12px
--tanrei-comp-navigation-bar-item-container-shape sys.shape.corner.full 9999px
--tanrei-comp-navigation-bar-item-gap sys.space.space50 4px
--tanrei-comp-navigation-bar-item-icon-indicator-height comp.navigation-bar.item.icon.size 24px
--tanrei-comp-navigation-bar-item-icon-indicator-shape sys.shape.corner.full 9999px
--tanrei-comp-navigation-bar-item-icon-indicator-width comp.navigation-bar.item.icon.size 24px
--tanrei-comp-navigation-bar-item-icon-size sys.icon.medium 24px
--tanrei-comp-navigation-bar-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-navigation-bar-motion-selection-duration sys.motion.role.selection.travel 250ms
--tanrei-comp-navigation-bar-motion-selection-easing sys.motion.easing.spatial cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-navigation-bar-motion-state-duration sys.motion.role.state.large 250ms
--tanrei-comp-navigation-bar-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-navigation-bar-outline-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-navigation-bar-outline-width sys.border.hairline 1px
--tanrei-comp-navigation-bar-pressed-opacity — 1
--tanrei-comp-navigation-bar-selected-disabled-item-container-color — #eeeeee
--tanrei-comp-navigation-bar-selected-disabled-item-covered-icon-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-selected-disabled-item-covered-label-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-selected-disabled-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-selected-disabled-item-icon-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-selected-disabled-item-label-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-selected-disabled-item-state-layer-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-selected-focused-item-container-color — #dadada
--tanrei-comp-navigation-bar-selected-focused-item-covered-icon-color — #262626
--tanrei-comp-navigation-bar-selected-focused-item-covered-label-color — #262626
--tanrei-comp-navigation-bar-selected-focused-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-selected-focused-item-icon-color — #262626
--tanrei-comp-navigation-bar-selected-focused-item-label-color — #1b1b1b
--tanrei-comp-navigation-bar-selected-focused-item-state-layer-color — #262626
--tanrei-comp-navigation-bar-selected-hovered-item-container-color — #dedede
--tanrei-comp-navigation-bar-selected-hovered-item-covered-icon-color — #262626
--tanrei-comp-navigation-bar-selected-hovered-item-covered-label-color — #262626
--tanrei-comp-navigation-bar-selected-hovered-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-selected-hovered-item-icon-color — #262626
--tanrei-comp-navigation-bar-selected-hovered-item-label-color — #1b1b1b
--tanrei-comp-navigation-bar-selected-hovered-item-state-layer-color — #262626
--tanrei-comp-navigation-bar-selected-item-container-color sys.color.secondary-container #eeeeee
--tanrei-comp-navigation-bar-selected-item-covered-icon-color sys.color.on-secondary-container #262626
--tanrei-comp-navigation-bar-selected-item-covered-label-color sys.color.on-secondary-container #262626
--tanrei-comp-navigation-bar-selected-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-selected-item-icon-color sys.color.on-secondary-container #262626
--tanrei-comp-navigation-bar-selected-item-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-navigation-bar-selected-item-state-layer-color comp.navigation-bar.selected.item.icon.color #262626
--tanrei-comp-navigation-bar-selected-pressed-item-container-color — #dadada
--tanrei-comp-navigation-bar-selected-pressed-item-covered-icon-color — #262626
--tanrei-comp-navigation-bar-selected-pressed-item-covered-label-color — #262626
--tanrei-comp-navigation-bar-selected-pressed-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-selected-pressed-item-icon-color — #262626
--tanrei-comp-navigation-bar-selected-pressed-item-label-color — #1b1b1b
--tanrei-comp-navigation-bar-selected-pressed-item-state-layer-color — #262626
--tanrei-comp-navigation-bar-unselected-disabled-item-container-color — #000000 · 0%
--tanrei-comp-navigation-bar-unselected-disabled-item-covered-icon-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-unselected-disabled-item-covered-label-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-unselected-disabled-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-unselected-disabled-item-icon-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-unselected-disabled-item-label-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-unselected-disabled-item-state-layer-color — #1b1b1b · 38%
--tanrei-comp-navigation-bar-unselected-focused-item-container-color — #4b4b4b · 10%
--tanrei-comp-navigation-bar-unselected-focused-item-covered-icon-color — #262626
--tanrei-comp-navigation-bar-unselected-focused-item-covered-label-color — #262626
--tanrei-comp-navigation-bar-unselected-focused-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-unselected-focused-item-icon-color — #4b4b4b
--tanrei-comp-navigation-bar-unselected-focused-item-label-color — #4b4b4b
--tanrei-comp-navigation-bar-unselected-focused-item-state-layer-color — #4b4b4b
--tanrei-comp-navigation-bar-unselected-hovered-item-container-color — #4b4b4b · 8%
--tanrei-comp-navigation-bar-unselected-hovered-item-covered-icon-color — #262626
--tanrei-comp-navigation-bar-unselected-hovered-item-covered-label-color — #262626
--tanrei-comp-navigation-bar-unselected-hovered-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-unselected-hovered-item-icon-color — #4b4b4b
--tanrei-comp-navigation-bar-unselected-hovered-item-label-color — #4b4b4b
--tanrei-comp-navigation-bar-unselected-hovered-item-state-layer-color — #4b4b4b
--tanrei-comp-navigation-bar-unselected-item-container-color — #000000 · 0%
--tanrei-comp-navigation-bar-unselected-item-covered-icon-color sys.color.on-secondary-container #262626
--tanrei-comp-navigation-bar-unselected-item-covered-label-color sys.color.on-secondary-container #262626
--tanrei-comp-navigation-bar-unselected-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-unselected-item-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-navigation-bar-unselected-item-label-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-navigation-bar-unselected-item-state-layer-color comp.navigation-bar.unselected.item.icon.color #4b4b4b
--tanrei-comp-navigation-bar-unselected-pressed-item-container-color — #4b4b4b · 10%
--tanrei-comp-navigation-bar-unselected-pressed-item-covered-icon-color — #262626
--tanrei-comp-navigation-bar-unselected-pressed-item-covered-label-color — #262626
--tanrei-comp-navigation-bar-unselected-pressed-item-icon-indicator-color — #000000 · 0%
--tanrei-comp-navigation-bar-unselected-pressed-item-icon-color — #4b4b4b
--tanrei-comp-navigation-bar-unselected-pressed-item-label-color — #4b4b4b
--tanrei-comp-navigation-bar-unselected-pressed-item-state-layer-color — #4b4b4b
--tanrei-comp-navigation-bar-item-label-typescale sys.typescale.label-small 500 12px/1.3333

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

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

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

الإرشادات

متى يُستخدم

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

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

الاختيار بين الشريط والدرج

الجانب شريط التنقل درج التنقل مطويًّا درج التنقل موسَّعًا
موضعه على امتداد الحافة السفلية عمود من الأيقونات على امتداد حافة البداية في لوحة عند حافة البداية
النافذة مضغوطة (Compact) متوسطة (Medium) فأوسع موسَّعة (Expanded) فأوسع (قياسي)، أو مضغوطة (نمطي)
الوجهات ثلاث إلى خمس وجهاته ومجموعاته العليا، خلية لكل منها بقدر ما في المنتج، في أقسام ومجموعات
الوجهة أيقونة فوق تسمية من سطر واحد، مع شارة على الأيقونة الصف مقطوعًا بعد أيقونته: الأيقونة وحدها، عليها شارة، واسمها في تلميح صف: أيقونة اختيارية، وتسمية تلتف، ونص أو محتوى في نهايته
يحمل أيضًا لا شيء آخر رأسًا: زر القائمة والإجراء الرئيسي مطويًّا إلى أيقونته رأسًا: زر القائمة، وعنوانًا أو مبدِّل حسابات، والإجراء الرئيسي
على المنصات الأصلية، الوجهة هي تبويب في شريط تبويب زر له حالة تحديد زر له حالة تحديد
ما هو منطقة من التخطيط منطقة من التخطيط، أو عمود في التخطيط يتّسع منه درج نمطي القياسي: منطقة من التخطيط. النمطي: تراكب نمطي
  • الدرج المطوي هو سكة التنقل (navigation rail). يضيق الدرج القابل للطي إلى عمود من الأيقونات حيث يحتاج المحتوى إلى العرض، ويوسّعه زر القائمة فيه من جديد؛ وتبقى كل أيقونة في مكانها. سكة التنقل المستقلة في Tanrei أُلغيت وصارت هذه الهيئة.
  • الشريط والدرج مجموعة واحدة من الوجهات في مكانين. يأخذان الوجهات نفسها والتحديد نفسه، فالمنتج الذي يتكيّف مع النافذة يضع الدرج مكان الشريط عند نقطة تحوّل (breakpoint) من دون أن يمسّ وجهاته. أظهر واحدًا منهما في كل مرة، لا الاثنين معًا أبدًا.
  • الدرج يحمل أكثر. استخدمه موسَّعًا حين يكون في المنتج وجهات أكثر مما يحمله الشريط، أو حين تقع في مجموعات، كمجلدات تطبيق بريد وتصنيفاته. على نافذة كبيرة يقع الدرج القياسي بجانب المحتوى. وعلى نافذة مضغوطة ينزلق الدرج النمطي فوقه من زر قائمة.
  • الدرج النمطي القابل للطي يُبقي عمود أيقوناته في التخطيط ويتّسع فوق المحتوى، فوق حجاب (scrim)، حين يفتحه زر القائمة فيه.
  • على كل المنصات، يحدّد كلاهما بقيمة الوجهة (في Compose بعلامة على كل عنصر، وفي Flutter بالفهرس) ويُصدران change و reselect.

التسميات

labelVisibility ما تفعله يُستخدم لـ
always (الافتراضية) ترسم كل وجهة تسميتها. معظم الأشرطة. التسميات تجعل الوجهات تُعرَف من نظرة.
selected الوجهة الحالية وحدها ترسم تسميتها. والأيقونة التي بلا تسمية تقع في وسط وجهتها. لا تغيّر أي وجهة حجمها ولا مكانها مع التحديد. الأشرطة التي يضيق فيها الحيّز وأيقوناتها معروفة جيدًا.
never لا ترسم أي وجهة تسميتها. منتجات الوسائط والتواصل الاجتماعي التي يعرف الجميع أيقوناتها الأربع أو الخمس.

التسمية التي لا تُرسم تبقى الاسم المتاح للتقنيات المساعدة لوجهتها، فكل وجهة تحتاج إلى تسمية.

التحديد والشارات

  • المؤشر النشط. لكل وجهة صندوق واحد: حصتها المتساوية من عرض الشريط، مُزاحةً إلى الداخل عن حواف الشريط، من الأعلى والأسفل وعند الطرفين، بإزاحة واحدة (4 في النظام الافتراضي). فأربع وجهات في شريط عرضه 360 وارتفاعه 64 لكل منها صندوق 88 × 56، وهو كبسولة في النظام الافتراضي. الصندوق واحد لكل وجهات الشريط، أيًّا كان ما تحمله، ولا يُقاس أبدًا على أيقونتها وتسميتها. صندوق الوجهة الحالية معبَّأ، وهذا ما يجعله المؤشر النشط، وأيقونتها معبَّأة. ويُرسم التحويم والتركيز والضغط في ذلك الصندوق نفسه لكل وجهة.
  • الانزلاق. حين يتغيّر التحديد ينزلق المؤشر من الوجهة القديمة إلى الجديدة بحجمه الكامل. لا تُعكس ألوان الأيقونات والتسميات إلا حيث يغطيها المؤشر، فالوجهة التي يمرّ فوقها تغيّر ألوانها تحته لحظة، والوجهة الجديدة تكون معكوسة الألوان كلها حين يستقر المؤشر. من انعدام التحديد يظهر المؤشر تدريجيًّا على الوجهة الجديدة؛ وإلى انعدام التحديد يختفي تدريجيًّا. لا يكبر ولا يصغر أبدًا. وحين يظهر الشريط أول مرة، أو يتغيّر حجم الوجهة الحالية من دون تحديد جديد (وصول خط، تغيّر حجم النص)، يوضع المؤشر في مكانه فورًا. وحيث تُقلَّل الحركة يحدث التغيّر فورًا.
  • السحب. الضغط على الشريط مع حركة جانبية، باللمس أو بقلم أو بفأرة، ووجهةٌ ما محدَّدة، يحمل المؤشر النشط مع مؤشر الإدخال. لا يُختار شيء حتى الإفلات: تبقى value والتركيز وتعبئة كل أيقونة وما يُعلَن مع الوجهة الحالية. يختار الإفلات أقرب الوجهتين اللتين يقع المؤشر بينهما، أو الوجهة التي تتجه إليها دفعة سريعة (fling)، ويُصدر change مرة واحدة. وإن أُفلت عائدًا إلى الوجهة الحالية فلا يُصدر شيئًا، ولا حتى reselect. يلغي المفتاح Escape السحب. والسحب ليس الطريق الوحيد أبدًا: اللمسة والنقرة والمفتاحان Enter و Space تختار كما من قبل.
  • إعادة التحديد. اختيار الوجهة الحالية مرة أخرى يُصدر reselect، لا change. والجواب المعتاد أن تُعاد تلك الشاشة إلى أعلاها، أو أن يُرجَع إلى صفحتها الأولى.
  • الشارات. الشارة التي لها عدد تعرض الرقم (999+ فوق 999)، والتي بلا عدد تعرض نقطة. والخاصية badgeLabel هي ما يقوله قارئ الشاشة بعد اسم الوجهة، مثل «3 غير مقروءة». النقطة لا رقم لها يُقرأ، فهي تحتاج إليها دائمًا. وتبقى الشارة ما دامت وجهتها معطَّلة.

الروابط

في موقع من صفحات منفصلة، أعطِ كل وجهة href واضبط قيمة value للشريط على عنوان الصفحة نفسها. فتصير الوجهة الحالية والصفحة الحالية شيئًا واحدًا، بعلامة واحدة.

  • يُتبع ولا يُحدَّد. التفعيل العادي للرابط يُصدر change، أو reselect على الوجهة الحالية، حاملًا قيمة الوجهة وقيمة href فيها، ثم يُتبع الرابط. ولا يكتب الشريط value: يبقى المؤشر النشط على الوجهة التي تسمّيها value ويتحرك حين تتغيّر value.
  • الموجِّه. ألغِ الحدث لتتولى التنقل بنفسك، واذهب إلى detail.href واضبط value. فينزلق المؤشر عندئذ إلى الوجهة الجديدة.
  • التبويبات الأخرى. النقرة أو المفتاح Enter مع مفتاح تعديل، والنقرة بالزر الأوسط، والرابط الذي قيمة target فيه new، والتنزيل، كلها من شأن المتصفح. لا يُصدر الشريط شيئًا ولا يحدّد شيئًا.
  • سياق جديد. القيمة target: new تفتح تبويبًا جديدًا أو نافذة جديدة. والوجهة أيقونة فوق تسمية، لا مكان فيها لعلامة، فلا تُرسم علامة: تقول الخاصية targetLabel ذلك للتقنيات المساعدة، بعد وصف الشارة.
  • السحب. السحب الجانبي الذي يبدأ على رابط يحرّك المؤشر النشط ولا يسحب عنوان الرابط. وإن أُفلت على رابط فهو ضغط عادي على ذلك الرابط: يعود المؤشر إلى الوجهة الحالية إلى أن تتغيّر value.
  • التعطيل. الرابط المعطَّل لا يمكن اتباعه بأي وسيلة، ويخرج من ترتيب التركيز.

الطرازان tonal و filled

variant الوجهة الحالية يُستخدم لـ
tonal (الافتراضي) مؤشر لطيف بلون الحاوية الثانوية، وأيقونته وتسميته بلون محتوى تلك الحاوية. معظم الأشرطة.
filled مؤشر قوي باللون الأساسي، وأيقونته وتسميته معكوستا اللون عليه. شريط يجب أن يبرز عن شاشة مزدحمة: خريطة، شبكة صور، مشغِّل.

الوجهات التي لا يغطيها المؤشر تبدو واحدة في الطرازين. يغيّر الطراز (variant) مدى قوة رسم التحديد، لا ما يُعلَن: الشريط من الطراز tonal والشريط من الطراز filled يعرِّفان الأسماء والأدوار والحالات نفسها. الوجهة الحالية المعطَّلة تحتفظ بمؤشرها من الطراز tonal وعليه محتوى معطَّل؛ أما المؤشر من الطراز filled فيبهت كما يبهت زر Button معطَّل من الطراز filled، لأن المحتوى المعطَّل على اللون الأساسي تصعب قراءته.

يحذف الإعدادان الجاهزان m3-like و cupertino القيمة filled، لأن لشريط التنقل في Material ولشريط التبويب عند Apple نمطًا واحدًا. يحتفظ m3-like بصندوق Material خلف الأيقونة وحدها، والتسمية تحته وبلا صندوق حول الوجهة، ويعلّم cupertino التبويب الحالي بصبغته فقط. وتحت كليهما لا يُظهر السحب شيئًا يتحرك، ومع ذلك يختار إفلاته.

افعل

  • اجعل التسمية كلمة واحدة قصيرة: «الوارد»، «البحث»، «الملف الشخصي». التسمية التي لا تسعها وجهتها تُقتطع عند نهايتها بعلامة الحذف (…)، ولا شيء يلتف.
  • لا تستخدم filled إلا حيث يضيع المؤشر من الطراز tonal أمام الشاشة التي خلف الشريط.
  • أبقِ الوجهات وترتيبها واحدًا في كل شاشة عليا.
  • استخدم نمط الأيقونات نفسه لكل الوجهات، ودع الشريط يرسم الهيئة المعبَّأة للوجهة الحالية.

لا تفعل

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

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

الدور
navigation
التسمية
Each destination is named by its label, drawn or not; its icon is decorative, and so is the covered copy of its icon and label. A badge adds its description (badgeLabel, or the displayed count) after the name, and a link that opens a new context its targetLabel after that. The bar's label names the bar itself.
الحد الأدنى للهدف
تستوفي مساحة اللمس الحد الأدنى لحجم الهدف حتى لو كان الحجم المرئي أصغر. minimum 32px · pointer 44px · spacing 8px · touch 48px

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

المفاتيح الإجراء
Tab Moves focus to the next destination that is not disabled: each destination is a tab stop, as the links of a navigation region are.
Enter / Space Activates the focused destination (Enter only for a destination that is a link: it follows the link, and Space is left to the platform).
Enter with a modifier key, on a link (the web) What the browser does with a link: another tab or window, a download. The bar emits nothing and selects nothing (NVB-31).
Escape Cancels a pointer drag of the active indicator in progress: the indicator returns and nothing is selected. Otherwise it does nothing.

ملاحظات

  • The semantics follow each platform's idiom for a bar of top-level destinations. On the web it is a navigation landmark whose destinations are links (or buttons in an app that does not change the URL) with aria-current="page" on the current one: that is how a web user recognises site navigation, and the tablist pattern would promise tab panels and arrow-key movement that a switch of whole screens does not have. On iOS, Android, Flutter and React Native the same bar is the platform's tab bar (UITabBar, BottomNavigationView and Material's NavigationBar all expose their items as tabs), so each destination is announced as a tab with its selected state and position.
  • Selected and the current page are one thing. The bar lists the product's top-level places and the selected one is the place being shown, which the web exposes as aria-current="page" on a button and on a link alike. A site of separate pages changes who says which it is — the page, through value, not a press — and nothing else: a link destination is followed, the bar never selects it by itself, and no destination is ever selected and another current.
  • Screen readers announce, for example, "Inbox, tab, 1 of 4, selected, 3 unread": name, role, position, state, then the badge's description.
  • Labels do not wrap: a bar's height is part of the screen's layout. A label longer than its item container allows is truncated, and stays whole as the destination's name. At 200% text size the bar and its item containers grow, and iOS shows the large-content viewer on a long press, as its own tab bar does.
  • The variant changes how strongly the selection is drawn, not what is announced: a tonal and a filled bar expose the same names, roles and states.
  • The drag is never the only way to choose (WCAG 2.5.7): a tap, a click, Enter and Space remain, and the keyboard is unchanged. While a drag lasts nothing a destination exposes changes and nothing is announced; the release announces once, as an activation does. With a screen reader running a one-finger drag is the reader's, and activation works as before.
  • How a state looks is a value decision of the active design system; components read the state's tokens and never compute the effect themselves.

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

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

  • على الويب الشريط مَعلم تنقل (navigation landmark) تسمّيه الخاصية label. كل وجهة رابط (مع href) أو زر، والحالية تحمل aria-current="page"، على الرابط وعلى الزر سواء: لا حالة ثانية للصفحة الحالية. وليس قائمة تبويبات (tab list): فالتبديل بين شاشات كاملة لا لوحات تبويب فيه ولا تنقّل بمفاتيح الأسهم.
  • على iOS و Android و Flutter و React Native الشريط هو شريط التبويب الخاص بالمنصة، كما هي أشرطتها السفلية. كل وجهة عنصر واحد له دور التبويب (وعلى SwiftUI زر له الصفة selected)، وحالة تحديده وموضعه، واسمه تسميته سواء رُسمت أم لم تُرسم. يقول قارئ الشاشة مثلًا: «الوارد، تبويب، 1 من 4، محدَّد، 3 غير مقروءة».
  • الأيقونة مخفية عن التقنيات المساعدة. وتضيف الشارة وصفها (badgeLabel، أو الرقم الذي تعرضه) بعد الاسم والحالة، ويضيف الرابط الذي يفتح سياقًا جديدًا نص targetLabel الخاص به بعد ذلك.
  • كل وجهة غير معطَّلة موضع توقف مستقل للمفتاح Tab، كروابط منطقة تنقل. يفعّل المفتاحان Enter و Space الوجهة التي عليها التركيز (المفتاح Enter فقط للرابط على الويب). الوجهات المعطَّلة لا تقبل التركيز وتُعلَن معطَّلة.
  • تُرسم حلقة التركيز حول صندوق الوجهة وتبقى داخل الشريط، لتركيز لوحة المفاتيح فقط.
  • سحب المؤشر لا يغيّر شيئًا مما يُعلَن حتى يُفلَت، ولا ينقل التركيز أبدًا.
  • النسخة المعكوسة الألوان من كل أيقونة وتسمية يغطيها المؤشر زخرفية: تلقى التقنيات المساعدة كل وجهة مرة واحدة، ولا تغيّر variant ما يُعلَن.
  • الخلية كلها مساحة اللمس: ارتفاع الشريط كاملًا وحصة متساوية من عرضه، بما لا يقل عن الحد الأدنى للهدف. والشريط الأضيق من أن يسع وجهاته يقسم عرضه بالتساوي مع ذلك: تضيق الخلايا دون الهدف بدل أن تجعل الشريط أعرض من حيّزه.
  • النص الأكبر يكبّر الشريط، ومعه صندوق كل وجهة، لتبقى التسميات في سطر واحد. أما الأيقونات فلا تُكبَّر. والتسمية المقتطعة تبقى كاملة اسمًا للوجهة. وحيث يوفّر iOS عارض المحتوى المكبَّر (large-content viewer)، يُظهر الضغط المطوَّل أيقونة الوجهة وتسميتها مكبَّرتين.
  • تبلّغ بنيات التنقيح عن أقل من ثلاث وجهات أو أكثر من خمس، ووجهة بلا تسمية، وقيمة لا تسمّي أي وجهة، وقيمة مستخدَمة مرتين. وتبلّغ أيضًا عن رابط يفتح سياقًا جديدًا بلا targetLabel، وعن href تسمّي سكربتًا أو تتعذّر قراءتها، وعن قيمة target لا يعرّفها الشريط.

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

  • ليس في SwiftUI و Compose و Flutter و React Native وجهات هي روابط: الوجهة لا تأخذ href هناك بعد.
  • على الويب: كل حجم يتبع rem، فتكبير حجم النص في المتصفح يكبّر كذلك حاويات العناصر والأيقونات. أما تكبير خطوط Tanrei وحدها فلا يفعل.
  • على SwiftUI: الحد الأدنى للخلايا هو هدف المنصة نفسها، 44 نقطة على macOS، حيث تسمّي المواصفة هدف اللمس ذا 48 نقطة.
  • على SwiftUI: خارج iOS، تحمل الوجهة موضعها نصًّا لقيمتها («2 of 4»)، بالإنجليزية فقط. والأيقونة التي هي صورة، مثل SF Symbol، لا تتبع التحديد: مرِّر الأيقونة المعبَّأة للوجهة المحدَّدة بنفسك. وعارض المحتوى المكبَّر مبنيّ في المكوّن لكن لم يختبره اختبار بعد.
  • ليس في Compose قيمة على مستوى الشريط، فالقيمة التي لا تسمّي أي وجهة ليس لها ما يُبلَّغ عنه: لا تعلّم أي عنصر بالخاصية selected لئلا يُحدَّد شيء.
  • ليس في دلالات Flutter موضع ضمن المجموعة، فلا يُعلَن «1 من 4».
  • يبدّل SwiftUI و Flutter و React Native فصيلة الأيقونة وتعبئتها فورًا بدل أن يجري ذلك على حركة التحديد.
  • ليس في React Native عارض محتوى مكبَّر. وتحمل الوجهة موضعها نصًّا لقيمتها («1 of 4»)، بالإنجليزية ما لم تمرِّر positionText.
  • على React Native: حركة التحديد، والهوامش، وما يعلنه TalkBack و VoiceOver لم تُفحص بعد على جهاز.
  • على كل المنصات، في النظام الافتراضي: تباين المؤشر من الطراز tonal مع الشريط نحو 1.15:1، ويشير إليه فحص إمكانية الوصول في iOS. والأيقونة المعبَّأة ولون التسمية يعلّمان التحديد أيضًا، والقيمة filled تعطي مؤشرًا بتباين 3:1 على الأقل حيث يهمّ ذلك.

ما عليك فعله

  • أعطِ كل وجهة تسمية، حتى في شريط لا يرسم أي تسمية.
  • سمِّ الشريط بالخاصية label، مثل «الرئيسي»، وخصوصًا حين تكون في الشاشة منطقة تنقل أخرى.
  • أعطِ كل شارة قيمة badgeLabel تقول ما الذي تعدّه، وأعطِها للنقطة دائمًا.
  • أبقِ قيمة كل وجهة فريدة وثابتة.
  • في موقع من صفحات منفصلة، اضبط value في كل صفحة على وجهة تلك الصفحة. الشريط لا يحدّد رابطًا من تلقاء نفسه.
  • أعطِ الرابط الذي يفتح تبويبًا جديدًا targetLabel بلغة منتجك، مثل «يُفتح في تبويب جديد». ولا يقدّم Tanrei كلمات من عنده.
  • على Compose، علِّم عنصرًا واحدًا بالضبط بالخاصية selected، أو لا تعلّم شيئًا. وعلى Flutter، قارن الفهرس الذي تتلقاه مع selectedIndex لتفرّق بين إعادة التحديد والتغيير.
  • على React Native، مرِّر positionText بلغة منتجك، وأعطِ TanreiProvider هوامش النافذة.