@sproutsocial/seeds-react-pagination 0.1.28 → 0.1.31
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.
- package/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +37 -0
- package/dist/esm/index.js +239 -66
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +32 -10
- package/dist/index.d.ts +32 -10
- package/dist/index.js +239 -66
- package/dist/index.js.map +1 -1
- package/dist/pagination.css +99 -0
- package/package.json +10 -9
- package/postcss.config.js +6 -0
- package/src/Pagination.stories.tsx +58 -0
- package/src/Pagination.tsx +17 -210
- package/src/PaginationHybrid.tsx +20 -0
- package/src/PaginationStyled.tsx +128 -0
- package/src/PaginationTailwind.tsx +152 -0
- package/src/PaginationTypes.ts +19 -0
- package/src/__tests__/Pagination.test.tsx +164 -0
- package/src/__tests__/usePaginationModel.test.ts +91 -0
- package/src/css.d.ts +1 -0
- package/src/pagination.css +99 -0
- package/src/usePaginationModel.ts +124 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
render,
|
|
3
|
+
screen,
|
|
4
|
+
fireEvent,
|
|
5
|
+
} from "@sproutsocial/seeds-react-testing-library";
|
|
6
|
+
import { Pagination } from "../Pagination";
|
|
7
|
+
import { PAGE_SIZE_OPTIONS } from "../PaginationTypes";
|
|
8
|
+
import type { TypePaginationConfig } from "../PaginationTypes";
|
|
9
|
+
|
|
10
|
+
const tableCountFunction: TypePaginationConfig["tableCountFunction"] = ({
|
|
11
|
+
startNum,
|
|
12
|
+
endNum,
|
|
13
|
+
totalRowCount,
|
|
14
|
+
}) => `${startNum}-${endNum} of ${totalRowCount}`;
|
|
15
|
+
|
|
16
|
+
const baseConfig: TypePaginationConfig = {
|
|
17
|
+
initialPageSize: 10,
|
|
18
|
+
tableCountFunction,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe("Pagination", () => {
|
|
22
|
+
it("is a named component definition (not a bare re-export) so react-docgen can extract its props", () => {
|
|
23
|
+
// The public entry must contain an actual component definition rather than a
|
|
24
|
+
// pure `export { ... } from "./PaginationHybrid"` re-export. The Publish
|
|
25
|
+
// pipeline runs `react-docgen --failOnWarning` over Pagination.tsx, which
|
|
26
|
+
// errors with "No suitable component definition found" when the file only
|
|
27
|
+
// re-exports the hybrid. A self-named component with a displayName gives
|
|
28
|
+
// react-docgen a definition to analyze.
|
|
29
|
+
expect(typeof Pagination).toBe("function");
|
|
30
|
+
expect(Pagination.displayName).toBe("Pagination");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("renders nothing when there is a single page and no page size options", () => {
|
|
34
|
+
const { container } = render(
|
|
35
|
+
<Pagination
|
|
36
|
+
paginationConfig={baseConfig}
|
|
37
|
+
currentPageIndex={0}
|
|
38
|
+
pageSize={10}
|
|
39
|
+
totalRows={0}
|
|
40
|
+
onPageChange={jest.fn()}
|
|
41
|
+
onPageSizeChange={jest.fn()}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
expect(container.firstChild).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("renders the count text and page-number controls for a multi-page config", () => {
|
|
48
|
+
render(
|
|
49
|
+
<Pagination
|
|
50
|
+
paginationConfig={baseConfig}
|
|
51
|
+
currentPageIndex={0}
|
|
52
|
+
pageSize={10}
|
|
53
|
+
totalRows={100}
|
|
54
|
+
onPageChange={jest.fn()}
|
|
55
|
+
onPageSizeChange={jest.fn()}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
expect(screen.getByText("1-10 of 100")).toBeInTheDocument();
|
|
59
|
+
expect(screen.getByLabelText("Page 2")).toBeInTheDocument();
|
|
60
|
+
expect(screen.getByLabelText("Previous page")).toBeInTheDocument();
|
|
61
|
+
expect(screen.getByLabelText("Next page")).toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("calls onPageChange with the 0-based index when a page button is clicked", () => {
|
|
65
|
+
const onPageChange = jest.fn();
|
|
66
|
+
render(
|
|
67
|
+
<Pagination
|
|
68
|
+
paginationConfig={baseConfig}
|
|
69
|
+
currentPageIndex={0}
|
|
70
|
+
pageSize={10}
|
|
71
|
+
totalRows={100}
|
|
72
|
+
onPageChange={onPageChange}
|
|
73
|
+
onPageSizeChange={jest.fn()}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
fireEvent.click(screen.getByLabelText("Page 2"));
|
|
77
|
+
expect(onPageChange).toHaveBeenCalledWith(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("marks the current page button as disabled with aria-current='page'", () => {
|
|
81
|
+
render(
|
|
82
|
+
<Pagination
|
|
83
|
+
paginationConfig={baseConfig}
|
|
84
|
+
currentPageIndex={0}
|
|
85
|
+
pageSize={10}
|
|
86
|
+
totalRows={100}
|
|
87
|
+
onPageChange={jest.fn()}
|
|
88
|
+
onPageSizeChange={jest.fn()}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
const currentPage = screen.getByLabelText("Page 1");
|
|
92
|
+
expect(currentPage).toBeDisabled();
|
|
93
|
+
expect(currentPage).toHaveAttribute("aria-current", "page");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("disables Previous on the first page and Next on the last page", () => {
|
|
97
|
+
const { rerender } = render(
|
|
98
|
+
<Pagination
|
|
99
|
+
paginationConfig={baseConfig}
|
|
100
|
+
currentPageIndex={0}
|
|
101
|
+
pageSize={10}
|
|
102
|
+
totalRows={100}
|
|
103
|
+
onPageChange={jest.fn()}
|
|
104
|
+
onPageSizeChange={jest.fn()}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
expect(screen.getByLabelText("Previous page")).toBeDisabled();
|
|
108
|
+
expect(screen.getByLabelText("Next page")).not.toBeDisabled();
|
|
109
|
+
|
|
110
|
+
rerender(
|
|
111
|
+
<Pagination
|
|
112
|
+
paginationConfig={baseConfig}
|
|
113
|
+
currentPageIndex={9}
|
|
114
|
+
pageSize={10}
|
|
115
|
+
totalRows={100}
|
|
116
|
+
onPageChange={jest.fn()}
|
|
117
|
+
onPageSizeChange={jest.fn()}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
expect(screen.getByLabelText("Previous page")).not.toBeDisabled();
|
|
121
|
+
expect(screen.getByLabelText("Next page")).toBeDisabled();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("forwards an optional className onto the root container so consumers can apply utility overrides", () => {
|
|
125
|
+
const { container } = render(
|
|
126
|
+
<Pagination
|
|
127
|
+
paginationConfig={baseConfig}
|
|
128
|
+
currentPageIndex={0}
|
|
129
|
+
pageSize={10}
|
|
130
|
+
totalRows={100}
|
|
131
|
+
onPageChange={jest.fn()}
|
|
132
|
+
onPageSizeChange={jest.fn()}
|
|
133
|
+
className="test-utility"
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
expect(container.firstChild).toHaveClass("test-utility");
|
|
137
|
+
// The base component class must remain so the override composes rather than replaces.
|
|
138
|
+
expect(container.firstChild).toHaveClass("seeds-pagination-container");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("calls onPageSizeChange and resets to the first page when the page size changes", () => {
|
|
142
|
+
const onPageChange = jest.fn();
|
|
143
|
+
const onPageSizeChange = jest.fn();
|
|
144
|
+
render(
|
|
145
|
+
<Pagination
|
|
146
|
+
paginationConfig={{
|
|
147
|
+
...baseConfig,
|
|
148
|
+
displayType: "dropdown",
|
|
149
|
+
pageSizeOptions: PAGE_SIZE_OPTIONS.STANDARD,
|
|
150
|
+
}}
|
|
151
|
+
currentPageIndex={2}
|
|
152
|
+
pageSize={10}
|
|
153
|
+
totalRows={100}
|
|
154
|
+
onPageChange={onPageChange}
|
|
155
|
+
onPageSizeChange={onPageSizeChange}
|
|
156
|
+
/>
|
|
157
|
+
);
|
|
158
|
+
fireEvent.change(screen.getByLabelText("Rows per page"), {
|
|
159
|
+
target: { value: "25" },
|
|
160
|
+
});
|
|
161
|
+
expect(onPageSizeChange).toHaveBeenCalledWith(25);
|
|
162
|
+
expect(onPageChange).toHaveBeenCalledWith(0);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import { usePaginationModel } from "../usePaginationModel";
|
|
3
|
+
import type { PaginationProps, TypePaginationConfig } from "../PaginationTypes";
|
|
4
|
+
|
|
5
|
+
const tableCountFunction: TypePaginationConfig["tableCountFunction"] = ({
|
|
6
|
+
startNum,
|
|
7
|
+
endNum,
|
|
8
|
+
totalRowCount,
|
|
9
|
+
}) => `${startNum}-${endNum} of ${totalRowCount}`;
|
|
10
|
+
|
|
11
|
+
const baseConfig: TypePaginationConfig = {
|
|
12
|
+
initialPageSize: 10,
|
|
13
|
+
tableCountFunction,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const buildProps = (
|
|
17
|
+
overrides: Partial<PaginationProps> = {}
|
|
18
|
+
): PaginationProps => ({
|
|
19
|
+
paginationConfig: baseConfig,
|
|
20
|
+
currentPageIndex: 0,
|
|
21
|
+
pageSize: 10,
|
|
22
|
+
totalRows: 100,
|
|
23
|
+
onPageChange: jest.fn(),
|
|
24
|
+
onPageSizeChange: jest.fn(),
|
|
25
|
+
...overrides,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("usePaginationModel", () => {
|
|
29
|
+
it("derives the page math (totalPages / currentPage / row range) for a multi-page config", () => {
|
|
30
|
+
const model = usePaginationModel(buildProps());
|
|
31
|
+
expect(model.totalPages).toBe(10);
|
|
32
|
+
expect(model.currentPage).toBe(1); // 0-based index -> 1-based display
|
|
33
|
+
expect(model.startRow).toBe(1);
|
|
34
|
+
expect(model.endRow).toBe(10);
|
|
35
|
+
expect(model.canGoPrevious).toBe(false);
|
|
36
|
+
expect(model.canGoNext).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("reports startRow as 0 when there are no rows", () => {
|
|
40
|
+
const model = usePaginationModel(buildProps({ totalRows: 0 }));
|
|
41
|
+
expect(model.totalPages).toBe(0);
|
|
42
|
+
expect(model.startRow).toBe(0);
|
|
43
|
+
expect(model.endRow).toBe(0);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("collapses the page-number list with ellipses for large page counts", () => {
|
|
47
|
+
// 20 pages, on the first page: first page, a window, ellipsis, last page.
|
|
48
|
+
const model = usePaginationModel(
|
|
49
|
+
buildProps({ totalRows: 200, currentPageIndex: 0 })
|
|
50
|
+
);
|
|
51
|
+
expect(model.pageNumbers).toEqual([1, 2, 3, 4, 5, 6, "...", 20]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("shows leading and trailing ellipses when the current page is in the middle", () => {
|
|
55
|
+
const model = usePaginationModel(
|
|
56
|
+
buildProps({ totalRows: 200, currentPageIndex: 9 }) // page 10 of 20
|
|
57
|
+
);
|
|
58
|
+
expect(model.pageNumbers).toEqual([1, "...", 9, 10, 11, 12, "...", 20]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("lists every page without ellipsis when the count is small", () => {
|
|
62
|
+
const model = usePaginationModel(
|
|
63
|
+
buildProps({ totalRows: 30, currentPageIndex: 0 }) // 3 pages
|
|
64
|
+
);
|
|
65
|
+
expect(model.pageNumbers).toEqual([1, 2, 3]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("handlePrevious / handleNext respect the boundary guards", () => {
|
|
69
|
+
const onPageChange = jest.fn();
|
|
70
|
+
const first = usePaginationModel(
|
|
71
|
+
buildProps({ currentPageIndex: 0, onPageChange })
|
|
72
|
+
);
|
|
73
|
+
first.handlePrevious(); // already on first page -> no-op
|
|
74
|
+
expect(onPageChange).not.toHaveBeenCalled();
|
|
75
|
+
first.handleNext();
|
|
76
|
+
expect(onPageChange).toHaveBeenCalledWith(1);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("handlePageSizeChange forwards the new size and resets to the first page", () => {
|
|
80
|
+
const onPageChange = jest.fn();
|
|
81
|
+
const onPageSizeChange = jest.fn();
|
|
82
|
+
const model = usePaginationModel(
|
|
83
|
+
buildProps({ onPageChange, onPageSizeChange })
|
|
84
|
+
);
|
|
85
|
+
model.handlePageSizeChange({
|
|
86
|
+
currentTarget: { value: "25" },
|
|
87
|
+
} as React.SyntheticEvent<HTMLSelectElement>);
|
|
88
|
+
expect(onPageSizeChange).toHaveBeenCalledWith(25);
|
|
89
|
+
expect(onPageChange).toHaveBeenCalledWith(0);
|
|
90
|
+
});
|
|
91
|
+
});
|
package/src/css.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "*.css";
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeds Pagination component classes
|
|
3
|
+
* Use these instead of writing out individual Tailwind utility classes.
|
|
4
|
+
*
|
|
5
|
+
* Requires @sproutsocial/seeds-react-theme/dist/theme-all.css imported for
|
|
6
|
+
* CSS variable definitions and dark mode support.
|
|
7
|
+
*
|
|
8
|
+
* Rules are wrapped in @layer components so consumer Tailwind utilities (which
|
|
9
|
+
* live in @layer utilities) win the cascade. Without this layer the unlayered
|
|
10
|
+
* component CSS would beat utilities like mx-200 / p-300 / inline-flex and they
|
|
11
|
+
* could not override component styling. Keep every rule inside this single block
|
|
12
|
+
* in source order — the -selected modifier and the :disabled overrides resolve
|
|
13
|
+
* by source order within the same layer.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
@layer components {
|
|
17
|
+
/* StyledPaginationContainer */
|
|
18
|
+
.seeds-pagination-container {
|
|
19
|
+
font-size: var(--font-size-200);
|
|
20
|
+
line-height: var(--line-height-200);
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: var(--space-300);
|
|
24
|
+
margin-top: var(--space-400);
|
|
25
|
+
flex-wrap: wrap;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* StyledPaginationInfo (styled(Text)) */
|
|
29
|
+
.seeds-pagination-info {
|
|
30
|
+
white-space: nowrap;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* StyledPageSizeSelector */
|
|
34
|
+
.seeds-pagination-page-size-selector {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
gap: var(--space-300);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* StyledPaginationControls */
|
|
41
|
+
.seeds-pagination-controls {
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
margin-left: auto;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* StyledPageButtons */
|
|
48
|
+
.seeds-pagination-page-buttons {
|
|
49
|
+
display: inline-flex;
|
|
50
|
+
flex-flow: row wrap;
|
|
51
|
+
background-clip: padding-box;
|
|
52
|
+
border: 1px solid var(--color-button-secondary-border-base);
|
|
53
|
+
border-radius: var(--radius-outer);
|
|
54
|
+
padding: var(--space-100);
|
|
55
|
+
background: transparent;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* StyledPaginationButton — base */
|
|
59
|
+
.seeds-pagination-button {
|
|
60
|
+
min-width: calc(var(--space-500) + 4px);
|
|
61
|
+
min-height: calc(var(--space-500) + 2px);
|
|
62
|
+
padding: calc(var(--space-350) - 6px);
|
|
63
|
+
line-height: var(--space-400);
|
|
64
|
+
font-weight: var(--font-weight-semibold);
|
|
65
|
+
background-color: transparent;
|
|
66
|
+
color: var(--color-text-body);
|
|
67
|
+
border: none;
|
|
68
|
+
border-radius: var(--radius-inner);
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
display: inline-flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
justify-content: center;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* selected modifier MUST follow base (equal specificity — source order wins) */
|
|
76
|
+
.seeds-pagination-button-selected {
|
|
77
|
+
background-color: var(--color-button-secondary-bg-active);
|
|
78
|
+
color: var(--color-text-inverse);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.seeds-pagination-button:hover:not(:disabled) {
|
|
82
|
+
background-color: var(--color-listItem-bg-hover);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.seeds-pagination-button-selected:hover:not(:disabled) {
|
|
86
|
+
background-color: var(--color-button-secondary-bg-active);
|
|
87
|
+
color: var(--color-text-inverse);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.seeds-pagination-button:disabled {
|
|
91
|
+
cursor: default;
|
|
92
|
+
opacity: 0.5;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* current page renders disabled + selected and must stay fully opaque */
|
|
96
|
+
.seeds-pagination-button-selected:disabled {
|
|
97
|
+
opacity: 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation-agnostic Pagination model: page math, the page-number token
|
|
3
|
+
* list, and the event handlers consumed identically by PaginationTailwind and
|
|
4
|
+
* PaginationStyled. Contains NO rendering — the JSX and the `return null` guard
|
|
5
|
+
* stay in each component — so the two implementations cannot drift apart. Call
|
|
6
|
+
* it unconditionally at the top of each component, before any early return.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import type { PaginationProps } from "./PaginationTypes";
|
|
11
|
+
|
|
12
|
+
export const usePaginationModel = ({
|
|
13
|
+
paginationConfig,
|
|
14
|
+
currentPageIndex,
|
|
15
|
+
pageSize,
|
|
16
|
+
totalRows,
|
|
17
|
+
onPageChange,
|
|
18
|
+
onPageSizeChange,
|
|
19
|
+
}: PaginationProps) => {
|
|
20
|
+
const { displayType, pageSizeOptions, tableCountFunction } = paginationConfig;
|
|
21
|
+
|
|
22
|
+
// Calculate pagination values
|
|
23
|
+
const totalPages = Math.ceil(totalRows / pageSize);
|
|
24
|
+
const currentPage = currentPageIndex + 1; // Convert 0-based to 1-based for display
|
|
25
|
+
const startRow = totalRows === 0 ? 0 : currentPageIndex * pageSize + 1;
|
|
26
|
+
const endRow = Math.min((currentPageIndex + 1) * pageSize, totalRows);
|
|
27
|
+
|
|
28
|
+
const canGoPrevious = currentPageIndex > 0;
|
|
29
|
+
const canGoNext = currentPageIndex < totalPages - 1;
|
|
30
|
+
|
|
31
|
+
const handlePrevious = () => {
|
|
32
|
+
if (canGoPrevious) {
|
|
33
|
+
onPageChange(currentPageIndex - 1);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleNext = () => {
|
|
38
|
+
if (canGoNext) {
|
|
39
|
+
onPageChange(currentPageIndex + 1);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handlePageSizeChange = (e: React.SyntheticEvent<HTMLSelectElement>) => {
|
|
44
|
+
const newPageSize = Number(e.currentTarget.value);
|
|
45
|
+
onPageSizeChange(newPageSize);
|
|
46
|
+
// Reset to first page when page size changes
|
|
47
|
+
onPageChange(0);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Generate page number buttons using web-app-core's algorithm
|
|
51
|
+
const getPageNumbers = () => {
|
|
52
|
+
const pages: (number | string)[] = [];
|
|
53
|
+
const size = paginationConfig.size || "default";
|
|
54
|
+
|
|
55
|
+
// Configuration for how many pages to show around current page
|
|
56
|
+
const NUM_PAGE_LOWER = size === "mini" ? 0 : 1;
|
|
57
|
+
const NUM_PAGE_UPPER = size === "mini" ? 0 : 2;
|
|
58
|
+
|
|
59
|
+
// If number of pages is small enough, show all the page tokens without ellipsis
|
|
60
|
+
if (totalPages <= 5 + NUM_PAGE_LOWER + NUM_PAGE_UPPER) {
|
|
61
|
+
for (let i = 1; i <= totalPages; i++) {
|
|
62
|
+
pages.push(i);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// Add first page
|
|
66
|
+
pages.push(1);
|
|
67
|
+
|
|
68
|
+
// Add ellipsis at the beginning if needed
|
|
69
|
+
if (currentPage > 3 + NUM_PAGE_LOWER) {
|
|
70
|
+
pages.push("...");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Add pages in the middle
|
|
74
|
+
let start: number;
|
|
75
|
+
let end: number; // inclusive index
|
|
76
|
+
|
|
77
|
+
// If near beginning - Show number of pages before ending ellipsis
|
|
78
|
+
if (currentPage <= 3 + NUM_PAGE_LOWER) {
|
|
79
|
+
start = 2;
|
|
80
|
+
end = 3 + NUM_PAGE_LOWER + NUM_PAGE_UPPER;
|
|
81
|
+
} else if (currentPage > totalPages - (3 + NUM_PAGE_UPPER)) {
|
|
82
|
+
// If near end - Show number of pages after starting ellipsis
|
|
83
|
+
start = totalPages - (2 + NUM_PAGE_LOWER + NUM_PAGE_UPPER);
|
|
84
|
+
end = totalPages - 1;
|
|
85
|
+
} else {
|
|
86
|
+
// If in the middle - Show number of pages between two ellipses
|
|
87
|
+
start = currentPage - NUM_PAGE_LOWER;
|
|
88
|
+
end = currentPage + NUM_PAGE_UPPER;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (let i = start; i <= end; i++) {
|
|
92
|
+
pages.push(i);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Add a final ellipsis if necessary
|
|
96
|
+
if (currentPage < totalPages - (2 + NUM_PAGE_UPPER)) {
|
|
97
|
+
pages.push("...");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Add the last page number
|
|
101
|
+
pages.push(totalPages);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return pages;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const pageNumbers = getPageNumbers();
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
displayType,
|
|
111
|
+
pageSizeOptions,
|
|
112
|
+
tableCountFunction,
|
|
113
|
+
totalPages,
|
|
114
|
+
currentPage,
|
|
115
|
+
startRow,
|
|
116
|
+
endRow,
|
|
117
|
+
canGoPrevious,
|
|
118
|
+
canGoNext,
|
|
119
|
+
handlePrevious,
|
|
120
|
+
handleNext,
|
|
121
|
+
handlePageSizeChange,
|
|
122
|
+
pageNumbers,
|
|
123
|
+
};
|
|
124
|
+
};
|