{"name":"data-grid","type":"registry:component","files":[{"path":"data-grid.tsx","type":"registry:component","target":"components/ui/data-grid/data-grid.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 Cell,\n  type Column,\n  type ColumnFiltersState,\n  type ColumnOrderState,\n  type ColumnPinningState,\n  type ColumnSizingState,\n  type OnChangeFn,\n  type PaginationState,\n  type Row,\n  type RowSelectionState,\n  type SortingState,\n  type VisibilityState,\n} from \"@tanstack/react-table\"\nimport { useVirtualizer } from \"@tanstack/react-virtual\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport {\n  Empty,\n  EmptyContent,\n  EmptyDescription,\n  EmptyHeader,\n  EmptyTitle,\n} from \"@/components/ui/empty\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\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 { DataGridColumnHeader } from \"./data-grid-column-header\"\nimport {\n  DataGridProvider,\n  getDataGridColumnMeta,\n  getDataGridColumnTitle,\n  useDataGridContext,\n  type DataGridColumn,\n  type DataGridColumnMeta,\n  type DataGridDensity,\n  type DataGridEditEvent,\n  type DataGridFilterValue,\n  type DataGridVirtualization,\n} from \"./data-grid-context\"\nimport { DataGridPagination } from \"./data-grid-pagination\"\nimport {\n  DataGridActiveFilters,\n  DataGridColumnVisibility,\n  DataGridExport,\n  DataGridFilterTrigger,\n  DataGridRefresh,\n  DataGridSearch,\n  DataGridToolbar,\n  DataGridToolbarSpacer,\n  type DataGridSearchProps,\n} from \"./data-grid-toolbar\"\nimport { dataGridFilterFn, moveDataGridColumn } from \"./data-grid-utils\"\n\ntype DataGridSearchConfig = {\n  placeholder?: string\n  debounceMs?: number\n  ariaLabel?: string\n}\n\ntype DataGridPaginationConfig = {\n  defaultPageSize?: number\n  pageSizeOptions?: number[]\n}\n\ntype DataGridToolbarConfig = {\n  export?: boolean | { fileName?: string; onExport?: () => void }\n  refresh?: { onRefresh: () => void | Promise<void> }\n  actions?: React.ReactNode\n}\n\nfunction isPaginationState(value: unknown): value is PaginationState {\n  return Boolean(\n    value &&\n      typeof value === \"object\" &&\n      typeof (value as PaginationState).pageIndex === \"number\" &&\n      typeof (value as PaginationState).pageSize === \"number\",\n  )\n}\n\nfunction isStateObject<T extends object>(value: unknown): value is T {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nexport interface DataGridProps<TData> {\n  columns: DataGridColumn<TData, unknown>[]\n  data: TData[]\n  children?: React.ReactNode\n  className?: string\n  caption?: string\n  getRowId?: (row: TData, index: number) => string\n  getRowLabel?: (row: Row<TData>) => string\n  getRowSelectionDisabledReason?: (row: Row<TData>) => string | undefined\n\n  search?: boolean | DataGridSearchConfig\n  sorting?: boolean | SortingState\n  filters?: boolean | ColumnFiltersState\n  inlineFilters?: boolean\n  pagination?: boolean | DataGridPaginationConfig | PaginationState\n  selection?: boolean | RowSelectionState | ((row: Row<TData>) => boolean)\n  columnVisibility?: boolean | VisibilityState\n  columnResize?: boolean\n  columnReorder?: boolean\n  columnPinning?: boolean | ColumnPinningState\n  stickyHeader?: boolean\n  keyboardNavigation?: boolean\n  virtualize?: boolean | DataGridVirtualization\n  density?: DataGridDensity\n  maxHeight?: number | string\n  toolbar?: DataGridToolbarConfig\n\n  loading?: boolean\n  refreshing?: boolean\n  error?: string | null\n  onRetry?: () => void\n  rowCount?: number\n  pageCount?: number\n  emptyState?: React.ReactNode\n  noResultsState?: React.ReactNode\n\n  manualSorting?: boolean\n  manualFiltering?: boolean\n  manualPagination?: boolean\n\n  defaultSorting?: SortingState\n  defaultFilters?: ColumnFiltersState\n  defaultGlobalSearch?: string\n  defaultPagination?: PaginationState\n  defaultSelection?: RowSelectionState\n  defaultColumnVisibility?: VisibilityState\n  defaultColumnOrder?: ColumnOrderState\n  defaultColumnSizing?: ColumnSizingState\n  defaultColumnPinning?: ColumnPinningState\n\n  globalSearch?: string\n  onGlobalSearchChange?: OnChangeFn<string>\n  onSortingChange?: OnChangeFn<SortingState>\n  onFiltersChange?: OnChangeFn<ColumnFiltersState>\n  onPaginationChange?: OnChangeFn<PaginationState>\n  onSelectionChange?: OnChangeFn<RowSelectionState>\n  onColumnVisibilityChange?: OnChangeFn<VisibilityState>\n  columnOrder?: ColumnOrderState\n  onColumnOrderChange?: OnChangeFn<ColumnOrderState>\n  columnSizing?: ColumnSizingState\n  onColumnSizingChange?: OnChangeFn<ColumnSizingState>\n  onColumnPinningChange?: OnChangeFn<ColumnPinningState>\n  onCellEdit?: (event: DataGridEditEvent<TData>) => void | Promise<void>\n}\n\nfunction normalizeColumns<TData>(\n  columns: DataGridColumn<TData, unknown>[],\n  selectable: boolean,\n  pinSelection: boolean,\n  getRowLabel?: (row: Row<TData>) => string,\n  getRowSelectionDisabledReason?: (row: Row<TData>) => string | undefined,\n) {\n  const prepared = columns.map((column) => {\n    const options = column as DataGridColumn<TData, unknown>\n    const type = options.filter?.type ?? options.type ?? \"text\"\n    const align =\n      options.align ??\n      (options.type === \"number\" || options.type === \"currency\" ? \"right\" : undefined)\n    const meta: DataGridColumnMeta<TData> = {\n      ...((column.meta ?? {}) as DataGridColumnMeta<TData>),\n      ...(align && { align }),\n      type,\n      ...(options.filter && { filter: options.filter }),\n      reorderable: options.reorderable ?? true,\n      editable: options.editable ?? false,\n      ...(options.edit && { edit: options.edit }),\n      ...(options.hideBelow && { hideBelow: options.hideBelow }),\n      ...(options.priority && { priority: options.priority }),\n    }\n\n    return {\n      ...column,\n      meta,\n      enableSorting: options.sortable ?? column.enableSorting ?? true,\n      enableColumnFilter:\n        options.filterable ?? column.enableColumnFilter ?? Boolean(options.filter),\n      enableHiding: options.hideable ?? column.enableHiding ?? true,\n      enableResizing: options.resizable ?? column.enableResizing ?? true,\n      enablePinning: options.pinnable ?? column.enablePinning ?? true,\n      ...(options.filter && !column.filterFn && { filterFn: dataGridFilterFn }),\n    } as DataGridColumn<TData, unknown>\n  })\n\n  if (selectable && !prepared.some((column) => column.id === \"select\")) {\n    prepared.unshift({\n      id: \"select\",\n      header: \"Select\",\n      size: 44,\n      minSize: 44,\n      maxSize: 44,\n      enableSorting: false,\n      enableColumnFilter: false,\n      enableHiding: false,\n      enableResizing: false,\n      enablePinning: pinSelection,\n      reorderable: false,\n      meta: { align: \"center\", reorderable: false },\n      cell: ({ row }) => {\n        const label = getRowLabel?.(row) ?? `Select row ${row.index + 1}`\n        const disabledReason = !row.getCanSelect()\n          ? getRowSelectionDisabledReason?.(row)\n          : undefined\n\n        return (\n          <Checkbox\n            aria-label={disabledReason ? `${label}. ${disabledReason}` : label}\n            checked={row.getIsSelected()}\n            disabled={!row.getCanSelect()}\n            onClick={(event) => event.stopPropagation()}\n            onCheckedChange={(checked) => row.toggleSelected(Boolean(checked))}\n          />\n        )\n      },\n    } as DataGridColumn<TData, unknown>)\n  }\n\n  return prepared\n}\n\nexport function DataGridRoot<TData>({\n  columns,\n  data,\n  children,\n  className,\n  caption,\n  getRowId,\n  getRowLabel,\n  getRowSelectionDisabledReason,\n  search = false,\n  sorting = true,\n  filters = false,\n  inlineFilters = false,\n  pagination = false,\n  selection = false,\n  columnVisibility = false,\n  columnResize = false,\n  columnReorder = false,\n  columnPinning = false,\n  stickyHeader = false,\n  keyboardNavigation = false,\n  virtualize = false,\n  density = \"default\",\n  maxHeight,\n  toolbar,\n  loading = false,\n  refreshing = false,\n  error = null,\n  onRetry,\n  rowCount,\n  pageCount,\n  emptyState,\n  noResultsState,\n  manualSorting = false,\n  manualFiltering = false,\n  manualPagination = false,\n  defaultSorting = [],\n  defaultFilters = [],\n  defaultGlobalSearch = \"\",\n  defaultPagination,\n  defaultSelection = {},\n  defaultColumnVisibility = {},\n  defaultColumnOrder = [],\n  defaultColumnSizing = {},\n  defaultColumnPinning = {},\n  globalSearch,\n  onGlobalSearchChange,\n  onSortingChange,\n  onFiltersChange,\n  onPaginationChange,\n  onSelectionChange,\n  onColumnVisibilityChange,\n  columnOrder,\n  onColumnOrderChange,\n  columnSizing,\n  onColumnSizingChange,\n  onColumnPinningChange,\n  onCellEdit,\n}: DataGridProps<TData>) {\n  const selectable = selection !== false\n  const filtersEnabled = filters !== false\n  const paginationEnabled = pagination !== false\n  const columnVisibilityEnabled = columnVisibility !== false\n  const columnPinningEnabled = columnPinning !== false\n  const virtualization: false | DataGridVirtualization =\n    virtualize === false ? false : virtualize === true ? {} : virtualize\n  const pageConfig =\n    typeof pagination === \"object\" && !isPaginationState(pagination)\n      ? pagination\n      : undefined\n  const initialPageSize = pageConfig?.defaultPageSize ?? 25\n  const viewportRef = React.useRef<HTMLDivElement>(null)\n\n  const resolvedColumns = React.useMemo(\n    () =>\n      normalizeColumns(\n        columns,\n        selectable,\n        columnPinningEnabled,\n        getRowLabel,\n        getRowSelectionDisabledReason,\n      ),\n    [\n      columns,\n      selectable,\n      columnPinningEnabled,\n      getRowLabel,\n      getRowSelectionDisabledReason,\n    ],\n  )\n\n  const controlledSorting = Array.isArray(sorting) ? sorting : undefined\n  const controlledFilters = Array.isArray(filters) ? filters : undefined\n  const controlledPagination = isPaginationState(pagination)\n    ? pagination\n    : undefined\n  const controlledSelection = isStateObject<RowSelectionState>(selection)\n    ? selection\n    : undefined\n  const controlledVisibility = isStateObject<VisibilityState>(columnVisibility)\n    ? columnVisibility\n    : undefined\n  const controlledPinning = isStateObject<ColumnPinningState>(columnPinning)\n    ? columnPinning\n    : undefined\n\n  const table = 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    enableSorting: sorting !== false,\n    enableMultiSort: true,\n    enableRowSelection: typeof selection === \"function\" ? selection : selectable,\n    enableColumnResizing: columnResize,\n    columnResizeMode: \"onChange\",\n    getRowId,\n    ...(rowCount !== undefined && { rowCount }),\n    initialState: {\n      sorting: defaultSorting,\n      columnFilters: defaultFilters,\n      globalFilter: defaultGlobalSearch,\n      pagination:\n        defaultPagination ?? { pageIndex: 0, pageSize: initialPageSize },\n      rowSelection: defaultSelection,\n      columnVisibility: defaultColumnVisibility,\n      columnOrder: defaultColumnOrder,\n      columnSizing: defaultColumnSizing,\n      columnPinning: {\n        ...defaultColumnPinning,\n        ...(columnPinningEnabled && selectable\n          ? {\n              left: Array.from(\n                new Set([\"select\", ...(defaultColumnPinning.left ?? [])]),\n              ),\n            }\n          : {}),\n      },\n    },\n    state: {\n      ...(controlledSorting !== undefined && { sorting: controlledSorting }),\n      ...(controlledFilters !== undefined && { columnFilters: controlledFilters }),\n      ...(controlledPagination !== undefined && { pagination: controlledPagination }),\n      ...(controlledSelection !== undefined && { rowSelection: controlledSelection }),\n      ...(controlledVisibility !== undefined && { columnVisibility: controlledVisibility }),\n      ...(controlledPinning !== undefined && { columnPinning: controlledPinning }),\n      ...(globalSearch !== undefined && { globalFilter: globalSearch }),\n      ...(columnOrder !== undefined && { columnOrder }),\n      ...(columnSizing !== undefined && { columnSizing }),\n    },\n    ...(onSortingChange && { onSortingChange }),\n    ...(onFiltersChange && { onColumnFiltersChange: onFiltersChange }),\n    ...(onPaginationChange && { onPaginationChange }),\n    ...(onSelectionChange && { onRowSelectionChange: onSelectionChange }),\n    ...(onColumnVisibilityChange && { onColumnVisibilityChange }),\n    ...(onColumnOrderChange && { onColumnOrderChange }),\n    ...(onColumnSizingChange && { onColumnSizingChange }),\n    ...(onColumnPinningChange && { onColumnPinningChange }),\n    ...(onGlobalSearchChange && { onGlobalFilterChange: onGlobalSearchChange }),\n  })\n\n  const resetView = React.useCallback(() => {\n    table.resetColumnOrder()\n    table.resetColumnVisibility()\n    table.resetColumnSizing()\n    table.resetColumnPinning()\n    table.resetSorting()\n  }, [table])\n\n  const contextValue = {\n    table,\n    caption,\n    density,\n    selectable,\n    filtersEnabled,\n    paginationEnabled,\n    inlineFilters,\n    columnVisibilityEnabled,\n    columnReorderEnabled: columnReorder,\n    columnPinningEnabled,\n    columnResizeEnabled: columnResize,\n    stickyHeader,\n    keyboardNavigation,\n    virtualization,\n    viewportRef,\n    loading,\n    refreshing,\n    error,\n    onRetry,\n    rowCount,\n    emptyState,\n    noResultsState,\n    getRowLabel,\n    getRowSelectionDisabledReason,\n    onCellEdit,\n    resetView,\n  }\n\n  const searchConfig = typeof search === \"object\" ? search : undefined\n  const exportConfig = typeof toolbar?.export === \"object\" ? toolbar.export : undefined\n\n  return (\n    <DataGridProvider value={contextValue}>\n      <div\n        data-slot=\"data-grid\"\n        data-density={density}\n        className={cn(\"w-full min-w-0 space-y-[var(--hui-space-3)]\", className)}\n      >\n        {children ?? (\n          <>\n            {(search || filtersEnabled || columnVisibilityEnabled || toolbar) && (\n              <DataGridToolbar>\n                {search && (\n                  <DataGridSearch\n                    placeholder={searchConfig?.placeholder}\n                    debounceMs={searchConfig?.debounceMs}\n                    ariaLabel={searchConfig?.ariaLabel}\n                  />\n                )}\n                <DataGridActiveFilters />\n                {filtersEnabled && <DataGridFilterTrigger />}\n                <DataGridToolbarSpacer />\n                {columnVisibilityEnabled && <DataGridColumnVisibility />}\n                {toolbar?.export && (\n                  <DataGridExport\n                    fileName={exportConfig?.fileName}\n                    onExport={exportConfig?.onExport}\n                  />\n                )}\n                {toolbar?.refresh && (\n                  <DataGridRefresh onRefresh={toolbar.refresh.onRefresh} />\n                )}\n                {toolbar?.actions}\n              </DataGridToolbar>\n            )}\n            <DataGridFrame>\n              <DataGridViewport maxHeight={maxHeight}>\n                <DataGridTable />\n              </DataGridViewport>\n              {(paginationEnabled || selectable) && (\n                <DataGridFooter>\n                  {selectable && <DataGridSelectionStatus />}\n                  {paginationEnabled && (\n                    <DataGridPagination\n                      pageSizeOptions={pageConfig?.pageSizeOptions}\n                    />\n                  )}\n                </DataGridFooter>\n              )}\n            </DataGridFrame>\n          </>\n        )}\n      </div>\n    </DataGridProvider>\n  )\n}\n\nexport function DataGridFrame({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"data-grid-frame\"\n      className={cn(\n        \"min-w-0 overflow-hidden rounded-[var(--hui-radius-3)] border-[0.5px] border-[var(--hui-color-border-base-primary)] bg-[var(--hui-color-background-base-primary)]\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport function DataGridViewport({\n  className,\n  maxHeight,\n  style,\n  ...props\n}: React.ComponentProps<\"div\"> & { maxHeight?: number | string }) {\n  const { viewportRef, loading, refreshing, virtualization } =\n    useDataGridContext<unknown>()\n  const resolvedMaxHeight =\n    typeof maxHeight === \"number\" ? `${maxHeight}px` : maxHeight\n  return (\n    <div\n      ref={viewportRef}\n      data-slot=\"data-grid-viewport\"\n      aria-busy={loading || refreshing || undefined}\n      className={cn(\n        \"relative min-w-0 overflow-auto [&>[data-slot=table-container]]:overflow-visible\",\n        className,\n      )}\n      style={{\n        containerType: \"inline-size\",\n        ...(virtualization && resolvedMaxHeight\n          ? { height: resolvedMaxHeight }\n          : {}),\n        maxHeight: resolvedMaxHeight,\n        ...style,\n      }}\n      {...props}\n    />\n  )\n}\n\nfunction getResponsiveClass(hideBelow?: \"sm\" | \"md\" | \"lg\") {\n  if (hideBelow === \"sm\") return \"hidden sm:table-cell\"\n  if (hideBelow === \"md\") return \"hidden md:table-cell\"\n  if (hideBelow === \"lg\") return \"hidden lg:table-cell\"\n  return undefined\n}\n\nfunction getAlignmentClass(align?: string) {\n  if (align === \"right\") return \"text-right\"\n  if (align === \"center\") return \"text-center\"\n  return \"text-left\"\n}\n\nfunction getPinnedStyles<TData>(column: Column<TData, unknown>) {\n  const pinned = column.getIsPinned()\n  return {\n    ...(pinned === \"left\" && { left: `${column.getStart(\"left\")}px` }),\n    ...(pinned === \"right\" && { right: `${column.getAfter(\"right\")}px` }),\n    ...(pinned && { position: \"sticky\" as const, zIndex: 2 }),\n    width: `${column.getSize()}px`,\n  }\n}\n\nfunction getPinnedBoundaryClass<TData>(column: Column<TData, unknown>) {\n  if (column.id === \"select\") return undefined\n\n  const pinned = column.getIsPinned()\n  if (pinned === \"left\" && column.getIsLastColumn(\"left\")) {\n    return \"border-e border-e-[var(--hui-color-border-base-secondary)]\"\n  }\n  if (pinned === \"right\" && column.getIsFirstColumn(\"right\")) {\n    return \"border-s border-s-[var(--hui-color-border-base-secondary)]\"\n  }\n  return undefined\n}\n\nfunction DataGridSelectAllCheckbox() {\n  const { table } = useDataGridContext<unknown>()\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={(checked) =>\n        table.toggleAllPageRowsSelected(Boolean(checked))\n      }\n    />\n  )\n}\n\nfunction DataGridInlineFilter<TData>({\n  column,\n}: {\n  column: Column<TData, unknown>\n}) {\n  const { table } = useDataGridContext<TData>()\n  const config = getDataGridColumnMeta(column).filter\n  const existing = column.getFilterValue() as DataGridFilterValue | undefined\n  if (!config) return null\n\n  const setValue = (value: unknown) => {\n    column.setFilterValue(\n      value === \"\"\n        ? undefined\n        : {\n            operator:\n              config.type === \"text\"\n                ? \"contains\"\n                : config.type === \"enum\" || config.type === \"boolean\"\n                  ? \"is\"\n                  : \"equals\",\n            value,\n          },\n    )\n    table.setPageIndex(0)\n  }\n\n  if (config.type === \"enum\" || config.type === \"boolean\") {\n    const options =\n      config.type === \"boolean\"\n        ? [\n            { label: \"Yes\", value: \"true\" },\n            { label: \"No\", value: \"false\" },\n          ]\n        : (config.options ?? []).map((option) =>\n            typeof option === \"string\" ? { label: option, value: option } : option,\n          )\n    const items = [{ label: \"All\", value: \"__all__\" }, ...options]\n    return (\n      <Select\n        items={items}\n        value={existing?.value == null ? \"__all__\" : String(existing.value)}\n        onValueChange={(value) => setValue(value === \"__all__\" ? \"\" : value)}\n      >\n        <SelectTrigger size=\"sm\" aria-label={`Filter ${getDataGridColumnTitle(column)}`} className=\"w-full\">\n          <SelectValue />\n        </SelectTrigger>\n        <SelectContent>\n          {items.map((item) => (\n            <SelectItem key={item.value} value={item.value}>\n              {item.label}\n            </SelectItem>\n          ))}\n        </SelectContent>\n      </Select>\n    )\n  }\n\n  return (\n    <Input\n      size=\"sm\"\n      type={config.type === \"date\" ? \"date\" : config.type === \"number\" || config.type === \"currency\" ? \"number\" : \"search\"}\n      aria-label={`Filter ${getDataGridColumnTitle(column)}`}\n      placeholder={config.type === \"text\" ? \"Search...\" : undefined}\n      value={String(existing?.value ?? \"\")}\n      onChange={(event) => setValue(event.target.value)}\n    />\n  )\n}\n\ntype EditingCell = {\n  rowId: string\n  columnId: string\n  value: unknown\n  previousValue: unknown\n  saving: boolean\n  error: string | null\n}\n\nfunction DataGridEditableCell<TData>({\n  cell,\n  editing,\n  setEditing,\n  onMove,\n}: {\n  cell: Cell<TData, unknown>\n  editing: EditingCell\n  setEditing: React.Dispatch<React.SetStateAction<EditingCell | null>>\n  onMove: (direction: -1 | 1) => void\n}) {\n  const { onCellEdit } = useDataGridContext<TData>()\n  const meta = getDataGridColumnMeta(cell.column)\n\n  const commit = async (\n    moveDirection?: -1 | 1,\n    valueOverride?: { value: unknown },\n  ) => {\n    if (!onCellEdit || editing.saving) return\n    setEditing((current) => (current ? { ...current, saving: true, error: null } : current))\n    try {\n      await onCellEdit({\n        row: cell.row,\n        columnId: cell.column.id,\n        value: valueOverride ? valueOverride.value : editing.value,\n        previousValue: editing.previousValue,\n      })\n      setEditing(null)\n      if (moveDirection) onMove(moveDirection)\n    } catch (error) {\n      setEditing((current) =>\n        current\n          ? {\n              ...current,\n              saving: false,\n              error: error instanceof Error ? error.message : \"Could not save this value. Try again.\",\n            }\n          : current,\n      )\n    }\n  }\n\n  if (meta.edit) {\n    return (\n      <div\n        onKeyDownCapture={(event) => {\n          if (event.key === \"Tab\") {\n            event.preventDefault()\n            void commit(event.shiftKey ? -1 : 1)\n          }\n          if (event.key === \"Escape\") {\n            event.preventDefault()\n            setEditing(null)\n          }\n        }}\n      >\n        {meta.edit({\n          row: cell.row,\n          value: editing.value,\n          onChange: (value) =>\n            setEditing((current) =>\n              current ? { ...current, value } : current,\n            ),\n          onCommit: (...args) =>\n            void commit(\n              undefined,\n              args.length > 0 ? { value: args[0] } : undefined,\n            ),\n          onCancel: () => setEditing(null),\n          saving: editing.saving,\n          error: editing.error,\n        })}\n      </div>\n    )\n  }\n\n  return (\n    <span className=\"grid min-w-36 gap-[var(--hui-space-1)]\">\n      <Input\n        autoFocus\n        size=\"sm\"\n        value={String(editing.value ?? \"\")}\n        disabled={editing.saving}\n        aria-invalid={Boolean(editing.error) || undefined}\n        aria-label={`Edit ${getDataGridColumnTitle(cell.column)}`}\n        onChange={(event) =>\n          setEditing((current) =>\n            current ? { ...current, value: event.target.value } : current,\n          )\n        }\n        onKeyDown={(event) => {\n          if (event.key === \"Enter\") {\n            event.preventDefault()\n            void commit()\n          }\n          if (event.key === \"Escape\") {\n            event.preventDefault()\n            setEditing(null)\n          }\n          if (event.key === \"Tab\") {\n            event.preventDefault()\n            void commit(event.shiftKey ? -1 : 1)\n          }\n        }}\n      />\n      {editing.error && (\n        <span role=\"alert\" className=\"whitespace-normal text-[var(--hui-color-foreground-danger-primary)] [font-size:var(--hui-font-size-micro)]\">\n          {editing.error}\n        </span>\n      )}\n    </span>\n  )\n}\n\nexport function DataGridTable({ className }: { className?: string }) {\n  const {\n    table,\n    caption,\n    density,\n    inlineFilters,\n    paginationEnabled,\n    stickyHeader,\n    columnReorderEnabled,\n    keyboardNavigation,\n    virtualization,\n    viewportRef,\n    loading,\n    error,\n    onRetry,\n    rowCount,\n    emptyState,\n    noResultsState,\n    onCellEdit,\n  } = useDataGridContext<unknown>()\n  const rows = table.getRowModel().rows\n  const columns = table.getVisibleLeafColumns()\n  const columnCount = Math.max(columns.length, 1)\n  const tableState = table.getState()\n  const isFiltering =\n    Boolean(tableState.globalFilter) || tableState.columnFilters.length > 0\n  const rowHeight = density === \"compact\" ? 44 : density === \"comfortable\" ? 64 : 56\n  const rowVirtualizer = useVirtualizer({\n    count: virtualization ? rows.length : 0,\n    getScrollElement: () => viewportRef.current,\n    estimateSize: () => (virtualization ? virtualization.estimateSize : undefined) ?? rowHeight,\n    initialRect: virtualization\n      ? { width: 0, height: rowHeight * 8 }\n      : undefined,\n    overscan: (virtualization ? virtualization.overscan : undefined) ?? 8,\n  })\n  const virtualItems = virtualization ? rowVirtualizer.getVirtualItems() : []\n  const renderedRows = virtualization\n    ? virtualItems.map((item) => ({ row: rows[item.index], virtualItem: item }))\n    : rows.map((row) => ({ row, virtualItem: null }))\n  const topPadding = virtualItems[0]?.start ?? 0\n  const bottomPadding = virtualItems.length\n    ? rowVirtualizer.getTotalSize() - virtualItems[virtualItems.length - 1].end\n    : 0\n  const [activeCell, setActiveCell] = React.useState({ row: 0, column: 0 })\n  const [editing, setEditing] = React.useState<EditingCell | null>(null)\n  const draggedColumn = React.useRef<string | null>(null)\n\n  const focusCell = (rowIndex: number, columnIndex: number) => {\n    const nextRow = Math.max(0, Math.min(rowIndex, rows.length - 1))\n    const nextColumn = Math.max(0, Math.min(columnIndex, columns.length - 1))\n    setActiveCell({ row: nextRow, column: nextColumn })\n    if (virtualization) rowVirtualizer.scrollToIndex(nextRow, { align: \"auto\" })\n    window.requestAnimationFrame(() => {\n      const target = viewportRef.current?.querySelector<HTMLElement>(\n        `[data-grid-row-index=\"${nextRow}\"][data-grid-column-index=\"${nextColumn}\"]`,\n      )\n      target?.focus()\n    })\n  }\n\n  const beginEditing = (cell: Cell<unknown, unknown>) => {\n    const meta = getDataGridColumnMeta(cell.column)\n    if (!meta.editable || !onCellEdit) return false\n    const value = cell.getValue()\n    setEditing({\n      rowId: cell.row.id,\n      columnId: cell.column.id,\n      value,\n      previousValue: value,\n      saving: false,\n      error: null,\n    })\n    return true\n  }\n\n  const handleCellKeyDown = (\n    event: React.KeyboardEvent<HTMLTableCellElement>,\n    cell: Cell<unknown, unknown>,\n    rowIndex: number,\n    columnIndex: number,\n  ) => {\n    if (event.target !== event.currentTarget) return\n    let nextRow = rowIndex\n    let nextColumn = columnIndex\n\n    if (event.key === \"ArrowDown\") nextRow += 1\n    else if (event.key === \"ArrowUp\") nextRow -= 1\n    else if (event.key === \"ArrowRight\") nextColumn += 1\n    else if (event.key === \"ArrowLeft\") nextColumn -= 1\n    else if (event.key === \"Home\") nextColumn = event.ctrlKey ? 0 : 0\n    else if (event.key === \"End\") nextColumn = columns.length - 1\n    else if (event.key === \"Enter\") {\n      if (!beginEditing(cell)) {\n        event.currentTarget.querySelector<HTMLElement>(\n          \"button, a[href], input, select, textarea, [tabindex]:not([tabindex='-1'])\",\n        )?.focus()\n      }\n      event.preventDefault()\n      return\n    } else if (event.key === \" \" && cell.row.getCanSelect()) {\n      cell.row.toggleSelected()\n      event.preventDefault()\n      return\n    } else return\n\n    if (event.ctrlKey && event.key === \"Home\") nextRow = 0\n    if (event.ctrlKey && event.key === \"End\") nextRow = rows.length - 1\n    event.preventDefault()\n    focusCell(nextRow, nextColumn)\n  }\n\n  const skeletonRows = Math.min(\n    Math.max(tableState.pagination?.pageSize ?? 8, 6),\n    10,\n  )\n  const headerRowCount = 1 + (inlineFilters ? 1 : 0)\n  const dataRowOffset = paginationEnabled\n    ? tableState.pagination.pageIndex * tableState.pagination.pageSize\n    : 0\n  const availableDataRowCount =\n    rowCount ??\n    (paginationEnabled\n      ? table.getPrePaginationRowModel().rows.length\n      : rows.length)\n\n  return (\n    <Table\n      aria-label={caption}\n      aria-rowcount={\n        virtualization ? headerRowCount + availableDataRowCount : undefined\n      }\n      className={cn(\"table-fixed\", className)}\n      style={{ width: `${Math.max(table.getTotalSize(), 1)}px`, minWidth: \"100%\" }}\n    >\n      <TableHeader className={cn(!stickyHeader && \"static\")}>\n        <TableRow\n          aria-rowindex={virtualization ? 1 : undefined}\n          className=\"hover:bg-transparent\"\n        >\n          {columns.map((column) => {\n            const meta = getDataGridColumnMeta(column)\n            const sorted = column.getIsSorted()\n            const canReorder =\n              columnReorderEnabled && meta.reorderable !== false\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={cn(\n                  \"group/header relative h-12 overflow-visible px-[var(--hui-space-4)] py-0\",\n                  density === \"compact\" && \"h-10\",\n                  density === \"comfortable\" && \"h-[3.25rem]\",\n                  getAlignmentClass(meta.align),\n                  getResponsiveClass(meta.hideBelow),\n                  getPinnedBoundaryClass(column),\n                  column.getIsResizing() && \"border-e border-e-[var(--hui-color-border-accent-emphasis)]\",\n                )}\n                style={getPinnedStyles(column)}\n              >\n                {column.id === \"select\" ? (\n                  <DataGridSelectAllCheckbox />\n                ) : (\n                  <DataGridColumnHeader\n                    column={column}\n                    draggable={canReorder}\n                    onDragStart={(event) => {\n                      draggedColumn.current = column.id\n                      event.dataTransfer.effectAllowed = \"move\"\n                      event.dataTransfer.setData(\"text/plain\", column.id)\n                    }}\n                    onDragOver={(event) => {\n                      if (!draggedColumn.current) return\n                      event.preventDefault()\n                      event.dataTransfer.dropEffect = \"move\"\n                    }}\n                    onDrop={(event) => {\n                      event.preventDefault()\n                      const source =\n                        draggedColumn.current ??\n                        event.dataTransfer.getData(\"text/plain\")\n                      const order = table.getState().columnOrder.length\n                        ? table.getState().columnOrder\n                        : table.getAllLeafColumns().map((item) => item.id)\n                      table.setColumnOrder(\n                        moveDataGridColumn(order, source, column.id),\n                      )\n                      draggedColumn.current = null\n                    }}\n                    onDragEnd={() => {\n                      draggedColumn.current = null\n                    }}\n                  />\n                )}\n              </TableHead>\n            )\n          })}\n        </TableRow>\n        {inlineFilters && (\n          <TableRow\n            aria-rowindex={virtualization ? 2 : undefined}\n            className=\"hover:bg-transparent\"\n          >\n            {columns.map((column) => {\n              const meta = getDataGridColumnMeta(column)\n              return (\n                <TableHead\n                  key={column.id}\n                  className={cn(\n                    \"h-[3.875rem] px-[var(--hui-space-3)] py-[var(--hui-space-3)]\",\n                    getResponsiveClass(meta.hideBelow),\n                    getPinnedBoundaryClass(column),\n                  )}\n                  style={getPinnedStyles(column)}\n                >\n                  <DataGridInlineFilter column={column} />\n                </TableHead>\n              )\n            })}\n          </TableRow>\n        )}\n      </TableHeader>\n      <TableBody>\n        {error ? (\n          <TableRow>\n            <TableCell colSpan={columnCount} className=\"h-60 overflow-visible p-0\">\n              <DataGridStateViewport>\n                <DataGridStateMessage\n                  title=\"Could not load data\"\n                  description={error}\n                  action={\n                    onRetry ? (\n                      <Button variant=\"outline\" size=\"sm\" onClick={onRetry}>\n                        Try again\n                      </Button>\n                    ) : undefined\n                  }\n                />\n              </DataGridStateViewport>\n            </TableCell>\n          </TableRow>\n        ) : loading ? (\n          Array.from({ length: skeletonRows }).map((_, rowIndex) => (\n            <TableRow key={`loading-${rowIndex}`} style={{ height: rowHeight }}>\n              {columns.map((column, columnIndex) => (\n                <TableCell\n                  key={column.id}\n                  className={cn(\n                    \"px-[var(--hui-space-4)] py-0\",\n                    getResponsiveClass(getDataGridColumnMeta(column).hideBelow),\n                  )}\n                  style={getPinnedStyles(column)}\n                >\n                  <Skeleton\n                    className={cn(\n                      \"h-[var(--hui-space-4)]\",\n                      columnIndex % 3 === 0 ? \"w-24\" : columnIndex % 3 === 1 ? \"w-32\" : \"w-16\",\n                    )}\n                  />\n                </TableCell>\n              ))}\n            </TableRow>\n          ))\n        ) : rows.length === 0 ? (\n          <TableRow>\n            <TableCell colSpan={columnCount} className=\"h-60 overflow-visible p-0\">\n              <DataGridStateViewport>\n                {isFiltering ? (\n                  noResultsState ?? (\n                    <DataGridStateMessage\n                      title=\"No matching rows\"\n                      description=\"Try changing or clearing your filters.\"\n                      action={\n                        <Button\n                          variant=\"outline\"\n                          size=\"sm\"\n                          onClick={() => {\n                            table.setGlobalFilter(\"\")\n                            table.resetColumnFilters()\n                          }}\n                        >\n                          Clear filters\n                        </Button>\n                      }\n                    />\n                  )\n                ) : (\n                  emptyState ?? (\n                    <DataGridStateMessage\n                      title=\"No data yet\"\n                      description=\"Rows will appear here once they are added.\"\n                    />\n                  )\n                )}\n              </DataGridStateViewport>\n            </TableCell>\n          </TableRow>\n        ) : (\n          <>\n            {topPadding > 0 && (\n              <tr aria-hidden=\"true\">\n                <td colSpan={columnCount} style={{ height: topPadding, padding: 0, border: 0 }} />\n              </tr>\n            )}\n            {renderedRows.map(({ row, virtualItem }) => {\n              const rowPosition = virtualItem?.index ?? rows.indexOf(row)\n              return (\n                <TableRow\n                  key={row.id}\n                  aria-rowindex={\n                    virtualization\n                      ? headerRowCount + dataRowOffset + rowPosition + 1\n                      : undefined\n                  }\n                  data-state={row.getIsSelected() ? \"selected\" : undefined}\n                  data-index={virtualItem?.index}\n                  className=\"transition-colors duration-100 hover:bg-[var(--hui-color-background-base-primary-hover)]\"\n                  style={{ height: rowHeight }}\n                >\n                  {row.getVisibleCells().map((cell, columnIndex) => {\n                    const meta = getDataGridColumnMeta(cell.column)\n                    const isEditing =\n                      editing?.rowId === row.id &&\n                      editing.columnId === cell.column.id\n                    return (\n                      <TableCell\n                        key={cell.id}\n                        data-grid-row-index={rowPosition}\n                        data-grid-column-index={columnIndex}\n                        tabIndex={\n                          keyboardNavigation &&\n                          activeCell.row === rowPosition &&\n                          activeCell.column === columnIndex\n                            ? 0\n                            : keyboardNavigation\n                              ? -1\n                              : undefined\n                        }\n                        className={cn(\n                          \"px-[var(--hui-space-4)] py-0 outline-none focus-visible:shadow-[inset_0_0_0_2px_var(--hui-color-border-accent-emphasis)]\",\n                          getAlignmentClass(meta.align),\n                          getResponsiveClass(meta.hideBelow),\n                          getPinnedBoundaryClass(cell.column),\n                          cell.column.getIsResizing() &&\n                            \"border-e border-e-[var(--hui-color-border-accent-emphasis)]\",\n                        )}\n                        style={getPinnedStyles(cell.column)}\n                        onFocus={() =>\n                          setActiveCell({\n                            row: rowPosition,\n                            column: columnIndex,\n                          })\n                        }\n                        onDoubleClick={() => beginEditing(cell)}\n                        onKeyDown={(event) =>\n                          handleCellKeyDown(\n                            event,\n                            cell,\n                            rowPosition,\n                            columnIndex,\n                          )\n                        }\n                      >\n                        {isEditing && editing ? (\n                          <DataGridEditableCell\n                            cell={cell}\n                            editing={editing}\n                            setEditing={setEditing}\n                            onMove={(direction) =>\n                              focusCell(rowPosition, columnIndex + direction)\n                            }\n                          />\n                        ) : (\n                          flexRender(\n                            cell.column.columnDef.cell,\n                            cell.getContext(),\n                          )\n                        )}\n                      </TableCell>\n                    )\n                  })}\n                </TableRow>\n              )\n            })}\n            {bottomPadding > 0 && (\n              <tr aria-hidden=\"true\">\n                <td colSpan={columnCount} style={{ height: bottomPadding, padding: 0, border: 0 }} />\n              </tr>\n            )}\n          </>\n        )}\n      </TableBody>\n    </Table>\n  )\n}\n\nfunction DataGridStateViewport({ children }: { children: React.ReactNode }) {\n  return (\n    <div\n      data-slot=\"data-grid-state\"\n      className=\"sticky start-0 flex min-h-60 w-[100cqw]\"\n    >\n      {children}\n    </div>\n  )\n}\n\nexport function DataGridStateMessage({\n  title,\n  description,\n  action,\n  className,\n}: {\n  title: string\n  description: string\n  action?: React.ReactNode\n  className?: string\n}) {\n  return (\n    <Empty className={cn(\"min-h-60 rounded-none border-0 p-[var(--hui-space-8)]\", className)}>\n      <EmptyHeader>\n        <EmptyTitle className=\"font-sans [font-size:var(--hui-font-size-large)] [font-weight:var(--hui-font-weight-medium)]\">\n          {title}\n        </EmptyTitle>\n        <EmptyDescription>{description}</EmptyDescription>\n      </EmptyHeader>\n      {action && <EmptyContent>{action}</EmptyContent>}\n    </Empty>\n  )\n}\n\nexport function DataGridFooter({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  const { table, loading, error } = useDataGridContext<unknown>()\n  if (!loading && (error || table.getFilteredRowModel().rows.length === 0)) {\n    return null\n  }\n\n  return (\n    <div\n      data-slot=\"data-grid-footer\"\n      className={cn(\n        \"flex min-h-14 flex-wrap items-center gap-x-[var(--hui-space-4)] gap-y-[var(--hui-space-2)] border-t-[0.5px] border-[var(--hui-color-border-base-primary)] px-[var(--hui-space-4)] py-[var(--hui-space-3)]\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport function DataGridSelectionStatus({\n  className,\n  children,\n}: {\n  className?: string\n  children?: React.ReactNode\n}) {\n  const { table } = useDataGridContext<unknown>()\n  const selectedCount = Object.values(table.getState().rowSelection).filter(Boolean).length\n  if (selectedCount === 0) return null\n\n  return (\n    <div\n      data-slot=\"data-grid-selection-status\"\n      className={cn(\n        \"flex flex-wrap items-center gap-[var(--hui-space-3)] [font-size:var(--hui-font-size-small)] text-[var(--hui-color-foreground-base-secondary)]\",\n        className,\n      )}\n    >\n      <span role=\"status\">\n        {selectedCount} {selectedCount === 1 ? \"row\" : \"rows\"} selected\n      </span>\n      {children}\n      <Button variant=\"link\" size=\"xs\" onClick={() => table.resetRowSelection()}>\n        Clear\n      </Button>\n    </div>\n  )\n}\n\nexport function DataGridRowActions({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"data-grid-row-actions\"\n      className={cn(\"flex justify-end\", className)}\n      onClick={(event) => event.stopPropagation()}\n      {...props}\n    />\n  )\n}\n\nexport const DataGrid = Object.assign(DataGridRoot, {\n  Root: DataGridRoot,\n  Toolbar: DataGridToolbar,\n  ToolbarSpacer: DataGridToolbarSpacer,\n  Search: DataGridSearch,\n  Filters: DataGridActiveFilters,\n  FilterTrigger: DataGridFilterTrigger,\n  ColumnVisibility: DataGridColumnVisibility,\n  Export: DataGridExport,\n  Refresh: DataGridRefresh,\n  Frame: DataGridFrame,\n  Viewport: DataGridViewport,\n  Table: DataGridTable,\n  Footer: DataGridFooter,\n  SelectionStatus: DataGridSelectionStatus,\n  Pagination: DataGridPagination,\n  RowActions: DataGridRowActions,\n})\n\nexport {\n  DataGridActiveFilters,\n  DataGridColumnHeader,\n  DataGridColumnVisibility,\n  DataGridExport,\n  DataGridFilterTrigger,\n  DataGridPagination,\n  DataGridRefresh,\n  DataGridSearch,\n  DataGridToolbar,\n  DataGridToolbarSpacer,\n  getDataGridColumnMeta,\n  getDataGridColumnTitle,\n  useDataGridContext,\n}\nexport type {\n  DataGridColumn,\n  DataGridDensity,\n  DataGridEditEvent,\n  DataGridSearchProps,\n}\n"},{"path":"data-grid-column-header.tsx","type":"registry:component","target":"components/ui/data-grid/data-grid-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  Ellipsis as EllipsisIcon,\n} from \"honestui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Menu,\n  MenuItem,\n  MenuPopup,\n  MenuSeparator,\n  MenuTrigger,\n} from \"@/components/ui/menu\"\n\nimport {\n  getDataGridColumnMeta,\n  getDataGridColumnTitle,\n  useDataGridContext,\n} from \"./data-grid-context\"\n\nfunction toggleColumnSorting<TData>(\n  column: Column<TData, unknown>,\n  multi: boolean,\n) {\n  const sorted = column.getIsSorted()\n  if (!sorted) column.toggleSorting(false, multi)\n  else if (sorted === \"asc\") column.toggleSorting(true, multi)\n  else column.clearSorting()\n}\n\nexport function DataGridColumnHeader<TData>({\n  column,\n  className,\n  draggable,\n  onDragStart,\n  onDragOver,\n  onDrop,\n  onDragEnd,\n}: {\n  column: Column<TData, unknown>\n  className?: string\n  draggable?: boolean\n  onDragStart?: React.DragEventHandler<HTMLDivElement>\n  onDragOver?: React.DragEventHandler<HTMLDivElement>\n  onDrop?: React.DragEventHandler<HTMLDivElement>\n  onDragEnd?: React.DragEventHandler<HTMLDivElement>\n}) {\n  const {\n    table,\n    columnReorderEnabled,\n    columnPinningEnabled,\n    columnResizeEnabled,\n  } = useDataGridContext<TData>()\n  const title = getDataGridColumnTitle(column)\n  const sorted = column.getIsSorted()\n  const sortIndex = column.getSortIndex()\n  const columns = table.getVisibleLeafColumns()\n  const alignment = getDataGridColumnMeta(column).align ?? \"left\"\n  const header = table.getFlatHeaders().find((item) => item.column.id === column.id)\n  const resizeHandler = header?.getResizeHandler()\n  const minSize = column.columnDef.minSize ?? 40\n  const maxSize = column.columnDef.maxSize ?? Number.MAX_SAFE_INTEGER\n  const currentSize = Math.round(column.getSize())\n  const visibleIndex = columns.findIndex((item) => item.id === column.id)\n  const canMove =\n    columnReorderEnabled && getDataGridColumnMeta(column).reorderable !== false\n\n  const move = (offset: -1 | 1) => {\n    const target = columns[visibleIndex + offset]\n    if (!target) return\n    const order = table.getState().columnOrder.length\n      ? table.getState().columnOrder\n      : table.getAllLeafColumns().map((item) => item.id)\n    const sourceIndex = order.indexOf(column.id)\n    const targetIndex = order.indexOf(target.id)\n    const next = [...order]\n    next.splice(sourceIndex, 1)\n    next.splice(targetIndex, 0, column.id)\n    table.setColumnOrder(next)\n  }\n\n  return (\n    <div\n      data-slot=\"data-grid-column-header\"\n      className={cn(\"flex min-w-0 items-center\", className)}\n    >\n      <div\n        data-slot=\"data-grid-column-drag-region\"\n        draggable={draggable}\n        className={cn(\n          \"relative flex min-w-0 flex-1 items-center gap-[var(--hui-space-1)]\",\n          alignment === \"right\" && \"justify-end ps-[var(--hui-space-6)]\",\n          alignment === \"center\" && \"justify-center px-[var(--hui-space-6)]\",\n          draggable && \"cursor-grab active:cursor-grabbing\",\n        )}\n        onDragStart={onDragStart}\n        onDragOver={onDragOver}\n        onDrop={onDrop}\n        onDragEnd={onDragEnd}\n      >\n        {column.getCanSort() ? (\n          <Button\n            variant=\"link\"\n            size=\"sm\"\n            className={cn(\n              \"min-w-0 border-0 px-[var(--hui-space-2)] py-[var(--hui-space-1)] text-[var(--hui-color-foreground-base-tertiary)] [font-size:var(--hui-font-size-small)] [font-weight:var(--hui-font-weight-medium)]\",\n              alignment === \"left\" &&\n                \"-ms-[var(--hui-space-2)] justify-start\",\n              alignment === \"center\" && \"justify-center\",\n              alignment === \"right\" &&\n                \"-me-[var(--hui-space-2)] ms-auto justify-end\",\n            )}\n            aria-label={`Sort by ${title}`}\n            onClick={(event) => toggleColumnSorting(column, event.shiftKey)}\n          >\n            <span className=\"truncate\">{title}</span>\n            {sorted === \"asc\" ? (\n              <ArrowUpIcon aria-hidden className=\"size-3.5\" />\n            ) : sorted === \"desc\" ? (\n              <ArrowDownIcon aria-hidden className=\"size-3.5\" />\n            ) : (\n              <ChevronsUpDownIcon aria-hidden className=\"size-3.5 opacity-60\" />\n            )}\n            {sortIndex > 0 && (\n              <span className=\"tabular-nums text-[var(--hui-color-foreground-base-secondary)] [font-size:var(--hui-font-size-micro)]\">\n                {sortIndex + 1}\n              </span>\n            )}\n          </Button>\n        ) : (\n          <span\n            className={cn(\n              \"truncate\",\n              alignment === \"center\" && \"mx-auto\",\n              alignment === \"right\" && \"ms-auto\",\n            )}\n          >\n            {title}\n          </span>\n        )}\n\n        <Menu>\n          <MenuTrigger\n            render={\n              <Button\n                variant=\"link\"\n                size=\"icon-sm\"\n                className={cn(\n                  \"opacity-0 focus-visible:opacity-100 group-hover/header:opacity-100 data-popup-open:opacity-100\",\n                  alignment === \"left\" && \"ms-auto\",\n                  alignment === \"center\" && \"absolute end-0\",\n                  alignment === \"right\" && \"absolute start-0\",\n                )}\n                aria-label={`Column options for ${title}`}\n              />\n            }\n          >\n            <EllipsisIcon aria-hidden />\n          </MenuTrigger>\n          <MenuPopup align=\"start\">\n            {column.getCanSort() && (\n              <>\n                <MenuItem onClick={() => column.toggleSorting(false)}>Sort ascending</MenuItem>\n                <MenuItem onClick={() => column.toggleSorting(true)}>Sort descending</MenuItem>\n                {sorted && <MenuItem onClick={() => column.clearSorting()}>Clear sort</MenuItem>}\n              </>\n            )}\n            {canMove && (\n              <>\n                <MenuSeparator />\n                <MenuItem disabled={visibleIndex <= 0} onClick={() => move(-1)}>Move left</MenuItem>\n                <MenuItem disabled={visibleIndex >= columns.length - 1} onClick={() => move(1)}>Move right</MenuItem>\n              </>\n            )}\n            {columnPinningEnabled && column.getCanPin() && (\n              <>\n                <MenuSeparator />\n                <MenuItem onClick={() => column.pin(\"left\")}>Pin left</MenuItem>\n                <MenuItem onClick={() => column.pin(\"right\")}>Pin right</MenuItem>\n                {column.getIsPinned() && <MenuItem onClick={() => column.pin(false)}>Unpin</MenuItem>}\n              </>\n            )}\n            {(column.getCanHide() || (columnResizeEnabled && column.getCanResize())) && <MenuSeparator />}\n            {columnResizeEnabled && column.getCanResize() && (\n              <MenuItem onClick={() => column.resetSize()}>Reset width</MenuItem>\n            )}\n            {column.getCanHide() && (\n              <MenuItem onClick={() => column.toggleVisibility(false)}>Hide column</MenuItem>\n            )}\n          </MenuPopup>\n        </Menu>\n      </div>\n\n      {columnResizeEnabled && column.getCanResize() && (\n        <span\n          role=\"separator\"\n          draggable={false}\n          aria-label={`Resize ${title} column`}\n          aria-orientation=\"vertical\"\n          aria-valuemin={minSize}\n          aria-valuemax={maxSize}\n          aria-valuenow={currentSize}\n          aria-valuetext={`${currentSize} pixels`}\n          tabIndex={0}\n          data-slot=\"data-grid-resize-handle\"\n          className=\"absolute inset-y-0 end-0 z-2 w-2 translate-x-1/2 cursor-col-resize touch-none select-none after:absolute after:inset-y-0 after:start-1/2 after:w-px after:-translate-x-1/2 after:bg-transparent hover:after:bg-[var(--hui-color-border-base-focus)] focus-visible:outline-none focus-visible:after:w-0.5 focus-visible:after:bg-[var(--hui-color-border-accent-emphasis)] data-[resizing=true]:after:bg-[var(--hui-color-border-accent-emphasis)] forced-colors:focus-visible:outline-2 forced-colors:focus-visible:outline-[Highlight]\"\n          data-resizing={column.getIsResizing() ? \"true\" : undefined}\n          onDoubleClick={() => column.resetSize()}\n          onDragStart={(event) => {\n            event.preventDefault()\n            event.stopPropagation()\n          }}\n          onMouseDown={(event) => {\n            event.stopPropagation()\n            resizeHandler?.(event)\n          }}\n          onTouchStart={(event) => {\n            event.stopPropagation()\n            resizeHandler?.(event)\n          }}\n          onKeyDown={(event) => {\n            if (event.key !== \"ArrowLeft\" && event.key !== \"ArrowRight\") return\n            event.preventDefault()\n            const direction = event.key === \"ArrowRight\" ? 8 : -8\n            table.setColumnSizing((current) => ({\n              ...current,\n              [column.id]: Math.min(\n                maxSize,\n                Math.max(\n                  minSize,\n                  column.getSize() + direction,\n                ),\n              ),\n            }))\n          }}\n        />\n      )}\n    </div>\n  )\n}\n"},{"path":"data-grid-context.tsx","type":"registry:component","target":"components/ui/data-grid/data-grid-context.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport type {\n  Column,\n  ColumnDef,\n  Row,\n  Table,\n} from \"@tanstack/react-table\"\n\nexport type DataGridDensity = \"compact\" | \"default\" | \"comfortable\"\nexport type DataGridAlignment = \"left\" | \"center\" | \"right\"\nexport type DataGridFilterType =\n  | \"text\"\n  | \"number\"\n  | \"currency\"\n  | \"date\"\n  | \"enum\"\n  | \"boolean\"\n\nexport type DataGridFilterOption = {\n  label: string\n  value: string\n}\n\nexport type DataGridFilterConfig = {\n  type: DataGridFilterType\n  options?: DataGridFilterOption[] | string[]\n}\n\nexport type DataGridFilterValue = {\n  operator: string\n  value?: unknown\n  valueTo?: unknown\n}\n\nexport type DataGridEditorProps<TData> = {\n  row: Row<TData>\n  value: unknown\n  onChange: (value: unknown) => void\n  onCommit: (value?: unknown) => void\n  onCancel: () => void\n  saving: boolean\n  error: string | null\n}\n\nexport type DataGridColumnOptions<TData> = {\n  type?: DataGridFilterType\n  align?: DataGridAlignment\n  sortable?: boolean\n  filterable?: boolean\n  hideable?: boolean\n  resizable?: boolean\n  reorderable?: boolean\n  pinnable?: boolean\n  editable?: boolean\n  filter?: DataGridFilterConfig\n  edit?: (props: DataGridEditorProps<TData>) => React.ReactNode\n  hideBelow?: \"sm\" | \"md\" | \"lg\"\n  priority?: \"primary\" | \"secondary\" | \"optional\"\n}\n\nexport type DataGridColumn<TData, TValue = unknown> = ColumnDef<\n  TData,\n  TValue\n> &\n  DataGridColumnOptions<TData>\n\nexport type DataGridColumnMeta<TData> = {\n  label?: string\n  align?: DataGridAlignment\n  type?: DataGridFilterType\n  filter?: DataGridFilterConfig\n  reorderable?: boolean\n  editable?: boolean\n  edit?: (props: DataGridEditorProps<TData>) => React.ReactNode\n  hideBelow?: \"sm\" | \"md\" | \"lg\"\n  priority?: \"primary\" | \"secondary\" | \"optional\"\n}\n\nexport type DataGridEditEvent<TData> = {\n  row: Row<TData>\n  columnId: string\n  value: unknown\n  previousValue: unknown\n}\n\nexport type DataGridVirtualization = {\n  estimateSize?: number\n  overscan?: number\n}\n\nexport type DataGridContextValue<TData> = {\n  table: Table<TData>\n  caption?: string\n  density: DataGridDensity\n  selectable: boolean\n  filtersEnabled: boolean\n  paginationEnabled: boolean\n  inlineFilters: boolean\n  columnVisibilityEnabled: boolean\n  columnReorderEnabled: boolean\n  columnPinningEnabled: boolean\n  columnResizeEnabled: boolean\n  stickyHeader: boolean\n  keyboardNavigation: boolean\n  virtualization: false | DataGridVirtualization\n  viewportRef: React.RefObject<HTMLDivElement | null>\n  loading: boolean\n  refreshing: boolean\n  error: string | null\n  onRetry?: () => void\n  rowCount?: number\n  emptyState?: React.ReactNode\n  noResultsState?: React.ReactNode\n  getRowLabel?: (row: Row<TData>) => string\n  getRowSelectionDisabledReason?: (row: Row<TData>) => string | undefined\n  onCellEdit?: (event: DataGridEditEvent<TData>) => void | Promise<void>\n  resetView: () => void\n}\n\nconst DataGridContext =\n  React.createContext<DataGridContextValue<unknown> | null>(null)\n\nexport function DataGridProvider<TData>({\n  value,\n  children,\n}: {\n  value: DataGridContextValue<TData>\n  children: React.ReactNode\n}) {\n  return (\n    <DataGridContext.Provider\n      value={value as unknown as DataGridContextValue<unknown>}\n    >\n      {children}\n    </DataGridContext.Provider>\n  )\n}\n\nexport function useDataGridContext<TData>(): DataGridContextValue<TData> {\n  const context = React.useContext(DataGridContext)\n\n  if (!context) {\n    throw new Error(\"DataGrid parts must be rendered inside <DataGrid>.\")\n  }\n\n  return context as DataGridContextValue<TData>\n}\n\nexport function getDataGridColumnMeta<TData>(\n  column: Column<TData, unknown>,\n): DataGridColumnMeta<TData> {\n  return (column.columnDef.meta ?? {}) as DataGridColumnMeta<TData>\n}\n\nexport function getDataGridColumnTitle(column: {\n  id: string\n  columnDef: { header?: unknown; meta?: unknown }\n}) {\n  const meta = (column.columnDef.meta ?? {}) as DataGridColumnMeta<unknown>\n\n  if (meta.label) return meta.label\n  if (typeof column.columnDef.header === \"string\") return column.columnDef.header\n  return column.id\n}\n"},{"path":"data-grid-pagination.tsx","type":"registry:component","target":"components/ui/data-grid/data-grid-pagination.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport {\n  ChevronLeft as ChevronLeftIcon,\n  ChevronRight as ChevronRightIcon,\n} from \"honestui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\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 { useDataGridContext } from \"./data-grid-context\"\nimport { getDataGridPageItems } from \"./data-grid-utils\"\n\nexport function DataGridPagination({\n  pageSizeOptions = [10, 25, 50, 100],\n  className,\n}: {\n  pageSizeOptions?: number[]\n  className?: string\n}) {\n  const { table, rowCount } = useDataGridContext<unknown>()\n  const { pageIndex, pageSize } = table.getState().pagination\n  const totalRows = rowCount ?? table.getFilteredRowModel().rows.length\n  const pageCount = Math.max(table.getPageCount(), 1)\n  const rangeStart = totalRows === 0 ? 0 : pageIndex * pageSize + 1\n  const rangeEnd = Math.min((pageIndex + 1) * pageSize, totalRows)\n  const pageSizeItems = pageSizeOptions.map((size) => ({\n    label: String(size),\n    value: String(size),\n  }))\n  const pageButtonClass = buttonVariants({\n    variant: \"link\",\n    size: \"icon\",\n    className: \"size-7\",\n  })\n\n  return (\n    <div\n      data-slot=\"data-grid-pagination\"\n      className={cn(\n        \"ms-auto flex min-w-0 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)] whitespace-nowrap [font-size:var(--hui-font-size-small)] text-[var(--hui-color-foreground-base-secondary)]\">\n        <span>Rows per page</span>\n        <Select\n          items={pageSizeItems}\n          value={String(pageSize)}\n          onValueChange={(value) =>\n            table.setPagination({ pageIndex: 0, pageSize: Number(value) })\n          }\n        >\n          <SelectTrigger size=\"sm\" className=\"w-16\" aria-label=\"Rows per page\">\n            <SelectValue />\n          </SelectTrigger>\n          <SelectContent>\n            {pageSizeItems.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        role=\"status\"\n        aria-live=\"polite\"\n        className=\"whitespace-nowrap tabular-nums [font-size:var(--hui-font-size-small)] text-[var(--hui-color-foreground-base-secondary)]\"\n      >\n        {rangeStart} to {rangeEnd} of {totalRows}\n      </span>\n\n      <Pagination className=\"mx-0 w-auto justify-end\" aria-label=\"Data grid 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={pageButtonClass}\n            >\n              <ChevronLeftIcon aria-hidden />\n            </PaginationLink>\n          </PaginationItem>\n          {getDataGridPageItems(pageIndex, pageCount).map((item, index) =>\n            item.type === \"page\" ? (\n              <PaginationItem key={item.page}>\n                <PaginationLink\n                  isActive={item.page === pageIndex}\n                  aria-label={`Go to page ${item.page + 1}`}\n                  render={<button type=\"button\" onClick={() => table.setPageIndex(item.page)} />}\n                  className={cn(pageButtonClass, \"tabular-nums\")}\n                >\n                  {item.page + 1}\n                </PaginationLink>\n              </PaginationItem>\n            ) : (\n              <PaginationItem key={`ellipsis-${index}`}>\n                <PaginationEllipsis />\n              </PaginationItem>\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={pageButtonClass}\n            >\n              <ChevronRightIcon aria-hidden />\n            </PaginationLink>\n          </PaginationItem>\n        </PaginationContent>\n      </Pagination>\n    </div>\n  )\n}\n"},{"path":"data-grid-toolbar.tsx","type":"registry:component","target":"components/ui/data-grid/data-grid-toolbar.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport {\n  Download as DownloadIcon,\n  FunnelPlus as FunnelPlusIcon,\n  RefreshRounded as RefreshIcon,\n  Search as SearchIcon,\n  Settings2 as Settings2Icon,\n  X as XIcon,\n} from \"honestui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  Menu,\n  MenuCheckboxItem,\n  MenuGroup,\n  MenuGroupLabel,\n  MenuItem,\n  MenuPopup,\n  MenuSeparator,\n  MenuTrigger,\n} from \"@/components/ui/menu\"\nimport {\n  Popover,\n  PopoverClose,\n  PopoverPopup,\n  PopoverTitle,\n  PopoverTrigger,\n} from \"@/components/ui/popover\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\nimport { Toolbar } from \"@/components/ui/toolbar\"\nimport {\n  Tooltip,\n  TooltipPopup,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\n\nimport {\n  getDataGridColumnMeta,\n  getDataGridColumnTitle,\n  useDataGridContext,\n  type DataGridFilterOption,\n  type DataGridFilterValue,\n} from \"./data-grid-context\"\nimport {\n  buildDataGridCsv,\n  DATA_GRID_FILTER_OPERATORS,\n  formatDataGridFilter,\n} from \"./data-grid-utils\"\n\nexport function DataGridToolbar({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof Toolbar>) {\n  return (\n    <Toolbar\n      data-slot=\"data-grid-toolbar\"\n      className={cn(\n        \"min-w-0 flex-wrap gap-[var(--hui-space-2)] overflow-visible rounded-none border-0 p-0\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </Toolbar>\n  )\n}\n\nexport function DataGridToolbarSpacer({ className }: { className?: string }) {\n  return <span aria-hidden className={cn(\"min-w-0 flex-1\", className)} />\n}\n\nexport interface DataGridSearchProps 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 DataGridSearch({\n  placeholder = \"Search...\",\n  debounceMs,\n  ariaLabel,\n  className,\n  ...props\n}: DataGridSearchProps) {\n  const { table } = useDataGridContext<unknown>()\n  const externalValue = String(table.getState().globalFilter ?? \"\")\n  const [draft, setDraft] = React.useState({\n    value: externalValue,\n    externalValue,\n  })\n  const value =\n    draft.externalValue === externalValue ? draft.value : externalValue\n\n  React.useEffect(() => {\n    if (value === externalValue) return\n    if (debounceMs === undefined) {\n      table.setGlobalFilter(value)\n      table.setPageIndex(0)\n      return\n    }\n\n    const timeout = window.setTimeout(() => {\n      table.setGlobalFilter(value)\n      table.setPageIndex(0)\n    }, debounceMs)\n\n    return () => window.clearTimeout(timeout)\n  }, [debounceMs, externalValue, table, value])\n\n  const defaultLabel = placeholder.replace(/\\.{3}$/, \"\").trim()\n\n  return (\n    <span\n      data-slot=\"data-grid-search\"\n      className={cn(\"relative block w-full sm:w-72\", 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={ariaLabel?.trim() || defaultLabel || \"Search data grid\"}\n        placeholder={placeholder}\n        value={value}\n        onChange={(event) =>\n          setDraft({ value: event.target.value, externalValue })\n        }\n        className=\"min-h-[var(--hui-space-10)] [&>[data-slot=input]]:!ps-[var(--hui-space-9)]\"\n        {...props}\n      />\n    </span>\n  )\n}\n\nfunction normalizeOptions(options?: DataGridFilterOption[] | string[]) {\n  return (options ?? []).map((option) =>\n    typeof option === \"string\" ? { label: option, value: option } : option,\n  )\n}\n\nfunction DataGridFilterValueField({\n  type,\n  options,\n  value,\n  onChange,\n  label,\n}: {\n  type: string\n  options?: DataGridFilterOption[] | string[]\n  value: unknown\n  onChange: (value: unknown) => void\n  label: string\n}) {\n  const normalizedOptions = normalizeOptions(options)\n\n  if (type === \"enum\" || type === \"boolean\") {\n    const items =\n      type === \"boolean\"\n        ? [\n            { label: \"Yes\", value: \"true\" },\n            { label: \"No\", value: \"false\" },\n          ]\n        : normalizedOptions\n\n    return (\n      <Select\n        items={items}\n        value={String(value ?? \"\")}\n        onValueChange={onChange}\n      >\n        <SelectTrigger aria-label={label} className=\"w-full\">\n          <SelectValue placeholder=\"Choose a value\" />\n        </SelectTrigger>\n        <SelectContent>\n          {items.map((item) => (\n            <SelectItem key={item.value} value={item.value}>\n              {item.label}\n            </SelectItem>\n          ))}\n        </SelectContent>\n      </Select>\n    )\n  }\n\n  return (\n    <Input\n      aria-label={label}\n      type={type === \"date\" ? \"date\" : type === \"number\" || type === \"currency\" ? \"number\" : \"text\"}\n      value={String(value ?? \"\")}\n      onChange={(event) => onChange(event.target.value)}\n    />\n  )\n}\n\nexport function DataGridFilterTrigger({ className }: { className?: string }) {\n  const { table } = useDataGridContext<unknown>()\n  const filterableColumns = table\n    .getAllLeafColumns()\n    .filter((column) => getDataGridColumnMeta(column).filter)\n  const [columnId, setColumnId] = React.useState(\n    filterableColumns[0]?.id ?? \"\",\n  )\n  const selectedColumn = filterableColumns.find(\n    (column) => column.id === columnId,\n  )\n  const config = selectedColumn\n    ? getDataGridColumnMeta(selectedColumn).filter\n    : undefined\n  const type = config?.type ?? \"text\"\n  const operators = DATA_GRID_FILTER_OPERATORS[type]\n  const [selectedOperator, setSelectedOperator] = React.useState(\n    operators[0]?.value ?? \"\",\n  )\n  const operator = operators.some((item) => item.value === selectedOperator)\n    ? selectedOperator\n    : operators[0]?.value ?? \"\"\n  const [value, setValue] = React.useState<unknown>(\"\")\n  const [valueTo, setValueTo] = React.useState<unknown>(\"\")\n  const activeOperator = operators.find((item) => item.value === operator)\n\n  if (filterableColumns.length === 0) return null\n\n  const columnItems = filterableColumns.map((column) => ({\n    label: getDataGridColumnTitle(column),\n    value: column.id,\n  }))\n  const operatorItems = operators.map((item) => ({\n    label: item.label,\n    value: item.value,\n  }))\n  const hasRequiredValue =\n    !activeOperator?.needsValue ||\n    (String(value).trim() !== \"\" &&\n      (!activeOperator.needsSecondValue || String(valueTo).trim() !== \"\"))\n\n  const apply = () => {\n    if (!selectedColumn || !hasRequiredValue) return\n    const nextValue: DataGridFilterValue = {\n      operator,\n      ...(activeOperator?.needsValue && {\n        value: operator === \"is-any-of\" ? [value] : value,\n      }),\n      ...(activeOperator?.needsSecondValue && { valueTo }),\n    }\n    selectedColumn.setFilterValue(nextValue)\n    table.setPageIndex(0)\n  }\n\n  return (\n    <Popover>\n      <PopoverTrigger\n        render={<Button variant=\"ghost\" size=\"sm\" className={className} />}\n      >\n        <FunnelPlusIcon aria-hidden />\n        Add filter\n      </PopoverTrigger>\n      <PopoverPopup align=\"start\" className=\"w-72 max-w-[calc(100vw-2rem)] p-[var(--hui-space-4)]\">\n        <PopoverTitle className=\"mb-[var(--hui-space-4)]\">Add filter</PopoverTitle>\n        <div className=\"grid gap-[var(--hui-space-4)]\">\n          <label className=\"grid gap-[var(--hui-space-2)] [font-size:var(--hui-font-size-small)]\">\n            <span>Column</span>\n            <Select\n              items={columnItems}\n              value={columnId}\n              onValueChange={(nextValue) => {\n                if (!nextValue) return\n                const nextColumn = filterableColumns.find(\n                  (column) => column.id === nextValue,\n                )\n                const nextType = nextColumn\n                  ? getDataGridColumnMeta(nextColumn).filter?.type\n                  : undefined\n                setColumnId(nextValue)\n                setSelectedOperator(\n                  nextType\n                    ? DATA_GRID_FILTER_OPERATORS[nextType][0]?.value ?? \"\"\n                    : \"\",\n                )\n                setValue(\"\")\n                setValueTo(\"\")\n              }}\n            >\n              <SelectTrigger aria-label=\"Filter column\" className=\"w-full\">\n                <SelectValue />\n              </SelectTrigger>\n              <SelectContent>\n                {columnItems.map((item) => (\n                  <SelectItem key={item.value} value={item.value}>\n                    {item.label}\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </label>\n          <label className=\"grid gap-[var(--hui-space-2)] [font-size:var(--hui-font-size-small)]\">\n            <span>Condition</span>\n            <Select\n              items={operatorItems}\n              value={operator}\n              onValueChange={(nextValue) =>\n                nextValue && setSelectedOperator(nextValue)\n              }\n            >\n              <SelectTrigger aria-label=\"Filter condition\" className=\"w-full\">\n                <SelectValue />\n              </SelectTrigger>\n              <SelectContent>\n                {operatorItems.map((item) => (\n                  <SelectItem key={item.value} value={item.value}>\n                    {item.label}\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </label>\n          {activeOperator?.needsValue && (\n            <label className=\"grid gap-[var(--hui-space-2)] [font-size:var(--hui-font-size-small)]\">\n              <span>Value</span>\n              <DataGridFilterValueField\n                type={type}\n                options={config?.options}\n                value={value}\n                onChange={setValue}\n                label=\"Filter value\"\n              />\n            </label>\n          )}\n          {activeOperator?.needsSecondValue && (\n            <label className=\"grid gap-[var(--hui-space-2)] [font-size:var(--hui-font-size-small)]\">\n              <span>Second value</span>\n              <DataGridFilterValueField\n                type={type}\n                options={config?.options}\n                value={valueTo}\n                onChange={setValueTo}\n                label=\"Second filter value\"\n              />\n            </label>\n          )}\n          <div className=\"flex justify-end gap-[var(--hui-space-2)]\">\n            <PopoverClose render={<Button variant=\"ghost\" size=\"sm\" />}>Cancel</PopoverClose>\n            <PopoverClose\n              render={<Button variant=\"outline\" size=\"sm\" disabled={!hasRequiredValue} onClick={apply} />}\n            >\n              Apply\n            </PopoverClose>\n          </div>\n        </div>\n      </PopoverPopup>\n    </Popover>\n  )\n}\n\nexport function DataGridActiveFilters({ className }: { className?: string }) {\n  const { table } = useDataGridContext<unknown>()\n  const filters = table.getState().columnFilters\n  if (filters.length === 0) return null\n\n  return (\n    <div\n      data-slot=\"data-grid-active-filters\"\n      className={cn(\"flex min-w-0 flex-wrap items-center gap-[var(--hui-space-2)]\", className)}\n    >\n      <span role=\"status\" className=\"sr-only\">\n        {table.getFilteredRowModel().rows.length} results\n      </span>\n      {filters.map((filter) => {\n        const column = table.getColumn(filter.id)\n        if (!column) return null\n        const label = formatDataGridFilter(\n          getDataGridColumnTitle(column),\n          filter.value as DataGridFilterValue,\n        )\n\n        return (\n          <Button\n            key={filter.id}\n            variant=\"secondary\"\n            size=\"xs\"\n            aria-label={`Remove filter: ${label}`}\n            onClick={() => column.setFilterValue(undefined)}\n            className=\"max-w-64\"\n          >\n            <span className=\"truncate\">{label}</span>\n            <XIcon aria-hidden />\n          </Button>\n        )\n      })}\n      {filters.length > 1 && (\n        <Button variant=\"link\" size=\"xs\" onClick={() => table.resetColumnFilters()}>\n          Clear all\n        </Button>\n      )}\n    </div>\n  )\n}\n\nexport function DataGridColumnVisibility({\n  className,\n  label = \"Columns\",\n}: {\n  className?: string\n  label?: string\n}) {\n  const { table, resetView } = useDataGridContext<unknown>()\n  const columns = table.getAllLeafColumns().filter((column) => column.getCanHide())\n  if (columns.length === 0) return null\n\n  return (\n    <Menu>\n      <MenuTrigger render={<Button variant=\"ghost\" size=\"sm\" className={className} />}>\n        <Settings2Icon aria-hidden />\n        {label}\n      </MenuTrigger>\n      <MenuPopup align=\"end\">\n        <MenuGroup>\n          <MenuGroupLabel>{label}</MenuGroupLabel>\n          {columns.map((column) => (\n            <MenuCheckboxItem\n              key={column.id}\n              checked={column.getIsVisible()}\n              onCheckedChange={(checked) => column.toggleVisibility(checked)}\n            >\n              {getDataGridColumnTitle(column)}\n            </MenuCheckboxItem>\n          ))}\n        </MenuGroup>\n        <MenuSeparator />\n        <MenuItem onClick={() => table.toggleAllColumnsVisible(true)}>Show all</MenuItem>\n        <MenuItem onClick={resetView}>Reset view</MenuItem>\n      </MenuPopup>\n    </Menu>\n  )\n}\n\nexport function DataGridExport({\n  className,\n  fileName = \"data-grid.csv\",\n  onExport,\n}: {\n  className?: string\n  fileName?: string\n  onExport?: () => void\n}) {\n  const { table } = useDataGridContext<unknown>()\n\n  const exportRows = () => {\n    if (onExport) {\n      onExport()\n      return\n    }\n\n    const columns = table\n      .getVisibleLeafColumns()\n      .filter((column) => column.id !== \"select\" && column.id !== \"actions\")\n    const csv = buildDataGridCsv(\n      columns.map(getDataGridColumnTitle),\n      table.getFilteredRowModel().rows.map((row) =>\n        columns.map((column) => row.getValue(column.id)),\n      ),\n    )\n    const url = URL.createObjectURL(new Blob([csv], { type: \"text/csv;charset=utf-8\" }))\n    const link = document.createElement(\"a\")\n    link.href = url\n    link.download = fileName\n    link.click()\n    URL.revokeObjectURL(url)\n  }\n\n  return (\n    <Button variant=\"ghost\" size=\"sm\" className={className} onClick={exportRows}>\n      <DownloadIcon aria-hidden />\n      Export CSV\n    </Button>\n  )\n}\n\nexport function DataGridRefresh({\n  className,\n  onRefresh,\n}: {\n  className?: string\n  onRefresh: () => void | Promise<void>\n}) {\n  const { refreshing } = useDataGridContext<unknown>()\n\n  return (\n    <Tooltip>\n      <TooltipTrigger\n        render={\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            aria-label=\"Refresh data\"\n            className={className}\n            disabled={refreshing}\n            onClick={onRefresh}\n          />\n        }\n      >\n        <RefreshIcon aria-hidden className={cn(refreshing && \"motion-safe:animate-spin\")} />\n      </TooltipTrigger>\n      <TooltipPopup>Refresh data</TooltipPopup>\n    </Tooltip>\n  )\n}\n"},{"path":"data-grid-utils.ts","type":"registry:component","target":"components/ui/data-grid/data-grid-utils.ts","content":"import type { Row } from \"@tanstack/react-table\"\n\nimport type {\n  DataGridFilterType,\n  DataGridFilterValue,\n} from \"./data-grid-context\"\n\nexport const DATA_GRID_FILTER_OPERATORS: Record<\n  DataGridFilterType,\n  Array<{ value: string; label: string; needsValue: boolean; needsSecondValue?: boolean }>\n> = {\n  text: [\n    { value: \"contains\", label: \"contains\", needsValue: true },\n    { value: \"not-contains\", label: \"does not contain\", needsValue: true },\n    { value: \"is\", label: \"is\", needsValue: true },\n    { value: \"is-not\", label: \"is not\", needsValue: true },\n    { value: \"starts-with\", label: \"starts with\", needsValue: true },\n    { value: \"ends-with\", label: \"ends with\", needsValue: true },\n    { value: \"is-empty\", label: \"is empty\", needsValue: false },\n    { value: \"is-not-empty\", label: \"is not empty\", needsValue: false },\n  ],\n  number: [\n    { value: \"equals\", label: \"equals\", needsValue: true },\n    { value: \"not-equal\", label: \"does not equal\", needsValue: true },\n    { value: \"greater-than\", label: \"is greater than\", needsValue: true },\n    { value: \"greater-than-or-equal\", label: \"is at least\", needsValue: true },\n    { value: \"less-than\", label: \"is less than\", needsValue: true },\n    { value: \"less-than-or-equal\", label: \"is at most\", needsValue: true },\n    { value: \"between\", label: \"is between\", needsValue: true, needsSecondValue: true },\n    { value: \"is-empty\", label: \"is empty\", needsValue: false },\n  ],\n  currency: [],\n  date: [\n    { value: \"is\", label: \"is\", needsValue: true },\n    { value: \"before\", label: \"is before\", needsValue: true },\n    { value: \"after\", label: \"is after\", needsValue: true },\n    { value: \"between\", label: \"is between\", needsValue: true, needsSecondValue: true },\n    { value: \"is-empty\", label: \"is empty\", needsValue: false },\n  ],\n  enum: [\n    { value: \"is\", label: \"is\", needsValue: true },\n    { value: \"is-not\", label: \"is not\", needsValue: true },\n    { value: \"is-any-of\", label: \"is any of\", needsValue: true },\n  ],\n  boolean: [\n    { value: \"is\", label: \"is\", needsValue: true },\n  ],\n}\n\nDATA_GRID_FILTER_OPERATORS.currency = DATA_GRID_FILTER_OPERATORS.number\n\nfunction isEmpty(value: unknown) {\n  return value === null || value === undefined || value === \"\"\n}\n\nfunction comparableDate(value: unknown) {\n  if (value instanceof Date) return value.getTime()\n  const time = new Date(String(value)).getTime()\n  return Number.isNaN(time) ? null : time\n}\n\nexport function dataGridFilterFn<TData>(\n  row: Row<TData>,\n  columnId: string,\n  rawFilter: unknown,\n) {\n  const filter = rawFilter as DataGridFilterValue | undefined\n  if (!filter?.operator) return true\n\n  const rowValue = row.getValue(columnId)\n  const operator = filter.operator\n\n  if (operator === \"is-empty\") return isEmpty(rowValue)\n  if (operator === \"is-not-empty\") return !isEmpty(rowValue)\n\n  if (operator === \"is-any-of\") {\n    const values = Array.isArray(filter.value) ? filter.value : [filter.value]\n    return values.map(String).includes(String(rowValue))\n  }\n\n  if (typeof rowValue === \"boolean\") {\n    return rowValue === (filter.value === true || filter.value === \"true\")\n  }\n\n  const rowDate = comparableDate(rowValue)\n  const filterDate = comparableDate(filter.value)\n  const filterDateTo = comparableDate(filter.valueTo)\n  const looksLikeDate =\n    rowValue instanceof Date ||\n    (typeof rowValue === \"string\" && /^\\d{4}-\\d{2}-\\d{2}/.test(rowValue))\n\n  if (looksLikeDate && rowDate !== null && filterDate !== null) {\n    if (operator === \"is\") return rowDate === filterDate\n    if (operator === \"before\") return rowDate < filterDate\n    if (operator === \"after\") return rowDate > filterDate\n    if (operator === \"between\" && filterDateTo !== null) {\n      return rowDate >= filterDate && rowDate <= filterDateTo\n    }\n  }\n\n  const rowNumber = typeof rowValue === \"number\" ? rowValue : Number(rowValue)\n  const filterNumber = Number(filter.value)\n  const filterNumberTo = Number(filter.valueTo)\n  const hasNumericValues =\n    !Number.isNaN(rowNumber) &&\n    !Number.isNaN(filterNumber) &&\n    typeof rowValue !== \"string\"\n\n  if (hasNumericValues) {\n    if (operator === \"equals\") return rowNumber === filterNumber\n    if (operator === \"not-equal\") return rowNumber !== filterNumber\n    if (operator === \"greater-than\") return rowNumber > filterNumber\n    if (operator === \"greater-than-or-equal\") return rowNumber >= filterNumber\n    if (operator === \"less-than\") return rowNumber < filterNumber\n    if (operator === \"less-than-or-equal\") return rowNumber <= filterNumber\n    if (operator === \"between\" && !Number.isNaN(filterNumberTo)) {\n      return rowNumber >= filterNumber && rowNumber <= filterNumberTo\n    }\n  }\n\n  const actual = String(rowValue ?? \"\").toLocaleLowerCase()\n  const expected = String(filter.value ?? \"\").toLocaleLowerCase()\n\n  if (operator === \"contains\") return actual.includes(expected)\n  if (operator === \"not-contains\") return !actual.includes(expected)\n  if (operator === \"is\") return actual === expected\n  if (operator === \"is-not\") return actual !== expected\n  if (operator === \"starts-with\") return actual.startsWith(expected)\n  if (operator === \"ends-with\") return actual.endsWith(expected)\n\n  return true\n}\n\nexport function moveDataGridColumn(\n  order: string[],\n  sourceId: string,\n  targetId: string,\n) {\n  if (sourceId === targetId) return order\n  const next = order.filter((id) => id !== sourceId)\n  const targetIndex = next.indexOf(targetId)\n  if (targetIndex === -1) return order\n  next.splice(targetIndex, 0, sourceId)\n  return next\n}\n\nexport function getDataGridPageItems(currentPage: number, pageCount: number) {\n  const visiblePages = new Set([\n    0,\n    pageCount - 1,\n    currentPage - 1,\n    currentPage,\n    currentPage + 1,\n  ])\n  const items: Array<{ type: \"page\"; page: number } | { type: \"ellipsis\" }> = []\n\n  for (let page = 0; page < pageCount; page++) {\n    if (visiblePages.has(page)) {\n      items.push({ type: \"page\", page })\n    } else if (items.at(-1)?.type !== \"ellipsis\") {\n      items.push({ type: \"ellipsis\" })\n    }\n  }\n\n  return items\n}\n\nexport function formatDataGridFilter(\n  title: string,\n  filter: DataGridFilterValue,\n) {\n  const operator = Object.values(DATA_GRID_FILTER_OPERATORS)\n    .flat()\n    .find((item) => item.value === filter.operator)?.label ?? filter.operator\n  const rawValue = Array.isArray(filter.value)\n    ? filter.value.join(\", \")\n    : String(filter.value ?? \"\")\n  const value = filter.valueTo\n    ? `${rawValue} and ${String(filter.valueTo)}`\n    : rawValue\n\n  return `${title} ${operator}${value ? ` ${value}` : \"\"}`\n}\n\nconst CSV_FORMULA_PREFIX = /^[=+\\-@\\t\\r\\n\\0\\uFEFF\\uFF0B\\uFF0D\\uFF1D\\uFF20]/\n\nfunction escapeCsvCell(value: unknown) {\n  const text = value == null ? \"\" : String(value)\n  const safeText =\n    typeof value === \"string\" && CSV_FORMULA_PREFIX.test(text)\n      ? `'${text}`\n      : text\n  return `\"${safeText.replaceAll('\"', '\"\"')}\"`\n}\n\nexport function buildDataGridCsv(\n  headers: string[],\n  rows: unknown[][],\n) {\n  return [headers, ...rows]\n    .map((row) => row.map(escapeCsvCell).join(\",\"))\n    .join(\"\\n\")\n}\n"}],"dependencies":["@tanstack/react-table","@tanstack/react-virtual","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/popover.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","https://www.honestui.com/r/tooltip.json"]}