mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
RTL carousels/FABs, special key+title fields, slider gallery fixes, and dashboard SSL sync agent for super-admin. Co-authored-by: Cursor <cursoragent@cursor.com>
125 lines
3.4 KiB
TypeScript
125 lines
3.4 KiB
TypeScript
import { useContext } from 'react'
|
|
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react'
|
|
import { LocaleContext } from '../context/LocaleContext'
|
|
import styles from './Pagination.module.css'
|
|
|
|
export interface PaginationProps {
|
|
currentPage: number
|
|
totalPages: number
|
|
onPageChange: (page: number) => void
|
|
ariaLabel?: string
|
|
/** Pages on each side of current. Default 3 → e.g. 1 2 3 [4] 5 6 7 */
|
|
siblingCount?: number
|
|
disabled?: boolean
|
|
/** `inline` for table footers (no top margin). */
|
|
variant?: 'default' | 'inline'
|
|
className?: string
|
|
}
|
|
|
|
function buildPageWindow(
|
|
currentPage: number,
|
|
totalPages: number,
|
|
siblingCount: number,
|
|
): number[] {
|
|
const start = Math.max(1, currentPage - siblingCount)
|
|
const end = Math.min(totalPages, currentPage + siblingCount)
|
|
const pages: number[] = []
|
|
for (let page = start; page <= end; page += 1) {
|
|
pages.push(page)
|
|
}
|
|
return pages
|
|
}
|
|
|
|
export function Pagination({
|
|
currentPage,
|
|
totalPages,
|
|
onPageChange,
|
|
ariaLabel = 'Pagination',
|
|
siblingCount = 3,
|
|
disabled = false,
|
|
variant = 'default',
|
|
className,
|
|
}: PaginationProps) {
|
|
const locale = useContext(LocaleContext)
|
|
const dir =
|
|
locale?.dir ??
|
|
(typeof document !== 'undefined' && document.documentElement.dir === 'rtl' ? 'rtl' : 'ltr')
|
|
const isRtl = dir === 'rtl'
|
|
|
|
if (totalPages <= 1) return null
|
|
|
|
const pages = buildPageWindow(currentPage, totalPages, siblingCount)
|
|
const rootClass = [
|
|
styles.pagination,
|
|
variant === 'inline' ? styles.paginationInline : '',
|
|
className ?? '',
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ')
|
|
|
|
const FirstIcon = isRtl ? ChevronsRight : ChevronsLeft
|
|
const PrevIcon = isRtl ? ChevronRight : ChevronLeft
|
|
const NextIcon = isRtl ? ChevronLeft : ChevronRight
|
|
const LastIcon = isRtl ? ChevronsLeft : ChevronsRight
|
|
|
|
return (
|
|
<nav className={rootClass} aria-label={ariaLabel} dir={dir}>
|
|
<button
|
|
type="button"
|
|
className={styles.navBtn}
|
|
onClick={() => onPageChange(1)}
|
|
disabled={disabled || currentPage === 1}
|
|
aria-label="First page"
|
|
>
|
|
<FirstIcon size={18} />
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
className={styles.navBtn}
|
|
onClick={() => onPageChange(currentPage - 1)}
|
|
disabled={disabled || currentPage === 1}
|
|
aria-label="Previous page"
|
|
>
|
|
<PrevIcon size={18} />
|
|
</button>
|
|
|
|
<div className={styles.pages}>
|
|
{pages.map((page) => (
|
|
<button
|
|
key={page}
|
|
type="button"
|
|
className={`${styles.pageBtn} ${page === currentPage ? styles.active : ''}`}
|
|
onClick={() => onPageChange(page)}
|
|
disabled={disabled}
|
|
aria-label={`Page ${page}`}
|
|
aria-current={page === currentPage ? 'page' : undefined}
|
|
>
|
|
{page}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
className={styles.navBtn}
|
|
onClick={() => onPageChange(currentPage + 1)}
|
|
disabled={disabled || currentPage === totalPages}
|
|
aria-label="Next page"
|
|
>
|
|
<NextIcon size={18} />
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
className={styles.navBtn}
|
|
onClick={() => onPageChange(totalPages)}
|
|
disabled={disabled || currentPage === totalPages}
|
|
aria-label="Last page"
|
|
>
|
|
<LastIcon size={18} />
|
|
</button>
|
|
</nav>
|
|
)
|
|
}
|