{"$schema":"https://ui.shadcn.com/schema/registry.json","author":"Kaiyu Hsu <uicapsule@kyh.io>","categories":["controls","minimal"],"dependencies":["@ai-sdk/react","@base-ui/react","@loremllm/transport","cmdk","cn","date-fns","lucide-react","react-day-picker"],"description":"Data table filter bar — build text, number, date and option filters as editable chips with operators, or type a prompt, hit Ask AI and watch simulated filters stream in.","devDependencies":[],"files":[{"content":"import { Button, Separator } from \"./ui\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { X } from \"lucide-react\";\n\nimport type {\n  Column,\n  FilterBinding,\n  DataTableFilterActions,\n  FiltersState,\n  FilterStrategy,\n} from \"../filter-package\";\nimport { getColumn, bindFilter } from \"../filter-package\";\nimport { FilterOperator } from \"./filter-operator\";\nimport { FilterSubject } from \"./filter-subject\";\nimport { FilterValue } from \"./filter-value\";\n\ninterface ActiveFiltersProps {\n  columns: Column[];\n  filters: FiltersState;\n  actions: DataTableFilterActions;\n  strategy: FilterStrategy;\n  entityName?: string;\n  aiGenerating?: boolean;\n}\n\nconst ActiveFilterSkeleton = () => (\n  <div className=\"border-(--border) bg-(--muted)/60 text-(--muted-foreground) flex h-7 items-center gap-2 rounded-2xl border px-3 text-xs shadow-xs\">\n    <div className=\"flex items-center gap-2\">\n      <span className=\"bg-(--muted-foreground)/60 block h-2 w-10 animate-pulse rounded\" />\n      <span className=\"bg-(--muted-foreground)/40 block h-2 w-6 animate-pulse rounded\" />\n    </div>\n  </div>\n);\n\ninterface ActiveFilterProps {\n  binding: FilterBinding;\n  actions: DataTableFilterActions;\n  strategy: FilterStrategy;\n  entityName?: string;\n}\n\nexport const ActiveFilter = ({ binding, actions, strategy, entityName }: ActiveFilterProps) => {\n  const { column, filter } = binding;\n  if (!filter) {\n    return null;\n  }\n  return (\n    <div className=\"border-(--border) bg-(--background) flex h-7 items-center rounded-2xl border text-xs shadow-xs\">\n      <FilterSubject column={column} entityName={entityName} />\n      <Separator orientation=\"vertical\" />\n      <FilterOperator filter={filter} actions={actions} />\n      <Separator orientation=\"vertical\" />\n      <FilterValue\n        binding={binding}\n        actions={actions}\n        strategy={strategy}\n        entityName={entityName}\n      />\n      <Separator orientation=\"vertical\" />\n      <Button\n        variant=\"ghost\"\n        className=\"text-(--muted-foreground) hover:text-(--primary) h-full w-7 rounded-none rounded-r-2xl text-xs\"\n        aria-label={`Remove ${column.displayName} filter`}\n        onClick={() => actions.removeFilter(filter.columnId)}\n      >\n        <X className=\"size-4 -translate-x-0.5\" />\n      </Button>\n    </div>\n  );\n};\n\nexport const ActiveFilters = ({\n  columns,\n  filters,\n  actions,\n  strategy,\n  entityName,\n  aiGenerating,\n}: ActiveFiltersProps) => (\n  <>\n    {filters.map((filter) => {\n      const id = filter.columnId;\n\n      const column = getColumn(columns, id);\n\n      return (\n        <ActiveFilter\n          key={`active-filter-${filter.columnId}`}\n          binding={bindFilter(column, filter)}\n          actions={actions}\n          strategy={strategy}\n          entityName={entityName}\n        />\n      );\n    })}\n    {aiGenerating && <ActiveFilterSkeleton />}\n  </>\n);\n\nexport const ActiveFiltersContainer = ({ children }: { children: ReactNode }) => {\n  const scrollContainerRef = useRef<HTMLDivElement>(null);\n  const [showLeftBlur, setShowLeftBlur] = useState(false);\n  const [showRightBlur, setShowRightBlur] = useState(true);\n\n  const checkScroll = useCallback(() => {\n    const el = scrollContainerRef.current;\n    if (!el) {\n      return;\n    }\n\n    const { scrollLeft, scrollWidth, clientWidth } = el;\n\n    setShowLeftBlur(scrollLeft > 0);\n    // 1px buffer absorbs subpixel rounding so the right blur clears at the end.\n    setShowRightBlur(scrollLeft + clientWidth < scrollWidth - 1);\n  }, []);\n\n  useEffect(() => {\n    const el = scrollContainerRef.current;\n    if (!el) {\n      return;\n    }\n\n    const resizeObserver = new ResizeObserver(checkScroll);\n    resizeObserver.observe(el);\n    const mutationObserver = new MutationObserver(checkScroll);\n    mutationObserver.observe(el, { characterData: true, childList: true, subtree: true });\n    return () => {\n      resizeObserver.disconnect();\n      mutationObserver.disconnect();\n    };\n  }, [checkScroll]);\n\n  return (\n    <div className=\"relative min-w-0 flex-1 overflow-x-hidden md:overflow-visible\">\n      {showLeftBlur && (\n        <div className=\"from-(--background) animate-in fade-in-0 pointer-events-none absolute top-0 bottom-0 left-0 z-10 md:hidden w-16 bg-gradient-to-r to-transparent\" />\n      )}\n\n      <div\n        ref={scrollContainerRef}\n        className=\"flex gap-2 overflow-x-auto md:flex-wrap md:overflow-visible\"\n        onScroll={checkScroll}\n      >\n        {children}\n      </div>\n\n      {showRightBlur && (\n        <div className=\"from-(--background) animate-in fade-in-0 pointer-events-none absolute top-0 right-0 bottom-0 z-10 md:hidden w-16 bg-gradient-to-l to-transparent\" />\n      )}\n    </div>\n  );\n};\n","path":"/_components/active-filters.tsx","target":"uicapsule/filter-bar/_components/active-filters.tsx","type":"registry:file"},{"content":"\"use client\";\n\nimport type {\n  Column,\n  DataTableFilterActions,\n  FiltersState,\n  FilterStrategy,\n} from \"../filter-package\";\nimport { ActiveFilters, ActiveFiltersContainer } from \"./active-filters\";\nimport { FilterActions } from \"./filter-actions\";\nimport { FilterSelector } from \"./filter-selector\";\nimport { useAiFilterSimulation } from \"./use-ai-filter-simulation\";\n\ninterface DataTableFilterProps {\n  columns: Column[];\n  filters: FiltersState;\n  actions: DataTableFilterActions;\n  strategy: FilterStrategy;\n  entityName?: string;\n}\n\nexport const DataTableFilter = ({\n  columns,\n  filters,\n  actions,\n  strategy,\n  entityName,\n}: DataTableFilterProps) => {\n  const { aiGenerating, handleAiFilterSubmit } = useAiFilterSimulation({\n    actions,\n    columns,\n  });\n\n  const selectorProps = {\n    actions,\n    aiGenerating,\n    columns,\n    filters,\n    onAIFilterSubmit: handleAiFilterSubmit,\n    strategy,\n  } as const;\n\n  return (\n    <div className=\"filter-bar flex w-full items-start gap-2\">\n      <FilterSelector {...selectorProps} />\n      <ActiveFiltersContainer>\n        <ActiveFilters\n          columns={columns}\n          filters={filters}\n          actions={actions}\n          strategy={strategy}\n          entityName={entityName}\n          aiGenerating={aiGenerating}\n        />\n        <FilterActions hasFilters={filters.length > 0} actions={actions} />\n      </ActiveFiltersContainer>\n    </div>\n  );\n};\n","path":"/_components/data-table-filter.tsx","target":"uicapsule/filter-bar/_components/data-table-filter.tsx","type":"registry:file"},{"content":"import { Button } from \"./ui\";\nimport { cn } from \"cn\";\n\nimport type { DataTableFilterActions } from \"../filter-package\";\n\ninterface FilterActionsProps {\n  hasFilters: boolean;\n  actions?: DataTableFilterActions;\n}\n\nexport const FilterActions = ({ hasFilters, actions }: FilterActionsProps) => (\n  <Button\n    className={cn(\"text-(--muted-foreground) h-7 px-2 font-normal\", !hasFilters && \"hidden\")}\n    variant=\"ghost\"\n    onClick={() => actions?.removeAllFilters()}\n  >\n    clear\n  </Button>\n);\n","path":"/_components/filter-actions.tsx","target":"uicapsule/filter-bar/_components/filter-actions.tsx","type":"registry:file"},{"content":"import { useState } from \"react\";\nimport {\n  Button,\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"./ui\";\nimport type {\n  ColumnDataType,\n  DataTableFilterActions,\n  FilterModel,\n  FilterOperatorDetails,\n  FilterOperators,\n  FilterOperatorUpdate,\n} from \"../filter-package\";\nimport { filterTypeOperatorDetails } from \"../filter-package\";\n\nconst operatorDetails = <K extends ColumnDataType>(\n  type: K,\n  operator: FilterOperators[K],\n): { key: string; target: \"single\" | \"multiple\" } => filterTypeOperatorDetails[type][operator];\n\nconst relatedOperators = <K extends ColumnDataType>(type: K, operator: FilterOperators[K]) => {\n  const current = operatorDetails(type, operator);\n  const details: FilterOperatorDetails<FilterOperators[K], K>[] = Object.values(\n    filterTypeOperatorDetails[type],\n  );\n  return details.filter((detail) => detail.target === current.target);\n};\n\nconst operatorChoices = (\n  filter: FilterModel,\n): { label: string; update: FilterOperatorUpdate }[] => {\n  const { columnId } = filter;\n  switch (filter.type) {\n    case \"text\": {\n      return relatedOperators(\"text\", filter.operator).map((detail) => ({\n        label: detail.key,\n        update: { columnId, operator: detail.value, type: \"text\" },\n      }));\n    }\n    case \"number\": {\n      return relatedOperators(\"number\", filter.operator).map((detail) => ({\n        label: detail.key,\n        update: { columnId, operator: detail.value, type: \"number\" },\n      }));\n    }\n    case \"date\": {\n      return relatedOperators(\"date\", filter.operator).map((detail) => ({\n        label: detail.key,\n        update: { columnId, operator: detail.value, type: \"date\" },\n      }));\n    }\n    case \"boolean\": {\n      return relatedOperators(\"boolean\", filter.operator).map((detail) => ({\n        label: detail.key,\n        update: { columnId, operator: detail.value, type: \"boolean\" },\n      }));\n    }\n    case \"option\": {\n      return relatedOperators(\"option\", filter.operator).map((detail) => ({\n        label: detail.key,\n        update: { columnId, operator: detail.value, type: \"option\" },\n      }));\n    }\n    case \"multiOption\": {\n      return relatedOperators(\"multiOption\", filter.operator).map((detail) => ({\n        label: detail.key,\n        update: { columnId, operator: detail.value, type: \"multiOption\" },\n      }));\n    }\n    default: {\n      return [];\n    }\n  }\n};\n\nexport const FilterOperator = ({\n  filter,\n  actions,\n}: {\n  filter: FilterModel;\n  actions: DataTableFilterActions;\n}) => {\n  const [open, setOpen] = useState(false);\n  const current = operatorDetails(filter.type, filter.operator);\n  return (\n    <Popover\n      open={open}\n      onOpenChange={(next) => {\n        if (filter.type !== \"boolean\") {\n          setOpen(next);\n        }\n      }}\n    >\n      <PopoverTrigger\n        render={\n          <Button\n            variant=\"ghost\"\n            className=\"m-0 h-full w-fit rounded-none p-0 px-2 text-xs whitespace-nowrap\"\n            onClick={() => {\n              if (filter.type !== \"boolean\") {\n                return;\n              }\n              actions.setFilterOperator({\n                columnId: filter.columnId,\n                operator: filter.operator === \"is\" ? \"is not\" : \"is\",\n                type: \"boolean\",\n              });\n            }}\n          />\n        }\n      >\n        <span className=\"text-(--muted-foreground)\">{current.key}</span>\n      </PopoverTrigger>\n      <PopoverContent align=\"start\" className=\"w-fit origin-(--transform-origin) p-0\">\n        <Command loop>\n          <CommandInput placeholder=\"search\" />\n          <CommandEmpty>No results</CommandEmpty>\n          <CommandList className=\"max-h-fit\">\n            <CommandGroup heading={filter.type === \"date\" ? undefined : \"operators\"}>\n              {operatorChoices(filter).map(({ label, update }) => (\n                <CommandItem\n                  key={update.operator}\n                  value={update.operator}\n                  onSelect={() => {\n                    actions.setFilterOperator(update);\n                    setOpen(false);\n                  }}\n                >\n                  {label}\n                </CommandItem>\n              ))}\n            </CommandGroup>\n          </CommandList>\n        </Command>\n      </PopoverContent>\n    </Popover>\n  );\n};\n","path":"/_components/filter-operator.tsx","target":"uicapsule/filter-bar/_components/filter-operator.tsx","type":"registry:file"},{"content":"import {\n  Button,\n  Checkbox,\n  Command,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"./ui\";\nimport { Fragment, isValidElement, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ElementType as ReactElementType, ReactElement } from \"react\";\nimport {\n  ArrowLeftIcon,\n  ArrowRightIcon,\n  ChevronRightIcon,\n  ListFilterIcon,\n  SparklesIcon,\n} from \"lucide-react\";\n\nimport type {\n  Column,\n  ColumnDataType,\n  DataTableFilterActions,\n  FiltersState,\n  FilterStrategy,\n} from \"../filter-package\";\nimport { getColumn, bindFilter } from \"../filter-package\";\nimport { FilterValueController } from \"./filter-value\";\n\nconst renderColumnIcon = (icon: ReactElement | ReactElementType, className: string) => {\n  if (isValidElement(icon)) {\n    return icon;\n  }\n  const IconComp = icon;\n  return <IconComp className={className} />;\n};\n\nexport const FilterableColumn = <TType extends ColumnDataType>({\n  column,\n  setProperty,\n  actions,\n  filters,\n}: {\n  column: Column<TType>;\n  setProperty: (value: string) => void;\n  actions: DataTableFilterActions;\n  filters: FiltersState;\n}) => {\n  const { icon: Icon } = column;\n  const isFiltered = filters.some(\n    (filter) => filter.columnId === column.id && filter.values.length > 0,\n  );\n\n  const handleSelect = () => {\n    if (column.type === \"boolean\") {\n      actions.setFilterValue({ columnId: column.id, type: \"boolean\", values: [true] });\n      return;\n    }\n\n    setProperty(column.id);\n  };\n\n  return (\n    <CommandItem\n      value={column.id}\n      keywords={[column.displayName]}\n      onSelect={handleSelect}\n      className=\"group\"\n    >\n      <div className=\"flex w-full items-center justify-between\">\n        <div className=\"inline-flex items-center gap-1.5\">\n          {Icon && (\n            <div className=\"relative\">\n              {renderColumnIcon(Icon, \"size-4 stroke-[2.25px]\")}\n              {isFiltered && (\n                <div className=\"absolute -right-0.5 -bottom-0.5 h-2 w-2 rounded-full bg-green-500\" />\n              )}\n            </div>\n          )}\n          {!Icon && isFiltered && <div className=\"h-2 w-2 rounded-full bg-green-500\" />}\n          <span>{column.displayName}</span>\n        </div>\n        {column.type !== \"boolean\" && (\n          <ArrowRightIcon className=\"size-4 opacity-0 group-aria-selected:opacity-100\" />\n        )}\n      </div>\n    </CommandItem>\n  );\n};\n\ninterface QuickSearchFiltersProps {\n  search?: string;\n  filters: FiltersState;\n  columns: Column[];\n  actions: DataTableFilterActions;\n}\n\nexport const QuickSearchFilters = ({\n  search,\n  filters,\n  columns,\n  actions,\n}: QuickSearchFiltersProps) => {\n  const cols = useMemo(\n    () => columns.filter((column) => column.type === \"option\" || column.type === \"multiOption\"),\n    [columns],\n  );\n\n  if (!search || search.trim().length < 2) {\n    return null;\n  }\n\n  return (\n    <>\n      {cols.map((column) => {\n        const filter = filters.find((f) => f.columnId === column.id);\n        const { options } = column;\n\n        const handleOptionSelect = (value: string, check: boolean) => {\n          if (check) {\n            actions.addFilterValue(column, [value]);\n          } else {\n            actions.removeFilterValue(column, [value]);\n          }\n        };\n\n        return (\n          <Fragment key={column.id}>\n            {options.map((v) => {\n              const checked =\n                (filter?.type === \"option\" || filter?.type === \"multiOption\") &&\n                filter.values.includes(v.value);\n\n              return (\n                <CommandItem\n                  key={v.value}\n                  value={v.value}\n                  keywords={[v.label, v.value]}\n                  onSelect={() => {\n                    handleOptionSelect(v.value, !checked);\n                  }}\n                  className=\"group\"\n                >\n                  <div className=\"group flex items-center gap-1.5\">\n                    <Checkbox\n                      checked={checked}\n                      className=\"dark:border-(--ring) mr-1 opacity-0 group-data-[selected=true]:opacity-100 data-checked:opacity-100\"\n                    />\n                    <div className=\"flex w-4 items-center justify-center\">\n                      {v.icon && renderColumnIcon(v.icon, \"text-(--primary) size-4\")}\n                    </div>\n                    <div className=\"flex items-center gap-0.5\">\n                      <span className=\"text-(--muted-foreground)\">{column.displayName}</span>\n                      <ChevronRightIcon className=\"text-(--muted-foreground)/75 size-3.5\" />\n                      <span>{v.label}</span>\n                    </div>\n                  </div>\n                </CommandItem>\n              );\n            })}\n          </Fragment>\n        );\n      })}\n    </>\n  );\n};\ninterface FilterSelectorProps {\n  filters: FiltersState;\n  columns: Column[];\n  actions: DataTableFilterActions;\n  strategy: FilterStrategy;\n  onAIFilterSubmit?: (prompt: string) => void;\n  aiGenerating?: boolean;\n}\n\nexport const FilterSelector = ({\n  filters,\n  columns,\n  actions,\n  strategy,\n  onAIFilterSubmit,\n  aiGenerating,\n}: FilterSelectorProps) => {\n  const [open, setOpen] = useState(false);\n  const [value, setValue] = useState(\"\");\n  const [property, setProperty] = useState<string | undefined>();\n  const inputRef = useRef<HTMLInputElement>(null);\n\n  const visibleColumns = useMemo(() => columns.filter((c) => !c.hidden), [columns]);\n\n  const visibleFilters = useMemo(\n    () => filters.filter((f) => visibleColumns.find((c) => c.id === f.columnId)),\n    [filters, visibleColumns],\n  );\n\n  const column = property ? getColumn(visibleColumns, property) : undefined;\n  const filter = property ? visibleFilters.find((f) => f.columnId === property) : undefined;\n\n  useEffect(() => {\n    if (!property) {\n      return;\n    }\n    inputRef.current?.focus();\n  }, [property]);\n\n  useEffect(() => {\n    if (open) {\n      return;\n    }\n\n    const timeoutId = setTimeout(() => setValue(\"\"), 150);\n    return () => clearTimeout(timeoutId);\n  }, [open]);\n\n  const content = useMemo(\n    () =>\n      property && column && column.type !== \"boolean\" ? (\n        <div className=\"flex flex-col\">\n          <div className=\"flex h-9 items-center gap-2 border-b px-2\">\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              onClick={() => setProperty(undefined)}\n              className=\"h-6 w-6 p-0\"\n            >\n              <ArrowLeftIcon className=\"size-4\" />\n              <span className=\"sr-only\">Back</span>\n            </Button>\n            <div className=\"flex items-center gap-1.5 text-sm\">\n              {column.icon && renderColumnIcon(column.icon, \"size-4 stroke-[2.25px]\")}\n              <span className=\"font-medium\">{column.displayName}</span>\n            </div>\n          </div>\n          <FilterValueController\n            binding={bindFilter(column, filter)}\n            actions={actions}\n            strategy={strategy}\n          />\n        </div>\n      ) : (\n        <Command\n          loop\n          filter={(itemValue, search, keywords) => {\n            const extendValue = `${itemValue} ${keywords?.join(\" \")}`;\n            return extendValue.toLowerCase().includes(search.toLowerCase()) ? 1 : 0;\n          }}\n        >\n          <CommandInput\n            value={value}\n            onValueChange={setValue}\n            ref={inputRef}\n            placeholder=\"Search or ask AI\"\n          />\n          <CommandList className=\"max-h-fit\">\n            <CommandGroup>\n              <CommandItem\n                value={value}\n                disabled={!onAIFilterSubmit || aiGenerating}\n                onSelect={() => {\n                  const prompt = value.trim();\n                  if (!prompt || !onAIFilterSubmit) {\n                    return;\n                  }\n\n                  onAIFilterSubmit(prompt);\n                  setOpen(false);\n                  setTimeout(() => {\n                    setValue(\"\");\n                    setProperty(undefined);\n                  }, 100);\n                }}\n              >\n                <div className=\"flex w-full items-center justify-between gap-2\">\n                  <div className=\"flex items-center gap-2\">\n                    <SparklesIcon className=\"size-4\" />\n                    <span>Ask AI</span>\n                  </div>\n                  <span className=\"text-(--muted-foreground) truncate text-xs\">\n                    {value.trim().length > 0 ? `\"${value.trim()}\"` : \"Type a prompt\"}\n                  </span>\n                </div>\n              </CommandItem>\n              {visibleColumns.map((visibleColumn) => (\n                <FilterableColumn\n                  key={visibleColumn.id}\n                  column={visibleColumn}\n                  setProperty={(id) => {\n                    setValue(\"\");\n                    setProperty(id);\n                  }}\n                  actions={actions}\n                  filters={visibleFilters}\n                />\n              ))}\n              <QuickSearchFilters\n                search={value}\n                filters={visibleFilters}\n                columns={visibleColumns}\n                actions={actions}\n              />\n            </CommandGroup>\n          </CommandList>\n        </Command>\n      ),\n    [\n      aiGenerating,\n      property,\n      column,\n      filter,\n      visibleFilters,\n      visibleColumns,\n      actions,\n      value,\n      strategy,\n      onAIFilterSubmit,\n    ],\n  );\n\n  return (\n    <Popover\n      open={open}\n      onOpenChange={(next) => {\n        setOpen(next);\n        if (!next) {\n          setTimeout(() => setProperty(undefined), 100);\n        }\n      }}\n    >\n      <PopoverTrigger\n        aria-label=\"Add filter\"\n        render={<Button variant=\"ghost\" size=\"icon\" className=\"size-7 p-0\" />}\n      >\n        <ListFilterIcon className=\"size-4\" />\n      </PopoverTrigger>\n      <PopoverContent align=\"start\" side=\"bottom\" className=\"w-fit origin-(--transform-origin) p-0\">\n        {content}\n      </PopoverContent>\n    </Popover>\n  );\n};\n","path":"/_components/filter-selector.tsx","target":"uicapsule/filter-bar/_components/filter-selector.tsx","type":"registry:file"},{"content":"import { isValidElement } from \"react\";\nimport type { Column, ColumnDataType } from \"../filter-package\";\n\ninterface FilterSubjectProps<TType extends ColumnDataType> {\n  column: Column<TType>;\n  entityName?: string;\n}\n\nexport const FilterSubject = <TType extends ColumnDataType>({\n  column,\n  entityName,\n}: FilterSubjectProps<TType>) => {\n  const subject = column.type === \"boolean\" ? entityName : column.displayName;\n\n  const { icon: Icon } = column;\n\n  return (\n    <span className=\"flex items-center gap-1 px-2 font-medium whitespace-nowrap select-none\">\n      {Icon &&\n        (isValidElement(Icon)\n          ? Icon\n          : (() => {\n              const IconComp = Icon;\n              return <IconComp className=\"text-(--primary) size-4 stroke-[2.25px]\" />;\n            })())}\n\n      <span>{subject}</span>\n    </span>\n  );\n};\n","path":"/_components/filter-subject.tsx","target":"uicapsule/filter-bar/_components/filter-subject.tsx","type":"registry:file"},{"content":"import {\n  Button,\n  Calendar,\n  Checkbox,\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandSeparator,\n  Input,\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n  Slider,\n  Tabs,\n  TabsContent,\n  TabsList,\n  TabsTrigger,\n} from \"./ui\";\nimport {\n  Fragment,\n  cloneElement,\n  isValidElement,\n  memo,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n} from \"react\";\nimport type {\n  ChangeEvent,\n  ElementType as ReactElementType,\n  InputHTMLAttributes,\n  Key,\n  ReactElement,\n} from \"react\";\nimport type { DateRange } from \"react-day-picker\";\nimport { cn } from \"cn\";\nimport { format, isEqual } from \"date-fns\";\nimport { Ellipsis } from \"lucide-react\";\n\nimport type {\n  Column,\n  FilterBinding,\n  ColumnDataType,\n  ColumnOptionExtended,\n  DataTableFilterActions,\n  FilterModel,\n  FilterStrategy,\n} from \"../filter-package\";\nimport { createNumberRange, numberFilterOperators } from \"../filter-package\";\nimport { useDebouncedCallback } from \"./use-debounced-callback\";\n\ninterface IconProps {\n  className?: string;\n  key?: Key;\n}\ntype IconLike = ReactElement | ReactElementType;\n\nconst renderIcon = (icon: IconLike, props: IconProps = {}) => {\n  if (isValidElement(icon)) {\n    // oxlint-disable-next-line react/no-clone-element -- applies the caller's className and key to an element the config supplied; wrapping it would change the DOM\n    return cloneElement(icon, props);\n  }\n  const IconComp = icon;\n  const { key, ...iconProps } = props;\n  return <IconComp key={key} {...iconProps} />;\n};\n\nexport const DebouncedInput = ({\n  value: initialValue,\n  onChange,\n  debounceMs = 500,\n  onBlur,\n  onKeyDown,\n  ...props\n}: {\n  value: string | number;\n  onChange: (value: string | number) => void;\n  debounceMs?: number;\n} & Omit<InputHTMLAttributes<HTMLInputElement>, \"onChange\">) => {\n  const [value, setValue] = useState(initialValue);\n  const [prevInitialValue, setPrevInitialValue] = useState(initialValue);\n\n  if (prevInitialValue !== initialValue) {\n    setPrevInitialValue(initialValue);\n    setValue(initialValue);\n  }\n\n  const { schedule: debouncedOnChange, cancel, flush } = useDebouncedCallback(onChange, debounceMs);\n  useEffect(cancel, [initialValue, cancel]);\n\n  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n    const newValue = e.target.value;\n    setValue(newValue);\n    debouncedOnChange(newValue);\n  };\n\n  return (\n    <Input\n      {...props}\n      value={value}\n      onChange={handleChange}\n      onBlur={(event) => {\n        flush();\n        onBlur?.(event);\n      }}\n      onKeyDown={(event) => {\n        onKeyDown?.(event);\n        if (!event.defaultPrevented && (event.key === \"Enter\" || event.key === \"Escape\")) {\n          flush();\n        }\n      }}\n    />\n  );\n};\n\ninterface FilterValueProps {\n  binding: FilterBinding;\n  actions: DataTableFilterActions;\n  strategy: FilterStrategy;\n  entityName?: string;\n}\n\ninterface FilterValueDisplayProps<K extends ColumnDataType> {\n  filter: FilterModel<K>;\n  column: Column<K>;\n  actions: DataTableFilterActions;\n  entityName?: string;\n}\n\nexport const FilterValueOptionDisplay = ({ filter, column }: FilterValueDisplayProps<\"option\">) => {\n  const { options } = column;\n  const selected = options.filter((o) => filter?.values.includes(o.value));\n\n  const [only] = selected;\n  if (selected.length === 1 && only) {\n    const { label, icon: Icon } = only;\n    const hasIcon = !!Icon;\n    return (\n      <span className=\"inline-flex items-center gap-1\">\n        {hasIcon && renderIcon(Icon, { className: \"text-(--primary) size-4\" })}\n        <span>{label}</span>\n      </span>\n    );\n  }\n  const name = column.displayName.toLowerCase();\n  const pluralName = name.endsWith(\"s\") ? `${name}es` : `${name}s`;\n\n  const hasOptionIcons = !options?.some((o) => !o.icon);\n\n  return (\n    <div className=\"inline-flex items-center gap-0.5\">\n      {hasOptionIcons &&\n        selected.slice(0, 3).map(({ value, icon }) => {\n          const Icon = icon;\n          if (!Icon) {\n            return null;\n          }\n          return renderIcon(Icon, { className: \"size-4\", key: value });\n        })}\n      <span className={cn(hasOptionIcons && \"ml-1.5\")}>\n        {selected.length} {pluralName}\n      </span>\n    </div>\n  );\n};\n\nexport const FilterValueMultiOptionDisplay = ({\n  filter,\n  column,\n}: FilterValueDisplayProps<\"multiOption\">) => {\n  const { options } = column;\n  const selected = options.filter((o) => filter.values.includes(o.value));\n\n  const [only] = selected;\n  if (selected.length === 1 && only) {\n    const { label, icon: Icon } = only;\n    const hasIcon = !!Icon;\n    return (\n      <span className=\"inline-flex items-center gap-1.5\">\n        {hasIcon && renderIcon(Icon, { className: \"text-(--primary) size-4\" })}\n\n        <span>{label}</span>\n      </span>\n    );\n  }\n\n  const name = column.displayName.toLowerCase();\n\n  const hasOptionIcons = !options?.some((o) => !o.icon);\n\n  return (\n    <div className=\"inline-flex items-center gap-1.5\">\n      {hasOptionIcons && (\n        <div key=\"icons\" className=\"inline-flex items-center gap-0.5\">\n          {selected.slice(0, 3).map(({ value, icon }) => {\n            const Icon = icon;\n            if (!Icon) {\n              return null;\n            }\n            return isValidElement(Icon) ? (\n              <Fragment key={value}>{Icon}</Fragment>\n            ) : (\n              renderIcon(Icon, { className: \"size-4\", key: value })\n            );\n          })}\n        </div>\n      )}\n      <span>\n        {selected.length} {name}\n      </span>\n    </div>\n  );\n};\n\nconst formatDateRange = (start: Date, end: Date) => {\n  const sameMonth = start.getMonth() === end.getMonth();\n  const sameYear = start.getFullYear() === end.getFullYear();\n\n  if (sameMonth && sameYear) {\n    return `${format(start, \"MMM d\")} - ${format(end, \"d, yyyy\")}`;\n  }\n\n  if (sameYear) {\n    return `${format(start, \"MMM d\")} - ${format(end, \"MMM d, yyyy\")}`;\n  }\n\n  return `${format(start, \"MMM d, yyyy\")} - ${format(end, \"MMM d, yyyy\")}`;\n};\n\nexport const FilterValueDateDisplay = ({ filter }: FilterValueDisplayProps<\"date\">) => {\n  if (!filter) {\n    return null;\n  }\n  if (filter.values.length === 0) {\n    return <Ellipsis className=\"size-4\" />;\n  }\n  const [first, second] = filter.values;\n  if (filter.values.length === 1 && first) {\n    const formattedDateStr = format(first, \"MMM d, yyyy\");\n\n    return <span>{formattedDateStr}</span>;\n  }\n  if (filter.values.length === 2 && first && second) {\n    const formattedRangeStr = formatDateRange(first, second);\n\n    return <span>{formattedRangeStr}</span>;\n  }\n\n  return null;\n};\n\nexport const FilterValueTextDisplay = ({ filter }: FilterValueDisplayProps<\"text\">) => {\n  if (!filter) {\n    return null;\n  }\n  if (filter.values.length === 0 || (filter.values[0] && filter.values[0].trim() === \"\")) {\n    return <Ellipsis className=\"size-4\" />;\n  }\n\n  const [value] = filter.values;\n\n  return <span>{value}</span>;\n};\n\nexport const FilterValueNumberDisplay = ({ filter }: FilterValueDisplayProps<\"number\">) => {\n  if (!filter || !filter.values || filter.values.length === 0) {\n    return null;\n  }\n\n  if (filter.operator === \"is between\" || filter.operator === \"is not between\") {\n    const [minValue, maxValue] = filter.values;\n\n    return (\n      <span className=\"tracking-tight tabular-nums\">\n        {minValue} and {maxValue}\n      </span>\n    );\n  }\n\n  const [value] = filter.values;\n  return <span className=\"tracking-tight tabular-nums\">{value}</span>;\n};\n\nexport const FilterValueBooleanDisplay = ({\n  filter,\n  column,\n}: FilterValueDisplayProps<\"boolean\">) => {\n  if (!filter || filter.values.length === 0) {\n    return null;\n  }\n  return <span>{column.toggledStateName}</span>;\n};\n\nexport const FilterValueDisplay = ({\n  binding,\n  actions,\n  entityName,\n}: Omit<FilterValueProps, \"strategy\">) => {\n  switch (binding.type) {\n    case \"option\": {\n      return (\n        binding.filter && (\n          <FilterValueOptionDisplay\n            filter={binding.filter}\n            column={binding.column}\n            actions={actions}\n            entityName={entityName}\n          />\n        )\n      );\n    }\n    case \"multiOption\": {\n      return (\n        binding.filter && (\n          <FilterValueMultiOptionDisplay\n            filter={binding.filter}\n            column={binding.column}\n            actions={actions}\n            entityName={entityName}\n          />\n        )\n      );\n    }\n    case \"date\": {\n      return (\n        binding.filter && (\n          <FilterValueDateDisplay\n            filter={binding.filter}\n            column={binding.column}\n            actions={actions}\n            entityName={entityName}\n          />\n        )\n      );\n    }\n    case \"text\": {\n      return (\n        binding.filter && (\n          <FilterValueTextDisplay\n            filter={binding.filter}\n            column={binding.column}\n            actions={actions}\n            entityName={entityName}\n          />\n        )\n      );\n    }\n    case \"number\": {\n      return (\n        binding.filter && (\n          <FilterValueNumberDisplay\n            filter={binding.filter}\n            column={binding.column}\n            actions={actions}\n            entityName={entityName}\n          />\n        )\n      );\n    }\n    case \"boolean\": {\n      return (\n        binding.filter && (\n          <FilterValueBooleanDisplay\n            filter={binding.filter}\n            column={binding.column}\n            actions={actions}\n            entityName={entityName}\n          />\n        )\n      );\n    }\n    default: {\n      return null;\n    }\n  }\n};\n\ninterface FilterValueControllerProps<K extends ColumnDataType> {\n  filter?: FilterModel<K>;\n  column: Column<K>;\n  actions: DataTableFilterActions;\n  strategy: FilterStrategy;\n}\n\ninterface OptionItemProps {\n  option: ColumnOptionExtended;\n  onToggle: (value: string, checked: boolean) => void;\n}\n\nconst OptionItemBase = ({ option, onToggle }: OptionItemProps) => {\n  const { value, label, icon: Icon, selected } = option;\n  const handleSelect = useCallback(() => {\n    onToggle(value, !selected);\n  }, [onToggle, value, selected]);\n\n  return (\n    <CommandItem\n      key={value}\n      onSelect={handleSelect}\n      className=\"group flex items-center justify-between gap-4\"\n    >\n      <div className=\"flex min-w-0 flex-1 items-center gap-1.5\">\n        <Checkbox\n          checked={selected}\n          className=\"dark:border-(--ring) mr-1 shrink-0 opacity-0 group-data-[selected=true]:opacity-100 data-checked:opacity-100\"\n        />\n        <div className=\"shrink-0\">\n          {Icon && renderIcon(Icon, { className: \"text-(--primary) size-4\" })}\n        </div>\n        <span className=\"overflow-x-hidden overflow-ellipsis whitespace-nowrap\">{label}</span>\n      </div>\n    </CommandItem>\n  );\n};\n\nconst OptionItem = memo(OptionItemBase);\n\nexport const FilterValueOptionController = ({\n  filter,\n  column,\n  actions,\n}: FilterValueControllerProps<\"option\" | \"multiOption\">) => {\n  // oxlint-disable-next-line react/hook-use-state -- a one-time snapshot of the selection when the controller opens, so items stay in their group while toggling; there is deliberately no setter\n  const [initialSelectedValues] = useState(() => new Set(filter?.values || []));\n\n  const { selectedOptions, unselectedOptions } = useMemo(() => {\n    const allOptions = column.options.map((o) => {\n      const currentlySelected = filter?.values.includes(o.value) ?? false;\n      return {\n        count: o.count ?? 0,\n        icon: o.icon,\n        label: o.label,\n        selected: currentlySelected,\n        value: o.value,\n      };\n    });\n\n    const selected = allOptions.filter((o) => initialSelectedValues.has(o.value));\n    const unselected = allOptions.filter((o) => !initialSelectedValues.has(o.value));\n    return { selectedOptions: selected, unselectedOptions: unselected };\n  }, [column, filter?.values, initialSelectedValues]);\n\n  const handleToggle = useCallback(\n    (value: string, checked: boolean) => {\n      if (checked) {\n        actions.addFilterValue(column, [value]);\n      } else {\n        actions.removeFilterValue(column, [value]);\n      }\n    },\n    [actions, column],\n  );\n\n  return (\n    <Command className=\"max-w-[300px]\" loop>\n      <CommandInput autoFocus placeholder=\"search\" />\n      <CommandEmpty>No results</CommandEmpty>\n      <CommandList>\n        <CommandGroup className={cn(selectedOptions.length === 0 && \"hidden\")}>\n          {selectedOptions.map((option) => (\n            <OptionItem key={option.value} option={option} onToggle={handleToggle} />\n          ))}\n        </CommandGroup>\n        <CommandSeparator\n          className={cn(\n            (unselectedOptions.length === 0 || selectedOptions.length === 0) && \"hidden\",\n          )}\n        />\n        <CommandGroup className={cn(unselectedOptions.length === 0 && \"hidden\")}>\n          {unselectedOptions.map((option) => (\n            <OptionItem key={option.value} option={option} onToggle={handleToggle} />\n          ))}\n        </CommandGroup>\n      </CommandList>\n    </Command>\n  );\n};\n\nexport const FilterValueDateController = ({\n  filter,\n  column,\n  actions,\n}: FilterValueControllerProps<\"date\">) => {\n  const start = filter?.values[0];\n  const date: DateRange | undefined = start ? { from: start, to: filter?.values[1] } : undefined;\n\n  const changeDateRange = (value: DateRange | undefined) => {\n    const from = value?.from;\n    const to = from && value?.to && !isEqual(from, value.to) ? value.to : undefined;\n\n    let newValues: Date[] = [];\n    if (from && to) {\n      newValues = [from, to];\n    } else if (from) {\n      newValues = [from];\n    }\n\n    actions.setFilterValue({ columnId: column.id, type: column.type, values: newValues });\n  };\n\n  return (\n    <Command>\n      <CommandList className=\"max-h-fit\">\n        <CommandGroup>\n          <div>\n            <Calendar\n              mode=\"range\"\n              defaultMonth={date?.from}\n              selected={date}\n              onSelect={changeDateRange}\n              numberOfMonths={1}\n            />\n          </div>\n        </CommandGroup>\n      </CommandList>\n    </Command>\n  );\n};\n\nexport const FilterValueTextController = ({\n  filter,\n  column,\n  actions,\n}: FilterValueControllerProps<\"text\">) => {\n  const changeText = (value: string | number) => {\n    actions.setFilterValue({ columnId: column.id, type: \"text\", values: [String(value)] });\n  };\n\n  return (\n    <Command>\n      <CommandList className=\"max-h-fit\">\n        <CommandGroup>\n          <CommandItem>\n            <DebouncedInput\n              placeholder=\"search\"\n              autoFocus\n              value={filter?.values[0] ?? \"\"}\n              onChange={changeText}\n            />\n          </CommandItem>\n        </CommandGroup>\n      </CommandList>\n    </Command>\n  );\n};\n\nexport const FilterValueNumberController = ({\n  filter,\n  column,\n  actions,\n}: FilterValueControllerProps<\"number\">) => {\n  const minMax = useMemo<[number, number]>(\n    () => [column.min, column.max],\n    [column.min, column.max],\n  );\n  const [sliderMin, sliderMax] = [minMax ? minMax[0] : 0, minMax ? minMax[1] : 0];\n\n  const [values, setValues] = useState(filter?.values ?? [0, 0]);\n  const [previousFilterValues, setPreviousFilterValues] = useState(filter?.values);\n\n  if (previousFilterValues !== filter?.values) {\n    setPreviousFilterValues(filter?.values);\n    setValues(filter?.values ?? [0, 0]);\n  }\n\n  const isNumberRange = filter && numberFilterOperators[filter.operator].target === \"multiple\";\n\n  const setNumberFilterValue = useCallback(\n    (newValues: number[]) =>\n      actions.setFilterValue({ columnId: column.id, type: \"number\", values: newValues }),\n    [actions, column],\n  );\n  const {\n    schedule: setFilterValueDebounced,\n    cancel: cancelFilterValueUpdate,\n    flush: flushFilterValueUpdate,\n  } = useDebouncedCallback(setNumberFilterValue, 500);\n  useEffect(cancelFilterValueUpdate, [filter?.values, cancelFilterValueUpdate]);\n\n  const changeNumber = (value: number[]) => {\n    cancelFilterValueUpdate();\n    setValues(value);\n    setNumberFilterValue(value);\n  };\n\n  const changeSlider = (value: number | readonly number[]) => {\n    const nextValues = Array.isArray(value) ? [...value] : [value];\n    setValues(nextValues);\n    setFilterValueDebounced(nextValues);\n  };\n\n  const changeMinNumber = (value: number) => {\n    const newValues = createNumberRange([value, values[1] ?? 0]);\n    changeNumber(newValues);\n  };\n\n  const changeMaxNumber = (value: number) => {\n    const newValues = createNumberRange([values[0] ?? 0, value]);\n    changeNumber(newValues);\n  };\n\n  const changeType = useCallback(\n    (type: \"single\" | \"range\") => {\n      let newValues: number[] = [];\n      if (type === \"single\") {\n        newValues = [values[0] ?? 0];\n      } else if (minMax) {\n        const value = values[0] ?? 0;\n        newValues =\n          value - minMax[0] < minMax[1] - value\n            ? createNumberRange([value, minMax[1]])\n            : createNumberRange([minMax[0], value]);\n      } else {\n        newValues = createNumberRange([values[0] ?? 0, values[1] ?? 0]);\n      }\n\n      const newOperator = type === \"single\" ? \"is\" : \"is between\";\n\n      setValues(newValues);\n\n      // Cancel the old value before changing operators.\n      cancelFilterValueUpdate();\n\n      actions.setFilterOperator({ columnId: column.id, operator: newOperator, type: \"number\" });\n      actions.setFilterValue({ columnId: column.id, type: column.type, values: newValues });\n    },\n    [values, column, actions, minMax, cancelFilterValueUpdate],\n  );\n\n  return (\n    <Command\n      onBlur={flushFilterValueUpdate}\n      onKeyDown={(event) => {\n        if (event.key === \"Escape\") {\n          flushFilterValueUpdate();\n        }\n      }}\n    >\n      <CommandList className=\"w-[300px] px-2 py-2\">\n        <CommandGroup>\n          <div className=\"flex w-full flex-col\">\n            <Tabs\n              value={isNumberRange ? \"range\" : \"single\"}\n              onValueChange={(v) => changeType(v === \"range\" ? \"range\" : \"single\")}\n            >\n              <TabsList className=\"w-full *:text-xs\">\n                <TabsTrigger value=\"single\">single</TabsTrigger>\n                <TabsTrigger value=\"range\">range</TabsTrigger>\n              </TabsList>\n              <TabsContent value=\"single\" className=\"mt-4 flex flex-col gap-4\">\n                {minMax && (\n                  <Slider\n                    value={[values[0] ?? 0]}\n                    onValueChange={changeSlider}\n                    onValueCommitted={flushFilterValueUpdate}\n                    min={sliderMin}\n                    max={sliderMax}\n                    step={1}\n                    aria-orientation=\"horizontal\"\n                  />\n                )}\n                <div className=\"flex items-center gap-2\">\n                  <span className=\"text-xs font-medium\">value</span>\n                  <DebouncedInput\n                    id=\"single\"\n                    type=\"number\"\n                    value={(values[0] ?? 0).toString()}\n                    onChange={(v) => changeNumber([Number(v)])}\n                  />\n                </div>\n              </TabsContent>\n              <TabsContent value=\"range\" className=\"mt-4 flex flex-col gap-4\">\n                {minMax && (\n                  <Slider\n                    value={values}\n                    onValueChange={changeSlider}\n                    onValueCommitted={flushFilterValueUpdate}\n                    min={sliderMin}\n                    max={sliderMax}\n                    step={1}\n                    aria-orientation=\"horizontal\"\n                  />\n                )}\n                <div className=\"grid grid-cols-2 gap-4\">\n                  <div className=\"flex items-center gap-2\">\n                    <span className=\"text-xs font-medium\">min</span>\n                    <DebouncedInput\n                      type=\"number\"\n                      value={values[0] ?? 0}\n                      onChange={(v) => changeMinNumber(Number(v))}\n                    />\n                  </div>\n                  <div className=\"flex items-center gap-2\">\n                    <span className=\"text-xs font-medium\">max</span>\n                    <DebouncedInput\n                      type=\"number\"\n                      value={values[1] ?? 0}\n                      onChange={(v) => changeMaxNumber(Number(v))}\n                    />\n                  </div>\n                </div>\n              </TabsContent>\n            </Tabs>\n          </div>\n        </CommandGroup>\n      </CommandList>\n    </Command>\n  );\n};\n\nexport const FilterValueController = ({\n  binding,\n  actions,\n  strategy,\n}: Omit<FilterValueProps, \"entityName\">) => {\n  switch (binding.type) {\n    case \"option\": {\n      return (\n        <FilterValueOptionController\n          filter={binding.filter}\n          column={binding.column}\n          actions={actions}\n          strategy={strategy}\n        />\n      );\n    }\n    case \"multiOption\": {\n      return (\n        <FilterValueOptionController\n          filter={binding.filter}\n          column={binding.column}\n          actions={actions}\n          strategy={strategy}\n        />\n      );\n    }\n    case \"date\": {\n      return (\n        <FilterValueDateController\n          filter={binding.filter}\n          column={binding.column}\n          actions={actions}\n          strategy={strategy}\n        />\n      );\n    }\n    case \"text\": {\n      return (\n        <FilterValueTextController\n          filter={binding.filter}\n          column={binding.column}\n          actions={actions}\n          strategy={strategy}\n        />\n      );\n    }\n    case \"number\": {\n      return (\n        <FilterValueNumberController\n          filter={binding.filter}\n          column={binding.column}\n          actions={actions}\n          strategy={strategy}\n        />\n      );\n    }\n    case \"boolean\": {\n      return null;\n    }\n    default: {\n      return null;\n    }\n  }\n};\n\nexport const FilterValue = ({ binding, actions, strategy, entityName }: FilterValueProps) => {\n  const [open, setOpen] = useState(false);\n  return (\n    <Popover\n      open={open}\n      onOpenChange={(next) => {\n        if (binding.type !== \"boolean\") {\n          setOpen(next);\n        }\n      }}\n    >\n      <PopoverTrigger\n        render={\n          <Button\n            variant=\"ghost\"\n            className={cn(\n              \"m-0 h-full w-fit rounded-none p-0 px-2 text-xs whitespace-nowrap\",\n              binding.type === \"boolean\" && \"hover:bg-inherit\",\n            )}\n          />\n        }\n      >\n        <FilterValueDisplay binding={binding} actions={actions} entityName={entityName} />\n      </PopoverTrigger>\n      <PopoverContent align=\"start\" side=\"bottom\" className=\"w-fit origin-(--transform-origin) p-0\">\n        <FilterValueController binding={binding} actions={actions} strategy={strategy} />\n      </PopoverContent>\n    </Popover>\n  );\n};\n","path":"/_components/filter-value.tsx","target":"uicapsule/filter-bar/_components/filter-value.tsx","type":"registry:file"},{"content":"\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { Button as ButtonPrimitive } from \"@base-ui/react/button\";\nimport { Checkbox as CheckboxPrimitive } from \"@base-ui/react/checkbox\";\nimport { Popover as PopoverPrimitive } from \"@base-ui/react/popover\";\nimport { Slider as SliderPrimitive } from \"@base-ui/react/slider\";\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport { Separator as SeparatorPrimitive } from \"@base-ui/react/separator\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { cn } from \"cn\";\nimport { CheckIcon, SearchIcon } from \"lucide-react\";\nimport { DayPicker } from \"react-day-picker\";\nimport \"react-day-picker/style.css\";\nimport \"../filter-bar.css\";\n\ntype Styled<Props> = Omit<Props, \"className\"> & { className?: string };\n\nexport const Button = ({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  ...props\n}: Styled<ButtonPrimitive.Props> & {\n  variant?: \"default\" | \"outline\" | \"ghost\";\n  size?: \"default\" | \"sm\" | \"icon\";\n}) => (\n  <ButtonPrimitive\n    className={cn(\n      \"inline-flex shrink-0 items-center justify-center gap-2 rounded-full text-sm font-medium outline-none transition-colors focus-visible:ring-2 focus-visible:ring-(--ring) disabled:pointer-events-none disabled:opacity-50 [&_svg]:shrink-0\",\n      variant === \"default\" && \"bg-(--primary) text-(--primary-foreground) hover:bg-(--primary)/80\",\n      variant === \"outline\" && \"border border-(--border) bg-(--background) hover:bg-(--muted)\",\n      variant === \"ghost\" && \"hover:bg-(--muted) aria-expanded:bg-(--muted)\",\n      size === \"default\" && \"h-9 px-4 py-2\",\n      size === \"sm\" && \"h-8 px-2.5\",\n      size === \"icon\" && \"size-9\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport const Input = ({ className, ...props }: ComponentProps<\"input\">) => (\n  <input\n    className={cn(\n      \"h-9 w-full min-w-0 rounded-md border border-(--input) bg-transparent px-2.5 py-1 text-sm outline-none focus-visible:ring-2 focus-visible:ring-(--ring) disabled:opacity-50\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport const Popover = PopoverPrimitive.Root;\nexport const PopoverTrigger = PopoverPrimitive.Trigger;\n\nexport const PopoverContent = ({\n  className,\n  align = \"center\",\n  side = \"bottom\",\n  ...props\n}: Styled<PopoverPrimitive.Popup.Props> &\n  Pick<PopoverPrimitive.Positioner.Props, \"align\" | \"side\">) => (\n  <PopoverPrimitive.Portal>\n    <PopoverPrimitive.Positioner\n      align={align}\n      side={side}\n      sideOffset={4}\n      className=\"filter-bar isolate z-50\"\n    >\n      <PopoverPrimitive.Popup\n        className={cn(\n          \"flex w-72 origin-(--transform-origin) flex-col gap-4 rounded-md bg-(--popover) p-4 text-sm text-(--popover-foreground) shadow-md ring-1 ring-(--foreground)/10 outline-none\",\n          className,\n        )}\n        {...props}\n      />\n    </PopoverPrimitive.Positioner>\n  </PopoverPrimitive.Portal>\n);\n\nexport const Checkbox = ({ className, ...props }: Styled<CheckboxPrimitive.Root.Props>) => (\n  <CheckboxPrimitive.Root\n    className={cn(\n      \"relative size-[15px] shrink-0 rounded-[5px] border-[1.5px] border-(--input) outline-none focus-visible:ring-2 focus-visible:ring-(--ring) data-checked:border-transparent\",\n      className,\n    )}\n    {...props}\n  >\n    <CheckboxPrimitive.Indicator className=\"absolute inset-0 grid place-content-center text-(--primary)\">\n      <CheckIcon className=\"size-4\" strokeWidth={2.5} />\n    </CheckboxPrimitive.Indicator>\n  </CheckboxPrimitive.Root>\n);\n\nexport const Separator = ({\n  className,\n  orientation = \"horizontal\",\n  ...props\n}: Styled<SeparatorPrimitive.Props>) => (\n  <SeparatorPrimitive\n    orientation={orientation}\n    className={cn(\n      \"shrink-0 bg-(--border) data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport const Command = ({ className, ...props }: ComponentProps<typeof CommandPrimitive>) => (\n  <CommandPrimitive\n    className={cn(\n      \"flex size-full flex-col overflow-hidden rounded-xl bg-(--popover) p-1 text-(--popover-foreground)\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport const CommandInput = ({\n  className,\n  ...props\n}: ComponentProps<typeof CommandPrimitive.Input>) => (\n  <div className=\"relative m-1 flex h-8 items-center gap-2 rounded-lg border border-(--input)/30 bg-(--input)/30 px-2\">\n    <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n    <CommandPrimitive.Input\n      aria-label=\"Search filters\"\n      className={cn(\"w-full bg-transparent text-sm outline-none disabled:opacity-50\", className)}\n      {...props}\n    />\n  </div>\n);\n\nexport const CommandList = ({\n  className,\n  ...props\n}: ComponentProps<typeof CommandPrimitive.List>) => (\n  <CommandPrimitive.List\n    className={cn(\"max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none\", className)}\n    {...props}\n  />\n);\n\nexport const CommandEmpty = ({\n  className,\n  ...props\n}: ComponentProps<typeof CommandPrimitive.Empty>) => (\n  <CommandPrimitive.Empty className={cn(\"py-6 text-center text-sm\", className)} {...props} />\n);\n\nexport const CommandGroup = ({\n  className,\n  ...props\n}: ComponentProps<typeof CommandPrimitive.Group>) => (\n  <CommandPrimitive.Group\n    className={cn(\n      \"overflow-hidden p-1 text-(--foreground) **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:text-(--muted-foreground)\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport const CommandItem = ({\n  className,\n  ...props\n}: ComponentProps<typeof CommandPrimitive.Item>) => (\n  <CommandPrimitive.Item\n    className={cn(\n      \"group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-(--muted) [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport const CommandSeparator = ({\n  className,\n  ...props\n}: ComponentProps<typeof CommandPrimitive.Separator>) => (\n  <CommandPrimitive.Separator className={cn(\"-mx-1 h-px bg-(--border)\", className)} {...props} />\n);\n\nexport const Slider = ({ className, value, ...props }: Styled<SliderPrimitive.Root.Props>) => {\n  const count = Array.isArray(value) ? value.length : 1;\n  return (\n    <SliderPrimitive.Root\n      value={value}\n      className={cn(\"relative flex w-full touch-none items-center select-none\", className)}\n      {...props}\n    >\n      <SliderPrimitive.Control className=\"relative flex w-full items-center\">\n        <SliderPrimitive.Track className=\"relative h-1.5 w-full rounded-full bg-(--muted)\">\n          <SliderPrimitive.Indicator className=\"rounded-full bg-(--primary)\" />\n        </SliderPrimitive.Track>\n        {Array.from({ length: count }, (_, index) => (\n          <SliderPrimitive.Thumb\n            key={index}\n            index={index}\n            className=\"block size-4 rounded-full border border-(--primary) bg-(--background) shadow-sm outline-none focus-visible:ring-4 focus-visible:ring-(--primary)/30\"\n          />\n        ))}\n      </SliderPrimitive.Control>\n    </SliderPrimitive.Root>\n  );\n};\n\nexport const Tabs = ({ className, ...props }: Styled<TabsPrimitive.Root.Props>) => (\n  <TabsPrimitive.Root className={cn(\"flex flex-col gap-2\", className)} {...props} />\n);\n\nexport const TabsList = ({ className, ...props }: Styled<TabsPrimitive.List.Props>) => (\n  <TabsPrimitive.List\n    className={cn(\n      \"inline-flex h-9 w-fit items-center justify-center rounded-lg bg-(--muted) p-[3px] text-(--muted-foreground)\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport const TabsTrigger = ({ className, ...props }: Styled<TabsPrimitive.Tab.Props>) => (\n  <TabsPrimitive.Tab\n    className={cn(\n      \"inline-flex h-full flex-1 items-center justify-center rounded-md px-2 py-1 text-sm outline-none focus-visible:ring-2 focus-visible:ring-(--ring) data-active:bg-(--background) data-active:text-(--foreground)\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport const TabsContent = ({ className, ...props }: Styled<TabsPrimitive.Panel.Props>) => (\n  <TabsPrimitive.Panel className={cn(\"flex-1 outline-none\", className)} {...props} />\n);\n\nexport const Calendar = (props: ComponentProps<typeof DayPicker>) => (\n  <DayPicker showOutsideDays className=\"filter-bar-calendar p-3\" {...props} />\n);\n","path":"/_components/ui.tsx","target":"uicapsule/filter-bar/_components/ui.tsx","type":"registry:file"},{"content":"import { useCallback } from \"react\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { StaticChatTransport } from \"@loremllm/transport\";\n\nimport type { Column, DataTableFilterActions } from \"../filter-package\";\nimport type { UIMessage } from \"@ai-sdk/react\";\n\ntype FilterUIMessage = UIMessage<\n  unknown,\n  {\n    setFilter: {\n      columnId: string;\n      values: string[];\n    };\n  }\n>;\n\nconst transport = new StaticChatTransport<FilterUIMessage>({\n  chunkDelayMs: [600, 1400],\n  async *mockResponse() {\n    yield {\n      data: {\n        columnId: \"skills\",\n        values: [\"javascript\", \"typescript\", \"react\", \"nodejs\"],\n      },\n      type: \"data-setFilter\",\n    };\n\n    yield {\n      data: {\n        columnId: \"department\",\n        values: [\"design\"],\n      },\n      type: \"data-setFilter\",\n    };\n  },\n});\n\ninterface UseAiFilterSimulationParams {\n  columns: Column[];\n  actions: DataTableFilterActions;\n}\n\nexport const useAiFilterSimulation = ({ columns, actions }: UseAiFilterSimulationParams) => {\n  const { sendMessage, status } = useChat<FilterUIMessage>({\n    onData: (dataPart) => {\n      if (dataPart.type === \"data-setFilter\") {\n        const { columnId, values } = dataPart.data;\n        const column = columns.find((col) => col.id === columnId);\n        if (column?.type === \"option\" || column?.type === \"multiOption\") {\n          actions.setFilterValue({ columnId, type: column.type, values });\n        }\n      }\n    },\n    transport,\n  });\n\n  const handleAiFilterSubmit = useCallback(\n    (prompt: string) => {\n      actions.removeAllFilters();\n      void sendMessage({ text: prompt });\n    },\n    [sendMessage, actions],\n  );\n\n  const aiGenerating = status === \"streaming\" || status === \"submitted\";\n\n  return {\n    aiGenerating,\n    handleAiFilterSubmit,\n  };\n};\n","path":"/_components/use-ai-filter-simulation.ts","target":"uicapsule/filter-bar/_components/use-ai-filter-simulation.ts","type":"registry:file"},{"content":"import { useCallback, useEffect, useRef } from \"react\";\n\nexport const useDebouncedCallback = <TArgs extends unknown[]>(\n  callback: (...args: TArgs) => void,\n  delay: number,\n) => {\n  const callbackRef = useRef(callback);\n  const pendingRef = useRef<{ timeout: ReturnType<typeof setTimeout>; args: TArgs } | null>(null);\n\n  useEffect(() => {\n    callbackRef.current = callback;\n  }, [callback]);\n\n  const cancel = useCallback(() => {\n    if (pendingRef.current !== null) {\n      clearTimeout(pendingRef.current.timeout);\n    }\n    pendingRef.current = null;\n  }, []);\n\n  useEffect(() => cancel, [cancel]);\n\n  const flush = useCallback(() => {\n    const pending = pendingRef.current;\n    if (!pending) {\n      return;\n    }\n    cancel();\n    callbackRef.current(...pending.args);\n  }, [cancel]);\n\n  const schedule = useCallback(\n    (...args: TArgs) => {\n      cancel();\n      pendingRef.current = { args, timeout: setTimeout(flush, delay) };\n    },\n    [cancel, delay, flush],\n  );\n\n  return { cancel, flush, schedule };\n};\n","path":"/_components/use-debounced-callback.ts","target":"uicapsule/filter-bar/_components/use-debounced-callback.ts","type":"registry:file"},{"content":".filter-bar {\n  --background: #fff;\n  --foreground: #18181b;\n  --primary: #f66b2c;\n  --primary-foreground: #fff;\n  --secondary: #f4f4f5;\n  --secondary-foreground: #27272a;\n  --muted: #f4f4f5;\n  --muted-foreground: #71717a;\n  --accent: #f4f4f5;\n  --accent-foreground: #27272a;\n  --popover: #fff;\n  --popover-foreground: #18181b;\n  --border: #e4e4e7;\n  --input: #e4e4e7;\n  --ring: #a1a1aa;\n  --destructive: #dc2626;\n  color: var(--foreground);\n}\n\n.dark .filter-bar {\n  --background: #18181b;\n  --foreground: #fafafa;\n  --primary-foreground: #27272a;\n  --secondary: #27272a;\n  --secondary-foreground: #fafafa;\n  --muted: #27272a;\n  --muted-foreground: #a1a1aa;\n  --accent: #27272a;\n  --accent-foreground: #fafafa;\n  --popover: #27272a;\n  --popover-foreground: #fafafa;\n  --border: #ffffff1a;\n  --input: #ffffff26;\n  --ring: #71717a;\n  --destructive: #f87171;\n}\n\n.filter-bar-calendar {\n  --rdp-accent-color: var(--primary);\n  --rdp-accent-background-color: var(--accent);\n  --rdp-day-height: 32px;\n  --rdp-day-width: 32px;\n  --rdp-day_button-height: 32px;\n  --rdp-day_button-width: 32px;\n  font-size: 0.875rem;\n}\n\n@layer base {\n  :where(.filter-bar, .filter-bar *) {\n    border-color: var(--border);\n  }\n}\n","path":"/filter-bar.css","target":"uicapsule/filter-bar/filter-bar.css","type":"registry:file"},{"content":"import type { Column, ColumnConfig, ColumnOption, FilterStrategy } from \"../types\";\n\nconst isPresent = <T>(value: T | null | undefined): value is T =>\n  value !== null && value !== undefined;\n\nconst prepareOptions = (\n  values: (string | ColumnOption)[],\n  configured: ColumnOption[] | undefined,\n  strategy: FilterStrategy,\n) => {\n  if (strategy === \"server\" && !configured) {\n    throw new Error(\"Server filters require explicit column options\");\n  }\n  const entries = values.map((value) =>\n    // eslint-disable-next-line anti-slop/no-runtime-typeof -- Discriminates the typed string | ColumnOption config union.\n    typeof value === \"string\" ? { label: value, value } : value,\n  );\n  const unique = new Map<string, ColumnOption>();\n  for (const option of configured ?? entries) {\n    if (!unique.has(option.value)) {\n      unique.set(option.value, option);\n    }\n  }\n  const counts = new Map<string, number>();\n  for (const entry of entries) {\n    counts.set(entry.value, (counts.get(entry.value) ?? 0) + 1);\n  }\n  return {\n    options: Array.from(unique.values(), (option) => ({\n      ...option,\n      count: strategy === \"server\" ? option.count : (counts.get(option.value) ?? 0),\n    })),\n    values: entries.filter((entry) => unique.has(entry.value)).map((entry) => entry.value),\n  };\n};\n\nconst createColumn = <Row>(\n  data: Row[],\n  config: ColumnConfig<Row>,\n  strategy: FilterStrategy,\n): Column => {\n  const common = {\n    displayName: config.displayName,\n    hidden: config.hidden,\n    icon: config.icon,\n    id: config.id,\n  };\n  switch (config.type) {\n    case \"text\": {\n      return {\n        ...common,\n        type: \"text\",\n        values: data.map(config.accessor).filter(isPresent),\n      };\n    }\n    case \"date\": {\n      return {\n        ...common,\n        type: \"date\",\n        values: data.map(config.accessor).filter(isPresent),\n      };\n    }\n    case \"boolean\": {\n      return {\n        ...common,\n        toggledStateName: config.toggledStateName,\n        type: \"boolean\",\n        values: data.map(config.accessor).filter(isPresent),\n      };\n    }\n    case \"number\": {\n      const values = data.map(config.accessor).filter(isPresent).filter(Number.isFinite);\n      let min = values[0] ?? 0;\n      let max = min;\n      for (const value of values) {\n        min = Math.min(min, value);\n        max = Math.max(max, value);\n      }\n      return {\n        ...common,\n        max: config.max ?? max,\n        min: config.min ?? min,\n        type: \"number\",\n        values,\n      };\n    }\n    case \"option\": {\n      return {\n        ...common,\n        type: \"option\",\n        ...prepareOptions(data.map(config.accessor).filter(isPresent), config.options, strategy),\n      };\n    }\n    case \"multiOption\": {\n      return {\n        ...common,\n        type: \"multiOption\",\n        ...prepareOptions(\n          data.flatMap((row) => config.accessor(row) ?? []),\n          config.options,\n          strategy,\n        ),\n      };\n    }\n    default: {\n      throw new Error(`Unsupported column type: ${String(config satisfies never)}`);\n    }\n  }\n};\n\nexport const createColumns = <Row>(\n  data: Row[],\n  configs: readonly ColumnConfig<Row>[],\n  strategy: FilterStrategy,\n): Column[] => configs.map((config) => createColumn(data, config, strategy));\n","path":"/filter-package/core/columns/column-factory.ts","target":"uicapsule/filter-bar/filter-package/core/columns/column-factory.ts","type":"registry:file"},{"content":"import type {\n  FilterModel,\n  FiltersState,\n  FilterValueUpdate,\n  FilterOperatorUpdate,\n  Column,\n  OptionBasedColumnDataType,\n} from \"./types\";\nimport { DEFAULT_OPERATORS, determineNewOperator } from \"./operators\";\n\nconst replaceFilter = (filters: FiltersState, next: FilterModel): FiltersState => {\n  if (next.values.length === 0) {\n    return filters.filter((filter) => filter.columnId !== next.columnId);\n  }\n  return filters.some((filter) => filter.columnId === next.columnId)\n    ? filters.map((filter) => (filter.columnId === next.columnId ? next : filter))\n    : [...filters, next];\n};\n\nconst defaultOperator = <Operator>(\n  count: number,\n  operators: { single: Operator; multiple: Operator },\n) => (count > 1 ? operators.multiple : operators.single);\n\nexport const setFilterValue = (filters: FiltersState, update: FilterValueUpdate): FiltersState => {\n  const current = filters.find((filter) => filter.columnId === update.columnId);\n  const { columnId } = update;\n  switch (update.type) {\n    case \"text\": {\n      const values = [...new Set(update.values)].filter((value) => value.trim().length > 0);\n      return replaceFilter(filters, {\n        columnId,\n        operator: current?.type === \"text\" ? current.operator : \"contains\",\n        type: \"text\",\n        values,\n      });\n    }\n    case \"boolean\": {\n      const values = update.values.slice(0, 1);\n      return replaceFilter(filters, {\n        columnId,\n        operator: current?.type === \"boolean\" ? current.operator : \"is\",\n        type: \"boolean\",\n        values,\n      });\n    }\n    case \"number\": {\n      const values = update.values\n        .filter(Number.isFinite)\n        .slice(0, 2)\n        .toSorted((a, b) => a - b);\n      const operator =\n        current?.type === \"number\"\n          ? determineNewOperator(\"number\", current.values, values, current.operator)\n          : defaultOperator(values.length, DEFAULT_OPERATORS.number);\n      return replaceFilter(filters, { columnId, operator, type: \"number\", values });\n    }\n    case \"date\": {\n      const values = update.values\n        .filter((value) => Number.isFinite(value.getTime()))\n        .slice(0, 2)\n        .toSorted((a, b) => a.getTime() - b.getTime());\n      const operator =\n        current?.type === \"date\"\n          ? determineNewOperator(\"date\", current.values, values, current.operator)\n          : defaultOperator(values.length, DEFAULT_OPERATORS.date);\n      return replaceFilter(filters, { columnId, operator, type: \"date\", values });\n    }\n    case \"option\": {\n      const values = [...new Set(update.values)];\n      const operator =\n        current?.type === \"option\"\n          ? determineNewOperator(\"option\", current.values, values, current.operator)\n          : defaultOperator(values.length, DEFAULT_OPERATORS.option);\n      return replaceFilter(filters, { columnId, operator, type: \"option\", values });\n    }\n    case \"multiOption\": {\n      const values = [...new Set(update.values)];\n      const operator =\n        current?.type === \"multiOption\"\n          ? determineNewOperator(\"multiOption\", current.values, values, current.operator)\n          : defaultOperator(values.length, DEFAULT_OPERATORS.multiOption);\n      return replaceFilter(filters, { columnId, operator, type: \"multiOption\", values });\n    }\n    default: {\n      throw new Error(`Unsupported filter type: ${String(update satisfies never)}`);\n    }\n  }\n};\n\nexport const setFilterOperator = (\n  filters: FiltersState,\n  update: FilterOperatorUpdate,\n): FiltersState =>\n  filters.map((filter) => {\n    if (filter.columnId !== update.columnId) {\n      return filter;\n    }\n    switch (update.type) {\n      case \"text\": {\n        return filter.type === \"text\" ? { ...filter, operator: update.operator } : filter;\n      }\n      case \"number\": {\n        return filter.type === \"number\" ? { ...filter, operator: update.operator } : filter;\n      }\n      case \"date\": {\n        return filter.type === \"date\" ? { ...filter, operator: update.operator } : filter;\n      }\n      case \"boolean\": {\n        return filter.type === \"boolean\" ? { ...filter, operator: update.operator } : filter;\n      }\n      case \"option\": {\n        return filter.type === \"option\" ? { ...filter, operator: update.operator } : filter;\n      }\n      case \"multiOption\": {\n        return filter.type === \"multiOption\" ? { ...filter, operator: update.operator } : filter;\n      }\n      default: {\n        return filter;\n      }\n    }\n  });\n\nexport const toggleFilterValues = (\n  filters: FiltersState,\n  column: Column<OptionBasedColumnDataType>,\n  values: string[],\n  add: boolean,\n): FiltersState => {\n  const current = filters.find((filter) => filter.columnId === column.id);\n  const existing =\n    current?.type === \"option\" || current?.type === \"multiOption\" ? current.values : [];\n  return setFilterValue(filters, {\n    columnId: column.id,\n    type: column.type,\n    values: add ? [...existing, ...values] : existing.filter((value) => !values.includes(value)),\n  });\n};\n","path":"/filter-package/core/filters.ts","target":"uicapsule/filter-bar/filter-package/core/filters.ts","type":"registry:file"},{"content":"import type {\n  ColumnDataType,\n  FilterDetails,\n  FilterOperators,\n  FilterOperatorTarget,\n  FilterTypeOperatorDetails,\n  FilterValues,\n} from \"./types\";\n\nexport const DEFAULT_OPERATORS = {\n  boolean: {\n    multiple: \"is\",\n    single: \"is\",\n  },\n  date: {\n    multiple: \"is between\",\n    single: \"is\",\n  },\n  multiOption: {\n    multiple: \"include any of\",\n    single: \"include\",\n  },\n  number: {\n    multiple: \"is between\",\n    single: \"is\",\n  },\n  option: {\n    multiple: \"is any of\",\n    single: \"is\",\n  },\n  text: {\n    multiple: \"contains\",\n    single: \"contains\",\n  },\n} satisfies Record<ColumnDataType, Record<FilterOperatorTarget, FilterOperators[ColumnDataType]>>;\n\nexport const optionFilterOperators = {\n  is: {\n    key: \"is\",\n    multiple: \"is any of\",\n    target: \"single\",\n    value: \"is\",\n  },\n  \"is any of\": {\n    key: \"is any of\",\n    single: \"is\",\n    target: \"multiple\",\n    value: \"is any of\",\n  },\n  \"is none of\": {\n    key: \"is none of\",\n    single: \"is not\",\n    target: \"multiple\",\n    value: \"is none of\",\n  },\n  \"is not\": {\n    key: \"is not\",\n    multiple: \"is none of\",\n    target: \"single\",\n    value: \"is not\",\n  },\n} as const satisfies FilterDetails<\"option\">;\n\nexport const multiOptionFilterOperators = {\n  exclude: {\n    key: \"excludes\",\n    multiple: \"exclude if any of\",\n    target: \"single\",\n    value: \"exclude\",\n  },\n  \"exclude if all\": {\n    key: \"excludes all of\",\n    single: \"exclude\",\n    target: \"multiple\",\n    value: \"exclude if all\",\n  },\n  \"exclude if any of\": {\n    key: \"excludes if any of\",\n    single: \"exclude\",\n    target: \"multiple\",\n    value: \"exclude if any of\",\n  },\n  include: {\n    key: \"includes\",\n    multiple: \"include any of\",\n    target: \"single\",\n    value: \"include\",\n  },\n  \"include all of\": {\n    key: \"includes all of\",\n    single: \"include\",\n    target: \"multiple\",\n    value: \"include all of\",\n  },\n  \"include any of\": {\n    key: \"includes any of\",\n    single: \"include\",\n    target: \"multiple\",\n    value: \"include any of\",\n  },\n} as const satisfies FilterDetails<\"multiOption\">;\n\nexport const dateFilterOperators = {\n  is: {\n    key: \"is\",\n    multiple: \"is between\",\n    target: \"single\",\n    value: \"is\",\n  },\n  \"is after\": {\n    key: \"is after\",\n    multiple: \"is between\",\n    target: \"single\",\n    value: \"is after\",\n  },\n  \"is before\": {\n    key: \"is before\",\n    multiple: \"is between\",\n    target: \"single\",\n    value: \"is before\",\n  },\n  \"is between\": {\n    key: \"is between\",\n    single: \"is\",\n    target: \"multiple\",\n    value: \"is between\",\n  },\n  \"is not\": {\n    key: \"is not\",\n    multiple: \"is not between\",\n    target: \"single\",\n    value: \"is not\",\n  },\n  \"is not between\": {\n    key: \"is not between\",\n    single: \"is not\",\n    target: \"multiple\",\n    value: \"is not between\",\n  },\n  \"is on or after\": {\n    key: \"is on or after\",\n    multiple: \"is between\",\n    target: \"single\",\n    value: \"is on or after\",\n  },\n  \"is on or before\": {\n    key: \"is on or before\",\n    multiple: \"is between\",\n    target: \"single\",\n    value: \"is on or before\",\n  },\n} as const satisfies FilterDetails<\"date\">;\n\nexport const textFilterOperators = {\n  contains: {\n    key: \"contains\",\n    target: \"single\",\n    value: \"contains\",\n  },\n  \"does not contain\": {\n    key: \"does not contain\",\n    target: \"single\",\n    value: \"does not contain\",\n  },\n} as const satisfies FilterDetails<\"text\">;\n\nexport const numberFilterOperators = {\n  is: {\n    key: \"is\",\n    multiple: \"is between\",\n    target: \"single\",\n    value: \"is\",\n  },\n  \"is between\": {\n    key: \"is between\",\n    single: \"is\",\n    target: \"multiple\",\n    value: \"is between\",\n  },\n  \"is greater than\": {\n    key: \"greater than\",\n    multiple: \"is between\",\n    target: \"single\",\n    value: \"is greater than\",\n  },\n  \"is greater than or equal to\": {\n    key: \"greater than or equal\",\n    multiple: \"is between\",\n    target: \"single\",\n    value: \"is greater than or equal to\",\n  },\n  \"is less than\": {\n    key: \"less than\",\n    multiple: \"is between\",\n    target: \"single\",\n    value: \"is less than\",\n  },\n  \"is less than or equal to\": {\n    key: \"less than or equal\",\n    multiple: \"is between\",\n    target: \"single\",\n    value: \"is less than or equal to\",\n  },\n  \"is not\": {\n    key: \"is not\",\n    multiple: \"is not between\",\n    target: \"single\",\n    value: \"is not\",\n  },\n  \"is not between\": {\n    key: \"is not between\",\n    single: \"is not\",\n    target: \"multiple\",\n    value: \"is not between\",\n  },\n} as const satisfies FilterDetails<\"number\">;\n\nexport const booleanFilterOperators = {\n  is: {\n    key: \"is\",\n    multiple: \"is not\",\n    target: \"single\",\n    value: \"is\",\n  },\n  \"is not\": {\n    key: \"is not\",\n    multiple: \"is\",\n    target: \"single\",\n    value: \"is not\",\n  },\n} as const satisfies FilterDetails<\"boolean\">;\n\nexport const filterTypeOperatorDetails: FilterTypeOperatorDetails = {\n  boolean: booleanFilterOperators,\n  date: dateFilterOperators,\n  multiOption: multiOptionFilterOperators,\n  number: numberFilterOperators,\n  option: optionFilterOperators,\n  text: textFilterOperators,\n};\n\n// Preserve negation when the value count switches between one and many.\nexport const determineNewOperator = <TType extends ColumnDataType>(\n  type: TType,\n  oldVals: FilterValues<TType>,\n  nextVals: FilterValues<TType>,\n  currentOperator: FilterOperators[TType],\n): FilterOperators[TType] => {\n  const a = oldVals.length;\n  const b = nextVals.length;\n\n  if (a === b || (a >= 2 && b >= 2) || (a <= 1 && b <= 1)) {\n    return currentOperator;\n  }\n\n  const opDetails = filterTypeOperatorDetails[type][currentOperator];\n\n  if (a < b && b >= 2) {\n    return opDetails.multiple ?? currentOperator;\n  }\n  if (a > b && b <= 1) {\n    return opDetails.single ?? currentOperator;\n  }\n  return currentOperator;\n};\n","path":"/filter-package/core/operators.ts","target":"uicapsule/filter-bar/filter-package/core/operators.ts","type":"registry:file"},{"content":"import type { ElementType, ReactElement, Dispatch, SetStateAction } from \"react\";\n\nexport interface ColumnOption {\n  label: string;\n  value: string;\n  icon?: ReactElement | ElementType;\n  count?: number;\n}\n\nexport interface ColumnOptionExtended extends ColumnOption {\n  selected?: boolean;\n}\n\nexport type ColumnDataType = \"text\" | \"number\" | \"date\" | \"boolean\" | \"option\" | \"multiOption\";\nexport type OptionBasedColumnDataType = \"option\" | \"multiOption\";\nexport type FilterStrategy = \"client\" | \"server\";\n\ninterface ColumnBase {\n  id: string;\n  displayName: string;\n  icon?: ReactElement | ElementType;\n  hidden?: boolean;\n}\n\ninterface ColumnValues {\n  text: string[];\n  number: number[];\n  date: Date[];\n  boolean: boolean[];\n  option: string[];\n  multiOption: string[];\n}\n\ninterface ColumnDetails {\n  text: object;\n  number: { min: number; max: number };\n  date: object;\n  boolean: { toggledStateName?: string };\n  option: { options: ColumnOption[] };\n  multiOption: { options: ColumnOption[] };\n}\n\nexport type Column<K extends ColumnDataType = ColumnDataType> = {\n  [Type in K]: ColumnBase & { type: Type; values: ColumnValues[Type] } & ColumnDetails[Type];\n}[K];\n\ntype OptionValue = string | ColumnOption;\n\ninterface ColumnInputs<Row> {\n  text: { accessor: (row: Row) => string | null | undefined };\n  number: { accessor: (row: Row) => number | null | undefined; min?: number; max?: number };\n  date: { accessor: (row: Row) => Date | null | undefined };\n  boolean: { accessor: (row: Row) => boolean | null | undefined; toggledStateName?: string };\n  option: { accessor: (row: Row) => OptionValue | null | undefined; options?: ColumnOption[] };\n  multiOption: {\n    accessor: (row: Row) => readonly OptionValue[] | null | undefined;\n    options?: ColumnOption[];\n  };\n}\n\nexport type ColumnConfig<Row> = {\n  [Type in ColumnDataType]: ColumnBase & { type: Type } & ColumnInputs<Row>[Type];\n}[ColumnDataType];\n\nexport type TextFilterOperator = \"contains\" | \"does not contain\";\n\nexport type NumberFilterOperator =\n  | \"is\"\n  | \"is not\"\n  | \"is less than\"\n  | \"is greater than or equal to\"\n  | \"is greater than\"\n  | \"is less than or equal to\"\n  | \"is between\"\n  | \"is not between\";\n\nexport type DateFilterOperator =\n  | \"is\"\n  | \"is not\"\n  | \"is before\"\n  | \"is on or after\"\n  | \"is after\"\n  | \"is on or before\"\n  | \"is between\"\n  | \"is not between\";\n\nexport type BooleanFilterOperator = \"is\" | \"is not\";\n\nexport type OptionFilterOperator = \"is\" | \"is not\" | \"is any of\" | \"is none of\";\n\nexport type MultiOptionFilterOperator =\n  | \"include\"\n  | \"exclude\"\n  | \"include any of\"\n  | \"include all of\"\n  | \"exclude if any of\"\n  | \"exclude if all\";\n\nexport interface FilterOperators {\n  text: TextFilterOperator;\n  number: NumberFilterOperator;\n  date: DateFilterOperator;\n  boolean: BooleanFilterOperator;\n  option: OptionFilterOperator;\n  multiOption: MultiOptionFilterOperator;\n}\n\nexport type FilterValues<K extends ColumnDataType> = ColumnValues[K];\n\nexport type FilterModel<K extends ColumnDataType = ColumnDataType> = {\n  [Type in K]: {\n    columnId: string;\n    type: Type;\n    operator: FilterOperators[Type];\n    values: FilterValues<Type>;\n  };\n}[K];\n\nexport type FiltersState = FilterModel[];\n\nexport type FilterValueUpdate = {\n  [Type in ColumnDataType]: Pick<FilterModel<Type>, \"columnId\" | \"type\" | \"values\">;\n}[ColumnDataType];\n\nexport type FilterOperatorUpdate = {\n  [Type in ColumnDataType]: Pick<FilterModel<Type>, \"columnId\" | \"type\" | \"operator\">;\n}[ColumnDataType];\n\nexport interface DataTableFilterActions {\n  setFilterValue: (update: FilterValueUpdate) => void;\n  setFilterOperator: (update: FilterOperatorUpdate) => void;\n  addFilterValue: (column: Column<OptionBasedColumnDataType>, values: string[]) => void;\n  removeFilterValue: (column: Column<OptionBasedColumnDataType>, values: string[]) => void;\n  removeFilter: (columnId: string) => void;\n  removeAllFilters: () => void;\n}\n\nexport type FilterOperatorTarget = \"single\" | \"multiple\";\nexport interface FilterOperatorDetails<Operator, K extends ColumnDataType> {\n  key: string;\n  value: Operator;\n  target: FilterOperatorTarget;\n  multiple?: FilterOperators[K];\n  single?: FilterOperators[K];\n}\n\nexport type FilterDetails<K extends ColumnDataType> = {\n  [Operator in FilterOperators[K]]: FilterOperatorDetails<Operator, K>;\n};\nexport type FilterTypeOperatorDetails = { [K in ColumnDataType]: FilterDetails<K> };\n\nexport type DataTableFiltersOptions<Row> = {\n  data: Row[];\n  columnsConfig: readonly ColumnConfig<Row>[];\n  strategy?: FilterStrategy;\n  entityName?: string;\n} & (\n  | {\n      filters: FiltersState;\n      onFiltersChange: Dispatch<SetStateAction<FiltersState>>;\n      defaultFilters?: never;\n    }\n  | { filters?: never; onFiltersChange?: never; defaultFilters?: FiltersState }\n);\n\nexport type FilterBinding = {\n  [K in ColumnDataType]: { type: K; column: Column<K>; filter?: FilterModel<K> };\n}[ColumnDataType];\n","path":"/filter-package/core/types.ts","target":"uicapsule/filter-bar/filter-package/core/types.ts","type":"registry:file"},{"content":"\"use client\";\n\nimport { useMemo, useState } from \"react\";\nimport type { DataTableFilterActions, DataTableFiltersOptions, FiltersState } from \"../core/types\";\nimport { createColumns } from \"../core/columns/column-factory\";\nimport { setFilterOperator, setFilterValue, toggleFilterValues } from \"../core/filters\";\n\nexport const useDataTableFilters = <Row>({\n  data,\n  columnsConfig,\n  filters: controlledFilters,\n  onFiltersChange,\n  defaultFilters = [],\n  strategy = \"client\",\n  entityName,\n}: DataTableFiltersOptions<Row>) => {\n  const [internalFilters, setInternalFilters] = useState<FiltersState>(defaultFilters);\n  const filters = controlledFilters ?? internalFilters;\n  const setFilters = onFiltersChange ?? setInternalFilters;\n  const columns = useMemo(\n    () => createColumns(data, columnsConfig, strategy),\n    [data, columnsConfig, strategy],\n  );\n  const actions = useMemo<DataTableFilterActions>(\n    () => ({\n      addFilterValue: (column, values) =>\n        setFilters((current) => toggleFilterValues(current, column, values, true)),\n      removeAllFilters: () => setFilters([]),\n      removeFilter: (columnId) =>\n        setFilters((current) => current.filter((filter) => filter.columnId !== columnId)),\n      removeFilterValue: (column, values) =>\n        setFilters((current) => toggleFilterValues(current, column, values, false)),\n      setFilterOperator: (update) => setFilters((current) => setFilterOperator(current, update)),\n      setFilterValue: (update) => setFilters((current) => setFilterValue(current, update)),\n    }),\n    [setFilters],\n  );\n  return { actions, columns, entityName, filters, strategy };\n};\n","path":"/filter-package/hooks/use-data-table-filters.ts","target":"uicapsule/filter-bar/filter-package/hooks/use-data-table-filters.ts","type":"registry:file"},{"content":"export * from \"./core/types\";\nexport * from \"./core/operators\";\nexport { useDataTableFilters } from \"./hooks/use-data-table-filters\";\nexport { getColumn, bindFilter, createNumberRange } from \"./lib/helpers\";\n","path":"/filter-package/index.ts","target":"uicapsule/filter-bar/filter-package/index.ts","type":"registry:file"},{"content":"import type { Column, FilterModel, FilterBinding } from \"../core/types\";\n\nexport const getColumn = (columns: Column[], id: string) => {\n  const column = columns.find((candidate) => candidate.id === id);\n  if (!column) {\n    throw new Error(`Column with id ${id} not found`);\n  }\n  return column;\n};\n\nexport const createNumberRange = (values: number[]): number[] => {\n  const [a = 0, b = 0] = values;\n  return a < b ? [a, b] : [b, a];\n};\n\nexport const bindFilter = (column: Column, filter?: FilterModel): FilterBinding => {\n  switch (column.type) {\n    case \"text\": {\n      return { column, filter: filter?.type === \"text\" ? filter : undefined, type: \"text\" };\n    }\n    case \"number\": {\n      return { column, filter: filter?.type === \"number\" ? filter : undefined, type: \"number\" };\n    }\n    case \"date\": {\n      return { column, filter: filter?.type === \"date\" ? filter : undefined, type: \"date\" };\n    }\n    case \"boolean\": {\n      return { column, filter: filter?.type === \"boolean\" ? filter : undefined, type: \"boolean\" };\n    }\n    case \"option\": {\n      return { column, filter: filter?.type === \"option\" ? filter : undefined, type: \"option\" };\n    }\n    case \"multiOption\": {\n      return {\n        column,\n        filter: filter?.type === \"multiOption\" ? filter : undefined,\n        type: \"multiOption\",\n      };\n    }\n    default: {\n      throw new Error(`Unsupported column type: ${String(column satisfies never)}`);\n    }\n  }\n};\n","path":"/filter-package/lib/helpers.ts","target":"uicapsule/filter-bar/filter-package/lib/helpers.ts","type":"registry:file"},{"content":"import { cn } from \"cn\";\nimport {\n  BuildingIcon,\n  CalendarArrowUpIcon,\n  DollarSignIcon,\n  TargetIcon,\n  UserCheckIcon,\n  UserIcon,\n} from \"lucide-react\";\n\nimport type { Person, PersonDepartment, PersonSkill } from \"./types\";\nimport type { ColumnConfig } from \"./filter-package\";\n\nexport const PERSON_DEPARTMENTS: PersonDepartment[] = [\n  { color: \"blue\", id: \"engineering\", name: \"Engineering\" },\n  { color: \"purple\", id: \"design\", name: \"Design\" },\n  { color: \"green\", id: \"product\", name: \"Product\" },\n  { color: \"orange\", id: \"marketing\", name: \"Marketing\" },\n  { color: \"red\", id: \"sales\", name: \"Sales\" },\n  { color: \"pink\", id: \"hr\", name: \"Human Resources\" },\n  { color: \"teal\", id: \"finance\", name: \"Finance\" },\n  { color: \"indigo\", id: \"operations\", name: \"Operations\" },\n  { color: \"emerald\", id: \"customer-success\", name: \"Customer Success\" },\n  { color: \"cyan\", id: \"data\", name: \"Data & Analytics\" },\n  { color: \"rose\", id: \"security\", name: \"Security\" },\n  { color: \"violet\", id: \"legal\", name: \"Legal\" },\n] as const;\n\nexport const PERSON_SKILLS = [\n  { color: \"yellow\", id: \"javascript\", level: \"expert\", name: \"JavaScript\" },\n  { color: \"blue\", id: \"typescript\", level: \"advanced\", name: \"TypeScript\" },\n  { color: \"cyan\", id: \"react\", level: \"expert\", name: \"React\" },\n  { color: \"green\", id: \"nodejs\", level: \"advanced\", name: \"Node.js\" },\n  { color: \"yellow\", id: \"python\", level: \"intermediate\", name: \"Python\" },\n  { color: \"red\", id: \"java\", level: \"advanced\", name: \"Java\" },\n  { color: \"blue\", id: \"go\", level: \"beginner\", name: \"Go\" },\n  { color: \"orange\", id: \"rust\", level: \"intermediate\", name: \"Rust\" },\n  { color: \"indigo\", id: \"sql\", level: \"advanced\", name: \"SQL\" },\n  { color: \"orange\", id: \"aws\", level: \"intermediate\", name: \"AWS\" },\n  { color: \"blue\", id: \"docker\", level: \"advanced\", name: \"Docker\" },\n  {\n    color: \"blue\",\n    id: \"kubernetes\",\n    level: \"intermediate\",\n    name: \"Kubernetes\",\n  },\n  { color: \"purple\", id: \"figma\", level: \"expert\", name: \"Figma\" },\n  { color: \"yellow\", id: \"sketch\", level: \"advanced\", name: \"Sketch\" },\n  { color: \"blue\", id: \"photoshop\", level: \"intermediate\", name: \"Photoshop\" },\n  {\n    color: \"orange\",\n    id: \"illustrator\",\n    level: \"beginner\",\n    name: \"Illustrator\",\n  },\n  { color: \"green\", id: \"leadership\", level: \"expert\", name: \"Leadership\" },\n  {\n    color: \"pink\",\n    id: \"communication\",\n    level: \"advanced\",\n    name: \"Communication\",\n  },\n  {\n    color: \"teal\",\n    id: \"project-management\",\n    level: \"intermediate\",\n    name: \"Project Management\",\n  },\n  {\n    color: \"cyan\",\n    id: \"data-analysis\",\n    level: \"advanced\",\n    name: \"Data Analysis\",\n  },\n  {\n    color: \"purple\",\n    id: \"machine-learning\",\n    level: \"beginner\",\n    name: \"Machine Learning\",\n  },\n  { color: \"rose\", id: \"ui-design\", level: \"expert\", name: \"UI Design\" },\n  {\n    color: \"indigo\",\n    id: \"ux-research\",\n    level: \"advanced\",\n    name: \"UX Research\",\n  },\n  {\n    color: \"emerald\",\n    id: \"content-strategy\",\n    level: \"intermediate\",\n    name: \"Content Strategy\",\n  },\n  { color: \"lime\", id: \"seo\", level: \"beginner\", name: \"SEO\" },\n  {\n    color: \"pink\",\n    id: \"social-media\",\n    level: \"advanced\",\n    name: \"Social Media\",\n  },\n  {\n    color: \"blue\",\n    id: \"email-marketing\",\n    level: \"intermediate\",\n    name: \"Email Marketing\",\n  },\n  { color: \"blue\", id: \"salesforce\", level: \"expert\", name: \"Salesforce\" },\n  { color: \"orange\", id: \"hubspot\", level: \"advanced\", name: \"HubSpot\" },\n  {\n    color: \"green\",\n    id: \"recruiting\",\n    level: \"intermediate\",\n    name: \"Recruiting\",\n  },\n  {\n    color: \"purple\",\n    id: \"training\",\n    level: \"beginner\",\n    name: \"Training & Development\",\n  },\n  { color: \"teal\", id: \"accounting\", level: \"advanced\", name: \"Accounting\" },\n  {\n    color: \"indigo\",\n    id: \"budgeting\",\n    level: \"intermediate\",\n    name: \"Budgeting\",\n  },\n  { color: \"red\", id: \"compliance\", level: \"expert\", name: \"Compliance\" },\n  {\n    color: \"orange\",\n    id: \"risk-management\",\n    level: \"advanced\",\n    name: \"Risk Management\",\n  },\n  {\n    color: \"green\",\n    id: \"customer-support\",\n    level: \"intermediate\",\n    name: \"Customer Support\",\n  },\n  {\n    color: \"blue\",\n    id: \"technical-writing\",\n    level: \"beginner\",\n    name: \"Technical Writing\",\n  },\n  {\n    color: \"purple\",\n    id: \"public-speaking\",\n    level: \"advanced\",\n    name: \"Public Speaking\",\n  },\n  {\n    color: \"pink\",\n    id: \"negotiation\",\n    level: \"intermediate\",\n    name: \"Negotiation\",\n  },\n  {\n    color: \"emerald\",\n    id: \"strategic-planning\",\n    level: \"expert\",\n    name: \"Strategic Planning\",\n  },\n  {\n    color: \"cyan\",\n    id: \"change-management\",\n    level: \"advanced\",\n    name: \"Change Management\",\n  },\n  {\n    color: \"lime\",\n    id: \"team-building\",\n    level: \"intermediate\",\n    name: \"Team Building\",\n  },\n  { color: \"rose\", id: \"mentoring\", level: \"beginner\", name: \"Mentoring\" },\n  {\n    color: \"violet\",\n    id: \"conflict-resolution\",\n    level: \"advanced\",\n    name: \"Conflict Resolution\",\n  },\n  {\n    color: \"amber\",\n    id: \"time-management\",\n    level: \"expert\",\n    name: \"Time Management\",\n  },\n  {\n    color: \"sky\",\n    id: \"problem-solving\",\n    level: \"expert\",\n    name: \"Problem Solving\",\n  },\n  {\n    color: \"indigo\",\n    id: \"critical-thinking\",\n    level: \"advanced\",\n    name: \"Critical Thinking\",\n  },\n  {\n    color: \"fuchsia\",\n    id: \"creativity\",\n    level: \"intermediate\",\n    name: \"Creativity\",\n  },\n  { color: \"teal\", id: \"adaptability\", level: \"expert\", name: \"Adaptability\" },\n  {\n    color: \"pink\",\n    id: \"emotional-intelligence\",\n    level: \"advanced\",\n    name: \"Emotional Intelligence\",\n  },\n  {\n    color: \"green\",\n    id: \"cross-cultural\",\n    level: \"intermediate\",\n    name: \"Cross-cultural Communication\",\n  },\n  {\n    color: \"blue\",\n    id: \"multilingual\",\n    level: \"beginner\",\n    name: \"Multilingual\",\n  },\n] as const satisfies readonly PersonSkill[];\n\nexport const LABEL_STYLES_BG = {\n  amber: \"bg-amber-500\",\n  blue: \"bg-blue-500\",\n  cyan: \"bg-cyan-500\",\n  emerald: \"bg-emerald-500\",\n  fuchsia: \"bg-fuchsia-500\",\n  green: \"bg-green-500\",\n  indigo: \"bg-indigo-500\",\n  lime: \"bg-lime-500\",\n  neutral: \"bg-neutral-500\",\n  orange: \"bg-orange-500\",\n  pink: \"bg-pink-500\",\n  purple: \"bg-purple-500\",\n  red: \"bg-red-500\",\n  rose: \"bg-rose-500\",\n  sky: \"bg-sky-500\",\n  teal: \"bg-teal-500\",\n  violet: \"bg-violet-500\",\n  yellow: \"bg-yellow-500\",\n} as const;\n\nexport type TW_COLOR = keyof typeof LABEL_STYLES_BG;\n\nexport const columnsConfig: readonly ColumnConfig<Person>[] = [\n  {\n    accessor: (row) => `${row.firstName} ${row.lastName}`,\n    displayName: \"Name\",\n    icon: UserIcon,\n    id: \"name\",\n    type: \"text\",\n  },\n  {\n    accessor: (row) => row.department.id,\n    displayName: \"Department\",\n    icon: BuildingIcon,\n    id: \"department\",\n    options: PERSON_DEPARTMENTS.map((department) => ({\n      icon: BuildingIcon,\n      label: department.name,\n      value: department.id,\n    })),\n    type: \"option\",\n  },\n  {\n    accessor: (row) => row.salary,\n    displayName: \"Salary\",\n    icon: DollarSignIcon,\n    id: \"salary\",\n    max: 300_000,\n    min: 20_000,\n    type: \"number\",\n  },\n  {\n    accessor: (row) => row.skills?.map((skill) => skill.id),\n    displayName: \"Skills\",\n    icon: TargetIcon,\n    id: \"skills\",\n    options: PERSON_SKILLS.map((skill) => ({\n      icon: <div className={cn(\"size-2.5 rounded-full\", LABEL_STYLES_BG[skill.color])} />,\n      label: skill.name,\n      value: skill.id,\n    })),\n    type: \"multiOption\",\n  },\n  {\n    accessor: (row) => row.startDate,\n    displayName: \"Start Date\",\n    icon: CalendarArrowUpIcon,\n    id: \"startDate\",\n    type: \"date\",\n  },\n  {\n    accessor: (row) => row.isActive,\n    displayName: \"Active employees\",\n    icon: UserCheckIcon,\n    id: \"isActive\",\n    toggledStateName: \"active\",\n    type: \"boolean\",\n  },\n];\n","path":"/filters.tsx","target":"uicapsule/filter-bar/filters.tsx","type":"registry:file"},{"content":"\"use client\";\n\nimport { useState } from \"react\";\n\nimport type { Person } from \"./types\";\nimport type { FiltersState } from \"./filter-package/core/types\";\nimport { DataTableFilter } from \"./_components/data-table-filter\";\nimport { useDataTableFilters } from \"./filter-package\";\nimport { columnsConfig } from \"./filters\";\n\nconst people: Person[] = [];\n\nconst Preview = () => {\n  const [filtersState, setFiltersState] = useState<FiltersState>([]);\n\n  const { columns, filters, actions, strategy, entityName } = useDataTableFilters({\n    columnsConfig,\n    data: people,\n    entityName: \"Person\",\n    filters: filtersState,\n    onFiltersChange: setFiltersState,\n    strategy: \"client\",\n  });\n\n  return (\n    <div className=\"flex h-screen items-center bg-stone-100 p-10 dark:bg-stone-900\">\n      <DataTableFilter\n        filters={filters}\n        columns={columns}\n        actions={actions}\n        strategy={strategy}\n        entityName={entityName}\n      />\n    </div>\n  );\n};\n\nexport default Preview;\n","path":"/preview.tsx","target":"uicapsule/filter-bar/preview.tsx","type":"registry:file"},{"content":"declare module \"*.css\" {}\n","path":"/styles.d.ts","target":"uicapsule/filter-bar/styles.d.ts","type":"registry:file"},{"content":"import type { LucideIcon } from \"lucide-react\";\n\nexport interface Person {\n  id: string;\n  firstName: string;\n  lastName: string;\n  email: string;\n  phone?: string;\n  age: number;\n  gender: PersonGender;\n  department: PersonDepartment;\n  position: string;\n  salary: number;\n  startDate: Date;\n  endDate?: Date;\n  isActive: boolean;\n  skills?: PersonSkill[];\n  location: PersonLocation;\n  manager?: Person;\n  isRemote: boolean;\n  experience: number;\n  education: PersonEducation;\n  performance: PersonPerformance;\n}\n\nexport interface PersonGender {\n  id: \"male\" | \"female\" | \"non-binary\" | \"other\";\n  name: string;\n  order: number;\n  icon: LucideIcon;\n}\n\nexport interface PersonDepartment {\n  id: string;\n  name: string;\n  color: string;\n}\n\nexport interface PersonSkill {\n  id: string;\n  name: string;\n  level: \"beginner\" | \"intermediate\" | \"advanced\" | \"expert\";\n  color: string;\n}\n\nexport interface PersonLocation {\n  city: string;\n  state: string;\n  country: string;\n  timezone: string;\n}\n\nexport interface PersonEducation {\n  degree: string;\n  field: string;\n  institution: string;\n  graduationYear: number;\n}\n\nexport interface PersonPerformance {\n  // 1-5\n  rating: number;\n  lastReview: Date;\n  goals: string[];\n}\n","path":"/types.ts","target":"uicapsule/filter-bar/types.ts","type":"registry:file"}],"homepage":"https://www.uicapsule.com/ui/filter-bar","name":"filter-bar","registryDependencies":[],"title":"Filter Bar","type":"registry:block"}