{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table-sort-header-button",
  "title": "Data Table Sort Header Button",
  "description": "Slot-mounted sort button for column headers.",
  "dependencies": [
    "@virtuoso.dev/data-table",
    "lucide-react"
  ],
  "registryDependencies": [
    "petyosi/react-virtuoso/data-table"
  ],
  "files": [
    {
      "path": "registry/new-york/data-table/column-sort/index.ts",
      "content": "export { SortHeaderButton } from './sort-header-button'\nexport type { SortDirection, SortHeaderButtonProps, SortPayload } from './sort-header-button'\n",
      "type": "registry:component",
      "target": "@ui/data-table/column-sort/index.ts"
    },
    {
      "path": "registry/new-york/data-table/column-sort/sort-header-button.tsx",
      "content": "'use client'\n\nimport { dispatchModelAction$, modelActionState$, useCellValue, usePublisher } from '@virtuoso.dev/data-table'\nimport { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\n\nimport type { HeaderSlotRenderParams } from '@virtuoso.dev/data-table'\n\nexport type SortDirection = 'asc' | 'desc'\nexport type SortPayload = {\n  field: string\n  direction: SortDirection\n}\n\nexport interface SortHeaderButtonProps extends Partial<HeaderSlotRenderParams> {\n  action?: string\n  className?: string\n  field?: string\n  getDirection?: (payload: unknown, field: string) => SortDirection | undefined\n  getPayload?: (context: { direction: SortDirection | undefined; field: string; previousDirection: SortDirection | undefined }) => unknown\n}\n\nfunction sortDirectionFromPayload(payload: unknown, field: string): SortDirection | undefined {\n  if (typeof payload !== 'object' || payload === null) {\n    return undefined\n  }\n\n  const sort = payload as Partial<SortPayload>\n  return sort.field === field && (sort.direction === 'asc' || sort.direction === 'desc') ? sort.direction : undefined\n}\n\nexport function SortHeaderButton({\n  action = 'sort',\n  className,\n  column,\n  field,\n  getDirection = sortDirectionFromPayload,\n  getPayload,\n}: SortHeaderButtonProps) {\n  const dispatch = usePublisher(dispatchModelAction$)\n  const actionState = useCellValue(modelActionState$)\n  const sortField = field ?? column?.field\n\n  if (!sortField) {\n    return null\n  }\n\n  const direction = getDirection(actionState[action]?.payload, sortField)\n  const nextDirection: SortDirection | undefined = direction === 'asc' ? 'desc' : direction === 'desc' ? undefined : 'asc'\n  const label =\n    nextDirection === 'asc'\n      ? `Sort ${sortField} ascending`\n      : nextDirection === 'desc'\n        ? `Sort ${sortField} descending`\n        : `Clear ${sortField} sorting`\n  const Icon = direction === 'asc' ? ArrowUp : direction === 'desc' ? ArrowDown : ArrowUpDown\n\n  return (\n    <button\n      aria-label={label}\n      aria-pressed={direction !== undefined}\n      className={cn(\n        'ml-1 inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',\n        direction !== undefined && 'bg-muted text-foreground',\n        className\n      )}\n      data-sort-direction={direction}\n      onClick={() => {\n        dispatch({\n          action,\n          payload: getPayload\n            ? getPayload({ direction: nextDirection, field: sortField, previousDirection: direction })\n            : nextDirection === undefined\n              ? undefined\n              : { field: sortField, direction: nextDirection },\n        })\n      }}\n      title={label}\n      type=\"button\"\n    >\n      <Icon className=\"size-3.5\" />\n    </button>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/data-table/column-sort/sort-header-button.tsx"
    }
  ],
  "type": "registry:component"
}