@ttt-productions/ui-core 0.11.9 → 0.11.11
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/react/components/list-pagination.d.ts +34 -0
- package/dist/react/components/list-pagination.d.ts.map +1 -0
- package/dist/react/components/list-pagination.js +34 -0
- package/dist/react/components/list-pagination.js.map +1 -0
- package/dist/react/components/scroll-to-top-button.d.ts +14 -4
- package/dist/react/components/scroll-to-top-button.d.ts.map +1 -1
- package/dist/react/components/scroll-to-top-button.js +2 -2
- package/dist/react/components/scroll-to-top-button.js.map +1 -1
- package/dist/react/hooks/use-cursor-page.d.ts +32 -0
- package/dist/react/hooks/use-cursor-page.d.ts.map +1 -0
- package/dist/react/hooks/use-cursor-page.js +50 -0
- package/dist/react/hooks/use-cursor-page.js.map +1 -0
- package/dist/react/hooks/use-paged-list.d.ts +60 -0
- package/dist/react/hooks/use-paged-list.d.ts.map +1 -0
- package/dist/react/hooks/use-paged-list.js +49 -0
- package/dist/react/hooks/use-paged-list.js.map +1 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +5 -0
- package/dist/react/index.js.map +1 -1
- package/package.json +3 -3
- package/src/react/components/list-pagination.tsx +79 -0
- package/src/react/components/scroll-to-top-button.tsx +15 -4
- package/src/react/hooks/use-cursor-page.ts +72 -0
- package/src/react/hooks/use-paged-list.ts +106 -0
- package/src/react/index.ts +11 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ListPaginationState } from "../hooks/use-paged-list.js";
|
|
2
|
+
export interface ListPaginationProps {
|
|
3
|
+
/**
|
|
4
|
+
* Page state from `usePagedList` (known total), `useCursorPage` (unknown
|
|
5
|
+
* total), or any caller-owned state of the same shape.
|
|
6
|
+
*/
|
|
7
|
+
pagination: ListPaginationState;
|
|
8
|
+
/**
|
|
9
|
+
* Disables BOTH controls while the next page is in flight, without hiding the
|
|
10
|
+
* row. Server-paged surfaces pass their query's `isFetching`; a slice-paged
|
|
11
|
+
* list has nothing to wait for and omits it.
|
|
12
|
+
*/
|
|
13
|
+
busy?: boolean;
|
|
14
|
+
/** Optional extra classes. Merged with the component's own classes. */
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The ONE Previous / counter / Next control for paginated lists, in both
|
|
19
|
+
* flavors. Pair it with the hook that owns the page state (`usePagedList` for a
|
|
20
|
+
* client-side slice, `useCursorPage` — or the caller's own query state — for a
|
|
21
|
+
* cursor feed); this component owns only the controls, so every paginated list
|
|
22
|
+
* reads and behaves the same (ENG-002, FRONTEND-001).
|
|
23
|
+
*
|
|
24
|
+
* The counter follows the flavor: a known `totalPages` reads "2 of 5", an
|
|
25
|
+
* omitted one reads "Page 2". Everything else is identical in both flavors —
|
|
26
|
+
* which is exactly why there is one component and not two.
|
|
27
|
+
*
|
|
28
|
+
* It renders NOTHING when neither direction is available, so no call site
|
|
29
|
+
* repeats a `totalPages > 1` / `page > 1 || hasMore` guard. Edge state is real
|
|
30
|
+
* `disabled` on the buttons (announced, and unclickable), and the counter is a
|
|
31
|
+
* `status` live region so a page change is announced to screen readers.
|
|
32
|
+
*/
|
|
33
|
+
export declare function ListPagination({ pagination, busy, className }: ListPaginationProps): import("react").JSX.Element | null;
|
|
34
|
+
//# sourceMappingURL=list-pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-pagination.d.ts","sourceRoot":"","sources":["../../../src/react/components/list-pagination.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,UAAU,EAAE,mBAAmB,CAAC;IAChC;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,IAAY,EAAE,SAAS,EAAE,EAAE,mBAAmB,sCAuC1F"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
4
|
+
import { Button } from "./button.js";
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
/**
|
|
7
|
+
* The ONE Previous / counter / Next control for paginated lists, in both
|
|
8
|
+
* flavors. Pair it with the hook that owns the page state (`usePagedList` for a
|
|
9
|
+
* client-side slice, `useCursorPage` — or the caller's own query state — for a
|
|
10
|
+
* cursor feed); this component owns only the controls, so every paginated list
|
|
11
|
+
* reads and behaves the same (ENG-002, FRONTEND-001).
|
|
12
|
+
*
|
|
13
|
+
* The counter follows the flavor: a known `totalPages` reads "2 of 5", an
|
|
14
|
+
* omitted one reads "Page 2". Everything else is identical in both flavors —
|
|
15
|
+
* which is exactly why there is one component and not two.
|
|
16
|
+
*
|
|
17
|
+
* It renders NOTHING when neither direction is available, so no call site
|
|
18
|
+
* repeats a `totalPages > 1` / `page > 1 || hasMore` guard. Edge state is real
|
|
19
|
+
* `disabled` on the buttons (announced, and unclickable), and the counter is a
|
|
20
|
+
* `status` live region so a page change is announced to screen readers.
|
|
21
|
+
*/
|
|
22
|
+
export function ListPagination({ pagination, busy = false, className }) {
|
|
23
|
+
const { currentPage, totalPages, canPreviousPage, canNextPage, goToPreviousPage, goToNextPage } = pagination;
|
|
24
|
+
// One visibility rule for both flavors. For a known total this is exactly
|
|
25
|
+
// `totalPages > 1`: page state is clamped into `1..totalPages`, so a
|
|
26
|
+
// single-page list has no reachable neighbour in either direction, and a
|
|
27
|
+
// multi-page list always has one. For a cursor feed it is the hand-rolled
|
|
28
|
+
// `page > 1 || hasMore`. `busy` deliberately does not hide the row — the
|
|
29
|
+
// controls grey out in place instead of vanishing mid-fetch.
|
|
30
|
+
if (!canPreviousPage && !canNextPage)
|
|
31
|
+
return null;
|
|
32
|
+
return (_jsxs("div", { className: cn("flex w-full items-center justify-between gap-4 mt-4", className), children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: goToPreviousPage, disabled: busy || !canPreviousPage, className: "h-11 min-w-11", children: [_jsx(ChevronLeft, { className: "icon-xs mr-1" }), "Previous"] }), _jsx("span", { className: "text-counter", role: "status", children: totalPages === undefined ? `Page ${currentPage}` : `${currentPage} of ${totalPages}` }), _jsxs(Button, { variant: "outline", size: "sm", onClick: goToNextPage, disabled: busy || !canNextPage, className: "h-11 min-w-11", children: ["Next", _jsx(ChevronRight, { className: "icon-xs ml-1" })] })] }));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=list-pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-pagination.js","sourceRoot":"","sources":["../../../src/react/components/list-pagination.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAmBxC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,cAAc,CAAC,EAAE,UAAU,EAAE,IAAI,GAAG,KAAK,EAAE,SAAS,EAAuB;IACzF,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,GAC7F,UAAU,CAAC;IAEb,0EAA0E;IAC1E,qEAAqE;IACrE,yEAAyE;IACzE,0EAA0E;IAC1E,yEAAyE;IACzE,6DAA6D;IAC7D,IAAI,CAAC,eAAe,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAElD,OAAO,CACL,eAAK,SAAS,EAAE,EAAE,CAAC,qDAAqD,EAAE,SAAS,CAAC,aAClF,MAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,IAAI,IAAI,CAAC,eAAe,EAClC,SAAS,EAAC,eAAe,aAEzB,KAAC,WAAW,IAAC,SAAS,EAAC,cAAc,GAAG,gBAEjC,EACT,eAAM,SAAS,EAAC,cAAc,EAAC,IAAI,EAAC,QAAQ,YACzC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,WAAW,EAAE,CAAC,CAAC,CAAC,GAAG,WAAW,OAAO,UAAU,EAAE,GAChF,EACP,MAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,IAAI,IAAI,CAAC,WAAW,EAC9B,SAAS,EAAC,eAAe,qBAGzB,KAAC,YAAY,IAAC,SAAS,EAAC,cAAc,GAAG,IAClC,IACL,CACP,CAAC;AACJ,CAAC","sourcesContent":["\"use client\";\n\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { Button } from \"./button.js\";\nimport { cn } from \"../../lib/utils.js\";\nimport type { ListPaginationState } from \"../hooks/use-paged-list.js\";\n\nexport interface ListPaginationProps {\n /**\n * Page state from `usePagedList` (known total), `useCursorPage` (unknown\n * total), or any caller-owned state of the same shape.\n */\n pagination: ListPaginationState;\n /**\n * Disables BOTH controls while the next page is in flight, without hiding the\n * row. Server-paged surfaces pass their query's `isFetching`; a slice-paged\n * list has nothing to wait for and omits it.\n */\n busy?: boolean;\n /** Optional extra classes. Merged with the component's own classes. */\n className?: string;\n}\n\n/**\n * The ONE Previous / counter / Next control for paginated lists, in both\n * flavors. Pair it with the hook that owns the page state (`usePagedList` for a\n * client-side slice, `useCursorPage` — or the caller's own query state — for a\n * cursor feed); this component owns only the controls, so every paginated list\n * reads and behaves the same (ENG-002, FRONTEND-001).\n *\n * The counter follows the flavor: a known `totalPages` reads \"2 of 5\", an\n * omitted one reads \"Page 2\". Everything else is identical in both flavors —\n * which is exactly why there is one component and not two.\n *\n * It renders NOTHING when neither direction is available, so no call site\n * repeats a `totalPages > 1` / `page > 1 || hasMore` guard. Edge state is real\n * `disabled` on the buttons (announced, and unclickable), and the counter is a\n * `status` live region so a page change is announced to screen readers.\n */\nexport function ListPagination({ pagination, busy = false, className }: ListPaginationProps) {\n const { currentPage, totalPages, canPreviousPage, canNextPage, goToPreviousPage, goToNextPage } =\n pagination;\n\n // One visibility rule for both flavors. For a known total this is exactly\n // `totalPages > 1`: page state is clamped into `1..totalPages`, so a\n // single-page list has no reachable neighbour in either direction, and a\n // multi-page list always has one. For a cursor feed it is the hand-rolled\n // `page > 1 || hasMore`. `busy` deliberately does not hide the row — the\n // controls grey out in place instead of vanishing mid-fetch.\n if (!canPreviousPage && !canNextPage) return null;\n\n return (\n <div className={cn(\"flex w-full items-center justify-between gap-4 mt-4\", className)}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={goToPreviousPage}\n disabled={busy || !canPreviousPage}\n className=\"h-11 min-w-11\"\n >\n <ChevronLeft className=\"icon-xs mr-1\" />\n Previous\n </Button>\n <span className=\"text-counter\" role=\"status\">\n {totalPages === undefined ? `Page ${currentPage}` : `${currentPage} of ${totalPages}`}\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={goToNextPage}\n disabled={busy || !canNextPage}\n className=\"h-11 min-w-11\"\n >\n Next\n <ChevronRight className=\"icon-xs ml-1\" />\n </Button>\n </div>\n );\n}\n"]}
|
|
@@ -6,9 +6,10 @@ import { type ButtonProps } from "./button.js";
|
|
|
6
6
|
* consumer can never route them through the passthrough channel: `aria-label`
|
|
7
7
|
* is set via {@link ScrollToTopButtonProps.ariaLabel}, extra classes via the
|
|
8
8
|
* merge-aware {@link ScrollToTopButtonProps.className}, content via
|
|
9
|
-
* {@link ScrollToTopButtonProps.icon}, and `onClick` is fixed to scroll-to-top
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* {@link ScrollToTopButtonProps.icon}, and `onClick` is fixed to scroll-to-top
|
|
10
|
+
* (its scroll animation configurable via
|
|
11
|
+
* {@link ScrollToTopButtonProps.scrollBehavior}). This mirrors how the sibling
|
|
12
|
+
* `Button` destructures its owned `className` out of the forwarded `...props`.
|
|
12
13
|
*/
|
|
13
14
|
type ScrollToTopButtonPassthrough = Omit<React.ComponentPropsWithoutRef<"button">, "className" | "onClick" | "aria-label" | "children">;
|
|
14
15
|
export interface ScrollToTopButtonProps extends ScrollToTopButtonPassthrough {
|
|
@@ -24,7 +25,16 @@ export interface ScrollToTopButtonProps extends ScrollToTopButtonPassthrough {
|
|
|
24
25
|
variant?: ButtonProps["variant"];
|
|
25
26
|
/** Button size. Forwarded to the underlying Button. Default "icon". */
|
|
26
27
|
size?: ButtonProps["size"];
|
|
28
|
+
/**
|
|
29
|
+
* Scroll behavior passed to `window.scrollTo`. Default "smooth". Pass "auto"
|
|
30
|
+
* to skip the smooth animation (e.g. for a reduced-motion mode). The package
|
|
31
|
+
* stays motion-policy agnostic: the consumer decides what to pass here — an
|
|
32
|
+
* explicit JS `behavior: "smooth"` is not overridden by a CSS
|
|
33
|
+
* `scroll-behavior` kill-switch, so an app-level reduced-motion mode must
|
|
34
|
+
* pass "auto" to actually stop the animation.
|
|
35
|
+
*/
|
|
36
|
+
scrollBehavior?: ScrollBehavior;
|
|
27
37
|
}
|
|
28
|
-
export declare function ScrollToTopButton({ threshold, ariaLabel, className, icon, variant, size, ...rest }: ScrollToTopButtonProps): React.JSX.Element;
|
|
38
|
+
export declare function ScrollToTopButton({ threshold, ariaLabel, className, icon, variant, size, scrollBehavior, ...rest }: ScrollToTopButtonProps): React.JSX.Element;
|
|
29
39
|
export {};
|
|
30
40
|
//# sourceMappingURL=scroll-to-top-button.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-to-top-button.d.ts","sourceRoot":"","sources":["../../../src/react/components/scroll-to-top-button.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAGvD
|
|
1
|
+
{"version":3,"file":"scroll-to-top-button.d.ts","sourceRoot":"","sources":["../../../src/react/components/scroll-to-top-button.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAGvD;;;;;;;;;;GAUG;AACH,KAAK,4BAA4B,GAAG,IAAI,CACtC,KAAK,CAAC,wBAAwB,CAAC,QAAQ,CAAC,EACxC,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,UAAU,CACpD,CAAC;AAEF,MAAM,WAAW,sBAAuB,SAAQ,4BAA4B;IAC1E,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,4FAA4F;IAC5F,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACjC,uEAAuE;IACvE,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,wBAAgB,iBAAiB,CAAC,EAChC,SAAe,EACf,SAA2B,EAC3B,SAAS,EACT,IAAI,EACJ,OAAO,EACP,IAAa,EACb,cAAyB,EACzB,GAAG,IAAI,EACR,EAAE,sBAAsB,qBA4BxB"}
|
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { ChevronUp } from "lucide-react";
|
|
5
5
|
import { Button } from "./button.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
-
export function ScrollToTopButton({ threshold = 400, ariaLabel = "Scroll to top", className, icon, variant, size = "icon", ...rest }) {
|
|
7
|
+
export function ScrollToTopButton({ threshold = 400, ariaLabel = "Scroll to top", className, icon, variant, size = "icon", scrollBehavior = "smooth", ...rest }) {
|
|
8
8
|
const [isVisible, setIsVisible] = React.useState(false);
|
|
9
9
|
React.useEffect(() => {
|
|
10
10
|
const toggle = () => setIsVisible(window.scrollY > threshold);
|
|
@@ -12,7 +12,7 @@ export function ScrollToTopButton({ threshold = 400, ariaLabel = "Scroll to top"
|
|
|
12
12
|
window.addEventListener("scroll", toggle, { passive: true });
|
|
13
13
|
return () => window.removeEventListener("scroll", toggle);
|
|
14
14
|
}, [threshold]);
|
|
15
|
-
const onClick = () => window.scrollTo({ top: 0, behavior:
|
|
15
|
+
const onClick = () => window.scrollTo({ top: 0, behavior: scrollBehavior });
|
|
16
16
|
return (_jsx(Button, { ...rest, variant: variant, size: size, onClick: onClick, "aria-label": ariaLabel, className: cn("fixed bottom-24 left-1/2 -translate-x-1/2 z-50 rounded-full shadow-lg transition-opacity duration-300", isVisible ? "opacity-100" : "opacity-0 pointer-events-none", className), children: icon ?? _jsx(ChevronUp, { className: "h-5 w-5" }) }));
|
|
17
17
|
}
|
|
18
18
|
//# sourceMappingURL=scroll-to-top-button.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-to-top-button.js","sourceRoot":"","sources":["../../../src/react/components/scroll-to-top-button.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,MAAM,EAAoB,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"scroll-to-top-button.js","sourceRoot":"","sources":["../../../src/react/components/scroll-to-top-button.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,MAAM,EAAoB,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AA0CxC,MAAM,UAAU,iBAAiB,CAAC,EAChC,SAAS,GAAG,GAAG,EACf,SAAS,GAAG,eAAe,EAC3B,SAAS,EACT,IAAI,EACJ,OAAO,EACP,IAAI,GAAG,MAAM,EACb,cAAc,GAAG,QAAQ,EACzB,GAAG,IAAI,EACgB;IACvB,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAExD,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;QAC9D,MAAM,EAAE,CAAC;QACT,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC;IAE5E,OAAO,CACL,KAAC,MAAM,OACD,IAAI,EACR,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,gBACJ,SAAS,EACrB,SAAS,EAAE,EAAE,CACX,uGAAuG,EACvG,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,+BAA+B,EAC3D,SAAS,CACV,YAEA,IAAI,IAAI,KAAC,SAAS,IAAC,SAAS,EAAC,SAAS,GAAG,GACnC,CACV,CAAC;AACJ,CAAC","sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronUp } from \"lucide-react\";\nimport { Button, type ButtonProps } from \"./button.js\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Standard button DOM props forwarded to the rendered element, minus the props\n * this component owns. Owned props are excluded (not just overridden) so a\n * consumer can never route them through the passthrough channel: `aria-label`\n * is set via {@link ScrollToTopButtonProps.ariaLabel}, extra classes via the\n * merge-aware {@link ScrollToTopButtonProps.className}, content via\n * {@link ScrollToTopButtonProps.icon}, and `onClick` is fixed to scroll-to-top\n * (its scroll animation configurable via\n * {@link ScrollToTopButtonProps.scrollBehavior}). This mirrors how the sibling\n * `Button` destructures its owned `className` out of the forwarded `...props`.\n */\ntype ScrollToTopButtonPassthrough = Omit<\n React.ComponentPropsWithoutRef<\"button\">,\n \"className\" | \"onClick\" | \"aria-label\" | \"children\"\n>;\n\nexport interface ScrollToTopButtonProps extends ScrollToTopButtonPassthrough {\n /** Scroll threshold (px) before the button appears. Default 400. */\n threshold?: number;\n /** Aria label. Default \"Scroll to top\". */\n ariaLabel?: string;\n /** Optional extra classes for the button. Merged with the component's own classes. */\n className?: string;\n /** Optional override for the icon. */\n icon?: React.ReactNode;\n /** Button variant. Forwarded to the underlying Button. Defaults to Button's own default. */\n variant?: ButtonProps[\"variant\"];\n /** Button size. Forwarded to the underlying Button. Default \"icon\". */\n size?: ButtonProps[\"size\"];\n /**\n * Scroll behavior passed to `window.scrollTo`. Default \"smooth\". Pass \"auto\"\n * to skip the smooth animation (e.g. for a reduced-motion mode). The package\n * stays motion-policy agnostic: the consumer decides what to pass here — an\n * explicit JS `behavior: \"smooth\"` is not overridden by a CSS\n * `scroll-behavior` kill-switch, so an app-level reduced-motion mode must\n * pass \"auto\" to actually stop the animation.\n */\n scrollBehavior?: ScrollBehavior;\n}\n\nexport function ScrollToTopButton({\n threshold = 400,\n ariaLabel = \"Scroll to top\",\n className,\n icon,\n variant,\n size = \"icon\",\n scrollBehavior = \"smooth\",\n ...rest\n}: ScrollToTopButtonProps) {\n const [isVisible, setIsVisible] = React.useState(false);\n\n React.useEffect(() => {\n const toggle = () => setIsVisible(window.scrollY > threshold);\n toggle();\n window.addEventListener(\"scroll\", toggle, { passive: true });\n return () => window.removeEventListener(\"scroll\", toggle);\n }, [threshold]);\n\n const onClick = () => window.scrollTo({ top: 0, behavior: scrollBehavior });\n\n return (\n <Button\n {...rest}\n variant={variant}\n size={size}\n onClick={onClick}\n aria-label={ariaLabel}\n className={cn(\n \"fixed bottom-24 left-1/2 -translate-x-1/2 z-50 rounded-full shadow-lg transition-opacity duration-300\",\n isVisible ? \"opacity-100\" : \"opacity-0 pointer-events-none\",\n className,\n )}\n >\n {icon ?? <ChevronUp className=\"h-5 w-5\" />}\n </Button>\n );\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ListPaginationState } from "./use-paged-list.js";
|
|
2
|
+
export interface CursorPage extends ListPaginationState {
|
|
3
|
+
/** Never known for cursor pagination — the counter reads "Page N". */
|
|
4
|
+
totalPages?: undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Return to page 1 without walking back. Call it whenever the underlying
|
|
7
|
+
* query's inputs change (a new filter, a new tag, a new sort) — page 4 of the
|
|
8
|
+
* old result set is not page 4 of the new one.
|
|
9
|
+
*/
|
|
10
|
+
reset: () => void;
|
|
11
|
+
}
|
|
12
|
+
export interface UseCursorPageOptions {
|
|
13
|
+
/** Called after the page actually changed. Same contract as `usePagedList`. */
|
|
14
|
+
onPageChange?: () => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Page state for a server-paged (cursor / `hasMore`) list: the 1-based page
|
|
18
|
+
* number plus guarded step controls, shaped as {@link ListPaginationState} so it
|
|
19
|
+
* drops straight into the ONE `ListPagination` control.
|
|
20
|
+
*
|
|
21
|
+
* The caller owns the data: it feeds `hasMore` from its query and passes
|
|
22
|
+
* `currentPage` back into that query. This hook owns only the page number — the
|
|
23
|
+
* piece each server-paged surface was otherwise re-declaring as a local
|
|
24
|
+
* `useState(1)` plus its own clamped increment/decrement (ENG-002, ENG-003).
|
|
25
|
+
*
|
|
26
|
+
* A surface whose data hook ALREADY owns its page state (it returns
|
|
27
|
+
* `page`/`hasNextPage`/`nextPage`/`prevPage` itself) does not need this hook —
|
|
28
|
+
* it maps those fields onto `ListPaginationState` directly. The hook exists for
|
|
29
|
+
* the surfaces that would otherwise hand-roll the state.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useCursorPage(hasMore: boolean, options?: UseCursorPageOptions): CursorPage;
|
|
32
|
+
//# sourceMappingURL=use-cursor-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-cursor-page.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-cursor-page.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,MAAM,WAAW,UAAW,SAAQ,mBAAmB;IACrD,sEAAsE;IACtE,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;OAIG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,oBAAyB,GACjC,UAAU,CAgCZ"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Page state for a server-paged (cursor / `hasMore`) list: the 1-based page
|
|
5
|
+
* number plus guarded step controls, shaped as {@link ListPaginationState} so it
|
|
6
|
+
* drops straight into the ONE `ListPagination` control.
|
|
7
|
+
*
|
|
8
|
+
* The caller owns the data: it feeds `hasMore` from its query and passes
|
|
9
|
+
* `currentPage` back into that query. This hook owns only the page number — the
|
|
10
|
+
* piece each server-paged surface was otherwise re-declaring as a local
|
|
11
|
+
* `useState(1)` plus its own clamped increment/decrement (ENG-002, ENG-003).
|
|
12
|
+
*
|
|
13
|
+
* A surface whose data hook ALREADY owns its page state (it returns
|
|
14
|
+
* `page`/`hasNextPage`/`nextPage`/`prevPage` itself) does not need this hook —
|
|
15
|
+
* it maps those fields onto `ListPaginationState` directly. The hook exists for
|
|
16
|
+
* the surfaces that would otherwise hand-roll the state.
|
|
17
|
+
*/
|
|
18
|
+
export function useCursorPage(hasMore, options = {}) {
|
|
19
|
+
const { onPageChange } = options;
|
|
20
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
21
|
+
// Guards live here, not at the call site: a click that cannot move the page
|
|
22
|
+
// must not fire `onPageChange` and must not push the page past the data.
|
|
23
|
+
const goToPreviousPage = useCallback(() => {
|
|
24
|
+
if (currentPage <= 1)
|
|
25
|
+
return;
|
|
26
|
+
setCurrentPage(currentPage - 1);
|
|
27
|
+
onPageChange?.();
|
|
28
|
+
}, [currentPage, onPageChange]);
|
|
29
|
+
const goToNextPage = useCallback(() => {
|
|
30
|
+
if (!hasMore)
|
|
31
|
+
return;
|
|
32
|
+
setCurrentPage(currentPage + 1);
|
|
33
|
+
onPageChange?.();
|
|
34
|
+
}, [currentPage, hasMore, onPageChange]);
|
|
35
|
+
const reset = useCallback(() => {
|
|
36
|
+
if (currentPage === 1)
|
|
37
|
+
return;
|
|
38
|
+
setCurrentPage(1);
|
|
39
|
+
onPageChange?.();
|
|
40
|
+
}, [currentPage, onPageChange]);
|
|
41
|
+
return {
|
|
42
|
+
currentPage,
|
|
43
|
+
canPreviousPage: currentPage > 1,
|
|
44
|
+
canNextPage: hasMore,
|
|
45
|
+
goToPreviousPage,
|
|
46
|
+
goToNextPage,
|
|
47
|
+
reset,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=use-cursor-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-cursor-page.js","sourceRoot":"","sources":["../../../src/react/hooks/use-cursor-page.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAmB9C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAgB,EAChB,UAAgC,EAAE;IAElC,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IACjC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAElD,4EAA4E;IAC5E,yEAAyE;IACzE,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE;QACxC,IAAI,WAAW,IAAI,CAAC;YAAE,OAAO;QAC7B,cAAc,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;QAChC,YAAY,EAAE,EAAE,CAAC;IACnB,CAAC,EAAE,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;IAEhC,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;QACpC,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,cAAc,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;QAChC,YAAY,EAAE,EAAE,CAAC;IACnB,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;IAEzC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,IAAI,WAAW,KAAK,CAAC;YAAE,OAAO;QAC9B,cAAc,CAAC,CAAC,CAAC,CAAC;QAClB,YAAY,EAAE,EAAE,CAAC;IACnB,CAAC,EAAE,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;IAEhC,OAAO;QACL,WAAW;QACX,eAAe,EAAE,WAAW,GAAG,CAAC;QAChC,WAAW,EAAE,OAAO;QACpB,gBAAgB;QAChB,YAAY;QACZ,KAAK;KACN,CAAC;AACJ,CAAC","sourcesContent":["\"use client\";\n\nimport { useCallback, useState } from \"react\";\nimport type { ListPaginationState } from \"./use-paged-list.js\";\n\nexport interface CursorPage extends ListPaginationState {\n /** Never known for cursor pagination — the counter reads \"Page N\". */\n totalPages?: undefined;\n /**\n * Return to page 1 without walking back. Call it whenever the underlying\n * query's inputs change (a new filter, a new tag, a new sort) — page 4 of the\n * old result set is not page 4 of the new one.\n */\n reset: () => void;\n}\n\nexport interface UseCursorPageOptions {\n /** Called after the page actually changed. Same contract as `usePagedList`. */\n onPageChange?: () => void;\n}\n\n/**\n * Page state for a server-paged (cursor / `hasMore`) list: the 1-based page\n * number plus guarded step controls, shaped as {@link ListPaginationState} so it\n * drops straight into the ONE `ListPagination` control.\n *\n * The caller owns the data: it feeds `hasMore` from its query and passes\n * `currentPage` back into that query. This hook owns only the page number — the\n * piece each server-paged surface was otherwise re-declaring as a local\n * `useState(1)` plus its own clamped increment/decrement (ENG-002, ENG-003).\n *\n * A surface whose data hook ALREADY owns its page state (it returns\n * `page`/`hasNextPage`/`nextPage`/`prevPage` itself) does not need this hook —\n * it maps those fields onto `ListPaginationState` directly. The hook exists for\n * the surfaces that would otherwise hand-roll the state.\n */\nexport function useCursorPage(\n hasMore: boolean,\n options: UseCursorPageOptions = {},\n): CursorPage {\n const { onPageChange } = options;\n const [currentPage, setCurrentPage] = useState(1);\n\n // Guards live here, not at the call site: a click that cannot move the page\n // must not fire `onPageChange` and must not push the page past the data.\n const goToPreviousPage = useCallback(() => {\n if (currentPage <= 1) return;\n setCurrentPage(currentPage - 1);\n onPageChange?.();\n }, [currentPage, onPageChange]);\n\n const goToNextPage = useCallback(() => {\n if (!hasMore) return;\n setCurrentPage(currentPage + 1);\n onPageChange?.();\n }, [currentPage, hasMore, onPageChange]);\n\n const reset = useCallback(() => {\n if (currentPage === 1) return;\n setCurrentPage(1);\n onPageChange?.();\n }, [currentPage, onPageChange]);\n\n return {\n currentPage,\n canPreviousPage: currentPage > 1,\n canNextPage: hasMore,\n goToPreviousPage,\n goToNextPage,\n reset,\n };\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The control state a paginated list exposes to its paginator UI, rendered by
|
|
3
|
+
* the ONE `ListPagination` component. Nothing else builds
|
|
4
|
+
* Previous / counter / Next controls.
|
|
5
|
+
*
|
|
6
|
+
* `totalPages` is the flavor discriminant, and the ONLY difference between the
|
|
7
|
+
* two flavors:
|
|
8
|
+
*
|
|
9
|
+
* - **Known total** (client-side slice pagination — {@link usePagedList}):
|
|
10
|
+
* `totalPages` is a number and the counter reads `"2 of 5"`.
|
|
11
|
+
* - **Unknown total** (server/cursor pagination — `useCursorPage`, or a
|
|
12
|
+
* caller's own query hook): `totalPages` is omitted and the counter reads
|
|
13
|
+
* `"Page 2"`.
|
|
14
|
+
*
|
|
15
|
+
* Both flavors share one visibility rule, one disabled-edge rule, one live
|
|
16
|
+
* region, and one set of classes because they share one component (ENG-002,
|
|
17
|
+
* FRONTEND-001).
|
|
18
|
+
*/
|
|
19
|
+
export interface ListPaginationState {
|
|
20
|
+
/** 1-based page currently on screen. */
|
|
21
|
+
currentPage: number;
|
|
22
|
+
/**
|
|
23
|
+
* Total page count when it is knowable — at least 1, even for an empty list
|
|
24
|
+
* (an empty list is one empty page). Omitted for cursor/`hasMore` pagination,
|
|
25
|
+
* where the total is unknown until the last page is reached.
|
|
26
|
+
*/
|
|
27
|
+
totalPages?: number;
|
|
28
|
+
canPreviousPage: boolean;
|
|
29
|
+
canNextPage: boolean;
|
|
30
|
+
goToPreviousPage: () => void;
|
|
31
|
+
goToNextPage: () => void;
|
|
32
|
+
}
|
|
33
|
+
export interface PagedList<T> extends ListPaginationState {
|
|
34
|
+
/** Always known for a slice-paginated list. */
|
|
35
|
+
totalPages: number;
|
|
36
|
+
/** The slice of `items` belonging to `currentPage`. */
|
|
37
|
+
pageItems: T[];
|
|
38
|
+
}
|
|
39
|
+
export interface UsePagedListOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Called after the page actually changed. Surfaces that keep a per-page
|
|
42
|
+
* selection (an open detail row) clear it here — the old selection is not on
|
|
43
|
+
* the new page.
|
|
44
|
+
*/
|
|
45
|
+
onPageChange?: () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The single owner of client-side slice pagination: local page state, the page
|
|
49
|
+
* math, and the current slice over an ALREADY-FETCHED list.
|
|
50
|
+
*
|
|
51
|
+
* Page size is the caller's — every surface passes its own page-size constant
|
|
52
|
+
* (ENG-005), never a literal. Ordering and filtering stay with the caller: pass
|
|
53
|
+
* the list already sorted and filtered.
|
|
54
|
+
*
|
|
55
|
+
* Not for server-paged lists — a cursor/`hasMore` feed pages through the
|
|
56
|
+
* caller's data layer, not through a slice; use `useCursorPage` (or the query
|
|
57
|
+
* hook's own page state) with the same `ListPagination` control.
|
|
58
|
+
*/
|
|
59
|
+
export declare function usePagedList<T>(items: readonly T[], pageSize: number, options?: UsePagedListOptions): PagedList<T>;
|
|
60
|
+
//# sourceMappingURL=use-paged-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-paged-list.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-paged-list.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,mBAAmB;IAClC,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS,CAAC,CAAC,CAAE,SAAQ,mBAAmB;IACvD,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,SAAS,EAAE,CAAC,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAC5B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,mBAAwB,GAChC,SAAS,CAAC,CAAC,CAAC,CAoCd"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useMemo, useState } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* The single owner of client-side slice pagination: local page state, the page
|
|
5
|
+
* math, and the current slice over an ALREADY-FETCHED list.
|
|
6
|
+
*
|
|
7
|
+
* Page size is the caller's — every surface passes its own page-size constant
|
|
8
|
+
* (ENG-005), never a literal. Ordering and filtering stay with the caller: pass
|
|
9
|
+
* the list already sorted and filtered.
|
|
10
|
+
*
|
|
11
|
+
* Not for server-paged lists — a cursor/`hasMore` feed pages through the
|
|
12
|
+
* caller's data layer, not through a slice; use `useCursorPage` (or the query
|
|
13
|
+
* hook's own page state) with the same `ListPagination` control.
|
|
14
|
+
*/
|
|
15
|
+
export function usePagedList(items, pageSize, options = {}) {
|
|
16
|
+
const { onPageChange } = options;
|
|
17
|
+
const [requestedPage, setRequestedPage] = useState(1);
|
|
18
|
+
const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
|
|
19
|
+
// The list can shrink under a page that is already open (an item is deleted,
|
|
20
|
+
// a filter narrows). Clamping keeps the paginator on a real page instead of
|
|
21
|
+
// rendering an empty slice under an out-of-range "4 of 2".
|
|
22
|
+
const currentPage = Math.min(Math.max(1, requestedPage), totalPages);
|
|
23
|
+
const pageItems = useMemo(() => {
|
|
24
|
+
const startIndex = (currentPage - 1) * pageSize;
|
|
25
|
+
return items.slice(startIndex, startIndex + pageSize);
|
|
26
|
+
}, [items, currentPage, pageSize]);
|
|
27
|
+
const goToPreviousPage = useCallback(() => {
|
|
28
|
+
if (currentPage <= 1)
|
|
29
|
+
return;
|
|
30
|
+
setRequestedPage(currentPage - 1);
|
|
31
|
+
onPageChange?.();
|
|
32
|
+
}, [currentPage, onPageChange]);
|
|
33
|
+
const goToNextPage = useCallback(() => {
|
|
34
|
+
if (currentPage >= totalPages)
|
|
35
|
+
return;
|
|
36
|
+
setRequestedPage(currentPage + 1);
|
|
37
|
+
onPageChange?.();
|
|
38
|
+
}, [currentPage, totalPages, onPageChange]);
|
|
39
|
+
return {
|
|
40
|
+
currentPage,
|
|
41
|
+
totalPages,
|
|
42
|
+
pageItems,
|
|
43
|
+
canPreviousPage: currentPage > 1,
|
|
44
|
+
canNextPage: currentPage < totalPages,
|
|
45
|
+
goToPreviousPage,
|
|
46
|
+
goToNextPage,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=use-paged-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-paged-list.js","sourceRoot":"","sources":["../../../src/react/hooks/use-paged-list.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAmDvD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAmB,EACnB,QAAgB,EAChB,UAA+B,EAAE;IAEjC,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IACjC,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEtD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IACnE,6EAA6E;IAC7E,4EAA4E;IAC5E,2DAA2D;IAC3D,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,CAAC,EAAE,UAAU,CAAC,CAAC;IAErE,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE;QAC7B,MAAM,UAAU,GAAG,CAAC,WAAW,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QAChD,OAAO,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,GAAG,QAAQ,CAAC,CAAC;IACxD,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEnC,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE;QACxC,IAAI,WAAW,IAAI,CAAC;YAAE,OAAO;QAC7B,gBAAgB,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;QAClC,YAAY,EAAE,EAAE,CAAC;IACnB,CAAC,EAAE,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;IAEhC,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;QACpC,IAAI,WAAW,IAAI,UAAU;YAAE,OAAO;QACtC,gBAAgB,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;QAClC,YAAY,EAAE,EAAE,CAAC;IACnB,CAAC,EAAE,CAAC,WAAW,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;IAE5C,OAAO;QACL,WAAW;QACX,UAAU;QACV,SAAS;QACT,eAAe,EAAE,WAAW,GAAG,CAAC;QAChC,WAAW,EAAE,WAAW,GAAG,UAAU;QACrC,gBAAgB;QAChB,YAAY;KACb,CAAC;AACJ,CAAC","sourcesContent":["\"use client\";\n\nimport { useCallback, useMemo, useState } from \"react\";\n\n/**\n * The control state a paginated list exposes to its paginator UI, rendered by\n * the ONE `ListPagination` component. Nothing else builds\n * Previous / counter / Next controls.\n *\n * `totalPages` is the flavor discriminant, and the ONLY difference between the\n * two flavors:\n *\n * - **Known total** (client-side slice pagination — {@link usePagedList}):\n * `totalPages` is a number and the counter reads `\"2 of 5\"`.\n * - **Unknown total** (server/cursor pagination — `useCursorPage`, or a\n * caller's own query hook): `totalPages` is omitted and the counter reads\n * `\"Page 2\"`.\n *\n * Both flavors share one visibility rule, one disabled-edge rule, one live\n * region, and one set of classes because they share one component (ENG-002,\n * FRONTEND-001).\n */\nexport interface ListPaginationState {\n /** 1-based page currently on screen. */\n currentPage: number;\n /**\n * Total page count when it is knowable — at least 1, even for an empty list\n * (an empty list is one empty page). Omitted for cursor/`hasMore` pagination,\n * where the total is unknown until the last page is reached.\n */\n totalPages?: number;\n canPreviousPage: boolean;\n canNextPage: boolean;\n goToPreviousPage: () => void;\n goToNextPage: () => void;\n}\n\nexport interface PagedList<T> extends ListPaginationState {\n /** Always known for a slice-paginated list. */\n totalPages: number;\n /** The slice of `items` belonging to `currentPage`. */\n pageItems: T[];\n}\n\nexport interface UsePagedListOptions {\n /**\n * Called after the page actually changed. Surfaces that keep a per-page\n * selection (an open detail row) clear it here — the old selection is not on\n * the new page.\n */\n onPageChange?: () => void;\n}\n\n/**\n * The single owner of client-side slice pagination: local page state, the page\n * math, and the current slice over an ALREADY-FETCHED list.\n *\n * Page size is the caller's — every surface passes its own page-size constant\n * (ENG-005), never a literal. Ordering and filtering stay with the caller: pass\n * the list already sorted and filtered.\n *\n * Not for server-paged lists — a cursor/`hasMore` feed pages through the\n * caller's data layer, not through a slice; use `useCursorPage` (or the query\n * hook's own page state) with the same `ListPagination` control.\n */\nexport function usePagedList<T>(\n items: readonly T[],\n pageSize: number,\n options: UsePagedListOptions = {},\n): PagedList<T> {\n const { onPageChange } = options;\n const [requestedPage, setRequestedPage] = useState(1);\n\n const totalPages = Math.max(1, Math.ceil(items.length / pageSize));\n // The list can shrink under a page that is already open (an item is deleted,\n // a filter narrows). Clamping keeps the paginator on a real page instead of\n // rendering an empty slice under an out-of-range \"4 of 2\".\n const currentPage = Math.min(Math.max(1, requestedPage), totalPages);\n\n const pageItems = useMemo(() => {\n const startIndex = (currentPage - 1) * pageSize;\n return items.slice(startIndex, startIndex + pageSize);\n }, [items, currentPage, pageSize]);\n\n const goToPreviousPage = useCallback(() => {\n if (currentPage <= 1) return;\n setRequestedPage(currentPage - 1);\n onPageChange?.();\n }, [currentPage, onPageChange]);\n\n const goToNextPage = useCallback(() => {\n if (currentPage >= totalPages) return;\n setRequestedPage(currentPage + 1);\n onPageChange?.();\n }, [currentPage, totalPages, onPageChange]);\n\n return {\n currentPage,\n totalPages,\n pageItems,\n canPreviousPage: currentPage > 1,\n canNextPage: currentPage < totalPages,\n goToPreviousPage,\n goToNextPage,\n };\n}\n"]}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -51,4 +51,10 @@ export { ScrollToTopButton } from "./components/scroll-to-top-button.js";
|
|
|
51
51
|
export type { ScrollToTopButtonProps } from "./components/scroll-to-top-button.js";
|
|
52
52
|
export { ChunkErrorRecovery } from "./components/chunk-error-recovery.js";
|
|
53
53
|
export type { ChunkErrorRecoveryProps } from "./components/chunk-error-recovery.js";
|
|
54
|
+
export { ListPagination } from "./components/list-pagination.js";
|
|
55
|
+
export type { ListPaginationProps } from "./components/list-pagination.js";
|
|
56
|
+
export { usePagedList } from "./hooks/use-paged-list.js";
|
|
57
|
+
export type { ListPaginationState, PagedList, UsePagedListOptions } from "./hooks/use-paged-list.js";
|
|
58
|
+
export { useCursorPage } from "./hooks/use-cursor-page.js";
|
|
59
|
+
export type { CursorPage, UseCursorPageOptions } from "./hooks/use-cursor-page.js";
|
|
54
60
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
|
package/dist/react/index.js
CHANGED
|
@@ -41,4 +41,9 @@ export { RelativeTime } from "./components/relative-time.js";
|
|
|
41
41
|
export { EndOfListIndicator } from "./components/end-of-list-indicator.js";
|
|
42
42
|
export { ScrollToTopButton } from "./components/scroll-to-top-button.js";
|
|
43
43
|
export { ChunkErrorRecovery } from "./components/chunk-error-recovery.js";
|
|
44
|
+
// List pagination — the one Previous / counter / Next control plus the page-state
|
|
45
|
+
// hooks for its two flavors (known total, and cursor/`hasMore`).
|
|
46
|
+
export { ListPagination } from "./components/list-pagination.js";
|
|
47
|
+
export { usePagedList } from "./hooks/use-paged-list.js";
|
|
48
|
+
export { useCursorPage } from "./hooks/use-cursor-page.js";
|
|
44
49
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nexport type { ButtonProps } from \"./components/button.js\";\nexport { Button, buttonVariants } from \"./components/button.js\";\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from \"./components/card.js\";\nexport { Label } from \"./components/label.js\";\nexport { Textarea } from \"./components/textarea.js\";\nexport { Skeleton } from \"./components/skeleton.js\";\nexport { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription } from \"./components/dialog.js\";\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } from \"./components/popover.js\";\nexport { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup } from \"./components/dropdown-menu.js\";\nexport { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarSeparator, MenubarLabel, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarPortal, MenubarSubContent, MenubarSubTrigger, MenubarGroup, MenubarSub, MenubarShortcut } from \"./components/menubar.js\";\nexport { AlertDialog, AlertDialogPortal, AlertDialogOverlay, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel } from \"./components/alert-dialog.js\";\nexport { Alert, AlertTitle, AlertDescription } from \"./components/alert.js\";\nexport type { ToastProps, ToastActionElement } from \"./components/toast.js\";\nexport { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction } from \"./components/toast.js\";\nexport { Toaster } from \"./components/toaster.js\";\nexport type { ToastVariant, ToasterToast } from \"./hooks/use-toast.js\";\nexport { toast, useToast } from \"./hooks/use-toast.js\";\nexport { Input } from \"./components/input.js\";\nexport { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton } from \"./components/select.js\";\nexport { Tabs, TabsList, TabsTrigger, TabsContent } from \"./components/tabs.js\";\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from \"./components/tooltip.js\";\nexport { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField } from \"./components/form.js\";\nexport { Checkbox } from \"./components/checkbox.js\";\nexport { Switch } from \"./components/switch.js\";\nexport type { BadgeProps } from \"./components/badge.js\";\nexport { Badge, badgeVariants } from \"./components/badge.js\";\nexport { Avatar, AvatarImage, AvatarFallback } from \"./components/avatar.js\";\nexport { Separator } from \"./components/separator.js\";\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from \"./components/accordion.js\";\nexport { RadioGroup, RadioGroupItem } from \"./components/radio-group.js\";\nexport { Progress } from \"./components/progress.js\";\nexport { Slider } from \"./components/slider.js\";\nexport type { DatePickerProps } from \"./components/date-picker.js\";\nexport { DatePicker } from \"./components/date-picker.js\";\nexport { ScrollArea, ScrollBar } from \"./components/scroll-area.js\";\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption } from \"./components/table.js\";\nexport { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription } from \"./components/sheet.js\";\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent } from \"./components/collapsible.js\";\nexport type { SearchDropdownProps } from \"./components/search-dropdown.js\";\nexport { SearchDropdown } from \"./components/search-dropdown.js\";\n\nexport type { ConsequenceDialogProps, ConsequenceDialogReason, ConsequenceDialogTypedConfirmation } from \"./components/consequence-dialog.js\";\nexport { ConsequenceDialog } from \"./components/consequence-dialog.js\";\n\nexport type { MaxWidthOption, ScreenAdaptiveViewProps } from \"./components/layout/screen-adaptive-view.js\";\nexport { ScreenAdaptiveView } from \"./components/layout/screen-adaptive-view.js\";\n\nexport { useMediaQuery } from \"./hooks/use-media-query.js\";\n\n// Shared building blocks\nexport { RelativeTime } from \"./components/relative-time.js\";\nexport type { RelativeTimeProps } from \"./components/relative-time.js\";\n\nexport { EndOfListIndicator } from \"./components/end-of-list-indicator.js\";\nexport type { EndOfListIndicatorProps } from \"./components/end-of-list-indicator.js\";\n\nexport { ScrollToTopButton } from \"./components/scroll-to-top-button.js\";\nexport type { ScrollToTopButtonProps } from \"./components/scroll-to-top-button.js\";\n\nexport { ChunkErrorRecovery } from \"./components/chunk-error-recovery.js\";\nexport type { ChunkErrorRecoveryProps } from \"./components/chunk-error-recovery.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nexport type { ButtonProps } from \"./components/button.js\";\nexport { Button, buttonVariants } from \"./components/button.js\";\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from \"./components/card.js\";\nexport { Label } from \"./components/label.js\";\nexport { Textarea } from \"./components/textarea.js\";\nexport { Skeleton } from \"./components/skeleton.js\";\nexport { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription } from \"./components/dialog.js\";\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } from \"./components/popover.js\";\nexport { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup } from \"./components/dropdown-menu.js\";\nexport { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarSeparator, MenubarLabel, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarPortal, MenubarSubContent, MenubarSubTrigger, MenubarGroup, MenubarSub, MenubarShortcut } from \"./components/menubar.js\";\nexport { AlertDialog, AlertDialogPortal, AlertDialogOverlay, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel } from \"./components/alert-dialog.js\";\nexport { Alert, AlertTitle, AlertDescription } from \"./components/alert.js\";\nexport type { ToastProps, ToastActionElement } from \"./components/toast.js\";\nexport { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction } from \"./components/toast.js\";\nexport { Toaster } from \"./components/toaster.js\";\nexport type { ToastVariant, ToasterToast } from \"./hooks/use-toast.js\";\nexport { toast, useToast } from \"./hooks/use-toast.js\";\nexport { Input } from \"./components/input.js\";\nexport { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton } from \"./components/select.js\";\nexport { Tabs, TabsList, TabsTrigger, TabsContent } from \"./components/tabs.js\";\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from \"./components/tooltip.js\";\nexport { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField } from \"./components/form.js\";\nexport { Checkbox } from \"./components/checkbox.js\";\nexport { Switch } from \"./components/switch.js\";\nexport type { BadgeProps } from \"./components/badge.js\";\nexport { Badge, badgeVariants } from \"./components/badge.js\";\nexport { Avatar, AvatarImage, AvatarFallback } from \"./components/avatar.js\";\nexport { Separator } from \"./components/separator.js\";\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from \"./components/accordion.js\";\nexport { RadioGroup, RadioGroupItem } from \"./components/radio-group.js\";\nexport { Progress } from \"./components/progress.js\";\nexport { Slider } from \"./components/slider.js\";\nexport type { DatePickerProps } from \"./components/date-picker.js\";\nexport { DatePicker } from \"./components/date-picker.js\";\nexport { ScrollArea, ScrollBar } from \"./components/scroll-area.js\";\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption } from \"./components/table.js\";\nexport { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription } from \"./components/sheet.js\";\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent } from \"./components/collapsible.js\";\nexport type { SearchDropdownProps } from \"./components/search-dropdown.js\";\nexport { SearchDropdown } from \"./components/search-dropdown.js\";\n\nexport type { ConsequenceDialogProps, ConsequenceDialogReason, ConsequenceDialogTypedConfirmation } from \"./components/consequence-dialog.js\";\nexport { ConsequenceDialog } from \"./components/consequence-dialog.js\";\n\nexport type { MaxWidthOption, ScreenAdaptiveViewProps } from \"./components/layout/screen-adaptive-view.js\";\nexport { ScreenAdaptiveView } from \"./components/layout/screen-adaptive-view.js\";\n\nexport { useMediaQuery } from \"./hooks/use-media-query.js\";\n\n// Shared building blocks\nexport { RelativeTime } from \"./components/relative-time.js\";\nexport type { RelativeTimeProps } from \"./components/relative-time.js\";\n\nexport { EndOfListIndicator } from \"./components/end-of-list-indicator.js\";\nexport type { EndOfListIndicatorProps } from \"./components/end-of-list-indicator.js\";\n\nexport { ScrollToTopButton } from \"./components/scroll-to-top-button.js\";\nexport type { ScrollToTopButtonProps } from \"./components/scroll-to-top-button.js\";\n\nexport { ChunkErrorRecovery } from \"./components/chunk-error-recovery.js\";\nexport type { ChunkErrorRecoveryProps } from \"./components/chunk-error-recovery.js\";\n\n// List pagination — the one Previous / counter / Next control plus the page-state\n// hooks for its two flavors (known total, and cursor/`hasMore`).\nexport { ListPagination } from \"./components/list-pagination.js\";\nexport type { ListPaginationProps } from \"./components/list-pagination.js\";\n\nexport { usePagedList } from \"./hooks/use-paged-list.js\";\nexport type { ListPaginationState, PagedList, UsePagedListOptions } from \"./hooks/use-paged-list.js\";\n\nexport { useCursorPage } from \"./hooks/use-cursor-page.js\";\nexport type { CursorPage, UseCursorPageOptions } from \"./hooks/use-cursor-page.js\";\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttt-productions/ui-core",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.11",
|
|
4
4
|
"description": "Shared UI components for TTT Productions projects",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"react-dom": ">=19.0.0",
|
|
50
50
|
"react-hook-form": ">=7.0.0",
|
|
51
51
|
"@hookform/resolvers": ">=3.0.0",
|
|
52
|
-
"@ttt-productions/firebase-helpers": "^0.12.
|
|
52
|
+
"@ttt-productions/firebase-helpers": "^0.12.4"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"react": {
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"typescript": "^5.8.3",
|
|
100
100
|
"react-hook-form": "^7.69.0",
|
|
101
101
|
"@hookform/resolvers": "^5.2.2",
|
|
102
|
-
"@ttt-productions/firebase-helpers": "^0.12.
|
|
102
|
+
"@ttt-productions/firebase-helpers": "^0.12.4"
|
|
103
103
|
},
|
|
104
104
|
"author": "DJ (TTT Productions)",
|
|
105
105
|
"license": "MIT"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
4
|
+
import { Button } from "./button.js";
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import type { ListPaginationState } from "../hooks/use-paged-list.js";
|
|
7
|
+
|
|
8
|
+
export interface ListPaginationProps {
|
|
9
|
+
/**
|
|
10
|
+
* Page state from `usePagedList` (known total), `useCursorPage` (unknown
|
|
11
|
+
* total), or any caller-owned state of the same shape.
|
|
12
|
+
*/
|
|
13
|
+
pagination: ListPaginationState;
|
|
14
|
+
/**
|
|
15
|
+
* Disables BOTH controls while the next page is in flight, without hiding the
|
|
16
|
+
* row. Server-paged surfaces pass their query's `isFetching`; a slice-paged
|
|
17
|
+
* list has nothing to wait for and omits it.
|
|
18
|
+
*/
|
|
19
|
+
busy?: boolean;
|
|
20
|
+
/** Optional extra classes. Merged with the component's own classes. */
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The ONE Previous / counter / Next control for paginated lists, in both
|
|
26
|
+
* flavors. Pair it with the hook that owns the page state (`usePagedList` for a
|
|
27
|
+
* client-side slice, `useCursorPage` — or the caller's own query state — for a
|
|
28
|
+
* cursor feed); this component owns only the controls, so every paginated list
|
|
29
|
+
* reads and behaves the same (ENG-002, FRONTEND-001).
|
|
30
|
+
*
|
|
31
|
+
* The counter follows the flavor: a known `totalPages` reads "2 of 5", an
|
|
32
|
+
* omitted one reads "Page 2". Everything else is identical in both flavors —
|
|
33
|
+
* which is exactly why there is one component and not two.
|
|
34
|
+
*
|
|
35
|
+
* It renders NOTHING when neither direction is available, so no call site
|
|
36
|
+
* repeats a `totalPages > 1` / `page > 1 || hasMore` guard. Edge state is real
|
|
37
|
+
* `disabled` on the buttons (announced, and unclickable), and the counter is a
|
|
38
|
+
* `status` live region so a page change is announced to screen readers.
|
|
39
|
+
*/
|
|
40
|
+
export function ListPagination({ pagination, busy = false, className }: ListPaginationProps) {
|
|
41
|
+
const { currentPage, totalPages, canPreviousPage, canNextPage, goToPreviousPage, goToNextPage } =
|
|
42
|
+
pagination;
|
|
43
|
+
|
|
44
|
+
// One visibility rule for both flavors. For a known total this is exactly
|
|
45
|
+
// `totalPages > 1`: page state is clamped into `1..totalPages`, so a
|
|
46
|
+
// single-page list has no reachable neighbour in either direction, and a
|
|
47
|
+
// multi-page list always has one. For a cursor feed it is the hand-rolled
|
|
48
|
+
// `page > 1 || hasMore`. `busy` deliberately does not hide the row — the
|
|
49
|
+
// controls grey out in place instead of vanishing mid-fetch.
|
|
50
|
+
if (!canPreviousPage && !canNextPage) return null;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className={cn("flex w-full items-center justify-between gap-4 mt-4", className)}>
|
|
54
|
+
<Button
|
|
55
|
+
variant="outline"
|
|
56
|
+
size="sm"
|
|
57
|
+
onClick={goToPreviousPage}
|
|
58
|
+
disabled={busy || !canPreviousPage}
|
|
59
|
+
className="h-11 min-w-11"
|
|
60
|
+
>
|
|
61
|
+
<ChevronLeft className="icon-xs mr-1" />
|
|
62
|
+
Previous
|
|
63
|
+
</Button>
|
|
64
|
+
<span className="text-counter" role="status">
|
|
65
|
+
{totalPages === undefined ? `Page ${currentPage}` : `${currentPage} of ${totalPages}`}
|
|
66
|
+
</span>
|
|
67
|
+
<Button
|
|
68
|
+
variant="outline"
|
|
69
|
+
size="sm"
|
|
70
|
+
onClick={goToNextPage}
|
|
71
|
+
disabled={busy || !canNextPage}
|
|
72
|
+
className="h-11 min-w-11"
|
|
73
|
+
>
|
|
74
|
+
Next
|
|
75
|
+
<ChevronRight className="icon-xs ml-1" />
|
|
76
|
+
</Button>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -11,9 +11,10 @@ import { cn } from "../../lib/utils.js";
|
|
|
11
11
|
* consumer can never route them through the passthrough channel: `aria-label`
|
|
12
12
|
* is set via {@link ScrollToTopButtonProps.ariaLabel}, extra classes via the
|
|
13
13
|
* merge-aware {@link ScrollToTopButtonProps.className}, content via
|
|
14
|
-
* {@link ScrollToTopButtonProps.icon}, and `onClick` is fixed to scroll-to-top
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* {@link ScrollToTopButtonProps.icon}, and `onClick` is fixed to scroll-to-top
|
|
15
|
+
* (its scroll animation configurable via
|
|
16
|
+
* {@link ScrollToTopButtonProps.scrollBehavior}). This mirrors how the sibling
|
|
17
|
+
* `Button` destructures its owned `className` out of the forwarded `...props`.
|
|
17
18
|
*/
|
|
18
19
|
type ScrollToTopButtonPassthrough = Omit<
|
|
19
20
|
React.ComponentPropsWithoutRef<"button">,
|
|
@@ -33,6 +34,15 @@ export interface ScrollToTopButtonProps extends ScrollToTopButtonPassthrough {
|
|
|
33
34
|
variant?: ButtonProps["variant"];
|
|
34
35
|
/** Button size. Forwarded to the underlying Button. Default "icon". */
|
|
35
36
|
size?: ButtonProps["size"];
|
|
37
|
+
/**
|
|
38
|
+
* Scroll behavior passed to `window.scrollTo`. Default "smooth". Pass "auto"
|
|
39
|
+
* to skip the smooth animation (e.g. for a reduced-motion mode). The package
|
|
40
|
+
* stays motion-policy agnostic: the consumer decides what to pass here — an
|
|
41
|
+
* explicit JS `behavior: "smooth"` is not overridden by a CSS
|
|
42
|
+
* `scroll-behavior` kill-switch, so an app-level reduced-motion mode must
|
|
43
|
+
* pass "auto" to actually stop the animation.
|
|
44
|
+
*/
|
|
45
|
+
scrollBehavior?: ScrollBehavior;
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
export function ScrollToTopButton({
|
|
@@ -42,6 +52,7 @@ export function ScrollToTopButton({
|
|
|
42
52
|
icon,
|
|
43
53
|
variant,
|
|
44
54
|
size = "icon",
|
|
55
|
+
scrollBehavior = "smooth",
|
|
45
56
|
...rest
|
|
46
57
|
}: ScrollToTopButtonProps) {
|
|
47
58
|
const [isVisible, setIsVisible] = React.useState(false);
|
|
@@ -53,7 +64,7 @@ export function ScrollToTopButton({
|
|
|
53
64
|
return () => window.removeEventListener("scroll", toggle);
|
|
54
65
|
}, [threshold]);
|
|
55
66
|
|
|
56
|
-
const onClick = () => window.scrollTo({ top: 0, behavior:
|
|
67
|
+
const onClick = () => window.scrollTo({ top: 0, behavior: scrollBehavior });
|
|
57
68
|
|
|
58
69
|
return (
|
|
59
70
|
<Button
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import type { ListPaginationState } from "./use-paged-list.js";
|
|
5
|
+
|
|
6
|
+
export interface CursorPage extends ListPaginationState {
|
|
7
|
+
/** Never known for cursor pagination — the counter reads "Page N". */
|
|
8
|
+
totalPages?: undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Return to page 1 without walking back. Call it whenever the underlying
|
|
11
|
+
* query's inputs change (a new filter, a new tag, a new sort) — page 4 of the
|
|
12
|
+
* old result set is not page 4 of the new one.
|
|
13
|
+
*/
|
|
14
|
+
reset: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UseCursorPageOptions {
|
|
18
|
+
/** Called after the page actually changed. Same contract as `usePagedList`. */
|
|
19
|
+
onPageChange?: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Page state for a server-paged (cursor / `hasMore`) list: the 1-based page
|
|
24
|
+
* number plus guarded step controls, shaped as {@link ListPaginationState} so it
|
|
25
|
+
* drops straight into the ONE `ListPagination` control.
|
|
26
|
+
*
|
|
27
|
+
* The caller owns the data: it feeds `hasMore` from its query and passes
|
|
28
|
+
* `currentPage` back into that query. This hook owns only the page number — the
|
|
29
|
+
* piece each server-paged surface was otherwise re-declaring as a local
|
|
30
|
+
* `useState(1)` plus its own clamped increment/decrement (ENG-002, ENG-003).
|
|
31
|
+
*
|
|
32
|
+
* A surface whose data hook ALREADY owns its page state (it returns
|
|
33
|
+
* `page`/`hasNextPage`/`nextPage`/`prevPage` itself) does not need this hook —
|
|
34
|
+
* it maps those fields onto `ListPaginationState` directly. The hook exists for
|
|
35
|
+
* the surfaces that would otherwise hand-roll the state.
|
|
36
|
+
*/
|
|
37
|
+
export function useCursorPage(
|
|
38
|
+
hasMore: boolean,
|
|
39
|
+
options: UseCursorPageOptions = {},
|
|
40
|
+
): CursorPage {
|
|
41
|
+
const { onPageChange } = options;
|
|
42
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
43
|
+
|
|
44
|
+
// Guards live here, not at the call site: a click that cannot move the page
|
|
45
|
+
// must not fire `onPageChange` and must not push the page past the data.
|
|
46
|
+
const goToPreviousPage = useCallback(() => {
|
|
47
|
+
if (currentPage <= 1) return;
|
|
48
|
+
setCurrentPage(currentPage - 1);
|
|
49
|
+
onPageChange?.();
|
|
50
|
+
}, [currentPage, onPageChange]);
|
|
51
|
+
|
|
52
|
+
const goToNextPage = useCallback(() => {
|
|
53
|
+
if (!hasMore) return;
|
|
54
|
+
setCurrentPage(currentPage + 1);
|
|
55
|
+
onPageChange?.();
|
|
56
|
+
}, [currentPage, hasMore, onPageChange]);
|
|
57
|
+
|
|
58
|
+
const reset = useCallback(() => {
|
|
59
|
+
if (currentPage === 1) return;
|
|
60
|
+
setCurrentPage(1);
|
|
61
|
+
onPageChange?.();
|
|
62
|
+
}, [currentPage, onPageChange]);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
currentPage,
|
|
66
|
+
canPreviousPage: currentPage > 1,
|
|
67
|
+
canNextPage: hasMore,
|
|
68
|
+
goToPreviousPage,
|
|
69
|
+
goToNextPage,
|
|
70
|
+
reset,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The control state a paginated list exposes to its paginator UI, rendered by
|
|
7
|
+
* the ONE `ListPagination` component. Nothing else builds
|
|
8
|
+
* Previous / counter / Next controls.
|
|
9
|
+
*
|
|
10
|
+
* `totalPages` is the flavor discriminant, and the ONLY difference between the
|
|
11
|
+
* two flavors:
|
|
12
|
+
*
|
|
13
|
+
* - **Known total** (client-side slice pagination — {@link usePagedList}):
|
|
14
|
+
* `totalPages` is a number and the counter reads `"2 of 5"`.
|
|
15
|
+
* - **Unknown total** (server/cursor pagination — `useCursorPage`, or a
|
|
16
|
+
* caller's own query hook): `totalPages` is omitted and the counter reads
|
|
17
|
+
* `"Page 2"`.
|
|
18
|
+
*
|
|
19
|
+
* Both flavors share one visibility rule, one disabled-edge rule, one live
|
|
20
|
+
* region, and one set of classes because they share one component (ENG-002,
|
|
21
|
+
* FRONTEND-001).
|
|
22
|
+
*/
|
|
23
|
+
export interface ListPaginationState {
|
|
24
|
+
/** 1-based page currently on screen. */
|
|
25
|
+
currentPage: number;
|
|
26
|
+
/**
|
|
27
|
+
* Total page count when it is knowable — at least 1, even for an empty list
|
|
28
|
+
* (an empty list is one empty page). Omitted for cursor/`hasMore` pagination,
|
|
29
|
+
* where the total is unknown until the last page is reached.
|
|
30
|
+
*/
|
|
31
|
+
totalPages?: number;
|
|
32
|
+
canPreviousPage: boolean;
|
|
33
|
+
canNextPage: boolean;
|
|
34
|
+
goToPreviousPage: () => void;
|
|
35
|
+
goToNextPage: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PagedList<T> extends ListPaginationState {
|
|
39
|
+
/** Always known for a slice-paginated list. */
|
|
40
|
+
totalPages: number;
|
|
41
|
+
/** The slice of `items` belonging to `currentPage`. */
|
|
42
|
+
pageItems: T[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface UsePagedListOptions {
|
|
46
|
+
/**
|
|
47
|
+
* Called after the page actually changed. Surfaces that keep a per-page
|
|
48
|
+
* selection (an open detail row) clear it here — the old selection is not on
|
|
49
|
+
* the new page.
|
|
50
|
+
*/
|
|
51
|
+
onPageChange?: () => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The single owner of client-side slice pagination: local page state, the page
|
|
56
|
+
* math, and the current slice over an ALREADY-FETCHED list.
|
|
57
|
+
*
|
|
58
|
+
* Page size is the caller's — every surface passes its own page-size constant
|
|
59
|
+
* (ENG-005), never a literal. Ordering and filtering stay with the caller: pass
|
|
60
|
+
* the list already sorted and filtered.
|
|
61
|
+
*
|
|
62
|
+
* Not for server-paged lists — a cursor/`hasMore` feed pages through the
|
|
63
|
+
* caller's data layer, not through a slice; use `useCursorPage` (or the query
|
|
64
|
+
* hook's own page state) with the same `ListPagination` control.
|
|
65
|
+
*/
|
|
66
|
+
export function usePagedList<T>(
|
|
67
|
+
items: readonly T[],
|
|
68
|
+
pageSize: number,
|
|
69
|
+
options: UsePagedListOptions = {},
|
|
70
|
+
): PagedList<T> {
|
|
71
|
+
const { onPageChange } = options;
|
|
72
|
+
const [requestedPage, setRequestedPage] = useState(1);
|
|
73
|
+
|
|
74
|
+
const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
|
|
75
|
+
// The list can shrink under a page that is already open (an item is deleted,
|
|
76
|
+
// a filter narrows). Clamping keeps the paginator on a real page instead of
|
|
77
|
+
// rendering an empty slice under an out-of-range "4 of 2".
|
|
78
|
+
const currentPage = Math.min(Math.max(1, requestedPage), totalPages);
|
|
79
|
+
|
|
80
|
+
const pageItems = useMemo(() => {
|
|
81
|
+
const startIndex = (currentPage - 1) * pageSize;
|
|
82
|
+
return items.slice(startIndex, startIndex + pageSize);
|
|
83
|
+
}, [items, currentPage, pageSize]);
|
|
84
|
+
|
|
85
|
+
const goToPreviousPage = useCallback(() => {
|
|
86
|
+
if (currentPage <= 1) return;
|
|
87
|
+
setRequestedPage(currentPage - 1);
|
|
88
|
+
onPageChange?.();
|
|
89
|
+
}, [currentPage, onPageChange]);
|
|
90
|
+
|
|
91
|
+
const goToNextPage = useCallback(() => {
|
|
92
|
+
if (currentPage >= totalPages) return;
|
|
93
|
+
setRequestedPage(currentPage + 1);
|
|
94
|
+
onPageChange?.();
|
|
95
|
+
}, [currentPage, totalPages, onPageChange]);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
currentPage,
|
|
99
|
+
totalPages,
|
|
100
|
+
pageItems,
|
|
101
|
+
canPreviousPage: currentPage > 1,
|
|
102
|
+
canNextPage: currentPage < totalPages,
|
|
103
|
+
goToPreviousPage,
|
|
104
|
+
goToNextPage,
|
|
105
|
+
};
|
|
106
|
+
}
|
package/src/react/index.ts
CHANGED
|
@@ -61,3 +61,14 @@ export type { ScrollToTopButtonProps } from "./components/scroll-to-top-button.j
|
|
|
61
61
|
|
|
62
62
|
export { ChunkErrorRecovery } from "./components/chunk-error-recovery.js";
|
|
63
63
|
export type { ChunkErrorRecoveryProps } from "./components/chunk-error-recovery.js";
|
|
64
|
+
|
|
65
|
+
// List pagination — the one Previous / counter / Next control plus the page-state
|
|
66
|
+
// hooks for its two flavors (known total, and cursor/`hasMore`).
|
|
67
|
+
export { ListPagination } from "./components/list-pagination.js";
|
|
68
|
+
export type { ListPaginationProps } from "./components/list-pagination.js";
|
|
69
|
+
|
|
70
|
+
export { usePagedList } from "./hooks/use-paged-list.js";
|
|
71
|
+
export type { ListPaginationState, PagedList, UsePagedListOptions } from "./hooks/use-paged-list.js";
|
|
72
|
+
|
|
73
|
+
export { useCursorPage } from "./hooks/use-cursor-page.js";
|
|
74
|
+
export type { CursorPage, UseCursorPageOptions } from "./hooks/use-cursor-page.js";
|