@izumisy-tailor/tailor-data-viewer 0.2.28 → 0.2.29

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@izumisy-tailor/tailor-data-viewer",
3
3
  "private": false,
4
- "version": "0.2.28",
4
+ "version": "0.2.29",
5
5
  "type": "module",
6
6
  "description": "Flexible data viewer component for Tailor Platform",
7
7
  "files": [
@@ -121,7 +121,7 @@ export function useCollection(
121
121
  // ---------------------------------------------------------------------------
122
122
  const [filters, setFiltersState] = useState<Filter[]>(initialFilters);
123
123
  const [sortStates, setSortStates] = useState<SortState[]>(initialSort);
124
- const [pageSize] = useState(initialPageSize);
124
+ const [pageSize, setPageSizeState] = useState(initialPageSize);
125
125
  const [cursor, setCursor] = useState<string | null>(null);
126
126
  const [paginationDirection, setPaginationDirection] = useState<
127
127
  "forward" | "backward"
@@ -233,6 +233,13 @@ export function useCollection(
233
233
  setCurrentPage(1);
234
234
  }, []);
235
235
 
236
+ const setPageSize = useCallback((size: number) => {
237
+ setPageSizeState(size);
238
+ setCursor(null);
239
+ setPaginationDirection("forward");
240
+ setCurrentPage(1);
241
+ }, []);
242
+
236
243
  const setPageInfo = useCallback((pageInfo: PageInfo) => {
237
244
  setCurrentPageInfo(pageInfo);
238
245
  }, []);
@@ -324,6 +331,7 @@ export function useCollection(
324
331
  setSort,
325
332
  clearSort,
326
333
  pageSize,
334
+ setPageSize,
327
335
  cursor,
328
336
  paginationDirection,
329
337
  nextPage,
@@ -210,4 +210,73 @@ describe("Pagination", () => {
210
210
  expect(() => render(<Pagination />)).toThrow();
211
211
  console.error = globalThis.console.error;
212
212
  });
213
+
214
+ // ===========================================================================
215
+ // Page size switcher
216
+ // ===========================================================================
217
+
218
+ it("does not render page-size selector when pageSizeOptions is not provided", () => {
219
+ render(
220
+ <TestProviders>
221
+ <Pagination />
222
+ </TestProviders>,
223
+ );
224
+
225
+ expect(screen.queryByLabelText("Rows per page")).not.toBeInTheDocument();
226
+ });
227
+
228
+ it("renders page-size selector when pageSizeOptions is provided", () => {
229
+ render(
230
+ <TestProviders collection={{ pageSize: 20 }}>
231
+ <Pagination pageSizeOptions={[10, 20, 50]} />
232
+ </TestProviders>,
233
+ );
234
+
235
+ const select = screen.getByLabelText("Rows per page");
236
+ expect(select).toBeInTheDocument();
237
+ expect(select).toHaveValue("20");
238
+
239
+ const options = select.querySelectorAll("option");
240
+ expect(options).toHaveLength(3);
241
+ expect(options[0]).toHaveValue("10");
242
+ expect(options[1]).toHaveValue("20");
243
+ expect(options[2]).toHaveValue("50");
244
+ });
245
+
246
+ it("calls setPageSize when page-size option is changed", () => {
247
+ const setPageSize = vi.fn();
248
+ render(
249
+ <TestProviders collection={{ pageSize: 20, setPageSize }}>
250
+ <Pagination pageSizeOptions={[10, 20, 50]} />
251
+ </TestProviders>,
252
+ );
253
+
254
+ fireEvent.change(screen.getByLabelText("Rows per page"), {
255
+ target: { value: "50" },
256
+ });
257
+ expect(setPageSize).toHaveBeenCalledWith(50);
258
+ });
259
+
260
+ it("supports custom rowsPerPage label", () => {
261
+ render(
262
+ <TestProviders collection={{ pageSize: 20 }}>
263
+ <Pagination
264
+ pageSizeOptions={[10, 20, 50]}
265
+ labels={{ rowsPerPage: "表示件数" }}
266
+ />
267
+ </TestProviders>,
268
+ );
269
+
270
+ expect(screen.getByLabelText("表示件数")).toBeInTheDocument();
271
+ });
272
+
273
+ it("does not render page-size selector when pageSizeOptions is empty", () => {
274
+ render(
275
+ <TestProviders>
276
+ <Pagination pageSizeOptions={[]} />
277
+ </TestProviders>,
278
+ );
279
+
280
+ expect(screen.queryByLabelText("Rows per page")).not.toBeInTheDocument();
281
+ });
213
282
  });
