feat : add fuzzy matching for searching
This commit is contained in:
@@ -4,6 +4,8 @@ import * as React from "react"
|
||||
import { Combobox as ComboboxPrimitive } from "@base-ui/react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { searchItems } from "@/lib/search"
|
||||
import { SearchNotice } from "@/components/search-notice"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
InputGroup,
|
||||
@@ -14,6 +16,30 @@ import {
|
||||
import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"
|
||||
|
||||
const Combobox = ComboboxPrimitive.Root
|
||||
const FuzzyContext = React.createContext(false)
|
||||
|
||||
function FuzzyCombobox<Value, Multiple extends boolean | undefined = false>({
|
||||
items = [], searchNames, searchText, children, onInputValueChange, ...props
|
||||
}: ComboboxPrimitive.Root.Props<Value, Multiple> & {
|
||||
items: Value[];
|
||||
searchNames: (item: Value) => readonly string[];
|
||||
searchText?: (item: Value) => string;
|
||||
}) {
|
||||
const [query, setQuery] = React.useState("")
|
||||
const result = React.useMemo(() => searchItems(items, query,
|
||||
searchText ?? ((item) => searchNames(item).join(" ")), searchNames),
|
||||
[items, query, searchNames, searchText])
|
||||
return (
|
||||
<FuzzyContext.Provider value={result.fuzzy}>
|
||||
<ComboboxPrimitive.Root {...props} items={result.items} filter={null}
|
||||
onInputValueChange={(value, details) => {
|
||||
setQuery(details.reason === "input-change" ? value : "")
|
||||
onInputValueChange?.(value, details)
|
||||
}}
|
||||
>{children}</ComboboxPrimitive.Root>
|
||||
</FuzzyContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
|
||||
return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />
|
||||
@@ -85,6 +111,7 @@ function ComboboxInput({
|
||||
}
|
||||
|
||||
function ComboboxContent({
|
||||
children,
|
||||
className,
|
||||
side = "bottom",
|
||||
sideOffset = 6,
|
||||
@@ -97,6 +124,7 @@ function ComboboxContent({
|
||||
ComboboxPrimitive.Positioner.Props,
|
||||
"side" | "align" | "sideOffset" | "alignOffset" | "anchor"
|
||||
>) {
|
||||
const fuzzy = React.useContext(FuzzyContext)
|
||||
return (
|
||||
<ComboboxPrimitive.Portal>
|
||||
<ComboboxPrimitive.Positioner
|
||||
@@ -112,7 +140,10 @@ function ComboboxContent({
|
||||
data-chips={!!anchor}
|
||||
className={cn("group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||
{...props}
|
||||
/>
|
||||
>
|
||||
<SearchNotice fuzzy={fuzzy} />
|
||||
{children}
|
||||
</ComboboxPrimitive.Popup>
|
||||
</ComboboxPrimitive.Positioner>
|
||||
</ComboboxPrimitive.Portal>
|
||||
)
|
||||
@@ -278,6 +309,7 @@ function useComboboxAnchor() {
|
||||
}
|
||||
|
||||
export {
|
||||
FuzzyCombobox,
|
||||
Combobox,
|
||||
ComboboxInput,
|
||||
ComboboxContent,
|
||||
|
||||
Reference in New Issue
Block a user