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 (