{"name":"filter-bar","type":"registry:component","files":[{"path":"filter-bar.tsx","type":"registry:component","target":"components/ui/filter-bar/filter-bar.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\n\nimport {\n  ChevronDown as ChevronDownIcon,\n  Funnel as FunnelIcon,\n  X as CloseIcon,\n} from \"honestui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Popover,\n  PopoverPopup,\n  PopoverTrigger,\n} from \"@/components/ui/popover\"\nimport { ScrollArea } from \"@/components/ui/scroll-area\"\nimport { Separator } from \"@/components/ui/separator\"\nimport {\n  Sheet,\n  SheetFooter,\n  SheetPopup,\n  SheetTitle,\n  SheetTrigger,\n} from \"@/components/ui/sheet\"\nimport { Toolbar } from \"@/components/ui/toolbar\"\n\nimport type {\n  FilterDateRangePreset,\n  FilterDefinition,\n  FilterOption,\n  FilterRenderProps,\n  FilterType,\n  FilterValue,\n} from \"./filter-bar-types\"\nimport {\n  DEFAULT_FILTER_BAR_LABELS,\n  cloneFilterValues,\n  FilterBarControllerContext,\n  useController,\n  type FilterBarControllerValue,\n  type FilterBarLabels,\n  type FilterBarMode,\n} from \"./filter-bar-context\"\nimport { renderFilterControl } from \"./filter-bar-fields\"\nimport {\n  createFilterDefinitionMap,\n  formatFilterValueText,\n  getActiveFilterCount,\n  isActiveFilterValue,\n  isValuelessOperator,\n  resolveDefaultOperator,\n} from \"./filter-bar-utils\"\n\nexport type {\n  FilterDateRangePreset,\n  FilterDefinition,\n  FilterOption,\n  FilterRenderProps,\n  FilterType,\n  FilterValue,\n}\n\nfunction useNarrowViewport(maxWidthPx: number) {\n  const subscribe = React.useCallback(\n    (onMediaChange: () => void) => {\n      const mediaQueryList = window.matchMedia(\n        `(max-width: ${maxWidthPx - 1}px)`\n      )\n\n      mediaQueryList.addEventListener(\"change\", onMediaChange)\n\n      return () =>\n        mediaQueryList.removeEventListener(\"change\", onMediaChange)\n    },\n    [maxWidthPx]\n  )\n\n  return React.useSyncExternalStore(\n    subscribe,\n    () => window.matchMedia(`(max-width: ${maxWidthPx - 1}px)`).matches,\n    () => false\n  )\n}\n\nexport interface FilterBarProps\n  extends Omit<React.ComponentProps<\"div\">, \"defaultValue\" | \"onChange\"> {\n  /** Every filter the application offers. */\n  filters: FilterDefinition[]\n  /** Committed filters owned by your application. */\n  value?: FilterValue[] | undefined\n  /** Initial filters when uncontrolled. */\n  defaultValue?: FilterValue[] | undefined\n  onValueChange?: ((values: FilterValue[]) => void) | undefined\n  /** instant commits each change; apply holds work behind an Apply press. */\n  mode?: FilterBarMode\n  open?: boolean | undefined\n  defaultOpen?: boolean | undefined\n  onOpenChange?: ((open: boolean) => void) | undefined\n  /** Shows every chip or collapses overflow into one \"+N\" control. */\n  valuesDisplay?: \"collapse\" | \"wrap\"\n  /** Option lists grow a search input past this many entries. */\n  searchableThreshold?: number\n  /** Renders result counts supplied on filter options. */\n  showOptionCounts?: boolean\n  /** Panels become a Sheet below this viewport width. */\n  mobileBreakpoint?: number\n  popoverAlign?: \"start\" | \"center\" | \"end\"\n  disabled?: boolean\n  /** Overrides English strings listed under Labels in the docs. */\n  labels?: Partial<FilterBarLabels>\n  /**\n   * Composed arrangement of FilterBar* parts for custom layouts such as a\n   * pinned side rail. Omit for the complete default bar plus panel.\n   */\n  children?: React.ReactNode\n}\n\n/**\n * One consistent way to add, view, edit, and clear filters above Data Table,\n * Kanban lists, search results, admin pages, or any collection. The bar owns\n * filter UI and values; applications own querying and interpretation.\n */\nexport function FilterBar({\n  filters,\n  value,\n  defaultValue,\n  onValueChange,\n  mode = \"instant\",\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  valuesDisplay = \"collapse\",\n  searchableThreshold = 10,\n  showOptionCounts = true,\n  mobileBreakpoint = 640,\n  popoverAlign = \"start\",\n  disabled = false,\n  labels: labelOverrides,\n  className,\n  children,\n  ...rootProps\n}: FilterBarProps) {\n  if (\n    process.env.NODE_ENV !== \"production\" &&\n    (!filters || filters.length === 0)\n  ) {\n    console.warn(\n      \"FilterBar received no filter definitions; nothing useful opens. Unmount FilterBar until definitions exist.\"\n    )\n  }\n\n  const definitionMap = React.useMemo(\n    () => createFilterDefinitionMap(filters ?? []),\n    [filters]\n  )\n\n  const [internalCommitted, setInternalCommitted] =\n    React.useState<FilterValue[]>(defaultValue ?? [])\n  const isControlled = value !== undefined\n\n  const resolvedLabels = React.useMemo<FilterBarLabels>(\n    () => ({ ...DEFAULT_FILTER_BAR_LABELS, ...labelOverrides }),\n    [labelOverrides]\n  )\n\n  const sourceCommitted = isControlled ? value : internalCommitted\n  const committed = React.useMemo(\n    () =>\n      sourceCommitted?.filter((entry) =>\n        isActiveFilterValue(definitionMap.get(entry.key), entry)\n      ) ?? [],\n    [sourceCommitted, definitionMap]\n  )\n\n  const [internalOpen, setInternalOpen] = React.useState(defaultOpen)\n  const open = openProp !== undefined ? openProp : internalOpen\n\n  // Drafts exist only between opening and resolving in apply mode. Declared\n  // before the callbacks so they close over live state instead of refs.\n  const [draft, setDraft] = React.useState<FilterValue[]>([])\n  const hasDraft = mode === \"apply\" && open\n  const editing = hasDraft ? draft : committed\n\n  function baseSetOpen(next: boolean) {\n    if (openProp === undefined) {\n      setInternalOpen(next)\n    }\n\n    onOpenChange?.(next)\n  }\n\n  /**\n   * Every closing path discards unfinished draft work; Apply is the only\n   * writer while a draft exists, so outside clicks and Escape behave like\n   * Cancel exactly as documented.\n   */\n  function setOpen(next: boolean) {\n    if (mode === \"apply\") {\n      setDraft(next ? cloneFilterValues(committed) : [])\n    }\n\n    baseSetOpen(next)\n  }\n\n  const focusGroups = React.useRef(new Map<string, HTMLElement>())\n  const pendingFocusKey = React.useRef<string | undefined>(undefined)\n\n  React.useEffect(() => {\n    if (!open || !pendingFocusKey.current) return\n\n    const focusKey = pendingFocusKey.current\n    pendingFocusKey.current = undefined\n\n    window.requestAnimationFrame(() => {\n      const target = focusGroups.current.get(focusKey)\n\n      target?.scrollIntoView({ block: \"nearest\", inline: \"nearest\" })\n      target?.focus({ preventScroll: true })\n    })\n  }, [open])\n\n  function registerFocusGroup(key: string, element: HTMLElement | null) {\n    if (element) {\n      focusGroups.current.set(key, element)\n    } else {\n      focusGroups.current.delete(key)\n    }\n  }\n\n  /**\n   * Field writes land on the draft while one exists and commit straight to\n   * the application otherwise, which is what instant mode should do.\n   */\n  function writeEditing(\n    next:\n      | FilterValue[]\n      | ((current: FilterValue[]) => FilterValue[])\n  ) {\n    if (disabled) return\n\n    const resolved =\n      typeof next === \"function\" ? next(hasDraft ? draft : committed) : next\n\n    if (hasDraft) {\n      setDraft(resolved)\n      return\n    }\n\n    if (!isControlled) setInternalCommitted(resolved)\n    onValueChange?.(resolved)\n  }\n\n  function setEntry(key: string, nextValue: unknown, nextOperator?: string) {\n    const definition = definitionMap.get(key)\n\n    if (!definition || definition.disabled || disabled) return\n\n    const current = hasDraft ? draft : committed\n    const existing = current.find((entry) => entry.key === key)\n    const operator =\n      nextOperator ?? existing?.operator ?? resolveDefaultOperator(definition)\n    const cleaned = current.filter((entry) => entry.key !== key)\n\n    const isEmptyWrite =\n      !isValuelessOperator(operator) &&\n      (nextValue == null ||\n        (typeof nextValue === \"string\" && nextValue.trim() === \"\") ||\n        (Array.isArray(nextValue) && nextValue.length === 0))\n\n    const value = isValuelessOperator(operator) ? undefined : nextValue\n\n    writeEditing(\n      isEmptyWrite ? cleaned : [...cleaned, { key, operator, value }]\n    )\n  }\n\n  function setEntryOperator(key: string, nextOperator: string) {\n    if (definitionMap.get(key)?.disabled) return\n\n    writeEditing((current) =>\n      current.map((entry) =>\n        entry.key === key ? { ...entry, operator: nextOperator } : entry\n      )\n    )\n  }\n\n  function removeEditingEntry(key: string) {\n    if (definitionMap.get(key)?.disabled) return\n\n    writeEditing((current) => current.filter((entry) => entry.key !== key))\n  }\n\n  function clearEditingEntries() {\n    writeEditing([])\n  }\n\n  function commitDirect(next: FilterValue[]) {\n    if (disabled) return\n\n    if (!isControlled) setInternalCommitted(next)\n    onValueChange?.(next)\n  }\n\n  function removeCommittedKey(key: string) {\n    commitDirect(committed.filter((entry) => entry.key !== key))\n  }\n\n  function clearAllCommitted() {\n    commitDirect([])\n  }\n\n  function applyDraftPress() {\n    const pruned = draft.filter((entry) =>\n      isActiveFilterValue(definitionMap.get(entry.key), entry)\n    )\n\n    commitDirect(pruned)\n    baseSetOpen(false)\n    setDraft([])\n  }\n\n  function cancelDraftPress() {\n    baseSetOpen(false)\n  }\n\n  const activeCount = getActiveFilterCount(committed, filters ?? [])\n\n  const controller: FilterBarControllerValue = {\n    mode,\n    orderedDefinitions: definitionMap.ordered(),\n    definitionOf: definitionMap.get,\n    committed,\n    editing,\n    hasDraft,\n    open,\n    activeCount,\n    activeEntries: committed,\n    labels: resolvedLabels,\n    searchThreshold: searchableThreshold,\n    showOptionCounts,\n    valuesDisplay,\n    disabled,\n    openPanel: (options: { focusKey?: string } = {}) => {\n      if (disabled) return\n\n      const { focusKey } = options\n      pendingFocusKey.current = focusKey\n      setOpen(true)\n    },\n    closePanel: () => baseSetOpen(false),\n    applyDraft: applyDraftPress,\n    cancelDraft: cancelDraftPress,\n    setEntry,\n    setEntryOperator,\n    removeEditing: removeEditingEntry,\n    clearEditing: clearEditingEntries,\n    removeCommitted: removeCommittedKey,\n    clearCommitted: clearAllCommitted,\n    registerFocusGroup,\n  }\n\n  return (\n    <FilterBarControllerContext.Provider value={controller}>\n      <div\n        {...rootProps}\n        data-slot=\"filter-bar-root\"\n        data-mode={mode}\n        className={cn(\"flex w-full min-w-0 items-center\", className)}\n      >\n        {children ?? (\n          <FilterBarDefaultSurface\n            mobileBreakpoint={mobileBreakpoint}\n            popoverAlign={popoverAlign}\n          />\n        )}\n      </div>\n    </FilterBarControllerContext.Provider>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/* Default arrangement                                                 */\n/* ------------------------------------------------------------------ */\n\n/** Trigger, chips, Clear all, then a panel anchored to the trigger. */\nfunction FilterBarDefaultSurface({\n  mobileBreakpoint,\n  popoverAlign,\n}: {\n  mobileBreakpoint: number\n  popoverAlign: \"start\" | \"center\" | \"end\"\n}) {\n  const isNarrow = useNarrowViewport(mobileBreakpoint)\n  const controller = useController(\"FilterBar\")\n  const panelColumnCount = Math.min(\n    Math.max(controller.orderedDefinitions.length, 1),\n    2\n  )\n  const defaultPanelMaxWidth = `${4 + panelColumnCount * 20}rem`\n  const chipsRow = (\n    <>\n      <FilterBarChipsRow />\n      <FilterBarClearAction />\n    </>\n  )\n\n  // Base UI triggers attach their ref and click behavior to whatever element\n  // they render, so the trigger must be a real Button, not a wrapper that\n  // swallows props.\n  const triggerElement = (\n    <Button\n      type=\"button\"\n      variant=\"outline\"\n      size=\"sm\"\n      disabled={controller.disabled}\n      data-slot=\"filter-bar-trigger\"\n      data-open={controller.open || undefined}\n      aria-label={\n        controller.activeCount > 0\n          ? `${controller.labels.trigger}, ${controller.labels.triggerActiveSuffix(controller.activeCount)}`\n          : controller.labels.trigger\n      }\n      // Keeps surrounding controls still when the count badge appears.\n      style={{ minWidth: \"var(--fb-trigger-min-width, 5.25rem)\" }}\n    >\n      <FunnelIcon aria-hidden />\n      <span>{controller.labels.trigger}</span>\n      {controller.activeCount > 0 ? (\n        <Badge size=\"sm\" variant=\"secondary\" className=\"tabular-nums\">\n          {controller.activeCount}\n        </Badge>\n      ) : null}\n      <ChevronDownIcon\n        aria-hidden\n        data-open={controller.open || undefined}\n        className=\"[transition:transform_var(--hui-duration-fast)_var(--hui-ease-out)] data-open:-rotate-180 motion-reduce:[transition:none]\"\n      />\n    </Button>\n  )\n\n  return (\n    <div className=\"flex w-full min-w-0 items-center gap-(--hui-space-3)\">\n      {isNarrow ? (\n        <Sheet\n          open={controller.open}\n          onOpenChange={(next) =>\n            next ? controller.openPanel() : controller.closePanel()\n          }\n        >\n          <SheetTrigger render={triggerElement} />\n          <SheetPopup\n            side=\"bottom\"\n            className=\"flex max-h-[85svh]! w-full! max-w-none! flex-col p-0!\"\n          >\n            <FilterBarPanelBody variant=\"sheet\" />\n          </SheetPopup>\n          {chipsRow}\n        </Sheet>\n      ) : (\n        <>\n          <Popover\n            open={controller.open}\n            onOpenChange={(next) =>\n              next ? controller.openPanel() : controller.closePanel()\n            }\n          >\n            <PopoverTrigger render={triggerElement} />\n            <PopoverPopup\n              side=\"bottom\"\n              align={popoverAlign}\n              style={\n                {\n                  \"--fb-panel-default-max\": defaultPanelMaxWidth,\n                } as React.CSSProperties\n              }\n              className=\"max-h-[min(38rem,var(--available-height))]! w-[min(var(--fb-panel-max,var(--fb-panel-default-max)),calc(100vw-2rem))]! max-w-none! p-0!\"\n            >\n              <FilterBarPanelBody variant=\"popover\" />\n            </PopoverPopup>\n          </Popover>\n          {chipsRow}\n        </>\n      )}\n    </div>\n  )\n}\n\n/** The removable record of everything currently filtering the view. */\n\nconst CHIP_ROW_GAP_PX = 8\nconst OVERFLOW_INDICATOR_CLASS =\n  \"shrink-0 rounded-[var(--hui-radius-full)] px-(--hui-space-2) py-(--hui-space-1) text-[var(--hui-color-foreground-base-secondary)] [font-size:var(--hui-font-size-mini)]\"\n\n/**\n * Shows one chip per active filter, trimmed with its human-readable value.\n * In collapse mode a pre-paint measurement keeps the row stable, then hides\n * whatever does not fit behind a single \"+N\" control.\n */\nfunction FilterBarChipsRow({ className }: { className?: string }) {\n  const {\n    activeEntries,\n    orderedDefinitions,\n    definitionOf,\n    valuesDisplay,\n    labels,\n  } = useController(\"FilterBarChipsRow\")\n  const containerRef = React.useRef<HTMLDivElement>(null)\n  // null means every chip fits or the first pre-paint measure has not run.\n  const [fitCount, setFitCount] = React.useState<number | null>(null)\n\n  const orderedChips = React.useMemo(() => {\n    const byPriority = orderedDefinitions.flatMap((definition) => {\n      const matching = activeEntries.find((entry) => entry.key === definition.key)\n\n      return matching ? [{ entry: matching, definition }] : []\n    })\n    const withoutDefinitions = activeEntries\n      .filter((entry) => !definitionOf(entry.key))\n      .map((entry) => ({\n        entry,\n        definition: {\n          key: entry.key,\n          label: entry.key,\n          type: \"custom\" as FilterType,\n        },\n      }))\n\n    return [...byPriority, ...withoutDefinitions]\n  }, [activeEntries, orderedDefinitions, definitionOf])\n\n  const isWrap = valuesDisplay === \"wrap\"\n\n  React.useLayoutEffect(() => {\n    const container = containerRef.current\n\n    if (!container || isWrap || typeof ResizeObserver === \"undefined\") return\n\n    let frame = requestAnimationFrame(measure)\n\n    function measure() {\n      if (!container) return\n\n      const limit = container.clientWidth\n      const chipSlots = Array.from(container.children).filter(\n        (node): node is HTMLElement =>\n          node instanceof HTMLElement && node.dataset.chipSlot != null\n      )\n      const totalChipWidth = chipSlots.reduce(\n        (width, slot, index) =>\n          width + slot.getBoundingClientRect().width + (index === 0 ? 0 : CHIP_ROW_GAP_PX),\n        0\n      )\n\n      if (totalChipWidth <= limit) {\n        setFitCount(null)\n        return\n      }\n\n      const overflowMeasure = container.querySelector<HTMLElement>(\n        \"[data-overflow-measure]\"\n      )\n      const chipLimit = Math.max(\n        0,\n        limit -\n          (overflowMeasure?.getBoundingClientRect().width ?? 0) -\n          CHIP_ROW_GAP_PX\n      )\n      let usedWidth = 0\n      let visible = 0\n\n      for (const slot of chipSlots) {\n        const slotWidth = slot.getBoundingClientRect().width\n        const gap = visible === 0 ? 0 : CHIP_ROW_GAP_PX\n\n        if (usedWidth + gap + slotWidth > chipLimit) break\n\n        usedWidth += gap + slotWidth\n        visible += 1\n      }\n\n      setFitCount(visible)\n    }\n\n    const observer = new ResizeObserver(() => {\n      cancelAnimationFrame(frame)\n      frame = requestAnimationFrame(measure)\n    })\n\n    observer.observe(container)\n\n    return () => {\n      observer.disconnect()\n      cancelAnimationFrame(frame)\n    }\n  }, [isWrap, orderedChips, labels])\n\n  if (orderedChips.length === 0) {\n    return (\n      <div\n        ref={containerRef}\n        data-slot=\"filter-bar-values\"\n        className={cn(\"min-w-0 flex-1\", className)}\n      />\n    )\n  }\n\n  const collapsed = !isWrap && fitCount != null\n  const visibleChipCount = collapsed ? fitCount : orderedChips.length\n  const hiddenCount = orderedChips.length - visibleChipCount\n\n  return (\n    <div\n      ref={containerRef}\n      data-slot=\"filter-bar-values\"\n      role=\"list\"\n      aria-label=\"Active filters\"\n      className={cn(\n        \"relative flex min-w-0 flex-1 items-center gap-(--hui-space-2)\",\n        collapsed ? \"flex-nowrap overflow-hidden\" : \"flex-wrap\",\n        className\n      )}\n    >\n      {orderedChips.map(({ entry, definition }, index) => {\n        const isChipHidden = collapsed && index >= visibleChipCount\n\n        return (\n          <div\n            key={entry.key}\n            data-chip-slot\n            data-slot=\"filter-bar-value\"\n            role=\"listitem\"\n            aria-hidden={isChipHidden || undefined}\n            className={cn(\n              isChipHidden &&\n                \"pointer-events-none absolute top-0 left-0 invisible\"\n            )}\n          >\n            <FilterBarChip entry={entry} definition={definition} />\n          </div>\n        )\n      })}\n      {!isWrap ? (\n        <span\n          aria-hidden=\"true\"\n          data-overflow-measure\n          className={cn(\n            OVERFLOW_INDICATOR_CLASS,\n            \"pointer-events-none absolute top-0 left-0 invisible\"\n          )}\n        >\n          {labels.moreCount(orderedChips.length)}\n        </span>\n      ) : null}\n      {hiddenCount > 0 ? (\n        <OverflowIndicator hiddenCount={hiddenCount} />\n      ) : null}\n    </div>\n  )\n}\n\nfunction OverflowIndicator({ hiddenCount }: { hiddenCount: number }) {\n  const { labels, openPanel, disabled } = useController(\"OverflowIndicator\")\n\n  return (\n    <div role=\"listitem\" className=\"shrink-0\">\n      <button\n        type=\"button\"\n        data-slot=\"filter-bar-more-values\"\n        className={cn(\n          OVERFLOW_INDICATOR_CLASS,\n          \"min-h-6 min-w-6 cursor-pointer outline-none hover:bg-[var(--hui-color-background-base-primary-hover)] hover:text-[var(--hui-color-foreground-base-primary)] focus-visible:[outline:var(--hui-focus-ring)] disabled:cursor-not-allowed\"\n        )}\n        onClick={() => openPanel()}\n        disabled={disabled}\n      >\n        {labels.moreCount(hiddenCount)}\n      </button>\n    </div>\n  )\n}\n\n/**\n * One active filter: label plus readable value, opening its group again on\n * click, removing it straight away through the cross control.\n */\nexport function FilterBarChip({\n  entry,\n  definition,\n}: {\n  entry: FilterValue\n  definition: FilterDefinition\n}) {\n  const { openPanel, removeCommitted, disabled, labels } =\n    useController(\"FilterBarChip\")\n  const valueText = formatFilterValueText(definition, entry)\n  const isDisabled = disabled || Boolean(definition.disabled)\n\n  return (\n    <div\n      data-slot=\"filter-bar-chip\"\n      title={`${definition.label}${valueText ? `: ${valueText}` : \"\"}`}\n      className={cn(\n        \"inline-flex h-(--hui-space-7) min-w-0 max-w-[16rem] items-center gap-(--hui-space-1) rounded-[var(--hui-radius-full)] bg-[var(--hui-color-background-neutral-secondary)] pl-(--hui-space-3) pr-(--hui-space-1)\",\n        \"[font-size:var(--hui-font-size-mini)] [line-height:var(--hui-line-height-mini)] text-[var(--hui-color-foreground-base-primary)]\",\n        isDisabled && \"opacity-64\"\n      )}\n    >\n      <button\n        type=\"button\"\n        data-slot=\"filter-bar-chip-open\"\n        className=\"h-full min-w-6 cursor-pointer truncate bg-transparent text-left outline-none focus-visible:[outline:var(--hui-focus-ring)] disabled:cursor-not-allowed\"\n        onClick={() => openPanel({ focusKey: definition.key })}\n        disabled={isDisabled}\n      >\n        {definition.label}\n        {valueText ? (\n          <span className=\"text-[var(--hui-color-foreground-base-secondary)]\">\n            {\" \"}\n            {valueText}\n          </span>\n        ) : null}\n      </button>\n      <button\n        type=\"button\"\n        data-slot=\"filter-bar-chip-remove\"\n        aria-label={labels.removeFilter(definition.label)}\n        className=\"inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-[var(--hui-radius-full)] text-[var(--hui-color-foreground-base-secondary)] outline-none hover:bg-[var(--hui-color-background-neutral-tertiary)] hover:text-[var(--hui-color-foreground-base-primary)] focus-visible:[outline:var(--hui-focus-ring)] disabled:pointer-events-none\"\n        onClick={(event) => {\n          event.stopPropagation()\n\n          const currentSlot = event.currentTarget.closest<HTMLElement>(\n            '[data-slot=\"filter-bar-value\"]'\n          )\n          const values = event.currentTarget.closest<HTMLElement>(\n            '[data-slot=\"filter-bar-values\"]'\n          )\n          const slots = values\n            ? Array.from(\n                values.querySelectorAll<HTMLElement>(\n                  '[data-slot=\"filter-bar-value\"]'\n                )\n              )\n            : []\n          const currentIndex = currentSlot ? slots.indexOf(currentSlot) : -1\n          const adjacentSlot =\n            slots[currentIndex + 1] ?? slots[currentIndex - 1]\n          const adjacentButton = adjacentSlot?.querySelector<HTMLButtonElement>(\n            '[data-slot=\"filter-bar-chip-open\"]'\n          )\n          const trigger = event.currentTarget\n            .closest<HTMLElement>('[data-slot=\"filter-bar-root\"]')\n            ?.querySelector<HTMLButtonElement>('[data-slot=\"filter-bar-trigger\"]')\n\n          removeCommitted(entry.key)\n\n          window.requestAnimationFrame(() => {\n            if (adjacentButton?.isConnected) {\n              adjacentButton.focus()\n              return\n            }\n\n            trigger?.focus()\n          })\n        }}\n        disabled={isDisabled}\n      >\n        <CloseIcon aria-hidden className=\"size-3\" />\n      </button>\n    </div>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/* Composable parts                                                    */\n/* ------------------------------------------------------------------ */\n\n/** Standalone chip list for custom toolbar arrangements. */\nexport function FilterBarValues({ className }: { className?: string }) {\n  return <FilterBarChipsRow className={className} />\n}\n\n/** One committed filter rendered with the shared chip presentation. */\nexport function FilterBarValue({ filterKey }: { filterKey: string }) {\n  const { committed, definitionOf } = useController(\"FilterBarValue\")\n  const definition = definitionOf(filterKey)\n  const entry = committed.find((item) => item.key === filterKey)\n\n  if (!definition || !entry) return null\n\n  return <FilterBarChip entry={entry} definition={definition} />\n}\n\n/** Action row for apply mode; defaults to Cancel plus Apply filters. */\nexport function FilterBarFooter({\n  className,\n  children,\n}: {\n  className?: string\n  children?: React.ReactNode\n}) {\n  return (\n    <footer\n      data-slot=\"filter-bar-footer\"\n      className={cn(\"px-(--hui-space-4) py-(--hui-space-3)\", className)}\n    >\n      {children ?? (\n        <Toolbar className=\"justify-end gap-(--hui-space-2)\">\n          <FilterBarCancel />\n          <FilterBarApply />\n        </Toolbar>\n      )}\n    </footer>\n  )\n}\n\nexport function FilterBarClear() {\n  return <FilterBarClearAction />\n}\n\nfunction FilterBarClearAction() {\n  const { activeCount, clearCommitted, labels, disabled } =\n    useController(\"FilterBarClear\")\n\n  // Nothing to clear means nothing to show; never render a dead action.\n  if (activeCount === 0) return null\n\n  return (\n    <Button\n      variant=\"link\"\n      size=\"sm\"\n      className=\"ml-auto shrink-0 text-[var(--hui-color-foreground-base-secondary)] hover:text-[var(--hui-color-foreground-base-primary)]\"\n      onClick={(event) => {\n        const trigger = event.currentTarget\n          .closest<HTMLElement>('[data-slot=\"filter-bar-root\"]')\n          ?.querySelector<HTMLButtonElement>('[data-slot=\"filter-bar-trigger\"]')\n\n        clearCommitted()\n        window.requestAnimationFrame(() => trigger?.focus())\n      }}\n      disabled={disabled}\n      data-slot=\"filter-bar-clear-all\"\n    >\n      {labels.clearAll}\n    </Button>\n  )\n}\n\nexport function FilterBarToolbar({\n  className,\n  children,\n}: {\n  className?: string\n  children: React.ReactNode\n}) {\n  return (\n    <Toolbar className={className} data-slot=\"filter-bar-toolbar\">\n      {children}\n    </Toolbar>\n  )\n}\n\n/**\n * Toggles the Filter Bar panel from anywhere inside a FilterBar. The default\n * surface wires this automatically; composed layouts embed it wherever their\n * design needs it.\n */\nexport function FilterBarTrigger(props: Omit<React.ComponentProps<typeof Button>, \"onClick\" | \"children\">) {\n  const { open, openPanel, closePanel, labels, activeCount, disabled } =\n    useController(\"FilterBarTrigger\")\n\n  return (\n    <FilterBarTriggerCore\n      {...props}\n      isOpen={open}\n      onToggle={() => (open ? closePanel() : openPanel())}\n      labels={labels}\n      activeCount={activeCount}\n      disabled={disabled || props.disabled}\n    />\n  )\n}\n\ninterface TriggerCoreProps extends Omit<React.ComponentProps<typeof Button>, \"children\"> {\n  isOpen: boolean\n  onToggle: () => void\n  labels: FilterBarLabels\n  activeCount: number\n}\n\nfunction FilterBarTriggerCore({\n  isOpen,\n  onToggle,\n  labels,\n  activeCount,\n  ...props\n}: TriggerCoreProps) {\n  return (\n    <Button\n      type=\"button\"\n      {...props}\n      variant=\"outline\"\n      size=\"sm\"\n      data-open={isOpen || undefined}\n      aria-expanded={isOpen}\n      aria-label={\n        activeCount > 0\n          ? `${labels.trigger}, ${labels.triggerActiveSuffix(activeCount)}`\n          : labels.trigger\n      }\n      onClick={onToggle}\n      data-slot=\"filter-bar-trigger\"\n    >\n      <FunnelIcon aria-hidden />\n      <span>{labels.trigger}</span>\n      {activeCount > 0 ? (\n        <Badge size=\"sm\" variant=\"secondary\" className=\"tabular-nums\">\n          {activeCount}\n        </Badge>\n      ) : null}\n      <ChevronDownIcon\n        aria-hidden\n        data-open={isOpen || undefined}\n        className=\"[transition:transform_var(--hui-duration-fast)_var(--hui-ease-out)] data-open:-rotate-180 motion-reduce:[transition:none]\"\n      />\n    </Button>\n  )\n}\n\n/**\n * Presentational panel card for pinned layouts. The default FilterBar owns\n * the floating Popover and Sheet; this part shares their inner structure.\n */\nexport function FilterBarContent({\n  className,\n  children,\n}: {\n  className?: string\n  children?: React.ReactNode\n}) {\n  return (\n    <section\n      data-slot=\"filter-bar-content\"\n      className={cn(\n        \"flex flex-col overflow-hidden rounded-[var(--hui-radius-2)] border-[0.5px] border-[var(--hui-color-border-base-primary)] bg-[var(--hui-color-background-base-primary)] shadow-[var(--hui-shadow-soft)]\",\n        className\n      )}\n    >\n      {children ?? (\n        <>\n          <FilterBarHeader />\n          <Separator />\n          <ScrollArea className=\"max-h-[24rem] min-h-0\">\n            <FilterBarGroups />\n          </ScrollArea>\n        </>\n      )}\n    </section>\n  )\n}\n\nexport function FilterBarHeader({\n  className,\n  children,\n}: {\n  className?: string\n  children?: React.ReactNode\n}) {\n  const { labels } = useController(\"FilterBarHeader\")\n\n  return (\n    <header\n      className={cn(\n        \"flex items-start justify-between gap-(--hui-space-4) px-(--hui-space-5) pt-(--hui-space-4) pb-(--hui-space-3)\",\n        className\n      )}\n      data-slot=\"filter-bar-header\"\n    >\n      {children ?? <FilterBarTitle>{labels.title}</FilterBarTitle>}\n      <DraftClearAction />\n    </header>\n  )\n}\n\nfunction DraftClearAction() {\n  const { hasDraft, editing, clearEditing, labels, disabled } =\n    useController(\"DraftClearAction\")\n\n  // Instant mode holds nothing back, so clearing a draft would lie about state.\n  if (!hasDraft || editing.length === 0) return null\n\n  return (\n    <Button\n      variant=\"link\"\n      size=\"sm\"\n      disabled={disabled}\n      onClick={() => clearEditing()}\n      data-slot=\"filter-bar-clear-draft\"\n    >\n      {labels.clearPanel}\n    </Button>\n  )\n}\n\nexport function FilterBarTitle(props: React.ComponentProps<\"h2\">) {\n  const { labels } = useController(\"FilterBarTitle\")\n\n  return (\n    <h2\n      tabIndex={-1}\n      data-slot=\"filter-bar-title\"\n      className=\"m-0! text-[var(--hui-color-foreground-base-primary)] [font-size:var(--hui-font-size-large)]! [font-weight:var(--hui-font-weight-medium)]! [line-height:var(--hui-line-height-large)]! outline-none\"\n      {...props}\n    >\n      {props.children ?? labels.title}\n    </h2>\n  )\n}\n\nexport function FilterBarGroups({ className }: { className?: string }) {\n  const { orderedDefinitions } = useController(\"FilterBarGroups\")\n\n  return (\n    <div\n      className={cn(\n        \"grid items-start grid-cols-[repeat(auto-fill,minmax(min(15rem,100%),1fr))] gap-x-(--hui-space-5) gap-y-(--hui-space-6)\",\n        className\n      )}\n      data-slot=\"filter-bar-groups\"\n    >\n      {orderedDefinitions.map((definition) => (\n        <FilterBarGroupSection key={definition.key} definition={definition} />\n      ))}\n    </div>\n  )\n}\n\n/** Renders exactly one filter's controls inside the shared group chrome. */\nexport function FilterBarGroup({ filterKey }: { filterKey: string }) {\n  const { definitionOf } = useController(\"FilterBarGroup\")\n  const definition = definitionOf(filterKey)\n\n  if (!definition) return null\n\n  return <FilterBarGroupSection definition={definition} />\n}\n\nfunction countSelectedOptions(\n  definition: FilterDefinition,\n  entry: FilterValue | undefined\n) {\n  if (definition.type !== \"multi-select\" || !Array.isArray(entry?.value)) {\n    return 0\n  }\n\n  return entry.value.length\n}\n\nfunction FilterBarGroupSection({\n  definition,\n}: {\n  definition: FilterDefinition\n}) {\n  const { editing, registerFocusGroup, disabled } =\n    useController(\"FilterBarGroupSection\")\n  const entry = editing.find((item) => item.key === definition.key)\n  const selectedOptionCount = countSelectedOptions(definition, entry)\n  const sectionRef = React.useCallback(\n    (node: HTMLElement | null) => {\n      registerFocusGroup(definition.key, node)\n    },\n    [registerFocusGroup, definition.key]\n  )\n\n  return (\n    <section\n      ref={sectionRef}\n      tabIndex={-1}\n      data-slot=\"filter-bar-group\"\n      data-filter-key={definition.key}\n      data-filter-type={definition.type}\n      data-disabled={definition.disabled || disabled || undefined}\n      className=\"flex min-w-0 flex-col gap-y-(--hui-space-2) outline-none\"\n    >\n      <div\n        className=\"flex items-baseline justify-between gap-(--hui-space-2)\"\n        data-slot=\"filter-bar-group-heading\"\n      >\n        <span className=\"text-[var(--hui-color-foreground-base-primary)] [font-size:var(--hui-font-size-mini)] [font-weight:var(--hui-font-weight-medium)] uppercase tracking-wide\">\n          {definition.label}\n        </span>\n        <span\n          className=\"tabular-nums text-[var(--hui-color-foreground-base-primary)] [font-size:var(--hui-font-size-mini)]\"\n          data-slot=\"filter-bar-group-count\"\n        >\n          {selectedOptionCount > 0 ? selectedOptionCount : \"\"}\n        </span>\n      </div>\n      {renderFilterControl(definition, entry)}\n    </section>\n  )\n}\n\nexport function FilterBarGroupLabel({ children }: { children: React.ReactNode }) {\n  return (\n    <span\n      data-slot=\"filter-bar-group-label\"\n      className=\"text-[var(--hui-color-foreground-base-primary)] [font-size:var(--hui-font-size-mini)] [font-weight:var(--hui-font-weight-medium)] uppercase tracking-wide\"\n    >\n      {children}\n    </span>\n  )\n}\n\n/** Commits the draft, reports it upward, and closes the panel. */\nexport function FilterBarApply(props: Omit<React.ComponentProps<typeof Button>, \"onClick\">) {\n  const { applyDraft, labels, disabled } = useController(\"FilterBarApply\")\n\n  return (\n    <Button\n      type=\"button\"\n      {...props}\n      disabled={disabled || props.disabled}\n      onClick={() => applyDraft()}\n      data-slot=\"filter-bar-apply\"\n    >\n      {props.children ?? labels.apply}\n    </Button>\n  )\n}\n\n/** Throws away draft changes and closes the panel. */\nexport function FilterBarCancel(props: Omit<React.ComponentProps<typeof Button>, \"onClick\" | \"variant\">) {\n  const { cancelDraft, labels } = useController(\"FilterBarCancel\")\n\n  return (\n    <Button\n      type=\"button\"\n      {...props}\n      variant=\"ghost\"\n      onClick={() => cancelDraft()}\n      data-slot=\"filter-bar-cancel\"\n    >\n      {props.children ?? labels.cancel}\n    </Button>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/* Panel body shared by popover and sheet                              */\n/* ------------------------------------------------------------------ */\n\nfunction FilterBarPanelBody({ variant }: { variant: \"popover\" | \"sheet\" }) {\n  const { mode, labels } = useController(\"FilterBarPanelBody\")\n  const isSheet = variant === \"sheet\"\n\n  return (\n    <div className=\"flex max-h-[inherit] flex-col\" data-slot=\"filter-bar-panel\">\n      <FilterBarHeader className={cn(isSheet && \"pt-(--hui-space-5)\")} />\n      {isSheet ? <SheetTitle className=\"sr-only\">{labels.title}</SheetTitle> : null}\n      <Separator />\n      <ScrollArea className=\"min-h-0 flex-1\">\n        <div className=\"px-(--hui-space-5) py-(--hui-space-5)\">\n          <FilterBarGroups />\n        </div>\n      </ScrollArea>\n      {mode === \"apply\" ? (\n        <>\n          <Separator />\n          {isSheet ? (\n            <SheetFooter className=\"justify-end pt-(--hui-space-2)\">\n              <Toolbar className=\"justify-end gap-(--hui-space-2)\">\n                <FilterBarCancel />\n                <FilterBarApply />\n              </Toolbar>\n            </SheetFooter>\n          ) : (\n            <FilterBarFooter />\n          )}\n        </>\n      ) : null}\n    </div>\n  )\n}\n"},{"path":"filter-bar-context.tsx","type":"registry:component","target":"components/ui/filter-bar/filter-bar-context.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\n\nimport type {\n  FilterDefinition,\n  FilterValue,\n} from \"./filter-bar-types\"\n\nexport type FilterBarMode = \"instant\" | \"apply\"\n\nexport interface FilterBarLabels {\n  /** Accessible name of the trigger button. */\n  trigger: string\n  /** Announced after the trigger name along with the active count. */\n  triggerActiveSuffix: (count: number) => string\n  /** Heading inside the filter panel. */\n  title: string\n  /** Removes every committed filter from the bar. */\n  clearAll: string\n  /** Removes every draft filter from the open panel header. */\n  clearPanel: string\n  cancel: string\n  apply: string\n  moreCount: (count: number) => string\n  removeFilter: (label: string) => string\n  loadingOptions: string\n  retryOptions: string\n  optionsError: string\n  noMatches: (query: string) => string\n}\n\nexport const DEFAULT_FILTER_BAR_LABELS: FilterBarLabels = {\n  trigger: \"Filter\",\n  triggerActiveSuffix: (count) =>\n    `${count} active filter${count === 1 ? \"\" : \"s\"}`,\n  title: \"Filters\",\n  clearAll: \"Clear all\",\n  clearPanel: \"Clear all filters\",\n  cancel: \"Cancel\",\n  apply: \"Apply filters\",\n  moreCount: (count) => `+${count}`,\n  removeFilter: (label) => `Remove ${label} filter`,\n  loadingOptions: \"Loading...\",\n  retryOptions: \"Try again\",\n  optionsError: \"Could not load these options.\",\n  noMatches: (query) => `No results match \"${query}\".`,\n}\n\n/**\n * Keeps Date objects intact across draft copies so committed ranges and\n * drafts stay comparable without serialization surprises.\n */\nexport function cloneFilterEntryValue(value: unknown): unknown {\n  if (value instanceof Date) return new Date(value.getTime())\n  if (Array.isArray(value)) return value.map(cloneFilterEntryValue)\n\n  if (typeof value === \"object\" && value != null) {\n    const copy: Record<string, unknown> = {}\n\n    for (const [part, partValue] of Object.entries(\n      value as Record<string, unknown>\n    )) {\n      copy[part] = cloneFilterEntryValue(partValue)\n    }\n\n    return copy\n  }\n\n  return value\n}\n\nexport function cloneFilterValues(values: FilterValue[]): FilterValue[] {\n  return values.map((entry) => ({\n    ...entry,\n    value: cloneFilterEntryValue(entry.value),\n  }))\n}\n\n/* ------------------------------------------------------------------ */\n/* Controller shared by every part                                     */\n/* ------------------------------------------------------------------ */\n\ninterface FilterBarControllerValue {\n  mode: FilterBarMode\n  orderedDefinitions: FilterDefinition[]\n  definitionOf: (key: string) => FilterDefinition | undefined\n  /** Filters owned by the application right now. */\n  committed: FilterValue[]\n  /** The surface fields read and write while a panel is open. */\n  editing: FilterValue[]\n  hasDraft: boolean\n  open: boolean\n  activeCount: number\n  activeEntries: FilterValue[]\n  labels: FilterBarLabels\n  searchThreshold: number\n  showOptionCounts: boolean\n  valuesDisplay: \"collapse\" | \"wrap\"\n  disabled: boolean\n  openPanel: (options?: { focusKey?: string }) => void\n  closePanel: () => void\n  applyDraft: () => void\n  cancelDraft: () => void\n  setEntry: (key: string, value: unknown, operator?: string) => void\n  setEntryOperator: (key: string, operator: string) => void\n  removeEditing: (key: string) => void\n  clearEditing: () => void\n  removeCommitted: (key: string) => void\n  clearCommitted: () => void\n  registerFocusGroup: (key: string, element: HTMLElement | null) => void\n}\n\nexport const FilterBarControllerContext =\n  React.createContext<FilterBarControllerValue | null>(null)\n\nexport function useController(componentName: string): FilterBarControllerValue {\n  const controller = React.useContext(FilterBarControllerContext)\n\n  if (!controller) {\n    throw new Error(`${componentName} must be used within FilterBar`)\n  }\n\n  return controller\n}\n\n/** Field controls read the same controller through the friendlier name. */\nexport const useFilterBarContext = useController\n\nexport type { FilterBarControllerValue }\n"},{"path":"filter-bar-fields.tsx","type":"registry:component","target":"components/ui/filter-bar/filter-bar-fields.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\n\nimport { LoaderCircle as LoaderCircleIcon } from \"honestui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport {\n  Empty,\n  EmptyDescription,\n  EmptyHeader,\n  EmptyTitle,\n} from \"@/components/ui/empty\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport {\n  NumberField,\n  NumberFieldInput,\n} from \"@/components/ui/number-field\"\nimport { Radio, RadioGroup } from \"@/components/ui/radio-group\"\nimport { ScrollArea } from \"@/components/ui/scroll-area\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { Switch } from \"@/components/ui/switch\"\n\nimport {\n  DateRangePicker,\n  getDateRangePresets,\n} from \"../date-range-picker/date-range-picker\"\n// Type-only import through the sibling component's barrel keeps the served\n// registry dependency list limited to whole components.\nimport type { DateRange } from \"../date-range-picker/date-range-picker\"\n\nimport { useFilterBarContext } from \"./filter-bar-context\"\nimport type {\n  FilterDefinition,\n  FilterOption,\n  FilterValue,\n} from \"./filter-bar-types\"\nimport {\n  getOperatorsFor,\n  isSameDay,\n  OPERATOR_LABELS,\n  resolveDefaultOperator,\n  resolveFilterOptionValue,\n} from \"./filter-bar-utils\"\n\nfunction optionKey(option: FilterOption) {\n  return String(option.value)\n}\n\ninterface FilterFieldScopeValue {\n  scopeId: string\n}\n\nconst FilterFieldScope = React.createContext<FilterFieldScopeValue | null>(null)\n\n/**\n * Wraps every filter control with its group label. Choice groups reference\n * the label through aria-labelledby; form widgets receive an explicit id.\n */\nexport function FilterField({\n  definition,\n  children,\n  hideLabel = false,\n}: {\n  definition: FilterDefinition\n  children: React.ReactNode\n  /** For controls whose own row carries the visible label. */\n  hideLabel?: boolean\n}) {\n  const scopeId = React.useId()\n  const { disabled } = useFilterBarContext(\"FilterField\")\n\n  return (\n    <div\n      data-slot=\"filter-bar-field\"\n      data-disabled={definition.disabled || disabled || undefined}\n      className=\"flex w-full min-w-0 flex-col gap-(--hui-space-2)\"\n    >\n      <span\n        id={`${scopeId}-label`}\n        data-slot=\"filter-bar-field-label\"\n        className={cn(\n          \"[font-size:var(--hui-font-size-small)] [font-weight:var(--hui-font-weight-medium)] [letter-spacing:var(--hui-letter-spacing-small)] [line-height:var(--hui-line-height-small)]\",\n          hideLabel && \"sr-only\"\n        )}\n      >\n        {definition.label}\n      </span>\n      <FilterFieldScope.Provider value={{ scopeId }}>\n        {children}\n      </FilterFieldScope.Provider>\n    </div>\n  )\n}\n\n/** Returns ids that connect controls back to their field label. */\nexport function useFilterFieldIds() {\n  const scope = React.useContext(FilterFieldScope)\n\n  if (!scope) {\n    throw new Error(\n      \"Filter field controls must be rendered inside FilterField\"\n    )\n  }\n\n  return {\n    label: `${scope.scopeId}-label`,\n    control: `${scope.scopeId}-control`,\n    description: `${scope.scopeId}-description`,\n  }\n}\n\nfunction DisabledReasonNotice({ definition }: { definition: FilterDefinition }) {\n  const { description } = useFilterFieldIds()\n\n  if (!definition.disabledReason) return null\n\n  return (\n    <p\n      id={description}\n      data-slot=\"filter-bar-field-disabled-reason\"\n      className=\"text-[var(--hui-color-foreground-base-secondary)] [font-size:var(--hui-font-size-mini)] [line-height:var(--hui-line-height-mini)]\"\n    >\n      {definition.disabledReason}\n    </p>\n  )\n}\n\n/** Renders the built-in control matching one definition's type. */\nexport function renderFilterControl(\n  definition: FilterDefinition,\n  entry?: FilterValue\n) {\n  switch (definition.type) {\n    case \"text\":\n      return <FilterText definition={definition} entry={entry} />\n    case \"select\":\n      return <FilterSelect definition={definition} entry={entry} />\n    case \"multi-select\":\n      return <FilterMultiSelect definition={definition} entry={entry} />\n    case \"number\":\n      return <FilterNumber definition={definition} entry={entry} />\n    case \"date\":\n      return <FilterDate definition={definition} entry={entry} />\n    case \"date-range\":\n      return <FilterDateRange definition={definition} entry={entry} />\n    case \"boolean\":\n      return <FilterBoolean definition={definition} entry={entry} />\n    case \"custom\":\n      return <FilterCustom definition={definition} entry={entry} />\n    default:\n      return null\n  }\n}\n\n/* ------------------------------------------------------------------ */\n/* Operators                                                           */\n/* ------------------------------------------------------------------ */\n\nconst OPERATOR_ARIA_SUFFIX = \" rule\"\n\nfunction OperatorChoice({\n  definition,\n  current,\n  onSelect,\n}: {\n  definition: FilterDefinition\n  current: string | undefined\n  onSelect: (operator: string) => void\n}) {\n  const { disabled } = useFilterBarContext(\"OperatorChoice\")\n  const { description } = useFilterFieldIds()\n  const operators = getOperatorsFor(definition)\n  const resolved = current ?? resolveDefaultOperator(definition)\n\n  // A rule picker adds nothing until one value can mean several things.\n  if (operators.length <= 1) return null\n\n  return (\n    <Select\n      value={resolved}\n      disabled={disabled || definition.disabled}\n      onValueChange={(next) => {\n        if (next != null) onSelect(next)\n      }}\n    >\n      <SelectTrigger\n        size=\"small\"\n        className=\"h-(--hui-space-10) w-full\"\n        aria-label={`${definition.label}${OPERATOR_ARIA_SUFFIX}`}\n        aria-describedby={\n          definition.disabledReason ? description : undefined\n        }\n      >\n        <SelectValue>{OPERATOR_LABELS[resolved] ?? resolved}</SelectValue>\n      </SelectTrigger>\n      <SelectContent>\n        {operators.map((operator) => (\n          <SelectItem key={operator} value={operator}>\n            {OPERATOR_LABELS[operator] ?? operator}\n          </SelectItem>\n        ))}\n      </SelectContent>\n    </Select>\n  )\n}\n\nfunction RuleValueRow({\n  definition,\n  current,\n  onSelect,\n  children,\n}: {\n  definition: FilterDefinition\n  current: string | undefined\n  onSelect: (operator: string) => void\n  children?: React.ReactNode\n}) {\n  if (getOperatorsFor(definition).length <= 1) return children\n\n  return (\n    <div\n      className={cn(\n        \"grid min-w-0 items-center gap-(--hui-space-2)\",\n        children &&\n          \"grid-cols-[minmax(8rem,10rem)_minmax(0,1fr)] sm:grid-cols-[minmax(10rem,12rem)_minmax(0,1fr)]\",\n        !children && \"grid-cols-[minmax(10rem,12rem)]\"\n      )}\n      data-slot=\"filter-bar-rule-value-row\"\n    >\n      <OperatorChoice\n        definition={definition}\n        current={current}\n        onSelect={onSelect}\n      />\n      {children}\n    </div>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/* Text                                                                */\n/* ------------------------------------------------------------------ */\n\nfunction FilterText({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  return (\n    <FilterField definition={definition} hideLabel>\n      <FilterTextControl definition={definition} entry={entry} />\n    </FilterField>\n  )\n}\n\nfunction FilterTextControl({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  const { setEntry, removeEditing, disabled } =\n    useFilterBarContext(\"FilterText\")\n  const { control, label, description } = useFilterFieldIds()\n  const operator = entry?.operator ?? resolveDefaultOperator(definition)\n  const hidesInput = operator === \"is-empty\" || operator === \"is-not-empty\"\n  const value = typeof entry?.value === \"string\" ? entry.value : \"\"\n\n  return (\n    <>\n      <RuleValueRow\n        definition={definition}\n        current={entry?.operator}\n        onSelect={(next) =>\n          setEntry(definition.key, hidesInput ? undefined : value, next)\n        }\n      >\n        {hidesInput ? null : (\n        <Input\n          id={control}\n          aria-labelledby={label}\n          aria-describedby={\n            definition.disabledReason ? description : undefined\n          }\n          type=\"text\"\n          value={value}\n          placeholder={\n            definition.meta?.placeholder ??\n            definition.placeholder ??\n            \"Search value...\"\n          }\n          onChange={(event) =>\n            event.target.value.trim() === \"\"\n              ? removeEditing(definition.key)\n              : setEntry(definition.key, event.target.value)\n          }\n          disabled={disabled || definition.disabled}\n        />\n        )}\n      </RuleValueRow>\n      <DisabledReasonNotice definition={definition} />\n    </>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/* Single choice                                                       */\n/* ------------------------------------------------------------------ */\n\nfunction FilterSelect({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  return (\n    <FilterField definition={definition} hideLabel>\n      <FilterSelectControl definition={definition} entry={entry} />\n    </FilterField>\n  )\n}\n\nfunction FilterSelectControl({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  const { setEntry, removeEditing, disabled } =\n    useFilterBarContext(\"FilterSelect\")\n  const { description } = useFilterFieldIds()\n  const hasSelection = entry?.value != null && entry.value !== \"\"\n  const selected = hasSelection ? String(entry?.value) : null\n\n  return (\n    <>\n      <RuleValueRow\n        definition={definition}\n        current={entry?.operator}\n        onSelect={(next) =>\n          setEntry(definition.key, hasSelection ? entry?.value : undefined, next)\n        }\n      >\n        <Select\n          value={selected}\n          disabled={disabled || definition.disabled}\n          onValueChange={(value) => {\n            if (value == null) {\n              removeEditing(definition.key)\n              return\n            }\n\n            setEntry(definition.key, resolveFilterOptionValue(definition, value))\n          }}\n        >\n          <SelectTrigger\n            size=\"small\"\n            className=\"h-(--hui-space-10) w-full\"\n            aria-label={definition.label}\n            aria-describedby={\n              definition.disabledReason ? description : undefined\n            }\n          >\n            <SelectValue placeholder={`Any ${definition.label.toLowerCase()}`} />\n          </SelectTrigger>\n          <SelectContent>\n            {(definition.options ?? []).map((option) => (\n              <SelectItem\n                key={optionKey(option)}\n                value={optionKey(option)}\n                disabled={option.disabled}\n              >\n                {option.label}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n      </RuleValueRow>\n      <DisabledReasonNotice definition={definition} />\n    </>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/* Multi choice                                                        */\n/* ------------------------------------------------------------------ */\n\ninterface AsyncListSnapshot {\n  options: FilterOption[]\n  query: string\n  status: \"idle\" | \"loading\" | \"error\"\n}\n\nconst ASYNC_REQUEST_DEBOUNCE_MS = 250\n\n/**\n * Checkbox list behind multi-select filters. Search runs locally until a\n * loader exists, selected values survive filtering, and failures never wipe\n * what the user already sees.\n */\nfunction MultiChoiceList({\n  definition,\n  selectedValues,\n  onToggle,\n}: {\n  definition: FilterDefinition\n  selectedValues: Set<string>\n  onToggle: (option: FilterOption, checked: boolean) => void\n}) {\n  const { labels, searchThreshold, showOptionCounts, disabled } =\n    useFilterBarContext(\"FilterMultiSelect\")\n  const { label, description } = useFilterFieldIds()\n  const isDisabled = disabled || Boolean(definition.disabled)\n  const EMPTY_OPTIONS: FilterOption[] = []\n  const staticOptions = definition.options ?? EMPTY_OPTIONS\n  const loadOptions = definition.loadOptions\n\n  const searchesLocally =\n    definition.searchable ??\n    (!loadOptions && staticOptions.length > Math.max(searchThreshold, 0))\n\n  const [query, setQuery] = React.useState(\"\")\n  const [retryToken, setRetryToken] = React.useState(0)\n  const requestCounter = React.useRef(0)\n  const debounceTimerRef = React.useRef<number | undefined>(undefined)\n  const [snapshot, setSnapshot] = React.useState<AsyncListSnapshot>({\n    options: [],\n    query: \"\",\n    status: \"idle\",\n  })\n\n  React.useEffect(() => {\n    if (!loadOptions) return\n\n    // Requests cancel by comparing nonces; aborting fetches the app owns\n    // stays out of scope here.\n    const nonce = ++requestCounter.current\n\n    debounceTimerRef.current = window.setTimeout(() => {\n      setSnapshot((current) => ({ ...current, status: \"loading\" }))\n      loadOptions(query)\n        .then((options) => {\n          if (requestCounter.current !== nonce) return\n          setSnapshot({ options, query, status: \"idle\" })\n        })\n        .catch(() => {\n          if (requestCounter.current !== nonce) return\n          // Previous results stay on screen; only status changes.\n          setSnapshot((current) => ({ ...current, status: \"error\" }))\n        })\n    }, ASYNC_REQUEST_DEBOUNCE_MS)\n\n    return () => window.clearTimeout(debounceTimerRef.current)\n  }, [query, retryToken, loadOptions])\n\n  const baseOptions = loadOptions ? snapshot.options : staticOptions\n\n  const matched = React.useMemo(() => {\n    if (loadOptions) {\n      return baseOptions\n    }\n\n    const needle = query.trim().toLowerCase()\n\n    if (!searchesLocally || !needle) return baseOptions\n\n    return baseOptions.filter((option) =>\n      option.label.toLowerCase().includes(needle)\n    )\n  }, [baseOptions, query, searchesLocally, loadOptions])\n\n  // Selected values filtered out of view stay listed so users find them.\n  const visible = React.useMemo(() => {\n    const matchedKeys = new Set(matched.map(optionKey))\n    const missingSelected = [...selectedValues]\n      .filter((key) => !matchedKeys.has(key))\n      .map((key) =>\n        [...staticOptions, ...matched].find(\n          (option) => optionKey(option) === key\n        )\n      )\n      .filter((option): option is FilterOption => option != null)\n\n    return [...missingSelected, ...matched]\n  }, [matched, selectedValues, staticOptions])\n\n  const isLoading = Boolean(loadOptions) && snapshot.status === \"loading\"\n  const isError = Boolean(loadOptions) && snapshot.status === \"error\"\n  const emptyResult =\n    !isLoading && (searchesLocally || loadOptions) && visible.length === 0\n  const scrollable = visible.length > 6\n\n  return (\n    <div\n      className=\"flex min-w-0 flex-col gap-(--hui-space-2)\"\n      data-slot=\"filter-bar-option-list-root\"\n    >\n      {searchesLocally || loadOptions ? (\n        <Input\n          type=\"search\"\n          value={query}\n          placeholder={\n            definition.placeholder ?? `Search ${definition.label.toLowerCase()}...`\n          }\n          aria-label={`Search ${definition.label}`}\n          aria-describedby={\n            definition.disabledReason ? description : undefined\n          }\n          onChange={(event) => setQuery(event.target.value)}\n          disabled={isDisabled}\n        />\n      ) : null}\n\n      <ScrollArea\n        className={cn(\n          scrollable && \"max-h-[calc(var(--hui-space-9)*4)]\"\n        )}\n      >\n        <ul\n          className=\"flex w-full flex-col gap-px pb-1\"\n          aria-labelledby={label}\n          aria-describedby={\n            definition.disabledReason ? description : undefined\n          }\n          aria-busy={isLoading || undefined}\n          data-slot=\"filter-bar-option-list\"\n        >\n          {visible.map((option) => {\n            const checked = selectedValues.has(optionKey(option))\n\n            return (\n              <li key={optionKey(option)}>\n                <Label\n                  className=\"flex! min-h-(--hui-space-8) cursor-pointer items-center justify-between gap-(--hui-space-2) rounded-[var(--hui-radius-2)] px-(--hui-space-2) py-(--hui-space-2) text-[var(--hui-color-foreground-base-primary)] [font-size:var(--hui-font-size-small)] [line-height:var(--hui-line-height-small)] hover:bg-[var(--hui-color-background-base-primary-hover)] data-checked:bg-[var(--hui-color-background-neutral-secondary)] data-disabled:cursor-not-allowed data-disabled:opacity-50\"\n                  data-slot=\"filter-bar-option-row\"\n                  data-checked={checked || undefined}\n                  data-disabled={\n                    option.disabled || isDisabled || undefined\n                  }\n                >\n                  <span className=\"flex min-w-0 items-center gap-(--hui-space-3)\">\n                    <Checkbox\n                      aria-label={option.label}\n                      checked={checked}\n                      disabled={option.disabled || isDisabled}\n                      onCheckedChange={(checked) =>\n                        onToggle(option, checked === true)\n                      }\n                    />\n                    <span className=\"truncate\">{option.label}</span>\n                  </span>\n                  {showOptionCounts && option.count != null ? (\n                    <span\n                      className=\"shrink-0 tabular-nums text-[var(--hui-color-foreground-base-primary)] [font-size:var(--hui-font-size-mini)]\"\n                      data-slot=\"filter-bar-option-count\"\n                    >\n                      {option.count.toLocaleString()}\n                    </span>\n                  ) : null}\n                </Label>\n              </li>\n            )\n          })}\n\n          {isLoading && snapshot.options.length === 0\n            ? [\n                { width: \"100%\", key: \"row-1\" },\n                { width: \"72%\", key: \"row-2\" },\n                { width: \"46%\", key: \"row-3\" },\n              ].map((row) => (\n                <li\n                  key={row.key}\n                  aria-hidden=\"true\"\n                  className=\"py-[calc(var(--hui-space-1)+2px)]\"\n                >\n                  <Skeleton className=\"h-5\" style={{ width: row.width }} />\n                </li>\n              ))\n            : null}\n        </ul>\n\n        {isLoading && snapshot.options.length > 0 ? (\n          <p\n            role=\"status\"\n            className=\"mt-1 flex items-center gap-(--hui-space-2) text-[var(--hui-color-foreground-base-secondary)] [font-size:var(--hui-font-size-micro)]\"\n          >\n            <LoaderCircleIcon aria-hidden className=\"size-3 animate-spin\" />\n            {labels.loadingOptions}\n          </p>\n        ) : null}\n      </ScrollArea>\n\n      {isError ? (\n        <p className=\"flex flex-col gap-(--hui-space-1)\">\n          <span className=\"text-[var(--hui-color-foreground-danger-primary)] [font-size:var(--hui-font-size-micro)]\">\n            {labels.optionsError}\n          </span>\n          <Button\n            variant=\"link\"\n            size=\"sm\"\n            disabled={isDisabled}\n            onClick={() => setRetryToken((token) => token + 1)}\n          >\n            {labels.retryOptions}\n          </Button>\n        </p>\n      ) : null}\n\n      {emptyResult ? (\n        <Empty className=\"border-0 bg-transparent py-(--hui-space-3)\">\n          <EmptyHeader>\n            <EmptyTitle>No matches</EmptyTitle>\n            <EmptyDescription>{labels.noMatches(query)}</EmptyDescription>\n          </EmptyHeader>\n        </Empty>\n      ) : null}\n\n      <DisabledReasonNotice definition={definition} />\n    </div>\n  )\n}\n\nfunction FilterMultiSelect({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  const { setEntry } = useFilterBarContext(\"FilterMultiSelect\")\n\n  const selectedEntries = Array.isArray(entry?.value) ? entry.value : []\n  const selectedValues = new Set(selectedEntries.map(String))\n\n  function toggleOption(option: FilterOption, checked: boolean) {\n    const nextValues = new Map(\n      selectedEntries.map((selected) => [String(selected), selected])\n    )\n\n    if (checked) {\n      nextValues.set(optionKey(option), option.value)\n    } else {\n      nextValues.delete(optionKey(option))\n    }\n\n    setEntry(definition.key, [...nextValues.values()], \"is\")\n  }\n\n  return (\n    <FilterField definition={definition} hideLabel>\n      <MultiChoiceList\n        definition={definition}\n        selectedValues={selectedValues}\n        onToggle={toggleOption}\n      />\n    </FilterField>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/* Number                                                              */\n/* ------------------------------------------------------------------ */\n\nfunction finiteNumber(input: unknown): number | undefined {\n  return typeof input === \"number\" && Number.isFinite(input) ? input : undefined\n}\n\nfunction AmountInput({\n  definition,\n  idSuffix,\n  amount,\n  onAmountChange,\n}: {\n  definition: FilterDefinition\n  idSuffix: string\n  amount: number | undefined\n  onAmountChange: (amount: number | undefined) => void\n}) {\n  const { control, description } = useFilterFieldIds()\n  const { disabled } = useFilterBarContext(\"AmountInput\")\n  const { prefix, suffix, min, max, step, maximumFractionDigits } =\n    definition.meta ?? {}\n\n  return (\n    <NumberField\n      id={`${control}-${idSuffix}`}\n      value={amount ?? null}\n      min={min}\n      max={max}\n      step={step}\n      format={{\n        maximumFractionDigits: maximumFractionDigits ?? 2,\n      }}\n      onValueChange={(next) => onAmountChange(finiteNumber(next))}\n      disabled={disabled || definition.disabled}\n      className=\"w-full min-w-0\"\n    >\n      <div className=\"flex h-(--hui-space-10) w-full min-w-0 items-center overflow-hidden rounded-[var(--hui-radius-2)] border-[0.5px] border-[var(--hui-color-border-base-tertiary)] bg-[var(--hui-color-background-base-primary)] focus-within:border-[var(--hui-color-border-accent-emphasis)]\">\n        {prefix ? (\n          <span\n            aria-hidden=\"true\"\n            className=\"pl-(--hui-space-2) text-[var(--hui-color-foreground-base-secondary)] [font-size:var(--hui-font-size-small)]\"\n          >\n            {prefix}\n          </span>\n        ) : null}\n        <NumberFieldInput\n          aria-label={\n            idSuffix === \"from\"\n              ? `${definition.label} minimum`\n              : idSuffix === \"to\"\n                ? `${definition.label} maximum`\n                : definition.label\n          }\n          aria-describedby={\n            definition.disabledReason ? description : undefined\n          }\n          className=\"h-full min-w-0 rounded-none border-0 bg-transparent px-(--hui-space-2)\"\n        />\n        {suffix ? (\n          <span\n            aria-hidden=\"true\"\n            className=\"pr-(--hui-space-2) text-[var(--hui-color-foreground-base-secondary)] [font-size:var(--hui-font-size-small)]\"\n          >\n            {suffix}\n          </span>\n        ) : null}\n      </div>\n    </NumberField>\n  )\n}\n\nfunction FilterNumber({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  const { setEntry } = useFilterBarContext(\"FilterNumber\")\n  const operator = entry?.operator ?? resolveDefaultOperator(definition)\n  const between = operator === \"between\"\n  const hidesInput = operator === \"is-empty\" || operator === \"is-not-empty\"\n  const range =\n    between && typeof entry?.value === \"object\" && entry.value != null\n      ? (entry.value as { min?: number; max?: number })\n      : {}\n\n  function writeRange(part: \"min\" | \"max\", amount: number | undefined) {\n    const merged = { ...range, [part]: amount }\n\n    if (merged.min == null && merged.max == null) {\n      setEntry(definition.key, null)\n      return\n    }\n\n    setEntry(definition.key, merged, \"between\")\n  }\n\n  return (\n    <FilterField definition={definition} hideLabel>\n      <RuleValueRow\n        definition={definition}\n        current={entry?.operator}\n        onSelect={(next) =>\n          setEntry(definition.key, entry?.value ?? undefined, next)\n        }\n      >\n        {between ? (\n        <div className=\"flex w-full min-w-0 items-center gap-(--hui-space-2)\">\n          <AmountInput\n            definition={definition}\n            idSuffix=\"from\"\n            amount={finiteNumber(range.min)}\n            onAmountChange={(amount) => writeRange(\"min\", amount)}\n          />\n          <span\n            aria-hidden=\"true\"\n            className=\"shrink-0 text-[var(--hui-color-foreground-base-secondary)] [font-size:var(--hui-font-size-micro)]\"\n          >\n            to\n          </span>\n          <AmountInput\n            definition={definition}\n            idSuffix=\"to\"\n            amount={finiteNumber(range.max)}\n            onAmountChange={(amount) => writeRange(\"max\", amount)}\n          />\n        </div>\n        ) : hidesInput ? null : (\n        <AmountInput\n          definition={definition}\n          idSuffix=\"amount\"\n          amount={finiteNumber(entry?.value)}\n          onAmountChange={(amount) =>\n            setEntry(definition.key, amount ?? null)\n          }\n        />\n        )}\n      </RuleValueRow>\n      <DisabledReasonNotice definition={definition} />\n    </FilterField>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/* Dates                                                               */\n/* ------------------------------------------------------------------ */\n\nfunction toDateInputString(date: unknown): string {\n  if (!(date instanceof Date) || isNaN(date.getTime())) return \"\"\n\n  const year = String(date.getFullYear()).padStart(4, \"0\")\n  const month = String(date.getMonth() + 1).padStart(2, \"0\")\n\n  return `${year}-${month}-${String(date.getDate()).padStart(2, \"0\")}`\n}\n\nfunction fromDateInputString(input: string): Date | undefined {\n  if (!input) return undefined\n\n  const [year, month, day] = input.split(\"-\").map(Number)\n\n  if (!year || !month || !day) return undefined\n\n  return new Date(year, month - 1, day)\n}\n\nfunction FilterDate({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  return (\n    <FilterField definition={definition} hideLabel>\n      <FilterDateControl definition={definition} entry={entry} />\n    </FilterField>\n  )\n}\n\nfunction FilterDateControl({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  const { setEntry, disabled } = useFilterBarContext(\"FilterDate\")\n  const { control, label, description } = useFilterFieldIds()\n  const operator = entry?.operator ?? resolveDefaultOperator(definition)\n  const presets = definition.meta?.datePresets\n  const pickedDay = entry?.value instanceof Date ? entry.value : undefined\n  const activePresetLabel =\n    pickedDay != null && presets\n      ? presets.find((preset) => {\n          const resolved = preset.value()\n\n          if (resolved.to != null) {\n            // Range presets belong to FilterDateRange.\n            return false\n          }\n\n          return resolved.from != null && isSameDay(resolved.from, pickedDay)\n        })?.label ?? \"\"\n      : \"\"\n\n  return (\n    <>\n      <RuleValueRow\n        definition={definition}\n        current={entry?.operator}\n        onSelect={(next) =>\n          setEntry(definition.key, pickedDay ?? undefined, next)\n        }\n      >\n        <Input\n          id={control}\n          type=\"date\"\n          value={toDateInputString(pickedDay)}\n          onChange={(event) => {\n            const date = fromDateInputString(event.target.value)\n\n            setEntry(definition.key, date ?? null)\n          }}\n          disabled={disabled || definition.disabled}\n          aria-describedby={\n            definition.disabledReason ? description : undefined\n          }\n          aria-label={\n            presets && presets.length > 0\n              ? `${definition.label}, exact date`\n              : `${definition.label} ${operator.replace(/-/g, \" \")}`\n          }\n        />\n      </RuleValueRow>\n      {presets && presets.length > 0 ? (\n        <RadioGroup\n          aria-labelledby={label}\n          aria-describedby={\n            definition.disabledReason ? description : undefined\n          }\n          disabled={disabled || definition.disabled}\n          value={activePresetLabel}\n          onValueChange={(next) => {\n            const preset = presets.find((candidate) => candidate.label === next)\n\n            if (!preset) {\n              setEntry(definition.key, null)\n              return\n            }\n\n            const resolved = preset.value()\n\n            if (resolved.from) {\n              setEntry(definition.key, resolved.from)\n            }\n          }}\n          className=\"gap-y-0.5\"\n        >\n          {presets.map((preset) => (\n            <Label\n              key={preset.label}\n              className=\"-mx-(--hui-space-2) rounded-[var(--hui-radius-2)] px-(--hui-space-2) py-(--hui-space-1) hover:bg-[var(--hui-color-background-base-primary-hover)]\"\n            >\n              <Radio\n                value={preset.label}\n                disabled={disabled || definition.disabled}\n              />\n              {preset.label}\n            </Label>\n          ))}\n        </RadioGroup>\n      ) : null}\n      <DisabledReasonNotice definition={definition} />\n    </>\n  )\n}\n\ntype PickerPresets = NonNullable<\n  React.ComponentProps<typeof DateRangePicker>[\"presets\"]\n>\n\n\n\nfunction adaptDateRangePresets(\n  presets: NonNullable<FilterDefinition[\"meta\"]>[\"datePresets\"]\n): PickerPresets {\n  if (!presets) {\n    return getDateRangePresets() as unknown as PickerPresets\n  }\n\n  const source = presets\n\n  return source.map((preset) => ({\n    label: preset.label,\n    getValue: () => {\n      const resolved = preset.value()\n\n      return {\n        from: resolved.from instanceof Date ? resolved.from : undefined,\n        to: resolved.to instanceof Date ? resolved.to : undefined,\n      } as DateRange\n    },\n  }))\n}\n\nfunction FilterDateRange({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  return (\n    <FilterField definition={definition} hideLabel>\n      <FilterDateRangeControl definition={definition} entry={entry} />\n    </FilterField>\n  )\n}\n\nfunction FilterDateRangeControl({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  const { setEntry, removeEditing, disabled } =\n    useFilterBarContext(\"FilterDateRange\")\n  const { description } = useFilterFieldIds()\n  const raw = (entry?.value ?? {}) as { from?: Date; to?: Date }\n  const value: DateRange = {\n    from: raw.from instanceof Date ? raw.from : undefined,\n    to: raw.to instanceof Date ? raw.to : undefined,\n  }\n  const complete = value.from != null && value.to != null\n  const pickerPresets = React.useMemo(\n    () => adaptDateRangePresets(definition.meta?.datePresets),\n    [definition.meta?.datePresets]\n  )\n\n  return (\n    <>\n      <DateRangePicker\n        value={complete ? value : undefined}\n        onValueChange={(next) => {\n          if (next?.from && next.to) {\n            setEntry(definition.key, { from: next.from, to: next.to })\n            return\n          }\n\n          removeEditing(definition.key)\n        }}\n        presets={pickerPresets}\n        confirmMode\n        numberOfMonths={1}\n        clearable={false}\n        disabled={disabled || definition.disabled}\n        aria-label={definition.label}\n        aria-describedby={\n          definition.disabledReason ? description : undefined\n        }\n      />\n      <DisabledReasonNotice definition={definition} />\n    </>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/* Boolean                                                             */\n/* ------------------------------------------------------------------ */\n\nconst BOOLEAN_CHOICES = [\n  { label: \"Any\", stored: \"any\" },\n  { label: \"Yes\", stored: \"yes\" },\n  { label: \"No\", stored: \"no\" },\n] as const\n\nfunction BooleanRadioChoice({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  const { setEntry, removeEditing, disabled } =\n    useFilterBarContext(\"FilterBoolean\")\n  const { label, description } = useFilterFieldIds()\n\n  return (\n    <RadioGroup\n      aria-labelledby={label}\n      aria-describedby={\n        definition.disabledReason ? description : undefined\n      }\n      disabled={disabled || definition.disabled}\n      value={\n        entry?.value === true ? \"yes\" : entry?.value === false ? \"no\" : \"any\"\n      }\n      onValueChange={(next) => {\n        if (next === \"any\") {\n          removeEditing(definition.key)\n          return\n        }\n\n        setEntry(definition.key, next === \"yes\")\n      }}\n      className=\"gap-y-0.5\"\n    >\n      {BOOLEAN_CHOICES.map((choice) => (\n        <Label\n          key={choice.stored}\n          className=\"-mx-(--hui-space-2) rounded-[var(--hui-radius-2)] px-(--hui-space-2) py-(--hui-space-1) hover:bg-[var(--hui-color-background-base-primary-hover)]\"\n        >\n          <Radio\n            value={choice.stored}\n            disabled={disabled || definition.disabled}\n          />\n          {choice.label}\n        </Label>\n      ))}\n    </RadioGroup>\n  )\n}\n\nfunction BooleanSwitchChoice({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  const { setEntry, disabled } = useFilterBarContext(\"FilterBoolean\")\n  const { label, description } = useFilterFieldIds()\n\n  // The switch keeps Yes/No meaning even when off, so the chip reports state\n  // while the filter being present means active.\n  return (\n    <div className=\"flex items-center justify-end\">\n      <Switch\n        aria-labelledby={label}\n        aria-describedby={\n          definition.disabledReason ? description : undefined\n        }\n        checked={entry?.value === true}\n        onCheckedChange={(checked) => setEntry(definition.key, checked, \"is\")}\n        disabled={disabled || definition.disabled}\n      />\n    </div>\n  )\n}\n\nfunction FilterBoolean({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  const style = definition.meta?.booleanStyle ?? \"radio\"\n\n  const control =\n    style === \"switch\" ? (\n      <BooleanSwitchChoice definition={definition} entry={entry} />\n    ) : (\n      <BooleanRadioChoice definition={definition} entry={entry} />\n    )\n\n  return (\n    <FilterField definition={definition} hideLabel>\n      {control}\n      <DisabledReasonNotice definition={definition} />\n    </FilterField>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/* Custom renderer                                                     */\n/* ------------------------------------------------------------------ */\n\nfunction FilterCustom({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  return (\n    <FilterField definition={definition} hideLabel>\n      <FilterCustomControl definition={definition} entry={entry} />\n    </FilterField>\n  )\n}\n\nfunction FilterCustomControl({\n  definition,\n  entry,\n}: {\n  definition: FilterDefinition\n  entry?: FilterValue\n}) {\n  const { setEntry, removeEditing, disabled } =\n    useFilterBarContext(\"FilterCustom\")\n  const { label, description } = useFilterFieldIds()\n\n  if (!definition.render) return null\n\n  return (\n    <>\n      {definition.render({\n        value: entry?.value,\n        operator: entry?.operator,\n        onChange: (next) =>\n          next == null\n            ? removeEditing(definition.key)\n            : setEntry(definition.key, next),\n        clear: () => removeEditing(definition.key),\n        disabled: disabled || Boolean(definition.disabled),\n        definition,\n        labelId: label,\n        descriptionId: definition.disabledReason ? description : undefined,\n      })}\n      <DisabledReasonNotice definition={definition} />\n    </>\n  )\n}\n"},{"path":"filter-bar-types.ts","type":"registry:component","target":"components/ui/filter-bar/filter-bar-types.ts","content":"import type * as React from \"react\"\n\nexport type FilterType =\n  | \"text\"\n  | \"select\"\n  | \"multi-select\"\n  | \"number\"\n  | \"date\"\n  | \"date-range\"\n  | \"boolean\"\n  | \"custom\"\n\nexport type TextOperator =\n  | \"contains\"\n  | \"does-not-contain\"\n  | \"is\"\n  | \"is-not\"\n  | \"starts-with\"\n  | \"ends-with\"\n  | \"is-empty\"\n  | \"is-not-empty\"\n\nexport type SelectOperator = \"is\" | \"is-not\"\n\nexport type NumberOperator =\n  | \"equals\"\n  | \"not-equals\"\n  | \"greater-than\"\n  | \"greater-than-or-equal\"\n  | \"less-than\"\n  | \"less-than-or-equal\"\n  | \"between\"\n  | \"is-empty\"\n  | \"is-not-empty\"\n\nexport type DateOperator = \"on\" | \"before\" | \"after\"\n\nexport type BooleanOperator = \"is\" | \"any\"\n\nexport type FilterOperator =\n  | TextOperator\n  | SelectOperator\n  | NumberOperator\n  | DateOperator\n  | BooleanOperator\n\n/**\n * One active filter. This is Filter Bar UI state; applications transform it\n * into their own query shape for tables, URLs, or APIs.\n */\nexport interface FilterValue {\n  key: string\n  operator?: string\n  value: unknown\n}\n\n/** One choice inside a select, multi-select, radio, or checkbox list. */\nexport interface FilterOption {\n  label: string\n  /** Use strings or numbers so defaults like chips and forms stay simple. */\n  value: string | number\n  count?: number\n  disabled?: boolean\n}\n\nexport interface FilterTextMeta {\n  placeholder?: string\n}\n\nexport interface FilterNumberMeta {\n  prefix?: string\n  suffix?: string\n  min?: number\n  max?: number\n  step?: number\n  /** Rough number formatting controls forwarded to NumberField. */\n  maximumFractionDigits?: number\n}\n\nexport interface FilterDateRangePreset {\n  label: string\n  value: () => { from: Date; to?: Date }\n}\n\nexport interface FilterDefinition {\n  key: string\n  label: string\n  type: FilterType\n  /** Operators follow each type's documented order. */\n  operators?: FilterOperator[]\n  options?: FilterOption[]\n  /** Turns on search below this many options too. */\n  searchable?: boolean\n  /** Options may change after other selections do. */\n  disabled?: boolean\n  /** Explain why a disabled filter cannot be used yet. */\n  disabledReason?: string\n  defaultOperator?: string\n  /** Replaces the automatic chip text for one filter. */\n  formatValue?: (value: unknown) => string\n  /** Lower sorts earlier in the panel and among initial values. */\n  priority?: number\n  placeholder?: string\n  /** Extra settings passed straight to the matching control. */\n  meta?: FilterTextMeta &\n    FilterNumberMeta & {\n      datePresets?: FilterDateRangePreset[]\n      /** \"switch\" hides yes/no uncertainty behind one toggle. */\n      booleanStyle?: \"radio\" | \"switch\"\n    }\n  /** Loads server-backed options. Provide your own debounce when needed. */\n  loadOptions?: (query: string) => Promise<FilterOption[]>\n  /** Renders controls for values outside the built-in types. */\n  render?: FilterCustomRenderer\n}\n\nexport interface FilterRenderProps {\n  value: unknown\n  operator?: string\n  onChange: (value: unknown) => void\n  clear: () => void\n  disabled: boolean\n  definition: FilterDefinition\n  /** Connect the custom control to Filter Bar's field label. */\n  labelId: string\n  /** Connect the custom control to its disabled explanation when present. */\n  descriptionId?: string\n}\n\ntype FilterCustomRenderer = (props: FilterRenderProps) => React.ReactNode\n"},{"path":"filter-bar-utils.ts","type":"registry:component","target":"components/ui/filter-bar/filter-bar-utils.ts","content":"import type {\n  FilterDefinition,\n  FilterOperator,\n  FilterType,\n  FilterValue,\n} from \"./filter-bar-types\"\n\nexport const OPERATOR_LABELS: Record<string, string> = {\n  contains: \"Contains\",\n  \"does-not-contain\": \"Does not contain\",\n  is: \"Is\",\n  \"is-not\": \"Is not\",\n  \"starts-with\": \"Starts with\",\n  \"ends-with\": \"Ends with\",\n  \"is-empty\": \"Is empty\",\n  \"is-not-empty\": \"Is not empty\",\n  equals: \"Equals\",\n  \"not-equals\": \"Does not equal\",\n  \"greater-than\": \"Greater than\",\n  \"greater-than-or-equal\": \"Greater than or equal\",\n  \"less-than\": \"Less than\",\n  \"less-than-or-equal\": \"Less than or equal\",\n  between: \"Between\",\n  on: \"On\",\n  before: \"Before\",\n  after: \"After\",\n  any: \"Any of\",\n}\n\nconst DEFAULT_OPERATORS: Record<FilterType, FilterOperator[]> = {\n  text: [\"contains\", \"does-not-contain\", \"is\", \"is-not\", \"starts-with\", \"ends-with\", \"is-empty\", \"is-not-empty\"],\n  select: [\"is\", \"is-not\"],\n  \"multi-select\": [\"is\"],\n  number: [\n    \"equals\",\n    \"not-equals\",\n    \"greater-than\",\n    \"greater-than-or-equal\",\n    \"less-than\",\n    \"less-than-or-equal\",\n    \"between\",\n    \"is-empty\",\n    \"is-not-empty\",\n  ],\n  date: [\"on\", \"before\", \"after\"],\n  \"date-range\": [\"between\"],\n  boolean: [\"is\"],\n  custom: [\"is\"],\n}\n\nexport function getDefaultOperators(type: FilterType) {\n  return DEFAULT_OPERATORS[type] ?? [\"is\"]\n}\n\nexport function getOperatorsFor(\n  definition: FilterDefinition\n): FilterOperator[] {\n  if (definition.operators && definition.operators.length > 0) {\n    return definition.operators\n  }\n\n  return getDefaultOperators(definition.type)\n}\n\nexport function resolveDefaultOperator(definition: FilterDefinition) {\n  const operators = getOperatorsFor(definition)\n\n  if (definition.defaultOperator && operators.includes(definition.defaultOperator as FilterOperator)) {\n    return definition.defaultOperator\n  }\n\n  return operators[0]\n}\n\n/** Operators where the selected rule carries the meaning on its own. */\nexport function isValuelessOperator(operator: string | undefined) {\n  return operator === \"is-empty\" || operator === \"is-not-empty\"\n}\n\nexport function needsTwoValues(operator: string | undefined) {\n  return operator === \"between\"\n}\n\nexport interface ResolvedDateRange {\n  from?: Date\n  to?: Date\n}\n\nfunction startOfDate(date: Date) {\n  return new Date(date.getFullYear(), date.getMonth(), date.getDate())\n}\n\nexport function isSameDay(a: Date | undefined, b: Date | undefined) {\n  if (!a || !b) return false\n\n  const aDay = startOfDate(a).getTime()\n  const bDay = startOfDate(b).getTime()\n\n  return aDay === bDay\n}\n\n/**\n * Matches committed ranges against known presets so chips can read\n * \"Last 30 days\" instead of two raw dates.\n */\nexport function matchDatePresets(\n  range: ResolvedDateRange,\n  presets: Array<{ label: string; value: () => ResolvedDateRange }>\n) {\n  if (!range.from || !range.to) return undefined\n\n  for (const preset of presets) {\n    const candidate = preset.value()\n\n    if (\n      candidate.from &&\n      candidate.to &&\n      isSameDay(candidate.from, range.from) &&\n      isSameDay(candidate.to, range.to)\n    ) {\n      return preset.label\n    }\n  }\n\n  return undefined\n}\n\nconst numberFormatCache = new Map<number, Intl.NumberFormat>()\n\nfunction formatNumberForDisplay(input: number, fractionDigits?: number) {\n  if (Number.isInteger(input)) {\n    return String(input)\n  }\n\n  const digits = fractionDigits ?? 2\n  let formatter = numberFormatCache.get(digits)\n\n  if (!formatter) {\n    formatter = new Intl.NumberFormat(undefined, {\n      maximumFractionDigits: digits,\n    })\n    numberFormatCache.set(digits, formatter)\n  }\n\n  return formatter.format(input)\n}\n\nfunction formatOptionLabels(definition: FilterDefinition, values: unknown[]) {\n  const labels = new Map(\n    (definition.options ?? []).map((option) => [String(option.value), option.label])\n  )\n\n  return values\n    .map((value) =>\n      value == null ? \"\" : labels.get(String(value)) ?? String(value)\n    )\n    .filter(Boolean)\n}\n\n/** Restores the public option value after string-only UI primitives select it. */\nexport function resolveFilterOptionValue(\n  definition: Pick<FilterDefinition, \"options\">,\n  key: string\n): string | number {\n  return (\n    definition.options?.find((option) => String(option.value) === key)?.value ??\n    key\n  )\n}\n\nfunction formatMultiSelectLabels(labels: string[]) {\n  if (labels.length <= 3) return labels.join(\", \")\n\n  return `${labels[0]} +${labels.length - 1}`\n}\n\nexport interface FilterNumberDisplayContext {\n  meta?: { prefix?: string; suffix?: string; maximumFractionDigits?: number }\n}\n\nfunction formatNumberTerm(\n  amount: unknown,\n  context: FilterNumberDisplayContext\n) {\n  if (typeof amount !== \"number\" || !Number.isFinite(amount)) return \"\"\n\n  const { prefix = \"\", suffix = \"\", maximumFractionDigits } =\n    context.meta ?? {}\n\n  return `${prefix}${formatNumberForDisplay(amount, maximumFractionDigits)}${suffix}`\n}\n\nfunction formatNumericPhrase(\n  definition: FilterDefinition,\n  operator: string,\n  value: unknown,\n  context: FilterNumberDisplayContext\n) {\n  const term = formatNumberTerm(value, context)\n\n  if (!term) return \"\"\n\n  switch (operator) {\n    case \"greater-than\":\n      return `Over ${term}`\n    case \"less-than\":\n      return `Under ${term}`\n    case \"greater-than-or-equal\":\n      return `${term} or higher`\n    case \"less-than-or-equal\":\n      return `${term} or lower`\n    case \"not-equals\":\n      return `Not ${term}`\n    default:\n      return term\n  }\n}\n\nfunction compactDate(date: Date) {\n  return date.toLocaleDateString(undefined, {\n    month: \"short\",\n    day: \"numeric\",\n  })\n}\n\n/** Returns the readable text shown inside an active filter chip. */\nexport function formatFilterValueText(\n  definition: FilterDefinition,\n  filterValue: FilterValue\n): string {\n  const operator = filterValue.operator ?? resolveDefaultOperator(definition)\n\n  if (isValuelessOperator(operator)) {\n    return OPERATOR_LABELS[operator] ?? operator\n  }\n\n  if (definition.formatValue) {\n    return definition.formatValue(filterValue.value)\n  }\n\n  switch (definition.type) {\n    case \"text\": {\n      return typeof filterValue.value === \"string\" ? filterValue.value : \"\"\n    }\n    case \"select\": {\n      return formatOptionLabels(definition, [filterValue.value]).join(\"\") ||\n        (filterValue.value == null ? \"\" : String(filterValue.value))\n    }\n    case \"multi-select\": {\n      const values = Array.isArray(filterValue.value)\n        ? filterValue.value\n        : [filterValue.value]\n\n      return formatMultiSelectLabels(formatOptionLabels(definition, values))\n    }\n    case \"number\": {\n      if (operator === \"between\" && typeof filterValue.value === \"object\") {\n        const range = filterValue.value as { min?: number; max?: number }\n        const min = formatNumberTerm(range.min, { meta: definition.meta })\n        const max = formatNumberTerm(range.max, { meta: definition.meta })\n\n        if (min && max) return `${min} to ${max}`\n\n        return min || max\n      }\n\n      return formatNumericPhrase(definition, operator, filterValue.value, {\n        meta: definition.meta,\n      })\n    }\n    case \"date\": {\n      if (filterValue.value instanceof Date && !isNaN(filterValue.value.getTime())) {\n        return filterValue.value.toLocaleDateString()\n      }\n\n      return typeof filterValue.value === \"string\"\n        ? filterValue.value\n        : \"\"\n    }\n    case \"date-range\": {\n      const range = filterValue.value as ResolvedDateRange\n\n      if (!range?.from && !range?.to) return \"\"\n\n      const presets = definition.meta?.datePresets ?? []\n      const presetLabel = matchDatePresets(range, presets)\n\n      if (presetLabel) return presetLabel\n\n      const parts = [\n        range.from ? compactDate(range.from) : \"\",\n        range.to ? compactDate(range.to) : \"\",\n      ].filter(Boolean)\n\n      return parts.join(\" to \")\n    }\n    case \"boolean\": {\n      if (filterValue.value === true) return \"Yes\"\n      if (filterValue.value === false) return \"No\"\n\n      return \"\"\n    }\n    default: {\n      if (typeof filterValue.value === \"string\") return filterValue.value\n      if (Array.isArray(filterValue.value)) {\n        return filterValue.value.map(String).join(\", \")\n      }\n      if (filterValue.value == null) return \"\"\n\n      return String(filterValue.value)\n    }\n  }\n}\n\nexport interface FilterDefinitionMap {\n  get(key: string): FilterDefinition | undefined\n}\n\nexport function createFilterDefinitionMap(\n  definitions: FilterDefinition[]\n): FilterDefinitionMap & { ordered(): FilterDefinition[] } {\n  const byKey = new Map(definitions.map((definition) => [definition.key, definition]))\n  const orderedDefinitions = [...definitions].sort((a, b) => {\n    const priorityA = a.priority ?? Number.MAX_SAFE_INTEGER\n    const priorityB = b.priority ?? Number.MAX_SAFE_INTEGER\n\n    return priorityA - priorityB\n  })\n\n  return {\n    get: (key) => byKey.get(key),\n    ordered: () => orderedDefinitions,\n  }\n}\n\nfunction findFilterEntry(\n  values: FilterValue[],\n  key: string\n): FilterValue | undefined {\n  return values.find((entry) => entry.key === key)\n}\n\nexport { findFilterEntry as getFilterEntry }\n\nfunction normalizeBoolean(\n  definition: FilterDefinition,\n  value: unknown\n): boolean {\n  return !(definition.type === \"boolean\" && value === false)\n}\n\nfunction hasMeaningfulScalarValue(value: unknown): boolean {\n  if (value == null) return false\n  if (typeof value === \"string\") return value.trim() !== \"\"\n  if (typeof value === \"number\") return Number.isFinite(value)\n  if (Array.isArray(value)) return value.length > 0\n  if (value instanceof Date) return !isNaN(value.getTime())\n  if (typeof value === \"object\") {\n    const record = value as Record<string, unknown>\n\n    return Object.values(record).some(hasMeaningfulScalarValue)\n  }\n\n  return Boolean(value)\n}\n\n/**\n * Whether one entry counts as active. Switch-style boolean filters stay\n * active while present so turning the switch off keeps its meaning.\n */\nexport function isActiveFilterValue(\n  definition: FilterDefinition | undefined,\n  entry: FilterValue | undefined\n): boolean {\n  if (!definition || !entry) return false\n\n  if (isValuelessOperator(entry.operator)) return true\n\n  if (normalizeBoolean(definition, entry.value)) {\n    return hasMeaningfulScalarValue(entry.value)\n  }\n\n  return true\n}\n\nexport function getActiveFilterValues(\n  values: FilterValue[],\n  definitions: Iterable<FilterDefinition>\n) {\n  const map = createFilterDefinitionMap([...definitions])\n\n  return values.filter((entry) => isActiveFilterValue(map.get(entry.key), entry))\n}\n\n/** The trigger count counts active fields, never selected options. */\nexport function getActiveFilterCount(values: FilterValue[], definitions: Iterable<FilterDefinition>) {\n  return getActiveFilterValues(values, definitions).length\n}\n"}],"dependencies":["honestui"],"registryDependencies":["https://www.honestui.com/r/badge.json","https://www.honestui.com/r/button.json","https://www.honestui.com/r/checkbox.json","https://www.honestui.com/r/date-range-picker.json","https://www.honestui.com/r/empty.json","https://www.honestui.com/r/input.json","https://www.honestui.com/r/label.json","https://www.honestui.com/r/number-field.json","https://www.honestui.com/r/popover.json","https://www.honestui.com/r/radio-group.json","https://www.honestui.com/r/scroll-area.json","https://www.honestui.com/r/select.json","https://www.honestui.com/r/separator.json","https://www.honestui.com/r/sheet.json","https://www.honestui.com/r/skeleton.json","https://www.honestui.com/r/switch.json","https://www.honestui.com/r/toolbar.json"]}