{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "record-table",
  "title": "Record Table",
  "description": "Table for lists of records with selection, bulk actions, loading, empty, error and pagination.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@ikas/button",
    "@ikas/checkbox",
    "@ikas/empty-state",
    "@ikas/skeleton",
    "@ikas/table",
    "@ikas/utils"
  ],
  "files": [
    {
      "path": "registry/ikas/record-table.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowDownIcon, ArrowUpDownIcon, ArrowUpIcon, ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from \"@/components/ui/table\"\nimport { EmptyState } from \"@/components/ikas/empty-state\"\n\ntype RecordTableColumn<T> = {\n  id: string\n  header: React.ReactNode\n  /** Cell renderer. Defaults to `String(row[id])`. */\n  cell?: (row: T) => React.ReactNode\n  align?: \"start\" | \"end\"\n  /** Tailwind width class, e.g. \"w-32\". */\n  className?: string\n  /** Hide below the md breakpoint. Keep the first column always visible. */\n  hideOnMobile?: boolean\n  /** Header becomes a sort button. Needs `sort` and `onSortChange` on the table. */\n  sortable?: boolean\n}\n\ntype RecordTableSort = {\n  /** Column id. */\n  id: string\n  direction: \"asc\" | \"desc\"\n}\n\ntype RecordTableProps<T> = {\n  columns: RecordTableColumn<T>[]\n  rows: T[]\n  /** Stable id per row, used for keys and selection. */\n  getRowId: (row: T) => string\n  /** Accessible name, e.g. \"Orders\". */\n  label: string\n\n  /** Enables checkboxes. Controlled. */\n  selectedIds?: string[]\n  onSelectedIdsChange?: (ids: string[]) => void\n  /** Rendered in the header row while rows are selected. Receives selected ids. */\n  bulkActions?: (selectedIds: string[]) => React.ReactNode\n\n  /** Makes rows clickable, usually to open the record. */\n  onRowClick?: (row: T) => void\n  /** Shows skeleton rows and disables selection and paging. */\n  loading?: boolean\n  /** Shown when `rows` is empty and not loading. Usually an EmptyState size=\"section\". */\n  emptyState?: React.ReactNode\n  /**\n   * The rows failed to load: replaces them with a danger EmptyState. `true` uses the\n   * default message; a string or node becomes its description. `loading` wins while set.\n   */\n  error?: React.ReactNode\n  /** Adds \"Tekrar dene\" to the error state. Set `loading` while the retry runs. */\n  onRetry?: () => void\n  /** Current sort. The table only shows it; sort `rows` yourself (or on the server). */\n  sort?: RecordTableSort\n  /** Called when a sortable header is clicked: ascending, then descending, then off. */\n  onSortChange?: (sort: RecordTableSort | undefined) => void\n  /** Search, filters, tabs. Rendered above the table inside the same card. */\n  toolbar?: React.ReactNode\n  /** Prev/next footer, shown when there is more than one page. */\n  pagination?: {\n    page: number\n    pageCount: number\n    onPageChange: (page: number) => void\n    /** e.g. \"1–20 of 134\" */\n    summary?: React.ReactNode\n  }\n  className?: string\n}\n\n/** List of records in a card: selection, bulk actions, paging, empty and error states. */\nfunction RecordTable<T>({\n  columns,\n  rows,\n  getRowId,\n  label,\n  selectedIds,\n  onSelectedIdsChange,\n  bulkActions,\n  onRowClick,\n  loading = false,\n  emptyState,\n  error,\n  onRetry,\n  sort,\n  onSortChange,\n  toolbar,\n  pagination,\n  className,\n}: RecordTableProps<T>) {\n  const failed = !loading && Boolean(error)\n  const selectable = Boolean(selectedIds && onSelectedIdsChange)\n  const selected = React.useMemo(() => new Set(selectedIds ?? []), [selectedIds])\n  const pageIds = rows.map(getRowId)\n  const selectedOnPage = pageIds.filter((id) => selected.has(id)).length\n  const allSelected = pageIds.length > 0 && selectedOnPage === pageIds.length\n  const someSelected = selectedOnPage > 0 && !allSelected\n  const showBulk = selectable && selected.size > 0 && Boolean(bulkActions)\n  const colSpan = columns.length + (selectable ? 1 : 0)\n\n  function cycleSort(id: string) {\n    if (!onSortChange) return\n    if (sort?.id !== id) onSortChange({ id, direction: \"asc\" })\n    else if (sort.direction === \"asc\") onSortChange({ id, direction: \"desc\" })\n    else onSortChange(undefined)\n  }\n\n  function toggleAll(checked: boolean) {\n    if (!onSelectedIdsChange) return\n    const next = new Set(selected)\n    for (const id of pageIds) {\n      if (checked) next.add(id)\n      else next.delete(id)\n    }\n    onSelectedIdsChange([...next])\n  }\n\n  function toggleOne(id: string, checked: boolean) {\n    if (!onSelectedIdsChange) return\n    const next = new Set(selected)\n    if (checked) next.add(id)\n    else next.delete(id)\n    onSelectedIdsChange([...next])\n  }\n\n  return (\n    <div\n      data-slot=\"record-table\"\n      className={cn(\"overflow-hidden rounded-xl bg-card shadow-card\", className)}\n    >\n      {toolbar && (\n        <div data-slot=\"record-table-toolbar\" className=\"flex flex-wrap items-center gap-2 border-b p-3\">\n          {toolbar}\n        </div>\n      )}\n      <Table aria-label={label} aria-busy={loading || undefined}>\n        <TableHeader className=\"bg-muted/60\">\n          <TableRow className=\"hover:bg-transparent\">\n            {selectable && (\n              <TableHead className=\"w-10 pr-0 pl-4\">\n                <Checkbox\n                  aria-label=\"Bu sayfadaki tüm satırları seç\"\n                  checked={allSelected ? true : someSelected ? \"indeterminate\" : false}\n                  onCheckedChange={(value) => toggleAll(value === true)}\n                  disabled={loading || failed || rows.length === 0}\n                />\n              </TableHead>\n            )}\n            {showBulk ? (\n              <TableHead colSpan={columns.length} className=\"py-1.5\">\n                <div className=\"flex items-center gap-3\">\n                  <span className=\"text-[13px] font-medium text-foreground\">{selected.size} seçildi</span>\n                  <div className=\"flex items-center gap-1.5\">{bulkActions?.([...selected])}</div>\n                </div>\n              </TableHead>\n            ) : (\n              columns.map((column, index) => {\n                const sortable = column.sortable && onSortChange\n                const direction = sort?.id === column.id ? sort.direction : undefined\n                const SortIcon = direction === \"asc\" ? ArrowUpIcon : direction === \"desc\" ? ArrowDownIcon : ArrowUpDownIcon\n                return (\n                  <TableHead\n                    key={column.id}\n                    aria-sort={sortable ? (direction === \"asc\" ? \"ascending\" : direction === \"desc\" ? \"descending\" : \"none\") : undefined}\n                    className={cn(\n                      column.align === \"end\" && \"text-right\",\n                      column.hideOnMobile && \"hidden md:table-cell\",\n                      index === 0 && !selectable && \"pl-4\",\n                      index === columns.length - 1 && \"pr-4\",\n                      column.className\n                    )}\n                  >\n                    {sortable ? (\n                      <button\n                        type=\"button\"\n                        onClick={() => cycleSort(column.id)}\n                        disabled={loading || failed}\n                        className={cn(\n                          \"group/sort -mx-1.5 inline-flex h-7 items-center gap-1 rounded-md px-1.5 font-medium outline-none hover:bg-foreground/[0.05] hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/30 disabled:pointer-events-none\",\n                          column.align === \"end\" && \"flex-row-reverse\",\n                          direction && \"text-foreground\"\n                        )}\n                      >\n                        {column.header}\n                        <SortIcon\n                          aria-hidden\n                          className={cn(\"size-3.5 shrink-0\", direction ? \"text-foreground\" : \"text-icon opacity-0 transition-opacity group-hover/sort:opacity-100 group-focus-visible/sort:opacity-100\")}\n                        />\n                      </button>\n                    ) : (\n                      column.header\n                    )}\n                  </TableHead>\n                )\n              })\n            )}\n          </TableRow>\n        </TableHeader>\n        <TableBody key={loading ? \"loading\" : failed ? \"error\" : \"rows\"} className=\"animate-in duration-200 ease-(--ease-out) fade-in-0 motion-reduce:animate-none\">\n          {loading ? (\n            Array.from({ length: 5 }, (_, i) => (\n              <TableRow key={`skeleton-${i}`} className=\"hover:bg-transparent\">\n                {selectable && (\n                  <TableCell className=\"pl-4\">\n                    <Skeleton className=\"size-4 rounded-[4px]\" />\n                  </TableCell>\n                )}\n                {columns.map((column, index) => (\n                  <TableCell\n                    key={column.id}\n                    className={cn(column.hideOnMobile && \"hidden md:table-cell\", index === 0 && !selectable && \"pl-4\")}\n                  >\n                    <Skeleton className={cn(\"h-4\", index === 0 ? \"w-32\" : \"w-16\", column.align === \"end\" && \"ml-auto\")} />\n                  </TableCell>\n                ))}\n              </TableRow>\n            ))\n          ) : failed ? (\n            <TableRow data-slot=\"record-table-error\" className=\"hover:bg-transparent\">\n              <TableCell colSpan={colSpan} className=\"p-0 whitespace-normal\">\n                <EmptyState\n                  status=\"danger\"\n                  title=\"Kayıtlar yüklenemedi\"\n                  description={error === true ? \"Bağlantınızı kontrol edip tekrar deneyin.\" : error}\n                  onRetry={onRetry}\n                />\n              </TableCell>\n            </TableRow>\n          ) : rows.length === 0 ? (\n            <TableRow className=\"hover:bg-transparent\">\n              <TableCell colSpan={colSpan} className=\"p-0 whitespace-normal\">\n                {emptyState ?? <p className=\"px-4 py-10 text-center text-sm text-muted-foreground\">Sonuç yok</p>}\n              </TableCell>\n            </TableRow>\n          ) : (\n            rows.map((row) => {\n              const id = getRowId(row)\n              const isSelected = selected.has(id)\n              return (\n                <TableRow\n                  key={id}\n                  data-state={isSelected ? \"selected\" : undefined}\n                  onClick={onRowClick ? () => onRowClick(row) : undefined}\n                  className={cn(onRowClick && \"cursor-pointer\", \"data-[state=selected]:bg-muted\")}\n                >\n                  {selectable && (\n                    <TableCell className=\"w-10 pr-0 pl-4\" onClick={(event) => event.stopPropagation()}>\n                      <Checkbox\n                        aria-label=\"Satırı seç\"\n                        checked={isSelected}\n                        onCheckedChange={(value) => toggleOne(id, value === true)}\n                      />\n                    </TableCell>\n                  )}\n                  {columns.map((column, index) => (\n                    <TableCell\n                      key={column.id}\n                      className={cn(\n                        column.align === \"end\" && \"text-right tabular-nums\",\n                        column.hideOnMobile && \"hidden md:table-cell\",\n                        index === 0 && !selectable && \"pl-4\",\n                        index === columns.length - 1 && \"pr-4\",\n                        column.className\n                      )}\n                    >\n                      {column.cell ? column.cell(row) : String((row as Record<string, unknown>)[column.id] ?? \"\")}\n                    </TableCell>\n                  ))}\n                </TableRow>\n              )\n            })\n          )}\n        </TableBody>\n      </Table>\n      {pagination && pagination.pageCount > 1 && !failed && (\n        <div className=\"flex items-center justify-between gap-3 border-t px-4 py-2.5\">\n          <p className=\"text-[13px] text-muted-foreground tabular-nums\">\n            {pagination.summary ?? `Sayfa ${pagination.page} / ${pagination.pageCount}`}\n          </p>\n          <div className=\"flex items-center gap-1.5\">\n            <Button\n              variant=\"outline\"\n              size=\"icon-sm\"\n              aria-label=\"Önceki sayfa\"\n              disabled={loading || pagination.page <= 1}\n              onClick={() => pagination.onPageChange(pagination.page - 1)}\n            >\n              <ChevronLeftIcon />\n            </Button>\n            <Button\n              variant=\"outline\"\n              size=\"icon-sm\"\n              aria-label=\"Sonraki sayfa\"\n              disabled={loading || pagination.page >= pagination.pageCount}\n              onClick={() => pagination.onPageChange(pagination.page + 1)}\n            >\n              <ChevronRightIcon />\n            </Button>\n          </div>\n        </div>\n      )}\n    </div>\n  )\n}\n\nexport { RecordTable, type RecordTableSort, type RecordTableColumn, type RecordTableProps }\n",
      "type": "registry:component",
      "target": "@components/ikas/record-table.tsx"
    }
  ],
  "docs": "https://builders.ikas.com/tr/docs/app-development/ui-kit/components/record-table",
  "categories": [
    "data-display"
  ],
  "type": "registry:component"
}