@@ -18,13 +18,21 @@ export interface SuggestionListProps {
1818 command : ( item : SuggestionItem ) => void ;
1919}
2020
21+ const Kbd = ( { children } : { children : string } ) => (
22+ < kbd className = "mx-0.5 rounded border border-[var(--gray-a6)] bg-[var(--gray-a3)] px-1 font-mono text-[10px]" >
23+ { children }
24+ </ kbd >
25+ ) ;
26+
27+ const CONTAINER_CLASS =
28+ "flex min-w-[300px] flex-col rounded border border-[var(--gray-a6)] bg-[var(--color-panel-solid)] font-mono text-xs shadow-lg" ;
29+
2130export const SuggestionList = forwardRef <
2231 SuggestionListRef ,
2332 SuggestionListProps
2433> ( ( { items, command } , ref ) => {
2534 const isDarkMode = useThemeStore ( ( state ) => state . isDarkMode ) ;
2635 const [ selectedIndex , setSelectedIndex ] = useState ( 0 ) ;
27- const containerRef = useRef < HTMLDivElement > ( null ) ;
2836 const itemRefs = useRef < ( HTMLButtonElement | null ) [ ] > ( [ ] ) ;
2937 const [ hasMouseMoved , setHasMouseMoved ] = useState ( false ) ;
3038 const prevItemsRef = useRef ( items ) ;
@@ -36,52 +44,27 @@ export const SuggestionList = forwardRef<
3644 }
3745
3846 useEffect ( ( ) => {
39- const container = containerRef . current ;
40- const item = itemRefs . current [ selectedIndex ] ;
41- if ( ! container || ! item ) return ;
42-
43- const containerTop = container . scrollTop ;
44- const containerBottom = containerTop + container . clientHeight ;
45- const itemTop = item . offsetTop ;
46- const itemBottom = itemTop + item . offsetHeight ;
47-
48- if ( itemTop < containerTop ) {
49- container . scrollTop = itemTop ;
50- } else if ( itemBottom > containerBottom ) {
51- container . scrollTop = itemBottom - container . clientHeight ;
52- }
47+ itemRefs . current [ selectedIndex ] ?. scrollIntoView ( { block : "nearest" } ) ;
5348 } , [ selectedIndex ] ) ;
5449
5550 useImperativeHandle ( ref , ( ) => ( {
5651 onKeyDown : ( { event } ) => {
5752 if ( event . key === "ArrowUp" ) {
58- setSelectedIndex ( ( prev ) => ( prev + items . length - 1 ) % items . length ) ;
53+ setSelectedIndex ( ( i ) => ( i + items . length - 1 ) % items . length ) ;
5954 return true ;
6055 }
61-
6256 if ( event . key === "ArrowDown" ) {
63- setSelectedIndex ( ( prev ) => ( prev + 1 ) % items . length ) ;
57+ setSelectedIndex ( ( i ) => ( i + 1 ) % items . length ) ;
6458 return true ;
6559 }
66-
6760 if ( event . key === "Enter" || event . key === "Tab" ) {
68- const item = items [ selectedIndex ] ;
69- if ( item ) {
70- command ( item ) ;
71- }
61+ if ( items [ selectedIndex ] ) command ( items [ selectedIndex ] ) ;
7262 return true ;
7363 }
74-
7564 return false ;
7665 } ,
7766 } ) ) ;
7867
79- const kbd = ( key : string ) => (
80- < kbd className = "mx-[2px] rounded border border-[var(--gray-a6)] bg-[var(--gray-a3)] px-1 font-mono text-[10px]" >
81- { key }
82- </ kbd >
83- ) ;
84-
8568 const themeProps = {
8669 appearance : isDarkMode ? "dark" : "light" ,
8770 accentColor : isDarkMode ? "orange" : "yellow" ,
@@ -94,22 +77,19 @@ export const SuggestionList = forwardRef<
9477 if ( items . length === 0 ) {
9578 return (
9679 < Theme { ...themeProps } >
97- < div className = "flex min-w-[300px] flex-col rounded border border-[var(--gray-a6)] bg-[var(--color-panel-solid)] font-mono text-[12px] shadow-lg" >
98- < div className = "p-2" >
99- < span className = "text-[var(--gray-11)]" > No results found</ span >
100- </ div >
80+ < div className = { CONTAINER_CLASS } >
81+ < div className = "p-2 text-[var(--gray-11)]" > No results found</ div >
10182 </ div >
10283 </ Theme >
10384 ) ;
10485 }
10586
10687 return (
10788 < Theme { ...themeProps } >
108- < div className = "flex min-w-[300px] flex-col rounded border border-[var(--gray-a6)] bg-[var(--color-panel-solid)] font-mono text-[12px] shadow-lg" >
89+ < div className = { CONTAINER_CLASS } >
10990 < div
110- ref = { containerRef }
11191 role = "listbox"
112- className = { ` max-h-[240px] flex-1 overflow-y-auto pb-1 [&::-webkit-scrollbar]:hidden ${ hasMouseMoved ? "" : "cursor-none" } ` }
92+ className = " max-h-60 flex-1 overflow-y-auto pb-1 [&::-webkit-scrollbar]:hidden"
11393 onMouseMove = { ( ) => ! hasMouseMoved && setHasMouseMoved ( true ) }
11494 >
11595 { items . map ( ( item , index ) => {
@@ -123,28 +103,18 @@ export const SuggestionList = forwardRef<
123103 } }
124104 onClick = { ( ) => command ( item ) }
125105 onMouseEnter = { ( ) => hasMouseMoved && setSelectedIndex ( index ) }
126- className = { `flex w-full flex-col gap-[2px] border-none text-left ${
127- item . description ? "px-2 py-[6px]" : "px-2 py-1"
128- } ${ isSelected ? "bg-[var(--accent-a4)]" : "bg-transparent" } ${
129- hasMouseMoved ? "cursor-pointer" : "cursor-none"
130- } `}
106+ className = { `flex w-full flex-col gap-0.5 border-none px-2 text-left ${
107+ item . description ? "py-1.5" : "py-1"
108+ } ${ isSelected ? "bg-[var(--accent-a4)]" : "" } `}
131109 >
132110 < span
133- className = { `truncate ${
134- isSelected
135- ? "text-[var(--accent-11)]"
136- : "text-[var(--gray-11)]"
137- } `}
111+ className = { `truncate ${ isSelected ? "text-[var(--accent-11)]" : "text-[var(--gray-11)]" } ` }
138112 >
139113 { item . label }
140114 </ span >
141115 { item . description && (
142116 < span
143- className = { `text-[11px] ${
144- isSelected
145- ? "text-[var(--accent-10)]"
146- : "text-[var(--gray-10)]"
147- } `}
117+ className = { `text-[11px] ${ isSelected ? "text-[var(--accent-10)]" : "text-[var(--gray-10)]" } ` }
148118 >
149119 { item . description }
150120 </ span >
@@ -154,8 +124,8 @@ export const SuggestionList = forwardRef<
154124 } ) }
155125 </ div >
156126 < div className = "border-[var(--gray-a4)] border-t bg-[var(--gray-a2)] px-2 py-1 text-[10px] text-[var(--gray-10)]" >
157- { kbd ( "↑" ) }
158- { kbd ( "↓" ) } navigate · { kbd ( "↵" ) } select · { kbd ( " esc" ) } dismiss
127+ < Kbd > ↑ </ Kbd >
128+ < Kbd > ↓ </ Kbd > navigate · < Kbd > ↵ </ Kbd > select · < Kbd > esc</ Kbd > dismiss
159129 </ div >
160130 </ div >
161131 </ Theme >
0 commit comments