@@ -84,17 +84,26 @@ export function Select<T extends React.Key = string>({
8484 const triggerRef = useRef < HTMLButtonElement > ( null ) ;
8585 const popoverRef = useRef < HTMLDivElement > ( null ) ;
8686 const selectedItemRef = useRef < HTMLElement > ( null ) ;
87+ const searchInputRef = useRef < HTMLInputElement > ( null ) ;
8788 const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
8889 // No need for JavaScript width measurement - Radix provides --radix-popover-trigger-width CSS variable
8990
9091 // When opening, ensure the currently selected option is the active item for keyboard nav
92+ // and focus the search input if searchable
9193 useEffect ( ( ) => {
92- if ( ! popoverState . isOpen ) return ;
94+ if ( ! popoverState . isOpen ) {
95+ // Clear search query when closing
96+ setSearchQuery ( '' ) ;
97+ return ;
98+ }
9399 requestAnimationFrame ( ( ) => {
100+ if ( searchable && searchInputRef . current ) {
101+ searchInputRef . current . focus ( ) ;
102+ }
94103 const selectedEl = selectedItemRef . current as HTMLElement | null ;
95104 if ( selectedEl ) selectedEl . scrollIntoView ( { block : 'center' } ) ;
96105 } ) ;
97- } , [ popoverState . isOpen ] ) ;
106+ } , [ popoverState . isOpen , searchable ] ) ;
98107
99108 const selectedOption = options . find ( ( o ) => o . value === value ) ;
100109
@@ -116,6 +125,34 @@ export function Select<T extends React.Key = string>({
116125 const ChevronIcon = components ?. ChevronIcon || DefaultChevronIcon ;
117126 const SearchInput = components ?. SearchInput || DefaultSearchInput ;
118127
128+ // Handle keydown on trigger to open popover and start typing
129+ const handleTriggerKeyDown = ( e : React . KeyboardEvent < HTMLButtonElement > ) => {
130+ // Allow normal keyboard navigation (Enter, Space, Arrow keys, etc.)
131+ if (
132+ e . key === 'Enter' ||
133+ e . key === ' ' ||
134+ e . key === 'ArrowDown' ||
135+ e . key === 'ArrowUp' ||
136+ e . key === 'Escape' ||
137+ e . key === 'Tab'
138+ ) {
139+ return ;
140+ }
141+
142+ // If it's a printable character and searchable is enabled, open the popover and start typing
143+ if ( searchable && e . key . length === 1 && ! e . ctrlKey && ! e . metaKey && ! e . altKey ) {
144+ e . preventDefault ( ) ;
145+ if ( ! popoverState . isOpen ) {
146+ popoverState . open ( ) ;
147+ }
148+ // Set the initial search query
149+ setSearchQuery ( e . key ) ;
150+ }
151+
152+ // Call the original onKeyDown if provided
153+ buttonProps . onKeyDown ?.( e ) ;
154+ } ;
155+
119156 return (
120157 < Popover open = { popoverState . isOpen } onOpenChange = { popoverState . setOpen } >
121158 < PopoverTrigger asChild >
@@ -131,6 +168,7 @@ export function Select<T extends React.Key = string>({
131168 aria-haspopup = "listbox"
132169 aria-expanded = { popoverState . isOpen }
133170 aria-controls = { listboxId }
171+ onKeyDown = { handleTriggerKeyDown }
134172 { ...buttonProps }
135173 >
136174 { value != null && value !== '' ? ( selectedOption ?. label ?? String ( value ) ) : placeholder }
@@ -159,6 +197,7 @@ export function Select<T extends React.Key = string>({
159197 { searchable && (
160198 < div className = "px-1.5 pb-1.5 pt-1.5" >
161199 < SearchInput
200+ ref = { searchInputRef }
162201 placeholder = "Search..."
163202 value = { searchQuery }
164203 onValueChange = { ( v : string ) => {
0 commit comments