{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "autocomplete",
  "type": "registry:ui",
  "title": "Autocomplete",
  "description": "A autocomplete component",
  "dependencies": [
    "downshift"
  ],
  "registryDependencies": [
    "popover",
    "https://ui.ednesdayw.com/r/input.json"
  ],
  "files": [
    {
      "path": "src/registry/ui/autocomplete.tsx",
      "content": "// This code is based on downshift-shadcn-combobox by OmerMakesStuff\n// Source: https://github.com/OmerMakesStuff/downshift-shadcn-combobox\n// Licensed under the MIT License\n\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\nimport { Input, type InputProps } from \"@/components/ui/input\";\nimport { cva } from \"class-variance-authority\";\nimport {\n  type UseComboboxGetInputPropsReturnValue,\n  type UseComboboxProps,\n  type UseComboboxReturnValue,\n  useCombobox,\n} from \"downshift\";\nimport { CheckIcon, ChevronDownIcon } from \"lucide-react\";\nimport {\n  Children,\n  type ComponentPropsWithoutRef,\n  type PropsWithChildren,\n  type ReactElement,\n  createContext,\n  isValidElement,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from \"react\";\n\ntype AutocompleteItemType = {\n  label: string;\n  value: string;\n  disabled?: boolean;\n};\n\ntype AutocompleteContextValue = Partial<\n  Pick<\n    UseComboboxReturnValue<AutocompleteItemType>,\n    | \"getInputProps\"\n    | \"getToggleButtonProps\"\n    | \"getItemProps\"\n    | \"getMenuProps\"\n    | \"highlightedIndex\"\n    | \"inputValue\"\n    | \"isOpen\"\n    | \"selectedItem\"\n    | \"selectItem\"\n    | \"setInputValue\"\n  > & {\n    size: InputProps[\"size\"];\n    variant: InputProps[\"variant\"];\n    filteredItems: AutocompleteItemType[];\n    items: AutocompleteItemType[];\n    onItemsChange: (items: AutocompleteItemType[]) => void;\n    onChange: (value: string) => void;\n    openedOnce: boolean;\n  }\n>;\n\nconst AutocompleteContext = createContext<AutocompleteContextValue>(\n  {} as AutocompleteContextValue,\n);\n\nconst useAutocompleteContext = () => useContext(AutocompleteContext);\n\ntype AutocompleteProps = PropsWithChildren<{\n  value?: string;\n  onChange?: (value: string) => void;\n  size?: InputProps[\"size\"];\n  variant?: InputProps[\"variant\"];\n  filterItems?: (\n    search: string,\n    items: AutocompleteItemType[],\n  ) => AutocompleteItemType[];\n}>;\n\nconst { stateChangeTypes } = useCombobox;\n\nconst defaultFilter = (inputValue: string, items: AutocompleteItemType[]) => {\n  return items.filter(\n    (item) =>\n      !inputValue ||\n      item.label.toLowerCase().includes(inputValue.toLowerCase()),\n  );\n};\n\nconst Autocomplete = ({\n  size,\n  variant,\n  value,\n  onChange,\n  filterItems = defaultFilter,\n  children,\n}: AutocompleteProps) => {\n  const [items, setItems] = useState<AutocompleteItemType[]>([]);\n  const [filteredItems, setFilteredItems] = useState<AutocompleteItemType[]>(\n    [],\n  );\n  const [openedOnce, setOpenedOnce] = useState(false);\n\n  const stateReducer = useCallback<\n    NonNullable<UseComboboxProps<AutocompleteItemType>[\"stateReducer\"]>\n  >(\n    (prev, { type, changes }) => {\n      switch (type) {\n        case stateChangeTypes.InputChange: {\n          const filteredEnabledItems = filterItems(\n            changes.inputValue || prev.inputValue,\n            items,\n          ).filter(({ disabled }) => !disabled);\n          const highlightedIndex =\n            typeof changes.highlightedIndex === \"number\"\n              ? changes.highlightedIndex\n              : prev.highlightedIndex;\n\n          return {\n            ...changes,\n            highlightedIndex:\n              changes.inputValue &&\n              filteredEnabledItems.length > 0 &&\n              highlightedIndex < 0\n                ? 0\n                : changes.highlightedIndex,\n          };\n        }\n\n        case stateChangeTypes.InputBlur:\n        case stateChangeTypes.InputClick:\n        case stateChangeTypes.InputKeyDownEnter:\n        case stateChangeTypes.InputKeyDownEscape: {\n          if (changes.isOpen || !prev.isOpen)\n            return {\n              ...changes,\n              inputValue: prev.inputValue,\n              selectedItem: prev.selectedItem,\n            };\n          if (!prev.inputValue && prev.highlightedIndex < 0)\n            return {\n              ...changes,\n              inputValue: \"\",\n              selectedItem: null,\n            };\n\n          const inputValue =\n            changes.selectedItem?.label || prev.selectedItem?.label || \"\";\n          return { ...changes, inputValue };\n        }\n\n        default:\n          return changes;\n      }\n    },\n    [filterItems, items],\n  );\n\n  const {\n    getInputProps,\n    getToggleButtonProps,\n    getItemProps,\n    getMenuProps,\n    highlightedIndex,\n    inputValue,\n    isOpen,\n    selectedItem,\n    selectItem,\n    setInputValue,\n  } = useCombobox({\n    items: filteredItems,\n    itemToString: (item) => (item ? item.label : \"\"),\n    isItemDisabled: (item) => item.disabled ?? false,\n\n    selectedItem:\n      typeof value !== \"undefined\"\n        ? items.find((item) => item.value === value) || null\n        : undefined,\n    onSelectedItemChange: ({ selectedItem }) => {\n      onChange?.(selectedItem?.value || \"\");\n    },\n\n    stateReducer,\n  });\n\n  useEffect(() => {\n    if (isOpen && !openedOnce) setOpenedOnce(isOpen);\n  }, [isOpen, openedOnce]);\n\n  useEffect(() => {\n    setFilteredItems(filterItems(inputValue, items));\n  }, [filterItems, inputValue, items]);\n\n  return (\n    <AutocompleteContext.Provider\n      value={{\n        size,\n        variant,\n        filteredItems,\n        getInputProps,\n        getToggleButtonProps,\n        getItemProps,\n        getMenuProps,\n        highlightedIndex,\n        inputValue,\n        isOpen,\n        items,\n        onItemsChange: setItems,\n        onChange,\n        openedOnce,\n        selectedItem,\n        selectItem,\n        setInputValue,\n      }}\n    >\n      <Popover open={isOpen}>{children}</Popover>\n    </AutocompleteContext.Provider>\n  );\n};\n\ntype AutocompleteInputProps = Omit<\n  InputProps,\n  keyof UseComboboxGetInputPropsReturnValue | \"endContent\"\n>;\n\nconst AutocompleteInput = (props: AutocompleteInputProps) => {\n  const { getInputProps, getToggleButtonProps, size, variant } =\n    useAutocompleteContext();\n\n  return (\n    <PopoverTrigger className=\"group outline-hidden\">\n      <Input\n        size={size}\n        variant={variant}\n        {...props}\n        {...getInputProps?.()}\n        endContent={\n          <div aria-label=\"toggle menu\" {...getToggleButtonProps?.()}>\n            <ChevronDownIcon\n              className={cn(\n                \"cursor-default size-4 opacity-50 transition-transform duration-100\",\n                \"group-data-[state=open]:rotate-180\",\n              )}\n            />\n          </div>\n        }\n      />\n    </PopoverTrigger>\n  );\n};\n\ntype AutocompleteItemProps = AutocompleteItemType &\n  ComponentPropsWithoutRef<\"li\">;\n\nconst autocompleteItemVariants = cva(\n  cn(\n    \"relative flex cursor-default select-none flex-col justify-center rounded-sm py-1.5 pr-8 pl-2\",\n    \"aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-selected:bg-accent aria-selected:text-accent-foreground\",\n  ),\n  {\n    variants: {\n      size: {\n        sm: \"text-base\",\n        md: \"text-base\",\n        lg: \"text-lg\",\n      },\n    },\n    defaultVariants: {\n      size: \"md\",\n    },\n  },\n);\n\nconst AutocompleteItem = ({\n  label,\n  value,\n  disabled,\n  className,\n  children,\n  ...props\n}: AutocompleteItemProps) => {\n  const { size, filteredItems, getItemProps, selectedItem } =\n    useAutocompleteContext();\n\n  const isSelected = selectedItem?.value === value;\n  const item = useMemo(\n    () => ({ disabled, label, value }),\n    [disabled, label, value],\n  );\n  const index = (filteredItems || []).findIndex(\n    (item) => item.value.toLowerCase() === value.toLowerCase(),\n  );\n  if (index < 0) return null;\n\n  return (\n    <li\n      {...props}\n      data-index={index}\n      className={cn(\n        autocompleteItemVariants({ size }),\n        !children && \"ps-8\",\n        className,\n      )}\n      {...getItemProps?.({ item, index })}\n    >\n      {isSelected && (\n        <span className=\"absolute right-2 flex size-3.5 items-center justify-center\">\n          <CheckIcon className=\"size-4\" />\n        </span>\n      )}\n      {children || <span className=\"text-sm text-foreground\">{label}</span>}\n    </li>\n  );\n};\n\nconst AutocompleteContent = ({\n  onOpenAutoFocus,\n  className,\n  children,\n  ...props\n}: ComponentPropsWithoutRef<typeof PopoverContent>) => {\n  const { getMenuProps, isOpen, openedOnce, onItemsChange } =\n    useAutocompleteContext();\n\n  const childItems = useMemo(\n    () =>\n      Children.toArray(children).filter(\n        (child): child is ReactElement<AutocompleteItemProps> =>\n          isValidElement(child) && child.type === AutocompleteItem,\n      ),\n    [children],\n  );\n\n  useEffect(() => {\n    onItemsChange?.(\n      childItems.map((child) => ({\n        disabled: child.props.disabled,\n        label: child.props.label,\n        value: child.props.value,\n      })),\n    );\n  }, [childItems, onItemsChange]);\n\n  return (\n    <PopoverContent\n      {...props}\n      forceMount\n      asChild\n      onOpenAutoFocus={(e) => {\n        e.preventDefault();\n        onOpenAutoFocus?.(e);\n      }}\n      className={cn(\n        \"w-[var(--radix-popover-trigger-width)] p-0\",\n        \"max-h-80 overflow-y-auto no-scrollbar\",\n        !isOpen && \"pointer-events-none\",\n        !openedOnce && \"hidden\",\n        className,\n      )}\n      {...getMenuProps?.({}, { suppressRefError: true })}\n    >\n      <div className=\"p-1\">{children}</div>\n    </PopoverContent>\n  );\n};\n\nconst AutocompleteEmpty = ({\n  className,\n  children,\n  ...props\n}: ComponentPropsWithoutRef<\"div\">) => {\n  const { filteredItems } = useAutocompleteContext();\n  if (filteredItems && filteredItems.length > 0) return null;\n\n  return (\n    <div\n      {...props}\n      className={cn(\"p-4 text-center text-sm text-muted-foreground\", className)}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport {\n  Autocomplete,\n  AutocompleteInput,\n  AutocompleteItem,\n  AutocompleteContent,\n  AutocompleteEmpty,\n  useAutocompleteContext,\n};\n",
      "type": "registry:ui",
      "target": "components/wed/autocomplete.tsx"
    }
  ]
}