Skip to content

Commit

Permalink
fix: keyboard navigation focus
Browse files Browse the repository at this point in the history
  • Loading branch information
amanharwara committed Feb 2, 2024
1 parent 9930712 commit ad1eb81
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 33 deletions.
13 changes: 6 additions & 7 deletions packages/web/src/javascripts/Components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
KeyboardEventHandler,
useCallback,
useImperativeHandle,
useRef,
useState,
} from 'react'
import { KeyboardKey } from '@standardnotes/ui-services'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
Expand Down Expand Up @@ -33,7 +33,7 @@ const Menu = forwardRef(
}: MenuProps,
forwardedRef,
) => {
const menuElementRef = useRef<HTMLMenuElement>(null)
const [menuElement, setMenuElement] = useState<HTMLMenuElement | null>(null)

const handleKeyDown: KeyboardEventHandler<HTMLMenuElement> = useCallback(
(event) => {
Expand All @@ -49,11 +49,10 @@ const Menu = forwardRef(

const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)

const { setInitialFocus } = useListKeyboardNavigation(
menuElementRef,
const { setInitialFocus } = useListKeyboardNavigation(menuElement, {
initialFocus,
isMobileScreen ? false : shouldAutoFocus,
)
shouldAutoFocus: isMobileScreen ? false : shouldAutoFocus,
})

useImperativeHandle(forwardedRef, () => ({
focus: () => {
Expand All @@ -65,7 +64,7 @@ const Menu = forwardRef(
<menu
className={`m-0 list-none px-4 focus:shadow-none md:px-0 ${className}`}
onKeyDown={handleKeyDown}
ref={mergeRefs([menuElementRef, forwardedRef])}
ref={mergeRefs([setMenuElement, forwardedRef])}
style={style}
aria-label={a11yLabel}
{...props}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NoteType, SNNote, classNames } from '@standardnotes/snjs'
import Modal, { ModalAction } from '../../Modal/Modal'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
import { useApplication } from '../../ApplicationProvider'
import { confirmDialog } from '@standardnotes/ui-services'
Expand Down Expand Up @@ -134,8 +134,8 @@ const NoteConflictResolutionModal = ({
[close],
)

const listRef = useRef<HTMLDivElement>(null)
useListKeyboardNavigation(listRef)
const [listElement, setListElement] = useState<HTMLDivElement | null>(null)
useListKeyboardNavigation(listElement)

const [selectedMobileTab, setSelectedMobileTab] = useState<'list' | 'preview'>('list')

Expand Down Expand Up @@ -279,7 +279,7 @@ const NoteConflictResolutionModal = ({
'w-full overflow-y-auto border-r border-border py-1.5 md:flex md:w-auto md:min-w-60 md:flex-col',
selectedMobileTab !== 'list' && 'hidden md:flex',
)}
ref={listRef}
ref={setListElement}
>
{allVersions.map((note, index) => (
<ConflictListItem
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Action } from '@standardnotes/snjs'
import { FunctionComponent, useRef } from 'react'
import { FunctionComponent, useState } from 'react'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
import HistoryListItem from './HistoryListItem'
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
Expand All @@ -13,16 +13,16 @@ type Props = {
const LegacyHistoryList: FunctionComponent<Props> = ({ legacyHistory, noteHistoryController, onSelectRevision }) => {
const { selectLegacyRevision, selectedEntry } = noteHistoryController

const legacyHistoryListRef = useRef<HTMLDivElement>(null)
const [listElement, setListElement] = useState<HTMLDivElement | null>(null)

useListKeyboardNavigation(legacyHistoryListRef)
useListKeyboardNavigation(listElement)

return (
<div
className={`flex h-full w-full flex-col focus:shadow-none ${
!legacyHistory?.length ? 'items-center justify-center' : ''
}`}
ref={legacyHistoryListRef}
ref={setListElement}
>
{legacyHistory?.map((entry) => {
const selectedEntryUrl = (selectedEntry as Action)?.subactions?.[0].url
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { observer } from 'mobx-react-lite'
import { Fragment, FunctionComponent, useMemo, useRef } from 'react'
import { Fragment, FunctionComponent, useMemo, useState } from 'react'
import Icon from '@/Components/Icon/Icon'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
import HistoryListItem from './HistoryListItem'
Expand All @@ -22,9 +22,9 @@ const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = ({
}) => {
const { remoteHistory, isFetchingRemoteHistory, selectRemoteRevision, selectedEntry } = noteHistoryController

const remoteHistoryListRef = useRef<HTMLDivElement>(null)
const [listElement, setListElement] = useState<HTMLDivElement | null>(null)

useListKeyboardNavigation(remoteHistoryListRef)
useListKeyboardNavigation(listElement)

const remoteHistoryLength = useMemo(() => remoteHistory?.map((group) => group.entries).flat().length, [remoteHistory])

Expand All @@ -33,7 +33,7 @@ const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = ({
className={`flex h-full w-full flex-col focus:shadow-none ${
isFetchingRemoteHistory || !remoteHistoryLength ? 'items-center justify-center' : ''
}`}
ref={remoteHistoryListRef}
ref={setListElement}
>
{isFetchingRemoteHistory && <Spinner className="h-5 w-5" />}
{remoteHistory?.map((group) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fragment, FunctionComponent, useMemo, useRef } from 'react'
import { Fragment, FunctionComponent, useMemo, useState } from 'react'
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
import HistoryListItem from './HistoryListItem'
import { observer } from 'mobx-react-lite'
Expand All @@ -12,9 +12,9 @@ type Props = {
const SessionHistoryList: FunctionComponent<Props> = ({ noteHistoryController, onSelectRevision }) => {
const { sessionHistory, selectedRevision, selectSessionRevision } = noteHistoryController

const sessionHistoryListRef = useRef<HTMLDivElement>(null)
const [listElement, setListElement] = useState<HTMLDivElement | null>(null)

useListKeyboardNavigation(sessionHistoryListRef)
useListKeyboardNavigation(listElement)

const sessionHistoryLength = useMemo(
() => sessionHistory?.map((group) => group.entries).flat().length,
Expand All @@ -26,7 +26,7 @@ const SessionHistoryList: FunctionComponent<Props> = ({ noteHistoryController, o
className={`flex h-full w-full flex-col focus:shadow-none ${
!sessionHistoryLength ? 'items-center justify-center' : ''
}`}
ref={sessionHistoryListRef}
ref={setListElement}
>
{sessionHistory?.map((group) => {
if (group.entries && group.entries.length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ const SmartViewsList: FunctionComponent<Props> = ({

const [container, setContainer] = useState<HTMLDivElement | null>(null)

useListKeyboardNavigation(container, undefined, false, false)
useListKeyboardNavigation(container, {
initialFocus: 0,
shouldAutoFocus: false,
shouldWrapAround: false,
resetLastFocusedOnBlur: true,
})

if (allViews.length === 0 && navigationController.isSearching) {
return (
Expand Down
7 changes: 6 additions & 1 deletion packages/web/src/javascripts/Components/Tags/TagsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ const TagsList: FunctionComponent<Props> = ({ type }: Props) => {

const [container, setContainer] = useState<HTMLDivElement | null>(null)

useListKeyboardNavigation(container, undefined, false, false)
useListKeyboardNavigation(container, {
initialFocus: 0,
shouldAutoFocus: false,
shouldWrapAround: false,
resetLastFocusedOnBlur: true,
})

if (allTags.length === 0) {
return (
Expand Down
26 changes: 18 additions & 8 deletions packages/web/src/javascripts/Hooks/useListKeyboardNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@ import { KeyboardKey } from '@standardnotes/ui-services'
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
import { useCallback, useEffect, useRef } from 'react'

export const useListKeyboardNavigation = (
containerElement: HTMLElement | null,
initialFocus = 0,
shouldAutoFocus = false,
shouldWrapAround = true,
) => {
type Options = {
initialFocus?: number
shouldAutoFocus?: boolean
shouldWrapAround?: boolean
resetLastFocusedOnBlur?: boolean
}

export const useListKeyboardNavigation = (containerElement: HTMLElement | null, options?: Options) => {
const {
initialFocus = 0,
shouldAutoFocus = false,
shouldWrapAround = true,
resetLastFocusedOnBlur = false,
} = options || {}

const listItems = useRef<HTMLButtonElement[]>([])
const setLatestListItems = useCallback(() => {
if (!containerElement) {
Expand Down Expand Up @@ -125,12 +134,13 @@ export const useListKeyboardNavigation = (

const focusOutHandler = useCallback(
(event: FocusEvent) => {
if (containerElement && containerElement.contains(event.relatedTarget as Node)) {
const isFocusInContainer = containerElement && containerElement.contains(event.relatedTarget as Node)
if (isFocusInContainer || !resetLastFocusedOnBlur) {
return
}
focusedItemIndex.current = initialFocus
},
[containerElement, initialFocus],
[containerElement, initialFocus, resetLastFocusedOnBlur],
)

useEffect(() => {
Expand Down

0 comments on commit ad1eb81

Please sign in to comment.