Skip to content

Commit a3dc543

Browse files
feat(pagination): add loading behavior
1 parent 61846c7 commit a3dc543

File tree

5 files changed

+150
-65
lines changed

5 files changed

+150
-65
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import type { ComponentPropsWithoutRef, ReactNode } from 'react'
2+
import React, { forwardRef } from 'react'
3+
4+
import { Action } from '../action'
5+
6+
import { Select, SelectPopover, SelectProvider } from '../select'
7+
import { Bleed } from '../bleed'
8+
import { Skeleton } from '../skeleton'
9+
10+
export const PaginationSelect = forwardRef<
11+
HTMLDivElement,
12+
PaginationSelectProps
13+
>(function Pagination(props, ref) {
14+
const {
15+
loading = false,
16+
value,
17+
options,
18+
label,
19+
onValueChange,
20+
children,
21+
disabled = false,
22+
...otherProps
23+
} = props
24+
25+
if (loading) {
26+
console.log({ loading })
27+
28+
return (
29+
<div data-sl-pagination-select data-loading={loading}>
30+
<Skeleton />
31+
</div>
32+
)
33+
}
34+
35+
return (
36+
<div data-sl-pagination-select ref={ref} {...otherProps}>
37+
{options.length === 1 ? (
38+
<div data-sl-pagination-label>{label}</div>
39+
) : (
40+
<SelectProvider
41+
value={String(value)}
42+
setValue={(value) => onValueChange?.(Number(value))}
43+
>
44+
<Select asChild>
45+
<Action data-sl-pagination-select-action disabled={disabled}>
46+
{label}
47+
</Action>
48+
</Select>
49+
<Bleed>
50+
<SelectPopover data-sl-pagination-select-popover>
51+
{children}
52+
</SelectPopover>
53+
</Bleed>
54+
</SelectProvider>
55+
)}
56+
</div>
57+
)
58+
})
59+
60+
export interface PaginationSelectProps extends ComponentPropsWithoutRef<'div'> {
61+
onValueChange?: (value: number) => void
62+
value: number
63+
options: number[]
64+
loading?: boolean
65+
label: ReactNode
66+
disabled?: boolean
67+
}

packages/components/src/pagination/pagination.css

+15
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,27 @@
55
letter-spacing: var(--sl-text-emphasis-letter-spacing);
66
}
77

8+
[data-sl-pagination-select-action] {
9+
font: var(--sl-text-emphasis-font);
10+
letter-spacing: var(--sl-text-emphasis-letter-spacing);
11+
}
12+
813
[data-sl-pagination-select-popover] {
914
margin-top: var(--sl-space-1);
1015
max-height: 12.75rem;
1116
overflow-y: auto;
1217
}
1318

