@gridui/pagination 1.0.7 → 1.1.1

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/README.md CHANGED
@@ -5,6 +5,24 @@ Core family. Public API: `Pagination`, `PaginationLabels`, `PaginationProps`.
5
5
  Import components from `@gridui/pagination` and load `@gridui/pagination/styles.css` after Theme.
6
6
  Load styles for any composed dependency used by this family. Applications own routes, labels, state persistence, and branding.
7
7
 
8
+ ## Unknown page count
9
+
10
+ Pass `pageCount={null}` and a boolean `hasNextPage` when the server does not
11
+ provide a total. Pagination shows Previous, the current page, and Next. The host
12
+ supplies either `onPageChange` or `getPageHref`, as with a known count. Previous
13
+ is disabled on page 1; Next is disabled when no next page is available. No last
14
+ page or total is inferred. Pages must remain positive safe integers; `Infinity`
15
+ is rejected, and Next is disabled at `Number.MAX_SAFE_INTEGER`.
16
+
17
+ ```tsx
18
+ <Pagination page={page} pageCount={null} hasNextPage={response.hasNextPage}
19
+ onPageChange={setPage} labels={labels} />
20
+ ```
21
+
22
+ A numeric `pageCount` retains the existing bounded page window and does not take
23
+ `hasNextPage`. Unknown totals still use page numbers; applications own cursor
24
+ mapping, fetching, and history when their backend uses cursor pagination.
25
+
8
26
  See [component ownership](https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/docs/component-ownership.md) and [release history](https://github.com/sudo-ui/GridUI-React-Monorepo/tree/main/packages-core/pagination/.changeset/history).
9
27
 
10
28
  [Component reference](docs/reference/README.md) contains focused API pages for each public component.
package/dist/index.js CHANGED
@@ -52,6 +52,26 @@ var paginationRange = (page, pageCount, compact = false) => {
52
52
  return entries;
53
53
  };
54
54
 
55
+ // src/pagination/style.ts
56
+ import { cva } from "class-variance-authority";
57
+ var paginationControlStyles = cva("", {
58
+ variants: {
59
+ kind: {
60
+ page: "gridui-u-pagination--h-auto gridui-u-pagination--min-h-11 gridui-u-pagination--w-auto gridui-u-pagination--min-w-11 gridui-u-pagination--px-gridui-8 gridui-u-pagination--whitespace-nowrap",
61
+ navigation: ""
62
+ }
63
+ }
64
+ });
65
+ var paginationEntryStyles = cva("", {
66
+ variants: {
67
+ visibility: {
68
+ all: "",
69
+ desktop: "gridui-u-pagination--hidden gridui-u-pagination--md:block",
70
+ mobile: "gridui-u-pagination--md:hidden"
71
+ }
72
+ }
73
+ });
74
+
55
75
  // src/pagination/pagination.tsx
56
76
  import { Button, ButtonLink } from "@gridui/button";
57
77
  import { cn } from "@gridui/style-utils";
@@ -59,17 +79,21 @@ import { ChevronLeft, ChevronRight, Ellipsis } from "lucide-react";
59
79
  import { Fragment } from "react";
60
80
  import { jsx, jsxs } from "react/jsx-runtime";
61
81
  var Pagination = (props) => {
62
- const { page, pageCount, labels, onPageChange, getPageHref, className, ...restProps } = props;
63
- const entries = paginationRange(page, pageCount);
64
- const compactEntries = paginationRange(page, pageCount, true);
65
- const current = Math.max(1, Math.min(page, pageCount));
82
+ const { page, pageCount, hasNextPage, labels, onPageChange, getPageHref, className, ...restProps } = props;
83
+ if (pageCount === null && (!Number.isSafeInteger(page) || page < 1 || typeof hasNextPage !== "boolean")) {
84
+ throw new RangeError("Unknown-count Pagination requires a positive safe integer page and a boolean hasNextPage.");
85
+ }
86
+ const entries = pageCount === null ? [page] : paginationRange(page, pageCount);
87
+ const compactEntries = pageCount === null ? entries : paginationRange(page, pageCount, true);
88
+ const current = pageCount === null ? page : Math.max(1, Math.min(page, pageCount));
89
+ const nextDisabled = pageCount === null ? !hasNextPage || current === Number.MAX_SAFE_INTEGER : pageCount === 0 || current >= pageCount;
66
90
  const control = (target, label, children, disabled, active = false) => {
67
91
  const shared = {
68
92
  "aria-label": label,
69
93
  "aria-current": active ? "page" : void 0,
70
94
  variant: active ? "secondary" : "quiet",
71
95
  layout: "icon",
72
- className: typeof children === "number" ? "gridui-u-pagination--h-auto gridui-u-pagination--min-h-11 gridui-u-pagination--w-auto gridui-u-pagination--min-w-11 gridui-u-pagination--px-gridui-8 gridui-u-pagination--whitespace-nowrap" : void 0
96
+ className: paginationControlStyles({ kind: typeof children === "number" ? "page" : "navigation" })
73
97
  };
74
98
  if (getPageHref && !disabled) {
75
99
  return /* @__PURE__ */ jsx(
@@ -95,11 +119,11 @@ var Pagination = (props) => {
95
119
  }
96
120
  );
97
121
  };
98
- const renderEntry = (entry, entryClassName) => {
122
+ const renderEntry = (entry, visibility = "all") => {
99
123
  return /* @__PURE__ */ jsx(
100
124
  "li",
101
125
  {
102
- className: entryClassName,
126
+ className: paginationEntryStyles({ visibility }) || void 0,
103
127
  children: typeof entry === "number" ? control(entry, labels.page(entry), entry, false, entry === current) : /* @__PURE__ */ jsx(
104
128
  "span",
105
129
  {
@@ -140,9 +164,9 @@ var Pagination = (props) => {
140
164
  Fragment,
141
165
  {
142
166
  children: [
143
- entry === pageCount && compactEntries.includes("end-ellipsis") && !entries.includes("end-ellipsis") && renderEntry("end-ellipsis", cn("gridui-u-pagination--md:hidden")),
144
- renderEntry(entry, compactEntries.includes(entry) ? void 0 : cn("gridui-u-pagination--hidden gridui-u-pagination--md:block")),
145
- entry === 1 && compactEntries.includes("start-ellipsis") && !entries.includes("start-ellipsis") && renderEntry("start-ellipsis", cn("gridui-u-pagination--md:hidden"))
167
+ entry === pageCount && compactEntries.includes("end-ellipsis") && !entries.includes("end-ellipsis") && renderEntry("end-ellipsis", "mobile"),
168
+ renderEntry(entry, compactEntries.includes(entry) ? "all" : "desktop"),
169
+ entry === 1 && compactEntries.includes("start-ellipsis") && !entries.includes("start-ellipsis") && renderEntry("start-ellipsis", "mobile")
146
170
  ]
147
171
  },
148
172
  entry
@@ -154,7 +178,7 @@ var Pagination = (props) => {
154
178
  className: "gridui-u-pagination--rtl:rotate-180",
155
179
  "aria-hidden": "true"
156
180
  }
157
- ), pageCount === 0 || current >= pageCount) })
181
+ ), nextDisabled) })
158
182
  ]
159
183
  }