@@ -86,11 +86,23 @@ export interface PaginationLabels {
86
86
  previous?: string;
87
87
  next?: string;
88
88
  last?: string;
89
+ /** Label displayed next to the page-size selector (default: "Rows per page"). */
90
+ rowsPerPage?: string;
89
91
  }
90
92
 
91
93
  export interface PaginationProps {
92
94
  /** Aria-labels for pagination buttons (defaults to English). */
93
95
  labels?: PaginationLabels;
96
+ /**
97
+ * Available page-size options shown in a dropdown selector.
98
+ * When provided, a page-size switcher is rendered.
99
+ *
100
+ * @example
101
+ * ```tsx
102
+ * <Pagination pageSizeOptions={[10, 20, 50, 100]} />
103
+ * ```
104
+ */
105
+ pageSizeOptions?: number[];
94
106
  }
95
107
 
96
108
  const btnClass =
@@ -113,7 +125,7 @@ const btnClass =
113
125
  * </DataTable.Provider>
114
126
  * ```
115
127
  */
116
- export function Pagination({ labels }: PaginationProps = {}) {
128
+ export function Pagination({ labels, pageSizeOptions }: PaginationProps = {}) {
117
129
  const { pageInfo } = useDataTableContext();
118
130
  const {
119
131
  nextPage,
@@ -124,15 +136,40 @@ export function Pagination({ labels }: PaginationProps = {}) {
124
136
  totalPages,
125
137
  goToFirstPage,
126
138
  goToLastPage,
139
+ pageSize,
140
+ setPageSize,
127
141
  } = useCollectionContext();
128
142
 
129
143
  const firstLabel = labels?.first ?? "First page";
130
144
  const previousLabel = labels?.previous ?? "Previous page";
131
145
  const nextLabel = labels?.next ?? "Next page";
132
146
  const lastLabel = labels?.last ?? "Last page";
147
+ const rowsPerPageLabel = labels?.rowsPerPage ?? "Rows per page";
133
148
 
134
149
  return (
135
150
  <div className="flex items-center justify-end gap-2 py-2">
151
+ {pageSizeOptions && pageSizeOptions.length > 0 && (
152
+ <div className="flex items-center gap-1.5">
153
+ <label
154
+ htmlFor="pagination-page-size"
155
+ className="text-sm text-muted-foreground whitespace-nowrap"
156
+ >
157
+ {rowsPerPageLabel}
158
+ </label>
159
+ <select
160
+ id="pagination-page-size"
161
+ className="h-8 rounded-md border bg-transparent px-2 text-sm"
162
+ value={pageSize}
163
+ onChange={(e) => setPageSize(Number(e.target.value))}
164
+ >
165
+ {pageSizeOptions.map((size) => (
166
+ <option key={size} value={size}>
167
+ {size}
168
+ </option>
169
+ ))}
170
+ </select>
171
+ </div>
172
+ )}
136
173
  {totalPages !== null && (
137
174
  <span className="text-sm text-muted-foreground tabular-nums">
138
175
  {currentPage} / {totalPages}
@@ -463,6 +463,8 @@ export interface UseCollectionReturn<
463
463
  // Pagination operations
464
464
  /** Number of items per page */
465
465
  pageSize: number;
466
+ /** Update the number of items per page (resets pagination to page 1) */
467
+ setPageSize: (size: number) => void;
466
468
  /** Current cursor position */
467
469
  cursor: string | null;
468
470
  /** Current pagination direction */
@@ -59,6 +59,7 @@ export function createMockCollectionContext(
59
59
  setSort: vi.fn(),
60
60
  clearSort: vi.fn(),
61
61
  pageSize: 20,
62
+ setPageSize: vi.fn(),
62
63
  cursor: null,
63
64
  paginationDirection: "forward",
64
65
  nextPage: vi.fn(),