{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "custom-table", "title": "Custom Table", "description": "カスタムテーブル", "dependencies": [ "tanstack-react-table", "nuqs", "lucide-react" ], "registryDependencies": [ "table", "button", "https://byhr-registry.vercel.app/r/table-utilities.json" ], "files": [ { "path": "registry/br-hr/blocks/custom-table.tsx", "content": "\"use client\";\nimport {\n\tflexRender,\n\ttype RowData,\n\ttype Table as TableType,\n} from \"@tanstack/react-table\";\nimport {\n\tChevronLeftIcon,\n\tChevronRightIcon,\n\tChevronsLeftIcon,\n\tChevronsRightIcon,\n\tXIcon,\n} from \"lucide-react\";\nimport { parseAsString, useQueryStates } from \"nuqs\";\nimport { useEffect, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/registry/br-hr/ui/button\";\nimport {\n\tTable,\n\tTableBody,\n\tTableCell,\n\tTableHead,\n\tTableHeader,\n\tTableRow,\n} from \"@/registry/br-hr/ui/table\";\nimport { getDispPage } from \"@/registry/br-hr/utilities/table/index\";\n\ndeclare module \"@tanstack/react-table\" {\n\tinterface ColumnMeta {\n\t\twidth?: string;\n\t\tminWidth?: string;\n\t\tmaxWidth?: string;\n\t}\n}\n\n/**\n * テーブル描画用の共通コンポーネント\n * @remarks\n * `data` は各行のキーに対してセルとして描画可能な値(`ReactNode | string | number | boolean | null | undefined`)を持つレコード配列として扱います。\n */\ntype PropsType = {\n\ttable: TableType;\n\ttotalPage: number;\n\tbodyClassName?: string;\n\trowClassName?: string;\n\ttoastContent?: (table: TableType) => React.ReactNode;\n\t/**\n\t * 右端のカラムを固定するかどうか\n\t * @default false\n\t */\n\tstickyRightColumn?: boolean;\n};\nconst pageParser = {\n\tpage_index: parseAsString.withDefault(\"0\"),\n\tpage_size: parseAsString.withDefault(\"30\"),\n};\n\nconst CustomTable = (props: PropsType) => {\n\tconst {\n\t\ttable,\n\t\ttotalPage,\n\t\tbodyClassName,\n\t\trowClassName,\n\t\ttoastContent,\n\t\tstickyRightColumn = false,\n\t} = props;\n\n\t// スクロール要素のref\n\tconst scrollContainerRef = useRef(null);\n\t// 前のページインデックスを追跡するref\n\tconst prevPageIndexRef = useRef(\"0\");\n\n\t// ページインデックスの管理\n\tconst [page, setPage] = useQueryStates(pageParser, {\n\t\tshallow: false,\n\t});\n\n\tconst handlePageIndexChange = (value: string) => {\n\t\tsetPage({ page_index: value });\n\t};\n\n\t// ページインデックス変更後にスクロールを一番上に戻す\n\tuseEffect(() => {\n\t\t// ページインデックスが実際に変更された場合のみスクロールをリセット\n\t\tif (\n\t\t\tprevPageIndexRef.current !== page.page_index &&\n\t\t\tscrollContainerRef.current\n\t\t) {\n\t\t\tscrollContainerRef.current.scrollTop = 0;\n\t\t\tprevPageIndexRef.current = page.page_index;\n\t\t}\n\t});\n\n\t// ページネーション情報取得\n\tconst currentPage = Number(page.page_index) ?? 0;\n\n\t// ページネーション表示情報取得\n\tconst display = getDispPage(currentPage, totalPage);\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t{/* ボディ部分 - スクロール可能 */}\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{table.getHeaderGroups().map((headerGroup) => {\n\t\t\t\t\t\t\t\tconst headers = headerGroup.headers;\n\t\t\t\t\t\t\t\tconst lastHeaderIndex = headers.length - 1;\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t{headers.map((header, index) => {\n\t\t\t\t\t\t\t\t\t\t\tconst isLastColumn =\n\t\t\t\t\t\t\t\t\t\t\t\tstickyRightColumn && index === lastHeaderIndex;\n\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{header.isPlaceholder\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t? null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t: flexRender(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\theader.column.columnDef.header,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\theader.getContext(),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{table.getRowModel().rows.map((row) => {\n\t\t\t\t\t\t\t\tconst cells = row.getVisibleCells();\n\t\t\t\t\t\t\t\tconst lastCellIndex = cells.length - 1;\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t{cells.map((cell, index) => {\n\t\t\t\t\t\t\t\t\t\t\tconst isLastColumn =\n\t\t\t\t\t\t\t\t\t\t\t\tstickyRightColumn && index === lastCellIndex;\n\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{flexRender(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcell.column.columnDef.cell,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcell.getContext(),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t{totalPage > 1 && (\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\thandlePageIndexChange(\"0\");\n\t\t\t\t\t\t\t\t\ttable.toggleAllPageRowsSelected(false);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tdisabled={currentPage === 0}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\thandlePageIndexChange((currentPage - 1).toString());\n\t\t\t\t\t\t\t\t\ttable.toggleAllPageRowsSelected(false);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tdisabled={currentPage === 0}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tGo to previous page\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{display?.map((d) => {\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\t\t\t\ttable.toggleAllPageRowsSelected(false);\n\t\t\t\t\t\t\t\t\t\t\t\tif (d.label !== \"...\") {\n\t\t\t\t\t\t\t\t\t\t\t\t\ttable.setPageIndex(d.value - 1);\n\t\t\t\t\t\t\t\t\t\t\t\t\thandlePageIndexChange((d.value - 1).toString());\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\tdisabled={\n\t\t\t\t\t\t\t\t\t\t\t\t(d.value === 1 && Number(currentPage) === 0) ||\n\t\t\t\t\t\t\t\t\t\t\t\td.value - 1 === Number(currentPage)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{d.label}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\ttable.toggleAllPageRowsSelected(false);\n\t\t\t\t\t\t\t\t\thandlePageIndexChange((currentPage + 1).toString());\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tdisabled={currentPage === totalPage - 1}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tGo to next page\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\ttable.toggleAllPageRowsSelected(false);\n\t\t\t\t\t\t\t\t\thandlePageIndexChange((totalPage - 1).toString());\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tdisabled={currentPage === totalPage - 1}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tGo to last page\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t)}\n\t\t\t{table.getFilteredSelectedRowModel().rows.length > 0 && (\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\t{table.getFilteredSelectedRowModel().rows.length}\n\t\t\t\t\t\t\t件の顧問先が選択されています\n\t\t\t\t\t\t

\n\t\t\t\t\t\t{toastContent?.(table)}\n\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\ttable.toggleAllPageRowsSelected(false);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tclassName=\"cursor-pointer\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t)}\n\t\t\n\t);\n};\n\nexport default CustomTable;\n", "type": "registry:component" } ], "type": "registry:component" }