التنقّل بين الصفحات (Pagination)
صفّ من روابط الصفحات للتنقّل في قائمة طويلة أو بين نتائج البحث. روابط الصفحات وزرّا السابق والتالي كلها مبنية على مكوّن زر، ويأتي المكوّن بثلاثة أنماط وشكلين للزوايا وأربعة أحجام، كما في Figma.
التثبيت
npx @smicolon/fasla-ui@latest add paginationقبل إضافة أي مكوّن، شغّل الأمر init مرة واحدة في مشروعك. التفاصيل في صفحة التثبيت.
المكوّن مبني على مكوّن زر، فإضافته تضيف معه العنصر button من السجل.
معاينة
الأنماط (variant)
تختار الخاصية variant نمط الزر لكل جزء. في النمط default تكون روابط الصفحات بنمط Ghost والصفحة الحالية بنمط Solid. وفي النمط solid تكون روابط الصفحات بنمط Soft والصفحة الحالية بنمط Solid. أما في النمط outlined فروابط الصفحات وزرّا السابق والتالي بنمط Outline، والصفحة الحالية بنمط Soft. وفي النمطين الآخرين يكون زرّا السابق والتالي بنمط Ghost.
defaultsolidoutlinedالأحجام
أحجام مكوّن زر الأربعة: ارتفاع الحجم xs هو 26px، والحجم sm هو 32px، والحجم md هو 36px، والحجم lg هو 40px. ورابط الصفحة مربّع بهذا الارتفاع، ورقمه بحجم 14px في كل الأحجام. وإذا لم يتّسع المربّع لرقم، مثل 100 في الحجم xs، اتّسع المربّع له.
xssmmdlgالشكل
تعطي القيمة square للخاصية shape كل الأجزاء زوايا الزر القياسية بنصف قطر 8px، وتعطيها القيمة rounded أقصى استدارة: دوائر لروابط الصفحات، وشكل الكبسولة لزرّي السابق والتالي. وتتبع حلقة التركيز شكل الجزء.
defaultsolidoutlinedزرّا السابق والتالي
يعرض المكوّنان PaginationPrevious، PaginationNext تسمية وسهمًا. وتتبع التسمية لغة الصفحة، فهي «السابق» و«التالي» في الصفحة العربية، ولتغييرها استخدم الخاصية text. ومع الخاصية iconOnly يظهر السهم وحده في مربّع، وتبقى التسمية لقارئ الشاشة. وعطّل زر السابق في الصفحة الأولى، وزر التالي في الأخيرة، بالخاصية disabled.
النطاقات الطويلة
اعرض الصفحة الأولى والأخيرة، والصفحة الحالية مع صفحة على كل جانب منها، وضع المكوّن PaginationEllipsis مكان الصفحات المحذوفة: 1 … 5 6 7 … 20. والنقاط الثلاث مربّع بحجم رابط الصفحة، لكنها ليست رابطًا، ويسمع منها قارئ الشاشة «مزيد من الصفحات». ويُبقي زرّا السابق والتالي بالسهم وحده الصفَّ الطويل قصيرًا. وعلى شاشة الهاتف اعرض صفحات أقل حول الصفحة الحالية، كما يفعل هذا المثال.
إتاحة الوصول (accessibility)
- المكوّن
Paginationمنطقة تنقّل في الصفحة. سمّها بلغة الصفحة بالسمةaria-labelكما تفعل الأمثلة هنا، وإلا فاسمها Pagination. وأعطِ كل منطقة منها في الصفحة اسمًا خاصًا بها. - تحمل الصفحة الحالية السمة
aria-current="page"، فيعلن قارئ الشاشة أنها الصفحة الحالية. - اسم زرّي السابق والتالي هو تسميتهما، وتبقى مقروءة حين تخفيها الخاصية
iconOnlyعن العين. والأسهم مخفية عن التقنيات المساعدة. - لا تأخذ النقاط الثلاث التركيز. رمزها مخفي، ويقرؤها قارئ الشاشة «مزيد من الصفحات»، أو «More pages» حين تكون أقرب قيمة لـ
langهي الإنجليزية. - يحمل الرابط المعطّل السمة
aria-disabled، ويخرج من ترتيب التنقّل بمفتاح Tab، ولا يستجيب للنقر. ويبقى رابطًا، فيجده قارئ الشاشة. - كل جزء زر من مكوّن زر، فيرسم التركيز من لوحة المفاتيح حلقة الزر بعرض 2px، على بعد 2px من الرابط.
- يتبع الاتجاه السمة
dirفي الصفحة، ولا توجد له خاصية. في الصفحات من اليمين إلى اليسار يبدأ الصفّ من اليمين بزر السابق، ويشير كل سهم إلى الخارج. وتبقى أرقام الصفحات 1، 2، 3 لا ١، ٢، ٣.
الخصائص (props)
Pagination
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
variant | "default" | "solid" | "outlined" | "default" | نمط الزر لكل جزء: روابط صفحات بنمط Ghost أو Soft أو Outline. |
shape | "square" | "rounded" | "square" | زوايا كل الأجزاء: زوايا الزر القياسية، أو أقصى استدارة. |
size | "xs" | "sm" | "md" | "lg" | "md" | ارتفاع كل الأجزاء، من 26px إلى 40px. |
aria-label | string | "Pagination" | اسم منطقة التنقّل لقارئ الشاشة، بلغة الصفحة. |
children | ReactNode | لا يوجد | المكوّن PaginationContent. |
className | string | لا يوجد | أصناف إضافية للعنصر nav، وهو افتراضيًا صفّ في المنتصف. |
PaginationContent
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
children | ReactNode | لا يوجد | عناصر PaginationItem بترتيب القراءة. |
className | string | لا يوجد | أصناف إضافية للعنصر ul. وينتقل الصفّ إلى سطر ثانٍ بدل أن يتجاوز حاويته. |
PaginationItem
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
children | ReactNode | لا يوجد | رابط صفحة واحد، أو زر السابق أو التالي، أو النقاط الثلاث. |
className | string | لا يوجد | أصناف إضافية للعنصر li. |
PaginationLink
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
href | string | لا يوجد | وجهة الرابط. ويقبل الرابط كل سمات العنصر a الأخرى أيضًا. |
isActive | boolean | false | يعلّم الصفحة الحالية: بمظهر الصفحة الحالية، وبالسمة aria-current="page". |
disabled | boolean | false | يُخفت الرابط، ويُخرجه من ترتيب التنقّل بمفتاح Tab، ويتجاهل النقر. |
asChild | boolean | false | يجعل عنصره الابن الوحيد هو الرابط بهذا الشكل، مثل المكوّن Link في Next.js، أو العنصر button إذا كان التنقّل يغيّر الحالة لا عنوان الصفحة. |
children | ReactNode | لا يوجد | رقم الصفحة، يُكتب 1، 2، 3 في الصفحات العربية أيضًا. |
className | string | لا يوجد | أصناف إضافية للرابط. |
PaginationPrevious, PaginationNext
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
href | string | لا يوجد | وجهة الرابط. |
text | ReactNode | لا يوجد | التسمية. افتراضيًا «السابق» أو «التالي» بلغة الصفحة. |
iconOnly | boolean | false | يُظهر السهم وحده في مربّع، وتبقى التسمية لقارئ الشاشة. |
disabled | boolean | false | يُخفت الرابط، ويُخرجه من ترتيب التنقّل بمفتاح Tab، ويتجاهل النقر. |
asChild | boolean | false | يجعل عنصره الابن الوحيد هو الرابط، ويضع فيه التسمية والسهم. |
className | string | لا يوجد | أصناف إضافية للرابط. |
PaginationEllipsis
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
className | string | لا يوجد | أصناف إضافية للنقاط الثلاث. نص قارئ الشاشة مضمَّن فيها. |
الاستخدام
روابط إلى الصفحات
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
export function OrdersPagination() {
return (
<Pagination aria-label="التنقّل بين الصفحات">
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="?page=1" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="?page=1">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="?page=2" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="?page=3">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="?page=3" />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}نطاق طويل
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
const PAGES = [1, null, 5, 6, 7, null, 20]
export function ArticlesPagination() {
return (
<Pagination aria-label="التنقّل بين الصفحات" variant="outlined">
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="?page=5" iconOnly />
</PaginationItem>
{PAGES.map((page, i) => (
<PaginationItem key={i}>
{page === null ? (
<PaginationEllipsis />
) : (
<PaginationLink href={`?page=${page}`} isActive={page === 6}>
{page}
</PaginationLink>
)}
</PaginationItem>
))}
<PaginationItem>
<PaginationNext href="?page=7" iconOnly />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}التنقّل بالحالة
"use client"
import { useState } from "react"
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
const TOTAL = 5
export function TablePagination() {
const [page, setPage] = useState(1)
return (
<Pagination aria-label="التنقّل بين الصفحات" size="sm">
<PaginationContent>
<PaginationItem>
<PaginationPrevious asChild disabled={page === 1}>
<button type="button" onClick={() => setPage(page - 1)} />
</PaginationPrevious>
</PaginationItem>
{Array.from({ length: TOTAL }, (_, i) => i + 1).map((n) => (
<PaginationItem key={n}>
<PaginationLink asChild isActive={n === page}>
<button type="button" onClick={() => setPage(n)}>
{n}
</button>
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
<PaginationNext asChild disabled={page === TOTAL}>
<button type="button" onClick={() => setPage(page + 1)} />
</PaginationNext>
</PaginationItem>
</PaginationContent>
</Pagination>
)
}مع المكوّن Link في Next.js
import Link from "next/link"
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
} from "@/components/ui/pagination"
export function ResultsPagination() {
return (
<Pagination aria-label="التنقّل بين الصفحات" shape="rounded">
<PaginationContent>
<PaginationItem>
<PaginationLink asChild isActive>
<Link href="/results?page=1">1</Link>
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink asChild>
<Link href="/results?page=2">2</Link>
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext asChild>
<Link href="/results?page=2" />
</PaginationNext>
</PaginationItem>
</PaginationContent>
</Pagination>
)
}