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

feat: Added prop for hidden heading to allow screen-readers to navigate to pagination #2976

Merged
merged 7 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
5 changes: 5 additions & 0 deletions .changeset/spicy-pens-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@navikt/ds-react": minor
---

Pagination: Added prop for hidden heading.
5 changes: 5 additions & 0 deletions @navikt/core/css/pagination.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
.navds-pagination {
position: relative;
}

.navds-pagination__list {
margin: 0;
padding: 0;
list-style: none;
display: flex;
gap: var(--a-spacing-3);
position: relative;
}

.navds-pagination__ellipsis {
Expand Down
27 changes: 26 additions & 1 deletion @navikt/core/react/src/pagination/Pagination.tsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has Morten tested it?

Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import cl from "clsx";
import React, { forwardRef } from "react";
import { ChevronLeftIcon, ChevronRightIcon } from "@navikt/aksel-icons";
import { BodyShort } from "../typography";
import { BodyShort, Heading } from "../typography";
import { useId } from "../util";
import PaginationItem, {
PaginationItemProps,
PaginationItemType,
Expand Down Expand Up @@ -47,6 +48,13 @@ export interface PaginationProps extends React.HTMLAttributes<HTMLElement> {
* @default (item: PaginationItemProps) => <PaginationItem {...item} />
*/
renderItem?: (item: PaginationItemProps) => ReturnType<React.FC>;
/**
* Pagination heading. We recommend adding heading instead of `aria-label` to help assistive technologies with an extra navigation-stop.
*/
srHeading?: {
tag: "h2" | "h3" | "h4" | "h5" | "h6";
text: string;
};
}

interface PaginationType
Expand Down Expand Up @@ -117,13 +125,17 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
className,
size = "medium",
prevNextTexts = false,
srHeading,
"aria-describedby": ariaDescribedBy,
renderItem: Item = (item: PaginationItemProps) => (
<PaginationItem {...item} />
),
...rest
},
ref,
) => {
const headingId = useId();

if (page < 1) {
console.error("page cannot be less than 1");
return null;
Expand All @@ -145,12 +157,25 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
<nav
ref={ref}
{...rest}
aria-describedby={
srHeading ? cl(headingId, ariaDescribedBy) : ariaDescribedBy
}
className={cl(
"navds-pagination",
`navds-pagination--${size}`,
className,
)}
>
{srHeading && (
<Heading
size="xsmall"
visuallyHidden
as={srHeading.tag}
id={headingId}
>
{srHeading.text}
</Heading>
)}
<ul className="navds-pagination__list">
<li>
<Item
Expand Down
4 changes: 4 additions & 0 deletions @navikt/core/react/src/pagination/PaginationItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const Item: PaginationItemType = forwardRef(
as: Component = "button",
selected = false,
className,
page,
...rest
},
ref,
Expand All @@ -46,6 +47,9 @@ export const Item: PaginationItemType = forwardRef(
className={cl("navds-pagination__item", className, {
"navds-pagination__item--selected": selected,
})}
data-page={page}
/* TODO: Breaking change to remove. Add to future major version. */
page={page}
{...(Component === "button" && { type: "button" })}
{...rest}
>
Expand Down
149 changes: 116 additions & 33 deletions @navikt/core/react/src/pagination/pagination.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Meta, StoryFn, StoryObj } from "@storybook/react";
import React, { useState } from "react";
import { Link, HashRouter as Router } from "react-router-dom";
import { VStack } from "../layout/stack";
import Pagination from "./Pagination";

export default {
const meta: Meta<typeof Pagination> = {
title: "ds-react/Pagination",
component: Pagination,
argTypes: {
Expand All @@ -13,12 +15,21 @@ export default {
},
},
},
parameters: {
chromatic: { disable: true },
},
};

export const Default = (props: any) => {
export default meta;

type Story = StoryObj<typeof Pagination>;
type StoryFunction = StoryFn<typeof Pagination>;

export const Default: StoryFunction = (props) => {
const [page, setPage] = useState(props.page);
return <Pagination {...props} page={page} onPageChange={setPage} />;
};

Default.args = {
page: 2,
count: 8,
Expand All @@ -27,14 +38,8 @@ Default.args = {
prevNextTexts: false,
};

export const PrevNextText = () => {
const [page, setPage] = useState(1);
const props = {
page: 1,
count: 8,
siblingCount: 1,
boundaryCount: 1,
};
export const PrevNextText: StoryFunction = (props) => {
const [page, setPage] = useState(2);
return (
<div className="colgap" style={{ alignItems: "center" }}>
<Pagination {...props} page={page} onPageChange={setPage} prevNextTexts />
Expand All @@ -56,40 +61,68 @@ export const PrevNextText = () => {
);
};

export const Small = () => {
const [page, setPage] = useState(1);
const props = {
page: 1,
count: 8,
siblingCount: 1,
boundaryCount: 1,
};
PrevNextText.args = {
page: 2,
count: 8,
siblingCount: 1,
boundaryCount: 1,
};

export const Small: StoryFunction = (props) => {
const [page, setPage] = useState(2);

return (
<Pagination {...props} page={page} onPageChange={setPage} size="small" />
);
};

export const XSmall = () => {
const [page, setPage] = useState(1);
const props = {
page: 1,
count: 8,
siblingCount: 1,
boundaryCount: 1,
};
Small.args = {
page: 1,
count: 8,
siblingCount: 1,
boundaryCount: 1,
};
export const XSmall: StoryFunction = (props) => {
const [page, setPage] = useState(2);

return (
<Pagination {...props} page={page} onPageChange={setPage} size="xsmall" />
);
};

export const AsLink = () => {
XSmall.args = {
page: 1,
count: 8,
siblingCount: 1,
boundaryCount: 1,
};

export const Heading: StoryFunction = (props) => {
const [page, setPage] = useState(2);

return (
<>
<h2>Heading før pagination</h2>
<Pagination
{...props}
page={page}
onPageChange={setPage}
srHeading={{ tag: "h2", text: "Dette er en pagination heading" }}
/>
<h2>Heading etter pagination</h2>
</>
);
};

Heading.args = {
page: 1,
count: 8,
siblingCount: 1,
boundaryCount: 1,
};

export const AsLink: StoryFunction = (props) => {
const [page, setPage] = useState(1);
const props = {
page: 1,
count: 8,
siblingCount: 1,
boundaryCount: 1,
};
return (
<Pagination
{...props}
Expand All @@ -101,10 +134,60 @@ export const AsLink = () => {
/>
);
};

AsLink.args = {
page: 1,
count: 8,
siblingCount: 1,
boundaryCount: 1,
};

AsLink.decorators = [
(Story) => (
<Router>
<Story />
</Router>
),
];

export const Chromatic: Story = {
render: (props) => (
<VStack gap="8">
<div>
<h2>Default</h2>
<Default {...props} />
</div>
<div>
<h2>PrevNextText</h2>
<PrevNextText {...props} />
</div>
<div>
<h2>Small</h2>
<Small {...props} />
</div>
<div>
<h2>XSmall</h2>
<XSmall {...props} />
</div>
<div>
<h2>Heading</h2>
<Heading {...props} />
</div>
<div>
<h2>AsLink</h2>
<AsLink {...props} />
</div>
</VStack>
),
parameters: {
chromatic: { disable: false },
},
decorators: [
(Story) => (
<Router>
<Story />
</Router>
),
],
args: { page: 2, count: 8, siblingCount: 1, boundaryCount: 1 },
};
35 changes: 35 additions & 0 deletions aksel.nav.no/website/pages/eksempler/pagination/labeling.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useState } from "react";
import { Pagination } from "@navikt/ds-react";
import { withDsExample } from "@/web/examples/withDsExample";

const Example = () => {
const [pageState, setPageState] = useState(2);
return (
<div>
<Pagination
page={pageState}
onPageChange={setPageState}
count={9}
boundaryCount={1}
siblingCount={1}
srHeading={{
tag: "h2",
text: "Tabellpaginering",
}}
/>
</div>
);
};

// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
export default withDsExample(Example);

/* Storybook story */
export const Demo = {
render: Example,
};

export const args = {
index: 2,
desc: "For å gjøre det lettere for skjermlesere å navigere til Pagination, anbefaler vi å bruke `srHeading`-prop for å legge til en skjult heading. Husk å bruke riktig h-tag.",
};
Loading