diff --git a/packages/components/src/pagination/messages/bg.json b/packages/components/src/pagination/messages/bg.json
new file mode 100644
index 0000000000..a5e1911473
--- /dev/null
+++ b/packages/components/src/pagination/messages/bg.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Show {size}",
+ "page-select": "{page} of {pages}",
+ "total-label": "{total} items",
+ "next-page-action": "Next page",
+ "previous-page-action": "Previous page"
+}
diff --git a/packages/components/src/pagination/messages/de.json b/packages/components/src/pagination/messages/de.json
new file mode 100644
index 0000000000..a5e1911473
--- /dev/null
+++ b/packages/components/src/pagination/messages/de.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Show {size}",
+ "page-select": "{page} of {pages}",
+ "total-label": "{total} items",
+ "next-page-action": "Next page",
+ "previous-page-action": "Previous page"
+}
diff --git a/packages/components/src/pagination/messages/en.json b/packages/components/src/pagination/messages/en.json
new file mode 100644
index 0000000000..a5e1911473
--- /dev/null
+++ b/packages/components/src/pagination/messages/en.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Show {size}",
+ "page-select": "{page} of {pages}",
+ "total-label": "{total} items",
+ "next-page-action": "Next page",
+ "previous-page-action": "Previous page"
+}
diff --git a/packages/components/src/pagination/messages/es.json b/packages/components/src/pagination/messages/es.json
new file mode 100644
index 0000000000..a5e1911473
--- /dev/null
+++ b/packages/components/src/pagination/messages/es.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Show {size}",
+ "page-select": "{page} of {pages}",
+ "total-label": "{total} items",
+ "next-page-action": "Next page",
+ "previous-page-action": "Previous page"
+}
diff --git a/packages/components/src/pagination/messages/fr.json b/packages/components/src/pagination/messages/fr.json
new file mode 100644
index 0000000000..a5e1911473
--- /dev/null
+++ b/packages/components/src/pagination/messages/fr.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Show {size}",
+ "page-select": "{page} of {pages}",
+ "total-label": "{total} items",
+ "next-page-action": "Next page",
+ "previous-page-action": "Previous page"
+}
diff --git a/packages/components/src/pagination/messages/index.ts b/packages/components/src/pagination/messages/index.ts
new file mode 100644
index 0000000000..7093d13cdd
--- /dev/null
+++ b/packages/components/src/pagination/messages/index.ts
@@ -0,0 +1,27 @@
+import bg from './bg.json'
+import de from './de.json'
+import en from './en.json'
+import es from './es.json'
+import fr from './fr.json'
+import it from './it.json'
+import ja from './ja.json'
+import ko from './ko.json'
+import nl from './nl.json'
+import pt from './pt.json'
+import ro from './ro.json'
+import th from './th.json'
+
+export const messages = {
+ 'en-US': en,
+ 'es-AR': es,
+ 'fr-FR': fr,
+ 'pt-BR': pt,
+ 'ja-JP': ja,
+ 'ko-KR': ko,
+ 'it-IT': it,
+ 'nl-NL': nl,
+ 'ro-RO': ro,
+ 'bg-BG': bg,
+ 'th-TH': th,
+ 'de-DE': de,
+}
diff --git a/packages/components/src/pagination/messages/it.json b/packages/components/src/pagination/messages/it.json
new file mode 100644
index 0000000000..a5e1911473
--- /dev/null
+++ b/packages/components/src/pagination/messages/it.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Show {size}",
+ "page-select": "{page} of {pages}",
+ "total-label": "{total} items",
+ "next-page-action": "Next page",
+ "previous-page-action": "Previous page"
+}
diff --git a/packages/components/src/pagination/messages/ja.json b/packages/components/src/pagination/messages/ja.json
new file mode 100644
index 0000000000..a5e1911473
--- /dev/null
+++ b/packages/components/src/pagination/messages/ja.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Show {size}",
+ "page-select": "{page} of {pages}",
+ "total-label": "{total} items",
+ "next-page-action": "Next page",
+ "previous-page-action": "Previous page"
+}
diff --git a/packages/components/src/pagination/messages/ko.json b/packages/components/src/pagination/messages/ko.json
new file mode 100644
index 0000000000..a5e1911473
--- /dev/null
+++ b/packages/components/src/pagination/messages/ko.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Show {size}",
+ "page-select": "{page} of {pages}",
+ "total-label": "{total} items",
+ "next-page-action": "Next page",
+ "previous-page-action": "Previous page"
+}
diff --git a/packages/components/src/pagination/messages/nl.json b/packages/components/src/pagination/messages/nl.json
new file mode 100644
index 0000000000..a5e1911473
--- /dev/null
+++ b/packages/components/src/pagination/messages/nl.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Show {size}",
+ "page-select": "{page} of {pages}",
+ "total-label": "{total} items",
+ "next-page-action": "Next page",
+ "previous-page-action": "Previous page"
+}
diff --git a/packages/components/src/pagination/messages/pt.json b/packages/components/src/pagination/messages/pt.json
new file mode 100644
index 0000000000..1bbb282818
--- /dev/null
+++ b/packages/components/src/pagination/messages/pt.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Mostrar {size}",
+ "page-select": "{page} de {pages}",
+ "total-label": "{total} itens",
+ "next-page-action": "Próxima página",
+ "previous-page-action": "Página anterior"
+}
diff --git a/packages/components/src/pagination/messages/ro.json b/packages/components/src/pagination/messages/ro.json
new file mode 100644
index 0000000000..a5e1911473
--- /dev/null
+++ b/packages/components/src/pagination/messages/ro.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Show {size}",
+ "page-select": "{page} of {pages}",
+ "total-label": "{total} items",
+ "next-page-action": "Next page",
+ "previous-page-action": "Previous page"
+}
diff --git a/packages/components/src/pagination/messages/th.json b/packages/components/src/pagination/messages/th.json
new file mode 100644
index 0000000000..a5e1911473
--- /dev/null
+++ b/packages/components/src/pagination/messages/th.json
@@ -0,0 +1,7 @@
+{
+ "size-select": "Show {size}",
+ "page-select": "{page} of {pages}",
+ "total-label": "{total} items",
+ "next-page-action": "Next page",
+ "previous-page-action": "Previous page"
+}
diff --git a/packages/components/src/pagination/pagination-select.tsx b/packages/components/src/pagination/pagination-select.tsx
new file mode 100644
index 0000000000..3851029e2f
--- /dev/null
+++ b/packages/components/src/pagination/pagination-select.tsx
@@ -0,0 +1,66 @@
+import type { ReactNode } from 'react'
+import React, { forwardRef } from 'react'
+
+import { Action } from '../action'
+
+import { Select, SelectPopover, SelectProvider } from '../select'
+import { Bleed } from '../bleed'
+import { Skeleton } from '../skeleton'
+
+export const PaginationSelect = forwardRef<
+ HTMLDivElement,
+ PaginationSelectProps
+>(function Pagination(props, ref) {
+ const {
+ loading = false,
+ value,
+ options,
+ label,
+ onValueChange,
+ children,
+ disabled = false,
+ ...otherProps
+ } = props
+
+ if (loading) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+ {options.length === 1 ? (
+
{label}
+ ) : (
+
onValueChange?.(Number(value))}
+ >
+
+
+
+ {options.map((option) => children(option))}
+
+
+
+ )}
+
+ )
+})
+
+export interface PaginationSelectProps {
+ onValueChange?: (value: number) => void
+ value: number
+ options: number[]
+ children: (option: number) => ReactNode
+ loading?: boolean
+ label: ReactNode
+ disabled?: boolean
+}
diff --git a/packages/components/src/pagination/pagination.css b/packages/components/src/pagination/pagination.css
index 9aebf6c58a..a012386609 100644
--- a/packages/components/src/pagination/pagination.css
+++ b/packages/components/src/pagination/pagination.css
@@ -5,37 +5,25 @@
letter-spacing: var(--sl-text-emphasis-letter-spacing);
}
- [data-sl-pagination-size] {
- position: relative;
+ [data-sl-pagination-select-action] {
+ font: var(--sl-text-emphasis-font);
+ letter-spacing: var(--sl-text-emphasis-letter-spacing);
}
- [data-sl-pagination-size-select] {
- color: var(--sl-fg-muted);
- border: none;
- border-radius: var(--sl-border-radius-medium);
- background: var(--sl-bg);
- padding: var(--sl-space-1) var(--sl-space-3);
- padding-right: var(--sl-space-8);
- appearance: none;
- cursor: pointer;
-
- &:focus {
- outline: none;
- }
+ [data-sl-pagination-select-popover] {
+ margin-top: var(--sl-space-1);
+ max-height: 12.75rem;
+ overflow-y: auto;
+ }
- &:focus-visible {
- box-shadow: var(--sl-focus-ring);
- }
+ [data-sl-pagination-select][data-loading='true'] {
+ width: 2.75rem;
+ height: 1.25rem;
}
- [data-sl-pagination-size-select-icon] {
- color: var(--sl-fg-soft);
- position: absolute;
- right: var(--sl-space-3);
- top: var(--sl-space-0);
- bottom: var(--sl-space-0);
- margin: auto;
- pointer-events: none;
+ [data-sl-pagination-total-label][data-loading='true'] {
+ width: 4.125rem;
+ height: 1.25rem;
}
[data-sl-pagination-actions] {
diff --git a/packages/components/src/pagination/pagination.tsx b/packages/components/src/pagination/pagination.tsx
index 8881a7b9df..419a4452c1 100644
--- a/packages/components/src/pagination/pagination.tsx
+++ b/packages/components/src/pagination/pagination.tsx
@@ -1,16 +1,17 @@
import type { ComponentPropsWithoutRef } from 'react'
-import React, { forwardRef } from 'react'
+import React, { forwardRef, useMemo } from 'react'
import { Stack } from '../stack'
import { Action } from '../action'
-import {
- IconCaretDown,
- IconCaretLeft,
- IconCaretRight,
-} from '@vtex/shoreline-icons'
-import { AccessibleIcon } from '../accessible-icon'
-import { VisuallyHidden } from '../visually-hidden'
-import { useId } from '@vtex/shoreline-utils'
+import { IconCaretLeft, IconCaretRight } from '@vtex/shoreline-icons'
+
import './pagination.css'
+import { SelectOption } from '../select'
+import { Skeleton } from '../skeleton'
+import { PaginationSelect } from './pagination-select'
+import { createMessageHook } from '../locale'
+import { messages } from './messages'
+
+const useMessage = createMessageHook(messages)
/**
* Pagination triggers allow merchants to view the size of a list and navigate between pages.
@@ -35,47 +36,46 @@ export const Pagination = forwardRef(
total,
onSizeChange,
onPageChange,
+ loading = false,
...otherProps
} = props
+ const getMessage = useMessage()
+
const hasSizes = sizeOptions.length > 0
- const totalPages = Math.ceil(total / size)
+ const { totalPages, pageOptions } = useMemo(() => {
+ const totalPages = Math.ceil(total / size)
+ const pageOptions = Array(totalPages)
+ .fill(1)
+ .map((_, index) => index + 1)
- const id = useId()
+ return { totalPages, pageOptions }
+ }, [total, size])
return (
{hasSizes && (
-
-
-
-
-
-
-
-
-
-
+ onSizeChange?.(value)}
+ label={getMessage('size-select', { size })}
+ disabled={loading}
+ >
+ {(option) => (
+
+ {getMessage('size-select', { size: option })}
+
+ )}
+
)}
- {total} items
+
+ {loading ? : getMessage('total-label', { total })}
+
(
onClick={() => {
onPageChange?.(page - 1, 'prev')
}}
- disabled={page === 1}
- aria-label="Previous page"
+ disabled={page === 1 || loading}
+ aria-label={getMessage('previous-page-action')}
data-sl-pagination-action-prev
>
-
- {page} of {totalPages}
-
+
+
onPageChange?.(value, 'selection')}
+ value={page}
+ options={pageOptions}
+ loading={loading}
+ label={getMessage('page-select', {
+ page,
+ pages: totalPages,
+ })}
+ >
+ {(option) => (
+ {option}
+ )}
+
+
{
onPageChange?.(page + 1, 'next')
}}
- disabled={page === totalPages}
- aria-label="Next page"
+ disabled={page === totalPages || loading}
+ aria-label={getMessage('next-page-action')}
data-sl-pagination-action-next
>
@@ -111,10 +125,11 @@ export const Pagination = forwardRef(
)
export interface PaginationProps extends ComponentPropsWithoutRef<'div'> {
- onPageChange?: (page: number, type: 'next' | 'prev') => void
+ onPageChange?: (page: number, type: 'next' | 'prev' | 'selection') => void
onSizeChange?: (size: number) => void
sizeOptions?: number[]
size: number
page: number
total: number
+ loading?: boolean
}
diff --git a/packages/components/src/pagination/stories/pagination.stories.tsx b/packages/components/src/pagination/stories/pagination.stories.tsx
index 6adac42634..04e1c00726 100644
--- a/packages/components/src/pagination/stories/pagination.stories.tsx
+++ b/packages/components/src/pagination/stories/pagination.stories.tsx
@@ -1,27 +1,28 @@
import './style.css'
+
import React, { useState } from 'react'
import { Pagination } from '../index'
+import { LocaleProvider } from '../../locale'
export default {
title: 'shoreline-components/pagination',
}
export function Default() {
- const [page, setPage] = useState(1)
- const [pageSize, setPageSize] = useState(25)
+ const [pagination, setPagination] = useState({ page: 1, size: 25 })
return (
{
- setPage(page)
+ setPagination((prev) => ({ ...prev, page }))
}}
total={754}
sizeOptions={[25, 50, 100]}
- size={pageSize}
- onSizeChange={(size) => setPageSize(size)}
+ size={pagination.size}
+ onSizeChange={(size) => setPagination((prev) => ({ ...prev, size }))}
/>
)
@@ -43,3 +44,38 @@ export function WithoutPageSize() {
)
}
+
+export function Loading() {
+ return (
+
+ )
+}
+
+export function Intl() {
+ const [pagination, setPagination] = useState({ page: 1, size: 25 })
+
+ return (
+
+
+
{
+ setPagination((prev) => ({ ...prev, page }))
+ }}
+ total={100}
+ sizeOptions={[25, 50, 100]}
+ size={pagination.size}
+ onSizeChange={(size) => setPagination((prev) => ({ ...prev, size }))}
+ />
+
+
+ )
+}
diff --git a/packages/components/src/pagination/tests/pagination.vitest.test.tsx b/packages/components/src/pagination/tests/pagination.vitest.test.tsx
index 368c78ef8c..78f8f7b192 100644
--- a/packages/components/src/pagination/tests/pagination.vitest.test.tsx
+++ b/packages/components/src/pagination/tests/pagination.vitest.test.tsx
@@ -17,6 +17,9 @@ describe('pagination', () => {
expect(
container.querySelector('[data-sl-pagination-actions]')
).toBeInTheDocument()
+ expect(
+ container.querySelector('[data-sl-pagination-page-select]')
+ ).toBeInTheDocument()
expect(
container.querySelector('[data-sl-pagination-total-label]')
).toBeInTheDocument()
diff --git a/packages/components/src/simple-table/stories/pagination.stories.tsx b/packages/components/src/simple-table/stories/pagination.stories.tsx
index a2b5e54d51..7cd2bbb1b9 100644
--- a/packages/components/src/simple-table/stories/pagination.stories.tsx
+++ b/packages/components/src/simple-table/stories/pagination.stories.tsx
@@ -149,7 +149,7 @@ function getItems(page: number, size: number) {
const paginatedData = data.slice(currentIndex, currentIndex + size)
res(paginatedData)
- }, 200)
+ }, 2000)
})
}
@@ -227,11 +227,15 @@ export function ServerPagination() {
)
const [{ page, size }, setPagination] = useState({ page: 1, size: 10 })
+ const [loading, setLoading] = useState(true)
const [products, setProducts] = useState([])
useEffect(() => {
+ if (loading) setLoading(true)
+
getItems(page, size).then((products: Product[]) => {
+ setLoading(false)
setProducts(products)
})
}, [page, size])
@@ -239,6 +243,7 @@ export function ServerPagination() {
return (