@@ -2,17 +2,184 @@ import * as React from 'react'
22import { ChevronLeftIcon , ChevronRightIcon , MoreHorizontalIcon } from 'lucide-react'
33
44import { cn } from '../../lib/utils'
5- import { Button , buttonVariants } from './button'
5+ import { buttonVariants } from './button'
6+
7+ type PaginationProps = Omit < React . ComponentProps < 'nav' > , 'onChange' > & {
8+ totalCount : number
9+ selectedPage : number
10+ showFirstPageButton ?: boolean
11+ showLastPageButton ?: boolean
12+ onPageChange ?: ( page : number ) => void
13+ isTotalCountClipped ?: boolean
14+ rowsPerPage : number
15+ }
16+
17+ function Pagination ( {
18+ className,
19+ totalCount,
20+ selectedPage,
21+ showFirstPageButton = false ,
22+ showLastPageButton = false ,
23+ onPageChange,
24+ isTotalCountClipped = false ,
25+ rowsPerPage,
26+ ...props
27+ } : PaginationProps ) {
28+ if ( totalCount <= 0 || selectedPage <= 0 || rowsPerPage <= 0 ) {
29+ return null
30+ }
31+ const totalCountBoundary = totalCount + ( selectedPage - 1 ) * rowsPerPage
32+ const count = Math . ceil ( totalCountBoundary / rowsPerPage )
33+
34+ if ( count <= 0 ) {
35+ return null
36+ }
37+
38+ const handlePageChange = ( newPage : number ) => {
39+ if ( onPageChange ) {
40+ onPageChange ( newPage )
41+ }
42+ }
43+
44+ const renderPaginationItems = ( ) => {
45+ const items = [ ]
46+ const maxVisiblePages = 7
47+
48+ items . push (
49+ < PaginationItem key = "prev" >
50+ < PaginationPrevious onClick = { ( ) => handlePageChange ( selectedPage - 1 ) } disabled = { selectedPage <= 1 } />
51+ </ PaginationItem >
52+ )
53+
54+ if ( showFirstPageButton ) {
55+ items . push (
56+ < PaginationItem key = "first" >
57+ < PaginationLink
58+ onClick = { ( ) => handlePageChange ( 1 ) }
59+ disabled = { selectedPage <= 1 }
60+ aria-label = "Go to first page"
61+ >
62+ First
63+ </ PaginationLink >
64+ </ PaginationItem >
65+ )
66+ }
67+
68+ if ( count <= maxVisiblePages ) {
69+ for ( let i = 1 ; i <= count ; i ++ ) {
70+ items . push (
71+ < PaginationItem key = { i } >
72+ < PaginationLink selected = { selectedPage === i } onClick = { ( ) => handlePageChange ( i ) } >
73+ { i }
74+ </ PaginationLink >
75+ </ PaginationItem >
76+ )
77+ }
78+ } else {
79+ const leftSiblingIndex = Math . max ( selectedPage - 1 , 1 )
80+ const rightSiblingIndex = Math . min ( selectedPage + 1 , count )
81+ const shouldShowLeftDots = leftSiblingIndex > 2
82+ const shouldShowRightDots = rightSiblingIndex < count - 1
83+ if ( ! shouldShowLeftDots && shouldShowRightDots ) {
84+ const leftItemCount = 5
85+ for ( let i = 1 ; i <= leftItemCount ; i ++ ) {
86+ items . push (
87+ < PaginationItem key = { i } >
88+ < PaginationLink selected = { selectedPage === i } onClick = { ( ) => handlePageChange ( i ) } >
89+ { i }
90+ </ PaginationLink >
91+ </ PaginationItem >
92+ )
93+ }
94+
95+ items . push ( < PaginationEllipsis key = "ellipsis-right" /> )
96+
97+ items . push (
98+ < PaginationItem key = { count } >
99+ < PaginationLink onClick = { ( ) => handlePageChange ( count ) } > { count } </ PaginationLink >
100+ </ PaginationItem >
101+ )
102+ } else if ( shouldShowLeftDots && ! shouldShowRightDots ) {
103+ items . push (
104+ < PaginationItem key = { 1 } >
105+ < PaginationLink onClick = { ( ) => handlePageChange ( 1 ) } > 1</ PaginationLink >
106+ </ PaginationItem >
107+ )
108+
109+ items . push ( < PaginationEllipsis key = "ellipsis-left" /> )
110+
111+ const rightItemCount = 5
112+ for ( let i = count - rightItemCount + 1 ; i <= count ; i ++ ) {
113+ items . push (
114+ < PaginationItem key = { i } >
115+ < PaginationLink selected = { selectedPage === i } onClick = { ( ) => handlePageChange ( i ) } >
116+ { i }
117+ </ PaginationLink >
118+ </ PaginationItem >
119+ )
120+ }
121+ } else if ( shouldShowLeftDots && shouldShowRightDots ) {
122+ items . push (
123+ < PaginationItem key = { 1 } >
124+ < PaginationLink onClick = { ( ) => handlePageChange ( 1 ) } > 1</ PaginationLink >
125+ </ PaginationItem >
126+ )
127+
128+ items . push ( < PaginationEllipsis key = "ellipsis-left" /> )
129+
130+ for ( let i = leftSiblingIndex ; i <= rightSiblingIndex ; i ++ ) {
131+ items . push (
132+ < PaginationItem key = { i } >
133+ < PaginationLink selected = { selectedPage === i } onClick = { ( ) => handlePageChange ( i ) } >
134+ { i }
135+ </ PaginationLink >
136+ </ PaginationItem >
137+ )
138+ }
139+
140+ items . push ( < PaginationEllipsis key = "ellipsis-right" /> )
141+
142+ items . push (
143+ < PaginationItem key = { count } >
144+ < PaginationLink onClick = { ( ) => handlePageChange ( count ) } > { count } </ PaginationLink >
145+ </ PaginationItem >
146+ )
147+ }
148+ }
149+
150+ if ( showLastPageButton && ! isTotalCountClipped ) {
151+ items . push (
152+ < PaginationItem key = "last" >
153+ < PaginationLink
154+ onClick = { ( ) => handlePageChange ( count ) }
155+ disabled = { selectedPage >= count }
156+ aria-label = "Go to last page"
157+ >
158+ Last
159+ </ PaginationLink >
160+ </ PaginationItem >
161+ )
162+ }
163+
164+ items . push (
165+ < PaginationItem key = "next" >
166+ < PaginationNext onClick = { ( ) => handlePageChange ( selectedPage + 1 ) } disabled = { selectedPage >= count } />
167+ </ PaginationItem >
168+ )
169+
170+ return items
171+ }
6172
7- function Pagination ( { className, ...props } : React . ComponentProps < 'nav' > ) {
8173 return (
9174 < nav
10175 role = "navigation"
11176 aria-label = "pagination"
12177 data-slot = "pagination"
13178 className = { cn ( 'mx-auto flex w-full justify-center' , className ) }
14179 { ...props }
15- />
180+ >
181+ < PaginationContent > { renderPaginationItems ( ) } </ PaginationContent >
182+ </ nav >
16183 )
17184}
18185
@@ -31,34 +198,32 @@ function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
31198}
32199
33200type PaginationLinkProps < C extends React . ElementType = 'a' > = {
34- isActive ?: boolean
201+ selected ?: boolean
35202 disabled ?: boolean
36203 linkComponent ?: C
37- } & Pick < React . ComponentProps < typeof Button > , 'size' > &
38- Omit < React . ComponentProps < C > , 'ref' >
204+ } & Omit < React . ComponentProps < C > , 'ref' >
39205
40206function PaginationLink < C extends React . ElementType = 'a' > ( {
41207 className,
42- isActive ,
208+ selected ,
43209 disabled,
44- size = 'icon' ,
45210 linkComponent,
46211 ...props
47212} : PaginationLinkProps < C > ) {
48213 const Component = linkComponent || 'a'
49214
50215 return (
51216 < Component
52- aria-current = { isActive ? 'page' : undefined }
217+ aria-current = { selected ? 'page' : undefined }
53218 aria-disabled = { disabled }
54219 tabIndex = { disabled ? - 1 : undefined }
55220 data-slot = "pagination-link"
56- data-active = { isActive }
221+ data-active = { selected }
57222 data-disabled = { disabled }
58223 className = { cn (
59224 buttonVariants ( {
60- variant : isActive ? 'outline' : 'ghost' ,
61- size,
225+ variant : selected ? 'outline' : 'ghost' ,
226+ size : 'default' ,
62227 } ) ,
63228 disabled && 'pointer-events-none opacity-50' ,
64229 className
@@ -77,7 +242,6 @@ function PaginationPrevious({
77242 return (
78243 < PaginationLink
79244 aria-label = "Go to previous page"
80- size = "default"
81245 className = { cn ( 'gap-1 px-2.5 sm:pl-2.5' , className ) }
82246 linkComponent = { linkComponent }
83247 { ...props }
@@ -97,7 +261,6 @@ function PaginationNext({
97261 return (
98262 < PaginationLink
99263 aria-label = "Go to next page"
100- size = "default"
101264 className = { cn ( 'gap-1 px-2.5 sm:pr-2.5' , className ) }
102265 linkComponent = { linkComponent }
103266 { ...props }
0 commit comments