{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table",
  "title": "Data Table",
  "description": "A feature-rich data table component with server-side filtering, sorting, and pagination",
  "dependencies": [
    "@tanstack/react-table",
    "lucide-react",
    "nuqs"
  ],
  "registryDependencies": [
    "badge",
    "button",
    "calendar",
    "command",
    "dropdown-menu",
    "input",
    "popover",
    "select",
    "separator",
    "slider",
    "table"
  ],
  "files": [
    {
      "path": "src/components/data-table/data-table.tsx",
      "content": "import { flexRender, type Table as TanstackTable } from \"@tanstack/react-table\";\nimport type * as React from \"react\";\n\nimport { DataTablePagination } from \"@/components/data-table/data-table-pagination\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\nimport { getColumnPinningStyle } from \"@/lib/data-table\";\nimport { cn } from \"@/lib/utils\";\n\ninterface DataTableProps<TData> extends React.ComponentProps<\"div\"> {\n  table: TanstackTable<TData>;\n  actionBar?: React.ReactNode;\n}\n\nexport function DataTable<TData>({\n  table,\n  actionBar,\n  children,\n  className,\n  ...props\n}: DataTableProps<TData>) {\n  return (\n    <div\n      className={cn(\"flex w-full flex-col gap-2.5 overflow-auto\", className)}\n      {...props}\n    >\n      {children}\n      <div className=\"overflow-hidden rounded-md border\">\n        <Table>\n          <TableHeader>\n            {table.getHeaderGroups().map((headerGroup) => (\n              <TableRow key={headerGroup.id}>\n                {headerGroup.headers.map((header) => (\n                  <TableHead\n                    key={header.id}\n                    colSpan={header.colSpan}\n                    style={{\n                      ...getColumnPinningStyle({ column: header.column }),\n                    }}\n                  >\n                    {header.isPlaceholder\n                      ? null\n                      : flexRender(\n                          header.column.columnDef.header,\n                          header.getContext(),\n                        )}\n                  </TableHead>\n                ))}\n              </TableRow>\n            ))}\n          </TableHeader>\n          <TableBody>\n            {table.getRowModel().rows?.length ? (\n              table.getRowModel().rows.map((row) => (\n                <TableRow\n                  key={row.id}\n                  data-state={row.getIsSelected() && \"selected\"}\n                >\n                  {row.getVisibleCells().map((cell) => (\n                    <TableCell\n                      key={cell.id}\n                      style={{\n                        ...getColumnPinningStyle({ column: cell.column }),\n                      }}\n                    >\n                      {flexRender(\n                        cell.column.columnDef.cell,\n                        cell.getContext(),\n                      )}\n                    </TableCell>\n                  ))}\n                </TableRow>\n              ))\n            ) : (\n              <TableRow>\n                <TableCell\n                  colSpan={table.getAllColumns().length}\n                  className=\"h-24 text-center\"\n                >\n                  No results.\n                </TableCell>\n              </TableRow>\n            )}\n          </TableBody>\n        </Table>\n      </div>\n      <div className=\"flex flex-col gap-2.5\">\n        <DataTablePagination table={table} />\n        {actionBar &&\n          table.getFilteredSelectedRowModel().rows.length > 0 &&\n          actionBar}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table.tsx"
    },
    {
      "path": "src/components/data-table/data-table-column-header.tsx",
      "content": "\"use client\";\n\nimport type { Column } from \"@tanstack/react-table\";\nimport {\n  ChevronDown,\n  ChevronsUpDown,\n  ChevronUp,\n  EyeOff,\n  X,\n} from \"lucide-react\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { cn } from \"@/lib/utils\";\n\ninterface DataTableColumnHeaderProps<TData, TValue>\n  extends React.ComponentProps<typeof DropdownMenuTrigger> {\n  column: Column<TData, TValue>;\n  label: string;\n}\n\nexport function DataTableColumnHeader<TData, TValue>({\n  column,\n  label,\n  className,\n  ...props\n}: DataTableColumnHeaderProps<TData, TValue>) {\n  if (!column.getCanSort() && !column.getCanHide()) {\n    return <div className={cn(className)}>{label}</div>;\n  }\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        className={cn(\n          \"-ml-1.5 flex h-8 items-center gap-1.5 rounded-md px-2 py-1.5 hover:bg-accent focus:outline-none focus:ring-1 focus:ring-ring data-[state=open]:bg-accent [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground\",\n          className,\n        )}\n        {...props}\n      >\n        {label}\n        {column.getCanSort() &&\n          (column.getIsSorted() === \"desc\" ? (\n            <ChevronDown />\n          ) : column.getIsSorted() === \"asc\" ? (\n            <ChevronUp />\n          ) : (\n            <ChevronsUpDown />\n          ))}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"start\" className=\"w-28\">\n        {column.getCanSort() && (\n          <>\n            <DropdownMenuCheckboxItem\n              className=\"relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground\"\n              checked={column.getIsSorted() === \"asc\"}\n              onClick={() => column.toggleSorting(false)}\n            >\n              <ChevronUp />\n              Asc\n            </DropdownMenuCheckboxItem>\n            <DropdownMenuCheckboxItem\n              className=\"relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground\"\n              checked={column.getIsSorted() === \"desc\"}\n              onClick={() => column.toggleSorting(true)}\n            >\n              <ChevronDown />\n              Desc\n            </DropdownMenuCheckboxItem>\n            {column.getIsSorted() && (\n              <DropdownMenuItem\n                className=\"pl-2 [&_svg]:text-muted-foreground\"\n                onClick={() => column.clearSorting()}\n              >\n                <X />\n                Reset\n              </DropdownMenuItem>\n            )}\n          </>\n        )}\n        {column.getCanHide() && (\n          <DropdownMenuCheckboxItem\n            className=\"relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground\"\n            checked={!column.getIsVisible()}\n            onClick={() => column.toggleVisibility(false)}\n          >\n            <EyeOff />\n            Hide\n          </DropdownMenuCheckboxItem>\n        )}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table-column-header.tsx"
    },
    {
      "path": "src/components/data-table/data-table-pagination.tsx",
      "content": "import type { Table } from \"@tanstack/react-table\";\nimport {\n  ChevronLeft,\n  ChevronRight,\n  ChevronsLeft,\n  ChevronsRight,\n} from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\n\ninterface DataTablePaginationProps<TData> extends React.ComponentProps<\"div\"> {\n  table: Table<TData>;\n  pageSizeOptions?: number[];\n}\n\nexport function DataTablePagination<TData>({\n  table,\n  pageSizeOptions = [10, 20, 30, 40, 50],\n  className,\n  ...props\n}: DataTablePaginationProps<TData>) {\n  return (\n    <div\n      className={cn(\n        \"flex w-full flex-col-reverse items-center justify-between gap-4 overflow-auto p-1 sm:flex-row sm:gap-8\",\n        className,\n      )}\n      {...props}\n    >\n      <div className=\"flex-1 whitespace-nowrap text-muted-foreground text-sm\">\n        {table.getFilteredSelectedRowModel().rows.length} of{\" \"}\n        {table.getFilteredRowModel().rows.length} row(s) selected.\n      </div>\n      <div className=\"flex flex-col-reverse items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8\">\n        <div className=\"flex items-center space-x-2\">\n          <p className=\"whitespace-nowrap font-medium text-sm\">Rows per page</p>\n          <Select\n            value={`${table.getState().pagination.pageSize}`}\n            onValueChange={(value) => {\n              table.setPageSize(Number(value));\n            }}\n          >\n            <SelectTrigger className=\"h-8 w-18 data-size:h-8\">\n              <SelectValue placeholder={table.getState().pagination.pageSize} />\n            </SelectTrigger>\n            <SelectContent side=\"top\">\n              <SelectGroup>\n                {pageSizeOptions.map((pageSize) => (\n                  <SelectItem key={pageSize} value={`${pageSize}`}>\n                    {pageSize}\n                  </SelectItem>\n                ))}\n              </SelectGroup>\n            </SelectContent>\n          </Select>\n        </div>\n        <div className=\"flex items-center justify-center font-medium text-sm\">\n          Page {table.getState().pagination.pageIndex + 1} of{\" \"}\n          {table.getPageCount()}\n        </div>\n        <div className=\"flex items-center space-x-2\">\n          <Button\n            aria-label=\"Go to first page\"\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"hidden size-8 lg:flex\"\n            onClick={() => table.setPageIndex(0)}\n            disabled={!table.getCanPreviousPage()}\n          >\n            <ChevronsLeft />\n          </Button>\n          <Button\n            aria-label=\"Go to previous page\"\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"size-8\"\n            onClick={() => table.previousPage()}\n            disabled={!table.getCanPreviousPage()}\n          >\n            <ChevronLeft />\n          </Button>\n          <Button\n            aria-label=\"Go to next page\"\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"size-8\"\n            onClick={() => table.nextPage()}\n            disabled={!table.getCanNextPage()}\n          >\n            <ChevronRight />\n          </Button>\n          <Button\n            aria-label=\"Go to last page\"\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"hidden size-8 lg:flex\"\n            onClick={() => table.setPageIndex(table.getPageCount() - 1)}\n            disabled={!table.getCanNextPage()}\n          >\n            <ChevronsRight />\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table-pagination.tsx"
    },
    {
      "path": "src/components/data-table/data-table-view-options.tsx",
      "content": "\"use client\";\n\nimport type { Table } from \"@tanstack/react-table\";\nimport { Settings2 } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\n\ninterface DataTableViewOptionsProps<TData>\n  extends React.ComponentProps<typeof PopoverContent> {\n  table: Table<TData>;\n  disabled?: boolean;\n}\n\nexport function DataTableViewOptions<TData>({\n  table,\n  disabled,\n  className,\n  ...props\n}: DataTableViewOptionsProps<TData>) {\n  const columns = React.useMemo(\n    () =>\n      table\n        .getAllColumns()\n        .filter(\n          (column) =>\n            typeof column.accessorFn !== \"undefined\" && column.getCanHide(),\n        ),\n    [table],\n  );\n\n  return (\n    <Popover>\n      <PopoverTrigger asChild>\n        <Button\n          aria-label=\"Toggle columns\"\n          role=\"combobox\"\n          variant=\"outline\"\n          className=\"ml-auto hidden h-8 font-normal lg:flex\"\n          disabled={disabled}\n        >\n          <Settings2 className=\"text-muted-foreground\" />\n          View\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className={cn(\"w-44 p-0\", className)} {...props}>\n        <Command>\n          <CommandInput placeholder=\"Search columns...\" />\n          <CommandList>\n            <CommandEmpty>No columns found.</CommandEmpty>\n            <CommandGroup>\n              {columns.map((column) => (\n                <CommandItem\n                  key={column.id}\n                  data-checked={column.getIsVisible()}\n                  onSelect={() =>\n                    column.toggleVisibility(!column.getIsVisible())\n                  }\n                >\n                  <span className=\"truncate\">\n                    {column.columnDef.meta?.label ?? column.id}\n                  </span>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n          </CommandList>\n        </Command>\n      </PopoverContent>\n    </Popover>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table-view-options.tsx"
    },
    {
      "path": "src/components/data-table/data-table-faceted-filter.tsx",
      "content": "\"use client\";\n\nimport type { Column } from \"@tanstack/react-table\";\nimport { Check, PlusCircle, XCircle } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandSeparator,\n} from \"@/components/ui/command\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\nimport type { Option } from \"@/types/data-table\";\n\ninterface DataTableFacetedFilterProps<TData, TValue> {\n  column?: Column<TData, TValue>;\n  title?: string;\n  options: Option[];\n  multiple?: boolean;\n}\n\nexport function DataTableFacetedFilter<TData, TValue>({\n  column,\n  title,\n  options,\n  multiple,\n}: DataTableFacetedFilterProps<TData, TValue>) {\n  const [open, setOpen] = React.useState(false);\n\n  const columnFilterValue = column?.getFilterValue();\n  const selectedValues = new Set(\n    Array.isArray(columnFilterValue) ? columnFilterValue : [],\n  );\n\n  const onItemSelect = React.useCallback(\n    (option: Option, isSelected: boolean) => {\n      if (!column) return;\n\n      if (multiple) {\n        const newSelectedValues = new Set(selectedValues);\n        if (isSelected) {\n          newSelectedValues.delete(option.value);\n        } else {\n          newSelectedValues.add(option.value);\n        }\n        const filterValues = Array.from(newSelectedValues);\n        column.setFilterValue(filterValues.length ? filterValues : undefined);\n      } else {\n        column.setFilterValue(isSelected ? undefined : [option.value]);\n        setOpen(false);\n      }\n    },\n    [column, multiple, selectedValues],\n  );\n\n  const onReset = React.useCallback(\n    (event?: React.MouseEvent) => {\n      event?.stopPropagation();\n      column?.setFilterValue(undefined);\n    },\n    [column],\n  );\n\n  return (\n    <Popover open={open} onOpenChange={setOpen}>\n      <PopoverTrigger asChild>\n        <Button variant=\"outline\" className=\"border-dashed font-normal\">\n          {selectedValues?.size > 0 ? (\n            <div\n              role=\"button\"\n              aria-label={`Clear ${title} filter`}\n              tabIndex={0}\n              className=\"rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\"\n              onClick={onReset}\n            >\n              <XCircle />\n            </div>\n          ) : (\n            <PlusCircle />\n          )}\n          {title}\n          {selectedValues?.size > 0 && (\n            <>\n              <Separator\n                orientation=\"vertical\"\n                className=\"mx-0.5 data-[orientation=vertical]:h-4\"\n              />\n              <Badge\n                variant=\"secondary\"\n                className=\"rounded-sm px-1 font-normal lg:hidden\"\n              >\n                {selectedValues.size}\n              </Badge>\n              <div className=\"hidden items-center gap-1 lg:flex\">\n                {selectedValues.size > 2 ? (\n                  <Badge\n                    variant=\"secondary\"\n                    className=\"rounded-sm px-1 font-normal\"\n                  >\n                    {selectedValues.size} selected\n                  </Badge>\n                ) : (\n                  options\n                    .filter((option) => selectedValues.has(option.value))\n                    .map((option) => (\n                      <Badge\n                        variant=\"secondary\"\n                        key={option.value}\n                        className=\"rounded-sm px-1 font-normal\"\n                      >\n                        {option.label}\n                      </Badge>\n                    ))\n                )}\n              </div>\n            </>\n          )}\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-50 p-0\" align=\"start\">\n        <Command>\n          <CommandInput placeholder={title} />\n          <CommandList className=\"max-h-full\">\n            <CommandEmpty>No results found.</CommandEmpty>\n            <CommandGroup className=\"max-h-[300px] scroll-py-1 overflow-y-auto overflow-x-hidden\">\n              {options.map((option) => {\n                const isSelected = selectedValues.has(option.value);\n\n                return (\n                  <CommandItem\n                    key={option.value}\n                    className=\"[&>svg:last-child]:hidden\"\n                    onSelect={() => onItemSelect(option, isSelected)}\n                  >\n                    <div\n                      className={cn(\n                        \"flex size-4 items-center justify-center rounded-sm border border-primary\",\n                        isSelected\n                          ? \"bg-primary text-primary-foreground\"\n                          : \"opacity-50 [&_svg]:invisible\",\n                      )}\n                    >\n                      <Check />\n                    </div>\n                    {option.icon && <option.icon />}\n                    <span className=\"truncate\">{option.label}</span>\n                    {option.count && (\n                      <span className=\"ml-auto font-mono text-xs\">\n                        {option.count}\n                      </span>\n                    )}\n                  </CommandItem>\n                );\n              })}\n            </CommandGroup>\n            {selectedValues.size > 0 && (\n              <>\n                <CommandSeparator />\n                <CommandGroup>\n                  <CommandItem\n                    onSelect={() => onReset()}\n                    className=\"justify-center text-center\"\n                  >\n                    Clear filters\n                  </CommandItem>\n                </CommandGroup>\n              </>\n            )}\n          </CommandList>\n        </Command>\n      </PopoverContent>\n    </Popover>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table-faceted-filter.tsx"
    },
    {
      "path": "src/components/data-table/data-table-toolbar.tsx",
      "content": "\"use client\";\n\nimport type { Column, Table } from \"@tanstack/react-table\";\nimport { X } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { DataTableDateFilter } from \"@/components/data-table/data-table-date-filter\";\nimport { DataTableFacetedFilter } from \"@/components/data-table/data-table-faceted-filter\";\nimport { DataTableSliderFilter } from \"@/components/data-table/data-table-slider-filter\";\nimport { DataTableViewOptions } from \"@/components/data-table/data-table-view-options\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { cn } from \"@/lib/utils\";\n\ninterface DataTableToolbarProps<TData> extends React.ComponentProps<\"div\"> {\n  table: Table<TData>;\n}\n\nexport function DataTableToolbar<TData>({\n  table,\n  children,\n  className,\n  ...props\n}: DataTableToolbarProps<TData>) {\n  const isFiltered = table.getState().columnFilters.length > 0;\n\n  const columns = React.useMemo(\n    () => table.getAllColumns().filter((column) => column.getCanFilter()),\n    [table],\n  );\n\n  const onReset = React.useCallback(() => {\n    table.resetColumnFilters();\n  }, [table]);\n\n  return (\n    <div\n      role=\"toolbar\"\n      aria-orientation=\"horizontal\"\n      className={cn(\n        \"flex w-full items-start justify-between gap-2 p-1\",\n        className,\n      )}\n      {...props}\n    >\n      <div className=\"flex flex-1 flex-wrap items-center gap-2\">\n        {columns.map((column) => (\n          <DataTableToolbarFilter key={column.id} column={column} />\n        ))}\n        {isFiltered && (\n          <Button\n            aria-label=\"Reset filters\"\n            variant=\"outline\"\n            className=\"border-dashed\"\n            onClick={onReset}\n          >\n            <X />\n            Reset\n          </Button>\n        )}\n      </div>\n      <div className=\"flex items-center gap-2\">\n        {children}\n        <DataTableViewOptions table={table} align=\"end\" />\n      </div>\n    </div>\n  );\n}\ninterface DataTableToolbarFilterProps<TData> {\n  column: Column<TData>;\n}\n\nfunction DataTableToolbarFilter<TData>({\n  column,\n}: DataTableToolbarFilterProps<TData>) {\n  {\n    const columnMeta = column.columnDef.meta;\n\n    const onFilterRender = React.useCallback(() => {\n      if (!columnMeta?.variant) return null;\n\n      switch (columnMeta.variant) {\n        case \"text\":\n          return (\n            <Input\n              placeholder={columnMeta.placeholder ?? columnMeta.label}\n              value={(column.getFilterValue() as string) ?? \"\"}\n              onChange={(event) => column.setFilterValue(event.target.value)}\n              className=\"h-8 w-40 lg:w-56\"\n            />\n          );\n\n        case \"number\":\n          return (\n            <div className=\"relative\">\n              <Input\n                type=\"number\"\n                inputMode=\"numeric\"\n                placeholder={columnMeta.placeholder ?? columnMeta.label}\n                value={(column.getFilterValue() as string) ?? \"\"}\n                onChange={(event) => column.setFilterValue(event.target.value)}\n                className={cn(\"h-8 w-[120px]\", columnMeta.unit && \"pr-8\")}\n              />\n              {columnMeta.unit && (\n                <span className=\"absolute top-0 right-0 bottom-0 flex items-center rounded-r-md bg-accent px-2 text-muted-foreground text-sm\">\n                  {columnMeta.unit}\n                </span>\n              )}\n            </div>\n          );\n\n        case \"range\":\n          return (\n            <DataTableSliderFilter\n              column={column}\n              title={columnMeta.label ?? column.id}\n            />\n          );\n\n        case \"date\":\n        case \"dateRange\":\n          return (\n            <DataTableDateFilter\n              column={column}\n              title={columnMeta.label ?? column.id}\n              multiple={columnMeta.variant === \"dateRange\"}\n            />\n          );\n\n        case \"select\":\n        case \"multiSelect\":\n          return (\n            <DataTableFacetedFilter\n              column={column}\n              title={columnMeta.label ?? column.id}\n              options={columnMeta.options ?? []}\n              multiple={columnMeta.variant === \"multiSelect\"}\n            />\n          );\n\n        default:\n          return null;\n      }\n    }, [column, columnMeta]);\n\n    return onFilterRender();\n  }\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table-toolbar.tsx"
    },
    {
      "path": "src/components/data-table/data-table-slider-filter.tsx",
      "content": "\"use client\";\n\nimport type { Column } from \"@tanstack/react-table\";\nimport { PlusCircle, XCircle } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Slider } from \"@/components/ui/slider\";\nimport { cn } from \"@/lib/utils\";\n\ninterface Range {\n  min: number;\n  max: number;\n}\n\ntype RangeValue = [number, number];\n\nfunction getIsValidRange(value: unknown): value is RangeValue {\n  return (\n    Array.isArray(value) &&\n    value.length === 2 &&\n    typeof value[0] === \"number\" &&\n    typeof value[1] === \"number\"\n  );\n}\n\nfunction parseValuesAsNumbers(value: unknown): RangeValue | undefined {\n  if (\n    Array.isArray(value) &&\n    value.length === 2 &&\n    value.every(\n      (v) =>\n        (typeof v === \"string\" || typeof v === \"number\") && !Number.isNaN(v),\n    )\n  ) {\n    return [Number(value[0]), Number(value[1])];\n  }\n\n  return undefined;\n}\n\ninterface DataTableSliderFilterProps<TData> {\n  column: Column<TData, unknown>;\n  title?: string;\n}\n\nexport function DataTableSliderFilter<TData>({\n  column,\n  title,\n}: DataTableSliderFilterProps<TData>) {\n  const id = React.useId();\n\n  const columnFilterValue = parseValuesAsNumbers(column.getFilterValue());\n\n  const defaultRange = column.columnDef.meta?.range;\n  const unit = column.columnDef.meta?.unit;\n\n  const { min, max, step } = React.useMemo<Range & { step: number }>(() => {\n    let minValue = 0;\n    let maxValue = 100;\n\n    if (defaultRange && getIsValidRange(defaultRange)) {\n      [minValue, maxValue] = defaultRange;\n    } else {\n      const values = column.getFacetedMinMaxValues();\n      if (values && Array.isArray(values) && values.length === 2) {\n        const [facetMinValue, facetMaxValue] = values;\n        if (\n          typeof facetMinValue === \"number\" &&\n          typeof facetMaxValue === \"number\"\n        ) {\n          minValue = facetMinValue;\n          maxValue = facetMaxValue;\n        }\n      }\n    }\n\n    const rangeSize = maxValue - minValue;\n    const step =\n      rangeSize <= 20\n        ? 1\n        : rangeSize <= 100\n          ? Math.ceil(rangeSize / 20)\n          : Math.ceil(rangeSize / 50);\n\n    return { min: minValue, max: maxValue, step };\n  }, [column, defaultRange]);\n\n  const range = React.useMemo((): RangeValue => {\n    return columnFilterValue ?? [min, max];\n  }, [columnFilterValue, min, max]);\n\n  const formatValue = React.useCallback((value: number) => {\n    return value.toLocaleString(undefined, { maximumFractionDigits: 0 });\n  }, []);\n\n  const onFromInputChange = React.useCallback(\n    (event: React.ChangeEvent<HTMLInputElement>) => {\n      const numValue = Number(event.target.value);\n      if (!Number.isNaN(numValue) && numValue >= min && numValue <= range[1]) {\n        column.setFilterValue([numValue, range[1]]);\n      }\n    },\n    [column, min, range],\n  );\n\n  const onToInputChange = React.useCallback(\n    (event: React.ChangeEvent<HTMLInputElement>) => {\n      const numValue = Number(event.target.value);\n      if (!Number.isNaN(numValue) && numValue <= max && numValue >= range[0]) {\n        column.setFilterValue([range[0], numValue]);\n      }\n    },\n    [column, max, range],\n  );\n\n  const onSliderValueChange = React.useCallback(\n    (value: RangeValue) => {\n      if (Array.isArray(value) && value.length === 2) {\n        column.setFilterValue(value);\n      }\n    },\n    [column],\n  );\n\n  const onReset = React.useCallback(\n    (event: React.MouseEvent) => {\n      if (event.target instanceof HTMLDivElement) {\n        event.stopPropagation();\n      }\n      column.setFilterValue(undefined);\n    },\n    [column],\n  );\n\n  return (\n    <Popover>\n      <PopoverTrigger asChild>\n        <Button variant=\"outline\" className=\"border-dashed font-normal\">\n          {columnFilterValue ? (\n            <div\n              role=\"button\"\n              aria-label={`Clear ${title} filter`}\n              tabIndex={0}\n              className=\"rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\"\n              onClick={onReset}\n            >\n              <XCircle />\n            </div>\n          ) : (\n            <PlusCircle />\n          )}\n          <span>{title}</span>\n          {columnFilterValue ? (\n            <>\n              <Separator\n                orientation=\"vertical\"\n                className=\"mx-0.5 data-[orientation=vertical]:h-4\"\n              />\n              {formatValue(columnFilterValue[0])} -{\" \"}\n              {formatValue(columnFilterValue[1])}\n              {unit ? ` ${unit}` : \"\"}\n            </>\n          ) : null}\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent align=\"start\" className=\"flex w-auto flex-col gap-4\">\n        <div className=\"flex flex-col gap-3\">\n          <p className=\"font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n            {title}\n          </p>\n          <div className=\"flex items-center gap-4\">\n            <Label htmlFor={`${id}-from`} className=\"sr-only\">\n              From\n            </Label>\n            <div className=\"relative\">\n              <Input\n                id={`${id}-from`}\n                type=\"number\"\n                aria-valuemin={min}\n                aria-valuemax={max}\n                inputMode=\"numeric\"\n                pattern=\"[0-9]*\"\n                placeholder={min.toString()}\n                min={min}\n                max={max}\n                value={range[0]?.toString()}\n                onChange={onFromInputChange}\n                className={cn(\"h-8 w-24\", unit && \"pr-8\")}\n              />\n              {unit && (\n                <span className=\"absolute top-0 right-0 bottom-0 flex items-center rounded-r-md bg-accent px-2 text-muted-foreground text-sm\">\n                  {unit}\n                </span>\n              )}\n            </div>\n            <Label htmlFor={`${id}-to`} className=\"sr-only\">\n              to\n            </Label>\n            <div className=\"relative\">\n              <Input\n                id={`${id}-to`}\n                type=\"number\"\n                aria-valuemin={min}\n                aria-valuemax={max}\n                inputMode=\"numeric\"\n                pattern=\"[0-9]*\"\n                placeholder={max.toString()}\n                min={min}\n                max={max}\n                value={range[1]?.toString()}\n                onChange={onToInputChange}\n                className={cn(\"h-8 w-24\", unit && \"pr-8\")}\n              />\n              {unit && (\n                <span className=\"absolute top-0 right-0 bottom-0 flex items-center rounded-r-md bg-accent px-2 text-muted-foreground text-sm\">\n                  {unit}\n                </span>\n              )}\n            </div>\n          </div>\n          <Label htmlFor={`${id}-slider`} className=\"sr-only\">\n            {title} slider\n          </Label>\n          <Slider\n            id={`${id}-slider`}\n            min={min}\n            max={max}\n            step={step}\n            value={range}\n            onValueChange={onSliderValueChange}\n          />\n        </div>\n        <Button\n          aria-label={`Clear ${title} filter`}\n          variant=\"outline\"\n          onClick={onReset}\n        >\n          Clear\n        </Button>\n      </PopoverContent>\n    </Popover>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table-slider-filter.tsx"
    },
    {
      "path": "src/components/data-table/data-table-date-filter.tsx",
      "content": "\"use client\";\n\nimport type { Column } from \"@tanstack/react-table\";\nimport { CalendarIcon, XCircle } from \"lucide-react\";\nimport * as React from \"react\";\nimport type { DateRange } from \"react-day-picker\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Calendar } from \"@/components/ui/calendar\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { formatDate } from \"@/lib/format\";\n\ntype DateSelection = Date[] | DateRange;\n\nfunction getIsDateRange(value: DateSelection): value is DateRange {\n  return value && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction parseAsDate(timestamp: number | string | undefined): Date | undefined {\n  if (!timestamp) return undefined;\n  const numericTimestamp =\n    typeof timestamp === \"string\" ? Number(timestamp) : timestamp;\n  const date = new Date(numericTimestamp);\n  return !Number.isNaN(date.getTime()) ? date : undefined;\n}\n\nfunction parseColumnFilterValue(value: unknown) {\n  if (value === null || value === undefined) {\n    return [];\n  }\n\n  if (Array.isArray(value)) {\n    return value.map((item) => {\n      if (typeof item === \"number\" || typeof item === \"string\") {\n        return item;\n      }\n      return undefined;\n    });\n  }\n\n  if (typeof value === \"string\" || typeof value === \"number\") {\n    return [value];\n  }\n\n  return [];\n}\n\ninterface DataTableDateFilterProps<TData> {\n  column: Column<TData, unknown>;\n  title?: string;\n  multiple?: boolean;\n}\n\nexport function DataTableDateFilter<TData>({\n  column,\n  title,\n  multiple,\n}: DataTableDateFilterProps<TData>) {\n  const columnFilterValue = column.getFilterValue();\n\n  const selectedDates = React.useMemo<DateSelection>(() => {\n    if (!columnFilterValue) {\n      return multiple ? { from: undefined, to: undefined } : [];\n    }\n\n    if (multiple) {\n      const timestamps = parseColumnFilterValue(columnFilterValue);\n      return {\n        from: parseAsDate(timestamps[0]),\n        to: parseAsDate(timestamps[1]),\n      };\n    }\n\n    const timestamps = parseColumnFilterValue(columnFilterValue);\n    const date = parseAsDate(timestamps[0]);\n    return date ? [date] : [];\n  }, [columnFilterValue, multiple]);\n\n  const onSelect = React.useCallback(\n    (date: Date | DateRange | undefined) => {\n      if (!date) {\n        column.setFilterValue(undefined);\n        return;\n      }\n\n      if (multiple && !(\"getTime\" in date)) {\n        const from = date.from?.getTime();\n        const to = date.to?.getTime();\n        column.setFilterValue(from || to ? [from, to] : undefined);\n      } else if (!multiple && \"getTime\" in date) {\n        column.setFilterValue(date.getTime());\n      }\n    },\n    [column, multiple],\n  );\n\n  const onReset = React.useCallback(\n    (event: React.MouseEvent) => {\n      event.stopPropagation();\n      column.setFilterValue(undefined);\n    },\n    [column],\n  );\n\n  const hasValue = React.useMemo(() => {\n    if (multiple) {\n      if (!getIsDateRange(selectedDates)) return false;\n      return selectedDates.from || selectedDates.to;\n    }\n    if (!Array.isArray(selectedDates)) return false;\n    return selectedDates.length > 0;\n  }, [multiple, selectedDates]);\n\n  const formatDateRange = React.useCallback((range: DateRange) => {\n    if (!range.from && !range.to) return \"\";\n    if (range.from && range.to) {\n      return `${formatDate(range.from)} - ${formatDate(range.to)}`;\n    }\n    return formatDate(range.from ?? range.to);\n  }, []);\n\n  const label = React.useMemo(() => {\n    if (multiple) {\n      if (!getIsDateRange(selectedDates)) return null;\n\n      const hasSelectedDates = selectedDates.from || selectedDates.to;\n      const dateText = hasSelectedDates\n        ? formatDateRange(selectedDates)\n        : \"Select date range\";\n\n      return (\n        <span className=\"flex items-center gap-2\">\n          <span>{title}</span>\n          {hasSelectedDates && (\n            <>\n              <Separator\n                orientation=\"vertical\"\n                className=\"mx-0.5 data-[orientation=vertical]:h-4\"\n              />\n              <span>{dateText}</span>\n            </>\n          )}\n        </span>\n      );\n    }\n\n    if (getIsDateRange(selectedDates)) return null;\n\n    const hasSelectedDate = selectedDates.length > 0;\n    const dateText = hasSelectedDate\n      ? formatDate(selectedDates[0])\n      : \"Select date\";\n\n    return (\n      <span className=\"flex items-center gap-2\">\n        <span>{title}</span>\n        {hasSelectedDate && (\n          <>\n            <Separator\n              orientation=\"vertical\"\n              className=\"mx-0.5 data-[orientation=vertical]:h-4\"\n            />\n            <span>{dateText}</span>\n          </>\n        )}\n      </span>\n    );\n  }, [selectedDates, multiple, formatDateRange, title]);\n\n  return (\n    <Popover>\n      <PopoverTrigger asChild>\n        <Button variant=\"outline\" className=\"border-dashed font-normal\">\n          {hasValue ? (\n            <div\n              role=\"button\"\n              aria-label={`Clear ${title} filter`}\n              tabIndex={0}\n              onClick={onReset}\n              className=\"rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\"\n            >\n              <XCircle />\n            </div>\n          ) : (\n            <CalendarIcon />\n          )}\n          {label}\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-auto p-0\" align=\"start\">\n        {multiple ? (\n          <Calendar\n            autoFocus\n            captionLayout=\"dropdown\"\n            mode=\"range\"\n            selected={\n              getIsDateRange(selectedDates)\n                ? selectedDates\n                : { from: undefined, to: undefined }\n            }\n            onSelect={onSelect}\n          />\n        ) : (\n          <Calendar\n            captionLayout=\"dropdown\"\n            mode=\"single\"\n            selected={\n              !getIsDateRange(selectedDates) ? selectedDates[0] : undefined\n            }\n            onSelect={onSelect}\n          />\n        )}\n      </PopoverContent>\n    </Popover>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table-date-filter.tsx"
    },
    {
      "path": "src/components/data-table/data-table-skeleton.tsx",
      "content": "import { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\nimport { cn } from \"@/lib/utils\";\n\ninterface DataTableSkeletonProps extends React.ComponentProps<\"div\"> {\n  columnCount: number;\n  rowCount?: number;\n  filterCount?: number;\n  cellWidths?: string[];\n  withViewOptions?: boolean;\n  withPagination?: boolean;\n  shrinkZero?: boolean;\n}\n\nexport function DataTableSkeleton({\n  columnCount,\n  rowCount = 10,\n  filterCount = 0,\n  cellWidths = [\"auto\"],\n  withViewOptions = true,\n  withPagination = true,\n  shrinkZero = false,\n  className,\n  ...props\n}: DataTableSkeletonProps) {\n  const cozyCellWidths = Array.from(\n    { length: columnCount },\n    (_, index) => cellWidths[index % cellWidths.length] ?? \"auto\",\n  );\n\n  return (\n    <div\n      className={cn(\"flex w-full flex-col gap-2.5 overflow-auto\", className)}\n      {...props}\n    >\n      <div className=\"flex w-full items-center justify-between gap-2 overflow-auto p-1\">\n        <div className=\"flex flex-1 items-center gap-2\">\n          {filterCount > 0\n            ? Array.from({ length: filterCount }).map((_, i) => (\n                <Skeleton key={i} className=\"h-7 w-18 border-dashed\" />\n              ))\n            : null}\n        </div>\n        {withViewOptions ? (\n          <Skeleton className=\"ml-auto hidden h-7 w-18 lg:flex\" />\n        ) : null}\n      </div>\n      <div className=\"rounded-md border\">\n        <Table>\n          <TableHeader>\n            {Array.from({ length: 1 }).map((_, i) => (\n              <TableRow key={i} className=\"hover:bg-transparent\">\n                {Array.from({ length: columnCount }).map((_, j) => (\n                  <TableHead\n                    key={j}\n                    style={{\n                      width: cozyCellWidths[j],\n                      minWidth: shrinkZero ? cozyCellWidths[j] : \"auto\",\n                    }}\n                  >\n                    <Skeleton className=\"h-6 w-full\" />\n                  </TableHead>\n                ))}\n              </TableRow>\n            ))}\n          </TableHeader>\n          <TableBody>\n            {Array.from({ length: rowCount }).map((_, i) => (\n              <TableRow key={i} className=\"hover:bg-transparent\">\n                {Array.from({ length: columnCount }).map((_, j) => (\n                  <TableCell\n                    key={j}\n                    style={{\n                      width: cozyCellWidths[j],\n                      minWidth: shrinkZero ? cozyCellWidths[j] : \"auto\",\n                    }}\n                  >\n                    <Skeleton className=\"h-6 w-full\" />\n                  </TableCell>\n                ))}\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n      </div>\n      {withPagination ? (\n        <div className=\"flex w-full items-center justify-between gap-4 overflow-auto p-1 sm:gap-8\">\n          <Skeleton className=\"h-7 w-40 shrink-0\" />\n          <div className=\"flex items-center gap-4 sm:gap-6 lg:gap-8\">\n            <div className=\"flex items-center gap-2\">\n              <Skeleton className=\"h-7 w-24\" />\n              <Skeleton className=\"h-7 w-18\" />\n            </div>\n            <div className=\"flex items-center justify-center font-medium text-sm\">\n              <Skeleton className=\"h-7 w-20\" />\n            </div>\n            <div className=\"flex items-center gap-2\">\n              <Skeleton className=\"hidden size-7 lg:block\" />\n              <Skeleton className=\"size-7\" />\n              <Skeleton className=\"size-7\" />\n              <Skeleton className=\"hidden size-7 lg:block\" />\n            </div>\n          </div>\n        </div>\n      ) : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table-skeleton.tsx"
    },
    {
      "path": "src/hooks/use-callback-ref.ts",
      "content": "import * as React from \"react\";\n\n/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx\n */\n\n/**\n * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a\n * prop or avoid re-executing effects when passed as a dependency\n */\nfunction useCallbackRef<T extends (...args: never[]) => unknown>(\n  callback: T | undefined,\n): T {\n  const callbackRef = React.useRef(callback);\n\n  React.useEffect(() => {\n    callbackRef.current = callback;\n  });\n\n  // https://github.com/facebook/react/issues/19240\n  return React.useMemo(\n    () => ((...args) => callbackRef.current?.(...args)) as T,\n    [],\n  );\n}\n\nexport { useCallbackRef };\n",
      "type": "registry:hook"
    },
    {
      "path": "src/hooks/use-data-table.ts",
      "content": "import {\n  type ColumnFiltersState,\n  getCoreRowModel,\n  getFacetedMinMaxValues,\n  getFacetedRowModel,\n  getFacetedUniqueValues,\n  getFilteredRowModel,\n  getPaginationRowModel,\n  getSortedRowModel,\n  type PaginationState,\n  type RowSelectionState,\n  type SortingState,\n  type TableOptions,\n  type TableState,\n  type Updater,\n  useReactTable,\n  type VisibilityState,\n} from \"@tanstack/react-table\";\nimport {\n  parseAsArrayOf,\n  parseAsInteger,\n  parseAsString,\n  type SingleParser,\n  type UseQueryStateOptions,\n  useQueryState,\n  useQueryStates,\n} from \"nuqs\";\nimport * as React from \"react\";\n\nimport { useDebouncedCallback } from \"@/hooks/use-debounced-callback\";\nimport { getSortingStateParser } from \"@/lib/parsers\";\nimport type { ExtendedColumnSort, QueryKeys } from \"@/types/data-table\";\n\nconst PAGE_KEY = \"page\";\nconst PER_PAGE_KEY = \"perPage\";\nconst SORT_KEY = \"sort\";\nconst FILTERS_KEY = \"filters\";\nconst JOIN_OPERATOR_KEY = \"joinOperator\";\nconst ARRAY_SEPARATOR = \",\";\nconst DEBOUNCE_MS = 300;\nconst THROTTLE_MS = 50;\n\ninterface UseDataTableProps<TData>\n  extends Omit<\n      TableOptions<TData>,\n      | \"state\"\n      | \"pageCount\"\n      | \"getCoreRowModel\"\n      | \"manualFiltering\"\n      | \"manualPagination\"\n      | \"manualSorting\"\n    >,\n    Required<Pick<TableOptions<TData>, \"pageCount\">> {\n  initialState?: Omit<Partial<TableState>, \"sorting\"> & {\n    sorting?: ExtendedColumnSort<TData>[];\n  };\n  queryKeys?: Partial<QueryKeys>;\n  history?: \"push\" | \"replace\";\n  debounceMs?: number;\n  throttleMs?: number;\n  clearOnDefault?: boolean;\n  enableAdvancedFilter?: boolean;\n  scroll?: boolean;\n  shallow?: boolean;\n  startTransition?: React.TransitionStartFunction;\n}\n\nexport function useDataTable<TData>(props: UseDataTableProps<TData>) {\n  const {\n    columns,\n    pageCount = -1,\n    initialState,\n    queryKeys,\n    history = \"replace\",\n    debounceMs = DEBOUNCE_MS,\n    throttleMs = THROTTLE_MS,\n    clearOnDefault = false,\n    enableAdvancedFilter = false,\n    scroll = false,\n    shallow = true,\n    startTransition,\n    ...tableProps\n  } = props;\n  const pageKey = queryKeys?.page ?? PAGE_KEY;\n  const perPageKey = queryKeys?.perPage ?? PER_PAGE_KEY;\n  const sortKey = queryKeys?.sort ?? SORT_KEY;\n  const filtersKey = queryKeys?.filters ?? FILTERS_KEY;\n  const joinOperatorKey = queryKeys?.joinOperator ?? JOIN_OPERATOR_KEY;\n\n  const queryStateOptions = React.useMemo<\n    Omit<UseQueryStateOptions<string>, \"parse\">\n  >(\n    () => ({\n      history,\n      scroll,\n      shallow,\n      throttleMs,\n      debounceMs,\n      clearOnDefault,\n      startTransition,\n    }),\n    [\n      history,\n      scroll,\n      shallow,\n      throttleMs,\n      debounceMs,\n      clearOnDefault,\n      startTransition,\n    ],\n  );\n\n  const [rowSelection, setRowSelection] = React.useState<RowSelectionState>(\n    initialState?.rowSelection ?? {},\n  );\n  const [columnVisibility, setColumnVisibility] =\n    React.useState<VisibilityState>(initialState?.columnVisibility ?? {});\n\n  const [page, setPage] = useQueryState(\n    pageKey,\n    parseAsInteger.withOptions(queryStateOptions).withDefault(1),\n  );\n  const [perPage, setPerPage] = useQueryState(\n    perPageKey,\n    parseAsInteger\n      .withOptions(queryStateOptions)\n      .withDefault(initialState?.pagination?.pageSize ?? 10),\n  );\n\n  const pagination: PaginationState = React.useMemo(() => {\n    return {\n      pageIndex: page - 1, // zero-based index -> one-based index\n      pageSize: perPage,\n    };\n  }, [page, perPage]);\n\n  const onPaginationChange = React.useCallback(\n    (updaterOrValue: Updater<PaginationState>) => {\n      if (typeof updaterOrValue === \"function\") {\n        const newPagination = updaterOrValue(pagination);\n        void setPage(newPagination.pageIndex + 1);\n        void setPerPage(newPagination.pageSize);\n      } else {\n        void setPage(updaterOrValue.pageIndex + 1);\n        void setPerPage(updaterOrValue.pageSize);\n      }\n    },\n    [pagination, setPage, setPerPage],\n  );\n\n  const columnIds = React.useMemo(() => {\n    return new Set(\n      columns.map((column) => column.id).filter(Boolean) as string[],\n    );\n  }, [columns]);\n\n  const [sorting, setSorting] = useQueryState(\n    sortKey,\n    getSortingStateParser<TData>(columnIds)\n      .withOptions(queryStateOptions)\n      .withDefault(initialState?.sorting ?? []),\n  );\n\n  const onSortingChange = React.useCallback(\n    (updaterOrValue: Updater<SortingState>) => {\n      if (typeof updaterOrValue === \"function\") {\n        const newSorting = updaterOrValue(sorting);\n        setSorting(newSorting as ExtendedColumnSort<TData>[]);\n      } else {\n        setSorting(updaterOrValue as ExtendedColumnSort<TData>[]);\n      }\n    },\n    [sorting, setSorting],\n  );\n\n  const filterableColumns = React.useMemo(() => {\n    if (enableAdvancedFilter) return [];\n\n    return columns.filter((column) => column.enableColumnFilter);\n  }, [columns, enableAdvancedFilter]);\n\n  const filterParsers = React.useMemo(() => {\n    if (enableAdvancedFilter) return {};\n\n    return filterableColumns.reduce<\n      Record<string, SingleParser<string> | SingleParser<string[]>>\n    >((acc, column) => {\n      if (column.meta?.options) {\n        acc[column.id ?? \"\"] = parseAsArrayOf(\n          parseAsString,\n          ARRAY_SEPARATOR,\n        ).withOptions(queryStateOptions);\n      } else {\n        acc[column.id ?? \"\"] = parseAsString.withOptions(queryStateOptions);\n      }\n      return acc;\n    }, {});\n  }, [filterableColumns, queryStateOptions, enableAdvancedFilter]);\n\n  const [filterValues, setFilterValues] = useQueryStates(filterParsers);\n\n  const debouncedSetFilterValues = useDebouncedCallback(\n    (values: typeof filterValues) => {\n      void setPage(1);\n      void setFilterValues(values);\n    },\n    debounceMs,\n  );\n\n  const initialColumnFilters: ColumnFiltersState = React.useMemo(() => {\n    if (enableAdvancedFilter) return [];\n\n    return Object.entries(filterValues).reduce<ColumnFiltersState>(\n      (filters, [key, value]) => {\n        if (value !== null) {\n          const processedValue = Array.isArray(value)\n            ? value\n            : typeof value === \"string\" && /[^a-zA-Z0-9]/.test(value)\n              ? value.split(/[^a-zA-Z0-9]+/).filter(Boolean)\n              : [value];\n\n          filters.push({\n            id: key,\n            value: processedValue,\n          });\n        }\n        return filters;\n      },\n      [],\n    );\n  }, [filterValues, enableAdvancedFilter]);\n\n  const [columnFilters, setColumnFilters] =\n    React.useState<ColumnFiltersState>(initialColumnFilters);\n\n  const onColumnFiltersChange = React.useCallback(\n    (updaterOrValue: Updater<ColumnFiltersState>) => {\n      if (enableAdvancedFilter) return;\n\n      setColumnFilters((prev) => {\n        const next =\n          typeof updaterOrValue === \"function\"\n            ? updaterOrValue(prev)\n            : updaterOrValue;\n\n        const filterUpdates = next.reduce<\n          Record<string, string | string[] | null>\n        >((acc, filter) => {\n          if (filterableColumns.find((column) => column.id === filter.id)) {\n            acc[filter.id] = filter.value as string | string[];\n          }\n          return acc;\n        }, {});\n\n        for (const prevFilter of prev) {\n          if (!next.some((filter) => filter.id === prevFilter.id)) {\n            filterUpdates[prevFilter.id] = null;\n          }\n        }\n\n        debouncedSetFilterValues(filterUpdates);\n        return next;\n      });\n    },\n    [debouncedSetFilterValues, filterableColumns, enableAdvancedFilter],\n  );\n\n  const table = useReactTable({\n    ...tableProps,\n    columns,\n    initialState,\n    pageCount,\n    state: {\n      pagination,\n      sorting,\n      columnVisibility,\n      rowSelection,\n      columnFilters,\n    },\n    defaultColumn: {\n      ...tableProps.defaultColumn,\n      enableColumnFilter: false,\n    },\n    enableRowSelection: true,\n    onRowSelectionChange: setRowSelection,\n    onPaginationChange,\n    onSortingChange,\n    onColumnFiltersChange,\n    onColumnVisibilityChange: setColumnVisibility,\n    getCoreRowModel: getCoreRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFacetedRowModel: getFacetedRowModel(),\n    getFacetedUniqueValues: getFacetedUniqueValues(),\n    getFacetedMinMaxValues: getFacetedMinMaxValues(),\n    manualPagination: true,\n    manualSorting: true,\n    manualFiltering: true,\n    meta: {\n      ...tableProps.meta,\n      queryKeys: {\n        page: pageKey,\n        perPage: perPageKey,\n        sort: sortKey,\n        filters: filtersKey,\n        joinOperator: joinOperatorKey,\n      },\n    },\n  });\n\n  return React.useMemo(\n    () => ({ table, shallow, debounceMs, throttleMs }),\n    [table, shallow, debounceMs, throttleMs],\n  );\n}\n",
      "type": "registry:hook"
    },
    {
      "path": "src/hooks/use-debounced-callback.ts",
      "content": "/**\n * @see https://github.com/mantinedev/mantine/blob/master/packages/@mantine/hooks/src/use-debounced-callback/use-debounced-callback.ts\n */\n\nimport * as React from \"react\";\n\nimport { useCallbackRef } from \"@/hooks/use-callback-ref\";\n\nexport function useDebouncedCallback<T extends (...args: never[]) => unknown>(\n  callback: T,\n  delay: number,\n) {\n  const handleCallback = useCallbackRef(callback);\n  const debounceTimerRef = React.useRef(0);\n  React.useEffect(\n    () => () => window.clearTimeout(debounceTimerRef.current),\n    [],\n  );\n\n  const setValue = React.useCallback(\n    (...args: Parameters<T>) => {\n      window.clearTimeout(debounceTimerRef.current);\n      debounceTimerRef.current = window.setTimeout(\n        () => handleCallback(...args),\n        delay,\n      );\n    },\n    [handleCallback, delay],\n  );\n\n  return setValue;\n}\n",
      "type": "registry:hook"
    },
    {
      "path": "src/lib/data-table.ts",
      "content": "import type { Column } from \"@tanstack/react-table\";\nimport { dataTableConfig } from \"@/config/data-table\";\nimport type {\n  ExtendedColumnFilter,\n  FilterOperator,\n  FilterVariant,\n} from \"@/types/data-table\";\n\nexport function getColumnPinningStyle<TData>({\n  column,\n  withBorder = false,\n}: {\n  column: Column<TData>;\n  withBorder?: boolean;\n}): React.CSSProperties {\n  const isPinned = column.getIsPinned();\n  const isLastLeftPinnedColumn =\n    isPinned === \"left\" && column.getIsLastColumn(\"left\");\n  const isFirstRightPinnedColumn =\n    isPinned === \"right\" && column.getIsFirstColumn(\"right\");\n\n  return {\n    boxShadow: withBorder\n      ? isLastLeftPinnedColumn\n        ? \"-4px 0 4px -4px var(--border) inset\"\n        : isFirstRightPinnedColumn\n          ? \"4px 0 4px -4px var(--border) inset\"\n          : undefined\n      : undefined,\n    left: isPinned === \"left\" ? `${column.getStart(\"left\")}px` : undefined,\n    right: isPinned === \"right\" ? `${column.getAfter(\"right\")}px` : undefined,\n    opacity: isPinned ? 0.97 : 1,\n    position: isPinned ? \"sticky\" : \"relative\",\n    background: isPinned ? \"var(--background)\" : \"var(--background)\",\n    width: column.getSize(),\n    zIndex: isPinned ? 1 : undefined,\n  };\n}\n\nexport function getFilterOperators(filterVariant: FilterVariant) {\n  const operatorMap: Record<\n    FilterVariant,\n    { label: string; value: FilterOperator }[]\n  > = {\n    text: dataTableConfig.textOperators,\n    number: dataTableConfig.numericOperators,\n    range: dataTableConfig.numericOperators,\n    date: dataTableConfig.dateOperators,\n    dateRange: dataTableConfig.dateOperators,\n    boolean: dataTableConfig.booleanOperators,\n    select: dataTableConfig.selectOperators,\n    multiSelect: dataTableConfig.multiSelectOperators,\n  };\n\n  return operatorMap[filterVariant] ?? dataTableConfig.textOperators;\n}\n\nexport function getDefaultFilterOperator(filterVariant: FilterVariant) {\n  const operators = getFilterOperators(filterVariant);\n\n  return operators[0]?.value ?? (filterVariant === \"text\" ? \"iLike\" : \"eq\");\n}\n\nexport function getValidFilters<TData>(\n  filters: ExtendedColumnFilter<TData>[],\n): ExtendedColumnFilter<TData>[] {\n  return filters.filter(\n    (filter) =>\n      filter.operator === \"isEmpty\" ||\n      filter.operator === \"isNotEmpty\" ||\n      (Array.isArray(filter.value)\n        ? filter.value.length > 0\n        : filter.value !== \"\" &&\n          filter.value !== null &&\n          filter.value !== undefined),\n  );\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "src/lib/format.ts",
      "content": "export function formatDate(\n  date: Date | string | number | undefined,\n  opts: Intl.DateTimeFormatOptions = {},\n) {\n  if (!date) return \"\";\n\n  try {\n    return new Intl.DateTimeFormat(\"en-US\", {\n      month: opts.month ?? \"long\",\n      day: opts.day ?? \"numeric\",\n      year: opts.year ?? \"numeric\",\n      ...opts,\n    }).format(new Date(date));\n  } catch (_err) {\n    return \"\";\n  }\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "src/lib/parsers.ts",
      "content": "import { createParser } from \"nuqs/server\";\nimport { z } from \"zod\";\n\nimport { dataTableConfig } from \"@/config/data-table\";\n\nimport type {\n  ExtendedColumnFilter,\n  ExtendedColumnSort,\n} from \"@/types/data-table\";\n\nconst sortingItemSchema = z.object({\n  id: z.string(),\n  desc: z.boolean(),\n});\n\nexport const getSortingStateParser = <TData>(\n  columnIds?: string[] | Set<string>,\n) => {\n  const validKeys = columnIds\n    ? columnIds instanceof Set\n      ? columnIds\n      : new Set(columnIds)\n    : null;\n\n  return createParser({\n    parse: (value) => {\n      try {\n        const parsed = JSON.parse(value);\n        const result = z.array(sortingItemSchema).safeParse(parsed);\n\n        if (!result.success) return null;\n\n        if (validKeys && result.data.some((item) => !validKeys.has(item.id))) {\n          return null;\n        }\n\n        return result.data as ExtendedColumnSort<TData>[];\n      } catch {\n        return null;\n      }\n    },\n    serialize: (value) => JSON.stringify(value),\n    eq: (a, b) =>\n      a.length === b.length &&\n      a.every(\n        (item, index) =>\n          item.id === b[index]?.id && item.desc === b[index]?.desc,\n      ),\n  });\n};\n\nconst filterItemSchema = z.object({\n  id: z.string(),\n  value: z.union([z.string(), z.array(z.string())]),\n  variant: z.enum(dataTableConfig.filterVariants),\n  operator: z.enum(dataTableConfig.operators),\n  filterId: z.string(),\n});\n\nexport type FilterItemSchema = z.infer<typeof filterItemSchema>;\n\nexport const getFiltersStateParser = <TData>(\n  columnIds?: string[] | Set<string>,\n) => {\n  const validKeys = columnIds\n    ? columnIds instanceof Set\n      ? columnIds\n      : new Set(columnIds)\n    : null;\n\n  return createParser({\n    parse: (value) => {\n      try {\n        const parsed = JSON.parse(value);\n        const result = z.array(filterItemSchema).safeParse(parsed);\n\n        if (!result.success) return null;\n\n        if (validKeys && result.data.some((item) => !validKeys.has(item.id))) {\n          return null;\n        }\n\n        return result.data as ExtendedColumnFilter<TData>[];\n      } catch {\n        return null;\n      }\n    },\n    serialize: (value) => JSON.stringify(value),\n    eq: (a, b) =>\n      a.length === b.length &&\n      a.every(\n        (filter, index) =>\n          filter.id === b[index]?.id &&\n          filter.value === b[index]?.value &&\n          filter.variant === b[index]?.variant &&\n          filter.operator === b[index]?.operator,\n      ),\n  });\n};\n",
      "type": "registry:lib"
    },
    {
      "path": "src/config/data-table.ts",
      "content": "export type DataTableConfig = typeof dataTableConfig;\n\nexport const dataTableConfig = {\n  textOperators: [\n    { label: \"Contains\", value: \"iLike\" as const },\n    { label: \"Does not contain\", value: \"notILike\" as const },\n    { label: \"Is\", value: \"eq\" as const },\n    { label: \"Is not\", value: \"ne\" as const },\n    { label: \"Is empty\", value: \"isEmpty\" as const },\n    { label: \"Is not empty\", value: \"isNotEmpty\" as const },\n  ],\n  numericOperators: [\n    { label: \"Is\", value: \"eq\" as const },\n    { label: \"Is not\", value: \"ne\" as const },\n    { label: \"Is less than\", value: \"lt\" as const },\n    { label: \"Is less than or equal to\", value: \"lte\" as const },\n    { label: \"Is greater than\", value: \"gt\" as const },\n    { label: \"Is greater than or equal to\", value: \"gte\" as const },\n    { label: \"Is between\", value: \"isBetween\" as const },\n    { label: \"Is empty\", value: \"isEmpty\" as const },\n    { label: \"Is not empty\", value: \"isNotEmpty\" as const },\n  ],\n  dateOperators: [\n    { label: \"Is\", value: \"eq\" as const },\n    { label: \"Is not\", value: \"ne\" as const },\n    { label: \"Is before\", value: \"lt\" as const },\n    { label: \"Is after\", value: \"gt\" as const },\n    { label: \"Is on or before\", value: \"lte\" as const },\n    { label: \"Is on or after\", value: \"gte\" as const },\n    { label: \"Is between\", value: \"isBetween\" as const },\n    { label: \"Is relative to today\", value: \"isRelativeToToday\" as const },\n    { label: \"Is empty\", value: \"isEmpty\" as const },\n    { label: \"Is not empty\", value: \"isNotEmpty\" as const },\n  ],\n  selectOperators: [\n    { label: \"Is\", value: \"eq\" as const },\n    { label: \"Is not\", value: \"ne\" as const },\n    { label: \"Is empty\", value: \"isEmpty\" as const },\n    { label: \"Is not empty\", value: \"isNotEmpty\" as const },\n  ],\n  multiSelectOperators: [\n    { label: \"Has any of\", value: \"inArray\" as const },\n    { label: \"Has none of\", value: \"notInArray\" as const },\n    { label: \"Is empty\", value: \"isEmpty\" as const },\n    { label: \"Is not empty\", value: \"isNotEmpty\" as const },\n  ],\n  booleanOperators: [\n    { label: \"Is\", value: \"eq\" as const },\n    { label: \"Is not\", value: \"ne\" as const },\n  ],\n  sortOrders: [\n    { label: \"Asc\", value: \"asc\" as const },\n    { label: \"Desc\", value: \"desc\" as const },\n  ],\n  filterVariants: [\n    \"text\",\n    \"number\",\n    \"range\",\n    \"date\",\n    \"dateRange\",\n    \"boolean\",\n    \"select\",\n    \"multiSelect\",\n  ] as const,\n  operators: [\n    \"iLike\",\n    \"notILike\",\n    \"eq\",\n    \"ne\",\n    \"inArray\",\n    \"notInArray\",\n    \"isEmpty\",\n    \"isNotEmpty\",\n    \"lt\",\n    \"lte\",\n    \"gt\",\n    \"gte\",\n    \"isBetween\",\n    \"isRelativeToToday\",\n  ] as const,\n  joinOperators: [\"and\", \"or\"] as const,\n};\n",
      "type": "registry:file",
      "target": "src/config/data-table.ts"
    },
    {
      "path": "src/types/data-table.ts",
      "content": "import type { ColumnSort, Row, RowData } from \"@tanstack/react-table\";\nimport type { DataTableConfig } from \"@/config/data-table\";\nimport type { FilterItemSchema } from \"@/lib/parsers\";\n\ndeclare module \"@tanstack/react-table\" {\n  interface TableMeta<TData extends RowData> {\n    queryKeys?: QueryKeys;\n  }\n\n  interface ColumnMeta<TData extends RowData, TValue> {\n    label?: string;\n    placeholder?: string;\n    variant?: FilterVariant;\n    options?: Option[];\n    range?: [number, number];\n    unit?: string;\n    icon?: React.ComponentType<React.ComponentProps<\"svg\">>;\n  }\n}\n\nexport interface QueryKeys {\n  page: string;\n  perPage: string;\n  sort: string;\n  filters: string;\n  joinOperator: string;\n}\n\nexport interface Option {\n  label: string;\n  value: string;\n  count?: number;\n  icon?: React.ComponentType<React.ComponentProps<\"svg\">>;\n}\n\nexport type FilterOperator = DataTableConfig[\"operators\"][number];\nexport type FilterVariant = DataTableConfig[\"filterVariants\"][number];\nexport type JoinOperator = DataTableConfig[\"joinOperators\"][number];\n\nexport interface ExtendedColumnSort<TData> extends Omit<ColumnSort, \"id\"> {\n  id: Extract<keyof TData, string>;\n}\n\nexport interface ExtendedColumnFilter<TData> extends FilterItemSchema {\n  id: Extract<keyof TData, string>;\n}\n\nexport interface DataTableRowAction<TData> {\n  row: Row<TData>;\n  variant: \"update\" | \"delete\";\n}\n",
      "type": "registry:file",
      "target": "src/types/data-table.ts"
    }
  ],
  "type": "registry:component"
}