{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "masai-pagination", "title": "Masai Pagination", "description": "Pagination control with prev/next chevrons, numbered pages, ellipsis truncation, and regular/large sizes.", "dependencies": [ "class-variance-authority", "lucide-react" ], "files": [ { "path": "registry/components/masai-pagination.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype PageItem =\n | { kind: \"page\"; page: number }\n | { kind: \"ellipsis\"; key: string };\n\n/** Build a compact list of page tokens around `currentPage`.\n * Always shows boundaries and a configurable window of siblings. */\nfunction buildPageItems(\n currentPage: number,\n totalPages: number,\n siblingCount: number,\n boundaryCount: number,\n): PageItem[] {\n if (totalPages <= 0) return [];\n\n const minVisible = boundaryCount * 2 + siblingCount * 2 + 3;\n if (totalPages <= minVisible) {\n return Array.from({ length: totalPages }, (_, i) => ({\n kind: \"page\" as const,\n page: i + 1,\n }));\n }\n\n const startPages = Array.from({ length: boundaryCount }, (_, i) => i + 1);\n const endPages = Array.from(\n { length: boundaryCount },\n (_, i) => totalPages - boundaryCount + i + 1,\n );\n\n const siblingsStart = Math.max(\n Math.min(currentPage - siblingCount, totalPages - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n );\n const siblingsEnd = Math.min(\n Math.max(currentPage + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages[0] - 2,\n );\n\n const items: PageItem[] = [];\n\n for (const p of startPages) items.push({ kind: \"page\", page: p });\n\n if (siblingsStart > boundaryCount + 2) {\n items.push({ kind: \"ellipsis\", key: \"start-ellipsis\" });\n } else if (boundaryCount + 1 < totalPages - boundaryCount) {\n items.push({ kind: \"page\", page: boundaryCount + 1 });\n }\n\n for (let p = siblingsStart; p <= siblingsEnd; p += 1) {\n items.push({ kind: \"page\", page: p });\n }\n\n if (siblingsEnd < totalPages - boundaryCount - 1) {\n items.push({ kind: \"ellipsis\", key: \"end-ellipsis\" });\n } else if (totalPages - boundaryCount > boundaryCount) {\n items.push({ kind: \"page\", page: totalPages - boundaryCount });\n }\n\n for (const p of endPages) items.push({ kind: \"page\", page: p });\n\n return items;\n}\n\nconst cellVariants = cva(\n \"inline-flex shrink-0 items-center justify-center bg-white outline-none transition-colors focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-primary-400 focus-visible:ring-offset-0\",\n {\n variants: {\n size: {\n regular: \"h-10 min-w-10 px-2 type-b2-md\",\n large: \"h-12 min-w-12 px-3 type-b1-md\",\n },\n interactive: {\n true: \"cursor-pointer hover:bg-gray-50 active:bg-gray-100\",\n false: \"cursor-default\",\n },\n state: {\n default: \"!text-gray-700\",\n active: \"bg-blue-50 !text-gray-800\",\n disabled: \"!text-gray-300 cursor-not-allowed\",\n },\n },\n defaultVariants: {\n size: \"regular\",\n interactive: false,\n state: \"default\",\n },\n },\n);\n\nexport type MasaiPaginationSize = \"regular\" | \"large\";\n\nexport type MasaiPaginationProps = Omit<\n React.HTMLAttributes,\n \"onChange\"\n> &\n VariantProps & {\n /** 1-based current page. */\n currentPage: number;\n /** Total number of pages (>= 1). */\n totalPages: number;\n /** Fired when the user clicks a page or the prev/next chevron. */\n onPageChange: (page: number) => void;\n /** Sibling pages on each side of the current page. Default: 1. */\n siblingCount?: number;\n /** Boundary pages pinned at the start and end. Default: 1. */\n boundaryCount?: number;\n /** Hide the leading \"previous\" chevron. */\n hidePrev?: boolean;\n /** Hide the trailing \"next\" chevron. */\n hideNext?: boolean;\n /** Visual size token. Default: regular. */\n size?: MasaiPaginationSize;\n /** Accessible label for the wrapping \n );\n}\n", "type": "registry:ui" } ], "type": "registry:ui" }