19+
[data-sl-pagination-select][data-loading='true'] {
20+
width: 2.75rem;
21+
height: 1.25rem;
22+
}
23+
24+
[data-sl-pagination-total-label][data-loading='true'] {
25+
width: 4.125rem;
26+
height: 1.25rem;
27+
}
28+
1429
[data-sl-pagination-actions] {
1530
display: flex;
1631
align-items: center;

packages/components/src/pagination/pagination.tsx

+42-58
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@ import type { ComponentPropsWithoutRef } from 'react'
22
import React, { forwardRef, useMemo } from 'react'
33
import { Stack } from '../stack'
44
import { Action } from '../action'
5-
import {
6-
IconCaretDown,
7-
IconCaretLeft,
8-
IconCaretRight,
9-
} from '@vtex/shoreline-icons'
5+
import { IconCaretLeft, IconCaretRight } from '@vtex/shoreline-icons'
106

117
import './pagination.css'
12-
import { Select, SelectOption, SelectPopover, SelectProvider } from '../select'
13-
import { Bleed } from '../bleed'
8+
import { SelectOption } from '../select'
9+
import { Skeleton } from '../skeleton'
10+
import { PaginationSelect } from './pagination-select'
1411

1512
/**
1613
* Pagination triggers allow merchants to view the size of a list and navigate between pages.
@@ -35,90 +32,76 @@ export const Pagination = forwardRef<HTMLDivElement, PaginationProps>(
3532
total,
3633
onSizeChange,
3734
onPageChange,
35+
loading = false,
3836
...otherProps
3937
} = props
4038

4139
const hasSizes = sizeOptions.length > 0
4240

43-
const totalPages = Math.ceil(total / size)
41+
const totalPages = useMemo(() => Math.ceil(total / size), [total, size])
4442

45-
const pageOptions = useMemo(
46-
() => [...Array(totalPages).keys()].slice(1),
47-
[total, size]
48-
)
43+
const pageOptions = useMemo(() => {
44+
const totalPages = Math.ceil(total / size)
45+
46+
return [...Array(totalPages).keys()].slice(1)
47+
}, [total, size])
4948

5049
return (
5150
<div data-sl-pagination ref={ref} {...otherProps}>
5251
<Stack direction="row" space="$space-2">
5352
{hasSizes && (
54-
<SelectProvider
55-
value={String(size)}
56-
setValue={(value) => onSizeChange?.(Number(value))}
53+
<PaginationSelect
54+
data-sl-pagination-size-select
55+
value={size}
56+
options={sizeOptions}
57+
onValueChange={(value) => onSizeChange?.(value)}
58+
label={`Show ${size}`}
59+
disabled={loading}
5760
>
58-
<Select asChild>
59-
<Action>
60-
Show {size} <IconCaretDown width={16} height={16} />
61-
</Action>
62-
</Select>
63-
<Bleed>
64-
<SelectPopover data-sl-pagination-select-popover>
65-
{sizeOptions.map((sizeOption) => (
66-
<SelectOption value={String(sizeOption)}>
67-
Show {sizeOption}
68-
</SelectOption>
69-
))}
70-
</SelectPopover>
71-
</Bleed>
72-
</SelectProvider>
61+
{sizeOptions.map((option) => (
62+
<SelectOption value={String(option)}>
63+
Show {option}
64+
</SelectOption>
65+
))}
66+
</PaginationSelect>
7367
)}
7468

75-
<div data-sl-pagination-total-label>{total} items</div>
69+
<div data-sl-pagination-total-label data-loading={loading}>
70+
{loading ? <Skeleton /> : `${total} items`}
71+
</div>
7672

7773
<div data-sl-pagination-actions>
7874
<Action
7975
iconOnly
8076
onClick={() => {
8177
onPageChange?.(page - 1, 'prev')
8278
}}
83-
disabled={page === 1}
79+
disabled={page === 1 || loading}
8480
aria-label="Previous page"
8581
data-sl-pagination-action-prev
8682
>
8783
<IconCaretLeft />
8884
</Action>
8985

90-
{totalPages === 1 ? (
91-
<div data-sl-pagination-page-label>
92-
{page} of {totalPages}
93-
</div>
94-
) : (
95-
<SelectProvider
96-
value={String(page)}
97-
setValue={(value) => onPageChange?.(Number(value), 'selection')}
98-
>
99-
<Select asChild>
100-
<Action>
101-
{page} of {totalPages}
102-
</Action>
103-
</Select>
104-
<Bleed>
105-
<SelectPopover data-sl-pagination-select-popover>
106-
{pageOptions.map((pageOption) => (
107-
<SelectOption value={String(pageOption)}>
108-
{pageOption}
109-
</SelectOption>
110-
))}
111-
</SelectPopover>
112-
</Bleed>
113-
</SelectProvider>
114-
)}
86+
<PaginationSelect
87+
data-sl-pagination-page-select
88+
onValueChange={(value) => onPageChange?.(value, 'selection')}
89+
value={page}
90+
options={pageOptions}
91+
loading={loading}
92+
label={`${page} of ${pageOptions.length}`}
93+
>
94+
{pageOptions.map((option) => (
95+
<SelectOption value={String(option)}>{option}</SelectOption>
96+
))}
97+
</PaginationSelect>
11598

11699
<Action
117100
iconOnly
118101
onClick={() => {
119102
onPageChange?.(page + 1, 'next')
120103
}}
121-
disabled={page === totalPages}
104+
disabled={page === totalPages || loading}
122105
aria-label="Next page"
123106
data-sl-pagination-action-next
124107
>
@@ -138,4 +121,5 @@ export interface PaginationProps extends ComponentPropsWithoutRef<'div'> {
138121
size: number
139122
page: number
140123
total: number
124+
loading?: boolean
141125
}

packages/components/src/pagination/stories/pagination.stories.tsx

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import './style.css'
2+
23
import React, { useState } from 'react'
34

45
import { Pagination } from '../index'
@@ -8,20 +9,19 @@ export default {
89
}
910

1011
export function Default() {
11-
const [page, setPage] = useState(1)
12-
const [pageSize, setPageSize] = useState(25)
12+
const [pagination, setPagination] = useState({ page: 1, size: 25 })
1313

1414
return (
1515
<div className="pagination-container">
1616
<Pagination
17-
page={page}
17+
page={pagination.page}
1818
onPageChange={(page) => {
19-
setPage(page)
19+
setPagination((prev) => ({ ...prev, page }))
2020
}}
2121
total={754}
2222
sizeOptions={[25, 50, 100]}
23-
size={pageSize}
24-
onSizeChange={(size) => setPageSize(size)}
23+
size={pagination.size}
24+
onSizeChange={(size) => setPagination((prev) => ({ ...prev, size }))}
2525
/>
2626
</div>
2727
)
@@ -43,3 +43,17 @@ export function WithoutPageSize() {
4343
</div>
4444
)
4545
}
46+
47+
export function Loading() {
48+
return (
49+
<div className="pagination-container">
50+
<Pagination
51+
page={1}
52+
total={754}
53+
sizeOptions={[25, 50, 100]}
54+
size={25}
55+
loading
56+
/>
57+
</div>
58+
)
59+
}

packages/components/src/simple-table/stories/pagination.stories.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ function getItems(page: number, size: number) {
149149
const paginatedData = data.slice(currentIndex, currentIndex + size)
150150

151151
res(paginatedData)
152-
}, 200)
152+
}, 2000)
153153
})
154154
}
155155

@@ -227,18 +227,23 @@ export function ServerPagination() {
227227
)
228228

229229
const [{ page, size }, setPagination] = useState({ page: 1, size: 10 })
230+
const [loading, setLoading] = useState(true)
230231

231232
const [products, setProducts] = useState<Product[]>([])
232233

233234
useEffect(() => {
235+
if (loading) setLoading(true)
236+
234237
getItems(page, size).then((products: Product[]) => {
238+
setLoading(false)
235239
setProducts(products)
236240
})
237241
}, [page, size])
238242

239243
return (
240244
<Stack>
241245
<Pagination
246+
loading={loading}
242247
page={page}
243248
size={size}
244249
sizeOptions={[10, 25, 50]}

0 commit comments

Comments
 (0)