Skip to content

Commit f9483e3

Browse files
committed
chore: make side navigation bar trap focus
INSTUI-4266
1 parent 48b5a4a commit f9483e3

File tree

1 file changed

+44
-2
lines changed

1 file changed

+44
-2
lines changed

packages/__docs__/src/App/index.tsx

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import type {
7676
} from '../../buildScripts/DataTypes.mjs'
7777
import { logError } from '@instructure/console'
7878
import type { Spacing } from '@instructure/emotion'
79+
import { FocusRegion } from '@instructure/ui-a11y-utils'
7980

8081
type AppContextType = {
8182
themeKey: keyof MainDocsData['themes']
@@ -94,6 +95,9 @@ class App extends Component<AppProps, AppState> {
9495
static propTypes = propTypes
9596
static allowedProps = allowedProps
9697

98+
private navRef = createRef<HTMLElement>()
99+
private focusRegion: FocusRegion | null = null
100+
97101
static defaultProps = {
98102
trayWidth: 300
99103
}
@@ -222,10 +226,18 @@ class App extends Component<AppProps, AppState> {
222226
})
223227
})
224228
.catch(errorHandler)
229+
document.addEventListener('focusin', this.handleFocusChange)
225230
}
226231

227-
componentDidUpdate() {
232+
componentDidUpdate(_prevProps: AppProps, prevState: AppState) {
228233
this.props.makeStyles?.()
234+
235+
if (
236+
prevState.showMenu !== this.state.showMenu ||
237+
prevState.layout !== this.state.layout
238+
) {
239+
this.handleNavigationFocusRegion()
240+
}
229241
}
230242

231243
componentWillUnmount() {
@@ -236,6 +248,8 @@ class App extends Component<AppProps, AppState> {
236248
if (this._mediaQueryListener) {
237249
this._mediaQueryListener.remove()
238250
}
251+
document.removeEventListener('focusin', this.handleFocusChange)
252+
this.focusRegion?.deactivate()
239253
}
240254

241255
trackPage(page: string) {
@@ -359,6 +373,32 @@ class App extends Component<AppProps, AppState> {
359373
}
360374
}
361375

376+
handleNavigationFocusRegion() {
377+
if (this.focusRegion) {
378+
this.focusRegion.deactivate()
379+
}
380+
381+
if (this.navRef.current) {
382+
this.focusRegion = new FocusRegion(this.navRef.current, {
383+
shouldContainFocus: true,
384+
shouldCloseOnEscape: true,
385+
shouldReturnFocus: false,
386+
onDismiss: this.handleMenuClose
387+
})
388+
this.focusRegion.activate()
389+
}
390+
}
391+
392+
handleFocusChange = (e: FocusEvent) => {
393+
const isFocusInsideNav = this.navRef.current?.contains(e.target as Node)
394+
if (isFocusInsideNav) {
395+
this.handleNavigationFocusRegion()
396+
} else {
397+
this.focusRegion?.deactivate()
398+
this.focusRegion = null
399+
}
400+
}
401+
362402
renderThemeSelect() {
363403
const themeKeys = Object.keys(this.state.docsData!.themes)
364404
const smallScreen = this.state.layout === 'small'
@@ -712,7 +752,9 @@ class App extends Component<AppProps, AppState> {
712752
)
713753

714754
return layout !== 'small' ? (
715-
<nav css={this.props.styles?.inlineNavigation}>{navContent}</nav>
755+
<nav css={this.props.styles?.inlineNavigation} ref={this.navRef}>
756+
{navContent}
757+
</nav>
716758
) : (
717759
<Tray label="Navigation" open={showMenu} onDismiss={this.handleMenuClose}>
718760
{navContent}

0 commit comments

Comments
 (0)