Skip to content

Commit 05818ca

Browse files
committed
also localize aria-labels
1 parent e305c0d commit 05818ca

5 files changed

Lines changed: 70 additions & 18 deletions

File tree

.changeset/pagination-labels-prop.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
'@primer/react-brand': patch
33
---
44

5-
Added a `labels` prop to the `Pagination` component for customizing the visible text of the previous and next controls.
5+
Added a `labels` prop to the `Pagination` component for customizing the user-facing labels of the previous and next controls.
66

77
```jsx
8-
<Pagination pageCount={10} currentPage={2} labels={{prev: 'Précédent', next: 'Suivant'}} />
8+
<Pagination
9+
pageCount={10}
10+
currentPage={2}
11+
labels={{prev: 'Précédent', next: 'Suivant', prevAriaLabel: 'Page précédente', nextAriaLabel: 'Page suivante'}}
12+
/>
913
```

apps/next-docs/content/components/Pagination/index.mdx

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,24 +71,33 @@ render(<App />)
7171

7272
### Custom button labels
7373

74-
Use the `labels` prop to customize the visible text of the previous and next controls.
74+
Use the `labels` prop to customize the visible text of the previous and next controls. When localizing content, remember to also set the matching `prevAriaLabel` and `nextAriaLabel` labels so that the accessible names stay in sync with the visible text.
7575

7676
```jsx live
77-
<Pagination pageCount={3} currentPage={2} labels={{prev: 'Précédent', next: 'Suivant'}} />
77+
<Pagination
78+
pageCount={3}
79+
currentPage={2}
80+
labels={{
81+
prev: 'Précédent',
82+
next: 'Suivant',
83+
prevAriaLabel: 'Page précédente',
84+
nextAriaLabel: 'Page suivante',
85+
}}
86+
/>
7887
```
7988

8089
## Component props
8190

8291
### Pagination <Label>Required</Label>
8392

84-
| name | type | default | required | description |
85-
| ----------------------- | ---------------------------------------------------------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
86-
| `pageCount` | `number` | | `true` | The total number of pages |
87-
| `currentPage` | `number` | | `true` | The current page number |
88-
| `onPageChange` | `(e: React.MouseEvent, n: number) => void` | | `false` | Callback function for when the page changes |
89-
| `hrefBuilder` | `(n: number) => string` | | `false` | Function to build the href for each page |
90-
| `pageAttributesBuilder` | `(n: number, page: PaginationPageType) => {[attributeName: string]: string}` | | `false` | Forward custom attributes to pagination items. |
91-
| `marginPageCount` | `number` | | `false` | Defines how many pages are to be displayed on the left and right of the component. Will be reduced on narrow viewports. |
92-
| `showPages` | `boolean` | | `false` | Whether to show the page numbers |
93-
| `surroundingPageCount` | `number` | | `false` | The number of pages to show on each side of the current page. Will be hidden on narrow viewports. |
94-
| `labels` | `{prev?: string; next?: string}` | | `false` | Custom text labels for the previous and next controls. Provide either or both. |
93+
| name | type | default | required | description |
94+
| ----------------------- | -------------------------------------------------------------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
95+
| `pageCount` | `number` | | `true` | The total number of pages |
96+
| `currentPage` | `number` | | `true` | The current page number |
97+
| `onPageChange` | `(e: React.MouseEvent, n: number) => void` | | `false` | Callback function for when the page changes |
98+
| `hrefBuilder` | `(n: number) => string` | | `false` | Function to build the href for each page |
99+
| `pageAttributesBuilder` | `(n: number, page: PaginationPageType) => {[attributeName: string]: string}` | | `false` | Forward custom attributes to pagination items. |
100+
| `marginPageCount` | `number` | | `false` | Defines how many pages are to be displayed on the left and right of the component. Will be reduced on narrow viewports. |
101+
| `showPages` | `boolean` | | `false` | Whether to show the page numbers |
102+
| `surroundingPageCount` | `number` | | `false` | The number of pages to show on each side of the current page. Will be hidden on narrow viewports. |
103+
| `labels` | `{prev?: string; next?: string; prevAriaLabel?: string; nextAriaLabel?: string}` | | `false` | Custom text and accessible labels for the previous and next controls. Provide any subset. |

packages/react/src/Pagination/Pagination.features.stories.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ export const CustomLabels: Story = {
102102
labels: {
103103
prev: 'Précédent',
104104
next: 'Suivant',
105+
prevAriaLabel: 'Page précédente',
106+
nextAriaLabel: 'Page suivante',
105107
},
106108
},
107109
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@ describe('Pagination', () => {
6363
expect(getByText('Next')).toBeInTheDocument()
6464
})
6565

