@codefast/ui 0.3.16-canary.1 → 0.3.16-canary.3
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/CHANGELOG.md +30 -0
- package/README.md +1 -1
- package/dist/components/accordion.mjs +2 -2
- package/dist/components/alert-dialog.d.mts +1 -1
- package/dist/components/alert-dialog.mjs +4 -4
- package/dist/components/alert.d.mts +3 -13
- package/dist/components/alert.mjs +3 -23
- package/dist/components/badge.d.mts +3 -15
- package/dist/components/badge.mjs +2 -44
- package/dist/components/breadcrumb.mjs +1 -1
- package/dist/components/button-group.d.mts +3 -13
- package/dist/components/button-group.mjs +3 -24
- package/dist/components/button.d.mts +3 -25
- package/dist/components/button.mjs +2 -72
- package/dist/components/calendar.mjs +2 -1
- package/dist/components/carousel.d.mts +1 -2
- package/dist/components/chart.d.mts +2 -4
- package/dist/components/checkbox.mjs +2 -2
- package/dist/components/context-menu.mjs +2 -2
- package/dist/components/dialog.d.mts +1 -1
- package/dist/components/dialog.mjs +4 -4
- package/dist/components/drawer.d.mts +1 -1
- package/dist/components/drawer.mjs +2 -2
- package/dist/components/dropdown-menu.mjs +2 -2
- package/dist/components/empty.d.mts +3 -13
- package/dist/components/empty.mjs +3 -18
- package/dist/components/field.d.mts +3 -14
- package/dist/components/field.mjs +3 -32
- package/dist/components/form.d.mts +2 -4
- package/dist/components/hover-card.mjs +1 -1
- package/dist/components/input-group.d.mts +4 -31
- package/dist/components/input-group.mjs +3 -90
- package/dist/components/input-number.mjs +4 -4
- package/dist/components/input-otp.mjs +2 -2
- package/dist/components/input.mjs +1 -1
- package/dist/components/item.d.mts +4 -29
- package/dist/components/item.mjs +3 -56
- package/dist/components/menubar.mjs +2 -2
- package/dist/components/native-select.mjs +1 -1
- package/dist/components/navigation-menu.d.mts +1 -6
- package/dist/components/navigation-menu.mjs +8 -15
- package/dist/components/pagination.d.mts +1 -1
- package/dist/components/pagination.mjs +1 -1
- package/dist/components/popover.mjs +1 -1
- package/dist/components/progress-circle.d.mts +3 -47
- package/dist/components/progress-circle.mjs +2 -47
- package/dist/components/progress.mjs +1 -1
- package/dist/components/radio-group.mjs +1 -1
- package/dist/components/radio.mjs +1 -1
- package/dist/components/scroll-area.d.mts +3 -19
- package/dist/components/scroll-area.mjs +4 -61
- package/dist/components/select.d.mts +1 -1
- package/dist/components/select.mjs +3 -3
- package/dist/components/separator.d.mts +3 -18
- package/dist/components/separator.mjs +3 -23
- package/dist/components/sheet.d.mts +6 -18
- package/dist/components/sheet.mjs +6 -49
- package/dist/components/sidebar.d.mts +4 -19
- package/dist/components/sidebar.mjs +10 -46
- package/dist/components/skeleton.mjs +1 -1
- package/dist/components/slider.mjs +1 -1
- package/dist/components/spinner.mjs +1 -1
- package/dist/components/switch.mjs +2 -2
- package/dist/components/table.mjs +1 -1
- package/dist/components/tabs.mjs +1 -1
- package/dist/components/textarea.mjs +1 -1
- package/dist/components/toggle-group.d.mts +3 -2
- package/dist/components/toggle-group.mjs +1 -1
- package/dist/components/toggle.d.mts +2 -21
- package/dist/components/toggle.mjs +2 -39
- package/dist/components/tooltip.mjs +1 -1
- package/dist/index.d.mts +31 -16
- package/dist/index.mjs +30 -15
- package/dist/lib/utils.d.mts +1 -12
- package/dist/lib/utils.mjs +1 -9
- package/dist/primitives/checkbox-group.d.mts +1 -2
- package/dist/primitives/input-number.d.mts +1 -2
- package/dist/primitives/input.d.mts +1 -2
- package/dist/primitives/progress-circle.d.mts +1 -2
- package/dist/variants/alert.d.mts +18 -0
- package/dist/variants/alert.mjs +25 -0
- package/dist/variants/badge.d.mts +20 -0
- package/dist/variants/badge.mjs +46 -0
- package/dist/variants/button-group.d.mts +18 -0
- package/dist/variants/button-group.mjs +26 -0
- package/dist/variants/button.d.mts +30 -0
- package/dist/variants/button.mjs +76 -0
- package/dist/variants/empty.d.mts +18 -0
- package/dist/variants/empty.mjs +20 -0
- package/dist/variants/field.d.mts +19 -0
- package/dist/variants/field.mjs +34 -0
- package/dist/variants/input-group.d.mts +43 -0
- package/dist/variants/input-group.mjs +93 -0
- package/dist/variants/item.d.mts +37 -0
- package/dist/variants/item.mjs +60 -0
- package/dist/variants/navigation-menu.d.mts +13 -0
- package/dist/variants/navigation-menu.mjs +12 -0
- package/dist/variants/progress-circle.d.mts +52 -0
- package/dist/variants/progress-circle.mjs +49 -0
- package/dist/variants/scroll-area.d.mts +24 -0
- package/dist/variants/scroll-area.mjs +63 -0
- package/dist/variants/separator.d.mts +23 -0
- package/dist/variants/separator.mjs +25 -0
- package/dist/variants/sheet.d.mts +20 -0
- package/dist/variants/sheet.mjs +50 -0
- package/dist/variants/sidebar.d.mts +23 -0
- package/dist/variants/sidebar.mjs +42 -0
- package/dist/variants/toggle.d.mts +23 -0
- package/dist/variants/toggle.mjs +43 -0
- package/package.json +169 -21
- package/src/components/accordion.tsx +156 -0
- package/src/components/alert-dialog.tsx +314 -0
- package/src/components/alert.tsx +86 -0
- package/src/components/aspect-ratio.tsx +28 -0
- package/src/components/avatar.tsx +84 -0
- package/src/components/badge.tsx +38 -0
- package/src/components/breadcrumb.tsx +197 -0
- package/src/components/button-group.tsx +107 -0
- package/src/components/button.tsx +66 -0
- package/src/components/calendar.tsx +277 -0
- package/src/components/card.tsx +175 -0
- package/src/components/carousel.tsx +367 -0
- package/src/components/chart.tsx +587 -0
- package/src/components/checkbox-cards.tsx +92 -0
- package/src/components/checkbox-group.tsx +83 -0
- package/src/components/checkbox.tsx +65 -0
- package/src/components/collapsible.tsx +60 -0
- package/src/components/command.tsx +311 -0
- package/src/components/context-menu.tsx +489 -0
- package/src/components/dialog.tsx +295 -0
- package/src/components/drawer.tsx +271 -0
- package/src/components/dropdown-menu.tsx +498 -0
- package/src/components/empty.tsx +169 -0
- package/src/components/field.tsx +362 -0
- package/src/components/form.tsx +300 -0
- package/src/components/hover-card.tsx +116 -0
- package/src/components/input-group.tsx +224 -0
- package/src/components/input-number.tsx +161 -0
- package/src/components/input-otp.tsx +151 -0
- package/src/components/input-password.tsx +74 -0
- package/src/components/input-search.tsx +98 -0
- package/src/components/input.tsx +52 -0
- package/src/components/item.tsx +280 -0
- package/src/components/kbd.tsx +59 -0
- package/src/components/label.tsx +44 -0
- package/src/components/menubar.tsx +531 -0
- package/src/components/native-select.tsx +96 -0
- package/src/components/navigation-menu.tsx +295 -0
- package/src/components/pagination.tsx +204 -0
- package/src/components/popover.tsx +139 -0
- package/src/components/progress-circle.tsx +203 -0
- package/src/components/progress.tsx +54 -0
- package/src/components/radio-cards.tsx +85 -0
- package/src/components/radio-group.tsx +79 -0
- package/src/components/radio.tsx +61 -0
- package/src/components/resizable.tsx +99 -0
- package/src/components/scroll-area.tsx +115 -0
- package/src/components/select.tsx +319 -0
- package/src/components/separator.tsx +74 -0
- package/src/components/sheet.tsx +278 -0
- package/src/components/sidebar.tsx +1056 -0
- package/src/components/skeleton.tsx +37 -0
- package/src/components/slider.tsx +95 -0
- package/src/components/sonner.tsx +47 -0
- package/src/components/spinner.tsx +75 -0
- package/src/components/switch.tsx +66 -0
- package/src/components/table.tsx +200 -0
- package/src/components/tabs.tsx +128 -0
- package/src/components/textarea.tsx +49 -0
- package/src/components/toggle-group.tsx +141 -0
- package/src/components/toggle.tsx +39 -0
- package/src/components/tooltip.tsx +141 -0
- package/src/css/amber.css +59 -22
- package/src/css/blue.css +59 -22
- package/src/css/cyan.css +59 -22
- package/src/css/emerald.css +59 -22
- package/src/css/fuchsia.css +59 -22
- package/src/css/gray.css +59 -22
- package/src/css/green.css +59 -22
- package/src/css/indigo.css +59 -22
- package/src/css/lime.css +59 -22
- package/src/css/neutral.css +59 -22
- package/src/css/orange.css +59 -22
- package/src/css/pink.css +59 -22
- package/src/css/preset.css +32 -13
- package/src/css/purple.css +59 -22
- package/src/css/red.css +59 -22
- package/src/css/rose.css +59 -22
- package/src/css/sky.css +59 -22
- package/src/css/slate.css +59 -22
- package/src/css/stone.css +59 -22
- package/src/css/teal.css +59 -22
- package/src/css/violet.css +59 -22
- package/src/css/yellow.css +59 -22
- package/src/css/zinc.css +59 -22
- package/src/hooks/use-animated-value.ts +97 -0
- package/src/hooks/use-copy-to-clipboard.ts +63 -0
- package/src/hooks/use-is-mobile.ts +27 -0
- package/src/hooks/use-media-query.ts +71 -0
- package/src/hooks/use-mutation-observer.ts +54 -0
- package/src/hooks/use-pagination.ts +166 -0
- package/src/index.ts +720 -0
- package/src/lib/utils.ts +5 -0
- package/src/primitives/checkbox-group.tsx +360 -0
- package/src/primitives/input-number.tsx +1013 -0
- package/src/primitives/input.tsx +243 -0
- package/src/primitives/progress-circle.tsx +537 -0
- package/src/variants/alert.ts +45 -0
- package/src/variants/badge.ts +66 -0
- package/src/variants/button-group.ts +49 -0
- package/src/variants/button.ts +93 -0
- package/src/variants/empty.ts +43 -0
- package/src/variants/field.ts +50 -0
- package/src/variants/input-group.ts +132 -0
- package/src/variants/item.ts +90 -0
- package/src/variants/navigation-menu.ts +32 -0
- package/src/variants/progress-circle.ts +47 -0
- package/src/variants/scroll-area.ts +79 -0
- package/src/variants/separator.ts +41 -0
- package/src/variants/sheet.ts +70 -0
- package/src/variants/sidebar.ts +61 -0
- package/src/variants/toggle.ts +59 -0
- package/dist/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/clsx.d.mts +0 -6
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Provide clipboard copy capability with a transient copied state.
|
|
7
|
+
*
|
|
8
|
+
* Internally uses the Clipboard API when available and sets a temporary
|
|
9
|
+
* `isCopied` flag for UI feedback. A custom callback may be invoked upon copy.
|
|
10
|
+
*
|
|
11
|
+
* @param options - Configuration options.
|
|
12
|
+
* - onCopy: Callback invoked after a successful copy.
|
|
13
|
+
* - timeout: Duration in milliseconds to keep `isCopied` true. Defaults to 2000.
|
|
14
|
+
* @returns An object with a `copyToClipboard` function and an `isCopied` flag.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* const { copyToClipboard, isCopied } = useCopyToClipboard({ timeout: 1500 });
|
|
19
|
+
* <button onClick={() => copyToClipboard("Hello")}>{isCopied ? "Copied" : "Copy"}</button>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @since 0.3.16-canary.0
|
|
23
|
+
*/
|
|
24
|
+
export function useCopyToClipboard({
|
|
25
|
+
onCopy,
|
|
26
|
+
timeout = 2000,
|
|
27
|
+
}: { onCopy?: () => void; timeout?: number } = {}): {
|
|
28
|
+
copyToClipboard: (value: string) => Promise<void>;
|
|
29
|
+
isCopied: boolean;
|
|
30
|
+
} {
|
|
31
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
32
|
+
|
|
33
|
+
const copyToClipboard = async (value: string): Promise<void> => {
|
|
34
|
+
if (
|
|
35
|
+
typeof window === "undefined" ||
|
|
36
|
+
!("clipboard" in navigator) ||
|
|
37
|
+
typeof navigator.clipboard.writeText !== "function"
|
|
38
|
+
) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!value) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await navigator.clipboard.writeText(value);
|
|
48
|
+
setIsCopied(true);
|
|
49
|
+
|
|
50
|
+
if (onCopy) {
|
|
51
|
+
onCopy();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
setTimeout(() => {
|
|
55
|
+
setIsCopied(false);
|
|
56
|
+
}, timeout);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(error);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return { copyToClipboard, isCopied };
|
|
63
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMediaQuery } from "#/hooks/use-media-query";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Determine whether the current viewport should be treated as mobile.
|
|
7
|
+
*
|
|
8
|
+
* Uses {@link useMediaQuery} to evaluate a max-width media query derived from the
|
|
9
|
+
* provided breakpoint. By default, widths below 768px are considered mobile.
|
|
10
|
+
*
|
|
11
|
+
* @param mobileBreakpoint - Pixel width used as the mobile breakpoint. Values strictly
|
|
12
|
+
* less than this breakpoint are treated as mobile. Defaults to 768.
|
|
13
|
+
* @returns true when the viewport width is less than the given breakpoint; otherwise false.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* const isMobile = useIsMobile();
|
|
18
|
+
* if (isMobile) {
|
|
19
|
+
* // Render compact layout
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @since 0.3.16-canary.0
|
|
24
|
+
*/
|
|
25
|
+
export function useIsMobile(mobileBreakpoint = 768): boolean {
|
|
26
|
+
return useMediaQuery(`(max-width: ${(mobileBreakpoint - 1).toString()}px)`);
|
|
27
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Event handler for MediaQueryList changes.
|
|
7
|
+
*
|
|
8
|
+
* @param event - Media query change event providing the updated match status.
|
|
9
|
+
*/
|
|
10
|
+
type MediaQueryChangeHandler = (event: MediaQueryListEvent) => void;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Subscribe to a CSS media query and receive its match state.
|
|
14
|
+
*
|
|
15
|
+
* Evaluates the query immediately (when supported) and updates on changes
|
|
16
|
+
* via an event listener.
|
|
17
|
+
*
|
|
18
|
+
* @param query - A valid media query string (e.g., "(max-width: 768px)").
|
|
19
|
+
* @returns true when the media query currently matches; otherwise false.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const isNarrow = useMediaQuery("(max-width: 768px)");
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @since 0.3.16-canary.0
|
|
27
|
+
*/
|
|
28
|
+
export function useMediaQuery(query: string): boolean {
|
|
29
|
+
/**
|
|
30
|
+
* State to store whether the query matches.
|
|
31
|
+
* The initial value is calculated based on the current window state.
|
|
32
|
+
*/
|
|
33
|
+
const [matches, setMatches] = useState<boolean>(() => {
|
|
34
|
+
// Ensure initial state matches current media query status
|
|
35
|
+
if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
|
36
|
+
return window.matchMedia(query).matches;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return false;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
// Only run in a browser environment where matchMedia is available
|
|
44
|
+
if (typeof window === "undefined") {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* MediaQueryList to evaluate and observe the provided query.
|
|
50
|
+
*/
|
|
51
|
+
const mediaQueryList = window.matchMedia(query);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Update state when the media query status changes.
|
|
55
|
+
*/
|
|
56
|
+
const onChange: MediaQueryChangeHandler = (event): void => {
|
|
57
|
+
setMatches(event.matches);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
mediaQueryList.addEventListener("change", onChange);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Remove the event listener on unmount or when the query changes.
|
|
64
|
+
*/
|
|
65
|
+
return (): void => {
|
|
66
|
+
mediaQueryList.removeEventListener("change", onChange);
|
|
67
|
+
};
|
|
68
|
+
}, [query]);
|
|
69
|
+
|
|
70
|
+
return matches;
|
|
71
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { RefObject } from "react";
|
|
4
|
+
|
|
5
|
+
import { useEffect } from "react";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default options for MutationObserver observing common mutation types.
|
|
9
|
+
*/
|
|
10
|
+
const defaultOptions: MutationObserverInit = {
|
|
11
|
+
attributes: true,
|
|
12
|
+
characterData: true,
|
|
13
|
+
childList: true,
|
|
14
|
+
subtree: true,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Observe DOM mutations on a referenced element and invoke a callback.
|
|
19
|
+
*
|
|
20
|
+
* Attaches a MutationObserver to the provided element reference with the given
|
|
21
|
+
* options and calls the callback whenever mutations occur.
|
|
22
|
+
*
|
|
23
|
+
* @param ref - Ref to the target HTMLElement to observe.
|
|
24
|
+
* @param callback - Mutation callback invoked with observed records.
|
|
25
|
+
* @param options - Observer configuration. Defaults watch attributes, characterData, childList, subtree.
|
|
26
|
+
* @returns void
|
|
27
|
+
*
|
|
28
|
+
* @see [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
|
|
29
|
+
*
|
|
30
|
+
* @since 0.3.16-canary.0
|
|
31
|
+
*/
|
|
32
|
+
export function useMutationObserver(
|
|
33
|
+
ref: RefObject<HTMLElement | null>,
|
|
34
|
+
callback: MutationCallback,
|
|
35
|
+
options: MutationObserverInit = defaultOptions,
|
|
36
|
+
): void {
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
// Abort if ref is not attached
|
|
39
|
+
if (!ref.current) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Create observer
|
|
44
|
+
const observer = new MutationObserver(callback);
|
|
45
|
+
|
|
46
|
+
// Observe with provided options
|
|
47
|
+
observer.observe(ref.current, options);
|
|
48
|
+
|
|
49
|
+
// Cleanup on unmount or when deps change
|
|
50
|
+
return (): void => {
|
|
51
|
+
observer.disconnect();
|
|
52
|
+
};
|
|
53
|
+
}, [ref, callback, options]);
|
|
54
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @since 0.3.16-canary.0
|
|
7
|
+
*/
|
|
8
|
+
export interface UsePaginationProps {
|
|
9
|
+
/**
|
|
10
|
+
* Current active page number.
|
|
11
|
+
*/
|
|
12
|
+
currentPage: number;
|
|
13
|
+
/**
|
|
14
|
+
* Number of results displayed per page.
|
|
15
|
+
*/
|
|
16
|
+
resultsPerPage: number;
|
|
17
|
+
/**
|
|
18
|
+
* Number of sibling pages to show on each side of the current page.
|
|
19
|
+
* Defaults to 1.
|
|
20
|
+
*/
|
|
21
|
+
siblingPagesCount?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Total number of results across all pages.
|
|
24
|
+
*/
|
|
25
|
+
totalResults: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Ellipsis marker used to collapse ranges in pagination output.
|
|
30
|
+
*
|
|
31
|
+
* @since 0.3.16-canary.0
|
|
32
|
+
*/
|
|
33
|
+
export const ELLIPSIS = "•••";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate a numeric range from start to end inclusive.
|
|
37
|
+
*
|
|
38
|
+
* @param start - Starting number (inclusive).
|
|
39
|
+
* @param end - Ending number (inclusive).
|
|
40
|
+
* @returns Array of numbers from start to end.
|
|
41
|
+
*/
|
|
42
|
+
const createRange = (start: number, end: number): Array<number> => {
|
|
43
|
+
const length = end - start + 1;
|
|
44
|
+
|
|
45
|
+
return Array.from({ length }, (_, index) => start + index);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Compute a pagination structure for result sets.
|
|
50
|
+
*
|
|
51
|
+
* Returns a mixed array of page numbers and the `ELLIPSIS` marker representing
|
|
52
|
+
* collapsed ranges. The shape adapts to the total pages and the requested
|
|
53
|
+
* sibling window around the current page.
|
|
54
|
+
*
|
|
55
|
+
* @param props - Pagination options. See {@link UsePaginationProps}.
|
|
56
|
+
* @returns Array of page numbers and `ELLIPSIS` representing the pagination model.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* const paginationRange = usePagination({
|
|
61
|
+
* currentPage: 3,
|
|
62
|
+
* resultsPerPage: 10,
|
|
63
|
+
* siblingPagesCount: 1,
|
|
64
|
+
* totalResults: 100
|
|
65
|
+
* });
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @since 0.3.16-canary.0
|
|
69
|
+
*/
|
|
70
|
+
export function usePagination({
|
|
71
|
+
currentPage,
|
|
72
|
+
resultsPerPage,
|
|
73
|
+
siblingPagesCount = 1,
|
|
74
|
+
totalResults,
|
|
75
|
+
}: UsePaginationProps): Array<number | string> {
|
|
76
|
+
return useMemo<Array<number | string>>(() => {
|
|
77
|
+
// Total pages derived from results and page size
|
|
78
|
+
const totalPages = Math.ceil(totalResults / Math.floor(resultsPerPage));
|
|
79
|
+
|
|
80
|
+
// Return an empty array if there are no pages to display
|
|
81
|
+
if (totalPages <= 0) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Total visible items including: first, last, current, siblings, and two ellipses.
|
|
87
|
+
*/
|
|
88
|
+
const visiblePageNumbers = siblingPagesCount + 5;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Case 1: If the number of pages is less than or equal to the visible page
|
|
92
|
+
* numbers, return an array of all page numbers.
|
|
93
|
+
*/
|
|
94
|
+
if (visiblePageNumbers >= totalPages) {
|
|
95
|
+
return createRange(1, totalPages);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Left sibling boundary (min 1).
|
|
100
|
+
*/
|
|
101
|
+
const leftSiblingIndex = Math.max(currentPage - siblingPagesCount, 1);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Right sibling boundary (max totalPages).
|
|
105
|
+
*/
|
|
106
|
+
const rightSiblingIndex = Math.min(currentPage + siblingPagesCount, totalPages);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Whether a left-side ellipsis is needed.
|
|
110
|
+
*/
|
|
111
|
+
const shouldShowLeftEllipsis = leftSiblingIndex > 2;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Whether a right-side ellipsis is needed.
|
|
115
|
+
*/
|
|
116
|
+
const shouldShowRightEllipsis = rightSiblingIndex < totalPages - 2;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* First page number (always 1).
|
|
120
|
+
*/
|
|
121
|
+
const firstPage = 1;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Last page number (equals totalPages).
|
|
125
|
+
*/
|
|
126
|
+
const lastPage = totalPages;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Case 2: No left ellipsis, but right ellipsis is necessary.
|
|
130
|
+
* This occurs when the current page is close to the start of the
|
|
131
|
+
* pagination range.
|
|
132
|
+
*/
|
|
133
|
+
if (!shouldShowLeftEllipsis && shouldShowRightEllipsis) {
|
|
134
|
+
// Range starting from page 1 through the right sibling window
|
|
135
|
+
const leftRange = createRange(1, 3 + 2 * siblingPagesCount);
|
|
136
|
+
|
|
137
|
+
return [...leftRange, ELLIPSIS, lastPage];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Case 3: No right ellipsis, but left ellipsis is necessary.
|
|
142
|
+
* This occurs when the current page is close to the end of the pagination
|
|
143
|
+
* range.
|
|
144
|
+
*/
|
|
145
|
+
if (shouldShowLeftEllipsis && !shouldShowRightEllipsis) {
|
|
146
|
+
// Range ending at last page through the left sibling window
|
|
147
|
+
const rightRange = createRange(totalPages - (3 + 2 * siblingPagesCount) + 1, totalPages);
|
|
148
|
+
|
|
149
|
+
return [firstPage, ELLIPSIS, ...rightRange];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Case 4: Both left and right ellipsis are necessary.
|
|
154
|
+
* This occurs when the current page is far enough from both the start and
|
|
155
|
+
* end of the pagination range.
|
|
156
|
+
*/
|
|
157
|
+
if (shouldShowLeftEllipsis && shouldShowRightEllipsis) {
|
|
158
|
+
// Middle window from left to right siblings
|
|
159
|
+
const middleRange = createRange(leftSiblingIndex, rightSiblingIndex);
|
|
160
|
+
|
|
161
|
+
return [firstPage, ELLIPSIS, ...middleRange, ELLIPSIS, lastPage];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return [];
|
|
165
|
+
}, [totalResults, resultsPerPage, siblingPagesCount, currentPage]);
|
|
166
|
+
}
|