شريط التطبيق
يسمّي الشاشة الحالية عند حافتها العليا ويحمل طريق الرجوع وبعض إجراءات الشاشة. تبقى حافته السفلية قائمةً حدًّا لترويسة الشاشة، ويأخذ مظهر التمرير حين يمرّ المحتوى تحته.
- الفئة
- التنقل
- الرموز
comp.app-bar- المستوى
- أساسي
- Web
tanrei-app-barschedule - SwiftUI
TanreiAppBarschedule - Jetpack Compose
TanreiAppBarschedule - Flutter
TanreiAppBarschedule - React Native
AppBarschedule
مولَّد من docs/status/status.json, specs/app-bar.spec.json
تعرض عيّنتا Web و React المواصفة الحالية، وفيها عناصر الشريط أزرار بأيقونة فقط. أما SwiftUI و Compose و Flutter و React Native فمتأخرة: ما زالت تقدّم المراجعة السابقة، وفيها مكوّن مستقل لزر الأيقونة. وتعرض عيّناتها تلك المراجعة إلى أن تلحق بالمواصفة.
شريط تطبيق صغير فيه زر رجوع وإجراءان
شريط التطبيق منطقة من التخطيط، لا تراكب. يأخذ ارتفاعه في أعلى الشاشة، ويبدأ المحتوى تحته، ولا يستقبل الشريط نفسه التركيز أبدًا.
يحمل نوعين من العناصر: عنصرًا واحدًا من النوع navigation في البداية (رجوع أو قائمة أو إغلاق)، وما لا يزيد على ثلاثة عناصر من النوع action في النهاية. وكلاهما أزرار بأيقونة فقط من عندك، لكل منها تسميته ونقرته، أو الخاصية href حيث يقود إلى مكان ما. يرتّبها الشريط ولا يغيّر مظهرها أبدًا. تأخذها كل منصة في منافذها: عناصر فرعية تحمل slot="navigation" و slot="actions" على الويب، وبانيا العرض navigation و actions على SwiftUI، والدالتان navigationIcon و actions على Compose، و leading و actions: على Flutter، والعقدتان navigation و actions على React Native.
العنوان هو اسم الشاشة وعنوانها (heading). على الويب هو الخاصية headline، لأن السمة العامة title في HTML كانت ستضع تلميحًا على الشريط كله.
حالة التمرير. ما دام تحت الشريط محتوى، يأخذ الشريط مظهر التمرير. اربط به عرض الشاشة القابل للتمرير فيضبط الشريط scrolled بنفسه: scroll-target على الويب (المستند افتراضيًّا)، و .tanreiAppBarScrollSource() على SwiftUI (يُطبَّق بعد .safeAreaInset الذي يحمل الشريط)، وقيمة scrollBehavior موصولة بواسطة Modifier.nestedScroll على Compose، وإشعارات تمرير الصفحة على Flutter، و useAppBarScroll() على React Native. وإن لم يكن ثمة ربط فاضبط scrolled بنفسك.
أشرطة النظام. يمتد لون الشريط تحت شريط الحالة أو فتحة الكاميرا أو شريط عنوان النافذة فوقه. ويبقى محتواه تحتها وداخل الهوامش الجانبية (insets). يقرأ الويب env(safe-area-inset-*)، ويأخذ Compose القيمة windowInsets (أشرطة الحالة وفتحة الشاشة الأفقية افتراضيًّا)، ويقرأ Flutter حشو MediaQuery، ويضع SwiftUI الشريط بواسطة .safeAreaInset(edge: .top). ليس في React Native واجهة للمنطقة الآمنة، ولا يعتمد Tanrei على أي حزمة للمنطقة الآمنة، فتأتي الهوامش من الخصائص وحدها. أعطِ TanreiProvider قيمة insets الخاصة بالنافذة (وإن كنت تستخدم react-native-safe-area-context فمرِّر إليه ناتج useSafeAreaInsets()). وخاصية insets على الشريط نفسه تستبدل الجوانب التي تسمّيها، فالشريط الذي ليس في أعلى النافذة يمرِّر { top: 0 }.
<!-- العنوان هو headline: خاصية HTML المسمّاة title كانت ستُظهر تلميحًا فوق الشريط. -->
<tanrei-app-bar headline="الوارد">
<tanrei-button variant="text" slot="navigation" label="رجوع"><span slot="icon">arrow_back</span></tanrei-button>
<tanrei-button variant="text" slot="actions" label="بحث"><span slot="icon">search</span></tanrei-button>
<tanrei-button variant="text" slot="actions" label="خيارات إضافية"><span slot="icon">more_vert</span></tanrei-button>
</tanrei-app-bar>
<!-- عنوان كبير ينطوي مع تمرير #inbox. العنوان h1 ما لم تقل heading-level غير ذلك. -->
<div style="display: flex; flex-direction: column; block-size: 100dvh">
<tanrei-app-bar size="large" headline="الوارد" scroll-target="inbox">
<tanrei-button variant="text" slot="navigation" label="القائمة"><span slot="icon">menu</span></tanrei-button>
</tanrei-app-bar>
<main id="inbox" style="flex: 1; overflow: auto">…</main>
</div>
<!-- الزر الذي يقود إلى صفحة رابط: أعطه href. والبحث قد يكون إجراءً: زر الأيقونة الذي يفتحه. -->
<tanrei-app-bar headline="علامات التبويب">
<tanrei-button variant="text" slot="navigation" href="/components/" label="كل المكوّنات"><span slot="icon">arrow_back</span></tanrei-button>
<tanrei-search slot="actions" bar="icon" presentation="dialog" label="البحث في التوثيق" placeholder="بحث"
clear-label="مسح البحث" back-label="رجوع"></tanrei-search>
</tanrei-app-bar>
<script type="module">
import '@tanrei/web/components/app-bar';
import '@tanrei/web/components/button';
import '@tanrei/web/components/search';
// الأزرار أزرارك: الشريط لا يضيف أحداثًا من عنده.
document.querySelector('[label="رجوع"]').addEventListener('click', () => history.back());
</script>import { AppBar, Button, Search } from '@tanrei/react';
<AppBar headline="الوارد">
<Button variant="text" slot="navigation" label="رجوع" onClick={goBack}><span slot="icon">arrow_back</span></Button>
<Button variant="text" slot="actions" label="بحث" onClick={search}><span slot="icon">search</span></Button>
<Button variant="text" slot="actions" label="خيارات إضافية" onClick={openMore}><span slot="icon">more_vert</span></Button>
</AppBar>
// الزر الذي يقود إلى صفحة رابط: أعطه href. والبحث قد يكون إجراءً: زر الأيقونة الذي يفتحه.
<AppBar headline="علامات التبويب">
<Button variant="text" slot="navigation" href="/components/" label="كل المكوّنات"><span slot="icon">arrow_back</span></Button>
<Search slot="actions" bar="icon" presentation="dialog" label="البحث في التوثيق" placeholder="بحث" clearLabel="مسح البحث" backLabel="رجوع" />
</AppBar>import Tanrei
// يقع الشريط في الهامش الآمن العلوي لعرض NavigationStack أُخفي شريط النظام فيه.
// ترك isScrolled بالقيمة nil يجعله يتبع عرض التمرير المعلَّم بـ .tanreiAppBarScrollSource().
NavigationStack {
ScrollView { InboxRows() }
.safeAreaInset(edge: .top, spacing: 0) {
TanreiAppBar("الوارد", size: .large) {
TanreiIconButton("القائمة", action: openMenu) { Image(systemName: "line.3.horizontal") }
} actions: {
TanreiIconButton("بحث", action: search) { Image(systemName: "magnifyingglass") }
}
}
// بعد الهامش الذي يحمل الشريط: المُعدِّل المطبَّق داخله لا يصل إلى الشريط.
.tanreiAppBarScrollSource()
.toolbar(.hidden, for: .navigationBar)
}import io.github.chnotchy.tanrei.components.TanreiAppBar
import io.github.chnotchy.tanrei.components.TanreiAppBarDefaults
import io.github.chnotchy.tanrei.components.TanreiIconButton
import io.github.chnotchy.tanrei.primitives.TanreiText
import io.github.chnotchy.tanrei.tokens.TanreiAppBarSize
// شريط صغير. من دون scrollBehavior يبقى مسطحًا.
TanreiAppBar(
title = "الوارد",
navigationIcon = { TanreiIconButton(onClick = onBack, contentDescription = "رجوع") { TanreiText("arrow_back") } },
actions = {
TanreiIconButton(onClick = onSearch, contentDescription = "بحث") { TanreiText("search") }
TanreiIconButton(onClick = onMore, contentDescription = "خيارات إضافية") { TanreiText("more_vert") }
},
)
// شريط كبير ينطوي مع تمرير القائمة: اربط السلوك على عنصر أب لعرض التمرير.
// pinnedScrollBehavior() للشريط الصغير: لا ينطوي أبدًا، ويصير scrolled ما دام تحته محتوى.
val scrollBehavior = TanreiAppBarDefaults.collapseScrollBehavior()
Column(Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
TanreiAppBar(title = "الوارد", size = TanreiAppBarSize.Large, scrollBehavior = scrollBehavior)
LazyColumn { /* محتوى الشاشة */ }
}import 'package:tanrei/tanrei.dart';
// شريط صغير، في منفذ شريط التطبيق من scaffold أو أول عنصر في Column.
// ترك scrolledUnder بالقيمة null يجعله يتبع أقرب ScrollNotificationObserver، كما يفعل AppBar.
TanreiAppBar(
leading: TanreiIconButton(onPressed: () => Navigator.of(context).maybePop(), icon: const Icon(AppSymbols.arrowBack), semanticLabel: 'رجوع'),
title: 'الوارد',
actions: <Widget>[
TanreiIconButton(onPressed: search, icon: const Icon(AppSymbols.search), semanticLabel: 'بحث'),
TanreiIconButton(onPressed: openMore, icon: const Icon(AppSymbols.moreVert), semanticLabel: 'خيارات إضافية'),
],
),
// الشريط الكبير الذي ينطوي مع محتواه sliver مثبَّت في أعلى CustomScrollView الذي يحويه.
CustomScrollView(
slivers: <Widget>[
TanreiSliverAppBar(title: 'الوارد', size: TanreiAppBarSize.large, leading: menuButton),
SliverList.list(children: messages),
],
),import { useState } from 'react';
import { ScrollView, View } from 'react-native';
import { AppBar, IconButton, useAppBarScroll } from '@tanrei/react-native';
// تأتي الهوامش من الخاصية insets على TanreiProvider، أو من خاصية الشريط نفسه.
<AppBar
title="الوارد"
navigation={<IconButton accessibilityLabel="رجوع" onPress={goBack}>arrow_back</IconButton>}
actions={
<>
<IconButton accessibilityLabel="بحث" onPress={search}>search</IconButton>
<IconButton accessibilityLabel="خيارات إضافية" onPress={openMore}>more_vert</IconButton>
</>
}
/>
// شريط كبير ينطوي: اربط عرض التمرير، وضع الشريط فوقه، وأضف إلى المحتوى حشوًا بارتفاع الشريط.
const scroll = useAppBarScroll();
const [barHeight, setBarHeight] = useState(0);
<View style={{ flex: 1 }}>
<ScrollView onScroll={scroll.onScroll} scrollEventThrottle={16} contentContainerStyle={{ paddingTop: barHeight }}>
{messages}
</ScrollView>
<View style={{ position: 'absolute', top: 0, left: 0, right: 0 }} onLayout={(e) => setBarHeight((h) => Math.max(h, e.nativeEvent.layout.height))}>
<AppBar size="large" title="الوارد" scrollState={scroll.state} navigation={menuButton} />
</View>
</View>شريط كبير له عنوان فرعي، وعنوان في الوسط
زر تنقل هو رابط، وزر يفتح البحث بين الإجراءات
البنية
| الجزء | الوصف |
|---|---|
1 container | The bar across the top of the screen. Its color reaches under the system inset above it (status bar, camera cutout, window caption). |
2 navigation · اختياري | The navigation item: one icon-only Button at the start (back, menu or close). The product's icon-only Button, not restyled by the bar. |
3 brand · اختياري | The product's brand: one link to its first page (or one image), after the navigation item, in a cell as large as an icon button's. It draws one image, the symbol alone, the wordmark alone, the symbol and the wordmark side by side (lockup), or one of the two by the window's width (responsive). |
4 context · اختياري | The product's context: one control after the brand (a workspace, a project, a design system), the product's own Button, not restyled by the bar. |
5 title | The screen's name. In the top row for a small bar; under the top row for an expanded large bar, moving into the top row as it collapses. |
6 subtitle · اختياري | One line under the title: context for it (an account, a status, when it was edited). |
7 actions · اختياري | The action items: up to three icon-only Buttons at the end. The product's icon-only Buttons, not restyled by the bar. |
8 outline | The bar's bottom edge. In the default system it is drawn at rest and while content is under the bar; dividerVisibility scroll draws it only once content has scrolled under the bar. |
الخصائص
| الخاصية | النوع | القيمة الافتراضية | الوصف |
|---|---|---|---|
size | small | large | "small" | Small puts the title in the one row the bar has; large gives the title a larger row of its own under the top row, which collapses into the top row as the content scrolls. One product uses small on most screens and large on its top-level ones. A design system may add values. |
title | string | "" | The screen's name, and the heading of the screen for assistive technology. |
subtitle | string | "" | One line under the title. Empty draws none. |
titleAlignment | start | center | "start" | Where the title sits in the top row: after the navigation button, or centered on the bar. An expanded large bar's own title row always starts at the start. |
brandHref | string | "" | The address of the product's first page: the brand is a link to it. Empty draws the brand as an image that is not a link. |
brandLabel | string | "" | The product's name: the brand's one accessible name, whichever of its images is shown. Required wherever the bar has a brand. |
brandLayout | lockup | responsive | "lockup" | How the brand's symbol and wordmark stand when the product gives both (APB-21): lockup sets them side by side, comp.app-bar.brand.lockup.gap apart and aligned as comp.app-bar.brand.lockup.alignment says; responsive shows the wordmark while the window is medium or wider and the symbol while it is compact, never both. One image, a symbol alone or a wordmark alone is shown as it is, at every width, under either value. |
scrolled | boolean | false | Whether content lies under the bar. A scrolling view connected to the bar sets it (see platforms); a product without one sets it itself. It selects the bar's scrolled condition, and a large bar is drawn collapsed while it is true. |
dividerVisibility | auto | always | scroll | "auto" | When the bar's bottom edge is drawn. always: at rest and while content is under the bar, so that the edge of the screen's header stands still (an app shell's header, a screen whose content starts right under the bar). scroll: only while content lies under the bar, so that the line itself says the content has moved (the scrolled condition's edge alone). auto takes comp.app-bar.outline.visibility, the design system's answer (always in the default system). The bar's color and elevation follow scrolled under either choice. |
المنافذ
| المنفذ | الوصف |
|---|---|
navigation | Holds the navigation item: one icon-only Button at the start. |
brand | The brand as one image: a product's single logo file, drawn comp.app-bar.brand.image.height high. Takes the place of brand-symbol and brand-wordmark where both are given. |
brand-symbol | The brand's symbol: comp.app-bar.brand.symbol.size high alone, or while the window is compact under brandLayout responsive; comp.app-bar.brand.lockup.symbol.size high (the wordmark's height) beside the wordmark in a lockup. |
brand-wordmark | The brand's wordmark, drawn comp.app-bar.brand.wordmark.height high: alone at every width, after the symbol in a lockup, or while the window is medium or wider under brandLayout responsive. |
context | Holds one control of the product's context after the brand: a Button, or a Button that opens a Menu, that says and changes the workspace, the project or the design system the screen is in. The product's own control, not restyled by the bar. |
actions | Holds the action items, up to three icon-only Buttons at the end, in reading order; more belong in an overflow menu the product provides. |
العناصر
أنواع العناصر التي يكررها المكوّن. يسرد كل نموذج اختبار عناصره بالترتيب، وتسمّي كل منصة كل نوع بأسلوبها.
navigation
The one icon-only Button at the start of the top row (back, menu or close): a Button of the text variant and the medium size with an icon and no label, unless the product sets otherwise, placed in the navigation slot, which the bar lays out and does not restyle.
| الخاصية | النوع | القيمة الافتراضية | الوصف |
|---|---|---|---|
label | string | "" | Its accessible name ("Back"), as the Button's label. Required. |
| المنفذ | الوصف |
|---|---|
icon | Its glyph, as the Button's icon slot. A fixture item's content is this glyph. |
| الحدث | الوصف |
|---|---|
click | The button was activated, as the Button's click. |
| المواصفة | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| العنصر | tanrei-button | TanreiButton | TanreiButton | TanreiButton | Button |
label · الخاصية | label | label | contentDescription | semanticLabel | accessibilityLabel |
click · الحدث | click | action | onClick | onPressed | onPress |
| ملاحظات | An icon-only <tanrei-button> child with slot="navigation"; the glyph is its content. | An icon-only TanreiButton in the navigation view builder. | An icon-only TanreiButton in the navigationIcon lambda. | An icon-only TanreiButton given as leading. | An icon-only Button element passed as navigation. |
action
An icon-only Button at the end of the top row, up to three in reading order: a Button of the text variant and the medium size with an icon and no label, unless the product sets otherwise, placed in the actions slot, which the bar lays out and does not restyle.
| الخاصية | النوع | القيمة الافتراضية | الوصف |
|---|---|---|---|
label | string | "" | Its accessible name ("Search"), as the Button's label. Required. |
| المنفذ | الوصف |
|---|---|
icon | Its glyph, as the Button's icon slot. A fixture item's content is this glyph. |
| الحدث | الوصف |
|---|---|
click | The button was activated, as the Button's click. |
| المواصفة | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| العنصر | tanrei-button | TanreiButton | TanreiButton | TanreiButton | Button |
label · الخاصية | label | label | contentDescription | semanticLabel | accessibilityLabel |
click · الحدث | click | action | onClick | onPressed | onPress |
| ملاحظات | An icon-only <tanrei-button> child with slot="actions"; the glyph is its content. | An icon-only TanreiButton in the actions view builder. | An icon-only TanreiButton in the actions lambda. | An icon-only TanreiButton in the bar's actions: list. | An icon-only Button element among the actions nodes. |
الحالات
السلوك
متطلبات بصيغة EARS. يُختبر تنفيذ كل منصة مقابل هذه المعرّفات. يُعرض نص المتطلبات كما ورد في ملف المواصفة، بالإنجليزية.
| المعرّف | المتطلب |
|---|---|
APB-1 | WHILE scrolled is false THE app bar SHALL fill its container with comp.app-bar.flat.container.color, raise it by comp.app-bar.flat.container.elevation and draw its bottom edge comp.app-bar.outline.width thick: in comp.app-bar.scrolled.outline.color WHILE its divider visibility (APB-18) is always, so that the edge stands still whether or not content lies under the bar, and in comp.app-bar.flat.outline.color WHILE it is scroll. |
APB-2 | WHILE scrolled is true THE app bar SHALL fill its container with comp.app-bar.scrolled.container.color, raise it by comp.app-bar.scrolled.container.elevation and draw its bottom edge comp.app-bar.outline.width thick in comp.app-bar.scrolled.outline.color, under either divider visibility (APB-18). |
APB-3 | WHEN scrolled changes THE app bar SHALL change its container color, elevation and bottom edge over comp.app-bar.motion.condition-duration with comp.app-bar.motion.condition-easing, and at once where motion is reduced; WHILE its divider visibility (APB-18) is always the bottom edge SHALL NOT change. |
APB-4 | THE app bar SHALL span the full width of its parent and lay out a top row at least comp.app-bar.container.height.small high: the navigation item comp.app-bar.container.padding-inline from the start edge, the title block, and the action items comp.app-bar.container.padding-inline from the end edge, with comp.app-bar.container.gap between them and comp.app-bar.actions.gap between two actions, each vertically centered in the row; a navigation item SHALL go to the start and action items to the end wherever they stand among the items; WHILE there is no navigation item the title block SHALL start comp.app-bar.title.inset from the start edge, and WHILE there are no action items it SHALL end comp.app-bar.title.inset from the end edge. Right-to-left layouts SHALL mirror the order. |
APB-5 | THE app bar SHALL draw a title in the top row on one line in comp.app-bar.title.typescale.small and comp.app-bar.title.color, and WHILE subtitle is not empty the subtitle on one line below it in comp.app-bar.subtitle.typescale.small and comp.app-bar.subtitle.color, the two vertically centered as a block; each SHALL be truncated at the end with an ellipsis when it does not fit. THE app bar SHALL set the title's letter-spacing, in every size, by the script of its text, as every title is set (PRS-20): no negative letter-spacing WHERE its language is Chinese, Japanese or Korean, and none WHERE its script joins its letters or stacks marks on them. |
APB-6 | WHILE titleAlignment is center THE app bar SHALL center the top row's title block on the bar's full width and narrow it equally on both sides, truncating its text, so that it never overlaps the navigation or actions slots, and SHALL center its text; WHILE titleAlignment is start THE app bar SHALL start the title block after the navigation slot (or at comp.app-bar.title.inset) with its text start-aligned. |
APB-7 | WHILE size is large and the bar is expanded THE app bar SHALL be at least comp.app-bar.container.height.large high, SHALL draw no title in its top row, and SHALL draw below the top row the title in comp.app-bar.title.typescale.large and, WHILE subtitle is not empty, the subtitle in comp.app-bar.subtitle.typescale.large, both starting comp.app-bar.title.inset from the start edge, each on one line truncated at the end, the block ending comp.app-bar.container.padding-block-end above the bar's bottom edge. |
APB-8 | WHILE size is large and a scrolling view is connected THE app bar SHALL shrink by the distance the content has scrolled from its top, one for one, from its expanded height to comp.app-bar.container.height.small, the expanded title block moving up with the bar's bottom edge and clipped under the top row; the top row's title SHALL be invisible while the expanded title is wholly visible, fully visible once the bar has collapsed, and in between fade in linearly with the part of the expanded title that is covered. WHEN the content scrolls back toward its top THE app bar SHALL expand the same way. |
APB-9 | WHERE a scrolling view is connected THE app bar SHALL take scrolled from it: true while any content lies under the bar (for a large bar, only once it has fully collapsed) and false when the content is at its top. WHILE size is large and scrolled is true THE app bar SHALL be drawn fully collapsed; WHERE nothing is connected and scrolled is false a large app bar SHALL stay expanded. |
APB-10 | THE app bar SHALL extend its container under the top system inset (status bar, camera cutout, window caption) and SHALL place its content below that inset and inside the left and right insets, so that its color reaches the screen's top edge while no content sits under a system bar or cutout. |
APB-11 | THE app bar SHALL NOT restyle its navigation and action items: each SHALL render as the icon-only Button it is, from its own tokens, and keep its own semantics, and the app bar SHALL NOT be focusable itself, so that focus moves from the navigation item to the action items in reading order. A glyph of an item that the system's icon set marks as mirrored (a navigation item's arrow_back) SHALL be mirrored in a right-to-left layout by the item's own icon helper (BTN-19): that is the icon's own behaviour, not a restyling by the app bar. |
APB-12 | THE app bar SHALL expose title as a heading and subtitle as text read after it, and SHALL expose the title only once WHILE size is large: the expanded title while any of it is visible, the top row's title once the bar has collapsed. |
APB-13 | WHILE title is empty THE app bar SHALL draw no title block (an expanded large bar then has an empty title row) and SHALL expose no heading; IF the app bar holds more than three action items or more than one navigation item THE app bar SHALL still render them, and in debug builds report that the extra actions belong in an overflow menu or that a bar has one navigation button. |
APB-14 | WHEN size names a value that the active design system does not have THE app bar SHALL render with the default value's tokens, and in debug builds report the unknown value. |
APB-15 | WHILE the text size is enlarged THE app bar SHALL grow its rows to hold the title and subtitle lines (its heights are minimums), SHALL keep each on one line truncated at the end, and SHALL NOT scale the navigation and action items it holds. |
APB-16 | THE app 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 app bar is shown THE app bar SHALL draw itself for the new direction at once, without a motion of its own, keeping its size and the scroll state it follows. |
APB-17 | THE app bar SHALL lay out its title and its subtitle 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 app bar SHALL still place each and align its lines by the layout direction, not by the text's own, and SHALL cut a title or a subtitle that does not fit (APB-5, APB-7) 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. |
APB-18 | THE app bar SHALL draw its bottom edge (APB-1, APB-2) as its dividerVisibility names: WHILE dividerVisibility is auto, as comp.app-bar.outline.visibility names, which follows sys.behavior.divider-visibility unless a design system sets the app bar apart; WHILE dividerVisibility is always or scroll, as that value names, whatever the tokens say. WHEN dividerVisibility, or the token it follows, changes THE app bar SHALL draw the other form at once, moving no part and keeping the scroll position and focus; WHEN dividerVisibility names a value the app bar does not define THE app bar SHALL treat it as auto and in debug builds report the unknown value. A platform that cannot draw a choice SHALL fall back to the first value, always, and state that in its README. |
APB-19 | WHERE the product gives the app bar a brand (an image in brand, brand-symbol or brand-wordmark) THE app bar SHALL place it at the start of the top row, after the navigation item at comp.app-bar.actions.gap from it, or comp.app-bar.container.padding-inline from the start edge WHILE no navigation item is drawn, with the title block after it at comp.app-bar.container.gap; the brand SHALL take a cell at least the minimum target high and wide, padded at its start and end by half the minimum target less comp.app-bar.brand.symbol.size, its images centred on the row's centre line, so that a symbol stands where an icon button's icon stands and a wordmark keeps that inset from the cell's edges. Right-to-left layouts SHALL mirror the order. |
APB-20 | THE app bar SHALL draw the image in brand comp.app-bar.brand.image.height high, the symbol comp.app-bar.brand.symbol.size high and the wordmark comp.app-bar.brand.wordmark.height high, each as wide as its own proportions make it, and SHALL give the brand comp.app-bar.brand.color, which an image drawn in the current color takes and an image of its own colors keeps. WHILE the brand is a lockup THE app bar SHALL draw the symbol and then the wordmark in one row, comp.app-bar.brand.lockup.gap apart, aligned in the row's block axis as comp.app-bar.brand.lockup.alignment names (center, start or end). |
APB-21 | WHERE the brand holds one image, a symbol alone or a wordmark alone THE app bar SHALL show it at every window width. WHERE it holds both a symbol and a wordmark, WHILE brandLayout is lockup THE app bar SHALL show both as a lockup (APB-20), and WHILE brandLayout is responsive THE app bar SHALL show the wordmark WHILE the window class is medium or expanded and the symbol WHILE it is compact, never both: the window class of the app shell the bar stands in, or, outside one, the window's width against sys.breakpoint.medium. WHEN the window class changes THE app bar SHALL show the other at once, without a motion of its own, the brand's link and its focus unchanged. |
APB-22 | THE app bar SHALL expose the brand as one link to brandHref named by brandLabel — or, WHILE brandHref is empty, as one image named by brandLabel — between the navigation item and the action items in reading order, and SHALL hide the brand's images from assistive technology, so that the product's name is read once whichever arrangement is shown. THE brand's link SHALL be a stop in the tab order and SHALL draw the focus indicator around its cell, in the shape comp.app-bar.brand.shape. |
APB-23 | IF brandLayout names a value the app bar does not define THE app bar SHALL treat it as lockup and in debug builds report it; IF brand holds an image together with brand-symbol or brand-wordmark THE app bar SHALL show the image in brand alone and in debug builds report the others; IF the bar has a brand and brandLabel is empty, or a brand slot holds more than one image, THE app bar SHALL still draw what it holds and in debug builds report what is missing or extra. |
APB-24 | WHILE the brand is a lockup THE app bar SHALL draw the symbol comp.app-bar.brand.lockup.symbol.size high, which is the wordmark's height (comp.app-bar.brand.wordmark.height) unless a design system sets it apart, so that the symbol and the wordmark stand at one height; a symbol shown alone keeps comp.app-bar.brand.symbol.size. |
APB-25 | WHERE the product puts a control in context THE app bar SHALL place it at the start of the top row after the brand, at comp.app-bar.actions.gap from the brand's cell (from the navigation item WHILE no brand is drawn, and comp.app-bar.container.padding-inline from the start edge WHILE neither is), vertically centred in the row, with the title block after it at comp.app-bar.container.gap; SHALL NOT restyle it, as APB-11 says of the navigation and action items; and SHALL keep it in reading order after the brand and before the title block and the action items. Right-to-left layouts SHALL mirror the order. |
مطابقة الأسماء بين المنصات
التكافؤ لا يعني واجهات برمجية متطابقة. تعبّر كل منصة عن العقد نفسه بأسلوبها.
| المواصفة | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| المكوّن | tanrei-app-bar | TanreiAppBar | TanreiAppBar | TanreiAppBar | AppBar |
size · الخاصية | size | size | size | size | size |
title · الخاصية | headline | title | title | title | title |
subtitle · الخاصية | subtitle | subtitle | subtitle | subtitle | subtitle |
titleAlignment · الخاصية | titleAlignment | titleAlignment | titleAlignment | centerTitle | titleAlignment |
brandHref · الخاصية | brandHref | brandHref | brandHref | brandHref | brandHref |
brandLabel · الخاصية | brandLabel | brandLabel | brandLabel | brandLabel | brandLabel |
brandLayout · الخاصية | brandLayout | brandLayout | brandLayout | brandLayout | brandLayout |
scrolled · الخاصية | scrolled | isScrolled | scrollBehavior | scrolledUnder | scrolled |
dividerVisibility · الخاصية | dividerVisibility | dividerVisibility | dividerVisibility | dividerVisibility | dividerVisibility |
| ملاحظات | title is the headline property and attribute, because HTML's global title attribute would show a tooltip over the whole bar. titleAlignment is the title-alignment attribute. The slots are navigation, context and actions. A web-only scroll-target attribute names the id of the scrolling element to connect (default: the document's scrolling element; none connects nothing), from which the bar sets scrolled itself. A web-only heading-level (default 1) sets the title's heading level. Insets are env(safe-area-inset-top|left|right). dividerVisibility is the divider-visibility attribute (auto, always, scroll); auto reads --tanrei-comp-app-bar-outline-visibility from the cascade with a style query, and where the browser has none the bar writes the name it read on its container when it renders. The brand: brand-href, brand-label and brand-layout (lockup | responsive); its images are children with slot="brand", slot="brand-symbol" or slot="brand-wordmark" (an <img> or an inline <svg>), which the bar sizes from its tokens, marks aria-hidden and draws inside its own link (part="brand"). The link is the bar's: a router that follows links by their clicks reads it from the click's composed path. The window class is the enclosing <tanrei-app-shell>'s, else matchMedia against sys.breakpoint.medium. | TanreiAppBar(_ title:, subtitle:, size:, titleAlignment:, isScrolled: Bool? = nil) { navigation } actions: { … }, placed with .safeAreaInset(edge: .top) in a NavigationStack whose system bar is hidden; the system bar's heights, title font and edge cannot be set from the tokens. Hiding it must not lose the interactive back swipe: the implementation keeps it working. The connected scroll view is the one marked .tanreiAppBarScrollSource(), applied after the .safeAreaInset that holds the bar (the inset's content sees only the environment where the inset is applied); it connects on iOS 18 and macOS 15 and later. isScrolled: nil follows it, a value overrides it. Not yet: the title's letter-spacing by the script of its text (APB-5, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; APB-18) — the bar still draws its bottom edge from its condition's outline color, as scroll does. Not built on this platform yet: the brand (brandHref, brandLabel, brandLayout and its slots, APB-19 to APB-24). Not built on this platform yet: the context slot (APB-25). | Compose idiom (as TopAppBar): TanreiAppBar(title, navigationIcon = { … }, actions = { … }, subtitle, size, titleAlignment, scrollBehavior, windowInsets). scrollBehavior (TanreiAppBarDefaults.pinnedScrollBehavior() for small, .collapseScrollBehavior() for large) is connected with Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), and its state carries scrolled and the collapse; without one the bar is flat and expanded, and a state created with a given offset draws the scrolled fixtures. windowInsets defaults to the status bars plus the horizontal display cutout. Not yet: the title's letter-spacing by the script of its text (APB-5, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; APB-18) — the bar still draws its bottom edge from its condition's outline color, as scroll does. Not built on this platform yet: the brand (brandHref, brandLabel, brandLayout and its slots, APB-19 to APB-24). Not built on this platform yet: the context slot (APB-25). | Flutter idiom (as AppBar and SliverAppBar.large): TanreiAppBar is a PreferredSizeWidget for Scaffold.appBar, with leading, actions, title (a String), subtitle, size and centerTitle (true for center). scrolledUnder: null follows the ScrollNotifications of the scaffold's body, as AppBar's scrolled-under state does; a bool overrides it. A large bar that collapses is TanreiSliverAppBar inside a CustomScrollView. Insets through SafeArea/MediaQuery.padding. Not yet: the title's letter-spacing by the script of its text (APB-5, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; APB-18) — the bar still draws its bottom edge from its condition's outline color, as scroll does. Not built on this platform yet: the brand (brandHref, brandLabel, brandLayout and its slots, APB-19 to APB-24). Not built on this platform yet: the context slot (APB-25). | navigation and actions are React nodes (an icon-only Button, and a fragment of up to three). The scroll connection is a hook: const scroll = useAppBarScroll() gives scroll.onScroll for a ScrollView or FlatList and scroll.state for the bar's scrollState prop, which sets scrolled and the collapse; without it the bar draws what scrolled says. 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. Not yet: the title's letter-spacing by the script of its text (APB-5, PRS-20). Not built on this platform yet: dividerVisibility (auto, always, scroll; APB-18) — the bar still draws its bottom edge from its condition's outline color, as scroll does. Not built on this platform yet: the brand (brandHref, brandLabel, brandLayout and its slots, APB-19 to APB-24). Not built on this platform yet: the context slot (APB-25). |
الرموز
| الرمز | يشير إلى | القيمة المحلولة (فاتح) |
|---|---|---|
--tanrei-comp-app-bar-actions-gap | sys.target.spacing | 8px |
--tanrei-comp-app-bar-brand-color | sys.color.on-surface | |
--tanrei-comp-app-bar-brand-image-height | comp.app-bar.brand.wordmark.height | 40px |
--tanrei-comp-app-bar-brand-lockup-alignment | — | 0 |
--tanrei-comp-app-bar-brand-lockup-gap | sys.space.space100 | 8px |
--tanrei-comp-app-bar-brand-lockup-symbol-size | comp.app-bar.brand.wordmark.height | 40px |
--tanrei-comp-app-bar-brand-shape | sys.shape.corner.full | 9999px |
--tanrei-comp-app-bar-brand-symbol-size | sys.icon.medium | 24px |
--tanrei-comp-app-bar-brand-wordmark-height | — | 40px |
--tanrei-comp-app-bar-container-gap | sys.space.space100 | 8px |
--tanrei-comp-app-bar-container-height-large | — | 120px |
--tanrei-comp-app-bar-container-height-small | — | 64px |
--tanrei-comp-app-bar-container-padding-block-end | sys.space.space150 | 12px |
--tanrei-comp-app-bar-container-padding-inline | sys.space.space50 | 4px |
--tanrei-comp-app-bar-flat-container-color | sys.color.surface | |
--tanrei-comp-app-bar-flat-container-elevation | sys.elevation.level0 | |
--tanrei-comp-app-bar-flat-outline-color | — | |
--tanrei-comp-app-bar-motion-condition-duration | sys.motion.role.state.large | 250ms |
--tanrei-comp-app-bar-motion-condition-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-app-bar-outline-visibility | sys.behavior.divider-visibility | 0 |
--tanrei-comp-app-bar-outline-width | sys.border.hairline | 1px |
--tanrei-comp-app-bar-scrolled-container-color | sys.color.surface | |
--tanrei-comp-app-bar-scrolled-container-elevation | sys.elevation.level0 | |
--tanrei-comp-app-bar-scrolled-outline-color | sys.color.outline-variant | |
--tanrei-comp-app-bar-subtitle-color | sys.color.on-surface-variant | |
--tanrei-comp-app-bar-title-color | sys.color.on-surface | |
--tanrei-comp-app-bar-title-inset | sys.space.space200 | 16px |
--tanrei-comp-app-bar-subtitle-typescale-large | sys.typescale.body | 400 14px/1.5714 |
--tanrei-comp-app-bar-subtitle-typescale-small | sys.typescale.body-small | 400 13px/1.5385 |
--tanrei-comp-app-bar-title-typescale-large | sys.typescale.heading-1 | 500 32px/1.25 |
--tanrei-comp-app-bar-title-typescale-small | sys.typescale.heading-4 | 500 18px/1.4444 |
نماذج الاختبار
عروض مرجعية. تلتقط كل المنصات لقطة لكل نموذج بالمعرّف نفسه حتى يمكن مقارنة النتائج.
size: small (10)
small
title: Inbox items: 3 small-scrolled
title: Inbox scrolled: true items: 3 small-center
title: Inbox titleAlignment: center items: 3 small-subtitle
title: Quarterly report subtitle: Edited 5 minutes ago items: 2 small-title-only
title: Settings small-brand
brandHref: / brandLabel: Tanrei items: 2 slots: brand small-brand-symbol
title: Inbox brandHref: / brandLabel: Tanrei items: 2 slots: brand-symbol small-brand-wordmark
brandHref: / brandLabel: Tanrei items: 2 slots: brand-wordmark small-brand-lockup
brandHref: / brandLabel: Tanrei items: 2 slots: brand-symbol, brand-wordmark small-brand-responsive
brandHref: / brandLabel: Tanrei brandLayout: responsive items: 2 slots: brand-symbol, brand-wordmark size: large (3)
large
size: large title: Inbox items: 3 large-subtitle
size: large title: Inbox subtitle: [email protected] items: 3 large-scrolled
size: large title: Inbox scrolled: true items: 3 مولَّد من specs/app-bar.spec.json, tokens/comp/app-bar.tokens.json, design/tokens/
استخدم شريط التطبيق في أعلى الشاشة ليقول أين يوجد الشخص، وليعيده إلى الخلف أو يفتح القائمة، وليصل إلى الإجراءات القليلة التي تخص الشاشة كلها: البحث، المشاركة، الحساب. للشاشة الواحدة شريط تطبيق واحد.
استخدم شريط التنقل أو درج التنقل للانتقال بين الوجهات العليا في التطبيق. ويستطيع زر القائمة في شريط التطبيق أن يفتح درج تنقل نمطيًّا (modal). ليس في Tanrei شريط تطبيق سفلي: الإجراءات في أسفل شاشة مضغوطة (Compact) من شأن شريط الأدوات.
| الاختيار | ما يفعله | يُستخدم لـ |
|---|---|---|
| صغير (الافتراضي) | صف واحد: زر التنقل والعنوان والإجراءات. | معظم الشاشات، وكل شاشة يصل إليها الشخص من شاشة أخرى. |
| كبير | عنوان أكبر في صف خاص به تحت الصف العلوي. مع تمرير المحتوى ينطوي في الصف العلوي بمقدار التمرير نفسه، ويظهر عنوان الصف العلوي تدريجيًّا. ويتمدد من جديد حين يعود المحتوى إلى أعلاه. | الشاشات العليا في المنتج، حيث العنوان هو أيضًا علامة الشاشة المميِّزة. |
الخاصية titleAlignment بالقيمة start (الافتراضي) |
يتبع العنوان زر التنقل. | معظم الأشرطة. |
الخاصية titleAlignment بالقيمة center |
يتوسط العنوان الشريط كله ويُضيَّق من الجانبين بالقدر نفسه، فلا يمتد تحت الأزرار أبدًا. | المنتجات التي تتبع عناوين iOS الموسَّطة. والعنوان الكبير المتمدد يبدأ دائمًا من البداية. |
يجوز لنظام التصميم أن يضيف أحجامًا. على SwiftUI و Compose و Flutter و React Native، يحصل الحجم الأطول من small على صف عنوان خاص به، كما يحصل large. وعلى الويب يحصل عليه كل حجم يضيفه نظام التصميم. وليس في الإعداد الجاهز m3-like حجم Material المتوسط (medium) بعد.
- الحافة السفلية ثابتة. في النظام الافتراضي يرسم الشريط حافة سفلية بخط رفيع وهو ساكن وحين يكون تحته محتوى. فالشريط حدّ منطقة تبقى في مكانها، كترويسة هيكل التطبيق، فلا يظهر خطه ولا يختفي مع التمرير.
- أو تتبع التمرير. اضبط
dividerVisibilityعلىscroll(divider-visibilityعلى الويب) ليُرسم الحد فقط حين يصير تحت الشريط محتوى، فيقول الخط نفسه إن المحتوى قد تحرّك. ويتخذ نظام التصميم هذا الاختيار لكل الأشرطة واللوحات ومربعات الحوار بالرمزsys.behavior.divider-visibility، أو للأشرطة وحدها بالرمزcomp.app-bar.outline.visibility. ويختارcupertinoوm3-likeالقيمةscrollكما تفعل أصولهما. - مسطح وممرَّر. ما دام المحتوى في أعلاه يبقى الشريط مسطحًا. وحين يصير تحته محتوى يأخذ مظهر التمرير: في
m3-likeحاوية ملوَّنة بصبغة، وفيcupertinoحاوية مملوءة. يجري التغيّر بسلاسة على حركة تغيّر الحال (condition) الخاصة بالشريط، ويحدث فورًا حيث تُقلَّل الحركة. - الشريط الكبير لا يُعدّ ممرَّرًا إلا بعد أن ينطوي كليًّا. وحتى ذلك الحين يبيّن الانطواء نفسه أن المحتوى تحرّك.
- الشريط يبقى في مكانه. لا يختفي حين يُمرَّر المحتوى نحو الأسفل. ذاك سلوك تمرير يخص الشاشة، لا الشريط.
- أعطِ الشريط زر تنقل واحدًا على الأكثر: رجوع في شاشة وصل إليها الشخص من أخرى، وقائمة في شاشة عليا، وإغلاق في مهمة تملأ الشاشة.
- التزم بثلاثة إجراءات. واجعل الباقي في قائمة خيارات إضافية (overflow): زر أيقونة «خيارات إضافية» يفتح قائمة خيارات من Tanrei. تبلّغ بنيات التنقيح (debug) عن الإجراء الرابع وعن زر التنقل الثاني، وترسمهما مع ذلك.
- تبدو الأزرار كما تبدو في أي مكان آخر. يرتّبها الشريط ولا يغيّر ألوانها أبدًا.
- الزر الذي يذهب إلى مكان ما رابط: أعطِه الخاصية
href. فزر الرجوع الذي يقود إلى الصفحة الأم، أو إجراء المساعدة الذي يفتح صفحة، يصير عندئذ رابطًا حقيقيًّا يُرسم بهيئة الزر نفسه. أما زر الرجوع الذي يعود عبر سجل التصفح فيبقى زرًّا. - على الويب، يمكن أن يكون البحث أحد الإجراءات في صورة زر الأيقونة الذي يفتحه (الخاصية
barبالقيمةicon): زر بأيقونة فقط يفتح مربع حوار البحث أو عرضه الذي يملأ الشاشة.
يمكن للمنتج أن يضع شعاره في الشريط رابطًا إلى صفحته الأولى. أعطِ الشريط العنوان (brandHref، وbrand-href على الويب) واسم المنتج (brandLabel): يرسم الشريط رابطًا واحدًا حول الصور التي تعطيها له، ويسمّيه بذلك الاسم، ويخفي الصور عن التقنيات المساعدة، فيُقرأ الاسم مرة واحدة. ويضع العلامة بعد زر التنقل، في خلية بحجم خلية زر الأيقونة، ويعطيها comp.app-bar.brand.color ما لم تكن لصورك ألوانها الخاصة.
- صورة واحدة. ملف شعار واحد في
slot="brand"، بارتفاعcomp.app-bar.brand.image.height. - الرمز وحده. في
slot="brand-symbol"، بارتفاعcomp.app-bar.brand.symbol.size، في موضع أيقونة زر الأيقونة. - العلامة النصية وحدها. في
slot="brand-wordmark"، بارتفاعcomp.app-bar.brand.wordmark.height. - الرمز والعلامة النصية معًا. أعطِ الاثنين. افتراضيًا (
brandLayoutعلىlockup) يقفان جنبًا إلى جنب، تفصل بينهماcomp.app-bar.brand.lockup.gap، ومحاذاتهما كما يقولcomp.app-bar.brand.lockup.alignment. ويكون الرمز حينها بارتفاع العلامة النصية (comp.app-bar.brand.lockup.symbol.size). - حسب عرض النافذة. أعطِ الاثنين واضبط
brandLayoutعلىresponsive(brand-layoutعلى الويب): يعرض الشريط العلامة النصية ما دامت النافذة medium أو أعرض، والرمز ما دامت compact، ولا يعرضهما معًا أبدًا. داخل app shell يتبع فئة نافذة الـ shell.
يحدد الشريط حجم كل صورة ويحفظ نسبها. لا تستخدم العنوان لاسم المنتج: العنوان هو اسم الشاشة. لا ترسم SwiftUI وCompose وFlutter وReact Native العلامة بعد.
يعرض رأس هذا الموقع علامة Tanrei النصية وحدها، في كل عرض.
- اكتب العنوان اسمًا للشاشة، مثل «الوارد» أو «التقرير الفصلي». واجعله قصيرًا: فهو يبقى في سطر واحد ويُقتطع بعلامة الحذف (…).
- استخدم العنوان الفرعي لسياق يساعد على التفريق بين شاشتين متشابهتين: حساب، حالة، وقت آخر تعديل.
- اختر حجمًا واحدًا لكل الشاشات العليا وحجمًا واحدًا لكل ما عداها.
- لا تضع في المنافذ شيئًا غير أزرار الأيقونات: لا حقل نص، ولا حقل بحث، ولا علامات تبويب، ولا زرًّا مقسَّمًا. صف علامات التبويب يوضع تحت الشريط، ويمكن أن يُربط به عرض تبويبات يقع أسفل منه.
- لا تضع في الشريط إجراءً أساسيًّا مثل «إنشاء». استخدم زر الإجراء الرئيسي.
- لا تجعل للعنوان سطرًا ثانيًا ولا تجعله رابطًا.
- الدور
none- التسمية
- The title is the screen's heading (a level 1 heading on the web unless the product sets another level). The subtitle is read after it. The navigation and action items, and the control in context, are named by their own labels. The brand is one link to the product's first page named by brandLabel (the product's name), whichever of its images is shown; its images are hidden from assistive technology.
- الحد الأدنى للهدف
- تستوفي مساحة اللمس الحد الأدنى لحجم الهدف حتى لو كان الحجم المرئي أصغر. minimum 32px · pointer 44px · spacing 8px · touch 48px
لوحة المفاتيح
| المفاتيح | الإجراء |
|---|---|
| Tab | Moves focus to the navigation item, then to the brand's link, then to the control in context, then through the action items; the bar itself is not a tab stop. |
ملاحظات
- The app bar has no role of its own and takes no input: its buttons are the product's icon-only Buttons, which meet the minimum target themselves (BTN-5). The bar is not a landmark, because only the product knows whether it is its page's banner; a product that wants one wraps the bar in its own header element.
- The title is truncated rather than wrapped even at 200% text size: the bar's rows grow with the text, but a bar of several lines would push the screen's content away. The full title stays available to assistive technology.
- Where both titles of a large bar are partly visible, only the expanded one is exposed, so the heading is never read twice.
مولَّد من specs/app-bar.spec.json, tokens/sys/measure.tokens.json
- ليس للشريط دور خاص به ولا يقبل التركيز أبدًا. ينتقل المفتاح Tab من زر التنقل إلى الإجراءات واحدًا بعد آخر، بترتيب القراءة. يحتفظ كل زر بدوره واسمه والحد الأدنى لهدفه، والزر الذي له
hrefيُعرَّف للتقنيات المساعدة رابطًا. - يُعرَّف العنوان للتقنيات المساعدة عنوانًا (heading) للشاشة، من المستوى 1 على الويب ما لم تقل
heading-levelغير ذلك. ويُقرأ العنوان الفرعي بعده. والعنوان الفارغ لا يرسم شيئًا ولا يعرِّف أي عنوان. - يرسم الشريط الكبير عنوانين وهو ينطوي، لكنه لا يعرِّف إلا واحدًا: العنوان المتمدد ما دام شيء منه ظاهرًا، وعنوان الصف العلوي بعد أن ينطوي الشريط. لا يُقرأ العنوان مرتين أبدًا.
- الشريط ليس مَعلَمًا (landmark). أنت وحدك تعرف هل هو ترويسة الصفحة (banner).
- النص الأكبر يزيد ارتفاع صفوف الشريط، وهي ارتفاعات دنيا. يبقى العنوان والعنوان الفرعي في سطر واحد ويُقتطعان بدل أن يلتفّا، حتى عند 200%، فلا يدفع الشريط المحتوى بعيدًا أبدًا. ويبقى العنوان كاملًا متاحًا للتقنيات المساعدة. أما أزرار الأيقونات فلا تُكبَّر.
- يأخذ الشريط اتجاهه من موضع استخدامه. في تخطيط من اليمين إلى اليسار ينعكس: زر التنقل على اليمين والإجراءات على اليسار. والرسم الذي تعلّمه مجموعة الأيقونات على أنه يُعكس، كسهم الرجوع، يعكسه زره هو، لا الشريط.
- يُرتَّب العنوان والعنوان الفرعي نصًّا له اتجاهه الخاص، معزولًا عن النص المحيط به. فالاسم المكتوب من اليسار إلى اليمين في تخطيط من اليمين إلى اليسار يحتفظ بترتيبه، ويُقتطع عند نهايته هو، لا عند بدايته أبدًا.
- تبلّغ بنيات التنقيح عن أكثر من ثلاثة إجراءات، وأكثر من زر تنقل واحد، وحجم ليس في نظام التصميم.
فجوات معروفة في المنصات:
- على SwiftUI و Compose و Flutter و React Native: الخاصية
dividerVisibilityتنتظر أن تلحق تلك المنصات. فهي ترسم الحافة السفلية فقط حين يصير تحت الشريط محتوى، كما تفعلscroll. - على SwiftUI و Compose و Flutter و React Native: قواعد الاتجاه من اليمين إلى اليسار (رسم يُعكس حيث تعلّمه مجموعة الأيقونات، وعنوان يُرتَّب باتجاهه الخاص) تنتظر أن تلحق تلك المنصات، وكذلك الزر الذي له
href. - على الويب: كل حجم يتبع
rem، فتكبير حجم النص في المتصفح يكبّر أزرار الأيقونات مع النص. أما تكبير خطوط Tanrei وحدها فلا يفعل. - على الويب: الشريط الكبير الذي يتبع تمرير المستند نفسه ويوضع بالقيمة
position: stickyفي انسياب المستند يحرّك المحتوى بمقدار انطوائه فضلًا عن مقدار التمرير، فيتحرك المحتوى بضعف السرعة والشريط ينطوي. ضع الشريط الذي ينطوي فوق عنصر قابل للتمرير (scroll-target) أو في ترويسة ثابتة. - على الويب: يقع كل زر أيقونة في خلية بالحد الأدنى لهدف المؤشر المستخدَم، وهو 44 للفأرة، فيتباعد مركزا إجراءين 52، في حين يرسمهما Compose و Flutter، وهما يقيسان هدف اللمس 48، متباعدين 56.
- على SwiftUI: لا يتبع الشريط عرض التمرير إلا ابتداءً من iOS 18 و macOS 15. قبل ذلك لا شيء مربوط، ويرسم الشريط ما تقوله
isScrolled. - على SwiftUI: القيمة
isScrolled: trueعلى شريط كبير مربوط ترسمه منطويًا لكنها تُبقي ارتفاعه المتمدد في التخطيط، فتظهر فجوة فوق المحتوى ما دام في أعلاه. - على SwiftUI: حين يكون شريط النظام مخفيًّا، يُبقي الشريط سحبة الرجوع من الحافة عاملة باستبدال مفوَّض إيماءة الرجوع (pop-gesture delegate) في متحكم التنقل. وقد فُحص هذا على iOS 26.5 فقط. وكون الشريط الكبير يعرِّف عنوانًا واحدًا في كل مرة لم يُفحص بعد مع VoiceOver.
- على Flutter: المكوّن
TanreiAppBarبالحجم الكبير لا ينطوي مع محتواه، لأن عنصرًا صندوقيًّا (box widget) لا يستطيع أن ينكمش مع عرض التمرير الذي تحته. يتجاهل إشعارات التمرير، ويبقى متمددًا، ولا يُرسم منطويًا إلا ما دامتscrolledUnderبالقيمة true. استخدمTanreiSliverAppBarللشريط الذي ينطوي. - على Flutter: تبلّغ
preferredSizeعن ارتفاع غير محدود، لأنها لا تستطيع قراءة السمة. يترك scaffold الشريط يحدد حجمه بنفسه، لكن تخطيطك أنت لا يستطيع استخدام الرقم. - على Compose: يتوقف الانطواء حيث يتوقف التمرير، فقد يستقر الشريط الكبير نصف منطوٍ. لا ينجذب إلى الفتح أو الإغلاق بعد دفعة سريعة (fling) كما يفعل شريط Material. والمواصفة لا تطلب انجذابًا.
- على React Native: لينطوي الشريط الكبير بمقدار تمرير محتواه نفسه، تضع الشريط فوق عرض التمرير وتضيف إلى المحتوى حشوًا بارتفاع الشريط، كما يفعل المثال أعلاه. ولم يُفحص بعد على جهاز الانطواءُ ولا الهوامشُ ولا ما تعلنه قارئات الشاشة.
- على كل المنصات: تحت
cupertino، يزيد الصف العلوي ذو 44 نقطة إلى 48 حين يحمل أزرار أيقونات، لأن هدف اللمس لها 48 والحد الأدنى للهدف يغلب. وعلى الويب لا يزيد إلا مع مؤشر غير دقيق (coarse)، لأن هدف الفأرة 44. والصف المتمدد في الشريط الكبير أطول كذلك بنقطة واحدة من ارتفاع الحاوية في cupertino. - على كل المنصات: مع وجود عنوان فرعي، يُغطّى العنوان المتمدد قبل أن ينطوي الشريط كليًّا. ولا تقول المواصفة بعد أي عنوان يُعرَّف في ما بين ذلك. تنقل كل المنصات العنوان إلى الصف العلوي حالما يُغطّى العنوان المتمدد.
- أعطِ كل شريط عنوانًا. فهو عنوان الشاشة (heading).
- أعطِ كل زر أيقونة تسمية، مثل «رجوع» أو «بحث».
- على الويب، اضبط
heading-levelحين يكون في الصفحة عنوان من المستوى 1، وضع الشريط داخل<header>حين يكون ترويسة الصفحة. - اربط عرض الشاشة القابل للتمرير، أو اضبط
scrolledبنفسك، ليُظهر الشريط متى يكون تحته محتوى. - على React Native، مرِّر هوامش النافذة إلى
TanreiProvider. وعلى React Native و Compose، يأخذ الشريط الذي ليس في أعلى النافذة هوامش مختلفة (insets={{ top: 0 }}، أو قيمةwindowInsetsمن عندك). - اجعل العناوين قصيرة. العنوان المقتطع بعلامة الحذف يُقرأ كاملًا مع ذلك، لكن المبصرين لا يرون إلا أوله.