عرض التبويبات
يعرض عرض التبويب المحدَّد تحت صف Tabs خاص به، أو مرتبطًا بصف موضوع في مكان آخر، ويتيح للشخص أن يسحب من عرض إلى عرض ومؤشر التبويبات يتبع الإصبع.
- الفئة
- التنقل
- الرموز
comp.tab-view- المستوى
- مركّب
- مبني من
- Tabs
- Web
tanrei-tab-viewschedule - SwiftUI
TanreiTabViewradio_button_unchecked - Jetpack Compose
TanreiTabViewradio_button_unchecked - Flutter
TanreiTabViewradio_button_unchecked - React Native
TabViewradio_button_unchecked
مولَّد من docs/status/status.json, specs/tab-view.spec.json
يحمل عرض التبويبات لوحة واحدة لكل تبويب ويعرض لوحة التبويب المحدَّد. تحديد تبويب يبدّل العرض بإزاحة قصيرة وتلاشٍ وتغبيش؛ ويستطيع الشخص كذلك أن يسحب العرض جانبيًّا، فيتبع مؤشر التبويبات الإصبع ويستقر على التبويب الذي يحطّ عليه العرض. ومع تقليل الحركة يكون التبديل تلاشيًا متقاطعًا يظل يتبع الإصبع، بلا إزاحة ولا تغبيش.
عرض تبويبات بصفّه الخاص: اختر تبويبًا، أو اسحب اللوحة باللمس أو بالقلم
ثلاث مهام مفتوحة، مستحقة هذا الأسبوع.
علّقت Ada على الخطة قبل ساعة.
مرئي لكل من في مساحة العمل.
لعرض التبويبات هيئتان:
- بصفّه الخاص، وهي الافتراضية. يرسم العرض صفًّا من Tabs في أعلاه، واللوحات تحته مباشرة. تحمل كل لوحة خصائص تبويبها
labelوiconوlabelHiddenوdisabled، فتصريح واحد يصنع التبويب وعرضه معًا. وخصائص العرضvariantوsizeوtrackوfullWidthوlabelوgroupوrememberAsهي خصائص الصف. والصف مكوّن Tabs حقيقي: المسار والمؤشر نفساهما، والرموز (tokens) والطُرُز والسلوك نفسها، فالطبقة التي تعيد تنسيق التبويبات تعيد تنسيقه أيضًا. وهو لا يتحرك ولا يتلاشى ولا يتغبّش مع التبديل أبدًا. - مرتبط، لتبويبات موضوعة في مكان آخر. الخاصية
tabsتربط العرض بصف في شريط تطبيق أو في ترويسة ثابتة. ما دامت معيَّنة لا يرسم العرض صفًّا خاصًّا به، حتى حين يكون ذلك الصف مفقودًا، وخصائص صفّه وخصائص التبويب في اللوحات لا تُستخدم.
بصفّه الخاص يُصدر العرض change مع كل تحديد، من تبويب أو من سحب، ولا يصلك change الخاص بالصف. وفي الهيئة المرتبطة تُصدر التبويبات change للتبويب ويُصدره العرض للسحب، فيُعلَن كل تحديد مرة واحدة.
عروض التبويبات التي تسمّي group نفسها تعرض الاختيار نفسه، والخاصية rememberAs تحفظه على جهاز الشخص. وتبويبات المحتوى في هذا الموقع مثال: كل مجموعة من عيّنات المنصات عرض تبويبات ثانوي من مجموعة واحدة، يُحفظ تحت مفتاح الموقع الخاص.
على الويب هو <tanrei-tab-view> يحمل أبناء من <tanrei-tab-panel>، وهو العنصر نفسه الذي يستخدمه صف Tabs للوحاته الخاصة. اللوحة المحدَّدة هي value للعرض، وتبقى مساوية لقيمة value لتبويباته: تعيين أي منهما يعيّن الآخر، بلا حدث. وحين يكون العرض مرتبطًا تسمّي tabs المعرِّف id لعنصر <tanrei-tabs> في المستند نفسه أو الجذر الظلّي (shadow root) نفسه. وتُكتب swipeable بعكسها، no-swipe، لأن السمة المنطقية في HTML قيمتها الافتراضية false.
لم تنفّذ SwiftUI و Compose و Flutter و React Native عرض التبويبات بعد. وإلى أن تنفّذه، يعرض صف Tabs اللوحة المحدَّدة هناك، كما تصف صفحته، من دون السحب.
<!-- بصفّه الخاص (الافتراضي): كل لوحة تصرّح بتبويبها. -->
<tanrei-tab-view label="المشروع">
<tanrei-tab-panel value="overview" label="نظرة عامة" icon="dashboard">…</tanrei-tab-panel>
<tanrei-tab-panel value="activity" label="النشاط" icon="history">…</tanrei-tab-panel>
<tanrei-tab-panel value="billing" label="الفوترة" icon="payments" disabled>…</tanrei-tab-panel>
</tanrei-tab-view>
<!-- مرتبط، لتبويبات موضوعة في مكان آخر (شريط تطبيق، ترويسة ثابتة): العرض لا يرسم صفًّا. -->
<tanrei-tabs id="project-tabs" label="المشروع">
<tanrei-tab value="overview" label="نظرة عامة"></tanrei-tab>
<tanrei-tab value="activity" label="النشاط"></tanrei-tab>
</tanrei-tabs>
<tanrei-tab-view tabs="project-tabs">
<tanrei-tab-panel value="overview">…</tanrei-tab-panel>
<tanrei-tab-panel value="activity">…</tanrei-tab-panel>
</tanrei-tab-view>
<!-- العروض التي تسمّي مجموعة واحدة تعرض اختيارًا واحدًا. و remember-as تحفظه على الجهاز، تحت مفتاحك أنت. -->
<tanrei-tab-view variant="secondary" group="platform" remember-as="my-docs-platform" label="المنصة">
<tanrei-tab-panel value="web" label="Web">…</tanrei-tab-panel>
<tanrei-tab-panel value="flutter" label="Flutter">…</tanrei-tab-panel>
</tanrei-tab-view>
<script type="module">
import '@tanrei/web/components/tab-view'; // يعرّف التبويبات واللوحات أيضًا
// حدث change واحد لكل تحديد. بصفّه الخاص: حدث العرض، للتبويب وللسحب.
// مرتبط: حدث التبويبات للتبويب، وحدث العرض للسحب. في مجموعة: العرض الذي استخدمه الشخص فقط.
document.addEventListener('change', (e) => console.log(e.target.localName, e.detail.value));
</script>import { Tab, TabPanel, Tabs, TabView } from '@tanrei/react';
// بصفّه الخاص: onChange واحدة تسمع كل تحديد، من تبويب أو من سحب.
<TabView label="المشروع" value={view} onChange={(e) => setView(e.detail.value)}>
<TabPanel value="overview" label="نظرة عامة" icon="dashboard"><Overview /></TabPanel>
<TabPanel value="activity" label="النشاط" icon="history"><Activity /></TabPanel>
</TabView>
// مرتبط: التبويبات تُعلِن التبويب، والعرض يُعلِن السحب. حدِّث حالتك من الاثنين.
<Tabs id="project-tabs" label="المشروع" value={view} onChange={(e) => setView(e.detail.value)}>
<Tab value="overview" label="نظرة عامة" />
<Tab value="activity" label="النشاط" />
</Tabs>
<TabView tabs="project-tabs" onChange={(e) => setView(e.detail.value)}>
<TabPanel value="overview"><Overview /></TabPanel>
<TabPanel value="activity"><Activity /></TabPanel>
</TabView>
// اختيار مشترك ومحفوظ. و noSwipe هي swipeable في المواصفة، معكوسة.
<TabView variant="secondary" group="platform" rememberAs="my-docs-platform" label="المنصة" noSwipe>
<TabPanel value="web" label="Web"><WebSample /></TabPanel>
<TabPanel value="flutter" label="Flutter"><FlutterSample /></TabPanel>
</TabView>صف ثانوي يملأ العرض، مع أيقونات وتبويب معطَّل؛ وعرض مرتبط بصف موضوع بعيدًا عنه
ثلاث خدمات، كلها تعمل.
نُشر قبل ساعتين.
الفوترة غير مُعدّة.
اليوم 1: الوصول، وجولة في البلدة القديمة.
أربعة أماكن محفوظة قرب المحطة.
المتحف مغلق أيام الاثنين.
البنية
| الجزء | الوصف |
|---|---|
1 tabs · اختياري | The row of tabs the Tab view draws above its container while it is not linked to a row placed elsewhere (TBV-16): a Tabs, whole (tab list, tabs, active indicator, underline and divider), drawn from the tabs' own tokens, with one tab for each panel, named and marked as the panel says. Linked to another row, the Tab view draws none (TBV-17). |
2 container | The region that holds the panels, below the row when the Tab view draws one: as wide as the Tab view and as tall as the shown panel. It draws nothing of its own, takes the swipe, and clips the panels to its corners while they travel and blur. The row is outside it and is never moved, faded or blurred by a switch. |
3 panel | One view, matched to the tab of the same value. While the Tab view draws its own row, the panel also says what its tab shows (a label, an icon) and whether it is disabled. One panel is shown at rest; during a switch the old and the new panel are drawn in the same place, the new one over the old. Its content is the product's. |
4 focus-ring | Gap-then-ring indicator shown for keyboard focus on a panel that holds nothing focusable, drawn inside the container's edge. |
الخصائص
| الخاصية | النوع | القيمة الافتراضية | الوصف |
|---|---|---|---|
value | string | null | The value of the selected tab, whose panel is shown. The Tab view keeps it equal to its tabs' value, whether it draws them or is linked to them. null shows the panel of the tab the tabs select while theirs is null (the first enabled tab). |
variant | primary | secondary | tonal | "primary" | The variant of the row the Tab view draws itself, with the Tabs' meaning: primary for the main views of a screen, secondary for views within one of them, tonal for the quiet row of a sample or a setting. A design system may add values to the tabs' axis, and the row takes them. Ignored while the Tab view is linked to a row placed elsewhere, which has its own. |
size | small | medium | "medium" | The size of the row the Tab view draws itself, with the Tabs' meaning: medium for the views of a screen, small for a row in a card's or a sample's header. A design system may add values to the tabs' axis, and the row takes them. Ignored while the Tab view is linked to a row placed elsewhere, which has its own. |
track | boolean | null | Whether the row the Tab view draws itself draws its track, with the Tabs' meaning: false draws none, true draws it, and null, the default, takes comp.tabs.track.visible, the system's choice. Ignored while the Tab view is linked to a row placed elsewhere, which has its own. |
fullWidth | boolean | false | How the row the Tab view draws itself lays out its tabs, with the Tabs' meaning: false (hug) sizes each tab to its label and scrolls the row when they do not fit; true (fill) shares the row's width among the tabs. The Tab view itself is always as wide as its parent. Ignored while the Tab view is linked to a row placed elsewhere. |
label | string | "" | The accessible name of the row the Tab view draws itself: what the views are views of. Needed when a screen has more than one row of tabs. Ignored while the Tab view is linked to a row placed elsewhere, which is named by its own label. |
tabs | string | "" | Empty (the default): the Tab view draws its own row of tabs above its panels, one per panel. Set: the Tab view is linked to a Tabs placed elsewhere (in an app bar, a sticky header) and draws no row; those tabs name its panels, move between them without a swipe and draw their indicator where a swipe has brought it. How a platform names the link is in its mapping. |
swipeable | boolean | true | Lets a person switch panels by dragging the shown panel sideways. False leaves the switch to the tabs alone, for panels whose content is itself dragged sideways (a map, a drawing surface). |
transition | auto | slide | fade | none | "auto" | The effect of switching views: the form in which the shown panel gives way to the next. slide: the panels travel, and fade and blur on the way, as the system's transition values say, and follow a swipe. fade: the two panels crossfade in place. none: the new panel is shown whole in one frame, and the row the Tab view draws puts its indicator on the new tab in that same frame (TBV-22, TBV-23). Auto takes comp.tab-view.motion.transition, the design system's answer (slide in the default system). Where motion is reduced slide is drawn as fade, and nothing is ever added to fade or none. It applies whether the Tab view draws its row or is linked to one; a linked row keeps its own transition. |
group | string | "" | The group of the row the Tab view draws itself, with the Tabs' meaning: Tab views and rows of tabs that name the same group show the same choice, and a view that has no panel of that value keeps its own. Empty (the default): the view's choice is its own. Ignored while the Tab view is linked to a row placed elsewhere, which names its own group. |
rememberAs | string | "" | The key under which the row the Tab view draws itself keeps a person's choice on their device and restores it before the view is first drawn, with the Tabs' meaning. Empty (the default): nothing is read or written. Ignored while the Tab view is linked to a row placed elsewhere, which names its own key. |
الأحداث
| الحدث | الوصف |
|---|---|
change | The user selected another panel; carries its value, which value and the tabs' value now hold. The Tab view announces every selection made on its own row and every swipe. While it is linked, a selection made on the linked tabs is announced by their own change instead, so every selection is announced once. |
العناصر
أنواع العناصر التي يكررها المكوّن. يسرد كل نموذج اختبار عناصره بالترتيب، وتسمّي كل منصة كل نوع بأسلوبها.
panel
One view of the Tab view: the content shown while the tab with its value is selected, and, for the row the Tab view draws itself, what that tab shows.
| الخاصية | النوع | القيمة الافتراضية | الوصف |
|---|---|---|---|
value | string | "" | The value of the tab that shows this panel. Unique within the Tab view; required. |
label | string | "" | The text of this panel's tab in the row the Tab view draws itself, and the accessible name of both the tab and the panel. Required there, even when labelHidden is true; not used while the Tab view is linked, where the linked row's own tab names the panel. |
icon | string | "" | The name of an icon (a glyph of the system's icon set) for this panel's tab in the row the Tab view draws itself. Give every panel an icon, or none. Not used while the Tab view is linked. |
labelHidden | boolean | false | Draws this panel's tab as its icon alone and keeps label as its accessible name (an icon-only tab), in the row the Tab view draws itself. Not used while the Tab view is linked. |
disabled | boolean | false | Makes this panel's tab disabled in the row the Tab view draws itself: a person cannot select it, and a swipe skips its panel. Not used while the Tab view is linked, where the linked row's own tab says whether it is disabled. |
| المواصفة | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| العنصر | tanrei-tab-panel | TanreiTabPanel | TanreiTabPanel | TanreiTabPanel | TabPanel |
value · الخاصية | value | tag | value | value | value |
label · الخاصية | label | title | label | label | label |
icon · الخاصية | icon | icon | icon | icon | icon |
labelHidden · الخاصية | labelHidden | labelStyle | labelHidden | labelHidden | labelHidden |
disabled · الخاصية | disabled | disabled | enabled | enabled | disabled |
| ملاحظات | The same element the tabs' own panels are: <tanrei-tab-panel value="overview" label="Overview" icon="dashboard">, a child of tanrei-tab-view, with label-hidden and disabled attributes, as tanrei-tab has them. label, icon, label-hidden and disabled are read only by a tanrei-tab-view that draws its own row; a panel of a tanrei-tabs, or of a linked view, ignores them. | TanreiTabPanel(_ title:, icon:) { … }.tag(value) in the view's builder, as the tabs' TanreiTab is; .labelStyle(.iconOnly) is labelHidden and .disabled(_:) is disabled. A linked view's panels may omit the title: TanreiTabPanel { … }.tag(value). | TanreiTabPanel(value, label, icon, labelHidden, enabled) { … } called in the TanreiTabView { } content, a scope that collects the panels (as a lazy list's content does) so that the row can be drawn from their labels before a panel is composed. enabled is the inverse flag. | TanreiTabPanel<T>(value:, label:, icon:, labelHidden:, enabled:, child:) entries of the view's panels: list, as the bar's TanreiTab<T> entries are; enabled is the inverse flag. | <TabPanel value="overview" label="Overview" icon="dashboard">…</TabPanel> children of <TabView>; labelHidden and disabled as on the row's Tab. |
الحالات
السلوك
متطلبات بصيغة EARS. يُختبر تنفيذ كل منصة مقابل هذه المعرّفات. يُعرض نص المتطلبات كما ورد في ملف المواصفة، بالإنجليزية.
| المعرّف | المتطلب |
|---|---|
TBV-1 | THE Tab view SHALL show the panel whose value is the value of its tabs' selected tab, its tabs being the row it draws itself (TBV-16) or the row it is linked to (TBV-17) (TAB-11: the first enabled tab while their value is null), and hide every other panel; THE Tab view SHALL keep its own value equal to its tabs' value, and WHEN either of them is set THE Tab view SHALL set the other to the same value without emitting change. |
TBV-2 | WHEN its tabs' value changes while their indicatorOffset is 0 (the user selected a tab, by activating it or by releasing a drag across the row on it, TAB-1, TAB-12 and TAB-29, or the product set it) THE Tab view SHALL switch from the shown panel to the newly selected tab's panel as TBV-5 and TBV-6 say, in the form its transition names (TBV-22); WHILE it is linked THE Tab view SHALL NOT emit change for that selection, which the linked tabs have announced themselves, and WHILE it draws its own row it SHALL announce it as TBV-18 says. WHERE their value changes because they follow a choice of their group that was made on another member, or one restored from storage, THE Tab view SHALL show the panel at once instead, as TBV-20 says. |
TBV-3 | WHILE swipeable is true, WHEN the user drags the shown panel by touch or pen past the platform's touch slop, further sideways than up or down, THE Tab view SHALL take the drag and track it with a progress p, the drag's sideways distance divided by the width of its container and kept between 0 and 1: toward the next enabled tab of its tabs when the drag moves toward the start edge, and toward the previous enabled tab when it moves toward the end edge. In every frame of the drag THE Tab view SHALL draw the switch to that tab's panel at p (TBV-5) and set its tabs' indicatorOffset to p toward the next tab or to −p toward the previous one; WHERE there is no enabled tab in the drag's direction THE Tab view SHALL leave the panel at rest and indicatorOffset at 0. A drag that is further up or down than sideways when it passes the slop SHALL be left to vertical scrolling for its whole length. A drag that starts on the row the Tab view draws itself is the row's (its tabs are pressed, its active indicator follows the drag, TAB-28, and it scrolls sideways itself) and SHALL NOT be taken; WHILE its tabs follow a drag of their own, in its own row or in the linked row, THE Tab view SHALL take no drag and SHALL leave the shown panel at rest until that drag's release selects a tab (TBV-2): the indicator changes continuously under the finger, but which panel is shown is not decided before the release. |
TBV-4 | WHEN the user releases a drag the Tab view has taken THE Tab view SHALL commit it if p is at least 0.5 or the release is a fling toward the tab, and cancel it otherwise, a fling away from the tab always cancelling; 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 Tab view 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. To commit THE Tab view SHALL set its value and its tabs' value to that tab's value and emit change exactly once with it, re-express indicatorOffset from the new tab (1 − p toward the tab it left) in the same frame, and finish the switch while bringing indicatorOffset to 0 over comp.tab-view.motion.switch-duration with comp.tab-view.motion.switch-easing, starting from where the release left them. To cancel THE Tab view SHALL bring the switch back to p = 0 and indicatorOffset to 0 over the same motion and emit nothing. |
TBV-5 | WHILE a switch from panel A to panel B is at progress p THE Tab view SHALL draw both panels in the same place, B over A, with a travel D of comp.tab-view.transition.distance plus comp.tab-view.transition.distance-fraction times the width of its container: A moved p × D toward the edge it leaves by (the start edge when B's tab comes after A's among its tabs, the end edge otherwise), at an opacity of 1 − p × (1 − comp.tab-view.transition.opacity) and blurred by p × comp.tab-view.transition.blur; B moved (1 − p) × D from the opposite edge, at an opacity of 1 − (1 − p) × (1 − comp.tab-view.transition.opacity) and blurred by (1 − p) × comp.tab-view.transition.blur. The travel SHALL follow p as eased, an overshoot included; the opacity and the blur SHALL follow p clamped between 0 and 1. THE Tab view SHALL clip both panels to its container, its corners rounded by comp.tab-view.container.shape, so that neither travel nor blur draws outside it or over the row it draws itself; WHERE a platform cannot blur a view THE Tab view SHALL draw the travel and the fade without the blur and say so in its README. |
TBV-6 | WHEN a switch starts from a selection on its tabs or by the product (TBV-2) THE Tab view SHALL run p from 0 to 1 over comp.tab-view.motion.switch-duration with comp.tab-view.motion.switch-easing; WHEN the selection changes again, or the user starts a drag, before a switch has ended THE Tab view SHALL end that switch at once, as if it had finished, and start from the panel it switched to. WHEN the Tab view first appears, or its panels are added, removed or given other values, or it is linked or unlinked (TBV-17), THE Tab view SHALL show the selected tab's panel at once, without a switch. |
TBV-7 | WHERE motion is reduced THE Tab view SHALL draw every switch as TBV-5 says with comp.tab-view.transition.distance, comp.tab-view.transition.distance-fraction and comp.tab-view.transition.blur taken as 0, so that only the fade is left: during a drag the panels crossfade with p, and a switch from a selection, and the rest of a switch after a release, SHALL run over comp.tab-view.motion.fade-duration with comp.tab-view.motion.fade-easing. Its tabs' indicatorOffset SHALL still follow the drag, which the person's finger moves, and SHALL return to 0 at once on release. |
TBV-8 | THE Tab view SHALL NOT take a drag that starts on its own row or while its tabs follow a drag of their own (TBV-3), on content that handles sideways drags itself (a region that scrolls sideways, a slider, a carousel, a text field, selected text or its handles) or that is made by mouse, trackpad or wheel, and WHILE swipeable is false THE Tab view SHALL take no drag at all; in every such case the panels switch only through the tabs (TBV-2). |
TBV-9 | THE Tab view SHALL expose the shown panel with the tab panel role, named by its tab and associated with it where the platform can express that (the tab controls the panel; the panel is labelled by the tab), whether the tab is in the row it draws itself or in the row it is linked to, SHALL hide every other panel from assistive technology and from focus, including a panel that a switch or a drag still draws, SHALL move that exposure to the new panel when value changes rather than when the switch's drawing ends, and SHALL make a shown panel with no focusable content itself focusable so that Tab reaches it. |
TBV-10 | WHEN a switch hides the panel that holds keyboard focus THE Tab view SHALL move focus to its tabs' selected tab. WHEN a panel with no focusable content receives keyboard focus THE Tab view SHALL show the focus ring inside the container's edge, its corners following comp.tab-view.container.shape, as sys.behavior.focus-style names (ring or glow), without animation; focus gained by pointer or touch SHALL NOT show it; a platform that cannot draw a choice SHALL fall back to the first value and state that in its README. |
TBV-11 | THE Tab view SHALL be as wide as its parent and, unless the product sizes it through the platform's own sizing, as tall as the row it draws itself (none while it is linked) plus its shown panel; WHILE a switch draws two panels its container SHALL be as tall as the taller of them, and SHALL take the new panel's height at once when the switch ends. THE Tab view SHALL place its own row at its top edge, across its whole width, with the container directly below it and no space between them. WHILE the product sizes it THE Tab view SHALL keep its own row at its own height at the top, and make each panel fill the rest and scroll up and down inside it, below the row. |
TBV-12 | THE Tab view SHALL keep a hidden panel and its state (its scroll position, what was typed into it), so that it is shown as it was left, and SHALL NOT discard or recreate a panel because it is hidden, nor because its tab in the row it draws itself changes. |
TBV-13 | WHEN the layout direction is right-to-left THE Tab view SHALL mirror the drag and the travel with the reading order: a drag toward the start edge, which is the right, moves toward the next tab, and a panel leaves by the start edge when the tab it gives way to comes after its own; THE Tab view SHALL NOT mirror a panel's content. Its own row SHALL mirror as TAB-21 says. |
TBV-14 | WHEN tabs names a row that is not there (on a platform that links by an object, a link that no row uses) THE Tab view SHALL show the panel whose value equals the view's value (the first panel while the view's value is null), take no drag, expose no panel with the tab panel role, draw no row of its own (TBV-17), and in debug builds report that its panels have no tabs; WHEN the selected tab has no panel THE Tab view SHALL show none and in debug builds report the value. WHILE the Tab view draws its own row and holds no panel THE Tab view SHALL draw that row with no tab and in debug builds report that it has no panels. |
TBV-15 | WHEN a panel's value is empty, repeats another panel's, or, while the Tab view is linked, is the value of no tab of the linked row THE Tab view SHALL still hold it, show only the first panel with a given value, give its own row no tab for any other, and in debug builds report the panel; WHEN the linked row holds panels of its own THE Tab view SHALL show and associate its own panels in their place, keep the tabs' panels hidden, and in debug builds report the tabs' panels. |
TBV-16 | WHILE tabs is empty THE Tab view SHALL draw its own row of Tabs above its container (TBV-11) and be linked to that row as it would be to a row placed elsewhere: one tab for each panel that TBV-15 shows, in the panels' order, whose value, label, icon, labelHidden and disabled are the panel's; the row's variant, size, track, fullWidth, label, group and rememberAs SHALL be the Tab view's own, its transition what TBV-23 says, and its value the Tab view's value. The row SHALL be a Tabs in every respect, drawn from the tabs' tokens of its variant and size with every TAB behaviour (among them the press answer of TAB-8 to TAB-10, the keyboard of TAB-12, the names of TAB-13 and TAB-22, and the indicator of TAB-17 and TAB-26), and SHALL change in the same frame as the panels it is drawn from are added, removed, reordered or given another value, label, icon, labelHidden or disabled. |
TBV-17 | WHILE tabs names a row THE Tab view SHALL draw no row of its own, whether or not the named row is there (TBV-14), and SHALL use none of its variant, size, track, fullWidth, label, group and rememberAs or of its panels' label, icon, labelHidden and disabled, which the linked row's own props and tabs replace; WHEN tabs names a row while variant, size, track, fullWidth, label, group or rememberAs differ from their defaults THE Tab view SHALL in debug builds report that they apply only to a row it draws itself. WHEN tabs changes from empty to a row, or back, THE Tab view SHALL remove or draw its own row at once, keep its value (giving it to the newly linked row, or taking that row's while its own is null) and show the selected tab's panel at once (TBV-6). |
TBV-18 | WHILE the Tab view draws its own row, WHEN the user selects another tab on that row (TAB-1, TAB-12, TAB-29) THE Tab view SHALL set its value to that tab's value and emit change exactly once with it, in the same frame as the row sets its own value, and SHALL NOT let the row's own change reach the product as a second announcement; together with TBV-4 every selection a person makes on a Tab view with its own row, by a tab or by a swipe, SHALL be announced once, by the Tab view. |
TBV-19 | THE Tab view 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 Tab view is shown THE Tab view SHALL draw itself for the new direction at once, without a motion of its own, keeping the selected panel and its focus. |
TBV-20 | WHEN its tabs' value changes because their group's choice was made on another member or restored from storage (TAB-36, TAB-38, TAB-39) THE Tab view SHALL show the newly selected tab's panel at once, with none of the travel, fade and blur of TBV-5 and without the motion of TBV-6 or TBV-7, SHALL take that panel's height in the same frame (TBV-11), SHALL keep the panel it hides and its state (TBV-12), SHALL move the panels' exposure to assistive technology in that frame (TBV-9), and SHALL emit no change. WHERE that hides the panel that holds keyboard focus THE Tab view SHALL move focus to its tabs' selected tab as TBV-10 says, without scrolling anything to show it. WHERE the Tab view follows a swipe when the choice changes THE Tab view SHALL end the swipe without committing it, emit nothing for it, and show the chosen panel at rest. THE Tab view a person selected on SHALL switch as TBV-2 and TBV-6 say and announce the selection once (TBV-18, or the linked tabs' change). |
TBV-21 | WHEN a person commits a swipe (TBV-4) on a Tab view whose tabs are a member of a group and, in following that choice, other members change the size of what lies before the Tab view in a region that scrolls, THE Tab view SHALL keep its container where the person is using it, as TAB-40 says of a row: in the frame that first draws the new choice the container's top edge SHALL be at the place in the window that it had in the frame before, the scroll positions of the regions it scrolls in having been moved by the difference at once and without a scrolling motion, as far as each region can scroll. A selection made on a tab SHALL be kept in place by the row (TAB-40), whether the Tab view drew that row or is linked to it. |
TBV-22 | THE Tab view SHALL draw every switch between two panels in the form its transition names: the name transition gives, or, WHILE transition is auto, the name comp.tab-view.motion.transition holds (slide, fade or none); WHEN transition names a value the Tab view does not define THE Tab view SHALL treat it as auto and in debug builds report it. WHILE the form is slide THE Tab view SHALL switch as TBV-3 to TBV-6 say. WHILE the form is fade THE Tab view SHALL draw every switch as TBV-5 says with comp.tab-view.transition.distance, comp.tab-view.transition.distance-fraction, comp.tab-view.transition.blur and comp.tab-view.transition.opacity all taken as 0, so that the two panels crossfade in place: a switch from a selection, and the rest of a switch after a swipe is released, SHALL run over comp.tab-view.motion.fade-duration with comp.tab-view.motion.fade-easing, and during a swipe the panels SHALL crossfade with p (TBV-3). WHILE the form is none THE Tab view SHALL show the newly selected tab's panel whole in the frame the selection is made, with none of the travel, fade and blur of TBV-5 and no motion of TBV-6, SHALL take that panel's height in the same frame (TBV-11), SHALL keep the panel it hides and its state (TBV-12) and SHALL move the panels' exposure and keyboard focus as TBV-9 and TBV-10 say — the drawing TBV-20 gives a choice made elsewhere; a swipe SHALL still be taken while swipeable is true (TBV-3), SHALL leave the shown panel at rest for its whole length while its tabs' indicatorOffset follows the finger, and WHEN it is released SHALL be committed or cancelled as TBV-4 decides, the new panel shown at once and indicatorOffset brought to 0 at once. In every form THE Tab view SHALL select, emit and announce as it does under slide (TBV-4, TBV-18): the form changes what is drawn between two panels and nothing else. |
TBV-23 | WHERE motion is reduced THE Tab view SHALL draw the form slide as TBV-7 says, the form fade as TBV-22 says, and the form none as none: reduced motion SHALL never add a motion that transition took away. WHILE the Tab view draws its own row (TBV-16) and the form is none, THE Tab view SHALL give that row the tabs' transition none (TAB-43), so that the indicator is on the new tab in the frame the new panel is shown and nothing is left traveling over a view that has already changed; WHILE the form is slide or fade the row's transition SHALL be auto, the tabs' own answer (comp.tabs.motion.transition). THE Tab view SHALL NOT set the transition of a row it is linked to (TBV-17), which has its own. WHEN transition changes while a switch is drawn THE Tab view SHALL end that switch at once, as if it had finished (TBV-6), and SHALL draw the next one in the new form; changing transition SHALL NOT change value, the shown panel, keyboard focus or what is emitted. |
TBV-24 | 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 tab view 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 tab list of the row it draws itself (TBV-16), as the tabs of that row expose what is set on theirs — an id reference by the elements it names in the host's tree or a tree around it, a description after its own; WHILE label is not empty, label is the tab list's name, over a name given so; WHILE label is empty, a name given so is the tab list's name. 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 tab view SHALL take it off that element. WHILE the tab view is linked to tabs placed elsewhere (TBV-17) it draws no row, so what the product sets on its host reaches no element: THE tab view SHALL move it all the same and report it in debug builds, and the product sets it on the linked tabs instead. |
مطابقة الأسماء بين المنصات
التكافؤ لا يعني واجهات برمجية متطابقة. تعبّر كل منصة عن العقد نفسه بأسلوبها.
| المواصفة | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| المكوّن | tanrei-tab-view | TanreiTabView | TanreiTabView | TanreiTabView | TabView |
value · الخاصية | value | selection | selectedValue | value | value |
variant · الخاصية | variant | variant | variant | variant | variant |
size · الخاصية | size | size | size | size | size |
track · الخاصية | track | track | track | track | track |
fullWidth · الخاصية | fullWidth | fullWidth | fullWidth | isScrollable | fullWidth |
label · الخاصية | label | label | contentDescription | semanticLabel | accessibilityLabel |
tabs · الخاصية | tabs | link | link | link | link |
swipeable · الخاصية | noSwipe | swipeable | swipeable | swipeable | swipeable |
transition · الخاصية | transition | transition | transition | transition | transition |
group · الخاصية | group | selection | selectedValue | value | value |
rememberAs · الخاصية | rememberAs | selection | selectedValue | value | value |
change · الحدث | change | selection | onSelectedValueChange | onChanged | onValueChange |
| ملاحظات | Its own row: <tanrei-tab-view label="Project"> with tanrei-tab-panel children, and the attributes variant and full-width for the row. The view draws its row as a tanrei-tabs element of its own, placed as its first light-DOM child (in a tabs slot above the container) and marked as the view's, with one tanrei-tab per panel, and links itself to it as to any row. It is in the light DOM, not the shadow root, so that the tab's aria-controls and the panel's aria-labelledby point within one tree; the product does not edit it. The row's change stops at the view, which fires its own (TBV-18). Linked: <tanrei-tabs id="project-tabs"> … </tanrei-tabs> <tanrei-tab-view tabs="project-tabs">: tabs is the id of a tanrei-tabs in the same tree (as popovertarget is), and the tabsElement property sets the element itself; either one set means the view draws no row, even while the element is missing. The selected panel is the view's value, kept equal to its tabs' value in both directions; the view sets its own value before firing change, as a native control does. no-swipe is the inverse of swipeable, since an HTML boolean attribute defaults to false. The view writes its tabs' indicatorOffset property during a drag and its settle. The drag is Pointer Events on touch and pen with touch-action: pan-y on the container (not on the row), so vertical scrolling stays the browser's; the travel is a transform, the fade opacity and the blur filter: blur(), on the two panels only while a switch runs. The panels keep their place in the light DOM; a hidden one has hidden and inert. A shared choice: group and remember-as are attributes with the properties group and rememberAs, and the view writes both onto the tanrei-tabs it draws, which is the member of the group; a linked view has neither, and its tanrei-tabs carries them. A view that follows writes its panels' hidden and inert in the frame its row changes and starts no switch. For a committed swipe (TBV-21) the view measures and corrects its container's top as its row does for a tab (TAB-40). Sixty views of one group cost one read of localStorage, one write for a choice, and one measured element. size and track are forwarded to the tanrei-tabs the view draws, as variant is (track is true, false or null, with the attribute forms of the tabs'). transition is an attribute and a property (auto, slide, fade, none); auto reads --tanrei-comp-tab-view-motion-transition. fade runs the code path of prefers-reduced-motion (TBV-7) with the opacity end taken as 0; none runs the code path of a choice that came from the group (TBV-20) and sets transition="none" on the view's own row, removing it again when the form is no longer none. | Its own row: TanreiTabView(selection: Binding<V>, variant:, fullWidth:, label:, swipeable:) { TanreiTabPanel … }; the view draws a TanreiTabs above its panels and links it through a TanreiTabLink of its own. Linked: TanreiTabView(selection:, link: TanreiTabLink, swipeable:) { … }, a second initializer without the row's parameters, so that a view cannot be given both; the link is an observable object the product creates once and passes to its tabs too (TanreiTabs(…, link:)), through which the tabs report their order and disabled tabs and read the indicator offset. Writing the binding is change: a tab on the view's own row and a swipe write it; a linked row writes it through its own binding. The drag is a DragGesture on the panels that yields to vertical scrolling; the blur is .blur(radius:), converted from the token's standard deviation. SwiftUI's page-style TabView is a full-width pager without a tab row, so it is not this component. A shared choice has no parameter here: views given the same Binding show the same choice, and the product remembers it in @AppStorage, as the tabs' mapping says. A view whose selection changes that way switches as TBV-2 says for a value the product sets, with its motion: this platform cannot tell a view that follows from one the product set, so TBV-20 and TBV-21 are not reached here, and the README says so. Not built on this platform yet: size and track are the row's, with the Tabs' meaning and type; transition (.auto, .slide, .fade, .none) is the form of a switch (TBV-22): fade is the crossfade the view already draws where motion is reduced, none the change at once it already draws for a choice made elsewhere (TBV-20), and under none the view's own row is given the tabs' transition none (TBV-23). | Its own row: TanreiTabView(selectedValue, onSelectedValueChange, modifier, variant, fullWidth, contentDescription, swipeable) { TanreiTabPanel(…) { … } }; the view composes a TanreiTabs above its panels, linked through a link it remembers itself. Linked: an overload TanreiTabView(selectedValue, onSelectedValueChange, link, modifier, swipeable) { … } without the row's parameters, with val link = rememberTanreiTabLink() passed to TanreiTabs(…, link = link) too; the tabs read the indicator offset from it as a frame-time lambda, so a drag does not recompose the row. State hoisting: the caller keeps selectedValue; with its own row the view calls onSelectedValueChange for a tab and for a swipe, and linked the caller moves it from both components' callbacks. Not HorizontalPager: the travel is a short offset, not a scroll, and hidden panels stay composed (TBV-12). The blur is Modifier.blur, which draws nothing below Android 12; there the switch has no blur, as the README says. A shared choice has no parameter here: views given the same hoisted selectedValue show the same choice, and the caller remembers it in its own store, as the tabs' mapping says. A view whose selectedValue changes that way switches as TBV-2 says for a value the product sets, with its motion: this platform cannot tell a view that follows from one the product set, so TBV-20 and TBV-21 are not reached here, and the README says so. Not built on this platform yet: size and track are the row's, with the Tabs' meaning and type; transition (Auto, Slide, Fade, None) is the form of a switch (TBV-22): fade is the crossfade the view already draws where motion is reduced, none the change at once it already draws for a choice made elsewhere (TBV-20), and under none the view's own row is given the tabs' transition none (TBV-23). | Its own row: TanreiTabView<T>(value:, onChanged:, variant:, isScrollable:, semanticLabel:, swipeable:, panels: [TanreiTabPanel<T>(…)]); the view builds a TanreiTabBar<T> above its panels, linked through a TanreiTabLink<T> of its own. isScrollable is the inverse of fullWidth and defaults to true (hug), as on the bar. Linked: TanreiTabView<T>.linked(link:, value:, onChanged:, swipeable:, panels:), a named constructor without the row's parameters, with the TanreiTabLink<T> the product creates and passes to TanreiTabBar(…, link:) too, as Flutter's TabController is shared by a TabBar and a TabBarView. It replaces the existing TanreiTabBarView, which named only the content; its semanticLabels give way to the tabs, through which each panel is named by its tab with Flutter's tab panel role. The blur is an ImageFiltered blur, converted from the token's standard deviation. A shared choice has no parameter here: views given the same value from one piece of the product's state show the same choice, and the product remembers it in its own store, as the bar' mapping says. A view whose value changes that way switches as TBV-2 says for a value the product sets, with its motion: this platform cannot tell a view that follows from one the product set, so TBV-20 and TBV-21 are not reached here, and the README says so. Not built on this platform yet: size and track are the row's, with the Tabs' meaning and type; transition (auto, slide, fade, none) is the form of a switch (TBV-22): fade is the crossfade the view already draws where motion is reduced, none the change at once it already draws for a choice made elsewhere (TBV-20), and under none the view's own row is given the tabs' transition none (TBV-23). | Its own row: <TabView value onValueChange variant fullWidth accessibilityLabel swipeable> with <TabPanel> children; the view renders a <Tabs> above its panels from their props, linked through a link of its own. Linked: <TabView value onValueChange link swipeable>, where const link = useTabLink() is passed to <Tabs link={link}> too and carries the indicator offset as an animated value, so a drag does not re-render the row; the props type is a union, so that link and the row's props cannot be given together. Controlled: with its own row the view calls onValueChange for a tab and for a swipe; linked, the caller moves value from both components' callbacks. React Native has no tab panel role and cannot blur a view without a native module, so the switch draws its travel and fade only, as the README says. A shared choice has no prop here: views given the same controlled value show the same choice, and the product remembers it in its own store, as the tabs' mapping says. A view whose value changes that way switches as TBV-2 says for a value the product sets, with its motion: this platform cannot tell a view that follows from one the product set, so TBV-20 and TBV-21 are not reached here, and the README says so. Not built on this platform yet: size and track are the row's, with the Tabs' meaning and type; transition ("auto", "slide", "fade", "none") is the form of a switch (TBV-22): fade is the crossfade the view already draws where motion is reduced, none the change at once it already draws for a choice made elsewhere (TBV-20), and under none the view's own row is given the tabs' transition none (TBV-23). |
الرموز
| الرمز | يشير إلى | القيمة المحلولة (فاتح) |
|---|---|---|
--tanrei-comp-tab-view-container-shape | sys.shape.corner.none | 0px |
--tanrei-comp-tab-view-motion-fade-duration | sys.motion.role.reduced | 100ms |
--tanrei-comp-tab-view-motion-fade-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-tab-view-motion-switch-duration | sys.motion.role.selection.travel | 250ms |
--tanrei-comp-tab-view-motion-switch-easing | sys.motion.easing.spatial | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-tab-view-motion-transition | — | 0 |
--tanrei-comp-tab-view-transition-blur | — | 8px |
--tanrei-comp-tab-view-transition-distance | — | 40px |
--tanrei-comp-tab-view-transition-distance-fraction | — | 0 |
--tanrei-comp-tab-view-transition-opacity | — | 0 |
نماذج الاختبار
عروض مرجعية. تلتقط كل المنصات لقطة لكل نموذج بالمعرّف نفسه حتى يمكن مقارنة النتائج.
variant: primary (13)
default
label: Project items: 3 second-selected
value: activity label: Project items: 3 icons
label: Project items: 3 disabled-tab
label: Project items: 3 tall-panel
value: settings label: Project items: 3 linked
tabs: project-tabs items: 3 missing-panel
value: activity tabs: project-tabs items: 2 shared-choice
value: flutter label: Platform group: fixture-platform items: 4 shared-choice-lacking
value: react label: Platform group: fixture-platform items: 2 primary-small
size: small label: Project items: 3 slide
label: Project transition: slide items: 3 fade
label: Project transition: fade items: 3 no-transition
label: Project transition: none items: 3 variant: secondary (1)
secondary-fill
variant: secondary fullWidth: true label: Project items: 3 variant: tonal (2)
tonal
variant: tonal label: Project items: 3 tonal-no-track-no-transition
variant: tonal track: false label: Platform transition: none items: 4 مولَّد من specs/tab-view.spec.json, tokens/comp/tab-view.tokens.json, design/tokens/
استخدم عرض التبويبات حين يكون كل تبويب عرضًا ندًّا لموضوع واحد ويتنقل الناس بينها كثيرًا: «نظرة عامة» و«النشاط» و«الإعدادات» لمشروع، أو العيّنة نفسها لعدة منصات. يضيف إلى صف التبويبات ثلاثة أشياء: تصريحًا واحدًا للتبويب ولوحته، والسحب، وتبديلًا يصنعه المؤشر واللوحات معًا.
صف Tabs بلوحاته الخاصة يكفي حين تكون اللوحات محتوى بسيطًا ولن يسحبها أحد. وعرض التبويبات ليس عرضًا دوّارًا ولا مقلِّب صفحات: يعرض لوحة واحدة لكل تبويب، والسحب يحرّك لوحة واحدة. أما صف العناصر المعدّ للتصفح فمكانه العرض الدوّار.
| الهيئة | ما تكتبه | من أين يأتي change |
يُستخدم لـ |
|---|---|---|---|
| بصفّه الخاص (الافتراضي) | لوحات فقط. كل لوحة تسمّي تبويبها بالخاصيتين label و icon. |
من العرض، للتبويب وللسحب. | معظم عروض التبويبات: التبويبات تقع فوق محتواها مباشرة. |
مرتبط (tabs) |
صف Tabs في مكان آخر، وعرض من اللوحات يسمّيه. | من التبويبات للتبويب، ومن العرض للسحب. | التبويبات التي تقع بعيدًا عن محتواها، كما في ترويسة ثابتة. |
- خصائص الصف المرتبط نفسه هي التي تقرر مظهره واسمه ومجموعته ومفتاحه. الخصائص
variantوsizeوtrackوfullWidthوlabelوgroupوrememberAsعلى عرض مرتبط لا تُستخدم، وتبلّغ بنيات التنقيح (debug) عن المعيَّن منها. - ضع العرض المرتبط بعد تبويباته في ترتيب القراءة، لينتقل المفتاح Tab من التبويب المحدَّد إلى داخل اللوحة. والعرض الذي له صفّه الخاص يفعل ذلك بحكم بنائه.
- ليس للتبويبات شارة، فليس للوحة شارة كذلك. لتعليم تبويب بشارة، اكتب الصف بنفسك واربطه.
- لوحة واحدة لكل سحبة. السحب باللمس أو بالقلم، إذا كان جانبيًّا أكثر منه إلى الأعلى أو الأسفل، يتحرك نحو التبويب المفعَّل التالي أو السابق، ويتبع المؤشر الإصبع. فإذا أُفلت بعد المنتصف، أو بدفعة سريعة (fling) نحو التبويب، ثبت التبديل؛ وإلا عاد. والفأرة ولوحة اللمس والعجلة لا تسحب: تستخدم التبويبات.
- التبويبات المعطَّلة تُتخطّى، وعند التبويب الأول أو الأخير تبقى اللوحة ساكنة.
- المحتوى الذي يُسحب جانبيًّا يحتفظ بسحبه. لا يؤخذ السحب من منطقة تُمرَّر جانبيًّا، ولا من شريط تمرير (slider)، ولا من عرض دوّار، ولا من حقل نص، ولا من نص محدَّد. وللوحة التي تُسحب كلها جانبيًّا، كخريطة أو سطح رسم، عيِّن
swipeableإلى false واترك التبديل للتبويبات. - السحب على الصف هو إيماءة التبويبات نفسها: قد يمرّ بعدة تبويبات، واللوحة المعروضة تنتظر، والإفلات يبدّل إلى لوحة التبويب المختار.
- التبديل يقرره نظام التصميم. هو افتراضيًّا مسافة حركة قصيرة مع تلاشٍ وتغبيش؛ ويستطيع النظام أن يجعله تقليب صفحة بالعرض الكامل. وحيث تكون الحركة مقلَّلة لا يبقى إلا التلاشي.
الخاصية transition تسمّي الهيئة التي تُخلي بها اللوحة المعروضة مكانها للوحة التالية.
| القيمة | ما تفعله |
|---|---|
slide |
تتحرك اللوحات كما يقول نظام التصميم، وتتبع السحب. |
fade |
تتلاشى اللوحتان تلاشيًا متقاطعًا في مكانهما. |
none |
تُعرض اللوحة الجديدة كاملة في إطار واحد. والصف الذي يرسمه العرض يضع مؤشره على التبويب الجديد في الإطار نفسه. |
auto (الافتراضي) |
تأخذ جواب نظام التصميم (comp.tab-view.motion.transition، وهو القيمة slide في النظام الافتراضي). |
استخدم none للوحات تُقرأ ولا تُشاهَد، كالشيفرة. والسحب يؤخذ مع none أيضًا: تبقى اللوحة ساكنة، ويتبع المؤشر الإصبع، والإفلات يعرض اللوحة الجديدة فورًا. ولإيقاف السحب عيِّن swipeable إلى false. وحيث تكون الحركة مقلَّلة تُرسم slide كما تُرسم fade، ولا يُضاف شيء إلى fade ولا إلى none. والصف الذي يرتبط به العرض يحتفظ بالخاصية transition الخاصة به.
عرضا تبويبات من مجموعة واحدة: اختر تبويبًا في أي منهما
ثبِّت الحزمة باستخدام npm.
ثبِّت الحزمة باستخدام pnpm.
ثبِّت الحزمة باستخدام Yarn.
شغِّل البناء باستخدام npm.
شغِّل البناء باستخدام pnpm.
- أعطِ كل عرض يجيب عن السؤال نفسه قيمة
groupنفسها. الصف الخاص بالعرض هو العضو، فيصح كل ما تقوله صفحة Tabs عن المجموعة: يُطابَق الاختيار بقيمةvalueللوحة، لا بتسميتها أبدًا، والعرض الذي ليس فيه لوحة بتلك القيمة يحتفظ باختياره. في العرض التوضيحي، Yarn موجود في العرض الأول فقط. - العرض الذي استخدمه الشخص وحده يجري تبديلًا ويُصدر
change. وكل عرض آخر يعرض لوحته فورًا، بلا مسافة حركة ولا تلاشٍ ولا تغبيش، ويحتفظ بحالة اللوحات التي يخفيها، ولا يأخذ تركيزًا. - يبقى العرض الذي استخدمه الشخص في مكانه من النافذة فيما يتغير ارتفاع العروض التي فوقه.
- الخاصية
rememberAsتسمّي المفتاح الذي يُحفظ تحته الاختيار على جهاز الشخص ويُستعاد قبل أن يُرسم العرض أول مرة. لا يُخزَّن شيء بلا مفتاح، ولا تُخزَّن إلا قيمة اللوحة. على الويب المفتاح مفتاح فيlocalStorage، يُستخدم كما أُعطي تمامًا. والعرض التوضيحي لا يسمّي مفتاحًا. - العرض المرتبط لا يستخدم أيًّا من الخاصيتين: الصف المرتبط يسمّي مجموعته ومفتاحه.
- العرض بعرض أبيه وبارتفاع صفّه مضافًا إليه اللوحة المعروضة. اللوحات المتفاوتة الارتفاع أمر عادي، فما يلي العرض يتحرك حين تتغير لوحته.
- أعطِ العرض ارتفاعًا خاصًّا به حين لا ينبغي أن يتحرك شيء تحته. يبقى الصف في الأعلى، وكل لوحة تملأ الباقي وتُمرَّر داخله.
- اللوحة المخفيّة تبقى في مكانها بحالتها: ما كُتب فيها وإلى أين مُرِّرت. وليس للعرض وضع تحميل مؤجَّل: ارسم محتوى اللوحة المكلِفة حين يُحدَّد تبويبها أول مرة.
- أعطِ كل لوحة
label، حتى حين تعرضlabelHiddenأيقونتها وحدها: فهي تسمّي التبويب، ومن خلاله اللوحة. - أعطِ كل لوحة أيقونة، أو لا تعطِ أيًّا منها.
- أبقِ قيمة
valueلكل لوحة فريدة في عرضها وثابتة ما دامت الشاشة معروضة. - سمِّ العرض بالخاصية
labelحين يكون في الشاشة أكثر من صف تبويبات.
- لا تضع عرض تبويبات من الطراز نفسه داخل لوحة. ضع التبويبات الثانوية داخل عرض من التبويبات الأساسية.
- لا تعتمد على السحب لتبيّن أن هناك عروضًا أخرى. التبويبات تقول ذلك؛ والسحب طريق مختصر.
- لا تعدّل الصف الذي يرسمه العرض لنفسه. غيِّر اللوحات، والصف يتبع.
- لا تستخدم عرض التبويبات للتنقل بين وجهات تطبيق. تلك مهمة شريط التنقل أو درج التنقل.
- الدور
tabpanel- التسمية
- The row the Tab view draws itself is a tab list named by the Tab view's label, each tab named by its panel's label. Each panel is named by the tab of the same value, in the Tab view's own row or in the linked row, and each tab controls its panel. The Tab view itself is not named: it has no role of its own beyond its row and its panels.
- الحد الأدنى للهدف
- تستوفي مساحة اللمس الحد الأدنى لحجم الهدف حتى لو كان الحجم المرئي أصغر. minimum 32px · pointer 44px · spacing 8px · touch 48px
لوحة المفاتيح
| المفاتيح | الإجراء |
|---|---|
| Tab / Shift+Tab | Moves focus from the selected tab (in the Tab view's own row, or in the linked row) into the shown panel (its first focusable element, or the panel itself when it holds none), and back. |
| Right Arrow / Left Arrow, Home / End (on a tab) | Select another tab (TAB-12), and the Tab view switches to its panel. The Tab view adds no keys of its own: inside a panel the arrow keys belong to its content. |
ملاحظات
- The Tab view has no target of its own: its own row's tabs meet the minimum target as every tab does (TAB-5), and its panels hold the product's controls, which meet it themselves.
- The swipe is never the only way (WCAG 2.5.1): every panel is reached by a single tap on its tab, and by the arrow keys on the tabs. With a screen reader running, a one-finger swipe belongs to the reader; the tabs remain the way to switch panels.
- The swipe of the view and the drag across its tabs are one gesture in two places (TBV-3, TBV-4; TAB-28 to TAB-30): the tabs' active indicator follows the finger, the selection is made on release, from halfway or by a fling, a cancelled gesture returns the indicator, nothing is announced before the release, and under reduced motion the indicator still follows the finger and lands at once. They differ in reach. A swipe of the view moves to the neighbouring panel, and the panels travel with the finger. A drag across the row can pass several tabs, so the panels wait: the shown panel stays at rest during the drag, and the release switches to the chosen tab's panel as any selected tab does (TBV-2, TBV-6), its switch and the indicator's settling running over equal times.
- One declaration makes the tab and its view: while the Tab view draws its own row, each panel carries its tab's label, icon, labelHidden and disabled, so the tabs and the panels cannot fall out of step. Its row is a Tabs, not a look-alike: the same tokens, the same variants and the same behaviours, so a system that restyles the tabs restyles the Tab view's row too.
- Linked, for tabs placed elsewhere: a product whose tabs sit in a top app bar or a sticky header, apart from their content, sets tabs to name that row, and the Tab view draws none. The linked row names the panels, and its own props decide its look. The Tab view must then follow its tabs in reading order, so that Tab moves from the selected tab into the panel; a Tab view with its own row meets that by construction.
- Selection belongs to the Tab view and its tabs together: they hold the same value, and each selection is announced once. With its own row, the Tab view announces every selection (TBV-18). Linked, the component the person used announces it: the linked tabs' change for a tab, the Tab view's change for a swipe; a product that keeps the selection itself updates it from both.
- The panels' role, name and association are the Tab view's; the tabs' own panels (TAB-13) are for a row without a Tab view. Web: `tanrei-tab-panel` children of `tanrei-tab-view` get role tabpanel, aria-labelledby and the tab's aria-controls, and the view's own row is in the same tree as its panels so that these references resolve. SwiftUI, Compose, Flutter and React Native have no association between a tab and its panel; there each panel is named by its tab (Flutter exposes its tab panel role) and follows the row in reading order, and each README says so.
- A panel that is not shown stays in place, hidden, with its state. A product whose panels are costly renders their content when their tab is first selected; the Tab view has no lazy mode of its own yet (it can be added as a prop without breaking this spec).
- A tab of the Tab view's own row carries what a tab of the Tabs carries, and nothing more: the Tabs have no badge, so neither does a panel. A product that marks a tab with a badge writes its row itself and links it; a badge on the Tabs would reach the panel as one more tab prop.
- Tab view is not a carousel and not a pager: it shows one panel at a time, one per tab, and a person moves one panel per swipe. A row of items to browse belongs to the carousel.
- How a switch looks is a value decision of the active design system (tokens.md): a short travel with a fade and a blur by default, a full-width page turn in a system that pages.
- A Tab view in a group (group) is the usual home of a shared choice: the same sample in several languages or platforms, many times on one page. Its own row is the member (TBV-16), so everything the Tabs say of a group holds: the choice is matched by value, the views that follow change silently and at once and keep their hidden panels' state (TBV-20), the row the person used stays where it is on the screen (TAB-40, TBV-21), and remembering is off unless the product names a key (TAB-39).
- Panels of unequal height are the normal case for samples, and the view is as tall as its shown panel (TBV-11). What follows a view therefore moves when its panel changes; what a person is using does not. A product that wants no movement at all below a view sizes the view itself, and the panels then scroll inside it (TBV-11).
- The effect of switching views has two parts with two owners: the panels' change is the Tab view's (transition: slide, fade or none; TBV-22), and the indicator's travel to the new tab is the row's (the tabs' transition: slide or none; TAB-43). Set on a Tab view that draws its row, none covers both (TBV-23), so that "no effect" means that nothing moves; fade and slide leave the row to the system. A row placed by the product, linked or alone, is set by its own prop.
- transition changes what is drawn and never what is told: the selection, the panel's exposure and the announcement are made in the same frame in every form (TBV-9, TBV-22). None is the calm choice for panels that are read rather than watched — a code sample in several languages, a table in several units — and for a page that holds many views sharing one choice.
- Under fade the panels finish (comp.tab-view.motion.fade-duration) before the row's indicator lands (comp.tabs.motion.indicator-duration), as they do where motion is reduced; the rule that the switch and the indicator land together holds for slide.
- A swipe under none still switches panels and still shows the finger its progress, on the row's indicator; the panels wait for the release. A product that wants no swipe sets swipeable to false, which is a separate decision.
مولَّد من specs/tab-view.spec.json, tokens/sys/measure.tokens.json
- الصف الخاص بالعرض قائمة تبويبات (tab list) تسمّيها
labelللعرض، وكل تبويب تسمّيهlabelللوحته. والعرض نفسه لا دور له. - اللوحة المعروضة لوحة تبويب (tab panel) يسمّيها تبويبها ومرتبطة به، في صفّه الخاص أو في الصف المرتبط. وكل لوحة أخرى مخفيّة عن التقنيات المساعدة وعن التركيز، ومنها اللوحة التي ما زال تبديل أو سحب يرسمها. وينتقل العرض للتقنيات المساعدة حين ينتقل التحديد، لا حين تنتهي الحركة.
- المفتاح Tab ينتقل من التبويب المحدَّد إلى داخل اللوحة المعروضة: إلى أول عنصر فيها يقبل التركيز، أو إلى اللوحة نفسها حين لا تضم أيًّا منها. ولوحة كهذه تعرض حلقة التركيز داخل حافتها، لتركيز لوحة المفاتيح فقط.
- حين يخفي تبديل اللوحةَ التي فيها التركيز، ينتقل التركيز إلى التبويب المحدَّد.
- لا يضيف العرض مفاتيح خاصة به. مفاتيح الأسهم و Home و End على تبويب تحدّد تبويبًا آخر، فيبدّل العرض. وداخل اللوحة تخص مفاتيح الأسهم محتواها.
- ليس السحب الطريقة الوحيدة أبدًا: كل لوحة تُبلَغ بنقرة على تبويبها وبمفاتيح الأسهم. وحين يعمل قارئ الشاشة يكون السحب بإصبع واحدة للقارئ، وتبقى التبويبات وسيلة التبديل.
- لا يُعلَن شيء قبل إفلات السحب، والسحب الملغى لا يغيّر شيئًا.
- العرض الذي يتبع مجموعته يتغير بصمت: لا يُعلَن شيء، ويبقى التركيز حيث الشخص. وإن كان التركيز في لوحة يخفيها التغيّر، انتقل إلى التبويب المحدَّد في ذلك العرض.
- في التخطيطات من اليمين إلى اليسار ينعكس السحب ومسافة الحركة مع ترتيب القراءة. ومحتوى اللوحة لا يعكسه العرض.
- تبلّغ بنيات التنقيح (debug) عن
tabsلا تسمّي صفًّا، وعن تبويب محدَّد بلا لوحة، وعن عرض بصفّه الخاص وبلا لوحة، وعن لوحة قيمتها فارغة أو مكررة، وعن صف مرتبط يضم لوحات خاصة به، وعن خصائص صف معيَّنة على عرض مرتبط.
فجوات معروفة في المنصات:
- لم تنفّذ SwiftUI و Compose و Flutter و React Native عرض التبويبات.
- الويب: كل لوحة حاوية تمرير، فتُقصّ حلقة التركيز لعنصر تحكم عند حافة اللوحة تمامًا.
- الويب: اختبارات السلوك تعمل في Chromium فقط، والسحب بأحداث مؤشر اصطناعية فقط.
- أعطِ كل لوحة
label، وأعطِ العرضlabelحين يكون في الشاشة أكثر من صف تبويبات. - ضع العرض المرتبط بعد تبويباته في ترتيب القراءة.
- في الهيئة المرتبطة، حدِّث حالتك من حدثي
changeكليهما: حدث التبويبات وحدث العرض. - عيِّن
swipeableإلى false للوحة تُسحب هي نفسها جانبيًّا. - أعطِ عروض المجموعة الواحدة التسميات نفسها للقيم نفسها، وحيث تتباعد قل قرب أولها إن الاختيار يسري على الصفحة كلها.
- اختر مفتاح
rememberAsبنفسك، وسمِّه في إشعار الخصوصية حيث يحتاج منتجك إلى واحد.