{"name":"data-table","type":"registry:component","files":[{"path":"data-table.tsx","type":"registry:component","target":"components/ui/data-table/data-table.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport {\n  flexRender,\n  getCoreRowModel,\n  getFilteredRowModel,\n  getPaginationRowModel,\n  getSortedRowModel,\n  useReactTable,\n  type Column,\n  type ColumnDef,\n  type ColumnFiltersState,\n  type OnChangeFn,\n  type PaginationState,\n  type Row,\n  type RowSelectionState,\n  type SortingState,\n  type Table as TanStackTable,\n  type VisibilityState,\n} from \"@tanstack/react-table\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport {\n  Empty,\n  EmptyDescription,\n  EmptyHeader,\n  EmptyTitle,\n} from \"@/components/ui/empty\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\"\n\nimport {\n  DataTableProvider,\n  getColumnTitle,\n  useDataTableContext,\n  type DataTableContextValue,\n  type DataTableDensity,\n} from \"./data-table-context\"\nimport { DataTableColumnHeader } from \"./data-table-column-header\"\nimport {\n  DataTableFilter,\n  type DataTableFilterConfig,\n} from \"./data-table-filter\"\nimport { DataTablePagination } from \"./data-table-pagination\"\nimport { DataTableSearch, DataTableToolbar } from \"./data-table-toolbar\"\nimport { DataTableViewOptions } from \"./data-table-view-options\"\n\ntype SearchConfig = {\n  placeholder?: string\n  debounceMs?: number\n  ariaLabel?: string\n}\n\ntype PageSizeConfig = {\n  pageSizeOptions?: number[]\n}\n\nfunction isPaginationState(value: unknown): value is PaginationState {\n  return (\n    typeof value === \"object\" &&\n    value !== null &&\n    typeof (value as PaginationState).pageIndex === \"number\" &&\n    typeof (value as PaginationState).pageSize === \"number\"\n  )\n}\n\nexport interface DataTableProps<TData> {\n  columns: ColumnDef<TData>[]\n  data: TData[]\n  table?: TanStackTable<TData>\n  children?: React.ReactNode\n\n  search?: boolean | SearchConfig\n  filters?: DataTableFilterConfig[]\n  selectable?: boolean\n  pagination?: boolean | PageSizeConfig | PaginationState\n  pageSizeOptions?: number[]\n  toolbarActions?: React.ReactNode\n  density?: DataTableDensity\n  framed?: boolean\n  caption?: string\n  getRowId?: (originalRow: TData, index: number) => string\n  getRowLabel?: (row: Row<TData>) => string\n  pageCount?: number\n\n  loading?: boolean\n  error?: string | null\n  onRetry?: () => void\n  rowCount?: number\n  emptyState?: React.ReactNode\n  noResultsState?: React.ReactNode\n\n  manualSorting?: boolean\n  manualFiltering?: boolean\n  manualPagination?: boolean\n\n  sorting?: SortingState\n  onSortingChange?: OnChangeFn<SortingState>\n  globalFilter?: string\n  onGlobalFilterChange?: OnChangeFn<string>\n  columnFilters?: ColumnFiltersState\n  onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>\n  columnVisibility?: VisibilityState\n  onColumnVisibilityChange?: OnChangeFn<VisibilityState>\n  rowSelection?: RowSelectionState\n  onRowSelectionChange?: OnChangeFn<RowSelectionState>\n  onPaginationChange?: OnChangeFn<PaginationState>\n}\n\nfunction resolveOnChange<T>(\n  onChange: OnChangeFn<T> | undefined,\n  getValue: () => T,\n): OnChangeFn<T> | undefined {\n  if (!onChange) {\n    return undefined\n  }\n\n  return (updaterOrValue) => {\n    const nextValue =\n      typeof updaterOrValue === \"function\"\n        ? (updaterOrValue as (previous: T) => T)(getValue())\n        : updaterOrValue\n\n    onChange(nextValue)\n  }\n}\n\nexport function dataTableFacetFilterFn<TData>(\n  row: Row<TData>,\n  columnId: string,\n  filterValue: unknown,\n) {\n  if (!Array.isArray(filterValue) || filterValue.length === 0) {\n    return true\n  }\n\n  return filterValue.includes(String(row.getValue(columnId)))\n}\n\nexport function DataTable<TData>({\n  columns,\n  data,\n  table: externalTable,\n  children,\n\n  search = false,\n  filters,\n  selectable = false,\n  pagination: paginationProp = false,\n  pageSizeOptions,\n  toolbarActions,\n  density = \"default\",\n  framed = true,\n  caption,\n  getRowId,\n  getRowLabel,\n  pageCount,\n\n  loading = false,\n  error = null,\n  onRetry,\n  rowCount,\n  emptyState,\n  noResultsState,\n\n  manualSorting = false,\n  manualFiltering = false,\n  manualPagination = false,\n\n  sorting: controlledSorting,\n  onSortingChange,\n  globalFilter: controlledGlobalFilter,\n  onGlobalFilterChange,\n  columnFilters: controlledColumnFilters,\n  onColumnFiltersChange,\n  columnVisibility: controlledColumnVisibility,\n  onColumnVisibilityChange,\n  rowSelection: controlledRowSelection,\n  onRowSelectionChange,\n  onPaginationChange,\n}: DataTableProps<TData>) {\n  const facetIds = React.useMemo(\n    () => new Set((filters ?? []).map((facet) => facet.columnId)),\n    [filters],\n  )\n\n  const resolvedColumns = React.useMemo(() => {\n    const prepared = columns.map((column) => {\n      const id =\n        (column as { id?: string }).id ??\n        (\"accessorKey\" in column ? String(column.accessorKey) : undefined)\n\n      if (\n        id &&\n        facetIds.has(id) &&\n        !(\"filterFn\" in column && column.filterFn)\n      ) {\n        return {\n          ...column,\n          filterFn: dataTableFacetFilterFn,\n        } as ColumnDef<TData>\n      }\n\n      return column\n    })\n\n    if (selectable && !columns.some((column) => column.id === \"select\")) {\n      prepared.unshift({\n        id: \"select\",\n        enableSorting: false,\n        enableHiding: false,\n        size: 44,\n        cell: function SelectCell({ row }) {\n          return <DataTableRowCheckbox row={row} getRowLabel={getRowLabel} />\n        },\n      } as ColumnDef<TData>)\n    }\n\n    return prepared\n  }, [columns, facetIds, selectable, getRowLabel])\n\n  const searchEnabled = !!search\n  const searchConfig = typeof search === \"object\" ? search : undefined\n\n  const paginationStateProvided = isPaginationState(paginationProp)\n    ? paginationProp\n    : undefined\n  const pageSizeConfig =\n    typeof paginationProp === \"object\" && !isPaginationState(paginationProp)\n      ? paginationProp\n      : undefined\n  const paginationEnabled =\n    paginationProp !== false ||\n    !!paginationStateProvided ||\n    !!onPaginationChange\n\n  const state = {\n    ...(controlledSorting !== undefined && { sorting: controlledSorting }),\n    ...(controlledGlobalFilter !== undefined && {\n      globalFilter: controlledGlobalFilter,\n    }),\n    ...(controlledColumnFilters !== undefined && {\n      columnFilters: controlledColumnFilters,\n    }),\n    ...(controlledColumnVisibility !== undefined && {\n      columnVisibility: controlledColumnVisibility,\n    }),\n    ...(controlledRowSelection !== undefined && {\n      rowSelection: controlledRowSelection,\n    }),\n    ...(paginationStateProvided !== undefined && {\n      pagination: paginationStateProvided,\n    }),\n  }\n\n  const internalTable: TanStackTable<TData> = useReactTable({\n    data,\n    columns: resolvedColumns,\n    getCoreRowModel: getCoreRowModel(),\n    ...(manualSorting\n      ? { manualSorting: true as const }\n      : { getSortedRowModel: getSortedRowModel() }),\n    ...(manualFiltering\n      ? { manualFiltering: true as const }\n      : { getFilteredRowModel: getFilteredRowModel() }),\n    ...(manualPagination\n      ? { manualPagination: true as const, pageCount }\n      : paginationEnabled\n        ? { getPaginationRowModel: getPaginationRowModel() }\n        : {}),\n    enableRowSelection: selectable,\n    getRowId,\n    ...(rowCount !== undefined && { rowCount }),\n    state,\n    ...(onSortingChange !== undefined && {\n      onSortingChange: resolveOnChange(onSortingChange, () =>\n        internalTable?.getState().sorting ?? [],\n      ),\n    }),\n    ...(onGlobalFilterChange !== undefined && {\n      onGlobalFilterChange: resolveOnChange(onGlobalFilterChange, () =>\n        String(internalTable?.getState().globalFilter ?? \"\"),\n      ),\n    }),\n    ...(onColumnFiltersChange !== undefined && {\n      onColumnFiltersChange: resolveOnChange(onColumnFiltersChange, () =>\n        internalTable?.getState().columnFilters ?? [],\n      ),\n    }),\n    ...(onColumnVisibilityChange !== undefined && {\n      onColumnVisibilityChange: resolveOnChange(\n        onColumnVisibilityChange,\n        () => internalTable?.getState().columnVisibility ?? {},\n      ),\n    }),\n    ...(onRowSelectionChange !== undefined && {\n      onRowSelectionChange: resolveOnChange(onRowSelectionChange, () =>\n        internalTable?.getState().rowSelection ?? {},\n      ),\n    }),\n    ...(onPaginationChange !== undefined && {\n      onPaginationChange: resolveOnChange(onPaginationChange, () =>\n        internalTable?.getState().pagination ?? { pageIndex: 0, pageSize: 10 },\n      ),\n    }),\n  })\n\n  const table = externalTable ?? internalTable\n\n  const contextValue: DataTableContextValue<TData> = {\n    table,\n    caption,\n    getRowLabel,\n    loading,\n    error,\n    onRetry,\n    rowCount,\n    emptyState,\n    noResultsState,\n    selectable,\n    density,\n  }\n\n  return (\n    <DataTableProvider value={contextValue}>\n      <div\n        data-slot=\"data-table\"\n        data-density={density}\n        className={cn(\n          \"w-full min-w-0\",\n          framed &&\n            \"overflow-clip rounded-[var(--hui-radius-3)] border-[0.5px] border-[var(--hui-color-border-base-primary)]\",\n        )}\n      >\n        {children ?? (\n          <>\n            {(searchEnabled ||\n              (filters && filters.length > 0) ||\n              toolbarActions) && (\n              <DataTableToolbar className=\"flex-wrap gap-[var(--hui-space-2)] border-b-[0.5px] p-[var(--hui-space-3)] sm:p-[var(--hui-space-4)]\">\n                {searchEnabled && (\n                  <DataTableSearch\n                    placeholder={searchConfig?.placeholder}\n                    debounceMs={searchConfig?.debounceMs}\n                    ariaLabel={searchConfig?.ariaLabel}\n                  />\n                )}\n                {filters && filters.length > 0 && (\n                  <DataTableFilter facets={filters} />\n                )}\n                <DataTableViewOptions />\n                <div aria-hidden className=\"flex-1\" />\n                {toolbarActions}\n              </DataTableToolbar>\n            )}\n\n            <DataTableContent />\n\n            {(paginationEnabled || selectable) && (\n              <DataTableFooter className=\"flex-wrap gap-x-[var(--hui-space-4)] gap-y-[var(--hui-space-2)] px-[var(--hui-space-4)] py-[var(--hui-space-3)]\">\n                {selectable && <DataTableSelectionSummary />}\n                {paginationEnabled && (\n                  <DataTablePagination\n                    pageSizeOptions={\n                      pageSizeOptions ?? pageSizeConfig?.pageSizeOptions\n                    }\n                  />\n                )}\n              </DataTableFooter>\n            )}\n          </>\n        )}\n      </div>\n    </DataTableProvider>\n  )\n}\n\nfunction DataTableRowCheckbox<TData>({\n  row,\n  getRowLabel,\n}: {\n  row: Row<TData>\n  getRowLabel?: (row: Row<TData>) => string\n}) {\n  return (\n    <Checkbox\n      aria-label={getRowLabel?.(row) ?? `Select row ${row.index + 1}`}\n      checked={row.getIsSelected()}\n      onCheckedChange={(value) => row.toggleSelected(!!value)}\n    />\n  )\n}\n\nfunction DataTableEmptyMessage({\n  title,\n  description,\n  action,\n}: {\n  title: string\n  description: string\n  action?: React.ReactNode\n}) {\n  return (\n    <Empty className=\"p-[var(--hui-space-8)]\">\n      <EmptyHeader>\n        <EmptyTitle>{title}</EmptyTitle>\n        <EmptyDescription>{description}</EmptyDescription>\n      </EmptyHeader>\n      {action}\n    </Empty>\n  )\n}\n\nfunction DataTableSelectAllCheckbox() {\n  const { table } = useDataTableContext<unknown>()\n\n  return (\n    <Checkbox\n      aria-label=\"Select all rows on this page\"\n      checked={table.getIsAllPageRowsSelected()}\n      indeterminate={\n        table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected()\n      }\n      onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}\n    />\n  )\n}\n\nexport function DataTableContent({ className }: { className?: string }) {\n  const {\n    table,\n    caption,\n    loading,\n    error,\n    onRetry,\n    emptyState,\n    noResultsState,\n    density,\n  } = useDataTableContext<unknown>()\n\n  const rows = table.getRowModel().rows\n  const leafColumns = table.getVisibleLeafColumns()\n  const columnCount = leafColumns.length\n\n  const state = table.getState()\n  const isFiltering =\n    Boolean(state.globalFilter) ||\n    Boolean(state.columnFilters && state.columnFilters.length > 0)\n\n  const skeletonRowCount = Math.min(\n    Math.max(state.pagination?.pageSize ?? 8, 6),\n    10,\n  )\n\n  const alignClass = (align?: string) =>\n    align === \"right\"\n      ? \"text-right\"\n      : align === \"center\"\n        ? \"text-center\"\n        : undefined\n\n  const headerMeta = (column: Column<unknown, unknown>) =>\n    (column.columnDef.meta ?? {}) as {\n      align?: \"left\" | \"center\" | \"right\"\n    }\n\n  return (\n    <Table aria-label={caption} className={className}>\n      <TableHeader>\n        <TableRow className=\"hover:bg-transparent\">\n          {leafColumns.map((column) => {\n            const sorted = column.getIsSorted()\n            const meta = headerMeta(column)\n            const canSort = column.getCanSort()\n\n            return (\n              <TableHead\n                key={column.id}\n                scope=\"col\"\n                aria-sort={\n                  sorted === \"asc\"\n                    ? \"ascending\"\n                    : sorted === \"desc\"\n                      ? \"descending\"\n                      : undefined\n                }\n                className={alignClass(meta.align)}\n              >\n                {column.id === \"select\" ? (\n                  <DataTableSelectAllCheckbox />\n                ) : canSort ? (\n                  <DataTableColumnHeader\n                    column={column}\n                    title={getColumnTitle(column)}\n                    className={\n                      meta.align === \"right\"\n                        ? \"justify-end\"\n                        : meta.align === \"center\"\n                          ? \"justify-center\"\n                          : undefined\n                    }\n                  />\n                ) : (\n                  getColumnTitle(column)\n                )}\n              </TableHead>\n            )\n          })}\n        </TableRow>\n      </TableHeader>\n\n      <TableBody>\n        {error ? (\n          <TableRow>\n            <TableCell colSpan={columnCount} className=\"h-28 text-center\">\n              <p>{error}</p>\n              {onRetry && (\n                <Button\n                  variant=\"outline\"\n                  size=\"sm\"\n                  className=\"mt-[var(--hui-space-3)]\"\n                  onClick={onRetry}\n                >\n                  Try again\n                </Button>\n              )}\n            </TableCell>\n          </TableRow>\n        ) : loading ? (\n          Array.from({ length: skeletonRowCount }).map((_, rowIndex) => (\n            <TableRow key={`loading-${rowIndex}`}>\n              {leafColumns.map((column, columnIndex) => (\n                <TableCell key={column.id}>\n                  <Skeleton\n                    className={cn(\n                      \"h-[var(--hui-space-5)]\",\n                      columnIndex % 3 === 1\n                        ? \"w-full max-w-24\"\n                        : columnIndex % 3 === 2\n                          ? \"w-12\"\n                          : \"w-20\",\n                    )}\n                  />\n                </TableCell>\n              ))}\n            </TableRow>\n          ))\n        ) : rows.length === 0 ? (\n          <TableRow>\n            <TableCell colSpan={columnCount} className=\"p-0\">\n              {isFiltering ? (\n                noResultsState ?? (\n                  <DataTableEmptyMessage\n                    title=\"No results\"\n                    description=\"Nothing matches your current search and filters.\"\n                    action={\n                      <Button\n                        variant=\"outline\"\n                        size=\"sm\"\n                        onClick={() => {\n                          table.setGlobalFilter(\"\")\n                          table.setColumnFilters([])\n                        }}\n                      >\n                        Clear filters\n                      </Button>\n                    }\n                  />\n                )\n              ) : (\n                emptyState ?? (\n                  <DataTableEmptyMessage\n                    title=\"No data yet\"\n                    description=\"Rows will appear here once they are added.\"\n                  />\n                )\n              )}\n            </TableCell>\n          </TableRow>\n        ) : (\n          rows.map((row) => (\n            <TableRow\n              key={row.id}\n              className=\"hover:bg-[var(--hui-color-background-base-primary-hover)]\"\n              data-state={row.getIsSelected() ? \"selected\" : undefined}\n            >\n              {row.getVisibleCells().map((cell) => {\n                const meta = (cell.column.columnDef.meta ?? {}) as {\n                  align?: string\n                }\n\n                return (\n                  <TableCell\n                    key={cell.id}\n                    className={cn(\n                      density === \"compact\" && \"py-[var(--hui-space-2)]\",\n                      alignClass(meta.align),\n                    )}\n                  >\n                    {flexRender(cell.column.columnDef.cell, cell.getContext())}\n                  </TableCell>\n                )\n              })}\n            </TableRow>\n          ))\n        )}\n      </TableBody>\n    </Table>\n  )\n}\n\nexport function DataTableFooter({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"data-table-footer\"\n      className={cn(\"flex items-center\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport function DataTableSelectionSummary({\n  className,\n  actions,\n}: {\n  className?: string\n  actions?: React.ReactNode\n}) {\n  const { table } = useDataTableContext<unknown>()\n  const selectedCount = Object.values(\n    table.getState().rowSelection,\n  ).filter(Boolean).length\n\n  if (selectedCount === 0) {\n    return null\n  }\n\n  return (\n    <div\n      data-slot=\"data-table-selection-summary\"\n      className={cn(\n        \"flex flex-wrap items-center gap-x-[var(--hui-space-3)] gap-y-[var(--hui-space-2)] [font-size:var(--hui-font-size-small)] text-[var(--hui-color-foreground-base-secondary)]\",\n        className,\n      )}\n    >\n      <span>\n        {selectedCount} {selectedCount === 1 ? \"row\" : \"rows\"} selected\n      </span>\n      <Button\n        variant=\"ghost\"\n        size=\"xs\"\n        onClick={() => table.resetRowSelection()}\n      >\n        Clear\n      </Button>\n      {actions}\n    </div>\n  )\n}\n\nexport {\n  DataTableColumnHeader,\n  DataTableViewOptions,\n  useDataTableContext,\n  getColumnTitle,\n  type DataTableDensity,\n}\nexport {\n  DataTableSearch,\n  DataTableToolbar,\n}\nexport { DataTableFilter, type DataTableFilterConfig }\nexport { DataTablePagination }\n"},{"path":"data-table-column-header.tsx","type":"registry:component","target":"components/ui/data-table/data-table-column-header.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport type { Column } from \"@tanstack/react-table\"\nimport {\n  ArrowDown as ArrowDownIcon,\n  ArrowUp as ArrowUpIcon,\n  ChevronsUpDown as ChevronsUpDownIcon,\n} from \"honestui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\ninterface DataTableColumnHeaderProps<TData, TValue>\n  extends React.ComponentProps<\"div\"> {\n  column: Column<TData, TValue>\n  title: string\n}\n\nfunction toggleDataTableColumnSorting<TData, TValue>(\n  column: Column<TData, TValue>,\n) {\n  const sorted = column.getIsSorted()\n\n  if (!sorted) {\n    column.toggleSorting(false)\n  } else if (sorted === \"asc\") {\n    column.toggleSorting(true)\n  } else {\n    column.clearSorting()\n  }\n}\n\nexport function DataTableColumnHeader<TData, TValue>({\n  column,\n  title,\n  className,\n  ...props\n}: DataTableColumnHeaderProps<TData, TValue>) {\n  const sorted = column.getIsSorted()\n\n  return (\n    <div className={cn(\"flex items-center gap-[var(--hui-space-1)]\", className)} {...props}>\n      <Button\n        variant=\"ghost\"\n        size=\"sm\"\n        className=\"-ms-[var(--hui-space-2)] h-auto border-0 px-[var(--hui-space-2)] py-[var(--hui-space-1)] font-medium [font-size:var(--hui-font-size-small)] text-[var(--hui-color-foreground-base-tertiary)]\"\n        aria-label={`Sort by ${title}`}\n        onClick={() => toggleDataTableColumnSorting(column)}\n      >\n        <span>{title}</span>\n        {sorted === \"asc\" ? (\n          <ArrowUpIcon aria-hidden />\n        ) : sorted === \"desc\" ? (\n          <ArrowDownIcon aria-hidden />\n        ) : (\n          <ChevronsUpDownIcon aria-hidden className=\"opacity-60\" />\n        )}\n      </Button>\n    </div>\n  )\n}\n"},{"path":"data-table-context.tsx","type":"registry:component","target":"components/ui/data-table/data-table-context.tsx","content":"import * as React from \"react\"\nimport type { Row } from \"@tanstack/react-table\"\n\nexport type DataTableDensity = \"default\" | \"compact\"\n\nexport type DataTableContextValue<TData> = {\n  table: import(\"@tanstack/react-table\").Table<TData>\n  caption?: string\n  getRowLabel?: (row: Row<TData>) => string\n  loading: boolean\n  error: string | null\n  onRetry?: () => void\n  rowCount?: number\n  emptyState?: React.ReactNode\n  noResultsState?: React.ReactNode\n  selectable: boolean\n  density: DataTableDensity\n}\n\nconst DataTableContext =\n  React.createContext<DataTableContextValue<unknown> | null>(null)\n\nexport function DataTableProvider<TData>({\n  value,\n  children,\n}: {\n  value: DataTableContextValue<TData>\n  children: React.ReactNode\n}) {\n  return (\n    <DataTableContext.Provider\n      value={value as unknown as DataTableContextValue<unknown>}\n    >\n      {children}\n    </DataTableContext.Provider>\n  )\n}\n\nexport function useDataTableContext<\n  TData,\n>(): DataTableContextValue<TData> {\n  const context = React.useContext(DataTableContext)\n\n  if (!context) {\n    throw new Error(\n      \"DataTable parts must be rendered inside <DataTable>.\",\n    )\n  }\n\n  return context as DataTableContextValue<TData>\n}\n\nexport function getColumnTitle(column: {\n  id: string\n  columnDef: { header?: unknown; meta?: unknown }\n}): string {\n  const meta = (column.columnDef.meta ?? {}) as { label?: string }\n\n  if (meta.label) {\n    return meta.label\n  }\n\n  if (typeof column.columnDef.header === \"string\") {\n    return column.columnDef.header\n  }\n\n  return column.id\n}\n"},{"path":"data-table-filter.tsx","type":"registry:component","target":"components/ui/data-table/data-table-filter.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { Funnel as FunnelIcon } from \"honestui/icons\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Menu,\n  MenuCheckboxItem,\n  MenuGroup,\n  MenuGroupLabel,\n  MenuItem,\n  MenuPopup,\n  MenuSeparator,\n  MenuTrigger,\n} from \"@/components/ui/menu\"\n\nimport { useDataTableContext } from \"./data-table-context\"\n\nexport interface DataTableFilterOption {\n  label: string\n  value: string\n}\n\nexport interface DataTableFilterConfig {\n  columnId: string\n  title: string\n  options: DataTableFilterOption[]\n}\n\nfunction getActiveFilterCount(\n  filters: Array<{ id: string; value: unknown }>,\n  facets: DataTableFilterConfig[],\n) {\n  return filters.filter(\n    (filter) =>\n      facets.some((facet) => facet.columnId === filter.id) &&\n      Array.isArray(filter.value) &&\n      filter.value.length > 0,\n  ).length\n}\n\nexport function DataTableFilter({\n  facets,\n  className,\n}: {\n  facets: DataTableFilterConfig[]\n  className?: string\n}) {\n  const { table } = useDataTableContext<unknown>()\n  const columnFilters = table.getState().columnFilters ?? []\n  const activeCount = getActiveFilterCount(columnFilters, facets)\n\n  const selectedValues = (columnId: string): string[] => {\n    const match = columnFilters.find((filter) => filter.id === columnId)\n\n    return Array.isArray(match?.value) ? (match.value as string[]) : []\n  }\n\n  const setValueChecked = (\n    columnId: string,\n    optionValue: string,\n    checked: boolean,\n  ) => {\n    table.setColumnFilters((currentFilters) => {\n      const currentFilter = currentFilters.find(\n        (filter) => filter.id === columnId,\n      )\n      const currentValues = Array.isArray(currentFilter?.value)\n        ? (currentFilter.value as string[])\n        : []\n      const nextValues = checked\n        ? [...new Set([...currentValues, optionValue])]\n        : currentValues.filter((value) => value !== optionValue)\n      const otherFilters = currentFilters.filter(\n        (filter) => filter.id !== columnId,\n      )\n\n      return [\n        ...otherFilters,\n        ...(nextValues.length > 0\n          ? [{ id: columnId, value: nextValues }]\n          : []),\n      ]\n    })\n  }\n\n  const clearAll = () => {\n    const facetIds = new Set(facets.map((facet) => facet.columnId))\n\n    table.setColumnFilters((currentFilters) =>\n      currentFilters.filter((filter) => !facetIds.has(filter.id)),\n    )\n  }\n\n  return (\n    <Menu>\n      <MenuTrigger\n        render={\n          <Button variant=\"ghost\" size=\"sm\" className={className} />\n        }\n      >\n        <FunnelIcon aria-hidden className=\"opacity-72\" />\n        Filters\n        {activeCount > 0 && (\n          <span className=\"inline-flex min-w-[var(--hui-space-5)] items-center justify-center rounded-[var(--hui-radius-full)] bg-[var(--hui-color-background-neutral-secondary)] px-[var(--hui-space-1)] [font-size:var(--hui-font-size-micro)]\">\n            {activeCount}\n          </span>\n        )}\n      </MenuTrigger>\n      <MenuPopup align=\"start\">\n        {facets.map((facet, index) => (\n          <MenuGroup key={facet.columnId}>\n            {index > 0 && <MenuSeparator />}\n            <MenuGroupLabel>{facet.title}</MenuGroupLabel>\n            {facet.options.map((option) => (\n              <MenuCheckboxItem\n                key={option.value}\n                checked={selectedValues(facet.columnId).includes(option.value)}\n                onCheckedChange={(checked) =>\n                  setValueChecked(facet.columnId, option.value, checked)\n                }\n              >\n                {option.label}\n              </MenuCheckboxItem>\n            ))}\n          </MenuGroup>\n        ))}\n        {activeCount > 0 && (\n          <>\n            <MenuSeparator />\n            <MenuItem onClick={clearAll}>Clear all</MenuItem>\n          </>\n        )}\n      </MenuPopup>\n    </Menu>\n  )\n}\n"},{"path":"data-table-pagination.tsx","type":"registry:component","target":"components/ui/data-table/data-table-pagination.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\nimport {\n  ChevronLeft as ChevronLeftIcon,\n  ChevronRight as ChevronRightIcon,\n} from \"honestui/icons\"\nimport {\n  Pagination,\n  PaginationContent,\n  PaginationEllipsis,\n  PaginationItem,\n  PaginationLink,\n} from \"@/components/ui/pagination\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\n\nimport { useDataTableContext } from \"./data-table-context\"\n\nfunction getPageItems(currentPage: number, pageCount: number) {\n  const pages = new Set<number>([\n    0,\n    pageCount - 1,\n    currentPage - 1,\n    currentPage,\n    currentPage + 1,\n  ])\n\n  const items: Array<{ type: \"page\"; page: number } | { type: \"ellipsis\" }> = []\n\n  for (let page = 0; page < pageCount; page++) {\n    if (pages.has(page)) {\n      items.push({ type: \"page\", page })\n    } else if (\n      items[items.length - 1] &&\n      items[items.length - 1].type !== \"ellipsis\"\n    ) {\n      items.push({ type: \"ellipsis\" })\n    }\n  }\n\n  return items\n}\n\nexport function DataTablePagination({\n  pageSizeOptions = [10, 20, 50, 100],\n  className,\n}: {\n  pageSizeOptions?: number[]\n  className?: string\n}) {\n  const { table, rowCount } = useDataTableContext<unknown>()\n\n  const state = table.getState().pagination\n  const pageSize = state?.pageSize ?? pageSizeOptions[0]\n  const pageIndex = state?.pageIndex ?? 0\n\n  const filteredCount = table.getFilteredRowModel().rows.length\n  const totalRows = rowCount !== undefined ? rowCount : filteredCount\n  const pageCount = Math.max(table.getPageCount(), 1)\n\n  const rangeStart = totalRows === 0 ? 0 : pageIndex * pageSize + 1\n  const rangeEnd = Math.min((pageIndex + 1) * pageSize, totalRows)\n\n  const selectItems = pageSizeOptions.map((size) => ({\n    label: String(size),\n    value: String(size),\n  }))\n\n  const iconButtonClass = buttonVariants({\n    variant: \"link\",\n    size: \"icon\",\n    className: \"size-7\",\n  })\n\n  return (\n    <div\n      data-slot=\"data-table-pagination\"\n      className={cn(\n        \"ms-auto flex flex-wrap items-center justify-end gap-x-[var(--hui-space-4)] gap-y-[var(--hui-space-2)]\",\n        className,\n      )}\n    >\n      <div className=\"flex items-center gap-[var(--hui-space-2)] [font-size:var(--hui-font-size-small)] text-[var(--hui-color-foreground-base-secondary)]\">\n        <span>Rows per page</span>\n        <Select\n          items={selectItems}\n          value={String(pageSize)}\n          onValueChange={(value) =>\n            table.setPagination({\n              pageIndex: 0,\n              pageSize: Number(value),\n            })\n          }\n        >\n          <SelectTrigger size=\"sm\" className=\"w-16\" aria-label=\"Rows per page\">\n            <SelectValue />\n          </SelectTrigger>\n          <SelectContent>\n            {selectItems.map((item) => (\n              <SelectItem key={item.value} value={item.value}>\n                {item.label}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n      </div>\n\n      <span\n        aria-live=\"polite\"\n        className=\"[font-size:var(--hui-font-size-small)] text-[var(--hui-color-foreground-base-secondary)] tabular-nums\"\n      >\n        {rangeStart}-{rangeEnd} of {totalRows}\n      </span>\n\n      <Pagination className=\"mx-0 w-auto justify-end\" aria-label=\"Table pagination\">\n        <PaginationContent>\n          <PaginationItem>\n            <PaginationLink\n              aria-label=\"Go to previous page\"\n              render={\n                <button\n                  type=\"button\"\n                  disabled={pageIndex === 0}\n                  onClick={() => table.previousPage()}\n                />\n              }\n              className={cn(iconButtonClass)}\n            >\n              <ChevronLeftIcon />\n            </PaginationLink>\n          </PaginationItem>\n\n          {getPageItems(pageIndex, pageCount).map((item, index) =>\n            item.type === \"page\" ? (\n              <PaginationItem key={item.page}>\n                <PaginationLink\n                  isActive={item.page === pageIndex}\n                  render={\n                    <button\n                      type=\"button\"\n                      onClick={() => table.setPageIndex(item.page)}\n                    />\n                  }\n                  className={cn(\n                    iconButtonClass,\n                    \"tabular-nums\",\n                    item.page === pageIndex\n                      ? \"text-[var(--hui-color-foreground-base-primary)] [font-weight:var(--hui-font-weight-semibold)]\"\n                      : \"text-[var(--hui-color-foreground-base-secondary)]\"\n                  )}\n                >\n                  {item.page + 1}\n                </PaginationLink>\n              </PaginationItem>\n            ) : (\n              <PaginationItem key={`ellipsis-${index}`}>\n                <PaginationEllipsis />\n              </PaginationItem>\n            ),\n          )}\n\n          <PaginationItem>\n            <PaginationLink\n              aria-label=\"Go to next page\"\n              render={\n                <button\n                  type=\"button\"\n                  disabled={pageIndex >= pageCount - 1}\n                  onClick={() => table.nextPage()}\n                />\n              }\n              className={cn(iconButtonClass)}\n            >\n              <ChevronRightIcon />\n            </PaginationLink>\n          </PaginationItem>\n        </PaginationContent>\n      </Pagination>\n    </div>\n  )\n}\n"},{"path":"data-table-toolbar.tsx","type":"registry:component","target":"components/ui/data-table/data-table-toolbar.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { Search as SearchIcon } from \"honestui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Input } from \"@/components/ui/input\"\nimport { Toolbar } from \"@/components/ui/toolbar\"\n\nimport { useDataTableContext } from \"./data-table-context\"\n\nexport function DataTableToolbar({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof Toolbar>) {\n  return (\n    <Toolbar\n      data-slot=\"data-table-toolbar\"\n      className={cn(\n        \"flex-wrap items-center gap-[var(--hui-space-2)] rounded-none border-0 p-[var(--hui-space-3)] sm:p-[var(--hui-space-4)]\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </Toolbar>\n  )\n}\n\ninterface DataTableSearchProps extends Omit<\n  React.ComponentProps<typeof Input>,\n  \"value\" | \"onChange\" | \"size\" | \"type\"\n> {\n  placeholder?: string\n  debounceMs?: number\n  ariaLabel?: string\n}\n\nexport function DataTableSearch({\n  placeholder = \"Search...\",\n  debounceMs,\n  ariaLabel,\n  className,\n  ...props\n}: DataTableSearchProps) {\n  const { table } = useDataTableContext<unknown>()\n  const externalValue = String(table.getState().globalFilter ?? \"\")\n  const defaultAriaLabel = placeholder.replace(/\\.\\.\\.$/, \"\").trim()\n  const resolvedAriaLabel =\n    ariaLabel?.trim() || defaultAriaLabel || \"Search table\"\n  const [value, setValue] = React.useState(externalValue)\n  const [lastExternalValue, setLastExternalValue] = React.useState(\n    externalValue,\n  )\n\n  if (externalValue !== lastExternalValue) {\n    setLastExternalValue(externalValue)\n    setValue(externalValue)\n  }\n\n  React.useEffect(() => {\n    if (debounceMs === undefined) {\n      table.setGlobalFilter(value)\n      return\n    }\n\n    const timeout = setTimeout(() => {\n      table.setGlobalFilter(value)\n    }, debounceMs)\n\n    return () => clearTimeout(timeout)\n  }, [value, debounceMs, table])\n\n  return (\n    <span\n      data-slot=\"data-table-search\"\n      className={cn(\"relative block w-full sm:max-w-80\", className)}\n    >\n      <SearchIcon\n        aria-hidden\n        className=\"pointer-events-none absolute start-[var(--hui-space-3)] top-1/2 z-1 size-4 -translate-y-1/2 text-[var(--hui-color-foreground-base-secondary)]\"\n      />\n      <Input\n        type=\"search\"\n        aria-label={resolvedAriaLabel}\n        placeholder={placeholder}\n        value={value}\n        onChange={(event) => setValue(event.target.value)}\n        className=\"[&>[data-slot=input]]:!ps-[var(--hui-space-9)]\"\n        {...props}\n      />\n    </span>\n  )\n}\n"},{"path":"data-table-view-options.tsx","type":"registry:component","target":"components/ui/data-table/data-table-view-options.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { Settings2 as Settings2Icon } from \"honestui/icons\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Menu,\n  MenuCheckboxItem,\n  MenuGroup,\n  MenuGroupLabel,\n  MenuPopup,\n  MenuTrigger,\n} from \"@/components/ui/menu\"\n\nimport { useDataTableContext, getColumnTitle } from \"./data-table-context\"\n\nexport function DataTableViewOptions({\n  className,\n  label = \"Columns\",\n}: {\n  className?: string\n  label?: string\n}) {\n  const { table } = useDataTableContext<unknown>()\n  const columns = table\n    .getAllLeafColumns()\n    .filter((column) => column.getCanHide())\n\n  if (columns.length === 0) {\n    return null\n  }\n\n  return (\n    <Menu>\n      <MenuTrigger render={<Button variant=\"ghost\" size=\"sm\" className={className} />}>\n        <Settings2Icon aria-hidden className=\"opacity-72\" />\n        {label}\n      </MenuTrigger>\n      <MenuPopup align=\"start\">\n        <MenuGroup>\n          <MenuGroupLabel>{label}</MenuGroupLabel>\n          {columns.map((column) => (\n            <MenuCheckboxItem\n              key={column.id}\n              checked={column.getIsVisible()}\n              onCheckedChange={(checked) =>\n                column.toggleVisibility(checked)\n              }\n            >\n              {getColumnTitle(column)}\n            </MenuCheckboxItem>\n          ))}\n        </MenuGroup>\n      </MenuPopup>\n    </Menu>\n  )\n}\n"}],"dependencies":["@tanstack/react-table","honestui"],"registryDependencies":["https://www.honestui.com/r/button.json","https://www.honestui.com/r/checkbox.json","https://www.honestui.com/r/empty.json","https://www.honestui.com/r/input.json","https://www.honestui.com/r/menu.json","https://www.honestui.com/r/pagination.json","https://www.honestui.com/r/select.json","https://www.honestui.com/r/skeleton.json","https://www.honestui.com/r/table.json","https://www.honestui.com/r/toolbar.json"]}