diff --git a/.changeset/fresh-parents-kiss.md b/.changeset/fresh-parents-kiss.md new file mode 100644 index 00000000000..c6ed658c5f0 --- /dev/null +++ b/.changeset/fresh-parents-kiss.md @@ -0,0 +1,5 @@ +--- +"@primer/react": patch +--- + +experimental/SelectPanel: Improve keyboard navigation from search input diff --git a/packages/react/src/drafts/SelectPanel2/SelectPanel.tsx b/packages/react/src/drafts/SelectPanel2/SelectPanel.tsx index cccad7d792c..73d0e40be77 100644 --- a/packages/react/src/drafts/SelectPanel2/SelectPanel.tsx +++ b/packages/react/src/drafts/SelectPanel2/SelectPanel.tsx @@ -37,6 +37,7 @@ const SelectPanelContext = React.createContext<{ searchQuery: string setSearchQuery: React.Dispatch> selectionVariant: ActionListProps['selectionVariant'] | 'instant' + moveFocusToList: () => void }>({ title: '', description: undefined, @@ -46,6 +47,7 @@ const SelectPanelContext = React.createContext<{ searchQuery: '', setSearchQuery: () => {}, selectionVariant: 'multiple', + moveFocusToList: () => {}, }) export type SelectPanelProps = { @@ -161,6 +163,14 @@ const Panel: React.FC = ({ [internalOpen], ) + // used in SelectPanel.SearchInput + const moveFocusToList = () => { + const selector = 'ul[role=listbox] li:not([role=none])' + // being specific about roles because there can be another ul (tabs in header) and an ActionList.Group (li[role=none]) + const firstListElement = dialogRef.current?.querySelector(selector) as HTMLLIElement | undefined + firstListElement?.focus() + } + /* Dialog */ const dialogRef = React.useRef(null) @@ -265,6 +275,7 @@ const Panel: React.FC = ({ searchQuery, setSearchQuery, selectionVariant, + moveFocusToList, }} > = ({children, ...prop ) } -const SelectPanelSearchInput: React.FC = ({onChange: propsOnChange, ...props}) => { +const SelectPanelSearchInput: React.FC = ({ + onChange: propsOnChange, + onKeyDown: propsOnKeyDown, + ...props +}) => { // TODO: use forwardedRef const inputRef = React.createRef() - const {setSearchQuery} = React.useContext(SelectPanelContext) + const {setSearchQuery, moveFocusToList} = React.useContext(SelectPanelContext) const internalOnChange = (event: React.ChangeEvent) => { // If props.onChange is given, the application controls search, @@ -386,6 +401,15 @@ const SelectPanelSearchInput: React.FC = ({onChange: propsOnChan else setSearchQuery(event.target.value) } + const internalKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'ArrowDown') { + event.preventDefault() // prevent scroll + moveFocusToList() + } + + if (typeof propsOnKeyDown === 'function') propsOnKeyDown(event) + } + return ( = ({onChange: propsOnChan } sx={{'&:has(input:placeholder-shown) .TextInput-action': {display: 'none'}}} onChange={internalOnChange} + onKeyDown={internalKeyDown} {...props} /> )