mirror of https://github.com/grafana/grafana
SearchV2: support keyboard navigation (#49650)
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>pull/50720/head
parent
1ed7280363
commit
402b5ce4c6
@ -0,0 +1,100 @@ |
||||
import { useEffect, useRef, useState } from 'react'; |
||||
import { Observable, Subject } from 'rxjs'; |
||||
|
||||
import { Field, locationUtil } from '@grafana/data'; |
||||
import { locationService } from '@grafana/runtime'; |
||||
|
||||
import { QueryResponse } from '../service'; |
||||
|
||||
export function useKeyNavigationListener() { |
||||
const eventsRef = useRef(new Subject<React.KeyboardEvent>()); |
||||
return { |
||||
keyboardEvents: eventsRef.current, |
||||
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => { |
||||
switch (e.code) { |
||||
case 'ArrowDown': |
||||
case 'ArrowUp': |
||||
case 'ArrowLeft': |
||||
case 'ArrowRight': |
||||
case 'Enter': |
||||
eventsRef.current.next(e); |
||||
default: |
||||
// ignore
|
||||
} |
||||
}, |
||||
}; |
||||
} |
||||
|
||||
interface ItemSelection { |
||||
x: number; |
||||
y: number; |
||||
} |
||||
|
||||
export function useSearchKeyboardNavigation( |
||||
keyboardEvents: Observable<React.KeyboardEvent>, |
||||
numColumns: number, |
||||
response: QueryResponse |
||||
): ItemSelection { |
||||
const highlightIndexRef = useRef<ItemSelection>({ x: 0, y: -1 }); |
||||
const [highlightIndex, setHighlightIndex] = useState<ItemSelection>({ x: 0, y: -1 }); |
||||
const urlsRef = useRef<Field>(); |
||||
|
||||
// Clear selection when the search results change
|
||||
useEffect(() => { |
||||
urlsRef.current = response.view.fields.url; |
||||
highlightIndexRef.current.x = 0; |
||||
highlightIndexRef.current.y = -1; |
||||
setHighlightIndex({ ...highlightIndexRef.current }); |
||||
}, [response]); |
||||
|
||||
useEffect(() => { |
||||
const sub = keyboardEvents.subscribe({ |
||||
next: (keyEvent) => { |
||||
switch (keyEvent?.code) { |
||||
case 'ArrowDown': { |
||||
highlightIndexRef.current.y++; |
||||
setHighlightIndex({ ...highlightIndexRef.current }); |
||||
break; |
||||
} |
||||
case 'ArrowUp': |
||||
highlightIndexRef.current.y = Math.max(0, highlightIndexRef.current.y - 1); |
||||
setHighlightIndex({ ...highlightIndexRef.current }); |
||||
break; |
||||
case 'ArrowRight': { |
||||
if (numColumns > 0) { |
||||
highlightIndexRef.current.x = Math.min(numColumns, highlightIndexRef.current.x + 1); |
||||
setHighlightIndex({ ...highlightIndexRef.current }); |
||||
} |
||||
break; |
||||
} |
||||
case 'ArrowLeft': { |
||||
if (numColumns > 0) { |
||||
highlightIndexRef.current.x = Math.max(0, highlightIndexRef.current.x - 1); |
||||
setHighlightIndex({ ...highlightIndexRef.current }); |
||||
} |
||||
break; |
||||
} |
||||
case 'Enter': |
||||
if (!urlsRef.current) { |
||||
break; |
||||
} |
||||
const idx = highlightIndexRef.current.x * numColumns + highlightIndexRef.current.y; |
||||
if (idx < 0) { |
||||
highlightIndexRef.current.x = 0; |
||||
highlightIndexRef.current.y = 0; |
||||
setHighlightIndex({ ...highlightIndexRef.current }); |
||||
break; |
||||
} |
||||
const url = urlsRef.current.values?.get(idx) as string; |
||||
if (url) { |
||||
locationService.push(locationUtil.stripBaseFromUrl(url)); |
||||
} |
||||
} |
||||
}, |
||||
}); |
||||
|
||||
return () => sub.unsubscribe(); |
||||
}, [keyboardEvents, numColumns]); |
||||
|
||||
return highlightIndex; |
||||
} |
Loading…
Reference in new issue