From aebfa6e3ccea86901e238c9f1d3ff25336ec5427 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Mon, 28 Sep 2026 12:00:59 +0530 Subject: [PATCH] Add search to dropdown --- distribution_fe/components/ui/select.tsx | 69 +++++++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/distribution_fe/components/ui/select.tsx b/distribution_fe/components/ui/select.tsx index fbb1cd0..8b25b53 100644 --- a/distribution_fe/components/ui/select.tsx +++ b/distribution_fe/components/ui/select.tsx @@ -5,7 +5,9 @@ import { Select as SelectPrimitive } from "@base-ui/react/select" import { cn } from "@/lib/utils" import { HugeiconsIcon } from "@hugeicons/react" -import { UnfoldMoreIcon, Tick02Icon, ArrowUp01Icon, ArrowDown01Icon } from "@hugeicons/core-free-icons" +import { UnfoldMoreIcon, Tick02Icon, ArrowUp01Icon, ArrowDown01Icon, Search01Icon } from "@hugeicons/core-free-icons" + +const SEARCH_THRESHOLD = 7 function collectSelectItems(children: React.ReactNode): Array<{ value: unknown; label: string }> { const items: Array<{ value: unknown; label: string }> = [] @@ -66,6 +68,57 @@ function getItemLabel(children: React.ReactNode): string | undefined { return undefined } +function filterSelectChildren(children: React.ReactNode, query: string): React.ReactNode { + const normalizedQuery = query.trim().toLowerCase() + if (!normalizedQuery) return children + + const filtered = React.Children.map(children, (child) => { + if (!React.isValidElement<{ value?: unknown; label?: string; children?: React.ReactNode }>(child)) { + return child + } + + if ("value" in child.props) { + const label = child.props.label ?? getItemLabel(child.props.children) ?? "" + return label.toLowerCase().includes(normalizedQuery) ? child : null + } + + if (child.props.children) { + const filteredChildren = filterSelectChildren(child.props.children, query) + if (React.Children.count(filteredChildren) === 0) return null + return React.cloneElement(child, undefined, filteredChildren) + } + + return child + }) + + return filtered +} + +function SelectSearchInput({ + query, + onQueryChange, +}: { + query: string + onQueryChange: (value: string) => void +}) { + return ( +
+ + onQueryChange(event.target.value)} + onKeyDown={(event) => event.stopPropagation()} + placeholder="Search..." + className="w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground" + /> +
+ ) +} + function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) { return ( ) { + const [query, setQuery] = React.useState("") + const itemCount = React.useMemo(() => collectSelectItems(children).length, [children]) + const showSearch = itemCount > SEARCH_THRESHOLD + const filteredChildren = showSearch ? filterSelectChildren(children, query) : children + return ( + {showSearch && } - {children} + + {React.Children.count(filteredChildren) === 0 ? ( +
No results found.
+ ) : ( + filteredChildren + )} +