160
184
  )
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/pagination/range.ts", "../src/pagination/pagination.tsx"],
4
- "sourcesContent": ["/**\n * @author WMXPY\n * @package Pagination\n * @namespace Pagination\n * @description Range\n */\n\nexport type PaginationEntry = number | \"start-ellipsis\" | \"end-ellipsis\";\n\nexport const paginationRange = (page: number, pageCount: number, compact = false): PaginationEntry[] => {\n if (!Number.isSafeInteger(pageCount) || pageCount < 0 || !Number.isSafeInteger(page) || page < 1) {\n throw new RangeError(\"Pagination requires a positive integer page and a non-negative integer pageCount.\");\n }\n if (pageCount === 0) {\n return [];\n }\n if (pageCount <= (compact ? 5 : 7)) {\n return Array.from({ length: pageCount }, (_, index) => {\n return index + 1;\n });\n }\n const current = Math.min(page, pageCount);\n if (compact) {\n const positions = [\n ...new Set([\n 1,\n current,\n pageCount,\n ]),\n ];\n const entries: PaginationEntry[] = [];\n for (const [\n index,\n position,\n ] of positions.entries()) {\n if (index > 0 && position - positions[index - 1]! > 1) {\n entries.push(position <= current ? \"start-ellipsis\" : \"end-ellipsis\");\n }\n entries.push(position);\n }\n return entries;\n }\n const start = Math.max(2, Math.min(current - 1, pageCount - 4));\n const end = Math.min(pageCount - 1, Math.max(current + 1, 5));\n const entries: PaginationEntry[] = [1];\n if (start === 3) {\n entries.push(2);\n } else if (start > 3) {\n entries.push(\"start-ellipsis\");\n }\n for (let index = start; index <= end; index++) { entries.push(index); }\n if (end === pageCount - 2) {\n entries.push(pageCount - 1);\n } else if (end < pageCount - 2) {\n entries.push(\"end-ellipsis\");\n }\n entries.push(pageCount);\n return entries;\n};\n", "/**\n * @author WMXPY\n * @package Pagination\n * @namespace Pagination\n * @description Pagination\n */\n\nimport { type PaginationEntry, paginationRange } from \"./range\";\nimport { Button, ButtonLink } from \"@gridui/button\";\nimport { cn } from \"@gridui/style-utils\";\nimport { ChevronLeft, ChevronRight, Ellipsis } from \"lucide-react\";\nimport { type ComponentPropsWithRef, type FC, Fragment, type ReactNode } from \"react\";\n\nexport type PaginationLabels = {\n readonly navigation: string;\n readonly previous: string;\n readonly next: string;\n readonly page: (page: number) => string;\n readonly ellipsis: string;\n};\n\nexport type PaginationProps = Omit<ComponentPropsWithRef<\"nav\">, \"children\" | \"aria-label\"> & {\n readonly page: number;\n readonly pageCount: number;\n readonly labels: PaginationLabels;\n} & ({\n readonly onPageChange: (page: number) => void;\n readonly getPageHref?: never;\n} | {\n readonly getPageHref: (page: number) => string;\n readonly onPageChange?: never;\n});\n\nexport const Pagination: FC<PaginationProps> = (props: PaginationProps) => {\n const { page, pageCount, labels, onPageChange, getPageHref, className, ...restProps } = props;\n const entries = paginationRange(page, pageCount);\n const compactEntries = paginationRange(page, pageCount, true);\n const current = Math.max(1, Math.min(page, pageCount));\n const control = (target: number, label: string, children: ReactNode, disabled: boolean, active = false) => {\n const shared = {\n \"aria-label\": label,\n \"aria-current\": active ? \"page\" as const : undefined,\n variant: active ? \"secondary\" as const : \"quiet\" as const,\n layout: \"icon\" as const,\n className: typeof children === \"number\" ? \"gridui-u-pagination--h-auto gridui-u-pagination--min-h-11 gridui-u-pagination--w-auto gridui-u-pagination--min-w-11 gridui-u-pagination--px-gridui-8 gridui-u-pagination--whitespace-nowrap\" : undefined,\n };\n if (getPageHref && !disabled) {\n return (<ButtonLink\n {...shared}\n href={getPageHref(target)}\n >\n {children}\n </ButtonLink>);\n }\n return (<Button\n {...shared}\n disabled={disabled}\n onClick={() => {\n if (target !== current) {\n onPageChange?.(target);\n }\n }}\n >\n {children}\n </Button>);\n };\n const renderEntry = (entry: PaginationEntry, entryClassName?: string) => {\n return (<li\n className={entryClassName}\n >\n {typeof entry === \"number\"\n ? control(entry, labels.page(entry), entry, false, entry === current)\n : (<span\n role=\"img\"\n className=\"gridui-u-pagination--inline-flex gridui-u-pagination--min-h-11 gridui-u-pagination--min-w-11 gridui-u-pagination--items-center gridui-u-pagination--justify-center gridui-u-pagination--text-muted\"\n aria-label={labels.ellipsis}\n >\n <Ellipsis\n aria-hidden=\"true\"\n />\n </span>)}\n </li>);\n };\n return (<nav\n {...restProps}\n aria-label={labels.navigation}\n className={cn(\"gridui-u-pagination--min-w-0\", className)}\n >\n <ul\n className=\"gridui-u-pagination--m-0 gridui-u-pagination--flex gridui-u-pagination--list-none gridui-u-pagination--flex-wrap gridui-u-pagination--items-center gridui-u-pagination--gap-0 gridui-u-pagination--p-0 gridui-u-pagination--md:gap-gridui-4\"\n >\n <li>\n {control(current - 1, labels.previous, (<ChevronLeft\n className=\"gridui-u-pagination--rtl:rotate-180\"\n aria-hidden=\"true\"\n />), pageCount === 0 || current === 1)}\n </li>\n {entries.map(entry => {\n return (<Fragment\n key={entry}\n >\n {entry === pageCount && compactEntries.includes(\"end-ellipsis\") && !entries.includes(\"end-ellipsis\")\n && renderEntry(\"end-ellipsis\", cn(\"gridui-u-pagination--md:hidden\"))}\n {renderEntry(entry, compactEntries.includes(entry) ? undefined : cn(\"gridui-u-pagination--hidden gridui-u-pagination--md:block\"))}\n {entry === 1 && compactEntries.includes(\"start-ellipsis\") && !entries.includes(\"start-ellipsis\")\n && renderEntry(\"start-ellipsis\", cn(\"gridui-u-pagination--md:hidden\"))}\n </Fragment>);\n })}\n <li>\n {control(current + 1, labels.next, (<ChevronRight\n className=\"gridui-u-pagination--rtl:rotate-180\"\n aria-hidden=\"true\"\n />), pageCount === 0 || current >= pageCount)}\n </li>\n </ul>\n </nav>);\n};\n"],
5
- "mappings": ";AASO,IAAM,kBAAkB,CAAC,MAAc,WAAmB,UAAU,UAA6B;AACpG,MAAI,CAAC,OAAO,cAAc,SAAS,KAAK,YAAY,KAAK,CAAC,OAAO,cAAc,IAAI,KAAK,OAAO,GAAG;AAC9F,UAAM,IAAI,WAAW,mFAAmF;AAAA,EAC5G;AACA,MAAI,cAAc,GAAG;AACjB,WAAO,CAAC;AAAA,EACZ;AACA,MAAI,cAAc,UAAU,IAAI,IAAI;AAChC,WAAO,MAAM,KAAK,EAAE,QAAQ,UAAU,GAAG,CAAC,GAAG,UAAU;AACnD,aAAO,QAAQ;AAAA,IACnB,CAAC;AAAA,EACL;AACA,QAAM,UAAU,KAAK,IAAI,MAAM,SAAS;AACxC,MAAI,SAAS;AACT,UAAM,YAAY;AAAA,MACd,GAAG,oBAAI,IAAI;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,MACJ,CAAC;AAAA,IACL;AACA,UAAMA,WAA6B,CAAC;AACpC,eAAW;AAAA,MACP;AAAA,MACA;AAAA,IACJ,KAAK,UAAU,QAAQ,GAAG;AACtB,UAAI,QAAQ,KAAK,WAAW,UAAU,QAAQ,CAAC,IAAK,GAAG;AACnD,QAAAA,SAAQ,KAAK,YAAY,UAAU,mBAAmB,cAAc;AAAA,MACxE;AACA,MAAAA,SAAQ,KAAK,QAAQ;AAAA,IACzB;AACA,WAAOA;AAAA,EACX;AACA,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,UAAU,GAAG,YAAY,CAAC,CAAC;AAC9D,QAAM,MAAM,KAAK,IAAI,YAAY,GAAG,KAAK,IAAI,UAAU,GAAG,CAAC,CAAC;AAC5D,QAAM,UAA6B,CAAC,CAAC;AACrC,MAAI,UAAU,GAAG;AACb,YAAQ,KAAK,CAAC;AAAA,EAClB,WAAW,QAAQ,GAAG;AAClB,YAAQ,KAAK,gBAAgB;AAAA,EACjC;AACA,WAAS,QAAQ,OAAO,SAAS,KAAK,SAAS;AAAE,YAAQ,KAAK,KAAK;AAAA,EAAG;AACtE,MAAI,QAAQ,YAAY,GAAG;AACvB,YAAQ,KAAK,YAAY,CAAC;AAAA,EAC9B,WAAW,MAAM,YAAY,GAAG;AAC5B,YAAQ,KAAK,cAAc;AAAA,EAC/B;AACA,UAAQ,KAAK,SAAS;AACtB,SAAO;AACX;;;AClDA,SAAS,QAAQ,kBAAkB;AACnC,SAAS,UAAU;AACnB,SAAS,aAAa,cAAc,gBAAgB;AACpD,SAA8C,gBAAgC;AAoC1D,cAmDI,YAnDJ;AAdb,IAAM,aAAkC,CAAC,UAA2B;AACvE,QAAM,EAAE,MAAM,WAAW,QAAQ,cAAc,aAAa,WAAW,GAAG,UAAU,IAAI;AACxF,QAAM,UAAU,gBAAgB,MAAM,SAAS;AAC/C,QAAM,iBAAiB,gBAAgB,MAAM,WAAW,IAAI;AAC5D,QAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,SAAS,CAAC;AACrD,QAAM,UAAU,CAAC,QAAgB,OAAe,UAAqB,UAAmB,SAAS,UAAU;AACvG,UAAM,SAAS;AAAA,MACX,cAAc;AAAA,MACd,gBAAgB,SAAS,SAAkB;AAAA,MAC3C,SAAS,SAAS,cAAuB;AAAA,MACzC,QAAQ;AAAA,MACR,WAAW,OAAO,aAAa,WAAW,gMAAgM;AAAA,IAC9O;AACA,QAAI,eAAe,CAAC,UAAU;AAC1B,aAAQ;AAAA,QAAC;AAAA;AAAA,UACJ,GAAG;AAAA,UACJ,MAAM,YAAY,MAAM;AAAA,UAEvB;AAAA;AAAA,MACL;AAAA,IACJ;AACA,WAAQ;AAAA,MAAC;AAAA;AAAA,QACJ,GAAG;AAAA,QACJ;AAAA,QACA,SAAS,MAAM;AACX,cAAI,WAAW,SAAS;AACpB,2BAAe,MAAM;AAAA,UACzB;AAAA,QACJ;AAAA,QAEC;AAAA;AAAA,IACL;AAAA,EACJ;AACA,QAAM,cAAc,CAAC,OAAwB,mBAA4B;AACrE,WAAQ;AAAA,MAAC;AAAA;AAAA,QACL,WAAW;AAAA,QAEV,iBAAO,UAAU,WACZ,QAAQ,OAAO,OAAO,KAAK,KAAK,GAAG,OAAO,OAAO,UAAU,OAAO,IACjE;AAAA,UAAC;AAAA;AAAA,YACA,MAAK;AAAA,YACL,WAAU;AAAA,YACV,cAAY,OAAO;AAAA,YAEnB;AAAA,cAAC;AAAA;AAAA,gBACG,eAAY;AAAA;AAAA,YAChB;AAAA;AAAA,QACJ;AAAA;AAAA,IACR;AAAA,EACJ;AACA,SAAQ;AAAA,IAAC;AAAA;AAAA,MACJ,GAAG;AAAA,MACJ,cAAY,OAAO;AAAA,MACnB,WAAW,GAAG,gCAAgC,SAAS;AAAA,MAEvD;AAAA,QAAC;AAAA;AAAA,UACG,WAAU;AAAA,UAEV;AAAA,gCAAC,QACI,kBAAQ,UAAU,GAAG,OAAO,UAAW;AAAA,cAAC;AAAA;AAAA,gBACrC,WAAU;AAAA,gBACV,eAAY;AAAA;AAAA,YAChB,GAAK,cAAc,KAAK,YAAY,CAAC,GACzC;AAAA,YACC,QAAQ,IAAI,WAAS;AAClB,qBAAQ;AAAA,gBAAC;AAAA;AAAA,kBAGJ;AAAA,8BAAU,aAAa,eAAe,SAAS,cAAc,KAAK,CAAC,QAAQ,SAAS,cAAc,KAC5F,YAAY,gBAAgB,GAAG,gCAAgC,CAAC;AAAA,oBACtE,YAAY,OAAO,eAAe,SAAS,KAAK,IAAI,SAAY,GAAG,2DAA2D,CAAC;AAAA,oBAC/H,UAAU,KAAK,eAAe,SAAS,gBAAgB,KAAK,CAAC,QAAQ,SAAS,gBAAgB,KACxF,YAAY,kBAAkB,GAAG,gCAAgC,CAAC;AAAA;AAAA;AAAA,gBANpE;AAAA,cAOT;AAAA,YACJ,CAAC;AAAA,YACD,oBAAC,QACI,kBAAQ,UAAU,GAAG,OAAO,MAAO;AAAA,cAAC;AAAA;AAAA,gBACjC,WAAU;AAAA,gBACV,eAAY;AAAA;AAAA,YAChB,GAAK,cAAc,KAAK,WAAW,SAAS,GAChD;AAAA;AAAA;AAAA,MACJ;AAAA;AAAA,EACJ;AACJ;",
3
+ "sources": ["../src/pagination/range.ts", "../src/pagination/style.ts", "../src/pagination/pagination.tsx"],
4
+ "sourcesContent": ["/**\n * @author WMXPY\n * @package Pagination\n * @namespace Pagination\n * @description Range\n */\n\nexport type PaginationEntry = number | \"start-ellipsis\" | \"end-ellipsis\";\n\nexport const paginationRange = (page: number, pageCount: number, compact = false): PaginationEntry[] => {\n if (!Number.isSafeInteger(pageCount) || pageCount < 0 || !Number.isSafeInteger(page) || page < 1) {\n throw new RangeError(\"Pagination requires a positive integer page and a non-negative integer pageCount.\");\n }\n if (pageCount === 0) {\n return [];\n }\n if (pageCount <= (compact ? 5 : 7)) {\n return Array.from({ length: pageCount }, (_, index) => {\n return index + 1;\n });\n }\n const current = Math.min(page, pageCount);\n if (compact) {\n const positions = [\n ...new Set([\n 1,\n current,\n pageCount,\n ]),\n ];\n const entries: PaginationEntry[] = [];\n for (const [\n index,\n position,\n ] of positions.entries()) {\n if (index > 0 && position - positions[index - 1]! > 1) {\n entries.push(position <= current ? \"start-ellipsis\" : \"end-ellipsis\");\n }\n entries.push(position);\n }\n return entries;\n }\n const start = Math.max(2, Math.min(current - 1, pageCount - 4));\n const end = Math.min(pageCount - 1, Math.max(current + 1, 5));\n const entries: PaginationEntry[] = [1];\n if (start === 3) {\n entries.push(2);\n } else if (start > 3) {\n entries.push(\"start-ellipsis\");\n }\n for (let index = start; index <= end; index++) { entries.push(index); }\n if (end === pageCount - 2) {\n entries.push(pageCount - 1);\n } else if (end < pageCount - 2) {\n entries.push(\"end-ellipsis\");\n }\n entries.push(pageCount);\n return entries;\n};\n", "/**\n * @author WMXPY\n * @package Pagination\n * @namespace Pagination\n * @description Style\n */\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nexport const paginationControlStyles = cva(\"\", {\n variants: {\n kind: {\n page: \"gridui-u-pagination--h-auto gridui-u-pagination--min-h-11 gridui-u-pagination--w-auto gridui-u-pagination--min-w-11 gridui-u-pagination--px-gridui-8 gridui-u-pagination--whitespace-nowrap\",\n navigation: \"\",\n },\n },\n});\n\nexport const paginationEntryStyles = cva(\"\", {\n variants: {\n visibility: {\n all: \"\",\n desktop: \"gridui-u-pagination--hidden gridui-u-pagination--md:block\",\n mobile: \"gridui-u-pagination--md:hidden\",\n },\n },\n});\n\nexport type PaginationEntryVisibility = NonNullable<VariantProps<typeof paginationEntryStyles>[\"visibility\"]>;\n", "/**\n * @author WMXPY\n * @package Pagination\n * @namespace Pagination\n * @description Pagination\n */\n\nimport { type PaginationEntry, paginationRange } from \"./range\";\nimport { paginationControlStyles, paginationEntryStyles, type PaginationEntryVisibility } from \"./style\";\nimport { Button, ButtonLink } from \"@gridui/button\";\nimport { cn } from \"@gridui/style-utils\";\nimport { ChevronLeft, ChevronRight, Ellipsis } from \"lucide-react\";\nimport { type ComponentPropsWithRef, type FC, Fragment, type ReactNode } from \"react\";\n\nexport type PaginationLabels = {\n readonly navigation: string;\n readonly previous: string;\n readonly next: string;\n readonly page: (page: number) => string;\n readonly ellipsis: string;\n};\n\nexport type PaginationProps = Omit<ComponentPropsWithRef<\"nav\">, \"children\" | \"aria-label\"> & {\n readonly page: number;\n readonly labels: PaginationLabels;\n} & ({\n readonly pageCount: number;\n readonly hasNextPage?: never;\n} | {\n readonly pageCount: null;\n readonly hasNextPage: boolean;\n}) & ({\n readonly onPageChange: (page: number) => void;\n readonly getPageHref?: never;\n} | {\n readonly getPageHref: (page: number) => string;\n readonly onPageChange?: never;\n});\n\nexport const Pagination: FC<PaginationProps> = (props: PaginationProps) => {\n const { page, pageCount, hasNextPage, labels, onPageChange, getPageHref, className, ...restProps } = props;\n if (pageCount === null && (!Number.isSafeInteger(page) || page < 1 || typeof hasNextPage !== \"boolean\")) {\n throw new RangeError(\"Unknown-count Pagination requires a positive safe integer page and a boolean hasNextPage.\");\n }\n const entries = pageCount === null ? [page] : paginationRange(page, pageCount);\n const compactEntries = pageCount === null ? entries : paginationRange(page, pageCount, true);\n const current = pageCount === null ? page : Math.max(1, Math.min(page, pageCount));\n const nextDisabled = pageCount === null\n ? !hasNextPage || current === Number.MAX_SAFE_INTEGER\n : pageCount === 0 || current >= pageCount;\n const control = (target: number, label: string, children: ReactNode, disabled: boolean, active = false) => {\n const shared = {\n \"aria-label\": label,\n \"aria-current\": active ? \"page\" as const : undefined,\n variant: active ? \"secondary\" as const : \"quiet\" as const,\n layout: \"icon\" as const,\n className: paginationControlStyles({ kind: typeof children === \"number\" ? \"page\" : \"navigation\" }),\n };\n if (getPageHref && !disabled) {\n return (<ButtonLink\n {...shared}\n href={getPageHref(target)}\n >\n {children}\n </ButtonLink>);\n }\n return (<Button\n {...shared}\n disabled={disabled}\n onClick={() => {\n if (target !== current) {\n onPageChange?.(target);\n }\n }}\n >\n {children}\n </Button>);\n };\n const renderEntry = (entry: PaginationEntry, visibility: PaginationEntryVisibility = \"all\") => {\n return (<li\n className={paginationEntryStyles({ visibility }) || undefined}\n >\n {typeof entry === \"number\"\n ? control(entry, labels.page(entry), entry, false, entry === current)\n : (<span\n role=\"img\"\n className=\"gridui-u-pagination--inline-flex gridui-u-pagination--min-h-11 gridui-u-pagination--min-w-11 gridui-u-pagination--items-center gridui-u-pagination--justify-center gridui-u-pagination--text-muted\"\n aria-label={labels.ellipsis}\n >\n <Ellipsis\n aria-hidden=\"true\"\n />\n </span>)}\n </li>);\n };\n return (<nav\n {...restProps}\n aria-label={labels.navigation}\n className={cn(\"gridui-u-pagination--min-w-0\", className)}\n >\n <ul\n className=\"gridui-u-pagination--m-0 gridui-u-pagination--flex gridui-u-pagination--list-none gridui-u-pagination--flex-wrap gridui-u-pagination--items-center gridui-u-pagination--gap-0 gridui-u-pagination--p-0 gridui-u-pagination--md:gap-gridui-4\"\n >\n <li>\n {control(current - 1, labels.previous, (<ChevronLeft\n className=\"gridui-u-pagination--rtl:rotate-180\"\n aria-hidden=\"true\"\n />), pageCount === 0 || current === 1)}\n </li>\n {entries.map(entry => {\n return (<Fragment\n key={entry}\n >\n {entry === pageCount && compactEntries.includes(\"end-ellipsis\") && !entries.includes(\"end-ellipsis\")\n && renderEntry(\"end-ellipsis\", \"mobile\")}\n {renderEntry(entry, compactEntries.includes(entry) ? \"all\" : \"desktop\")}\n {entry === 1 && compactEntries.includes(\"start-ellipsis\") && !entries.includes(\"start-ellipsis\")\n && renderEntry(\"start-ellipsis\", \"mobile\")}\n </Fragment>);\n })}\n <li>\n {control(current + 1, labels.next, (<ChevronRight\n className=\"gridui-u-pagination--rtl:rotate-180\"\n aria-hidden=\"true\"\n />), nextDisabled)}\n </li>\n </ul>\n </nav>);\n};\n"],
5
+ "mappings": ";AASO,IAAM,kBAAkB,CAAC,MAAc,WAAmB,UAAU,UAA6B;AACpG,MAAI,CAAC,OAAO,cAAc,SAAS,KAAK,YAAY,KAAK,CAAC,OAAO,cAAc,IAAI,KAAK,OAAO,GAAG;AAC9F,UAAM,IAAI,WAAW,mFAAmF;AAAA,EAC5G;AACA,MAAI,cAAc,GAAG;AACjB,WAAO,CAAC;AAAA,EACZ;AACA,MAAI,cAAc,UAAU,IAAI,IAAI;AAChC,WAAO,MAAM,KAAK,EAAE,QAAQ,UAAU,GAAG,CAAC,GAAG,UAAU;AACnD,aAAO,QAAQ;AAAA,IACnB,CAAC;AAAA,EACL;AACA,QAAM,UAAU,KAAK,IAAI,MAAM,SAAS;AACxC,MAAI,SAAS;AACT,UAAM,YAAY;AAAA,MACd,GAAG,oBAAI,IAAI;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,MACJ,CAAC;AAAA,IACL;AACA,UAAMA,WAA6B,CAAC;AACpC,eAAW;AAAA,MACP;AAAA,MACA;AAAA,IACJ,KAAK,UAAU,QAAQ,GAAG;AACtB,UAAI,QAAQ,KAAK,WAAW,UAAU,QAAQ,CAAC,IAAK,GAAG;AACnD,QAAAA,SAAQ,KAAK,YAAY,UAAU,mBAAmB,cAAc;AAAA,MACxE;AACA,MAAAA,SAAQ,KAAK,QAAQ;AAAA,IACzB;AACA,WAAOA;AAAA,EACX;AACA,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,UAAU,GAAG,YAAY,CAAC,CAAC;AAC9D,QAAM,MAAM,KAAK,IAAI,YAAY,GAAG,KAAK,IAAI,UAAU,GAAG,CAAC,CAAC;AAC5D,QAAM,UAA6B,CAAC,CAAC;AACrC,MAAI,UAAU,GAAG;AACb,YAAQ,KAAK,CAAC;AAAA,EAClB,WAAW,QAAQ,GAAG;AAClB,YAAQ,KAAK,gBAAgB;AAAA,EACjC;AACA,WAAS,QAAQ,OAAO,SAAS,KAAK,SAAS;AAAE,YAAQ,KAAK,KAAK;AAAA,EAAG;AACtE,MAAI,QAAQ,YAAY,GAAG;AACvB,YAAQ,KAAK,YAAY,CAAC;AAAA,EAC9B,WAAW,MAAM,YAAY,GAAG;AAC5B,YAAQ,KAAK,cAAc;AAAA,EAC/B;AACA,UAAQ,KAAK,SAAS;AACtB,SAAO;AACX;;;ACnDA,SAAS,WAA8B;AAEhC,IAAM,0BAA0B,IAAI,IAAI;AAAA,EAC3C,UAAU;AAAA,IACN,MAAM;AAAA,MACF,MAAM;AAAA,MACN,YAAY;AAAA,IAChB;AAAA,EACJ;AACJ,CAAC;AAEM,IAAM,wBAAwB,IAAI,IAAI;AAAA,EACzC,UAAU;AAAA,IACN,YAAY;AAAA,MACR,KAAK;AAAA,MACL,SAAS;AAAA,MACT,QAAQ;AAAA,IACZ;AAAA,EACJ;AACJ,CAAC;;;ACjBD,SAAS,QAAQ,kBAAkB;AACnC,SAAS,UAAU;AACnB,SAAS,aAAa,cAAc,gBAAgB;AACpD,SAA8C,gBAAgC;AA+C1D,cAmDI,YAnDJ;AApBb,IAAM,aAAkC,CAAC,UAA2B;AACvE,QAAM,EAAE,MAAM,WAAW,aAAa,QAAQ,cAAc,aAAa,WAAW,GAAG,UAAU,IAAI;AACrG,MAAI,cAAc,SAAS,CAAC,OAAO,cAAc,IAAI,KAAK,OAAO,KAAK,OAAO,gBAAgB,YAAY;AACrG,UAAM,IAAI,WAAW,2FAA2F;AAAA,EACpH;AACA,QAAM,UAAU,cAAc,OAAO,CAAC,IAAI,IAAI,gBAAgB,MAAM,SAAS;AAC7E,QAAM,iBAAiB,cAAc,OAAO,UAAU,gBAAgB,MAAM,WAAW,IAAI;AAC3F,QAAM,UAAU,cAAc,OAAO,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,SAAS,CAAC;AACjF,QAAM,eAAe,cAAc,OAC7B,CAAC,eAAe,YAAY,OAAO,mBACnC,cAAc,KAAK,WAAW;AACpC,QAAM,UAAU,CAAC,QAAgB,OAAe,UAAqB,UAAmB,SAAS,UAAU;AACvG,UAAM,SAAS;AAAA,MACX,cAAc;AAAA,MACd,gBAAgB,SAAS,SAAkB;AAAA,MAC3C,SAAS,SAAS,cAAuB;AAAA,MACzC,QAAQ;AAAA,MACR,WAAW,wBAAwB,EAAE,MAAM,OAAO,aAAa,WAAW,SAAS,aAAa,CAAC;AAAA,IACrG;AACA,QAAI,eAAe,CAAC,UAAU;AAC1B,aAAQ;AAAA,QAAC;AAAA;AAAA,UACJ,GAAG;AAAA,UACJ,MAAM,YAAY,MAAM;AAAA,UAEvB;AAAA;AAAA,MACL;AAAA,IACJ;AACA,WAAQ;AAAA,MAAC;AAAA;AAAA,QACJ,GAAG;AAAA,QACJ;AAAA,QACA,SAAS,MAAM;AACX,cAAI,WAAW,SAAS;AACpB,2BAAe,MAAM;AAAA,UACzB;AAAA,QACJ;AAAA,QAEC;AAAA;AAAA,IACL;AAAA,EACJ;AACA,QAAM,cAAc,CAAC,OAAwB,aAAwC,UAAU;AAC3F,WAAQ;AAAA,MAAC;AAAA;AAAA,QACL,WAAW,sBAAsB,EAAE,WAAW,CAAC,KAAK;AAAA,QAEnD,iBAAO,UAAU,WACZ,QAAQ,OAAO,OAAO,KAAK,KAAK,GAAG,OAAO,OAAO,UAAU,OAAO,IACjE;AAAA,UAAC;AAAA;AAAA,YACA,MAAK;AAAA,YACL,WAAU;AAAA,YACV,cAAY,OAAO;AAAA,YAEnB;AAAA,cAAC;AAAA;AAAA,gBACG,eAAY;AAAA;AAAA,YAChB;AAAA;AAAA,QACJ;AAAA;AAAA,IACR;AAAA,EACJ;AACA,SAAQ;AAAA,IAAC;AAAA;AAAA,MACJ,GAAG;AAAA,MACJ,cAAY,OAAO;AAAA,MACnB,WAAW,GAAG,gCAAgC,SAAS;AAAA,MAEvD;AAAA,QAAC;AAAA;AAAA,UACG,WAAU;AAAA,UAEV;AAAA,gCAAC,QACI,kBAAQ,UAAU,GAAG,OAAO,UAAW;AAAA,cAAC;AAAA;AAAA,gBACrC,WAAU;AAAA,gBACV,eAAY;AAAA;AAAA,YAChB,GAAK,cAAc,KAAK,YAAY,CAAC,GACzC;AAAA,YACC,QAAQ,IAAI,WAAS;AAClB,qBAAQ;AAAA,gBAAC;AAAA;AAAA,kBAGJ;AAAA,8BAAU,aAAa,eAAe,SAAS,cAAc,KAAK,CAAC,QAAQ,SAAS,cAAc,KAC5F,YAAY,gBAAgB,QAAQ;AAAA,oBAC1C,YAAY,OAAO,eAAe,SAAS,KAAK,IAAI,QAAQ,SAAS;AAAA,oBACrE,UAAU,KAAK,eAAe,SAAS,gBAAgB,KAAK,CAAC,QAAQ,SAAS,gBAAgB,KACxF,YAAY,kBAAkB,QAAQ;AAAA;AAAA;AAAA,gBANxC;AAAA,cAOT;AAAA,YACJ,CAAC;AAAA,YACD,oBAAC,QACI,kBAAQ,UAAU,GAAG,OAAO,MAAO;AAAA,cAAC;AAAA;AAAA,gBACjC,WAAU;AAAA,gBACV,eAAY;AAAA;AAAA,YAChB,GAAK,YAAY,GACrB;AAAA;AAAA;AAAA,MACJ;AAAA;AAAA,EACJ;AACJ;",
6
6
  "names": ["entries"]