66+
it('falls back to the default labels when a key is explicitly set to undefined', () => {
67+
const {getByText} = render(<Pagination pageCount={5} currentPage={2} labels={{prev: undefined, next: undefined}} />)
68+
69+
expect(getByText('Previous')).toBeInTheDocument()
70+
expect(getByText('Next')).toBeInTheDocument()
71+
})
72+
73+
it('renders custom accessible labels for the previous and next controls', () => {
74+
const {getByRole, queryByRole} = render(
75+
<Pagination
76+
pageCount={5}
77+
currentPage={2}
78+
labels={{prevAriaLabel: 'Go to previous page', nextAriaLabel: 'Go to next page'}}
79+
/>,
80+
)
81+
82+
expect(getByRole('button', {name: 'Go to previous page'})).toBeInTheDocument()
83+
expect(getByRole('button', {name: 'Go to next page'})).toBeInTheDocument()
84+
expect(queryByRole('button', {name: 'Previous Page'})).not.toBeInTheDocument()
85+
expect(queryByRole('button', {name: 'Next Page'})).not.toBeInTheDocument()
86+
})
87+
6688
it('shows ellipsis just before the final item to reduce pagination links on longer lists, where the first item is selected', () => {
6789
const {getByRole} = render(<Pagination pageCount={10} currentPage={1} />)
6890
const rootEl = getByRole('navigation')

packages/react/src/Pagination/Pagination.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,17 @@ export type PaginationProps = {
3030
labels?: {
3131
prev?: string
3232
next?: string
33+
prevAriaLabel?: string
34+
nextAriaLabel?: string
3335
}
3436
'data-testid'?: string
3537
} & Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>
3638

3739
const defaultPaginationLabels = {
3840
prev: 'Previous',
3941
next: 'Next',
42+
prevAriaLabel: 'Previous Page',
43+
nextAriaLabel: 'Next Page',
4044
}
4145

4246
/**
@@ -67,7 +71,10 @@ export const Pagination = memo(
6771
surroundingPageCount = 0
6872
}
6973

70-
const {prev: prevLabel, next: nextLabel} = {...defaultPaginationLabels, ...labels}
74+
const prevLabel = labels?.prev ?? defaultPaginationLabels.prev
75+
const nextLabel = labels?.next ?? defaultPaginationLabels.next
76+
const prevAriaLabel = labels?.prevAriaLabel ?? defaultPaginationLabels.prevAriaLabel
77+
const nextAriaLabel = labels?.nextAriaLabel ?? defaultPaginationLabels.nextAriaLabel
7178

7279
const navRef = React.useRef<HTMLElement>(null)
7380

@@ -92,6 +99,8 @@ export const Pagination = memo(
9299
pageAttributesBuilder={pageAttributesBuilder}
93100
prevLabel={prevLabel}
94101
nextLabel={nextLabel}
102+
prevAriaLabel={prevAriaLabel}
103+
nextAriaLabel={nextAriaLabel}
95104
onClick={pageChange(page.num)}
96105
/>
97106
)
@@ -106,6 +115,8 @@ export const Pagination = memo(
106115
pageAttributesBuilder,
107116
prevLabel,
108117
nextLabel,
118+
prevAriaLabel,
119+
nextAriaLabel,
109120
pageChange,
110121
])
111122

@@ -130,6 +141,8 @@ type PaginationItemProps = {
130141
pageAttributesBuilder?: (n: number, page: PaginationPageType) => {[key: string]: string}
131142
prevLabel: string
132143
nextLabel: string
144+
prevAriaLabel: string
145+
nextAriaLabel: string
133146
onClick?: React.MouseEventHandler<HTMLAnchorElement>
134147
}
135148

@@ -139,6 +152,8 @@ const PaginationItem = ({
139152
pageAttributesBuilder,
140153
prevLabel,
141154
nextLabel,
155+
prevAriaLabel,
156+
nextAriaLabel,
142157
onClick,
143158
}: PaginationItemProps) => {
144159
const [isArrowExpanded, setIsArrowExpanded] = React.useState(false)
@@ -162,7 +177,7 @@ const PaginationItem = ({
162177
rel="prev"
163178
href={page.disabled ? undefined : hrefBuilder(page.num)}
164179
aria-disabled={page.disabled || undefined}
165-
aria-label="Previous Page"
180+
aria-label={prevAriaLabel}
166181
{...customAttributes}
167182
className={clsx(styles.Pagination__controlItem, customClassName)}
168183
onMouseEnter={() => setIsArrowExpanded(true)}
@@ -197,7 +212,7 @@ const PaginationItem = ({
197212
rel="next"
198213
href={page.disabled ? undefined : hrefBuilder(page.num)}
199214
aria-disabled={page.disabled || undefined}
200-
aria-label="Next Page"
215+
aria-label={nextAriaLabel}
201216
{...customAttributes}
202217
className={clsx(styles.Pagination__controlItem, customClassName)}
203218
onMouseEnter={() => setIsArrowExpanded(true)}

0 commit comments

Comments
 (0)