Skip to content

Commit 4740d11

Browse files
committed
fix(ui-top-nav-bar,ui-drilldown): fix Drilldown's and TopNavBar's keyboard navigation issues
INSTUI-4494
1 parent fed526c commit 4740d11

File tree

5 files changed

+36
-10
lines changed

5 files changed

+36
-10
lines changed

packages/ui-drilldown/src/Drilldown/index.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,14 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
889889
handleToggle = (event: React.UIEvent | React.FocusEvent, shown: boolean) => {
890890
const { onToggle } = this.props
891891

892-
this.setState({ isShowingPopover: shown })
892+
const mouseEvent = event as React.MouseEvent
893+
894+
this.setState({ isShowingPopover: shown }, () => {
895+
// Focus first item when it's a keyboard event (mouseEvent.detail === 0)
896+
if (mouseEvent.detail === 0 && shown) {
897+
this.focusFirstAvailableItem()
898+
}
899+
})
893900

894901
if (typeof onToggle === 'function') {
895902
onToggle(event, {
@@ -900,6 +907,24 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
900907
}
901908
}
902909

910+
focusFirstAvailableItem() {
911+
if (!this.currentPage) return
912+
913+
// Check for action option
914+
const actionLabel = callRenderProp(this.currentPage.renderActionLabel)
915+
// Use action ID if exists, otherwise first non-action option's ID
916+
const targetId = actionLabel
917+
? this._headerActionId
918+
: this.currentPage.children[0]?.props.id
919+
920+
if (!targetId) return
921+
922+
this.setState({ highlightedOptionId: targetId }, () => {
923+
// A slight delay is needed to focus the first item after the popover appears.
924+
setTimeout(() => this.focusOption(targetId), 10)
925+
})
926+
}
927+
903928
renderList(
904929
getOptionProps: SelectableRender['getOptionProps'],
905930
getDisabledOptionProps: SelectableRender['getDisabledOptionProps']
@@ -1055,7 +1080,8 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
10551080
role,
10561081
elementRef,
10571082
variant: 'default',
1058-
tabIndex: -1
1083+
// header should not get focused (role === 'presentation')
1084+
tabIndex: role === 'presentation' ? -1 : 0
10591085
}
10601086

10611087
// extra data we need track on the option,
@@ -1378,7 +1404,6 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
13781404
borderWidth="small"
13791405
as="div"
13801406
elementRef={this.handleDrilldownRef}
1381-
tabIndex={0}
13821407
css={styles?.drilldown}
13831408
position="relative"
13841409
borderRadius="small"
@@ -1481,7 +1506,7 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
14811506
this.handleToggle(event, false)
14821507
}}
14831508
onShowContent={(event) => this.handleToggle(event, true)}
1484-
mountNode={mountNode}
1509+
mountNode={mountNode || this.ref}
14851510
placement={placement}
14861511
withArrow={withArrow}
14871512
positionTarget={positionTarget}

packages/ui-drilldown/src/Drilldown/props.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ type DrilldownOwnProps = {
207207

208208
/**
209209
* If a trigger is supplied, an element or a function returning an element
210-
* to use as the mount node for the `<Drilldown />` (defaults to `document.body`)
210+
* to use as the mount node for the `<Drilldown />` (defaults to the component itself)
211211
*/
212212
mountNode?: PositionMountNode
213213

packages/ui-top-nav-bar/src/TopNavBar/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -648,12 +648,12 @@ class PlaygroundExample extends React.Component {
648648

649649
{...item.submenu && {
650650
renderSubmenu: this.generateSubmenu(item),
651-
'aria-label': `Open for ${item.label} menu`
651+
'aria-label': `${item.label} menu`
652652
}}
653653

654654
{...item.customPopoverConfig && {
655655
customPopoverConfig: item.customPopoverConfig,
656-
'aria-label': `Open for ${item.label} menu`
656+
'aria-label': `${item.label} menu`
657657
}}
658658

659659
{...!item.submenu && !item.customPopoverConfig ? {
@@ -720,7 +720,7 @@ class PlaygroundExample extends React.Component {
720720
<TopNavBar.Item
721721
id="Info"
722722
renderIcon={<IconQuestionLine />}
723-
aria-label="Open for info menu"
723+
aria-label="info menu"
724724
renderSubmenu={this.generateSubmenu({
725725
id: 'Info',
726726
submenu: ['Contact', 'Map', 'Career'].map(item => ({

packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/DesktopLayout/styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const generateStyle = (
6969
position: 'relative',
7070
zIndex: componentTheme.desktopZIndex,
7171
maxWidth: '100%',
72-
overflow: 'hidden',
72+
overflow: 'visible',
7373
paddingInline: componentTheme.desktopInlinePadding,
7474
paddingBlock: 0,
7575
...(hasBrandBlock && {

packages/ui-top-nav-bar/src/TopNavBar/TopNavBarMenuItems/styles.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ const generateStyle = (
5151
flexDirection: 'row',
5252
alignItems: 'stretch',
5353
// padding to prevent focus ring getting cropped by `overflow: hidden`
54-
padding: '0 0.125rem'
54+
padding: '0 0.125rem',
55+
overflow: 'visible'
5556
},
5657
submenuOption: {
5758
label: 'topNavBarMenuItems__submenuOption',

0 commit comments

Comments
 (0)