علامات التبويب
عروض متعددة لشيء واحد، يظهر منها واحد في كل مرة. كل لوحة تُعرض على الخادم وتبقى في HTML، فيُفهرَس النص المخفي أيضًا.
split - الحجم لم يُقَس بعد
علامات التبويب
split - الحجم لم يُقَس بعدعروض متعددة لشيء واحد، يظهر منها واحد في كل مرة. كل لوحة تُعرض على الخادم وتبقى في HTML، فيُفهرَس النص المخفي أيضًا.
Revenue is up 12% against last quarter.
Three people edited this project today.
Only an owner can rename or delete this project.
جرّبه
variant
size
keepMounted
الشيفرة
tabs.tsx
import { TabsList, TabsPanel, TabsRoot, TabsTab } from 'topclassui/tabs'
<TabsRoot defaultValue="overview">
<TabsList aria-label="Project">
<TabsTab value="overview">Overview</TabsTab>
<TabsTab value="activity">Activity</TabsTab>
<TabsTab value="settings">Settings</TabsTab>
</TabsList>
<TabsPanel value="overview">Revenue is up 12% against last quarter.</TabsPanel>
<TabsPanel value="activity">Three people edited this project today.</TabsPanel>
<TabsPanel value="settings">Only an owner can rename or delete this project.</TabsPanel>
</TabsRoot>keepMounted هنا true، وقيمته في Base UI هي false: اللوحات غير النشطة تبقى مخفية في HTML القادم من الخادم.
كل الحالات، بدون تمرير الفأرة
حالتانThe active tab is semibold over a bar of the primary colour.
A tab at rest is muted.
A disabled tab cannot be made active.
The active tab sits on a raised surface.
A tab at rest sits on the tinted box.
A disabled tab cannot be made active.
أو استورده من الحزمة
topclassui/tabs
import { TabsRoot, TabsList, TabsPanel, TabsTab } from 'topclassui/tabs'ثبّت مرة واحدة:npm i topclassui@beta zod
أمثلة
- أربعة أجزاء بأسماء مسطّحة، ولا يوجد
Tabsوحده:TabsRootوTabsListوTabsTabوTabsPanel. كل جزء يمرّر بقية خصائصه إلى العنصر الذي يرسمه. - مرّر دائمًا
defaultValueأوvalue. دونهما لا يُظهر HTML القادم من الخادم أي لوحة حتى الترطيب. - اكتب محتوى اللوحة في مكوّن الخادم الخاص بك. اللوحة الثقيلة تأخذ
keepMounted={false}، فلا تُضاف حتى تُفتح علامتها. variantعلىTabsRoot:lineيرسم خطًّا تحت العلامة النشطة، وenclosedيرفعها داخل صندوق ملوّن.sizeعلىTabsRootيضبط كل العلامات.customلا يضبط شيئًا: أعطِ كلTabsTabقيمةclassNameالخاصة به.- العلامات الكثيرة تمرّر أفقيًا داخل القائمة، ولا تتحرك الصفحة. التسمية الطويلة تنتهي بعلامة حذف وتُقصّ عند 100 حرف.
valueمعonValueChangeيحتاجان ملفًا فيه'use client'، كما في المثال المتحكَّم به.
basic
Revenue is up 12% against last quarter.
Three people edited this project today.
Only an owner can rename or delete this project.
basic.tsx
import { TabsList, TabsPanel, TabsRoot, TabsTab } from 'topclassui/tabs'
/** The drawn sample, in the line look: the active tab is semibold over a bar of the primary
* colour. Each panel is written here, on the server, and all three are in the HTML. */
export default function Demo() {
return (
<TabsRoot defaultValue="overview">
<TabsList aria-label="Project">
<TabsTab value="overview">Overview</TabsTab>
<TabsTab value="activity">Activity</TabsTab>
<TabsTab value="settings">Settings</TabsTab>
</TabsList>
<TabsPanel value="overview">Revenue is up 12% against last quarter.</TabsPanel>
<TabsPanel value="activity">Three people edited this project today.</TabsPanel>
<TabsPanel value="settings">Only an owner can rename or delete this project.</TabsPanel>
</TabsRoot>
)
}
enclosed
Revenue is up 12% against last quarter.
Three people edited this project today.
Only an owner can rename or delete this project.
enclosed.tsx
import { TabsList, TabsPanel, TabsRoot, TabsTab } from 'topclassui/tabs'
/** The enclosed look: the tabs sit in a tinted box as wide as they are, and the active one
* is lifted onto a raised surface. */
export default function Demo() {
return (
<TabsRoot variant="enclosed" defaultValue="overview">
<TabsList aria-label="Project">
<TabsTab value="overview">Overview</TabsTab>
<TabsTab value="activity">Activity</TabsTab>
<TabsTab value="settings">Settings</TabsTab>
</TabsList>
<TabsPanel value="overview">Revenue is up 12% against last quarter.</TabsPanel>
<TabsPanel value="activity">Three people edited this project today.</TabsPanel>
<TabsPanel value="settings">Only an owner can rename or delete this project.</TabsPanel>
</TabsRoot>
)
}
sizes
The xs tabs of the line look.
Props of xs.
The sm tabs of the line look.
Props of sm.
The md tabs of the line look.
Props of md.
The lg tabs of the line look.
Props of lg.
The xl tabs of the line look.
Props of xl.
Tabs sized by their own classes.
Props of your own size.
The xs tabs of the enclosed look.
Props of xs.
The sm tabs of the enclosed look.
Props of sm.
The md tabs of the enclosed look.
Props of md.
The lg tabs of the enclosed look.
Props of lg.
The xl tabs of the enclosed look.
Props of xl.
Tabs sized by their own classes.
Props of your own size.
sizes.tsx
import { TabsList, TabsPanel, TabsRoot, TabsTab } from 'topclassui/tabs'
const SIZES = ['xs', 'sm', 'md', 'lg', 'xl'] as const
const LOOKS = ['line', 'enclosed'] as const
/** size goes on the root and reaches every tab. custom emits no height, padding or type
* step, so each tab takes them from its own className. */
export default function Demo() {
return (
<div className="grid gap-8">
{LOOKS.map((variant) => (
<div key={variant} className="grid gap-4">
{SIZES.map((size) => (
<TabsRoot key={size} variant={variant} size={size} defaultValue="one">
<TabsList aria-label={`${variant} ${size}`}>
<TabsTab value="one">Size {size}</TabsTab>
<TabsTab value="two">Props</TabsTab>
</TabsList>
<TabsPanel value="one">The {size} tabs of the {variant} look.</TabsPanel>
<TabsPanel value="two">Props of {size}.</TabsPanel>
</TabsRoot>
))}
<TabsRoot variant={variant} size="custom" defaultValue="one">
<TabsList aria-label={`${variant} custom`}>
<TabsTab value="one" className="h-14 px-5 text-lg">Your own size</TabsTab>
<TabsTab value="two" className="h-14 px-5 text-lg">Props</TabsTab>
</TabsList>
<TabsPanel value="one">Tabs sized by their own classes.</TabsPanel>
<TabsPanel value="two">Props of your own size.</TabsPanel>
</TabsRoot>
</div>
))}
</div>
)
}
disabled
Version 2.4 ships on Monday.
The changelog is written after the release.
Two props changed their default.
Version 2.4 ships on Monday.
The changelog is written after the release.
Two props changed their default.
disabled.tsx
import { TabsList, TabsPanel, TabsRoot, TabsTab } from 'topclassui/tabs'
/** A disabled tab is dimmed and cannot be made active. The arrow keys still stop on it, so
* a screen reader hears that it is there and that it is unavailable. */
export default function Demo() {
return (
<div className="grid gap-8">
<TabsRoot defaultValue="overview">
<TabsList aria-label="Release">
<TabsTab value="overview">Overview</TabsTab>
<TabsTab value="changelog" disabled>Changelog</TabsTab>
<TabsTab value="props">Props</TabsTab>
</TabsList>
<TabsPanel value="overview">Version 2.4 ships on Monday.</TabsPanel>
<TabsPanel value="changelog">The changelog is written after the release.</TabsPanel>
<TabsPanel value="props">Two props changed their default.</TabsPanel>
</TabsRoot>
<TabsRoot variant="enclosed" defaultValue="overview">
<TabsList aria-label="Release, enclosed">
<TabsTab value="overview">Overview</TabsTab>
<TabsTab value="changelog" disabled>Changelog</TabsTab>
<TabsTab value="props">Props</TabsTab>
</TabsList>
<TabsPanel value="overview">Version 2.4 ships on Monday.</TabsPanel>
<TabsPanel value="changelog">The changelog is written after the release.</TabsPanel>
<TabsPanel value="props">Two props changed their default.</TabsPanel>
</TabsRoot>
</div>
)
}
server-content
- Amal Haddad
- Omar Abed
- Lina Saleh
- Yousef Nasser
server-content.tsx
import { Button } from 'topclassui/button'
import { Input } from 'topclassui/input'
import { TabsList, TabsPanel, TabsRoot, TabsTab } from 'topclassui/tabs'
const MEMBERS = ['Amal Haddad', 'Omar Abed', 'Lina Saleh', 'Yousef Nasser']
/** What a panel holds is rendered on the server and handed to the client part as children:
* a list read from data, and a form whose field and button are Server Components too.
* It opens on the form; the list waits in the HTML, hidden, until its tab is opened. */
export default function Demo() {
return (
<TabsRoot defaultValue="invite">
<TabsList aria-label="Team">
<TabsTab value="members">Members</TabsTab>
<TabsTab value="invite">Invite</TabsTab>
</TabsList>
<TabsPanel value="members">
<ul className="grid gap-2 text-sm">
{MEMBERS.map((member) => <li key={member}>{member}</li>)}
</ul>
</TabsPanel>
<TabsPanel value="invite">
<form className="grid max-w-sm gap-2">
<label htmlFor="demo-invite" className="text-sm font-medium text-body">Email</label>
<Input id="demo-invite" name="email" type="email" autoComplete="email" required fullWidth />
<Button type="submit" className="justify-self-start">Send the invite</Button>
</form>
</TabsPanel>
</TabsRoot>
)
}
controlled
Only you can see a draft.
Two reviewers have been asked.
Everyone can read a published post.
controlled.tsx
'use client'
import { useState } from 'react'
import { Button } from 'topclassui/button'
import { TabsList, TabsPanel, TabsRoot, TabsTab } from 'topclassui/tabs'
/** value and onValueChange need a client file: a Server Component page cannot pass a
* function. Here a button outside the list moves to the last tab. */
export default function Demo() {
const [value, setValue] = useState('draft')
return (
<div className="grid gap-4">
<TabsRoot value={value} onValueChange={(next: string) => { setValue(next) }}>
<TabsList aria-label="Post">
<TabsTab value="draft">Draft</TabsTab>
<TabsTab value="review">Review</TabsTab>
<TabsTab value="published">Published</TabsTab>
</TabsList>
<TabsPanel value="draft">Only you can see a draft.</TabsPanel>
<TabsPanel value="review">Two reviewers have been asked.</TabsPanel>
<TabsPanel value="published">Everyone can read a published post.</TabsPanel>
</TabsRoot>
<Button variant="outline" className="justify-self-start" onClick={() => { setValue('published') }}>
Go to Published
</Button>
</div>
)
}
many
Everything under Overview.
Everything under Activity.
Everything under Members.
Everything under Billing.
Everything under Integrations.
Everything under Security.
Everything under Notifications.
Everything under Changelog.
Everything under Overview.
Everything under Activity.
Everything under Members.
Everything under Billing.
Everything under Integrations.
Everything under Security.
Everything under Notifications.
Everything under Changelog.
many.tsx
import { TabsList, TabsPanel, TabsRoot, TabsTab } from 'topclassui/tabs'
const SECTIONS = ['Overview', 'Activity', 'Members', 'Billing', 'Integrations', 'Security', 'Notifications', 'Changelog']
/** Eight tabs, more than a phone is wide: each list scrolls sideways inside itself, and the
* page does not. An arrow key brings the next tab into view. */
export default function Demo() {
return (
<div className="grid gap-8">
<TabsRoot defaultValue="Overview">
<TabsList aria-label="Workspace">
{SECTIONS.map((section) => <TabsTab key={section} value={section}>{section}</TabsTab>)}
</TabsList>
{SECTIONS.map((section) => <TabsPanel key={section} value={section}>Everything under {section}.</TabsPanel>)}
</TabsRoot>
<TabsRoot variant="enclosed" defaultValue="Overview">
<TabsList aria-label="Workspace, enclosed">
{SECTIONS.map((section) => <TabsTab key={section} value={section}>{section}</TabsTab>)}
</TabsList>
{SECTIONS.map((section) => <TabsPanel key={section} value={section}>Everything under {section}.</TabsPanel>)}
</TabsRoot>
</div>
)
}
long-label
Quarterly revenue, costs and forecast for every region the finance team reports on.
Nobody has written a note yet.
Quarterly revenue, costs and forecast for every region the finance team reports on.
Nobody has written a note yet.
long-label.tsx
import { TabsList, TabsPanel, TabsRoot, TabsTab } from 'topclassui/tabs'
const LONG = 'Quarterly revenue, costs and forecast for every region the finance team reports on'
/** A label too long for its list ends in an ellipsis: no tab is ever wider than the list it
* sits in, so the other tabs are still reached by scrolling the list. */
export default function Demo() {
return (
<div className="grid max-w-sm gap-8">
<TabsRoot defaultValue="report">
<TabsList aria-label="Report">
<TabsTab value="report">{LONG}</TabsTab>
<TabsTab value="notes">Notes</TabsTab>
</TabsList>
<TabsPanel value="report">{LONG}.</TabsPanel>
<TabsPanel value="notes">Nobody has written a note yet.</TabsPanel>
</TabsRoot>
<TabsRoot variant="enclosed" defaultValue="report">
<TabsList aria-label="Report, enclosed">
<TabsTab value="report">{LONG}</TabsTab>
<TabsTab value="notes">Notes</TabsTab>
</TabsList>
<TabsPanel value="report">{LONG}.</TabsPanel>
<TabsPanel value="notes">Nobody has written a note yet.</TabsPanel>
</TabsRoot>
</div>
)
}
بالعربية، من اليمين إلى اليسار
ارتفعت الإيرادات 12% عن الربع الماضي.
عدّل ثلاثة أشخاص هذا المشروع اليوم.
لا يعيد تسمية المشروع أو يحذفه إلا مالكه.
rtl.tsx
import { TopClassProvider } from 'topclassui/provider'
import { TabsList, TabsPanel, TabsRoot, TabsTab } from 'topclassui/tabs'
/** Arabic, inside TopClassProvider as the root layout would mount it. The first tab is at the
* right, and the arrow keys follow: the left arrow goes to the next tab, the right one back. */
export default function Demo() {
return (
<TopClassProvider direction="rtl" locale="ar">
<div dir="rtl" lang="ar">
<TabsRoot defaultValue="overview">
<TabsList aria-label="المشروع">
<TabsTab value="overview">نظرة عامة</TabsTab>
<TabsTab value="activity">النشاط</TabsTab>
<TabsTab value="settings">الإعدادات</TabsTab>
</TabsList>
<TabsPanel value="overview">ارتفعت الإيرادات 12% عن الربع الماضي.</TabsPanel>
<TabsPanel value="activity">عدّل ثلاثة أشخاص هذا المشروع اليوم.</TabsPanel>
<TabsPanel value="settings">لا يعيد تسمية المشروع أو يحذفه إلا مالكه.</TabsPanel>
</TabsRoot>
</div>
</TopClassProvider>
)
}
الخصائص
TabsRoot
TabsList
TabsPanel
TabsTab
لا خصائص خاصة به: يأخذ سمات العنصر الذي يعرضه.
لوحة المفاتيح وإمكانية الوصول
TabsListيحتاجaria-labelأوaria-labelledby: يرفضه TypeScript دون أحدهما.- العلامة المعطّلة بـ
disabledلا تُفتح، لكن مفاتيح الأسهم تتوقف عندها، فيسمع قارئ الشاشة أنها موجودة. - اللوحة محطة Tab مستقلة. مرّر
tabIndex={-1}إلىTabsPanelيبدأ بزر أو حقل.
من اليمين إلى اليسار
العلامة الأولى على اليمين. مع TopClassProvider وdirection="rtl" ينقل السهم الأيسر إلى العلامة التالية، ودون مزوّد تبقى المفاتيح من اليسار إلى اليمين.
الخادم والمتصفح
يُعرض على الخادم، والجزء التفاعلي مكوّن متصفح لا يُرسل إلا عند استخدامه.
الملفات: index.tsx, client.tsx.
على الشارة: split - الحجم لم يُقَس بعد.