{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "table",
  "title": "table",
  "description": "A scrollable table with opt-in container-based stacked rows, a sticky first column, surface variants, and full-width loading and empty states.",
  "dependencies": [],
  "registryDependencies": [
    "@dwellir/dwellir-theme",
    "@dwellir/utils"
  ],
  "files": [
    {
      "path": "components/ui/table.tsx",
      "content": "'use client';\n\nimport type { ComponentProps, ReactNode } from 'react';\nimport { cn } from '@/lib/utils';\nimport {\n  bindScrollEdge,\n  stackClasses,\n  stickyClass,\n  stickyContainerClass,\n  surfaceClasses,\n  TableState,\n  updateScrollEdge,\n} from './table-responsive';\n\ntype TableProps = ComponentProps<'table'> & {\n  density?: 'default' | 'compact';\n  head?: 'eyebrow' | 'quiet';\n  /** Opt in to cards below the container size in stackBelow. */\n  stack?: 'auto' | 'never';\n  /** Container size token. Defaults to 3xl (768px). */\n  stackBelow?: keyof typeof stackClasses;\n  stickyFirstColumn?: boolean;\n  /** Ground for sticky cells and stacked cards. Defaults to card. */\n  surface?: keyof typeof surfaceClasses;\n  /** Replaces the table outside the horizontal scroller. */\n  state?: 'loading' | 'empty';\n  loadingRows?: number;\n  visibleColumns?: number;\n  empty?: ReactNode;\n};\n\nfunction Table({\n  className,\n  density = 'default',\n  head = 'eyebrow',\n  stack = 'never',\n  stackBelow = '3xl',\n  stickyFirstColumn = false,\n  surface = 'card',\n  state,\n  loadingRows = 5,\n  visibleColumns = 1,\n  empty,\n  ...props\n}: TableProps) {\n  if (state) {\n    return (\n      <TableState state={state} rows={loadingRows} columns={visibleColumns}>\n        {empty}\n      </TableState>\n    );\n  }\n  const responsive = stack === 'auto';\n  const containerClassName = cn(\n    'table-scroll w-full',\n    responsive && '@container/table',\n    stickyFirstColumn && stickyContainerClass,\n  );\n  const table = (\n    <table\n      data-slot=\"table\"\n      data-density={density}\n      // Only a quiet head is marked: the default adds no bytes to every table.\n      data-head={head === 'quiet' ? head : undefined}\n      role={responsive ? 'table' : undefined}\n      className={cn(\n        'group/table w-full caption-bottom text-sm',\n        responsive && stackClasses[stackBelow],\n        stickyFirstColumn && stickyClass,\n        (responsive || stickyFirstColumn) && surfaceClasses[surface],\n        className,\n      )}\n      {...props}\n    />\n  );\n  const scroller = (\n    <div\n      data-slot=\"table-container\"\n      // biome-ignore lint/a11y/noNoninteractiveTabindex: a scrollable region must take keyboard focus.\n      tabIndex={0}\n      ref={stickyFirstColumn ? bindScrollEdge : undefined}\n      onScroll={stickyFirstColumn ? (event) => updateScrollEdge(event.currentTarget) : undefined}\n      className={containerClassName}\n    >\n      {table}\n    </div>\n  );\n  return scroller;\n}\n\n/* The header row labels the columns: it takes no hover or open-disclosure tint. */\nfunction TableHeader({ className, ...props }: ComponentProps<'thead'>) {\n  return (\n    <thead\n      data-slot=\"table-header\"\n      className={cn(\n        '[&_tr]:border-b [&_tr]:hover:bg-transparent [&_tr]:has-aria-expanded:bg-transparent',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction TableBody({ className, ...props }: ComponentProps<'tbody'>) {\n  return (\n    <tbody\n      data-slot=\"table-body\"\n      className={cn('[&_tr:last-child]:border-0', className)}\n      {...props}\n    />\n  );\n}\n\nfunction TableFooter({ className, ...props }: ComponentProps<'tfoot'>) {\n  return (\n    <tfoot data-slot=\"table-footer\" className={cn('table-scroll-footer', className)} {...props} />\n  );\n}\n\nfunction TableRow({ className, ...props }: ComponentProps<'tr'>) {\n  return <tr data-slot=\"table-row\" className={cn('ui-table-row', className)} {...props} />;\n}\n\n/**\n * `highlight` marks one column of the table (a recommended plan): an aurora line down each side.\n * `numeric` marks a column of figures: aligned to the end, with digits of one width.\n * A row header (scope=\"row\") labels a row of facts: it wraps and takes the cells' padding.\n */\ninterface HighlightProps {\n  highlight?: boolean;\n  numeric?: boolean;\n}\n\nfunction TableHead({\n  className,\n  highlight,\n  numeric,\n  stackRole,\n  ...props\n}: ComponentProps<'th'> &\n  HighlightProps & {\n    stackRole?: 'title' | 'action' | 'hidden';\n  }) {\n  return (\n    <th\n      data-slot=\"table-head\"\n      data-stack-role={stackRole}\n      data-highlight={highlight ? '' : undefined}\n      data-numeric={numeric ? '' : undefined}\n      className={cn('table-head text-eyebrow', className)}\n      {...props}\n    />\n  );\n}\n\nfunction TableCell({\n  className,\n  highlight,\n  numeric,\n  stackRole,\n  label,\n  children,\n  ...props\n}: ComponentProps<'td'> &\n  HighlightProps & {\n    stackRole?: 'title' | 'action' | 'hidden';\n    label?: ReactNode;\n  }) {\n  return (\n    <td\n      data-slot=\"table-cell\"\n      data-stack-role={stackRole}\n      data-highlight={highlight ? '' : undefined}\n      data-numeric={numeric ? '' : undefined}\n      className={cn('ui-table-cell', className)}\n      {...props}\n    >\n      {label != null ? (\n        <>\n          <span data-slot=\"table-cell-label\" aria-hidden=\"true\">\n            {label}\n          </span>\n          <div data-slot=\"table-cell-value\">{children}</div>\n        </>\n      ) : (\n        children\n      )}\n    </td>\n  );\n}\n\nfunction TableCaption({ className, ...props }: ComponentProps<'caption'>) {\n  return (\n    <caption\n      data-slot=\"table-caption\"\n      className={cn('mt-4 text-sm text-muted-foreground', className)}\n      {...props}\n    />\n  );\n}\n\nexport type { TableProps };\n\nexport { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/table-responsive.tsx",
      "content": "import type { ReactNode } from 'react';\n\nconst stackClasses = {\n  lg: '@max-lg/table:ui-table-stack',\n  xl: '@max-xl/table:ui-table-stack',\n  '2xl': '@max-2xl/table:ui-table-stack',\n  '3xl': '@max-3xl/table:ui-table-stack',\n  '4xl': '@max-4xl/table:ui-table-stack',\n};\n\nconst surfaceClasses = {\n  card: 'table-surface-card',\n  background: 'table-surface-background',\n  muted: 'table-surface-muted',\n  glass: 'table-surface-glass',\n};\n\nconst stickyClass = 'ui-table-sticky';\nconst stickyContainerClass = 'ui-table-scroll';\n\nfunction updateScrollEdge(container: HTMLDivElement) {\n  container.toggleAttribute('data-scrolled', Math.abs(container.scrollLeft) > 0);\n}\n\nfunction revealFocusedCell(event: FocusEvent) {\n  const target = event.target;\n  if (!(target instanceof HTMLElement)) return;\n  const cell = target.closest('th, td');\n  if (!cell?.previousElementSibling) return;\n  // Native focus scrolling can leave partly visible controls beneath the pin.\n  target.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'instant' });\n}\n\n/** Resize the focus inset without React renders or scroll-time layout reads. */\nfunction bindScrollEdge(container: HTMLDivElement | null) {\n  if (!container) return;\n  const cell = container.querySelector('tr > :first-child');\n  const measure = () => {\n    const width =\n      cell && getComputedStyle(cell).position === 'sticky' ? cell.getBoundingClientRect().width : 0;\n    container.style.setProperty('--table-pin-width', `${width}px`);\n  };\n  const observer = new ResizeObserver(measure);\n  if (cell) observer.observe(cell);\n  observer.observe(container);\n  measure();\n  container.addEventListener('focusin', revealFocusedCell);\n  updateScrollEdge(container);\n  return () => {\n    observer.disconnect();\n    container.removeEventListener('focusin', revealFocusedCell);\n    container.style.removeProperty('--table-pin-width');\n    container.removeAttribute('data-scrolled');\n  };\n}\n\nfunction TableState({\n  state,\n  rows,\n  columns,\n  children,\n}: {\n  state: 'loading' | 'empty';\n  rows: number;\n  columns: number;\n  children?: ReactNode;\n}) {\n  return (\n    <div\n      data-slot=\"table-state\"\n      data-size=\"xs\"\n      role=\"status\"\n      aria-live=\"polite\"\n      aria-busy={state === 'loading'}\n      className=\"group/empty ui-table-state\"\n    >\n      {state === 'loading' ? (\n        <>\n          <span data-slot=\"table-loading-label\">Loading rows</span>\n          {Array.from({ length: rows }, (_, row) => (\n            <div data-slot=\"table-loading-row\" aria-hidden=\"true\" key={row}>\n              {Array.from({ length: columns }, (_, column) => (\n                <div data-slot=\"skeleton\" key={column} />\n              ))}\n            </div>\n          ))}\n        </>\n      ) : (\n        (children ?? (\n          <p data-slot=\"table-empty-description\">\n            No results. Rows will show here once there is data.\n          </p>\n        ))\n      )}\n    </div>\n  );\n}\n\nexport {\n  bindScrollEdge,\n  stackClasses,\n  stickyClass,\n  stickyContainerClass,\n  surfaceClasses,\n  TableState,\n  updateScrollEdge,\n};\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}