7
7
  }
@@ -14,9 +14,14 @@ export type PaginationLabels = {
14
14
  };
15
15
  export type PaginationProps = Omit<ComponentPropsWithRef<"nav">, "children" | "aria-label"> & {
16
16
  readonly page: number;
17
- readonly pageCount: number;
18
17
  readonly labels: PaginationLabels;
19
18
  } & ({
19
+ readonly pageCount: number;
20
+ readonly hasNextPage?: never;
21
+ } | {
22
+ readonly pageCount: null;
23
+ readonly hasNextPage: boolean;
24
+ }) & ({
20
25
  readonly onPageChange: (page: number) => void;
21
26
  readonly getPageHref?: never;
22
27
  } | {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @author WMXPY
3
+ * @package Pagination
4
+ * @namespace Pagination
5
+ * @description Style
6
+ */
7
+ import { type VariantProps } from "class-variance-authority";
8
+ export declare const paginationControlStyles: (props?: ({
9
+ kind?: "page" | "navigation" | null | undefined;
10
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
11
+ export declare const paginationEntryStyles: (props?: ({
12
+ visibility?: "all" | "desktop" | "mobile" | null | undefined;
13
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
14
+ export type PaginationEntryVisibility = NonNullable<VariantProps<typeof paginationEntryStyles>["visibility"]>;
package/docs/catalog.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "name": "@gridui/pagination",
4
- "version": "1.0.7",
4
+ "version": "1.1.1",
5
5
  "category": "core",
6
6
  "description": "GridUI React pagination",
7
- "usage": "Component reference contains focused API pages for each public component.",
7
+ "usage": "Pass `pageCount={null}` and a boolean `hasNextPage` when the server does not provide a total. Pagination shows Previous, the current page, and Next. The host supplies either `onPageChange` or `getPageHref`, as with a known count. Previous is disabled on page 1; Next is disabled when no next page is available. No last page or total is inferred. Pages must remain positive safe integers; `Infinity` is rejected, and Next is disabled at `Number.MAX_SAFE_INTEGER`.",
8
8
  "documentation": [
9
9
  "README.md",
10
10
  "docs/reference/README.md",
@@ -33,6 +33,7 @@
33
33
  "@gridui/pagination/styles.css"
34
34
  ],
35
35
  "examples": [
36
+ "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/pagination/stories/getting-started.mdx",
36
37
  "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/pagination/stories/paginated-navigation.mdx",
37
38
  "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/pagination/stories/pagination.stories.tsx"
38
39
  ]
@@ -53,13 +53,17 @@ Called when the corresponding interaction occurs; inspect the arguments in Actio
53
53
 
54
54
  ``number`` · Required
55
55
 
56
+ ### labels
57
+
58
+ ``PaginationLabels`` · Required
59
+
56
60
  ### pageCount
57
61
 
58
- ``number`` · Required
62
+ ``number | null`` · Required
59
63
 
60
- ### labels
64
+ ### hasNextPage
61
65
 
62
- ``PaginationLabels`` · Required
66
+ ``boolean | undefined`` · Optional
63
67
 
64
68
  ### onPageChange
65
69
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gridui/pagination",
3
- "version": "1.0.7",
3
+ "version": "1.1.1",
4
4
  "description": "GridUI React pagination",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -27,15 +27,16 @@
27
27
  "react-dom": "^19.2.0"
28
28
  },
29
29
  "dependencies": {
30
- "@gridui/button": "^4.1.0",
30
+ "@gridui/button": "^5.0.1",
31
31
  "@gridui/style-utils": "^4.0.0",
32
- "@gridui/theme": "^2.5.3",
33
- "lucide-react": "1.70.0"
32
+ "@gridui/theme": "^2.6.0",
33
+ "class-variance-authority": "0.7.1",
34
+ "lucide-react": "1.72.0"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@gridui-internal/configuration": "0.0.0-internal",
37
38
  "@jest/globals": "30.5.2",
38
- "@storybook/react-vite": "10.6.6",
39
+ "@storybook/react-vite": "10.6.7",
39
40
  "@types/jest": "30.0.0",
40
41
  "@types/node": "26.6.6",
41
42
  "@types/react": "19.3.0",
@@ -48,7 +49,6 @@
48
49
  "scripts": {
49
50
  "build": "node ../../scripts/compilation/package.ts",
50
51
  "compile": "tsc --project tsconfig.json",
51
- "test": "jest",
52
- "coverage": "jest --coverage"
52
+ "test": "jest"
53
53
  }
54
54
  }