diff --git a/.changeset/witty-elephants-tease.md b/.changeset/witty-elephants-tease.md new file mode 100644 index 00000000000..d38c0c1386e --- /dev/null +++ b/.changeset/witty-elephants-tease.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +Change focus strategy for TabNav after initial focus is set diff --git a/src/TabNav.tsx b/src/TabNav.tsx index 451f9ed0a4d..78e5d785f39 100644 --- a/src/TabNav.tsx +++ b/src/TabNav.tsx @@ -1,6 +1,6 @@ import classnames from 'classnames' import {To} from 'history' -import React, {useRef} from 'react' +import React, {useRef, useState} from 'react' import styled from 'styled-components' import {get} from './constants' import {FocusKeys, useFocusZone} from './hooks/useFocusZone' @@ -30,21 +30,34 @@ export type TabNavProps = ComponentProps function TabNav({children, 'aria-label': ariaLabel, ...rest}: TabNavProps) { const customContainerRef = useRef(null) + // TODO: revert tracking when `initialFocus` is set. This is a fix when TabNav + // is nested within another focus zone. This flag is used to indicate when + // focus has been initially set, this is useful for including the + // `aria-selected="true"` tab as the first interactive item. + // + // When set to `true`, this changes the behavior in `useFocusZone` to use + // the `'previous'` strategy which allows the tab to participate in nested + // focus zones without conflict + const [initialFocus, setInitialFocus] = useState(false) const customStrategy = React.useCallback(() => { if (customContainerRef.current) { const tabs = Array.from( customContainerRef.current.querySelectorAll('[role=tab][aria-selected=true]') ) + setInitialFocus(true) return tabs[0] } }, [customContainerRef]) - const {containerRef: navRef} = useFocusZone({ - containerRef: customContainerRef, - bindKeys: FocusKeys.ArrowHorizontal | FocusKeys.HomeAndEnd, - focusOutBehavior: 'wrap', - focusInStrategy: customStrategy, - focusableElementFilter: element => element.getAttribute('role') === 'tab' - }) + const {containerRef: navRef} = useFocusZone( + { + containerRef: customContainerRef, + bindKeys: FocusKeys.ArrowHorizontal | FocusKeys.HomeAndEnd, + focusOutBehavior: 'wrap', + focusInStrategy: initialFocus ? 'previous' : customStrategy, + focusableElementFilter: element => element.getAttribute('role') === 'tab' + }, + [initialFocus] + ) return ( }>