Skip to content

Commit 57d9641

Browse files
committed
feat(frontend): enhance searchable cloud filter dropdowns with clearable input
1 parent 484da8d commit 57d9641

1 file changed

Lines changed: 43 additions & 8 deletions

File tree

frontend/src/components/ui/searchable-select.tsx

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client"
22

33
import { useId, useState } from "react"
4-
import { Check, ChevronsUpDown } from "lucide-react"
4+
import { Check, ChevronsUpDown, X } from "lucide-react"
55

66
import { Button } from "@/components/ui/button"
77
import { Input } from "@/components/ui/input"
@@ -40,6 +40,10 @@ export function SearchableSelect({
4040
const filteredOptions = normalizedQuery
4141
? options.filter((option) => option.toLowerCase().includes(normalizedQuery))
4242
: options
43+
const resultLabel =
44+
filteredOptions.length === 1
45+
? "1 result"
46+
: `${filteredOptions.length} results`
4347

4448
return (
4549
<Popover
@@ -70,12 +74,36 @@ export function SearchableSelect({
7074
</PopoverTrigger>
7175
<PopoverContent className="w-[220px] p-0" align="start">
7276
<div className="border-b p-2">
73-
<Input
74-
value={query}
75-
onChange={(event) => setQuery(event.target.value)}
76-
placeholder={searchPlaceholder}
77-
autoFocus
78-
/>
77+
<div className="relative">
78+
<Input
79+
value={query}
80+
onChange={(event) => setQuery(event.target.value)}
81+
onKeyDown={(event) => {
82+
if (event.key === "Escape" && query) {
83+
event.preventDefault()
84+
event.stopPropagation()
85+
setQuery("")
86+
}
87+
}}
88+
placeholder={searchPlaceholder}
89+
autoFocus
90+
className="pr-8"
91+
/>
92+
{query ? (
93+
<button
94+
type="button"
95+
onClick={() => setQuery("")}
96+
className="absolute top-1/2 right-2 -translate-y-1/2 rounded-sm p-0.5 text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
97+
aria-label="Clear search"
98+
>
99+
<X className="size-4" />
100+
</button>
101+
) : null}
102+
</div>
103+
<div className="mt-2 flex items-center justify-between px-1 text-xs text-muted-foreground">
104+
<span>{query ? resultLabel : `${options.length} options`}</span>
105+
{query ? <span>Esc to clear</span> : null}
106+
</div>
79107
</div>
80108
<div
81109
id={`${inputId}-listbox`}
@@ -108,7 +136,14 @@ export function SearchableSelect({
108136
})
109137
) : (
110138
<div className="px-2 py-4 text-center text-sm text-muted-foreground">
111-
{emptyMessage}
139+
<div>{emptyMessage}</div>
140+
<button
141+
type="button"
142+
onClick={() => setQuery("")}
143+
className="mt-2 inline-flex items-center rounded-sm text-sm text-foreground underline underline-offset-4 transition-colors hover:text-primary"
144+
>
145+
Clear search
146+
</button>
112147
</div>
113148
)}
114149
</div>

0 commit comments

Comments
 (0)