@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.
@@ -1,210 +1,17 @@
1
- import * as React from "react";
2
- import { Select } from "@sproutsocial/seeds-react-select";
3
- import { Icon } from "@sproutsocial/seeds-react-icon";
4
- import type { TypePaginationConfig } from "./PaginationTypes";
5
- import {
6
- StyledPaginationContainer,
7
- StyledPaginationControls,
8
- StyledPaginationInfo,
9
- StyledPageSizeSelector,
10
- StyledPageButtons,
11
- StyledPaginationButton,
12
- } from "./styles";
13
-
14
- interface PaginationProps {
15
- paginationConfig: TypePaginationConfig;
16
- currentPageIndex: number;
17
- pageSize: number;
18
- totalRows: number;
19
- onPageChange: (pageIndex: number) => void;
20
- onPageSizeChange: (pageSize: number) => void;
21
- }
22
-
23
- export const Pagination = ({
24
- paginationConfig,
25
- currentPageIndex,
26
- pageSize,
27
- totalRows,
28
- onPageChange,
29
- onPageSizeChange,
30
- }: PaginationProps) => {
31
- const { displayType, pageSizeOptions, tableCountFunction } = paginationConfig;
32
-
33
- // Calculate pagination values
34
- const totalPages = Math.ceil(totalRows / pageSize);
35
- const currentPage = currentPageIndex + 1; // Convert 0-based to 1-based for display
36
- const startRow = totalRows === 0 ? 0 : currentPageIndex * pageSize + 1;
37
- const endRow = Math.min((currentPageIndex + 1) * pageSize, totalRows);
38
-
39
- const canGoPrevious = currentPageIndex > 0;
40
- const canGoNext = currentPageIndex < totalPages - 1;
41
-
42
- const handlePrevious = () => {
43
- if (canGoPrevious) {
44
- onPageChange(currentPageIndex - 1);
45
- }
46
- };
47
-
48
- const handleNext = () => {
49
- if (canGoNext) {
50
- onPageChange(currentPageIndex + 1);
51
- }
52
- };
53
-
54
- const handlePageSizeChange = (e: React.SyntheticEvent<HTMLSelectElement>) => {
55
- const newPageSize = Number(e.currentTarget.value);
56
- onPageSizeChange(newPageSize);
57
- // Reset to first page when page size changes
58
- onPageChange(0);
59
- };
60
-
61
- // Generate page number buttons using web-app-core's algorithm
62
- const getPageNumbers = () => {
63
- const pages: (number | string)[] = [];
64
- const size = paginationConfig.size || "default";
65
-
66
- // Configuration for how many pages to show around current page
67
- const NUM_PAGE_LOWER = size === "mini" ? 0 : 1;
68
- const NUM_PAGE_UPPER = size === "mini" ? 0 : 2;
69
-
70
- // If number of pages is small enough, show all the page tokens without ellipsis
71
- if (totalPages <= 5 + NUM_PAGE_LOWER + NUM_PAGE_UPPER) {
72
- for (let i = 1; i <= totalPages; i++) {
73
- pages.push(i);
74
- }
75
- } else {
76
- // Add first page
77
- pages.push(1);
78
-
79
- // Add ellipsis at the beginning if needed
80
- if (currentPage > 3 + NUM_PAGE_LOWER) {
81
- pages.push("...");
82
- }
83
-
84
- // Add pages in the middle
85
- let start: number;
86
- let end: number; // inclusive index
87
-
88
- // If near beginning - Show number of pages before ending ellipsis
89
- if (currentPage <= 3 + NUM_PAGE_LOWER) {
90
- start = 2;
91
- end = 3 + NUM_PAGE_LOWER + NUM_PAGE_UPPER;
92
- } else if (currentPage > totalPages - (3 + NUM_PAGE_UPPER)) {
93
- // If near end - Show number of pages after starting ellipsis
94
- start = totalPages - (2 + NUM_PAGE_LOWER + NUM_PAGE_UPPER);
95
- end = totalPages - 1;
96
- } else {
97
- // If in the middle - Show number of pages between two ellipses
98
- start = currentPage - NUM_PAGE_LOWER;
99
- end = currentPage + NUM_PAGE_UPPER;
100
- }
101
-
102
- for (let i = start; i <= end; i++) {
103
- pages.push(i);
104
- }
105
-
106
- // Add a final ellipsis if necessary
107
- if (currentPage < totalPages - (2 + NUM_PAGE_UPPER)) {
108
- pages.push("...");
109
- }
110
-
111
- // Add the last page number
112
- pages.push(totalPages);
113
- }
114
-
115
- return pages;
116
- };
117
-
118
- const pageNumbers = getPageNumbers();
119
-
120
- if (totalPages <= 1 && !pageSizeOptions) {
121
- // No pagination needed if only one page and no page size options
122
- return null;
123
- }
124
-
125
- return (
126
- <StyledPaginationContainer>
127
- <StyledPaginationInfo>
128
- {tableCountFunction({
129
- startNum: startRow,
130
- endNum: endRow,
131
- totalRowCount: totalRows,
132
- })}
133
- </StyledPaginationInfo>
134
- {/* Page size selector */}
135
- {pageSizeOptions && displayType === "dropdown" && (
136
- <StyledPageSizeSelector>
137
- <Select
138
- id="page-size-select"
139
- name="page-size"
140
- value={String(pageSize)}
141
- onChange={handlePageSizeChange}
142
- ariaLabel="Rows per page"
143
- size="small"
144
- >
145
- {pageSizeOptions.map((size) => (
146
- <option key={size} value={size}>
147
- {size} per page
148
- </option>
149
- ))}
150
- </Select>
151
- </StyledPageSizeSelector>
152
- )}
153
-
154
- {/* TODO: Add inline page size selector for displayType === 'inline' */}
155
-
156
- {/* Page navigation controls */}
157
- {totalPages > 1 && (
158
- <StyledPaginationControls>
159
- <StyledPageButtons>
160
- <StyledPaginationButton
161
- onClick={handlePrevious}
162
- disabled={!canGoPrevious}
163
- aria-label="Previous page"
164
- >
165
- <Icon name="chevron-left-outline" />
166
- </StyledPaginationButton>
167
-
168
- {pageNumbers.map((pageNum, index) => {
169
- if (pageNum === "...") {
170
- return (
171
- <StyledPaginationButton
172
- key={`ellipsis-${index}`}
173
- disabled
174
- aria-label="More pages"
175
- >
176
- <Icon name="ellipsis-horizontal-outline" />
177
- </StyledPaginationButton>
178
- );
179
- }
180
-
181
- const page = pageNum as number;
182
- const isCurrentPage = page === currentPage;
183
-
184
- return (
185
- <StyledPaginationButton
186
- key={page}
187
- onClick={() => onPageChange(page - 1)}
188
- disabled={isCurrentPage}
189
- aria-label={`Page ${page}`}
190
- aria-current={isCurrentPage ? "page" : undefined}
191
- isSelected={isCurrentPage}
192
- >
193
- {page}
194
- </StyledPaginationButton>
195
- );
196
- })}
197
-
198
- <StyledPaginationButton
199
- onClick={handleNext}
200
- disabled={!canGoNext}
201
- aria-label="Next page"
202
- >
203
- <Icon name="chevron-right-outline" />
204
- </StyledPaginationButton>
205
- </StyledPageButtons>
206
- </StyledPaginationControls>
207
- )}
208
- </StyledPaginationContainer>
209
- );
210
- };
1
+ import { PaginationHybrid } from "./PaginationHybrid";
2
+ import type { PaginationProps } from "./PaginationTypes";
3
+
4
+ /**
5
+ * Pagination component. Automatically routes between the Tailwind implementation
6
+ * (preferred) and the styled-components implementation (for consumers using
7
+ * system props or styled() extension).
8
+ *
9
+ * Defined as a self-named component (rather than a bare re-export of
10
+ * PaginationHybrid) so the `react-docgen` props-docs generation in the Publish
11
+ * pipeline has a suitable component definition to analyze.
12
+ */
13
+ export const Pagination = (props: PaginationProps) => (
14
+ <PaginationHybrid {...props} />
15
+ );
16
+
17
+ Pagination.displayName = "Pagination";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Hybrid Pagination component.
3
+ * Routes between the Tailwind implementation (preferred) and the
4
+ * styled-components implementation (for consumers using system props).
5
+ */
6
+
7
+ import { hasStyledProps } from "@sproutsocial/seeds-react-system-props";
8
+ import { PaginationStyled } from "./PaginationStyled";
9
+ import { PaginationTailwind } from "./PaginationTailwind";
10
+ import type { PaginationProps } from "./PaginationTypes";
11
+
12
+ export const PaginationHybrid = (props: PaginationProps) => {
13
+ // Pagination exposes domain props plus an optional className, none of which
14
+ // are styled-system props, so hasStyledProps is effectively always false; the
15
+ // styled-components branch is retained as a pattern-conformance fallback.
16
+ if (hasStyledProps(props)) {
17
+ return <PaginationStyled {...props} />;
18
+ }
19
+ return <PaginationTailwind {...props} />;
20
+ };
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Styled-components implementation of the Pagination component.
3
+ * Composes the styled sub-components from styles.ts. Retained for backward
4
+ * compatibility (system props / styled() extension) via PaginationHybrid.
5
+ */
6
+
7
+ import { Select } from "@sproutsocial/seeds-react-select";
8
+ import { Icon } from "@sproutsocial/seeds-react-icon";
9
+ import type { PaginationProps } from "./PaginationTypes";
10
+ import { usePaginationModel } from "./usePaginationModel";
11
+ import {
12
+ StyledPaginationContainer,
13
+ StyledPaginationControls,
14
+ StyledPaginationInfo,
15
+ StyledPageSizeSelector,
16
+ StyledPageButtons,
17
+ StyledPaginationButton,
18
+ } from "./styles";
19
+
20
+ export const PaginationStyled = (props: PaginationProps) => {
21
+ const { pageSize, totalRows, onPageChange, className } = props;
22
+ const {
23
+ displayType,
24
+ pageSizeOptions,
25
+ tableCountFunction,
26
+ totalPages,
27
+ currentPage,
28
+ startRow,
29
+ endRow,
30
+ canGoPrevious,
31
+ canGoNext,
32
+ handlePrevious,
33
+ handleNext,
34
+ handlePageSizeChange,
35
+ pageNumbers,
36
+ } = usePaginationModel(props);
37
+
38
+ if (totalPages <= 1 && !pageSizeOptions) {
39
+ // No pagination needed if only one page and no page size options
40
+ return null;
41
+ }
42
+
43
+ return (
44
+ <StyledPaginationContainer className={className}>
45
+ <StyledPaginationInfo>
46
+ {tableCountFunction({
47
+ startNum: startRow,
48
+ endNum: endRow,
49
+ totalRowCount: totalRows,
50
+ })}
51
+ </StyledPaginationInfo>
52
+ {/* Page size selector */}
53
+ {pageSizeOptions && displayType === "dropdown" && (
54
+ <StyledPageSizeSelector>
55
+ <Select
56
+ id="page-size-select"
57
+ name="page-size"
58
+ value={String(pageSize)}
59
+ onChange={handlePageSizeChange}
60
+ ariaLabel="Rows per page"
61
+ size="small"
62
+ >
63
+ {pageSizeOptions.map((size) => (
64
+ <option key={size} value={size}>
65
+ {size} per page
66
+ </option>
67
+ ))}
68
+ </Select>
69
+ </StyledPageSizeSelector>
70
+ )}
71
+
72
+ {/* TODO: Add inline page size selector for displayType === 'inline' */}
73
+
74
+ {/* Page navigation controls */}
75
+ {totalPages > 1 && (
76
+ <StyledPaginationControls>
77
+ <StyledPageButtons>
78
+ <StyledPaginationButton
79
+ onClick={handlePrevious}
80
+ disabled={!canGoPrevious}
81
+ aria-label="Previous page"
82
+ >
83
+ <Icon name="chevron-left-outline" />
84
+ </StyledPaginationButton>
85
+
86
+ {pageNumbers.map((pageNum, index) => {
87
+ if (pageNum === "...") {
88
+ return (
89
+ <StyledPaginationButton
90
+ key={`ellipsis-${index}`}
91
+ disabled
92
+ aria-label="More pages"
93
+ >
94
+ <Icon name="ellipsis-horizontal-outline" />
95
+ </StyledPaginationButton>
96
+ );
97
+ }
98
+
99
+ const page = pageNum as number;
100
+ const isCurrentPage = page === currentPage;
101
+
102
+ return (
103
+ <StyledPaginationButton
104
+ key={page}
105
+ onClick={() => onPageChange(page - 1)}
106
+ disabled={isCurrentPage}
107
+ aria-label={`Page ${page}`}
108
+ aria-current={isCurrentPage ? "page" : undefined}
109
+ isSelected={isCurrentPage}
110
+ >
111
+ {page}
112
+ </StyledPaginationButton>
113
+ );
114
+ })}
115
+
116
+ <StyledPaginationButton
117
+ onClick={handleNext}
118
+ disabled={!canGoNext}
119
+ aria-label="Next page"
120
+ >
121
+ <Icon name="chevron-right-outline" />
122
+ </StyledPaginationButton>
123
+ </StyledPageButtons>
124
+ </StyledPaginationControls>
125
+ )}
126
+ </StyledPaginationContainer>
127
+ );
128
+ };
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Tailwind/CSS implementation of the Pagination component.
3
+ * Uses Seeds pagination CSS classes from pagination.css.
4
+ */
5
+
6
+ import { Select } from "@sproutsocial/seeds-react-select";
7
+ import { Icon } from "@sproutsocial/seeds-react-icon";
8
+ import { Text } from "@sproutsocial/seeds-react-text";
9
+ import type { PaginationProps } from "./PaginationTypes";
10
+ import { usePaginationModel } from "./usePaginationModel";
11
+
12
+ // Utility for merging class names properly
13
+ function cn(
14
+ ...inputs: (string | undefined | null | false | Record<string, boolean>)[]
15
+ ): string {
16
+ const classes: string[] = [];
17
+
18
+ for (const input of inputs) {
19
+ if (!input) continue;
20
+
21
+ if (typeof input === "string") {
22
+ classes.push(input);
23
+ } else if (typeof input === "object") {
24
+ for (const [key, value] of Object.entries(input)) {
25
+ if (value) {
26
+ classes.push(key);
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ return classes.join(" ");
33
+ }
34
+
35
+ export const PaginationTailwind = (props: PaginationProps) => {
36
+ const { pageSize, totalRows, onPageChange, className } = props;
37
+ const {
38
+ displayType,
39
+ pageSizeOptions,
40
+ tableCountFunction,
41
+ totalPages,
42
+ currentPage,
43
+ startRow,
44
+ endRow,
45
+ canGoPrevious,
46
+ canGoNext,
47
+ handlePrevious,
48
+ handleNext,
49
+ handlePageSizeChange,
50
+ pageNumbers,
51
+ } = usePaginationModel(props);
52
+
53
+ if (totalPages <= 1 && !pageSizeOptions) {
54
+ // No pagination needed if only one page and no page size options
55
+ return null;
56
+ }
57
+
58
+ return (
59
+ <div className={cn("seeds-pagination-container", className)}>
60
+ <Text className={cn("seeds-pagination-info")}>
61
+ {tableCountFunction({
62
+ startNum: startRow,
63
+ endNum: endRow,
64
+ totalRowCount: totalRows,
65
+ })}
66
+ </Text>
67
+ {/* Page size selector */}
68
+ {pageSizeOptions && displayType === "dropdown" && (
69
+ <div className={cn("seeds-pagination-page-size-selector")}>
70
+ <Select
71
+ id="page-size-select"
72
+ name="page-size"
73
+ value={String(pageSize)}
74
+ onChange={handlePageSizeChange}
75
+ ariaLabel="Rows per page"
76
+ size="small"
77
+ >
78
+ {pageSizeOptions.map((size) => (
79
+ <option key={size} value={size}>
80
+ {size} per page
81
+ </option>
82
+ ))}
83
+ </Select>
84
+ </div>
85
+ )}
86
+
87
+ {/* TODO: Add inline page size selector for displayType === 'inline' */}
88
+
89
+ {/* Page navigation controls */}
90
+ {totalPages > 1 && (
91
+ <div className={cn("seeds-pagination-controls")}>
92
+ <div className={cn("seeds-pagination-page-buttons")}>
93
+ <button
94
+ type="button"
95
+ className={cn("seeds-pagination-button")}
96
+ onClick={handlePrevious}
97
+ disabled={!canGoPrevious}
98
+ aria-label="Previous page"
99
+ >
100
+ <Icon name="chevron-left-outline" />
101
+ </button>
102
+
103
+ {pageNumbers.map((pageNum, index) => {
104
+ if (pageNum === "...") {
105
+ return (
106
+ <button
107
+ type="button"
108
+ key={`ellipsis-${index}`}
109
+ className={cn("seeds-pagination-button")}
110
+ disabled
111
+ aria-label="More pages"
112
+ >
113
+ <Icon name="ellipsis-horizontal-outline" />
114
+ </button>
115
+ );
116
+ }
117
+
118
+ const page = pageNum as number;
119
+ const isCurrentPage = page === currentPage;
120
+
121
+ return (
122
+ <button
123
+ type="button"
124
+ key={page}
125
+ className={cn("seeds-pagination-button", {
126
+ "seeds-pagination-button-selected": isCurrentPage,
127
+ })}
128
+ onClick={() => onPageChange(page - 1)}
129
+ disabled={isCurrentPage}
130
+ aria-label={`Page ${page}`}
131
+ aria-current={isCurrentPage ? "page" : undefined}
132
+ >
133
+ {page}
134
+ </button>
135
+ );
136
+ })}
137
+
138
+ <button
139
+ type="button"
140
+ className={cn("seeds-pagination-button")}
141
+ onClick={handleNext}
142
+ disabled={!canGoNext}
143
+ aria-label="Next page"
144
+ >
145
+ <Icon name="chevron-right-outline" />
146
+ </button>
147
+ </div>
148
+ </div>
149
+ )}
150
+ </div>
151
+ );
152
+ };
@@ -44,6 +44,25 @@ export type TypeAllowedPageSizeOptions =
44
44
  * />
45
45
  * ```
46
46
  */
47
+ /**
48
+ * Props for the Pagination component.
49
+ */
50
+ export interface PaginationProps {
51
+ paginationConfig: TypePaginationConfig;
52
+ currentPageIndex: number;
53
+ pageSize: number;
54
+ totalRows: number;
55
+ onPageChange: (pageIndex: number) => void;
56
+ onPageSizeChange: (pageSize: number) => void;
57
+ /**
58
+ * Optional class name applied to the root container. Lets consumers layer
59
+ * Tailwind utility overrides (e.g. `mx-200`, `p-300`) on top of the component
60
+ * styles. Because `className` is not a styled-system prop, providing it keeps
61
+ * the component on the Tailwind rendering path.
62
+ */
63
+ className?: string;
64
+ }
65
+
47
66
  export interface TypePaginationConfig {
48
67
  /**
49
68
  * Initial number of rows per page.