@boxcustodia/library 2.0.0-alpha.22 → 2.0.0-alpha.23
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/dist/components/calendar/calendar.cjs.js +1 -1
- package/dist/components/calendar/calendar.es.js +43 -44
- package/dist/components/date-picker/date-input.cjs.js +1 -1
- package/dist/components/date-picker/date-input.es.js +160 -140
- package/dist/components/pagination/pagination.cjs.js +1 -1
- package/dist/components/pagination/pagination.es.js +37 -35
- package/dist/components/scroll-area/scroll-area.cjs.js +1 -1
- package/dist/components/scroll-area/scroll-area.es.js +4 -4
- package/dist/components/select/select.cjs.js +1 -1
- package/dist/components/select/select.es.js +94 -90
- package/dist/hooks/use-action/use-action.cjs.js +1 -0
- package/dist/hooks/use-action/use-action.es.js +41 -0
- package/dist/hooks/use-pagination/use-pagination.cjs.js +1 -1
- package/dist/hooks/use-pagination/use-pagination.es.js +77 -32
- package/dist/hooks/use-range-pagination/use-range-pagination.cjs.js +1 -1
- package/dist/hooks/use-range-pagination/use-range-pagination.es.js +8 -5
- package/dist/hooks/use-selection/use-selection.cjs.js +1 -1
- package/dist/hooks/use-selection/use-selection.es.js +95 -33
- package/dist/hooks/use-session-storage/use-session-storage.cjs.js +1 -0
- package/dist/hooks/use-session-storage/use-session-storage.es.js +57 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +61 -63
- package/dist/src/components/select/select.d.ts +9 -2
- package/dist/src/hooks/index.d.ts +2 -3
- package/dist/src/hooks/internal/index.d.ts +1 -0
- package/dist/src/hooks/internal/serializer.d.ts +4 -0
- package/dist/src/hooks/use-action/index.d.ts +1 -0
- package/dist/src/hooks/use-action/use-action.d.ts +22 -0
- package/dist/src/hooks/use-local-storage/use-local-storage.d.ts +2 -4
- package/dist/src/hooks/use-pagination/use-pagination.d.ts +47 -32
- package/dist/src/hooks/use-range-pagination/use-range-pagination.d.ts +16 -10
- package/dist/src/hooks/use-selection/use-selection.d.ts +39 -45
- package/dist/src/hooks/use-session-storage/index.d.ts +1 -0
- package/dist/src/hooks/use-session-storage/use-session-storage.d.ts +11 -0
- package/package.json +1 -1
- package/src/components/calendar/calendar.tsx +10 -8
- package/src/components/combobox/combobox.stories.tsx +16 -0
- package/src/components/date-picker/date-input.tsx +23 -2
- package/src/components/form/form.tsx +3 -2
- package/src/components/pagination/pagination.tsx +5 -3
- package/src/components/scroll-area/scroll-area.tsx +2 -2
- package/src/components/select/select.tsx +14 -3
- package/src/hooks/index.ts +2 -3
- package/src/hooks/internal/index.ts +1 -0
- package/src/hooks/internal/serializer.ts +4 -0
- package/src/hooks/use-action/index.ts +1 -0
- package/src/hooks/{use-mutation/use-mutation.stories.tsx → use-action/use-action.stories.tsx} +34 -34
- package/src/hooks/{use-mutation/use-mutation.test.ts → use-action/use-action.test.ts} +53 -53
- package/src/hooks/{use-mutation/use-mutation.ts → use-action/use-action.ts} +20 -20
- package/src/hooks/use-click-outside/use-click-outside.stories.tsx +0 -1
- package/src/hooks/use-clipboard/use-clipboard.stories.tsx +0 -1
- package/src/hooks/use-document-title/use-document-title.stories.tsx +0 -1
- package/src/hooks/use-is-visible/use-is-visible.test.tsx +1 -1
- package/src/hooks/use-local-storage/use-local-storage.stories.tsx +0 -1
- package/src/hooks/use-local-storage/use-local-storage.ts +2 -5
- package/src/hooks/use-media-query/use-media-query.stories.tsx +0 -1
- package/src/hooks/use-pagination/use-pagination.stories.tsx +720 -57
- package/src/hooks/use-pagination/use-pagination.test.tsx +560 -48
- package/src/hooks/use-pagination/use-pagination.ts +266 -0
- package/src/hooks/use-prevent-page-close/use-prevent-page-close.stories.tsx +0 -1
- package/src/hooks/use-range-pagination/use-range-pagination.test.tsx +2 -2
- package/src/hooks/use-range-pagination/use-range-pagination.tsx +24 -21
- package/src/hooks/use-selection/use-selection.stories.tsx +339 -84
- package/src/hooks/use-selection/use-selection.test.tsx +417 -2
- package/src/hooks/use-selection/use-selection.ts +212 -102
- package/src/hooks/use-session-storage/index.ts +1 -0
- package/src/hooks/use-session-storage/use-session-storage.stories.tsx +122 -0
- package/src/hooks/use-session-storage/use-session-storage.test.ts +164 -0
- package/src/hooks/use-session-storage/use-session-storage.ts +115 -0
- package/dist/hooks/use-async/use-async.cjs.js +0 -1
- package/dist/hooks/use-async/use-async.es.js +0 -57
- package/dist/hooks/use-focus-trap/scope-tab.cjs.js +0 -1
- package/dist/hooks/use-focus-trap/scope-tab.es.js +0 -21
- package/dist/hooks/use-focus-trap/tabbable.cjs.js +0 -1
- package/dist/hooks/use-focus-trap/tabbable.es.js +0 -38
- package/dist/hooks/use-focus-trap/use-focus-trap.cjs.js +0 -1
- package/dist/hooks/use-focus-trap/use-focus-trap.es.js +0 -34
- package/dist/hooks/use-mutation/use-mutation.cjs.js +0 -1
- package/dist/hooks/use-mutation/use-mutation.es.js +0 -41
- package/dist/src/hooks/use-async/index.d.ts +0 -1
- package/dist/src/hooks/use-async/use-async.d.ts +0 -21
- package/dist/src/hooks/use-focus-trap/index.d.ts +0 -1
- package/dist/src/hooks/use-focus-trap/scope-tab.d.ts +0 -1
- package/dist/src/hooks/use-focus-trap/tabbable.d.ts +0 -4
- package/dist/src/hooks/use-focus-trap/use-focus-trap.d.ts +0 -1
- package/dist/src/hooks/use-mutation/index.d.ts +0 -1
- package/dist/src/hooks/use-mutation/use-mutation.d.ts +0 -22
- package/dist/src/hooks/use-mutation/use-mutation.test.d.ts +0 -1
- package/src/hooks/use-async/index.ts +0 -1
- package/src/hooks/use-async/use-async.stories.tsx +0 -272
- package/src/hooks/use-async/use-async.test.ts +0 -397
- package/src/hooks/use-async/use-async.ts +0 -135
- package/src/hooks/use-focus-trap/index.ts +0 -1
- package/src/hooks/use-focus-trap/scope-tab.ts +0 -38
- package/src/hooks/use-focus-trap/tabbable.ts +0 -70
- package/src/hooks/use-focus-trap/use-focus-trap.stories.tsx +0 -37
- package/src/hooks/use-focus-trap/use-focus-trap.test.ts +0 -355
- package/src/hooks/use-focus-trap/use-focus-trap.ts +0 -78
- package/src/hooks/use-mutation/index.ts +0 -1
- package/src/hooks/use-pagination/use-pagination.tsx +0 -84
- /package/dist/src/hooks/{use-async/use-async.test.d.ts → use-action/use-action.test.d.ts} +0 -0
- /package/dist/src/hooks/{use-focus-trap/use-focus-trap.test.d.ts → use-session-storage/use-session-storage.test.d.ts} +0 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type MutableRefObject,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { useLatestRef } from "../internal/use-latest-ref";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Interfaces
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export interface UsePaginationProps {
|
|
15
|
+
/** Total number of items. Required. */
|
|
16
|
+
totalItems: number;
|
|
17
|
+
/** Controlled page size. Pair with defaultPageSize / onPageSizeChange. */
|
|
18
|
+
pageSize?: number;
|
|
19
|
+
/** Initial page size (uncontrolled). @default 10 */
|
|
20
|
+
defaultPageSize?: number;
|
|
21
|
+
/** Fired when page size changes. Stable via useLatestRef. */
|
|
22
|
+
onPageSizeChange?: (size: number) => void;
|
|
23
|
+
/** Controlled current page. */
|
|
24
|
+
page?: number;
|
|
25
|
+
/** Initial page (uncontrolled). @default 1 */
|
|
26
|
+
defaultPage?: number;
|
|
27
|
+
/** Fired when page changes. Stable via useLatestRef. */
|
|
28
|
+
onPageChange?: (page: number) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Fired once with BOTH resolved values whenever page or pageSize changes.
|
|
31
|
+
* Ideal as a single data-fetching trigger — no need to wire a `useEffect` on
|
|
32
|
+
* both `page` and `pageSize`. NOT called on mount. Stable via useLatestRef.
|
|
33
|
+
*/
|
|
34
|
+
onPaginationChange?: (state: { page: number; pageSize: number }) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UsePaginationActions {
|
|
38
|
+
/** Go to the next page. No-op on last page. Stable reference. */
|
|
39
|
+
next: () => void;
|
|
40
|
+
/** Go to the previous page. No-op on first page. Stable reference. */
|
|
41
|
+
prev: () => void;
|
|
42
|
+
/** Jump to the first page. Stable reference. */
|
|
43
|
+
firstPage: () => void;
|
|
44
|
+
/** Jump to the last page. Stable reference. */
|
|
45
|
+
lastPage: () => void;
|
|
46
|
+
/** Jump to a specific page, clamped to [1, pageCount]. Stable reference. */
|
|
47
|
+
goTo: (page: number) => void;
|
|
48
|
+
/** Change the page size. Resets to page 1. Stable reference. */
|
|
49
|
+
setPageSize: (size: number) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface UsePaginationReturn extends UsePaginationActions {
|
|
53
|
+
/** Current active page (1-indexed). */
|
|
54
|
+
page: number;
|
|
55
|
+
/** Current page size. */
|
|
56
|
+
pageSize: number;
|
|
57
|
+
/** Total number of pages. 0 when there are no items. */
|
|
58
|
+
pageCount: number;
|
|
59
|
+
/** Whether the current page is the first. */
|
|
60
|
+
isFirstPage: boolean;
|
|
61
|
+
/** Whether the current page is the last. */
|
|
62
|
+
isLastPage: boolean;
|
|
63
|
+
/** Whether there is a previous page. */
|
|
64
|
+
hasPrevPage: boolean;
|
|
65
|
+
/** Whether there is a next page. */
|
|
66
|
+
hasNextPage: boolean;
|
|
67
|
+
/** 1-based item range for the current page. */
|
|
68
|
+
range: { start: number; end: number };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Constants
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
76
|
+
const DEFAULT_PAGE = 1;
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Pure helpers (no React)
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
function coercePageSize(
|
|
83
|
+
size: number,
|
|
84
|
+
warnedRef?: MutableRefObject<boolean>,
|
|
85
|
+
): number {
|
|
86
|
+
if (size <= 0 || Number.isNaN(size)) {
|
|
87
|
+
if (process.env.NODE_ENV !== "production") {
|
|
88
|
+
if (!warnedRef || !warnedRef.current) {
|
|
89
|
+
if (warnedRef) warnedRef.current = true;
|
|
90
|
+
console.warn(
|
|
91
|
+
`[usePagination] pageSize must be a positive number, received ${size}. Coercing to 1.`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
return Math.floor(size);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function computePageCount(totalItems: number, pageSize: number): number {
|
|
101
|
+
if (totalItems <= 0) return 0;
|
|
102
|
+
return Math.ceil(totalItems / pageSize);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function clampPage(page: number, pageCount: number): number {
|
|
106
|
+
if (pageCount <= 0) return 1;
|
|
107
|
+
if (Number.isNaN(page)) return 1;
|
|
108
|
+
return Math.min(Math.max(1, Math.floor(page)), pageCount);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Hook
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
export function usePagination({
|
|
116
|
+
totalItems,
|
|
117
|
+
pageSize: pageSizeProp,
|
|
118
|
+
defaultPageSize = DEFAULT_PAGE_SIZE,
|
|
119
|
+
onPageSizeChange,
|
|
120
|
+
page: pageProp,
|
|
121
|
+
defaultPage = DEFAULT_PAGE,
|
|
122
|
+
onPageChange,
|
|
123
|
+
onPaginationChange,
|
|
124
|
+
}: UsePaginationProps): UsePaginationReturn {
|
|
125
|
+
// Internal state (uncontrolled path)
|
|
126
|
+
const [pageState, setPageState] = useState(defaultPage);
|
|
127
|
+
const [pageSizeState, setPageSizeState] = useState(defaultPageSize);
|
|
128
|
+
|
|
129
|
+
// Warn once per instance when pageSize is invalid
|
|
130
|
+
const warnedRef = useRef(false);
|
|
131
|
+
|
|
132
|
+
// Resolve controlled-vs-uncontrolled each render
|
|
133
|
+
const pageSize = coercePageSize(pageSizeProp ?? pageSizeState, warnedRef);
|
|
134
|
+
const pageCount = computePageCount(totalItems, pageSize);
|
|
135
|
+
const page = clampPage(pageProp ?? pageState, pageCount);
|
|
136
|
+
|
|
137
|
+
// Refs so memoized actions read live values without stale closures
|
|
138
|
+
const pageRef = useLatestRef(page);
|
|
139
|
+
const pageSizeRef = useLatestRef(pageSize);
|
|
140
|
+
const pageCountRef = useLatestRef(pageCount);
|
|
141
|
+
const onPageChangeRef = useLatestRef(onPageChange);
|
|
142
|
+
const onPageSizeChangeRef = useLatestRef(onPageSizeChange);
|
|
143
|
+
const onPaginationChangeRef = useLatestRef(onPaginationChange);
|
|
144
|
+
|
|
145
|
+
// Fire callbacks on real transitions, never on mount
|
|
146
|
+
const mountedRef = useRef(false);
|
|
147
|
+
|
|
148
|
+
// Track last-emitted page/pageSize to detect real changes.
|
|
149
|
+
// We watch pageState (not the resolved page) so controlled mode also fires the callback.
|
|
150
|
+
// The callback receives the resolved/clamped value.
|
|
151
|
+
const lastEmittedPageRef = useRef<number | null>(null);
|
|
152
|
+
const lastEmittedPageSizeRef = useRef<number | null>(null);
|
|
153
|
+
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!mountedRef.current) return;
|
|
156
|
+
// Compute what page the current pageState would resolve to (for callback)
|
|
157
|
+
const resolved = clampPage(pageState, pageCountRef.current);
|
|
158
|
+
if (lastEmittedPageRef.current === resolved) return;
|
|
159
|
+
lastEmittedPageRef.current = resolved;
|
|
160
|
+
onPageChangeRef.current?.(resolved);
|
|
161
|
+
}, [pageState]);
|
|
162
|
+
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (!mountedRef.current) return;
|
|
165
|
+
const resolved = coercePageSize(pageSizeState);
|
|
166
|
+
if (lastEmittedPageSizeRef.current === resolved) return;
|
|
167
|
+
lastEmittedPageSizeRef.current = resolved;
|
|
168
|
+
onPageSizeChangeRef.current?.(resolved);
|
|
169
|
+
}, [pageSizeState]);
|
|
170
|
+
|
|
171
|
+
// Unified change callback. Keyed on the RESOLVED render-time values (not the
|
|
172
|
+
// internal state) so it fires exactly once per logical transition in both
|
|
173
|
+
// controlled and uncontrolled modes: when setPageSize batches a page+pageSize
|
|
174
|
+
// change into a single render, this effect sees both final values together.
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (!mountedRef.current) return;
|
|
177
|
+
onPaginationChangeRef.current?.({ page, pageSize });
|
|
178
|
+
}, [page, pageSize]);
|
|
179
|
+
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
mountedRef.current = true;
|
|
182
|
+
}, []);
|
|
183
|
+
|
|
184
|
+
// Actions built once — setState identities are stable → memo computes once
|
|
185
|
+
const actions = useMemo<UsePaginationActions>(() => {
|
|
186
|
+
const applyPage = (target: number) => {
|
|
187
|
+
const clamped = clampPage(target, pageCountRef.current);
|
|
188
|
+
if (clamped === pageRef.current) return; // no-op for same page
|
|
189
|
+
setPageState(clamped);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
goTo: (target) => {
|
|
194
|
+
if (pageCountRef.current <= 0) return;
|
|
195
|
+
applyPage(target);
|
|
196
|
+
},
|
|
197
|
+
next: () => {
|
|
198
|
+
if (pageRef.current >= pageCountRef.current) return;
|
|
199
|
+
applyPage(pageRef.current + 1);
|
|
200
|
+
},
|
|
201
|
+
prev: () => {
|
|
202
|
+
if (pageRef.current <= 1) return;
|
|
203
|
+
applyPage(pageRef.current - 1);
|
|
204
|
+
},
|
|
205
|
+
firstPage: () => {
|
|
206
|
+
if (pageCountRef.current <= 0) return;
|
|
207
|
+
applyPage(1);
|
|
208
|
+
},
|
|
209
|
+
lastPage: () => {
|
|
210
|
+
if (pageCountRef.current <= 0) return;
|
|
211
|
+
applyPage(pageCountRef.current);
|
|
212
|
+
},
|
|
213
|
+
setPageSize: (size) => {
|
|
214
|
+
const coerced = coercePageSize(size);
|
|
215
|
+
// No-op if same size
|
|
216
|
+
if (coerced === pageSizeRef.current) return;
|
|
217
|
+
|
|
218
|
+
// setPageSize mutates TWO values: pageSize and (reset) page. Both
|
|
219
|
+
// callbacks MUST fire synchronously here, inside the same event-handler
|
|
220
|
+
// tick, so a fully-controlled consumer's two setState calls batch into a
|
|
221
|
+
// single render. If pageSize fired from its useEffect while page fired
|
|
222
|
+
// synchronously, the controlled values would land in separate renders and
|
|
223
|
+
// a consumer effect keyed on [page, pageSize] would run twice (once per
|
|
224
|
+
// value), causing a duplicate fetch.
|
|
225
|
+
//
|
|
226
|
+
// We stamp the lastEmitted refs first so the useEffect transitions below
|
|
227
|
+
// recognize these values as already-emitted and skip re-firing.
|
|
228
|
+
lastEmittedPageSizeRef.current = coerced;
|
|
229
|
+
setPageSizeState(coerced);
|
|
230
|
+
onPageSizeChangeRef.current?.(coerced);
|
|
231
|
+
|
|
232
|
+
// Reset to page 1. In controlled mode the internal pageState may already
|
|
233
|
+
// be 1 (its default), so setPageState(1) alone would be a no-op and the
|
|
234
|
+
// page useEffect would never fire — onPageChange would be silently
|
|
235
|
+
// skipped. Fire it directly when the resolved page is not already 1.
|
|
236
|
+
if (pageRef.current !== 1) {
|
|
237
|
+
lastEmittedPageRef.current = 1;
|
|
238
|
+
onPageChangeRef.current?.(1);
|
|
239
|
+
}
|
|
240
|
+
setPageState(1);
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}, [setPageState, setPageSizeState]);
|
|
244
|
+
|
|
245
|
+
const isFirstPage = pageCount <= 0 ? true : page <= 1;
|
|
246
|
+
const isLastPage = pageCount <= 0 ? true : page >= pageCount;
|
|
247
|
+
const range =
|
|
248
|
+
totalItems <= 0
|
|
249
|
+
? { start: 0, end: 0 }
|
|
250
|
+
: {
|
|
251
|
+
start: (page - 1) * pageSize + 1,
|
|
252
|
+
end: Math.min(page * pageSize, totalItems),
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
page,
|
|
257
|
+
pageSize,
|
|
258
|
+
pageCount,
|
|
259
|
+
isFirstPage,
|
|
260
|
+
isLastPage,
|
|
261
|
+
hasPrevPage: !isFirstPage,
|
|
262
|
+
hasNextPage: !isLastPage,
|
|
263
|
+
range,
|
|
264
|
+
...actions,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
@@ -34,7 +34,7 @@ describe("usePagination hook", () => {
|
|
|
34
34
|
useRangePagination({
|
|
35
35
|
totalItems: 10,
|
|
36
36
|
pageSize: 1,
|
|
37
|
-
|
|
37
|
+
defaultPage: 8,
|
|
38
38
|
}),
|
|
39
39
|
);
|
|
40
40
|
|
|
@@ -46,7 +46,7 @@ describe("usePagination hook", () => {
|
|
|
46
46
|
useRangePagination({
|
|
47
47
|
totalItems: 10,
|
|
48
48
|
pageSize: 1,
|
|
49
|
-
|
|
49
|
+
defaultPage: 5,
|
|
50
50
|
}),
|
|
51
51
|
);
|
|
52
52
|
|
|
@@ -3,24 +3,24 @@ import { useMemo } from "react";
|
|
|
3
3
|
const range = (start: number, end: number): number[] =>
|
|
4
4
|
Array.from({ length: end - start }, (_, i) => i + start);
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { type UsePaginationProps, usePagination } from "../../hooks";
|
|
7
7
|
|
|
8
8
|
export const DOTS = "...";
|
|
9
9
|
|
|
10
|
-
export type
|
|
10
|
+
export type UseRangePaginationProps = {
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Visible siblings on each side of the current page.
|
|
13
13
|
*/
|
|
14
14
|
siblingCount?: number;
|
|
15
|
-
} &
|
|
15
|
+
} & UsePaginationProps;
|
|
16
16
|
|
|
17
17
|
export const useRangePagination = ({
|
|
18
18
|
siblingCount = 1,
|
|
19
19
|
...paginationProps
|
|
20
|
-
}:
|
|
20
|
+
}: UseRangePaginationProps) => {
|
|
21
21
|
const pagination = usePagination(paginationProps);
|
|
22
|
-
const {
|
|
23
|
-
const { totalItems
|
|
22
|
+
const { page, pageCount, pageSize } = pagination;
|
|
23
|
+
const { totalItems } = paginationProps;
|
|
24
24
|
|
|
25
25
|
const generateRange = () => {
|
|
26
26
|
const DOTS_THRESHOLD = 2;
|
|
@@ -28,33 +28,36 @@ export const useRangePagination = ({
|
|
|
28
28
|
const ITEMS_UNTIL_DOTS = DOTS_THRESHOLD + EXTRA_ITEMS * siblingCount;
|
|
29
29
|
const FIRST_PAGE = 1;
|
|
30
30
|
|
|
31
|
-
//
|
|
32
|
-
if (ITEMS_UNTIL_DOTS >=
|
|
33
|
-
return range(FIRST_PAGE,
|
|
31
|
+
// Not enough pages to show dots
|
|
32
|
+
if (ITEMS_UNTIL_DOTS >= pageCount) {
|
|
33
|
+
return range(FIRST_PAGE, pageCount + 1);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
//
|
|
37
|
-
const leftSiblingIndex = Math.max(
|
|
38
|
-
const rightSiblingIndex = Math.min(
|
|
36
|
+
// Left and right sibling indices
|
|
37
|
+
const leftSiblingIndex = Math.max(page - siblingCount, 1);
|
|
38
|
+
const rightSiblingIndex = Math.min(page + siblingCount, pageCount);
|
|
39
39
|
|
|
40
40
|
const shouldShowLeftDots = leftSiblingIndex > DOTS_THRESHOLD;
|
|
41
|
-
const shouldShowRightDots = rightSiblingIndex <
|
|
41
|
+
const shouldShowRightDots = rightSiblingIndex < pageCount - DOTS_THRESHOLD;
|
|
42
42
|
|
|
43
|
-
//
|
|
43
|
+
// Show dots on both sides
|
|
44
44
|
if (shouldShowLeftDots && shouldShowRightDots) {
|
|
45
45
|
const middleElements = range(leftSiblingIndex, rightSiblingIndex + 1);
|
|
46
|
-
return [FIRST_PAGE, DOTS, ...middleElements, DOTS,
|
|
46
|
+
return [FIRST_PAGE, DOTS, ...middleElements, DOTS, pageCount];
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
//
|
|
49
|
+
// Show dots on the right only
|
|
50
50
|
if (shouldShowRightDots) {
|
|
51
51
|
const leftElements = range(FIRST_PAGE, ITEMS_UNTIL_DOTS + 1);
|
|
52
|
-
return [...leftElements, DOTS,
|
|
52
|
+
return [...leftElements, DOTS, pageCount];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
//
|
|
55
|
+
// Show dots on the left only
|
|
56
56
|
if (shouldShowLeftDots) {
|
|
57
|
-
const rightElements = range(
|
|
57
|
+
const rightElements = range(
|
|
58
|
+
pageCount - ITEMS_UNTIL_DOTS + 1,
|
|
59
|
+
pageCount + 1,
|
|
60
|
+
);
|
|
58
61
|
return [FIRST_PAGE, DOTS, ...rightElements];
|
|
59
62
|
}
|
|
60
63
|
};
|
|
@@ -62,7 +65,7 @@ export const useRangePagination = ({
|
|
|
62
65
|
const paginationRange = useMemo(generateRange, [
|
|
63
66
|
totalItems,
|
|
64
67
|
pageSize,
|
|
65
|
-
|
|
68
|
+
page,
|
|
66
69
|
siblingCount,
|
|
67
70
|
]);
|
|
68
71
|
|