@purpurds/pagination 5.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/LICENSE.txt +73 -0
  2. package/dist/navigation.d.ts +2 -0
  3. package/dist/navigation.d.ts.map +1 -0
  4. package/dist/pagination-page-selector.d.ts +17 -0
  5. package/dist/pagination-page-selector.d.ts.map +1 -0
  6. package/dist/pagination-page-size-selector.d.ts +13 -0
  7. package/dist/pagination-page-size-selector.d.ts.map +1 -0
  8. package/dist/pagination-page-trigger.d.ts +14 -0
  9. package/dist/pagination-page-trigger.d.ts.map +1 -0
  10. package/dist/pagination-pages.d.ts +13 -0
  11. package/dist/pagination-pages.d.ts.map +1 -0
  12. package/dist/pagination-step-trigger.d.ts +14 -0
  13. package/dist/pagination-step-trigger.d.ts.map +1 -0
  14. package/dist/pagination-truncation-separator.d.ts +11 -0
  15. package/dist/pagination-truncation-separator.d.ts.map +1 -0
  16. package/dist/pagination.cjs.js +134 -0
  17. package/dist/pagination.cjs.js.map +1 -0
  18. package/dist/pagination.d.ts +24 -0
  19. package/dist/pagination.d.ts.map +1 -0
  20. package/dist/pagination.es.js +4041 -0
  21. package/dist/pagination.es.js.map +1 -0
  22. package/dist/styles.css +1 -0
  23. package/dist/types.d.ts +35 -0
  24. package/dist/types.d.ts.map +1 -0
  25. package/dist/use-page-options.hook.d.ts +8 -0
  26. package/dist/use-page-options.hook.d.ts.map +1 -0
  27. package/dist/use-page-size-options.hook.d.ts +8 -0
  28. package/dist/use-page-size-options.hook.d.ts.map +1 -0
  29. package/dist/use-pagination-page-selector-events.hook.d.ts +10 -0
  30. package/dist/use-pagination-page-selector-events.hook.d.ts.map +1 -0
  31. package/dist/use-pagination-pages.hook.d.ts +7 -0
  32. package/dist/use-pagination-pages.hook.d.ts.map +1 -0
  33. package/package.json +69 -0
  34. package/src/global.d.ts +4 -0
  35. package/src/navigation.ts +3 -0
  36. package/src/pagination-page-selector.module.scss +26 -0
  37. package/src/pagination-page-selector.test.tsx +78 -0
  38. package/src/pagination-page-selector.tsx +105 -0
  39. package/src/pagination-page-size-selector.module.scss +34 -0
  40. package/src/pagination-page-size-selector.test.tsx +73 -0
  41. package/src/pagination-page-size-selector.tsx +78 -0
  42. package/src/pagination-page-trigger.module.scss +41 -0
  43. package/src/pagination-page-trigger.test.tsx +172 -0
  44. package/src/pagination-page-trigger.tsx +93 -0
  45. package/src/pagination-pages.module.scss +8 -0
  46. package/src/pagination-pages.test.tsx +95 -0
  47. package/src/pagination-pages.tsx +72 -0
  48. package/src/pagination-step-trigger.module.scss +27 -0
  49. package/src/pagination-step-trigger.test.tsx +106 -0
  50. package/src/pagination-step-trigger.tsx +92 -0
  51. package/src/pagination-truncation-separator.module.scss +8 -0
  52. package/src/pagination-truncation-separator.test.tsx +25 -0
  53. package/src/pagination-truncation-separator.tsx +29 -0
  54. package/src/pagination.module.scss +105 -0
  55. package/src/pagination.stories.tsx +197 -0
  56. package/src/pagination.test.tsx +76 -0
  57. package/src/pagination.tsx +177 -0
  58. package/src/types.ts +43 -0
  59. package/src/use-page-options.hook.ts +21 -0
  60. package/src/use-page-size-options.hook.ts +21 -0
  61. package/src/use-pagination-page-selector-events.hook.ts +59 -0
  62. package/src/use-pagination-pages.hook.ts +41 -0
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import * as matchers from "@testing-library/jest-dom/matchers";
3
+ import { cleanup, render, screen } from "@testing-library/react";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+
6
+ import { PaginationTruncationSeparator } from "./pagination-truncation-separator";
7
+
8
+ expect.extend(matchers);
9
+
10
+ afterEach(() => {
11
+ vi.clearAllMocks();
12
+ cleanup();
13
+ });
14
+
15
+ describe("PaginationTruncationSeparator", () => {
16
+ it("renders without crashing", () => {
17
+ render(<PaginationTruncationSeparator />);
18
+ expect(screen.getByTestId(Selectors.PAGINATION_TRUNCATION_SEPARATOR)).toBeInTheDocument();
19
+ expect(screen.getByTestId(Selectors.PAGINATION_TRUNCATION_SEPARATOR)).toHaveTextContent("...");
20
+ });
21
+ });
22
+
23
+ const Selectors = {
24
+ PAGINATION_TRUNCATION_SEPARATOR: "purpur-pagination-truncation-separator",
25
+ };
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { Paragraph } from "@purpurds/paragraph";
3
+ import c from "classnames/bind";
4
+
5
+ import styles from "./pagination-truncation-separator.module.scss";
6
+ const cx = c.bind(styles);
7
+
8
+ export type PaginationTruncationSeparatorProps = {
9
+ ["data-testid"]?: string;
10
+ className?: string;
11
+ };
12
+
13
+ const rootClassName = "purpur-pagination-truncation-separator";
14
+
15
+ export const PaginationTruncationSeparator = ({
16
+ ["data-testid"]: dataTestId = "purpur-pagination-truncation-separator",
17
+ className,
18
+ ...props
19
+ }: PaginationTruncationSeparatorProps) => {
20
+ const classes = cx([className, rootClassName]);
21
+
22
+ return (
23
+ <li className={classes} data-testid={dataTestId} {...props}>
24
+ <Paragraph>...</Paragraph>
25
+ </li>
26
+ );
27
+ };
28
+
29
+ PaginationTruncationSeparator.displayName = "PaginationTruncationSeparator";
@@ -0,0 +1,105 @@
1
+ @import "@purpurds/tokens/breakpoint/variables";
2
+
3
+ .purpur-pagination {
4
+ $root: &;
5
+ align-items: center;
6
+ display: flex;
7
+ flex-direction: column-reverse;
8
+ gap: var(--purpur-spacing-300);
9
+
10
+ @media screen and (min-width: $purpur-breakpoint-lg) {
11
+ align-items: center;
12
+ display: grid;
13
+ gap: var(--purpur-spacing-100);
14
+ grid-template-areas: "pagesize pagination empty";
15
+ grid-template-columns: 1fr 3fr 1fr;
16
+ }
17
+
18
+ &__page-size-select-container {
19
+ flex: 0 0 auto;
20
+ width: 100%;
21
+
22
+ @media screen and (min-width: $purpur-breakpoint-lg) {
23
+ grid-area: pagesize;
24
+ width: auto;
25
+ }
26
+ }
27
+
28
+ &__pagination-container {
29
+ align-items: center;
30
+ display: inline-flex;
31
+ gap: var(--purpur-spacing-200);
32
+ margin: 0 auto;
33
+ max-width: calc(3 * var(--purpur-spacing-1200));
34
+ width: 100%;
35
+
36
+ @media screen and (min-width: $purpur-breakpoint-md) {
37
+ gap: var(--purpur-spacing-400);
38
+ max-width: none;
39
+ width: auto;
40
+ }
41
+
42
+ @media screen and (min-width: $purpur-breakpoint-lg) {
43
+ grid-area: pagination;
44
+ justify-self: center;
45
+ }
46
+ }
47
+
48
+ &__step-trigger-label {
49
+ border: 0;
50
+ clip: rect(0, 0, 0, 0);
51
+ display: block;
52
+ height: 1px;
53
+ margin: -1px;
54
+ overflow: hidden;
55
+ padding: 0;
56
+ position: absolute;
57
+ white-space: nowrap;
58
+ width: 1px;
59
+
60
+ @media screen and (min-width: $purpur-breakpoint-lg) {
61
+ clip: auto;
62
+ display: inline;
63
+ height: auto;
64
+ margin: 0;
65
+ overflow: visible;
66
+ position: static;
67
+ white-space: normal;
68
+ width: auto;
69
+ }
70
+ }
71
+
72
+ &__step-trigger {
73
+ flex: 0 0 auto;
74
+ }
75
+
76
+ &__page-trigger-container {
77
+ display: flex;
78
+ flex: 1 1 auto;
79
+ justify-content: center;
80
+ }
81
+
82
+ #{$root}__pages {
83
+ display: none;
84
+
85
+ &--visible {
86
+ display: flex;
87
+ }
88
+
89
+ @media screen and (min-width: $purpur-breakpoint-md) {
90
+ display: flex;
91
+ }
92
+ }
93
+
94
+ #{$root}__page-selector {
95
+ display: none;
96
+
97
+ &--visible {
98
+ display: flex;
99
+ }
100
+
101
+ @media screen and (min-width: $purpur-breakpoint-md) {
102
+ display: none;
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,197 @@
1
+ import React, { forwardRef } from "react";
2
+ import { Skeleton } from "@purpurds/skeleton";
3
+ import { useArgs } from "@storybook/preview-api";
4
+ import type { Meta, StoryObj } from "@storybook/react";
5
+
6
+ import "@purpurds/autocomplete/styles";
7
+ import "@purpurds/icon/styles";
8
+ import "@purpurds/label/styles";
9
+ import "@purpurds/listbox/styles";
10
+ import "@purpurds/paragraph/styles";
11
+ import "@purpurds/select/styles";
12
+ import "@purpurds/skeleton/styles";
13
+ import "@purpurds/text-field/styles";
14
+ import { Pagination, PaginationProps } from "./pagination";
15
+ import { PaginationInformation } from "./types";
16
+
17
+ type CustomTestLinkProps = {
18
+ children?: React.ReactNode;
19
+ href?: string;
20
+ target?: string;
21
+ };
22
+
23
+ const CustomTestLink = forwardRef(({ children, href, target, ...props }: CustomTestLinkProps) => {
24
+ return (
25
+ <a href={href} target={target} {...props}>
26
+ {children}
27
+ </a>
28
+ );
29
+ });
30
+
31
+ const meta = {
32
+ title: "Components/Pagination",
33
+ component: Pagination,
34
+ argTypes: {
35
+ asLink: {
36
+ table: {
37
+ type: { summary: "Whether or not the pages should be rendered as links or buttons" },
38
+ },
39
+ },
40
+ hrefGetter: {
41
+ table: {
42
+ type: {
43
+ summary: `hrefGetter: (hrefInformation: HrefInformation) => string;
44
+ - Function that returns a url for a step when asLink is true.
45
+ `,
46
+ },
47
+ },
48
+ },
49
+ navigationFunction: {
50
+ table: {
51
+ type: {
52
+ summary: `(navigationInformation: NavigationInformation) => void
53
+ - Optional function to be used when navigating from javascript.
54
+ Defaults to a function that changes window.location.href.
55
+ Will only be called in link mode from the page select dropdown or the page size select.`,
56
+ },
57
+ },
58
+ },
59
+ anchorTagElement: {
60
+ table: {
61
+ type: {
62
+ summary:
63
+ "A React.ForwardRefExoticComponent<React.AnchorHTMLAttributes<HTMLAnchorElement>> that will be used as the link if asLink is true.",
64
+ },
65
+ },
66
+ },
67
+ onPageChange: {
68
+ table: {
69
+ type: {
70
+ summary: `onPageChange: (paginationChangeInformation: PaginationInformation) => void
71
+ - Function that will be called when the page changes. Mandatory for keeping up with the currentPage in button mode.
72
+ Optional when asLink is true, can be used to run some code on page change.`,
73
+ },
74
+ },
75
+ },
76
+ },
77
+ parameters: {
78
+ design: [
79
+ {
80
+ name: "Pagination",
81
+ type: "figma",
82
+ url: "https://www.figma.com/design/XEaIIFskrrxIBHMZDkIuIg/Purpur-DS---Component-library-%26-guidelines?node-id=33818-15953",
83
+ },
84
+ ],
85
+ },
86
+ } satisfies Meta<typeof Pagination>;
87
+
88
+ export default meta;
89
+ type Story = StoryObj<typeof Pagination>;
90
+
91
+ export const Showcase: Story = {
92
+ args: {
93
+ className: "myClassName",
94
+ currentPage: 1,
95
+ pageSize: 10,
96
+ pageSizeLabel: "Items per page",
97
+ nextButtonText: "Next",
98
+ outOfLabel: "of",
99
+ previousButtonText: "Previous",
100
+ pageSelectorAutocompleteId: "pageSelectorAutocompleteId",
101
+ pageSelectorListBoxLabel: "Select a page",
102
+ pageSelectorNoOptionsText: "Page does not exist",
103
+ totalItems: 145,
104
+ },
105
+ render: ({ ...args }) => {
106
+ const [{ currentPage }, updateArgs]: [
107
+ PaginationProps,
108
+ (newArgs: Partial<PaginationProps>) => void,
109
+ (argNames?: (keyof PaginationProps)[] | undefined) => void
110
+ ] = useArgs(); // eslint-disable-line react-hooks/rules-of-hooks
111
+ const onPageChange = ({ currentPage }: PaginationInformation) => {
112
+ updateArgs({ currentPage });
113
+ };
114
+
115
+ return (
116
+ <div>
117
+ <Skeleton style={{ height: "25rem", width: "100%", marginBottom: "2rem" }} />
118
+ <Pagination {...args} currentPage={currentPage} onPageChange={onPageChange} />
119
+ </div>
120
+ );
121
+ },
122
+ };
123
+
124
+ export const AsLinks: Story = {
125
+ args: {
126
+ asLink: true,
127
+ className: "myClassName",
128
+ currentPage: 1,
129
+ pageSize: 10,
130
+ pageSizeLabel: "Items per page",
131
+ hrefGetter: ({ page, pageSize }) =>
132
+ `?path=/story/components-pagination--as-links&args=currentPage:${page};pageSize:${pageSize}`,
133
+ nextButtonText: "Next",
134
+ outOfLabel: "of",
135
+ previousButtonText: "Previous",
136
+ pageSelectorListBoxLabel: "Select a page",
137
+ pageSelectorNoOptionsText: "Page does not exist",
138
+ totalItems: 145,
139
+ },
140
+ render: ({ ...args }) => {
141
+ const [{ currentPage }, updateArgs]: [
142
+ PaginationProps,
143
+ (newArgs: Partial<PaginationProps>) => void,
144
+ (argNames?: (keyof PaginationProps)[] | undefined) => void
145
+ ] = useArgs(); // eslint-disable-line react-hooks/rules-of-hooks
146
+ const onPageChange = ({ currentPage }: PaginationInformation) => {
147
+ updateArgs({ currentPage });
148
+ };
149
+
150
+ return (
151
+ <div>
152
+ <Skeleton style={{ height: "25rem", width: "100%", marginBottom: "2rem" }} />
153
+ <Pagination {...args} currentPage={currentPage} onPageChange={onPageChange} />
154
+ </div>
155
+ );
156
+ },
157
+ };
158
+
159
+ export const AsCustomLinks: Story = {
160
+ args: {
161
+ anchorTagElement: CustomTestLink,
162
+ asLink: true,
163
+ className: "myClassName",
164
+ currentPage: 5,
165
+ hrefGetter: ({ page, pageSize }) =>
166
+ `?path=/story/components-pagination--as-custom-links&args=currentPage:${page};pageSize:${pageSize}`,
167
+ navigationFunction: function ({ currentPage, pageSize, url }) {
168
+ // eslint-disable-next-line no-console
169
+ console.log(currentPage, pageSize, url);
170
+ },
171
+ nextButtonText: "Next",
172
+ outOfLabel: "of",
173
+ previousButtonText: "Previous",
174
+ pageSize: 10,
175
+ pageSizeLabel: "Items per page",
176
+ pageSelectorListBoxLabel: "Select a page",
177
+ pageSelectorNoOptionsText: "Page does not exist",
178
+ totalItems: 145,
179
+ },
180
+ render: ({ ...args }) => {
181
+ const [{ currentPage }, updateArgs]: [
182
+ PaginationProps,
183
+ (newArgs: Partial<PaginationProps>) => void,
184
+ (argNames?: (keyof PaginationProps)[] | undefined) => void
185
+ ] = useArgs(); // eslint-disable-line react-hooks/rules-of-hooks
186
+ const onPageChange = ({ currentPage }: PaginationInformation) => {
187
+ updateArgs({ currentPage });
188
+ };
189
+
190
+ return (
191
+ <div>
192
+ <Skeleton style={{ height: "25rem", width: "100%", marginBottom: "2rem" }} />
193
+ <Pagination {...args} currentPage={currentPage} onPageChange={onPageChange} />
194
+ </div>
195
+ );
196
+ },
197
+ };
@@ -0,0 +1,76 @@
1
+ import React from "react";
2
+ import * as matchers from "@testing-library/jest-dom/matchers";
3
+ import { cleanup, render, screen } from "@testing-library/react";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+
6
+ import { Pagination } from "./pagination";
7
+
8
+ expect.extend(matchers);
9
+
10
+ const defaultArgs = {
11
+ currentPage: 1,
12
+ pageSize: 10,
13
+ pageSizeLabel: "Items per page",
14
+ nextButtonText: "Next",
15
+ outOfLabel: "of",
16
+ previousButtonText: "Previous",
17
+ pageSelectorListBoxLabel: "Select a page",
18
+ pageSelectorNoOptionsText: "Page does not exist",
19
+ totalItems: 145,
20
+ onPageChange: vi.fn(),
21
+ };
22
+
23
+ afterEach(() => {
24
+ cleanup();
25
+ });
26
+
27
+ describe("Pagination", () => {
28
+ it("should render", () => {
29
+ render(<Pagination {...defaultArgs} />);
30
+ const pagination = screen.getByTestId(Selectors.PAGINATION);
31
+ expect(pagination).toBeInTheDocument();
32
+ });
33
+
34
+ it("should have a previous page button", () => {
35
+ render(<Pagination {...defaultArgs} />);
36
+ const previousButton = screen.getByTestId(Selectors.PREVIOUS_BUTTON);
37
+ expect(previousButton).toBeInTheDocument();
38
+ });
39
+
40
+ it("should have a next page button", () => {
41
+ render(<Pagination {...defaultArgs} />);
42
+ const nextButton = screen.getByTestId(Selectors.NEXT_BUTTON);
43
+ expect(nextButton).toBeInTheDocument();
44
+ });
45
+
46
+ it("should have a page size selector", () => {
47
+ render(<Pagination {...defaultArgs} />);
48
+ const pageSizeSelector = screen.getByTestId(Selectors.PAGE_SIZE_SELECTOR);
49
+ expect(pageSizeSelector).toBeInTheDocument();
50
+ });
51
+
52
+ it("should always show regular page buttons if total number of pages is less than or equal to 3", () => {
53
+ render(<Pagination {...defaultArgs} pageSize={75} />);
54
+ const paginationPages = screen.getByTestId(Selectors.PAGINATION_PAGES);
55
+ const paginationPageSelector = screen.getByTestId(Selectors.PAGINATION_PAGE_SELECTOR);
56
+ expect(paginationPages).toHaveClass("purpur-pagination__pages--visible");
57
+ expect(paginationPageSelector).not.toHaveClass("purpur-pagination__page-selector--visible");
58
+ });
59
+
60
+ it("should show pagination page selector if total number of pages is greater than 3 (shown on small screen sizes)", () => {
61
+ render(<Pagination {...defaultArgs} pageSize={10} />);
62
+ const paginationPages = screen.getByTestId(Selectors.PAGINATION_PAGES);
63
+ const paginationPageSelector = screen.getByTestId(Selectors.PAGINATION_PAGE_SELECTOR);
64
+ expect(paginationPages).not.toHaveClass("purpur-pagination__pages--visible");
65
+ expect(paginationPageSelector).toHaveClass("purpur-pagination__page-selector--visible");
66
+ });
67
+ });
68
+
69
+ const Selectors = {
70
+ PAGINATION: "purpur-pagination",
71
+ PREVIOUS_BUTTON: "purpur-pagination-previous-step-trigger",
72
+ NEXT_BUTTON: "purpur-pagination-next-step-trigger",
73
+ PAGE_SIZE_SELECTOR: "purpur-pagination-page-size-selector",
74
+ PAGINATION_PAGES: "purpur-pagination-pages",
75
+ PAGINATION_PAGE_SELECTOR: "purpur-pagination-page-selector",
76
+ };
@@ -0,0 +1,177 @@
1
+ import React, { ForwardedRef, forwardRef } from "react";
2
+ import { IconArrowLeft, IconArrowRight } from "@purpurds/icon";
3
+ import c from "classnames/bind";
4
+
5
+ import { navigateToPage } from "./navigation";
6
+ import styles from "./pagination.module.scss";
7
+ import { PaginationPageSelector } from "./pagination-page-selector";
8
+ import { PaginationPageSizeSelector } from "./pagination-page-size-selector";
9
+ import { PaginationPages } from "./pagination-pages";
10
+ import { PaginationStepTrigger } from "./pagination-step-trigger";
11
+ import { LinkProps, NoLinkProps, PaginationInformation } from "./types";
12
+ import { usePaginationPages } from "./use-pagination-pages.hook";
13
+
14
+ const cx = c.bind(styles);
15
+
16
+ export type PaginationTexts = {
17
+ nextButtonText: string;
18
+ outOfLabel: string;
19
+ pageSelectorListBoxLabel: string;
20
+ pageSelectorNoOptionsText: string;
21
+ previousButtonText: string;
22
+ };
23
+
24
+ export type PageSizeProps = {
25
+ availablePageSizes?: number[];
26
+ pageSize?: number;
27
+ pageSizeLabel: string;
28
+ };
29
+
30
+ export type PaginationProps = {
31
+ ["data-testid"]?: string;
32
+ className?: string;
33
+ currentPage: number;
34
+ pageSelectorAutocompleteId?: string;
35
+ totalItems: number;
36
+ } & PaginationTexts &
37
+ PageSizeProps &
38
+ (LinkProps | NoLinkProps);
39
+
40
+ const rootClassName = "purpur-pagination";
41
+
42
+ export const Pagination = forwardRef(
43
+ (
44
+ {
45
+ ["data-testid"]: dataTestId = "purpur-pagination",
46
+ anchorTagElement = "a",
47
+ asLink,
48
+ availablePageSizes = [10, 25, 50, 75],
49
+ className,
50
+ currentPage = 1,
51
+ hrefGetter,
52
+ navigationFunction,
53
+ nextButtonText,
54
+ onPageChange,
55
+ outOfLabel,
56
+ pageSelectorAutocompleteId,
57
+ pageSelectorListBoxLabel,
58
+ pageSelectorNoOptionsText,
59
+ pageSize = 10,
60
+ pageSizeLabel,
61
+ previousButtonText,
62
+ totalItems,
63
+ ...props
64
+ }: PaginationProps,
65
+ ref: ForwardedRef<HTMLDivElement>
66
+ ) => {
67
+ const classes = cx([className, rootClassName]);
68
+ const [_pageSize, _setPageSize] = React.useState(pageSize);
69
+ const { pages, numberOfPages } = usePaginationPages(totalItems, _pageSize, currentPage);
70
+
71
+ const _onPageChange = (page: number): void => {
72
+ onPageChange?.({ currentPage: page, pageSize });
73
+ };
74
+
75
+ const _hrefGetter = (page: number): string => {
76
+ return hrefGetter?.({ page, pageSize }) ?? "";
77
+ };
78
+
79
+ const _onPageSizeChange = (newPageSize: number): void => {
80
+ _setPageSize(newPageSize);
81
+ onPageChange?.({ currentPage: 1, pageSize: newPageSize });
82
+ };
83
+
84
+ const _navigationFunction = (page: number, url: string): void => {
85
+ if (navigationFunction) {
86
+ navigationFunction({ currentPage: page, pageSize: _pageSize, url });
87
+ } else {
88
+ navigateToPage(url);
89
+ }
90
+ };
91
+
92
+ const linkProps = asLink
93
+ ? { asLink: true as const, hrefGetter: _hrefGetter }
94
+ : {
95
+ asLink: undefined as never,
96
+ hrefGetter: undefined as never,
97
+ };
98
+
99
+ const ContainerTag = asLink ? "nav" : "div";
100
+
101
+ return (
102
+ <ContainerTag className={classes} data-testid={dataTestId} ref={ref} {...props}>
103
+ <div className={cx(`${rootClassName}__page-size-select-container`)}>
104
+ <PaginationPageSizeSelector
105
+ {...(asLink
106
+ ? { asLink: true, hrefGetter, navigationFunction }
107
+ : { asLink: undefined as never, hrefGetter: undefined as never })}
108
+ availablePageSizes={availablePageSizes}
109
+ className={cx(`${rootClassName}__page-size-selector`)}
110
+ data-testid={`${dataTestId}-page-size-selector`}
111
+ onPageSizeChange={_onPageSizeChange}
112
+ pageSize={_pageSize}
113
+ pageSizeLabel={pageSizeLabel}
114
+ />
115
+ </div>
116
+ <div className={cx(`${rootClassName}__pagination-container`)}>
117
+ <PaginationStepTrigger
118
+ {...linkProps}
119
+ anchorTagElement={anchorTagElement}
120
+ className={cx(`${rootClassName}__step-trigger`)}
121
+ data-testid={`${dataTestId}-previous-step-trigger`}
122
+ disabled={currentPage === 1}
123
+ onPageChange={_onPageChange}
124
+ pageToNavigateTo={currentPage - 1}
125
+ >
126
+ <IconArrowLeft size="sm" />
127
+ <span className={cx(`${rootClassName}__step-trigger-label`)}>{previousButtonText}</span>
128
+ </PaginationStepTrigger>
129
+ <div className={cx(`${rootClassName}__page-trigger-container`)}>
130
+ <PaginationPageSelector
131
+ {...linkProps}
132
+ className={cx([
133
+ `${rootClassName}__page-selector`,
134
+ { [`${rootClassName}__page-selector--visible`]: pages.length > 3 },
135
+ ])}
136
+ currentPage={currentPage}
137
+ data-testid={`${dataTestId}-page-selector`}
138
+ navigationFunction={_navigationFunction}
139
+ numberOfPages={numberOfPages}
140
+ onPageChange={_onPageChange}
141
+ outOfLabel={outOfLabel}
142
+ pageSelectorAutocompleteId={pageSelectorAutocompleteId}
143
+ pageSelectorListBoxLabel={pageSelectorListBoxLabel}
144
+ pageSelectorNoOptionsText={pageSelectorNoOptionsText}
145
+ />
146
+ <PaginationPages
147
+ {...linkProps}
148
+ anchorTagElement={anchorTagElement}
149
+ className={cx([
150
+ `${rootClassName}__pages`,
151
+ { [`${rootClassName}__pages--visible`]: pages.length <= 3 },
152
+ ])}
153
+ currentPage={currentPage}
154
+ data-testid={`${dataTestId}-pages`}
155
+ onPageChange={_onPageChange}
156
+ pages={pages}
157
+ />
158
+ </div>
159
+ <PaginationStepTrigger
160
+ {...linkProps}
161
+ anchorTagElement={anchorTagElement}
162
+ className={cx(`${rootClassName}__step-trigger`)}
163
+ data-testid={`${dataTestId}-next-step-trigger`}
164
+ disabled={currentPage === numberOfPages}
165
+ onPageChange={_onPageChange}
166
+ pageToNavigateTo={currentPage + 1}
167
+ >
168
+ <span className={cx(`${rootClassName}__step-trigger-label`)}>{nextButtonText}</span>
169
+ <IconArrowRight size="sm" />
170
+ </PaginationStepTrigger>
171
+ </div>
172
+ </ContainerTag>
173
+ );
174
+ }
175
+ );
176
+
177
+ Pagination.displayName = "Pagination";
package/src/types.ts ADDED
@@ -0,0 +1,43 @@
1
+ export type PaginationInformation = {
2
+ pageSize: number;
3
+ currentPage: number;
4
+ };
5
+
6
+ export type NavigationInformation = PaginationInformation & {
7
+ url: string;
8
+ };
9
+
10
+ export type HrefInformation = {
11
+ pageSize: number;
12
+ page: number;
13
+ };
14
+
15
+ export type LinkProps = {
16
+ anchorTagElement?: Link;
17
+ asLink: true;
18
+ hrefGetter: (hrefInformation: HrefInformation) => string;
19
+ navigationFunction?: (navigationInformation: NavigationInformation) => void;
20
+ onPageChange?: (paginationChangeInformation: PaginationInformation) => void;
21
+ };
22
+
23
+ export type NoLinkProps = {
24
+ anchorTagElement?: never;
25
+ asLink?: never;
26
+ hrefGetter?: never;
27
+ navigationFunction?: never;
28
+ onPageChange: (paginationChangeInformation: PaginationInformation) => void;
29
+ };
30
+
31
+ export type InternalLinkProps = {
32
+ asLink: true;
33
+ hrefGetter: (page: number) => string;
34
+ };
35
+
36
+ export type InternalNoLinkProps = {
37
+ asLink?: never;
38
+ hrefGetter?: never;
39
+ };
40
+
41
+ export type Link =
42
+ | React.ForwardRefExoticComponent<React.AnchorHTMLAttributes<HTMLAnchorElement>>
43
+ | "a";
@@ -0,0 +1,21 @@
1
+ import { useMemo } from "react";
2
+ import { Option } from "@purpurds/autocomplete";
3
+
4
+ type UsePageOptionsHook = {
5
+ options: Option[];
6
+ };
7
+
8
+ export const usePageOptions = (numberOfPages: number): UsePageOptionsHook => {
9
+ const options = useMemo(
10
+ () =>
11
+ Array.from({ length: numberOfPages }, (_, i) => ({
12
+ id: `${i + 1}`,
13
+ label: `${i + 1}`,
14
+ })),
15
+ [numberOfPages]
16
+ );
17
+
18
+ return {
19
+ options,
20
+ };
21
+ };
@@ -0,0 +1,21 @@
1
+ import { useMemo } from "react";
2
+ import { SelectOption } from "@purpurds/select";
3
+
4
+ type UsePageSizeOptionsHook = {
5
+ options: SelectOption[];
6
+ };
7
+
8
+ export const usePageSizeOptions = (availablePageSizes: number[]): UsePageSizeOptionsHook => {
9
+ const options = useMemo(
10
+ () =>
11
+ availablePageSizes.map((pageSize: number) => ({
12
+ label: `${pageSize}`,
13
+ value: `${pageSize}`,
14
+ })),
15
+ [availablePageSizes]
16
+ );
17
+
18
+ return {
19
+ options,
20
+ };
21
+ };