{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"table","type":"registry:component","title":"Table","description":"A plain, composable data table: sortable headers with an arrow that only shows on hover until active, and rows that tint when selected.","author":"Ryan","dependencies":["clsx","lucide-react","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/table.tsx","type":"registry:component","target":"@components/motion/table.tsx","content":"// easeui.dev/components/motion/table\nimport { ArrowUp } from \"lucide-react\";\nimport type { HTMLAttributes, TdHTMLAttributes, ThHTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\n/** Smallest width a dragged column can shrink to. */\nconst MIN_COLUMN_WIDTH = 80;\n\nexport function Table({ className, ...props }: HTMLAttributes<HTMLTableElement>) {\n  return (\n    <div className=\"w-full overflow-x-auto rounded-2xl shadow-[0_0_0_1px_var(--border)]\">\n      <table className={cn(\"w-full border-collapse text-sm\", className)} {...props} />\n    </div>\n  );\n}\n\nexport function TableHeader({ className, ...props }: HTMLAttributes<HTMLTableSectionElement>) {\n  return <thead className={cn(\"bg-muted/50\", className)} {...props} />;\n}\n\nexport function TableBody({ className, ...props }: HTMLAttributes<HTMLTableSectionElement>) {\n  return <tbody className={cn(\"divide-y divide-border\", className)} {...props} />;\n}\n\nexport interface TableRowProps extends HTMLAttributes<HTMLTableRowElement> {\n  /** Highlights the row as selected. Default false. */\n  selected?: boolean;\n}\n\nexport function TableRow({ selected = false, className, ...props }: TableRowProps) {\n  return (\n    <tr\n      data-selected={selected}\n      className={cn(\n        \"transition-colors duration-150 hover:bg-muted/40 data-[selected=true]:bg-accent/5\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport interface TableHeadProps extends ThHTMLAttributes<HTMLTableCellElement> {\n  /** Shows a sort arrow and makes the header clickable. Omit for a plain, unsortable column. */\n  sorted?: \"asc\" | \"desc\" | false;\n  onSort?: () => void;\n  /** Shows a drag handle on the trailing edge; reports the column's new width in pixels as the pointer moves. Width itself stays the caller's state, same as sort. */\n  onResize?: (width: number) => void;\n}\n\n/** A thin strip on a header's trailing edge that drags the column wider or narrower. */\nfunction ResizeHandle({ onResize }: { onResize: (width: number) => void }) {\n  return (\n    <span\n      aria-hidden=\"true\"\n      onPointerDown={(event) => {\n        event.preventDefault();\n        const th = event.currentTarget.closest(\"th\");\n        if (!th) return;\n        const startX = event.clientX;\n        const startWidth = th.getBoundingClientRect().width;\n        const previousCursor = document.body.style.cursor;\n        const previousSelect = document.body.style.userSelect;\n        document.body.style.cursor = \"col-resize\";\n        document.body.style.userSelect = \"none\";\n\n        const move = (moveEvent: PointerEvent) => {\n          onResize(Math.max(MIN_COLUMN_WIDTH, startWidth + moveEvent.clientX - startX));\n        };\n        const finish = () => {\n          window.removeEventListener(\"pointermove\", move);\n          window.removeEventListener(\"pointerup\", finish);\n          document.body.style.cursor = previousCursor;\n          document.body.style.userSelect = previousSelect;\n        };\n        window.addEventListener(\"pointermove\", move);\n        window.addEventListener(\"pointerup\", finish);\n      }}\n      className=\"absolute inset-y-2 right-0 w-1 cursor-col-resize touch-none rounded-full bg-foreground/15 opacity-0 transition-opacity duration-150 group-hover:opacity-100\"\n    />\n  );\n}\n\n/** A column heading. Pass onSort and it becomes a button with a sort arrow that only shows on hover until active. Pass onResize to add a drag handle on its trailing edge. */\nexport function TableHead({ sorted = false, onSort, onResize, className, children, ...props }: TableHeadProps) {\n  if (!onSort) {\n    return (\n      <th\n        className={cn(\n          \"group relative h-10 px-3 text-left text-xs font-medium text-muted-foreground\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n        {onResize ? <ResizeHandle onResize={onResize} /> : null}\n      </th>\n    );\n  }\n  return (\n    <th\n      className={cn(\"group relative h-10 px-1 text-left text-xs font-medium text-muted-foreground\", className)}\n      {...props}\n    >\n      <button\n        type=\"button\"\n        onClick={onSort}\n        className=\"inline-flex h-8 touch-manipulation items-center gap-1 rounded-md px-2 outline-none transition-colors duration-150 hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-foreground/40\"\n      >\n        {children}\n        <ArrowUp\n          aria-hidden=\"true\"\n          className={cn(\n            \"h-3 w-3 transition-[opacity,transform] duration-150\",\n            sorted ? \"opacity-100\" : \"opacity-0 group-hover:opacity-40\",\n            sorted === \"desc\" && \"rotate-180\",\n          )}\n        />\n      </button>\n      {onResize ? <ResizeHandle onResize={onResize} /> : null}\n    </th>\n  );\n}\n\nexport interface TableCellProps extends TdHTMLAttributes<HTMLTableCellElement> {\n  /** Bolds the cell in the foreground color, for a row's primary column, such as a name. Default false. */\n  emphasis?: boolean;\n  /** Shows a muted \"Calculating…\" placeholder in place of children, for a value still being computed. Default false. */\n  loading?: boolean;\n}\n\nexport function TableCell({ emphasis = false, loading = false, className, children, ...props }: TableCellProps) {\n  return (\n    <td\n      className={cn(\"px-3 py-2.5 align-middle\", emphasis && \"font-medium text-foreground\", className)}\n      {...props}\n    >\n      {loading ? (\n        <span className=\"inline-flex items-center gap-1.5 text-muted-foreground\">\n          Calculating…\n          <span\n            aria-hidden=\"true\"\n            className=\"h-1.5 w-1.5 animate-pulse rounded-full bg-muted-foreground motion-reduce:animate-none\"\n          />\n        </span>\n      ) : (\n        children\n      )}\n    </td>\n  );\n}\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"}]}