{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"data-grid-column-header","type":"registry:ui","title":"Data Grid Column Header","description":"","dependencies":["@tanstack/react-table"],"registryDependencies":["button","@neui/data-grid","dropdown-menu"],"files":[{"path":"data-grid-column-header.tsx","type":"registry:ui","content":"\"use client\"\n\"use no memo\"\n\nimport { memo, useMemo } from \"react\"\nimport type { HTMLAttributes, ReactNode } from \"react\"\nimport {\n getColumnHeaderLabel,\n useDataGrid,\n} from \"@/components/neui/data-grid/data-grid\"\nimport type { Column } from \"@tanstack/react-table\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { IconPlaceholder } from \"@/app/(create)/components/icon-placeholder\"\n\ninterface DataGridColumnHeaderProps<\n TData,\n TValue,\n> extends HTMLAttributes {\n column: Column\n /** When omitted, uses `column.columnDef.meta.headerTitle`, then a string `columnDef.header`, then `column.id`. */\n title?: string\n icon?: ReactNode\n /** Reserved; pin controls are gated by tableLayout.columnsPinnable + column.getCanPin(). */\n pinnable?: boolean\n filter?: ReactNode\n visibility?: boolean\n}\n\nfunction DataGridColumnHeaderInner({\n column,\n title,\n icon,\n className,\n filter,\n visibility = false,\n}: DataGridColumnHeaderProps) {\n const { isLoading, table, props } = useDataGrid()\n const resolvedTitle = title ?? getColumnHeaderLabel(column)\n\n // TanStack's columnOrder defaults to [] until a consumer seeds it; fall\n // back to the definition order so Move Left/Right work out of the box.\n const columnOrderState = table.getState().columnOrder\n const columnOrder =\n columnOrderState.length > 0\n ? columnOrderState\n : table.getAllLeafColumns().map((leafColumn) => leafColumn.id)\n const columnVisibilityKey =\n props.tableLayout?.columnsVisibility && visibility\n ? JSON.stringify(table.getState().columnVisibility)\n : \"\"\n const isSorted = column.getIsSorted()\n const isPinned = column.getIsPinned()\n const canSort = column.getCanSort()\n const canPin = column.getCanPin()\n const canResize = column.getCanResize()\n\n const columnIndex = columnOrder.indexOf(column.id)\n const canMoveLeft = columnIndex > 0\n const canMoveRight = columnIndex < columnOrder.length - 1\n\n const handleSort = () => {\n if (isSorted === \"asc\") {\n column.toggleSorting(true)\n } else if (isSorted === \"desc\") {\n column.clearSorting()\n } else {\n column.toggleSorting(false)\n }\n }\n\n const headerLabelClassName = cn(\n \"text-secondary-foreground/80 inline-flex h-full items-center gap-1.5 font-normal [&_svg]:opacity-60 text-[0.8125rem] leading-[calc(1.125/0.8125)] [&_svg]:size-3.5\",\n className\n )\n\n const headerButtonClassName = cn(\n \"text-secondary-foreground/80 hover:bg-secondary data-[state=open]:bg-secondary hover:text-foreground data-[state=open]:text-foreground px-2 font-normal h-6 rounded-full\",\n className\n )\n\n const sortIcon =\n canSort &&\n (isSorted === \"desc\" ? (\n \n ) : isSorted === \"asc\" ? (\n \n ) : (\n \n ))\n\n const hasControls =\n props.tableLayout?.columnsMovable ||\n (props.tableLayout?.columnsVisibility && visibility) ||\n (props.tableLayout?.columnsPinnable && canPin) ||\n filter\n\n const menuItems = useMemo(() => {\n const items: ReactNode[] = []\n let hasPreviousSection = false\n\n // Filter section\n if (filter) {\n items.push(\n \n {filter}\n \n )\n hasPreviousSection = true\n }\n\n // Sort section\n if (canSort) {\n if (hasPreviousSection) {\n items.push()\n }\n items.push(\n {\n if (isSorted === \"asc\") {\n column.clearSorting()\n } else {\n column.toggleSorting(false)\n }\n }}\n disabled={!canSort}\n >\n \n Asc\n {isSorted === \"asc\" && (\n \n )}\n ,\n {\n if (isSorted === \"desc\") {\n column.clearSorting()\n } else {\n column.toggleSorting(true)\n }\n }}\n disabled={!canSort}\n >\n \n Desc\n {isSorted === \"desc\" && (\n \n )}\n \n )\n hasPreviousSection = true\n }\n\n // Pin section\n if (props.tableLayout?.columnsPinnable && canPin) {\n if (hasPreviousSection) {\n items.push()\n }\n items.push(\n column.pin(isPinned === \"left\" ? false : \"left\")}\n >\n \n Pin to left\n {isPinned === \"left\" && (\n \n )}\n ,\n column.pin(isPinned === \"right\" ? false : \"right\")}\n >\n \n Pin to right\n {isPinned === \"right\" && (\n \n )}\n \n )\n hasPreviousSection = true\n }\n\n // Move section\n if (props.tableLayout?.columnsMovable) {\n if (hasPreviousSection) {\n items.push()\n }\n items.push(\n {\n if (columnIndex > 0) {\n const newOrder = [...columnOrder]\n const [movedColumn] = newOrder.splice(columnIndex, 1)\n newOrder.splice(columnIndex - 1, 0, movedColumn)\n table.setColumnOrder(newOrder)\n }\n }}\n disabled={!canMoveLeft || isPinned !== false}\n >\n \n Move to Left\n ,\n {\n if (columnIndex < columnOrder.length - 1) {\n const newOrder = [...columnOrder]\n const [movedColumn] = newOrder.splice(columnIndex, 1)\n newOrder.splice(columnIndex + 1, 0, movedColumn)\n table.setColumnOrder(newOrder)\n }\n }}\n disabled={!canMoveRight || isPinned !== false}\n >\n \n Move to Right\n \n )\n hasPreviousSection = true\n }\n\n // Visibility section\n if (props.tableLayout?.columnsVisibility && visibility) {\n if (hasPreviousSection) {\n items.push()\n }\n items.push(\n \n \n \n Columns\n \n \n {table\n .getAllColumns()\n .filter((col) => col.getCanHide())\n .map((col) => (\n event.preventDefault()}\n onCheckedChange={(value) => col.toggleVisibility(!!value)}\n className=\"capitalize\"\n >\n {getColumnHeaderLabel(col)}\n \n ))}\n \n \n )\n }\n\n return items\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n filter,\n canSort,\n isSorted,\n column,\n props.tableLayout?.columnsPinnable,\n props.tableLayout?.columnsMovable,\n props.tableLayout?.columnsVisibility,\n canPin,\n isPinned,\n canMoveLeft,\n canMoveRight,\n visibility,\n table,\n columnIndex,\n columnOrder,\n columnVisibilityKey, // Needed to update checkbox states when visibility changes\n ])\n\n if (hasControls) {\n return (\n
\n \n \n \n {icon && icon}\n {resolvedTitle}\n {sortIcon}\n \n \n \n {menuItems}\n \n \n {props.tableLayout?.columnsPinnable && canPin && isPinned && (\n column.pin(false)}\n aria-label={`Unpin ${resolvedTitle} column`}\n title={`Unpin ${resolvedTitle} column`}\n >\n \n \n )}\n
\n )\n }\n\n if (canSort || (props.tableLayout?.columnsResizable && canResize)) {\n return (\n
\n \n {icon && icon}\n {resolvedTitle}\n {sortIcon}\n \n
\n )\n }\n\n return (\n
\n {icon && icon}\n {resolvedTitle}\n
\n )\n}\n\nconst DataGridColumnHeader = memo(\n DataGridColumnHeaderInner\n) as typeof DataGridColumnHeaderInner\n\nexport { DataGridColumnHeader, type DataGridColumnHeaderProps }","target":"components/neui/data-grid/data-grid-column-header.tsx"}]}