Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Pagination): api alignment #2460

Merged
merged 19 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions apps/theme/components/Previews/Components/Components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,34 @@ export const Components = () => {
</Table.Row>
</Table.Body>
</Table>
<Pagination
currentPage={3}
nextLabel='Neste'
onChange={function Ya() {}}
previousLabel='Forrige'
size='sm'
totalPages={6}
/>
<Pagination>
<Pagination.List>
<Pagination.Item>
<Pagination.Button>Forrige</Pagination.Button>
</Pagination.Item>
<Pagination.Item>
<Pagination.Button aria-current='page'>1</Pagination.Button>
</Pagination.Item>
<Pagination.Item>
<Pagination.Button>2</Pagination.Button>
</Pagination.Item>
<Pagination.Item>
<Pagination.Button>3</Pagination.Button>
</Pagination.Item>
<Pagination.Item>
<Pagination.Button />
</Pagination.Item>
<Pagination.Item>
<Pagination.Button>6</Pagination.Button>
</Pagination.Item>
<Pagination.Item>
<Pagination.Button>7</Pagination.Button>
</Pagination.Item>
<Pagination.Item>
<Pagination.Button>Neste</Pagination.Button>
</Pagination.Item>
</Pagination.List>
</Pagination>
</div>
<div className={cl(classes.card, classes.help)}>
<Heading size='xs' className={classes.helpHeading}>
Expand Down
57 changes: 33 additions & 24 deletions packages/css/pagination.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,42 @@
--dsc-pagination-ellipsis-width: var(--ds-sizing-12);
--dsc-pagination-chevron-size: var(--ds-sizing-6);

display: flex;
flex-wrap: wrap;
gap: var(--dsc-pagination-gap);
list-style: none;
margin: 0;
padding: 0;

/* Style ellipsis When not containing interactive element */
& > li:not(:has(a, button)) {
align-items: center;
& > :is(ol, ul) {
display: flex;
justify-content: center;
min-width: var(--dsc-pagination-ellipsis-width);
}
flex-wrap: wrap;
gap: var(--dsc-pagination-gap);
list-style: none;
margin: 0;
padding: 0;

& > li:first-child > ::before,
& > li:last-child > ::after {
background: currentcolor;
content: '';
height: var(--dsc-pagination-chevron-size);
mask: center/contain no-repeat
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'%3E%3Cpath d='M9.47 5.97a.75.75 0 0 1 1.06 0l5.5 5.5a.75.75 0 0 1 0 1.06l-5.5 5.5a.75.75 0 1 1-1.06-1.06L14.44 12 9.47 7.03a.75.75 0 0 1 0-1.06'/%3E%3C/svg%3E");
width: var(--dsc-pagination-chevron-size);
}
& > li:first-child > ::before,
& > li:last-child > ::after {
content: '';
background: currentcolor;
height: var(--dsc-pagination-chevron-size);
mask: center/contain no-repeat
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'%3E%3Cpath d='M9.47 5.97a.75.75 0 0 1 1.06 0l5.5 5.5a.75.75 0 0 1 0 1.06l-5.5 5.5a.75.75 0 1 1-1.06-1.06L14.44 12 9.47 7.03a.75.75 0 0 1 0-1.06'/%3E%3C/svg%3E");
width: var(--dsc-pagination-chevron-size);
}

& > li:first-child > ::before {
rotate: 180deg;
}

/* Makes page buttons square */
& > li:not(:first-child, :last-child) > :is(a, button) {
--dsc-button-padding-inline: var(--dsc-button-padding-block);
}

/* Style as non-interactive ellipsis when empty */
& > li > [aria-hidden='true'] {
color: inherit;
pointer-events: none;

& > li:first-child > ::before {
rotate: 180deg;
&::before {
content: '\2026'; /* ellipsis */
}
}
}

&[data-size='sm'] {
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
data-variant={variant}
ref={ref}
/* don't set type when we use `asChild` */
{...(asChild ? { asChild: true } : { type: 'button' })}
type={asChild ? undefined : 'button'}
/* if consumers set type, our default does not set anything, as `rest` contains this */
{...rest}
>
Expand Down
58 changes: 26 additions & 32 deletions packages/react/src/components/Pagination/Pagination.mdx
eirikbacker marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -23,49 +23,40 @@ Vær oppmerksom på:

### Eksempel på bruk med `Pagination`

`Pagination` er en kontrollert komponent. Det vil si at komponentens tilstand om hvilken side som er aktiv styres utenfra. Når komponenten er på første eller siste side, skjules "forrige"/"neste"-pilene.
`Pagination` er en kontrollert komponent. Det vil si at komponentens tilstand om hvilken side som er aktiv styres utenfra.

```tsx
import { Pagination } from '@digdir/designsystemet-react';

const [currentPage, setCurrentPage] = useState(1);
const [currentPage, setCurrentPage] = useState(4);
const { pages, onChange, prevButtonProps, nextButtonProps, hasNext, hasPrevious } = usePagination({
currentPage,
setCurrentPage,
totalPages: 10,
showPages: 7,
});

<Pagination
currentPage={1}
totalPages={10}
onChange={setCurrentPage}
nextLabel='Neste'
previousLabel='Forrige'
itemLabel={(num) => `Side ${num}`}
/>;
```

### Eksempel på bruk med del-komponenter

Er det behov for en tilpasset `Pagination` kan du ta i bruk de enkelte del-kompontenene.

```tsx
<Pagination.Root>
<Pagination>
<Pagination.List>
<Pagination.Item>
<Pagination.Previous>
<ChevronLeftIcon aria-hidden />
<Pagination.Button aria-label='Forrige side' {...prevButtonProps}>
Forrige
</Pagination.Previous>
</Pagination.Button>
</Pagination.Item>

{pages.map(({ page, itemKey, buttonProps }) => (
eirikbacker marked this conversation as resolved.
Show resolved Hide resolved
<Pagination.Item key={itemKey}>
<Pagination.Button {...buttonProps} aria-label={`Side ${page}`}>
{page}
</Pagination.Button>
</Pagination.Item>
))}
<Pagination.Item>
<Pagination.Button isActive>1</Pagination.Button>
</Pagination.Item>

<Pagination.Item>
<Pagination.Next>
<Pagination.Button aria-label='Neste side' {...nextButtonProps}>
Neste
<ChevronRightIcon aria-hidden />
</Pagination.Next>
</Pagination.Button>
</Pagination.Item>
</Pagination.List>
</Pagination.Root>
</Pagination>
```

## Paginering logikk
Expand All @@ -74,9 +65,12 @@ Bruk `usePagination` for paginering logikk sammen med de enkelt del-komponentene

`Pagination` er bygget på `usePagination`.

Bruk `isActive` på `Pagination.Button` for å indikere hvilken side som er aktiv.
Bruk `...prevButtonProps` og `...nextButtonProps` for å få funsjonalitet på forrige- og neste-knappene.
I sidelisten, bruk `key={itemKey}` på `Pagination.Item` og `...buttonProps` på `Pagination.Button`.

Bruk `hasNext` og `hasPrev` dersom du trenger å sjekke om det er flere sider å gå til.

<Canvas of={PaginationStories.UsePagination} />
<Canvas of={PaginationStories.Preview} />

### Pagenering med lenker

Expand Down
149 changes: 61 additions & 88 deletions packages/react/src/components/Pagination/Pagination.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,127 +1,100 @@
import { useArgs } from '@storybook/preview-api';
import type { Meta, StoryFn } from '@storybook/react';
import { useEffect, useState } from 'react';
import { useState } from 'react';

import { Pagination, usePagination } from '.';
import { Pagination, type UsePaginationProps, usePagination } from '.';

export default {
title: 'Komponenter/Pagination',
component: Pagination,
} as Meta;

export const Preview: StoryFn<typeof Pagination> = (args) => {
const [currentPage, setCurrentPage] = useState(args.currentPage);

useEffect(() => {
setCurrentPage(args.currentPage);
}, [args.currentPage]);

return (
<Pagination {...args} onChange={setCurrentPage} currentPage={currentPage} />
);
};
Preview.args = {
'aria-label': 'Sidenavigering',
size: 'md',
nextLabel: 'Neste',
previousLabel: 'Forrige',
totalPages: 10,
hideLabels: false,
compact: false,
currentPage: 1,
itemLabel: (num) => `Side ${num}`,
};

export const UsePagination: StoryFn<typeof Pagination> = (args) => {
const { totalPages = 10 } = args;
const {
pages,
currentPage,
setCurrentPage,
nextPage,
previousPage,
showNextPage,
showPreviousPage,
} = usePagination({
currentPage: 4,
totalPages,
export const Preview: StoryFn<UsePaginationProps> = (args) => {
eirikbacker marked this conversation as resolved.
Show resolved Hide resolved
const [, updateArgs] = useArgs();
const { pages, nextButtonProps, prevButtonProps } = usePagination({
...args,
setCurrentPage: (currentPage) => updateArgs({ currentPage }),
});

return (
<Pagination.Root aria-label='Sidenavigering'>
<Pagination aria-label='Sidenavigering'>
<Pagination.List>
<Pagination.Item>
<Pagination.Previous
onClick={previousPage}
disabled={!showPreviousPage}
>
<Pagination.Button aria-label='Forrige side' {...prevButtonProps}>
Forrige
</Pagination.Previous>
</Pagination.Button>
</Pagination.Item>

{pages.map((page, index) => (
<Pagination.Item key={`${page}-${index}`}>
{page === 'ellipsis' ? (
<Pagination.Ellipsis />
) : (
<Pagination.Button
isActive={currentPage === page}
onClick={() => setCurrentPage(page)}
aria-label={`Side ${page}`}
>
{page}
</Pagination.Button>
)}
{pages.map(({ page, itemKey, buttonProps }) => (
<Pagination.Item key={itemKey}>
<Pagination.Button {...buttonProps} aria-label={`Side ${page}`}>
{page}
</Pagination.Button>
</Pagination.Item>
))}

<Pagination.Item>
<Pagination.Next onClick={nextPage} disabled={!showNextPage}>
<Pagination.Button aria-label='Neste side' {...nextButtonProps}>
Neste
</Pagination.Next>
</Pagination.Button>
</Pagination.Item>
</Pagination.List>
</Pagination.Root>
</Pagination>
);
};

export const WithAnchor: StoryFn<typeof Pagination> = (args) => {
const { totalPages = 4 } = args;
const { pages, currentPage } = usePagination({
currentPage: 2,
totalPages,
Preview.args = {
currentPage: 4,
onChange: console.log, // Open console to see this event
totalPages: 10,
showPages: 7,
};

export const WithAnchor: StoryFn<UsePaginationProps> = (args) => {
const [, updateArgs] = useArgs();
const { pages, nextButtonProps, prevButtonProps } = usePagination({
...args,
setCurrentPage: (currentPage) => updateArgs({ currentPage }),
});

return (
<Pagination.Root aria-label='Sidenavigering'>
<Pagination aria-label='Sidenavigering'>
<Pagination.List>
<Pagination.Item>
<Pagination.Previous asChild aria-label='Naviger til forrige side'>
<Pagination.Button
asChild
aria-label='Forrige side'
{...prevButtonProps}
>
<a href='#forrige-side'>Forrige</a>
</Pagination.Previous>
</Pagination.Button>
</Pagination.Item>

{pages.map((page, index) => (
<Pagination.Item key={`${page}-${index}`}>
{page === 'ellipsis' ? (
<Pagination.Ellipsis />
) : (
<Pagination.Button
asChild
isActive={currentPage === page}
aria-label={`Naviger til side ${page}`}
>
<a href={`#side-${page}`}> {page}</a>
</Pagination.Button>
)}
{pages.map(({ page, itemKey, buttonProps }) => (
<Pagination.Item key={itemKey}>
<Pagination.Button
asChild
aria-label={`Side ${page}`}
{...buttonProps}
>
<a href={`#side-${page}`}>{page}</a>
</Pagination.Button>
</Pagination.Item>
))}

<Pagination.Item>
<Pagination.Next asChild aria-label='Naviger til neste side'>
<Pagination.Button
asChild
aria-label='Neste side'
{...nextButtonProps}
>
<a href='#neste-side'>Neste</a>
</Pagination.Next>
</Pagination.Button>
</Pagination.Item>
</Pagination.List>
</Pagination.Root>
</Pagination>
);
};

WithAnchor.args = {
currentPage: 2,
onChange: console.log, // Open console to see this event
totalPages: 10,
showPages: 7,
};
Loading
Loading