Skip to content

Commit 6bd5911

Browse files
Merge 19c4fee into 3b6b1d1
2 parents 3b6b1d1 + 19c4fee commit 6bd5911

File tree

3 files changed

+57
-112
lines changed

3 files changed

+57
-112
lines changed

.changeset/thick-days-fly.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
fix(DataTable): fix incorrect page numbers rendered bug

packages/react/src/DataTable/Pagination.tsx

Lines changed: 25 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {ChevronLeftIcon, ChevronRightIcon} from '@primer/octicons-react'
22
import type React from 'react'
3-
import {useCallback, useState} from 'react'
3+
import {useCallback, useMemo, useState} from 'react'
44
import styled from 'styled-components'
55
import {get} from '../constants'
66
import {Button} from '../internal/components/ButtonReset'
@@ -9,6 +9,7 @@ import {VisuallyHidden} from '../VisuallyHidden'
99
import {warning} from '../utils/warning'
1010
import type {ResponsiveValue} from '../hooks/useResponsiveValue'
1111
import {viewportRanges} from '../hooks/useResponsiveValue'
12+
import {buildPaginationModel} from '../Pagination/model'
1213

1314
const StyledPagination = styled.nav`
1415
display: flex;
@@ -166,12 +167,6 @@ export type PaginationProps = Omit<React.ComponentPropsWithoutRef<'nav'>, 'onCha
166167
totalCount: number
167168
}
168169

169-
/**
170-
* Specifies the maximum number of items in between the first and last page,
171-
* including truncated steps
172-
*/
173-
const MAX_TRUNCATED_STEP_COUNT = 7
174-
175170
export function Pagination({
176171
'aria-label': label,
177172
defaultPageIndex,
@@ -197,19 +192,7 @@ export function Pagination({
197192
pageSize,
198193
totalCount,
199194
})
200-
const truncatedPageCount = pageCount > 2 ? Math.min(pageCount - 2, MAX_TRUNCATED_STEP_COUNT) : 0
201-
const defaultOffset = getDefaultOffsetStartIndex(pageIndex, pageCount, truncatedPageCount)
202-
const [defaultOffsetStartIndex, setDefaultOffsetStartIndex] = useState(defaultOffset)
203-
const [offsetStartIndex, setOffsetStartIndex] = useState(defaultOffsetStartIndex)
204-
205-
if (defaultOffsetStartIndex !== defaultOffset) {
206-
setOffsetStartIndex(defaultOffset)
207-
setDefaultOffsetStartIndex(defaultOffset)
208-
}
209195

210-
const offsetEndIndex = offsetStartIndex + truncatedPageCount - 1
211-
const hasLeadingTruncation = offsetStartIndex >= 2
212-
const hasTrailingTruncation = pageCount - 1 - offsetEndIndex > 1
213196
const getViewportRangesToHidePages = useCallback(() => {
214197
if (typeof showPages !== 'boolean') {
215198
return Object.keys(showPages).filter(key => !showPages[key as keyof typeof viewportRanges]) as Array<
@@ -224,6 +207,10 @@ export function Pagination({
224207
}
225208
}, [showPages])
226209

210+
const model = useMemo(() => {
211+
return buildPaginationModel(pageCount, pageIndex + 1, !!showPages, 1, 2)
212+
}, [pageCount, pageIndex, showPages])
213+
227214
return (
228215
<LiveRegion>
229216
<LiveRegionOutlet />
@@ -240,77 +227,33 @@ export function Pagination({
240227
if (!hasPreviousPage) {
241228
return
242229
}
243-
244230
selectPreviousPage()
245-
if (hasLeadingTruncation) {
246-
if (pageIndex - 1 < offsetStartIndex + 1) {
247-
setOffsetStartIndex(offsetStartIndex - 1)
248-
}
249-
}
250231
}}
251232
>
252233
{hasPreviousPage ? <ChevronLeftIcon /> : null}
253234
<span className="TablePaginationActionLabel">Previous</span>
254235
<VisuallyHidden>&nbsp;page</VisuallyHidden>
255236
</Button>
256237
</Step>
257-
{pageCount > 0 ? (
258-
<Step>
259-
<Page
260-
active={pageIndex === 0}
261-
onClick={() => {
262-
selectPage(0)
263-
if (pageCount > 1) {
264-
setOffsetStartIndex(1)
265-
}
266-
}}
267-
>
268-
{1}
269-
{hasLeadingTruncation ? <VisuallyHidden></VisuallyHidden> : null}
270-
</Page>
271-
</Step>
272-
) : null}
273-
{pageCount > 2
274-
? Array.from({length: truncatedPageCount}).map((_, i) => {
275-
if (i === 0 && hasLeadingTruncation) {
276-
return <TruncationStep key={`truncation-${i}`} />
277-
}
278-
279-
if (i === truncatedPageCount - 1 && hasTrailingTruncation) {
280-
return <TruncationStep key={`truncation-${i}`} />
281-
}
282-
283-
const page = offsetStartIndex + i
284-
return (
285-
<Step key={i}>
286-
<Page
287-
active={pageIndex === page}
288-
onClick={() => {
289-
selectPage(page)
290-
}}
291-
>
292-
{page + 1}
293-
{i === truncatedPageCount - 2 && hasTrailingTruncation ? (
294-
<VisuallyHidden></VisuallyHidden>
295-
) : null}
296-
</Page>
297-
</Step>
298-
)
299-
})
300-
: null}
301-
{pageCount > 1 ? (
302-
<Step>
303-
<Page
304-
active={pageIndex === pageCount - 1}
305-
onClick={() => {
306-
selectPage(pageCount - 1)
307-
setOffsetStartIndex(pageCount - 1 - truncatedPageCount)
308-
}}
309-
>
310-
{pageCount}
311-
</Page>
312-
</Step>
313-
) : null}
238+
{model.map((page, i) => {
239+
if (page.type === 'BREAK') {
240+
return <TruncationStep key={`truncation-${i}`} />
241+
} else if (page.type === 'NUM') {
242+
return (
243+
<Step key={i}>
244+
<Page
245+
active={!!page.selected}
246+
onClick={() => {
247+
selectPage(page.num)
248+
}}
249+
>
250+
{page.num}
251+
{page.precedesBreak ? <VisuallyHidden></VisuallyHidden> : null}
252+
</Page>
253+
</Step>
254+
)
255+
}
256+
})}
314257
<Step>
315258
<Button
316259
className="TablePaginationAction"
@@ -321,13 +264,7 @@ export function Pagination({
321264
if (!hasNextPage) {
322265
return
323266
}
324-
325267
selectNextPage()
326-
if (hasTrailingTruncation) {
327-
if (pageIndex + 1 > offsetEndIndex - 1) {
328-
setOffsetStartIndex(offsetStartIndex + 1)
329-
}
330-
}
331268
}}
332269
>
333270
<span className="TablePaginationActionLabel">Next</span>
@@ -341,30 +278,6 @@ export function Pagination({
341278
)
342279
}
343280

344-
function getDefaultOffsetStartIndex(pageIndex: number, pageCount: number, truncatedPageCount: number): number {
345-
// When the current page is closer to the end of the list than the beginning
346-
if (pageIndex > pageCount - 1 - pageIndex) {
347-
if (pageCount - 1 - pageIndex >= truncatedPageCount) {
348-
return Math.max(pageIndex - 3, 1)
349-
}
350-
return Math.max(pageCount - 1 - truncatedPageCount, 1)
351-
}
352-
353-
// When the current page is closer to the beginning of the list than the end
354-
if (pageIndex < pageCount - 1 - pageIndex) {
355-
if (pageIndex >= truncatedPageCount) {
356-
return Math.max(pageIndex - 3, 1)
357-
}
358-
return 1
359-
}
360-
361-
// When the current page is the midpoint between the beginning and the end
362-
if (pageIndex < truncatedPageCount) {
363-
return pageIndex
364-
}
365-
return Math.max(pageIndex - 3, 1)
366-
}
367-
368281
type RangeProps = {
369282
pageStart: number
370283
pageEnd: number

packages/react/src/DataTable/__tests__/Pagination.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,33 @@ describe('Table.Pagination', () => {
376376
expect(getCurrentPage()).toEqual(getPage(1))
377377
expect(getInvalidPages()).toHaveLength(0)
378378
})
379+
380+
it('should display correct page range when navigating to page 4 with 100 items and page size of 15', async () => {
381+
// When we have 100 items spread across pages of 15 items, and we go to page 4, we should get the proper page range
382+
const user = userEvent.setup()
383+
const onChange = vi.fn()
384+
385+
render(<Pagination aria-label="Test label" onChange={onChange} pageSize={15} totalCount={100} />)
386+
387+
// With 100 items and page size 15, we should have 7 pages total (pages 1-6 have 15 items each, page 7 has 10 items)
388+
const pages = getPages()
389+
expect(pages).toHaveLength(7)
390+
391+
// Navigate to page 4 (index 3)
392+
await user.click(getPage(3))
393+
394+
expect(onChange).toHaveBeenCalledWith({
395+
pageIndex: 3,
396+
})
397+
398+
// Page 4 should show items 46-60
399+
expect(getPageRange()).toEqual('46 through 60 of 100')
400+
expect(getCurrentPage()).toEqual(getPage(3))
401+
402+
// Verify all pages are displayed (no truncation needed with only 7 pages)
403+
const pageNumbers = getPages().map(p => p.textContent?.replace(/\D/g, ''))
404+
expect(pageNumbers).toEqual(['1', '2', '3', '4', '5', '6', '7'])
405+
})
379406
})
380407

381408
function getPages() {

0 commit comments

Comments
 (0)