11import { useTypeFilter , useSearchActions } from '../search.store'
22import { useEuiTheme , EuiButton , EuiSkeletonRectangle } from '@elastic/eui'
33import { css } from '@emotion/react'
4+ import { useRef , useCallback , MutableRefObject } from 'react'
45
56interface SearchFiltersProps {
67 counts : {
@@ -9,14 +10,54 @@ interface SearchFiltersProps {
910 totalCount : number
1011 }
1112 isLoading : boolean
13+ inputRef ?: React . RefObject < HTMLInputElement >
14+ itemRefs ?: MutableRefObject < ( HTMLAnchorElement | null ) [ ] >
15+ resultsCount ?: number
1216}
1317
14- export const SearchFilters = ( { counts, isLoading } : SearchFiltersProps ) => {
18+ export const SearchFilters = ( {
19+ counts,
20+ isLoading,
21+ inputRef,
22+ itemRefs,
23+ resultsCount = 0 ,
24+ } : SearchFiltersProps ) => {
1525 const { euiTheme } = useEuiTheme ( )
1626 const selectedFilter = useTypeFilter ( )
1727 const { setTypeFilter } = useSearchActions ( )
1828 const { apiResultsCount, docsResultsCount, totalCount } = counts
1929
30+ const filterRefs = useRef < ( HTMLButtonElement | null ) [ ] > ( [ ] )
31+
32+ const handleFilterKeyDown = useCallback (
33+ ( e : React . KeyboardEvent < HTMLButtonElement > , filterIndex : number ) => {
34+ const filterCount = 3 // ALL, DOCS, API
35+
36+ if ( e . key === 'ArrowUp' ) {
37+ e . preventDefault ( )
38+ // Go back to input
39+ inputRef ?. current ?. focus ( )
40+ } else if ( e . key === 'ArrowDown' ) {
41+ e . preventDefault ( )
42+ // Go to first result if available
43+ if ( resultsCount > 0 ) {
44+ itemRefs ?. current [ 0 ] ?. focus ( )
45+ }
46+ } else if ( e . key === 'ArrowLeft' ) {
47+ e . preventDefault ( )
48+ if ( filterIndex > 0 ) {
49+ filterRefs . current [ filterIndex - 1 ] ?. focus ( )
50+ }
51+ } else if ( e . key === 'ArrowRight' ) {
52+ e . preventDefault ( )
53+ if ( filterIndex < filterCount - 1 ) {
54+ filterRefs . current [ filterIndex + 1 ] ?. focus ( )
55+ }
56+ }
57+ } ,
58+ [ inputRef , itemRefs , resultsCount ]
59+ )
60+
2061 const buttonStyle = css `
2162 border-radius : 99999px ;
2263 padding-inline : ${ euiTheme . size . m } ;
@@ -34,6 +75,8 @@ export const SearchFilters = ({ counts, isLoading }: SearchFiltersProps) => {
3475 gap : ${ euiTheme . size . s } ;
3576 padding-inline : ${ euiTheme . size . base } ;
3677 ` }
78+ role = "group"
79+ aria-label = "Search filters"
3780 >
3881 < EuiSkeletonRectangle
3982 isLoading = { isLoading }
@@ -49,6 +92,12 @@ export const SearchFilters = ({ counts, isLoading }: SearchFiltersProps) => {
4992 fill = { selectedFilter === 'all' }
5093 isLoading = { isLoading }
5194 onClick = { ( ) => setTypeFilter ( 'all' ) }
95+ onKeyDown = { ( e : React . KeyboardEvent < HTMLButtonElement > ) =>
96+ handleFilterKeyDown ( e , 0 )
97+ }
98+ buttonRef = { ( el : HTMLButtonElement | null ) => {
99+ filterRefs . current [ 0 ] = el
100+ } }
52101 css = { buttonStyle }
53102 aria-label = { `Show all results, ${ totalCount } total` }
54103 aria-pressed = { selectedFilter === 'all' }
@@ -70,6 +119,12 @@ export const SearchFilters = ({ counts, isLoading }: SearchFiltersProps) => {
70119 fill = { selectedFilter === 'doc' }
71120 isLoading = { isLoading }
72121 onClick = { ( ) => setTypeFilter ( 'doc' ) }
122+ onKeyDown = { ( e : React . KeyboardEvent < HTMLButtonElement > ) =>
123+ handleFilterKeyDown ( e , 1 )
124+ }
125+ buttonRef = { ( el : HTMLButtonElement | null ) => {
126+ filterRefs . current [ 1 ] = el
127+ } }
73128 css = { buttonStyle }
74129 aria-label = { `Filter to documentation results, ${ docsResultsCount } available` }
75130 aria-pressed = { selectedFilter === 'doc' }
@@ -91,6 +146,12 @@ export const SearchFilters = ({ counts, isLoading }: SearchFiltersProps) => {
91146 fill = { selectedFilter === 'api' }
92147 isLoading = { isLoading }
93148 onClick = { ( ) => setTypeFilter ( 'api' ) }
149+ onKeyDown = { ( e : React . KeyboardEvent < HTMLButtonElement > ) =>
150+ handleFilterKeyDown ( e , 2 )
151+ }
152+ buttonRef = { ( el : HTMLButtonElement | null ) => {
153+ filterRefs . current [ 2 ] = el
154+ } }
94155 css = { buttonStyle }
95156 aria-label = { `Filter to API results, ${ apiResultsCount } available` }
96157 aria-pressed = { selectedFilter === 'api' }
0 commit comments