{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table-filter-list",
  "title": "Data Table Filter List",
  "description": "A filter list component for the data table",
  "dependencies": [
    "@tanstack/react-table",
    "lucide-react",
    "nuqs"
  ],
  "registryDependencies": [
    "badge",
    "button",
    "calendar",
    "command",
    "input",
    "popover",
    "select"
  ],
  "files": [
    {
      "path": "src/components/data-table/data-table-filter-list.tsx",
      "content": "\"use client\";\n\nimport type { Column, ColumnMeta, Table } from \"@tanstack/react-table\";\nimport {\n  CalendarIcon,\n  Check,\n  ChevronsUpDown,\n  GripVertical,\n  ListFilter,\n  Trash2,\n} from \"lucide-react\";\nimport { parseAsStringEnum, useQueryState } from \"nuqs\";\nimport * as React from \"react\";\n\nimport { DataTableRangeFilter } from \"@/components/data-table/data-table-range-filter\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Calendar } from \"@/components/ui/calendar\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport {\n  Faceted,\n  FacetedBadgeList,\n  FacetedContent,\n  FacetedEmpty,\n  FacetedGroup,\n  FacetedInput,\n  FacetedItem,\n  FacetedList,\n  FacetedTrigger,\n} from \"@/components/ui/faceted\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport {\n  Sortable,\n  SortableContent,\n  SortableItem,\n  SortableItemHandle,\n  SortableOverlay,\n} from \"@/components/ui/sortable\";\nimport { dataTableConfig } from \"@/config/data-table\";\nimport { useDebouncedCallback } from \"@/hooks/use-debounced-callback\";\nimport { getDefaultFilterOperator, getFilterOperators } from \"@/lib/data-table\";\nimport { formatDate } from \"@/lib/format\";\nimport { generateId } from \"@/lib/id\";\nimport { getFiltersStateParser } from \"@/lib/parsers\";\nimport { cn } from \"@/lib/utils\";\nimport type {\n  ExtendedColumnFilter,\n  FilterOperator,\n  JoinOperator,\n} from \"@/types/data-table\";\n\nconst DEBOUNCE_MS = 300;\nconst THROTTLE_MS = 50;\nconst FILTER_SHORTCUT_KEY = \"f\";\nconst REMOVE_FILTER_SHORTCUTS = [\"backspace\", \"delete\"];\n\ninterface DataTableFilterListProps<TData>\n  extends React.ComponentProps<typeof PopoverContent> {\n  table: Table<TData>;\n  debounceMs?: number;\n  throttleMs?: number;\n  shallow?: boolean;\n  disabled?: boolean;\n}\n\nexport function DataTableFilterList<TData>({\n  table,\n  debounceMs = DEBOUNCE_MS,\n  throttleMs = THROTTLE_MS,\n  shallow = true,\n  disabled,\n  ...props\n}: DataTableFilterListProps<TData>) {\n  const id = React.useId();\n  const labelId = React.useId();\n  const descriptionId = React.useId();\n  const [open, setOpen] = React.useState(false);\n  const addButtonRef = React.useRef<HTMLButtonElement>(null);\n\n  const columns = React.useMemo(() => {\n    return table\n      .getAllColumns()\n      .filter((column) => column.columnDef.enableColumnFilter);\n  }, [table]);\n\n  const [filters, setFilters] = useQueryState(\n    table.options.meta?.queryKeys?.filters ?? \"filters\",\n    getFiltersStateParser<TData>(columns.map((field) => field.id))\n      .withDefault([])\n      .withOptions({\n        clearOnDefault: true,\n        shallow,\n        throttleMs,\n      }),\n  );\n  const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);\n\n  const [joinOperator, setJoinOperator] = useQueryState(\n    table.options.meta?.queryKeys?.joinOperator ?? \"\",\n    parseAsStringEnum([\"and\", \"or\"]).withDefault(\"and\").withOptions({\n      clearOnDefault: true,\n      shallow,\n    }),\n  );\n\n  const onFilterAdd = React.useCallback(() => {\n    const column = columns[0];\n\n    if (!column) return;\n\n    debouncedSetFilters([\n      ...filters,\n      {\n        id: column.id as Extract<keyof TData, string>,\n        value: \"\",\n        variant: column.columnDef.meta?.variant ?? \"text\",\n        operator: getDefaultFilterOperator(\n          column.columnDef.meta?.variant ?? \"text\",\n        ),\n        filterId: generateId({ length: 8 }),\n      },\n    ]);\n  }, [columns, filters, debouncedSetFilters]);\n\n  const onFilterUpdate = React.useCallback(\n    (\n      filterId: string,\n      updates: Partial<Omit<ExtendedColumnFilter<TData>, \"filterId\">>,\n    ) => {\n      debouncedSetFilters((prevFilters) => {\n        const updatedFilters = prevFilters.map((filter) => {\n          if (filter.filterId === filterId) {\n            return { ...filter, ...updates } as ExtendedColumnFilter<TData>;\n          }\n          return filter;\n        });\n        return updatedFilters;\n      });\n    },\n    [debouncedSetFilters],\n  );\n\n  const onFilterRemove = React.useCallback(\n    (filterId: string) => {\n      const updatedFilters = filters.filter(\n        (filter) => filter.filterId !== filterId,\n      );\n      void setFilters(updatedFilters);\n      requestAnimationFrame(() => {\n        addButtonRef.current?.focus();\n      });\n    },\n    [filters, setFilters],\n  );\n\n  const onFiltersReset = React.useCallback(() => {\n    void setFilters(null);\n    void setJoinOperator(\"and\");\n  }, [setFilters, setJoinOperator]);\n\n  React.useEffect(() => {\n    function onKeyDown(event: KeyboardEvent) {\n      if (\n        event.target instanceof HTMLInputElement ||\n        event.target instanceof HTMLTextAreaElement ||\n        (event.target instanceof HTMLElement &&\n          event.target.contentEditable === \"true\")\n      ) {\n        return;\n      }\n\n      if (\n        event.key.toLowerCase() === FILTER_SHORTCUT_KEY &&\n        (event.ctrlKey || event.metaKey) &&\n        event.shiftKey\n      ) {\n        event.preventDefault();\n        setOpen((prev) => !prev);\n      }\n    }\n\n    window.addEventListener(\"keydown\", onKeyDown);\n    return () => window.removeEventListener(\"keydown\", onKeyDown);\n  }, []);\n\n  const onTriggerKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLButtonElement>) => {\n      if (\n        REMOVE_FILTER_SHORTCUTS.includes(event.key.toLowerCase()) &&\n        filters.length > 0\n      ) {\n        event.preventDefault();\n        onFilterRemove(filters[filters.length - 1]?.filterId ?? \"\");\n      }\n    },\n    [filters, onFilterRemove],\n  );\n\n  return (\n    <Sortable\n      value={filters}\n      onValueChange={setFilters}\n      getItemValue={(item) => item.filterId}\n    >\n      <Popover open={open} onOpenChange={setOpen}>\n        <PopoverTrigger asChild>\n          <Button\n            variant=\"outline\"\n            className=\"font-normal\"\n            onKeyDown={onTriggerKeyDown}\n            disabled={disabled}\n          >\n            <ListFilter className=\"text-muted-foreground\" />\n            Filter\n            {filters.length > 0 && (\n              <Badge\n                variant=\"secondary\"\n                className=\"h-[18.24px] rounded-[3.2px] px-[5.12px] font-mono font-normal text-[10.4px]\"\n              >\n                {filters.length}\n              </Badge>\n            )}\n          </Button>\n        </PopoverTrigger>\n        <PopoverContent\n          aria-describedby={descriptionId}\n          aria-labelledby={labelId}\n          className=\"flex w-full max-w-(--radix-popover-content-available-width) flex-col gap-3.5 p-4 sm:min-w-[380px]\"\n          {...props}\n        >\n          <div className=\"flex flex-col gap-1\">\n            <h4 id={labelId} className=\"font-medium leading-none\">\n              {filters.length > 0 ? \"Filters\" : \"No filters applied\"}\n            </h4>\n            <p\n              id={descriptionId}\n              className={cn(\n                \"text-muted-foreground text-sm\",\n                filters.length > 0 && \"sr-only\",\n              )}\n            >\n              {filters.length > 0\n                ? \"Modify filters to refine your rows.\"\n                : \"Add filters to refine your rows.\"}\n            </p>\n          </div>\n          {filters.length > 0 ? (\n            <SortableContent asChild>\n              <div\n                role=\"list\"\n                className=\"flex max-h-[300px] flex-col gap-2 overflow-y-auto p-1\"\n              >\n                {filters.map((filter, index) => (\n                  <DataTableFilterItem<TData>\n                    key={filter.filterId}\n                    filter={filter}\n                    index={index}\n                    filterItemId={`${id}-filter-${filter.filterId}`}\n                    joinOperator={joinOperator}\n                    setJoinOperator={setJoinOperator}\n                    columns={columns}\n                    onFilterUpdate={onFilterUpdate}\n                    onFilterRemove={onFilterRemove}\n                  />\n                ))}\n              </div>\n            </SortableContent>\n          ) : null}\n          <div className=\"flex w-full items-center gap-2\">\n            <Button\n              className=\"rounded\"\n              ref={addButtonRef}\n              onClick={onFilterAdd}\n            >\n              Add filter\n            </Button>\n            {filters.length > 0 ? (\n              <Button\n                variant=\"outline\"\n                className=\"rounded\"\n                onClick={onFiltersReset}\n              >\n                Reset filters\n              </Button>\n            ) : null}\n          </div>\n        </PopoverContent>\n      </Popover>\n      <SortableOverlay>\n        <div className=\"flex items-center gap-2\">\n          <div className=\"h-8 min-w-[72px] rounded-sm bg-primary/10\" />\n          <div className=\"h-8 w-32 rounded-sm bg-primary/10\" />\n          <div className=\"h-8 w-32 rounded-sm bg-primary/10\" />\n          <div className=\"h-8 min-w-36 flex-1 rounded-sm bg-primary/10\" />\n          <div className=\"size-8 shrink-0 rounded-sm bg-primary/10\" />\n          <div className=\"size-8 shrink-0 rounded-sm bg-primary/10\" />\n        </div>\n      </SortableOverlay>\n    </Sortable>\n  );\n}\n\ninterface DataTableFilterItemProps<TData> {\n  filter: ExtendedColumnFilter<TData>;\n  index: number;\n  filterItemId: string;\n  joinOperator: JoinOperator;\n  setJoinOperator: (value: JoinOperator) => void;\n  columns: Column<TData>[];\n  onFilterUpdate: (\n    filterId: string,\n    updates: Partial<Omit<ExtendedColumnFilter<TData>, \"filterId\">>,\n  ) => void;\n  onFilterRemove: (filterId: string) => void;\n}\n\nfunction DataTableFilterItem<TData>({\n  filter,\n  index,\n  filterItemId,\n  joinOperator,\n  setJoinOperator,\n  columns,\n  onFilterUpdate,\n  onFilterRemove,\n}: DataTableFilterItemProps<TData>) {\n  const [showFieldSelector, setShowFieldSelector] = React.useState(false);\n  const [showOperatorSelector, setShowOperatorSelector] = React.useState(false);\n  const [showValueSelector, setShowValueSelector] = React.useState(false);\n\n  const column = columns.find((column) => column.id === filter.id);\n\n  const joinOperatorListboxId = `${filterItemId}-join-operator-listbox`;\n  const fieldListboxId = `${filterItemId}-field-listbox`;\n  const operatorListboxId = `${filterItemId}-operator-listbox`;\n  const inputId = `${filterItemId}-input`;\n\n  const columnMeta = column?.columnDef.meta;\n  const filterOperators = getFilterOperators(filter.variant);\n\n  const onItemKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (\n        event.target instanceof HTMLInputElement ||\n        event.target instanceof HTMLTextAreaElement\n      ) {\n        return;\n      }\n\n      if (showFieldSelector || showOperatorSelector || showValueSelector) {\n        return;\n      }\n\n      if (REMOVE_FILTER_SHORTCUTS.includes(event.key.toLowerCase())) {\n        event.preventDefault();\n        onFilterRemove(filter.filterId);\n      }\n    },\n    [\n      filter.filterId,\n      showFieldSelector,\n      showOperatorSelector,\n      showValueSelector,\n      onFilterRemove,\n    ],\n  );\n\n  if (!column) return null;\n\n  return (\n    <SortableItem value={filter.filterId} asChild>\n      <div\n        role=\"listitem\"\n        id={filterItemId}\n        tabIndex={-1}\n        className=\"flex items-center gap-2\"\n        onKeyDown={onItemKeyDown}\n      >\n        <div className=\"min-w-[72px] text-center\">\n          {index === 0 ? (\n            <span className=\"text-muted-foreground text-sm\">Where</span>\n          ) : index === 1 ? (\n            <Select\n              value={joinOperator}\n              onValueChange={(value: JoinOperator) => setJoinOperator(value)}\n            >\n              <SelectTrigger\n                aria-label=\"Select join operator\"\n                aria-controls={joinOperatorListboxId}\n                className=\"rounded lowercase\"\n              >\n                <SelectValue placeholder={joinOperator} />\n              </SelectTrigger>\n              <SelectContent\n                id={joinOperatorListboxId}\n                position=\"popper\"\n                className=\"min-w-(--radix-select-trigger-width) lowercase\"\n              >\n                <SelectGroup>\n                  {dataTableConfig.joinOperators.map((joinOperator) => (\n                    <SelectItem key={joinOperator} value={joinOperator}>\n                      {joinOperator}\n                    </SelectItem>\n                  ))}\n                </SelectGroup>\n              </SelectContent>\n            </Select>\n          ) : (\n            <span className=\"text-muted-foreground text-sm\">\n              {joinOperator}\n            </span>\n          )}\n        </div>\n        <Popover open={showFieldSelector} onOpenChange={setShowFieldSelector}>\n          <PopoverTrigger asChild>\n            <Button\n              aria-controls={fieldListboxId}\n              variant=\"outline\"\n              className=\"w-32 justify-between rounded font-normal\"\n            >\n              <span className=\"truncate\">\n                {columns.find((column) => column.id === filter.id)?.columnDef\n                  .meta?.label ?? \"Select field\"}\n              </span>\n              <ChevronsUpDown className=\"opacity-50\" />\n            </Button>\n          </PopoverTrigger>\n          <PopoverContent\n            id={fieldListboxId}\n            align=\"start\"\n            className=\"w-40 p-0\"\n          >\n            <Command>\n              <CommandInput placeholder=\"Search fields...\" />\n              <CommandList>\n                <CommandEmpty>No fields found.</CommandEmpty>\n                <CommandGroup>\n                  {columns.map((column) => (\n                    <CommandItem\n                      key={column.id}\n                      value={column.id}\n                      onSelect={(value) => {\n                        onFilterUpdate(filter.filterId, {\n                          id: value as Extract<keyof TData, string>,\n                          variant: column.columnDef.meta?.variant ?? \"text\",\n                          operator: getDefaultFilterOperator(\n                            column.columnDef.meta?.variant ?? \"text\",\n                          ),\n                          value: \"\",\n                        });\n\n                        setShowFieldSelector(false);\n                      }}\n                    >\n                      <span className=\"truncate\">\n                        {column.columnDef.meta?.label}\n                      </span>\n                      <Check\n                        className={cn(\n                          \"ml-auto\",\n                          column.id === filter.id ? \"opacity-100\" : \"opacity-0\",\n                        )}\n                      />\n                    </CommandItem>\n                  ))}\n                </CommandGroup>\n              </CommandList>\n            </Command>\n          </PopoverContent>\n        </Popover>\n        <Select\n          open={showOperatorSelector}\n          onOpenChange={setShowOperatorSelector}\n          value={filter.operator}\n          onValueChange={(value: FilterOperator) =>\n            onFilterUpdate(filter.filterId, {\n              operator: value,\n              value:\n                value === \"isEmpty\" || value === \"isNotEmpty\"\n                  ? \"\"\n                  : filter.value,\n            })\n          }\n        >\n          <SelectTrigger\n            aria-controls={operatorListboxId}\n            className=\"w-32 rounded lowercase\"\n          >\n            <div className=\"truncate\">\n              <SelectValue placeholder={filter.operator} />\n            </div>\n          </SelectTrigger>\n          <SelectContent id={operatorListboxId}>\n            <SelectGroup>\n              {filterOperators.map((operator) => (\n                <SelectItem\n                  key={operator.value}\n                  value={operator.value}\n                  className=\"lowercase\"\n                >\n                  {operator.label}\n                </SelectItem>\n              ))}\n            </SelectGroup>\n          </SelectContent>\n        </Select>\n        <div className=\"min-w-36 max-w-60 flex-1\">\n          {onFilterInputRender({\n            filter,\n            inputId,\n            column,\n            columnMeta,\n            onFilterUpdate,\n            showValueSelector,\n            setShowValueSelector,\n          })}\n        </div>\n        <Button\n          aria-controls={filterItemId}\n          variant=\"outline\"\n          size=\"icon\"\n          className=\"size-8 rounded\"\n          onClick={() => onFilterRemove(filter.filterId)}\n        >\n          <Trash2 />\n        </Button>\n        <SortableItemHandle asChild>\n          <Button variant=\"outline\" size=\"icon\" className=\"size-8 rounded\">\n            <GripVertical />\n          </Button>\n        </SortableItemHandle>\n      </div>\n    </SortableItem>\n  );\n}\n\nfunction onFilterInputRender<TData>({\n  filter,\n  inputId,\n  column,\n  columnMeta,\n  onFilterUpdate,\n  showValueSelector,\n  setShowValueSelector,\n}: {\n  filter: ExtendedColumnFilter<TData>;\n  inputId: string;\n  column: Column<TData>;\n  columnMeta?: ColumnMeta<TData, unknown>;\n  onFilterUpdate: (\n    filterId: string,\n    updates: Partial<Omit<ExtendedColumnFilter<TData>, \"filterId\">>,\n  ) => void;\n  showValueSelector: boolean;\n  setShowValueSelector: (value: boolean) => void;\n}) {\n  if (filter.operator === \"isEmpty\" || filter.operator === \"isNotEmpty\") {\n    return (\n      <div\n        id={inputId}\n        role=\"status\"\n        aria-label={`${columnMeta?.label} filter is ${\n          filter.operator === \"isEmpty\" ? \"empty\" : \"not empty\"\n        }`}\n        aria-live=\"polite\"\n        className=\"h-8 w-full rounded border bg-transparent dark:bg-input/30\"\n      />\n    );\n  }\n\n  switch (filter.variant) {\n    case \"text\":\n    case \"number\":\n    case \"range\": {\n      if (\n        (filter.variant === \"range\" && filter.operator === \"isBetween\") ||\n        filter.operator === \"isBetween\"\n      ) {\n        return (\n          <DataTableRangeFilter\n            filter={filter}\n            column={column}\n            inputId={inputId}\n            onFilterUpdate={onFilterUpdate}\n          />\n        );\n      }\n\n      const isNumber =\n        filter.variant === \"number\" || filter.variant === \"range\";\n\n      return (\n        <Input\n          id={inputId}\n          type={isNumber ? \"number\" : filter.variant}\n          aria-label={`${columnMeta?.label} filter value`}\n          aria-describedby={`${inputId}-description`}\n          inputMode={isNumber ? \"numeric\" : undefined}\n          placeholder={columnMeta?.placeholder ?? \"Enter a value...\"}\n          className=\"h-8 w-full rounded\"\n          defaultValue={\n            typeof filter.value === \"string\" ? filter.value : undefined\n          }\n          onChange={(event) =>\n            onFilterUpdate(filter.filterId, {\n              value: event.target.value,\n            })\n          }\n        />\n      );\n    }\n\n    case \"boolean\": {\n      if (Array.isArray(filter.value)) return null;\n\n      const inputListboxId = `${inputId}-listbox`;\n\n      return (\n        <Select\n          open={showValueSelector}\n          onOpenChange={setShowValueSelector}\n          value={filter.value}\n          onValueChange={(value) =>\n            onFilterUpdate(filter.filterId, {\n              value,\n            })\n          }\n        >\n          <SelectTrigger\n            id={inputId}\n            aria-controls={inputListboxId}\n            aria-label={`${columnMeta?.label} boolean filter`}\n            className=\"w-full rounded\"\n          >\n            <SelectValue placeholder={filter.value ? \"True\" : \"False\"} />\n          </SelectTrigger>\n          <SelectContent id={inputListboxId}>\n            <SelectGroup>\n              <SelectItem value=\"true\">True</SelectItem>\n              <SelectItem value=\"false\">False</SelectItem>\n            </SelectGroup>\n          </SelectContent>\n        </Select>\n      );\n    }\n\n    case \"select\":\n    case \"multiSelect\": {\n      const inputListboxId = `${inputId}-listbox`;\n\n      const multiple = filter.variant === \"multiSelect\";\n      const selectedValues = multiple\n        ? Array.isArray(filter.value)\n          ? filter.value\n          : []\n        : typeof filter.value === \"string\"\n          ? filter.value\n          : undefined;\n\n      return (\n        <Faceted\n          open={showValueSelector}\n          onOpenChange={setShowValueSelector}\n          value={selectedValues}\n          onValueChange={(value) => {\n            onFilterUpdate(filter.filterId, {\n              value,\n            });\n          }}\n          multiple={multiple}\n        >\n          <FacetedTrigger asChild>\n            <Button\n              id={inputId}\n              aria-controls={inputListboxId}\n              aria-label={`${columnMeta?.label} filter value${multiple ? \"s\" : \"\"}`}\n              variant=\"outline\"\n              className=\"w-full rounded font-normal\"\n            >\n              <FacetedBadgeList\n                options={columnMeta?.options}\n                placeholder={\n                  columnMeta?.placeholder ??\n                  `Select option${multiple ? \"s\" : \"\"}...`\n                }\n              />\n            </Button>\n          </FacetedTrigger>\n          <FacetedContent id={inputListboxId} className=\"w-[200px]\">\n            <FacetedInput\n              aria-label={`Search ${columnMeta?.label} options`}\n              placeholder={columnMeta?.placeholder ?? \"Search options...\"}\n            />\n            <FacetedList>\n              <FacetedEmpty>No options found.</FacetedEmpty>\n              <FacetedGroup>\n                {columnMeta?.options?.map((option) => (\n                  <FacetedItem key={option.value} value={option.value}>\n                    {option.icon && <option.icon />}\n                    <span>{option.label}</span>\n                    {option.count && (\n                      <span className=\"ml-auto font-mono text-xs\">\n                        {option.count}\n                      </span>\n                    )}\n                  </FacetedItem>\n                ))}\n              </FacetedGroup>\n            </FacetedList>\n          </FacetedContent>\n        </Faceted>\n      );\n    }\n\n    case \"date\":\n    case \"dateRange\": {\n      const inputListboxId = `${inputId}-listbox`;\n\n      const dateValue = Array.isArray(filter.value)\n        ? filter.value.filter(Boolean)\n        : [filter.value, filter.value].filter(Boolean);\n\n      const startDate = dateValue[0]\n        ? new Date(Number(dateValue[0]))\n        : undefined;\n      const endDate = dateValue[1] ? new Date(Number(dateValue[1])) : undefined;\n\n      const isSameDate =\n        startDate &&\n        endDate &&\n        startDate.toDateString() === endDate.toDateString();\n\n      const displayValue =\n        filter.operator === \"isBetween\" && dateValue.length === 2 && !isSameDate\n          ? `${formatDate(startDate, { month: \"short\" })} - ${formatDate(endDate, { month: \"short\" })}`\n          : startDate\n            ? formatDate(startDate, { month: \"short\" })\n            : \"Pick a date\";\n\n      return (\n        <Popover open={showValueSelector} onOpenChange={setShowValueSelector}>\n          <PopoverTrigger asChild>\n            <Button\n              id={inputId}\n              aria-controls={inputListboxId}\n              aria-label={`${columnMeta?.label} date filter`}\n              variant=\"outline\"\n              className={cn(\n                \"w-full justify-start rounded text-left font-normal\",\n                !filter.value && \"text-muted-foreground\",\n              )}\n            >\n              <CalendarIcon />\n              <span className=\"truncate\">{displayValue}</span>\n            </Button>\n          </PopoverTrigger>\n          <PopoverContent\n            id={inputListboxId}\n            align=\"start\"\n            className=\"w-auto p-0\"\n          >\n            {filter.operator === \"isBetween\" ? (\n              <Calendar\n                aria-label={`Select ${columnMeta?.label} date range`}\n                autoFocus\n                captionLayout=\"dropdown\"\n                mode=\"range\"\n                selected={\n                  dateValue.length === 2\n                    ? {\n                        from: new Date(Number(dateValue[0])),\n                        to: new Date(Number(dateValue[1])),\n                      }\n                    : {\n                        from: new Date(),\n                        to: new Date(),\n                      }\n                }\n                onSelect={(date) => {\n                  onFilterUpdate(filter.filterId, {\n                    value: date\n                      ? [\n                          (date.from?.getTime() ?? \"\").toString(),\n                          (date.to?.getTime() ?? \"\").toString(),\n                        ]\n                      : [],\n                  });\n                }}\n              />\n            ) : (\n              <Calendar\n                aria-label={`Select ${columnMeta?.label} date`}\n                autoFocus\n                captionLayout=\"dropdown\"\n                mode=\"single\"\n                selected={\n                  dateValue[0] ? new Date(Number(dateValue[0])) : undefined\n                }\n                onSelect={(date) => {\n                  onFilterUpdate(filter.filterId, {\n                    value: (date?.getTime() ?? \"\").toString(),\n                  });\n                  setShowValueSelector(false);\n                }}\n              />\n            )}\n          </PopoverContent>\n        </Popover>\n      );\n    }\n\n    default:\n      return null;\n  }\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table-filter-list.tsx"
    },
    {
      "path": "src/components/data-table/data-table-range-filter.tsx",
      "content": "\"use client\";\n\nimport type { Column } from \"@tanstack/react-table\";\nimport * as React from \"react\";\n\nimport { Input } from \"@/components/ui/input\";\nimport { cn } from \"@/lib/utils\";\nimport type { ExtendedColumnFilter } from \"@/types/data-table\";\n\ninterface DataTableRangeFilterProps<TData> extends React.ComponentProps<\"div\"> {\n  filter: ExtendedColumnFilter<TData>;\n  column: Column<TData>;\n  inputId: string;\n  onFilterUpdate: (\n    filterId: string,\n    updates: Partial<Omit<ExtendedColumnFilter<TData>, \"filterId\">>,\n  ) => void;\n}\n\nexport function DataTableRangeFilter<TData>({\n  filter,\n  column,\n  inputId,\n  onFilterUpdate,\n  className,\n  ...props\n}: DataTableRangeFilterProps<TData>) {\n  const meta = column.columnDef.meta;\n\n  const [min, max] = React.useMemo(() => {\n    const range = column.columnDef.meta?.range;\n    if (range) return range;\n\n    const values = column.getFacetedMinMaxValues();\n    if (!values) return [0, 100];\n\n    return [values[0], values[1]];\n  }, [column]);\n\n  const formatValue = React.useCallback(\n    (value: string | number | undefined) => {\n      if (value === undefined || value === \"\") return \"\";\n      const numValue = Number(value);\n      return Number.isNaN(numValue)\n        ? \"\"\n        : numValue.toLocaleString(undefined, {\n            maximumFractionDigits: 0,\n          });\n    },\n    [],\n  );\n\n  const value = React.useMemo(() => {\n    if (Array.isArray(filter.value)) return filter.value.map(formatValue);\n    return [formatValue(filter.value), \"\"];\n  }, [filter.value, formatValue]);\n\n  const onRangeValueChange = React.useCallback(\n    (value: string, isMin?: boolean) => {\n      const numValue = Number(value);\n      const currentValues = Array.isArray(filter.value)\n        ? filter.value\n        : [\"\", \"\"];\n      const otherValue = isMin\n        ? (currentValues[1] ?? \"\")\n        : (currentValues[0] ?? \"\");\n\n      if (\n        value === \"\" ||\n        (!Number.isNaN(numValue) &&\n          (isMin\n            ? numValue >= min && numValue <= (Number(otherValue) || max)\n            : numValue <= max && numValue >= (Number(otherValue) || min)))\n      ) {\n        onFilterUpdate(filter.filterId, {\n          value: isMin ? [value, otherValue] : [otherValue, value],\n        });\n      }\n    },\n    [filter.filterId, filter.value, min, max, onFilterUpdate],\n  );\n\n  return (\n    <div\n      data-slot=\"range\"\n      className={cn(\"flex w-full items-center gap-2\", className)}\n      {...props}\n    >\n      <Input\n        id={`${inputId}-min`}\n        type=\"number\"\n        aria-label={`${meta?.label} minimum value`}\n        aria-valuemin={min}\n        aria-valuemax={max}\n        data-slot=\"range-min\"\n        inputMode=\"numeric\"\n        placeholder={min.toString()}\n        min={min}\n        max={max}\n        className=\"h-8 w-full rounded\"\n        defaultValue={value[0]}\n        onChange={(event) => onRangeValueChange(event.target.value, true)}\n      />\n      <span className=\"sr-only shrink-0 text-muted-foreground\">to</span>\n      <Input\n        id={`${inputId}-max`}\n        type=\"number\"\n        aria-label={`${meta?.label} maximum value`}\n        aria-valuemin={min}\n        aria-valuemax={max}\n        data-slot=\"range-max\"\n        inputMode=\"numeric\"\n        placeholder={max.toString()}\n        min={min}\n        max={max}\n        className=\"h-8 w-full rounded\"\n        defaultValue={value[1]}\n        onChange={(event) => onRangeValueChange(event.target.value)}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table-range-filter.tsx"
    },
    {
      "path": "src/components/data-table/data-table-advanced-toolbar.tsx",
      "content": "\"use client\";\n\nimport type { Table } from \"@tanstack/react-table\";\nimport type * as React from \"react\";\n\nimport { DataTableViewOptions } from \"@/components/data-table/data-table-view-options\";\nimport { cn } from \"@/lib/utils\";\n\ninterface DataTableAdvancedToolbarProps<TData>\n  extends React.ComponentProps<\"div\"> {\n  table: Table<TData>;\n}\n\nexport function DataTableAdvancedToolbar<TData>({\n  table,\n  children,\n  className,\n  ...props\n}: DataTableAdvancedToolbarProps<TData>) {\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\">{children}</div>\n      <div className=\"flex items-center gap-2\">\n        <DataTableViewOptions table={table} align=\"end\" />\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-table/data-table-advanced-toolbar.tsx"
    },
    {
      "path": "src/components/ui/sortable.tsx",
      "content": "\"use client\";\n\nimport {\n  type Announcements,\n  closestCenter,\n  closestCorners,\n  DndContext,\n  type DndContextProps,\n  type DragEndEvent,\n  type DraggableAttributes,\n  type DraggableSyntheticListeners,\n  DragOverlay,\n  type DragStartEvent,\n  type DropAnimation,\n  defaultDropAnimationSideEffects,\n  KeyboardSensor,\n  MouseSensor,\n  type ScreenReaderInstructions,\n  TouchSensor,\n  type UniqueIdentifier,\n  useSensor,\n  useSensors,\n} from \"@dnd-kit/core\";\nimport {\n  restrictToHorizontalAxis,\n  restrictToParentElement,\n  restrictToVerticalAxis,\n} from \"@dnd-kit/modifiers\";\nimport {\n  arrayMove,\n  horizontalListSortingStrategy,\n  SortableContext,\n  type SortableContextProps,\n  sortableKeyboardCoordinates,\n  useSortable,\n  verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport * as ReactDOM from \"react-dom\";\nimport { useComposedRefs } from \"@/lib/compose-refs\";\nimport { cn } from \"@/lib/utils\";\n\nconst orientationConfig = {\n  vertical: {\n    modifiers: [restrictToVerticalAxis, restrictToParentElement],\n    strategy: verticalListSortingStrategy,\n    collisionDetection: closestCenter,\n  },\n  horizontal: {\n    modifiers: [restrictToHorizontalAxis, restrictToParentElement],\n    strategy: horizontalListSortingStrategy,\n    collisionDetection: closestCenter,\n  },\n  mixed: {\n    modifiers: [restrictToParentElement],\n    strategy: undefined,\n    collisionDetection: closestCorners,\n  },\n};\n\nconst ROOT_NAME = \"Sortable\";\nconst CONTENT_NAME = \"SortableContent\";\nconst ITEM_NAME = \"SortableItem\";\nconst ITEM_HANDLE_NAME = \"SortableItemHandle\";\nconst OVERLAY_NAME = \"SortableOverlay\";\n\ninterface SortableRootContextValue<T> {\n  id: string;\n  items: UniqueIdentifier[];\n  modifiers: DndContextProps[\"modifiers\"];\n  strategy: SortableContextProps[\"strategy\"];\n  activeId: UniqueIdentifier | null;\n  setActiveId: (id: UniqueIdentifier | null) => void;\n  getItemValue: (item: T) => UniqueIdentifier;\n  flatCursor: boolean;\n}\n\nconst SortableRootContext =\n  React.createContext<SortableRootContextValue<unknown> | null>(null);\n\nfunction useSortableContext(consumerName: string) {\n  const context = React.useContext(SortableRootContext);\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n  }\n  return context;\n}\n\ninterface GetItemValue<T> {\n  /**\n   * Callback that returns a unique identifier for each sortable item. Required for array of objects.\n   * @example getItemValue={(item) => item.id}\n   */\n  getItemValue: (item: T) => UniqueIdentifier;\n}\n\ntype SortableProps<T> = DndContextProps &\n  (T extends object ? GetItemValue<T> : Partial<GetItemValue<T>>) & {\n    value: T[];\n    onValueChange?: (items: T[]) => void;\n    onMove?: (\n      event: DragEndEvent & { activeIndex: number; overIndex: number },\n    ) => void;\n    strategy?: SortableContextProps[\"strategy\"];\n    orientation?: \"vertical\" | \"horizontal\" | \"mixed\";\n    flatCursor?: boolean;\n  };\n\nfunction Sortable<T>(props: SortableProps<T>) {\n  const {\n    value,\n    onValueChange,\n    collisionDetection,\n    modifiers,\n    strategy,\n    onMove,\n    orientation = \"vertical\",\n    flatCursor = false,\n    getItemValue: getItemValueProp,\n    accessibility,\n    ...sortableProps\n  } = props;\n\n  const id = React.useId();\n  const [activeId, setActiveId] = React.useState<UniqueIdentifier | null>(null);\n\n  const sensors = useSensors(\n    useSensor(MouseSensor),\n    useSensor(TouchSensor),\n    useSensor(KeyboardSensor, {\n      coordinateGetter: sortableKeyboardCoordinates,\n    }),\n  );\n  const config = React.useMemo(\n    () => orientationConfig[orientation],\n    [orientation],\n  );\n\n  const getItemValue = React.useCallback(\n    (item: T): UniqueIdentifier => {\n      if (typeof item === \"object\" && !getItemValueProp) {\n        throw new Error(\n          \"`getItemValue` is required when using array of objects\",\n        );\n      }\n      return getItemValueProp\n        ? getItemValueProp(item)\n        : (item as UniqueIdentifier);\n    },\n    [getItemValueProp],\n  );\n\n  const items = React.useMemo(() => {\n    return value.map((item) => getItemValue(item));\n  }, [value, getItemValue]);\n\n  const onDragStart = React.useCallback(\n    (event: DragStartEvent) => {\n      sortableProps.onDragStart?.(event);\n\n      if (event.activatorEvent.defaultPrevented) return;\n\n      setActiveId(event.active.id);\n    },\n    [sortableProps.onDragStart],\n  );\n\n  const onDragEnd = React.useCallback(\n    (event: DragEndEvent) => {\n      sortableProps.onDragEnd?.(event);\n\n      if (event.activatorEvent.defaultPrevented) return;\n\n      const { active, over } = event;\n      if (over && active.id !== over?.id) {\n        const activeIndex = value.findIndex(\n          (item) => getItemValue(item) === active.id,\n        );\n        const overIndex = value.findIndex(\n          (item) => getItemValue(item) === over.id,\n        );\n\n        if (onMove) {\n          onMove({ ...event, activeIndex, overIndex });\n        } else {\n          onValueChange?.(arrayMove(value, activeIndex, overIndex));\n        }\n      }\n      setActiveId(null);\n    },\n    [value, onValueChange, onMove, getItemValue, sortableProps.onDragEnd],\n  );\n\n  const onDragCancel = React.useCallback(\n    (event: DragEndEvent) => {\n      sortableProps.onDragCancel?.(event);\n\n      if (event.activatorEvent.defaultPrevented) return;\n\n      setActiveId(null);\n    },\n    [sortableProps.onDragCancel],\n  );\n\n  const announcements: Announcements = React.useMemo(\n    () => ({\n      onDragStart({ active }) {\n        const activeValue = active.id.toString();\n        return `Grabbed sortable item \"${activeValue}\". Current position is ${active.data.current?.sortable.index + 1} of ${value.length}. Use arrow keys to move, space to drop.`;\n      },\n      onDragOver({ active, over }) {\n        if (over) {\n          const overIndex = over.data.current?.sortable.index ?? 0;\n          const activeIndex = active.data.current?.sortable.index ?? 0;\n          const moveDirection = overIndex > activeIndex ? \"down\" : \"up\";\n          const activeValue = active.id.toString();\n          return `Sortable item \"${activeValue}\" moved ${moveDirection} to position ${overIndex + 1} of ${value.length}.`;\n        }\n        return \"Sortable item is no longer over a droppable area. Press escape to cancel.\";\n      },\n      onDragEnd({ active, over }) {\n        const activeValue = active.id.toString();\n        if (over) {\n          const overIndex = over.data.current?.sortable.index ?? 0;\n          return `Sortable item \"${activeValue}\" dropped at position ${overIndex + 1} of ${value.length}.`;\n        }\n        return `Sortable item \"${activeValue}\" dropped. No changes were made.`;\n      },\n      onDragCancel({ active }) {\n        const activeIndex = active.data.current?.sortable.index ?? 0;\n        const activeValue = active.id.toString();\n        return `Sorting cancelled. Sortable item \"${activeValue}\" returned to position ${activeIndex + 1} of ${value.length}.`;\n      },\n      onDragMove({ active, over }) {\n        if (over) {\n          const overIndex = over.data.current?.sortable.index ?? 0;\n          const activeIndex = active.data.current?.sortable.index ?? 0;\n          const moveDirection = overIndex > activeIndex ? \"down\" : \"up\";\n          const activeValue = active.id.toString();\n          return `Sortable item \"${activeValue}\" is moving ${moveDirection} to position ${overIndex + 1} of ${value.length}.`;\n        }\n        return \"Sortable item is no longer over a droppable area. Press escape to cancel.\";\n      },\n    }),\n    [value],\n  );\n\n  const screenReaderInstructions: ScreenReaderInstructions = React.useMemo(\n    () => ({\n      draggable: `\n        To pick up a sortable item, press space or enter.\n        While dragging, use the ${orientation === \"vertical\" ? \"up and down\" : orientation === \"horizontal\" ? \"left and right\" : \"arrow\"} keys to move the item.\n        Press space or enter again to drop the item in its new position, or press escape to cancel.\n      `,\n    }),\n    [orientation],\n  );\n\n  const contextValue = React.useMemo(\n    () => ({\n      id,\n      items,\n      modifiers: modifiers ?? config.modifiers,\n      strategy: strategy ?? config.strategy,\n      activeId,\n      setActiveId,\n      getItemValue,\n      flatCursor,\n    }),\n    [\n      id,\n      items,\n      modifiers,\n      strategy,\n      config.modifiers,\n      config.strategy,\n      activeId,\n      getItemValue,\n      flatCursor,\n    ],\n  );\n\n  return (\n    <SortableRootContext.Provider\n      value={contextValue as SortableRootContextValue<unknown>}\n    >\n      <DndContext\n        collisionDetection={collisionDetection ?? config.collisionDetection}\n        modifiers={modifiers ?? config.modifiers}\n        sensors={sensors}\n        {...sortableProps}\n        id={id}\n        onDragStart={onDragStart}\n        onDragEnd={onDragEnd}\n        onDragCancel={onDragCancel}\n        accessibility={{\n          announcements,\n          screenReaderInstructions,\n          ...accessibility,\n        }}\n      />\n    </SortableRootContext.Provider>\n  );\n}\n\nconst SortableContentContext = React.createContext<boolean>(false);\n\ninterface SortableContentProps extends React.ComponentProps<\"div\"> {\n  strategy?: SortableContextProps[\"strategy\"];\n  children: React.ReactNode;\n  asChild?: boolean;\n  withoutSlot?: boolean;\n}\n\nfunction SortableContent(props: SortableContentProps) {\n  const {\n    strategy: strategyProp,\n    asChild,\n    withoutSlot,\n    children,\n    ref,\n    ...contentProps\n  } = props;\n\n  const context = useSortableContext(CONTENT_NAME);\n\n  const ContentPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <SortableContentContext.Provider value={true}>\n      <SortableContext\n        items={context.items}\n        strategy={strategyProp ?? context.strategy}\n      >\n        {withoutSlot ? (\n          children\n        ) : (\n          <ContentPrimitive\n            data-slot=\"sortable-content\"\n            {...contentProps}\n            ref={ref}\n          >\n            {children}\n          </ContentPrimitive>\n        )}\n      </SortableContext>\n    </SortableContentContext.Provider>\n  );\n}\n\ninterface SortableItemContextValue {\n  id: string;\n  attributes: DraggableAttributes;\n  listeners: DraggableSyntheticListeners | undefined;\n  setActivatorNodeRef: (node: HTMLElement | null) => void;\n  isDragging?: boolean;\n  disabled?: boolean;\n}\n\nconst SortableItemContext =\n  React.createContext<SortableItemContextValue | null>(null);\n\nfunction useSortableItemContext(consumerName: string) {\n  const context = React.useContext(SortableItemContext);\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ITEM_NAME}\\``);\n  }\n  return context;\n}\n\ninterface SortableItemProps extends React.ComponentProps<\"div\"> {\n  value: UniqueIdentifier;\n  asHandle?: boolean;\n  asChild?: boolean;\n  disabled?: boolean;\n}\n\nfunction SortableItem(props: SortableItemProps) {\n  const {\n    value,\n    style,\n    asHandle,\n    asChild,\n    disabled,\n    className,\n    ref,\n    ...itemProps\n  } = props;\n\n  const inSortableContent = React.useContext(SortableContentContext);\n  const inSortableOverlay = React.useContext(SortableOverlayContext);\n\n  if (!inSortableContent && !inSortableOverlay) {\n    throw new Error(\n      `\\`${ITEM_NAME}\\` must be used within \\`${CONTENT_NAME}\\` or \\`${OVERLAY_NAME}\\``,\n    );\n  }\n\n  if (value === \"\") {\n    throw new Error(`\\`${ITEM_NAME}\\` value cannot be an empty string`);\n  }\n\n  const context = useSortableContext(ITEM_NAME);\n  const id = React.useId();\n  const {\n    attributes,\n    listeners,\n    setNodeRef,\n    setActivatorNodeRef,\n    transform,\n    transition,\n    isDragging,\n  } = useSortable({ id: value, disabled });\n\n  const composedRef = useComposedRefs(ref, (node) => {\n    if (disabled) return;\n    setNodeRef(node);\n    if (asHandle) setActivatorNodeRef(node);\n  });\n\n  const composedStyle = React.useMemo<React.CSSProperties>(() => {\n    return {\n      transform: CSS.Translate.toString(transform),\n      transition,\n      ...style,\n    };\n  }, [transform, transition, style]);\n\n  const itemContext = React.useMemo<SortableItemContextValue>(\n    () => ({\n      id,\n      attributes,\n      listeners,\n      setActivatorNodeRef,\n      isDragging,\n      disabled,\n    }),\n    [id, attributes, listeners, setActivatorNodeRef, isDragging, disabled],\n  );\n\n  const ItemPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <SortableItemContext.Provider value={itemContext}>\n      <ItemPrimitive\n        id={id}\n        data-disabled={disabled}\n        data-dragging={isDragging ? \"\" : undefined}\n        data-slot=\"sortable-item\"\n        {...itemProps}\n        {...(asHandle && !disabled ? attributes : {})}\n        {...(asHandle && !disabled ? listeners : {})}\n        ref={composedRef}\n        style={composedStyle}\n        className={cn(\n          \"focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1\",\n          {\n            \"touch-none select-none\": asHandle,\n            \"cursor-default\": context.flatCursor,\n            \"data-dragging:cursor-grabbing\": !context.flatCursor,\n            \"cursor-grab\": !isDragging && asHandle && !context.flatCursor,\n            \"opacity-50\": isDragging,\n            \"pointer-events-none opacity-50\": disabled,\n          },\n          className,\n        )}\n      />\n    </SortableItemContext.Provider>\n  );\n}\n\ninterface SortableItemHandleProps extends React.ComponentProps<\"button\"> {\n  asChild?: boolean;\n}\n\nfunction SortableItemHandle(props: SortableItemHandleProps) {\n  const { asChild, disabled, className, ref, ...itemHandleProps } = props;\n\n  const context = useSortableContext(ITEM_HANDLE_NAME);\n  const itemContext = useSortableItemContext(ITEM_HANDLE_NAME);\n\n  const isDisabled = disabled ?? itemContext.disabled;\n\n  const composedRef = useComposedRefs(ref, (node) => {\n    if (!isDisabled) return;\n    itemContext.setActivatorNodeRef(node);\n  });\n\n  const HandlePrimitive = asChild ? SlotPrimitive.Slot : \"button\";\n\n  return (\n    <HandlePrimitive\n      type=\"button\"\n      aria-controls={itemContext.id}\n      data-disabled={isDisabled}\n      data-dragging={itemContext.isDragging ? \"\" : undefined}\n      data-slot=\"sortable-item-handle\"\n      {...itemHandleProps}\n      {...(isDisabled ? {} : itemContext.attributes)}\n      {...(isDisabled ? {} : itemContext.listeners)}\n      ref={composedRef}\n      className={cn(\n        \"select-none disabled:pointer-events-none disabled:opacity-50\",\n        context.flatCursor\n          ? \"cursor-default\"\n          : \"cursor-grab data-dragging:cursor-grabbing\",\n        className,\n      )}\n      disabled={isDisabled}\n    />\n  );\n}\n\nconst SortableOverlayContext = React.createContext(false);\n\nconst dropAnimation: DropAnimation = {\n  sideEffects: defaultDropAnimationSideEffects({\n    styles: {\n      active: {\n        opacity: \"0.4\",\n      },\n    },\n  }),\n};\n\ninterface SortableOverlayProps\n  extends Omit<React.ComponentProps<typeof DragOverlay>, \"children\"> {\n  container?: Element | DocumentFragment | null;\n  children?:\n    | ((params: { value: UniqueIdentifier }) => React.ReactNode)\n    | React.ReactNode;\n}\n\nfunction SortableOverlay(props: SortableOverlayProps) {\n  const { container: containerProp, children, ...overlayProps } = props;\n\n  const context = useSortableContext(OVERLAY_NAME);\n\n  const [mounted, setMounted] = React.useState(false);\n\n  React.useLayoutEffect(() => setMounted(true), []);\n\n  const container =\n    containerProp ?? (mounted ? globalThis.document?.body : null);\n\n  if (!container) return null;\n\n  return ReactDOM.createPortal(\n    <DragOverlay\n      dropAnimation={dropAnimation}\n      modifiers={context.modifiers}\n      className={cn(!context.flatCursor && \"cursor-grabbing\")}\n      {...overlayProps}\n    >\n      <SortableOverlayContext.Provider value={true}>\n        {context.activeId\n          ? typeof children === \"function\"\n            ? children({ value: context.activeId })\n            : children\n          : null}\n      </SortableOverlayContext.Provider>\n    </DragOverlay>,\n    container,\n  );\n}\n\nexport {\n  Sortable,\n  SortableContent,\n  SortableItem,\n  SortableItemHandle,\n  SortableOverlay,\n  type SortableProps,\n};\n",
      "type": "registry:ui",
      "target": "src/components/ui/sortable.tsx"
    },
    {
      "path": "src/components/ui/faceted.tsx",
      "content": "\"use client\";\n\nimport { Check, ChevronsUpDown } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { Badge } from \"@/components/ui/badge\";\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 { cn } from \"@/lib/utils\";\n\ntype FacetedValue<Multiple extends boolean> = Multiple extends true\n  ? string[]\n  : string;\n\ninterface FacetedContextValue<Multiple extends boolean = boolean> {\n  value?: FacetedValue<Multiple>;\n  onItemSelect?: (value: string) => void;\n  multiple?: Multiple;\n}\n\nconst FacetedContext = React.createContext<FacetedContextValue<boolean> | null>(\n  null,\n);\n\nfunction useFacetedContext(name: string) {\n  const context = React.useContext(FacetedContext);\n  if (!context) {\n    throw new Error(`\\`${name}\\` must be within Faceted`);\n  }\n  return context;\n}\n\ninterface FacetedProps<Multiple extends boolean = false>\n  extends React.ComponentProps<typeof Popover> {\n  value?: FacetedValue<Multiple>;\n  onValueChange?: (value: FacetedValue<Multiple> | undefined) => void;\n  children?: React.ReactNode;\n  multiple?: Multiple;\n}\n\nfunction Faceted<Multiple extends boolean = false>(\n  props: FacetedProps<Multiple>,\n) {\n  const {\n    open: openProp,\n    onOpenChange: onOpenChangeProp,\n    value,\n    onValueChange,\n    children,\n    multiple = false,\n    ...facetedProps\n  } = props;\n\n  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);\n  const isControlled = openProp !== undefined;\n  const open = isControlled ? openProp : uncontrolledOpen;\n\n  const onOpenChange = React.useCallback(\n    (newOpen: boolean) => {\n      if (!isControlled) {\n        setUncontrolledOpen(newOpen);\n      }\n      onOpenChangeProp?.(newOpen);\n    },\n    [isControlled, onOpenChangeProp],\n  );\n\n  const onItemSelect = React.useCallback(\n    (selectedValue: string) => {\n      if (!onValueChange) return;\n\n      if (multiple) {\n        const currentValue = (Array.isArray(value) ? value : []) as string[];\n        const newValue = currentValue.includes(selectedValue)\n          ? currentValue.filter((v) => v !== selectedValue)\n          : [...currentValue, selectedValue];\n        onValueChange(newValue as FacetedValue<Multiple>);\n      } else {\n        if (value === selectedValue) {\n          onValueChange(undefined);\n        } else {\n          onValueChange(selectedValue as FacetedValue<Multiple>);\n        }\n\n        requestAnimationFrame(() => onOpenChange(false));\n      }\n    },\n    [multiple, value, onValueChange, onOpenChange],\n  );\n\n  const contextValue = React.useMemo<FacetedContextValue<typeof multiple>>(\n    () => ({ value, onItemSelect, multiple }),\n    [value, onItemSelect, multiple],\n  );\n\n  return (\n    <FacetedContext.Provider value={contextValue}>\n      <Popover open={open} onOpenChange={onOpenChange} {...facetedProps}>\n        {children}\n      </Popover>\n    </FacetedContext.Provider>\n  );\n}\n\nfunction FacetedTrigger(props: React.ComponentProps<typeof PopoverTrigger>) {\n  const { className, children, ...triggerProps } = props;\n\n  return (\n    <PopoverTrigger\n      {...triggerProps}\n      className={cn(\"justify-between text-left\", className)}\n    >\n      {children}\n    </PopoverTrigger>\n  );\n}\n\ninterface FacetedBadgeListProps extends React.ComponentProps<\"div\"> {\n  options?: { label: string; value: string }[];\n  max?: number;\n  badgeClassName?: string;\n  placeholder?: string;\n}\n\nfunction FacetedBadgeList(props: FacetedBadgeListProps) {\n  const {\n    options = [],\n    max = 2,\n    placeholder = \"Select options...\",\n    className,\n    badgeClassName,\n    ...badgeListProps\n  } = props;\n\n  const context = useFacetedContext(\"FacetedBadgeList\");\n  const values = Array.isArray(context.value)\n    ? context.value\n    : ([context.value].filter(Boolean) as string[]);\n\n  const getLabel = React.useCallback(\n    (value: string) => {\n      const option = options.find((opt) => opt.value === value);\n      return option?.label ?? value;\n    },\n    [options],\n  );\n\n  if (!values || values.length === 0) {\n    return (\n      <div\n        {...badgeListProps}\n        className=\"flex w-full items-center gap-1 text-muted-foreground\"\n      >\n        {placeholder}\n        <ChevronsUpDown className=\"ml-auto size-4 shrink-0 opacity-50\" />\n      </div>\n    );\n  }\n\n  return (\n    <div\n      {...badgeListProps}\n      className={cn(\"flex flex-wrap items-center gap-1\", className)}\n    >\n      {values.length > max ? (\n        <Badge\n          variant=\"secondary\"\n          className={cn(\"rounded-sm px-1 font-normal\", badgeClassName)}\n        >\n          {values.length} selected\n        </Badge>\n      ) : (\n        values.map((value) => (\n          <Badge\n            key={value}\n            variant=\"secondary\"\n            className={cn(\"rounded-sm px-1 font-normal\", badgeClassName)}\n          >\n            <span className=\"truncate\">{getLabel(value)}</span>\n          </Badge>\n        ))\n      )}\n    </div>\n  );\n}\n\nfunction FacetedContent(props: React.ComponentProps<typeof PopoverContent>) {\n  const { className, children, ...contentProps } = props;\n\n  return (\n    <PopoverContent\n      {...contentProps}\n      align=\"start\"\n      className={cn(\n        \"w-[200px] origin-(--radix-popover-content-transform-origin) p-0\",\n        className,\n      )}\n    >\n      <Command>{children}</Command>\n    </PopoverContent>\n  );\n}\n\nconst FacetedInput = CommandInput;\n\nconst FacetedList = CommandList;\n\nconst FacetedEmpty = CommandEmpty;\n\nconst FacetedGroup = CommandGroup;\n\ninterface FacetedItemProps extends React.ComponentProps<typeof CommandItem> {\n  value: string;\n}\n\nfunction FacetedItem(props: FacetedItemProps) {\n  const { value, onSelect, className, children, ...itemProps } = props;\n  const context = useFacetedContext(\"FacetedItem\");\n\n  const isSelected = context.multiple\n    ? Array.isArray(context.value) && context.value.includes(value)\n    : context.value === value;\n\n  const onItemSelect = React.useCallback(\n    (currentValue: string) => {\n      if (onSelect) {\n        onSelect(currentValue);\n      } else if (context.onItemSelect) {\n        context.onItemSelect(currentValue);\n      }\n    },\n    [onSelect, context],\n  );\n\n  return (\n    <CommandItem\n      aria-selected={isSelected}\n      data-selected={isSelected}\n      className={cn(\"gap-2\", className)}\n      onSelect={() => onItemSelect(value)}\n      {...itemProps}\n    >\n      <span\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 className=\"size-4\" />\n      </span>\n      {children}\n    </CommandItem>\n  );\n}\n\nconst FacetedSeparator = CommandSeparator;\n\nexport {\n  Faceted,\n  FacetedBadgeList,\n  FacetedContent,\n  FacetedEmpty,\n  FacetedGroup,\n  FacetedInput,\n  FacetedItem,\n  FacetedList,\n  FacetedSeparator,\n  FacetedTrigger,\n};\n",
      "type": "registry:ui",
      "target": "src/components/ui/faceted.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-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/compose-refs.ts",
      "content": "/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx\n */\n\nimport * as React from \"react\";\n\ntype PossibleRef<T> = React.Ref<T> | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n  if (typeof ref === \"function\") {\n    return ref(value);\n  }\n\n  if (ref !== null && ref !== undefined) {\n    ref.current = value;\n  }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  return (node) => {\n    let hasCleanup = false;\n    const cleanups = refs.map((ref) => {\n      const cleanup = setRef(ref, node);\n      if (!hasCleanup && typeof cleanup === \"function\") {\n        hasCleanup = true;\n      }\n      return cleanup;\n    });\n\n    // React <19 will log an error to the console if a callback ref returns a\n    // value. We don't use ref cleanups internally so this will only happen if a\n    // user's ref callback returns a value, which we only expect if they are\n    // using the cleanup functionality added in React 19.\n    if (hasCleanup) {\n      return () => {\n        for (let i = 0; i < cleanups.length; i++) {\n          const cleanup = cleanups[i];\n          if (typeof cleanup === \"function\") {\n            cleanup();\n          } else {\n            setRef(refs[i], null);\n          }\n        }\n      };\n    }\n  };\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nfunction useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  // biome-ignore lint/correctness/useExhaustiveDependencies: we don't want to re-run this callback when the refs change\n  return React.useCallback(composeRefs(...refs), refs);\n}\n\nexport { composeRefs, useComposedRefs };\n",
      "type": "registry:lib"
    },
    {
      "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/id.ts",
      "content": "const DEFAULT_ALPHABET =\n  \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\ninterface GenerateIdOptions {\n  alphabet?: string;\n  length?: number;\n}\n\nexport function generateId(\n  prefixOrOptions?: string | GenerateIdOptions,\n  inputOptions: GenerateIdOptions = {},\n): string {\n  const options =\n    typeof prefixOrOptions === \"object\" ? prefixOrOptions : inputOptions;\n\n  const { alphabet = DEFAULT_ALPHABET, length = 12 } = options;\n\n  const chars = new Array(length);\n\n  for (let i = 0; i < length; i++) {\n    chars[i] = alphabet[(Math.random() * alphabet.length) | 0];\n  }\n\n  return chars.join(\"\");\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"
}