Skip to content

Commit

Permalink
refactor: create get-entry-relative-to in utils
Browse files Browse the repository at this point in the history
  • Loading branch information
amy-corson-ibigroup committed Apr 23, 2024
1 parent 005eb30 commit f3fcc11
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 61 deletions.
26 changes: 8 additions & 18 deletions lib/components/app/app-menu-item.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ChevronDown } from '@styled-icons/fa-solid/ChevronDown'
import { ChevronUp } from '@styled-icons/fa-solid/ChevronUp'
import AnimateHeight from 'react-animate-height'
import React, { Component, HTMLAttributes, KeyboardEvent } from 'react'

import { getEntryRelativeTo } from '../util/get-entry-relative-to'
import AnimateHeight from 'react-animate-height'
import Link from '../util/link'

import React, { Component, HTMLAttributes, KeyboardEvent } from 'react'

interface Props extends HTMLAttributes<HTMLElement> {
href?: string
icon?: JSX.Element
Expand All @@ -17,20 +19,8 @@ interface Props extends HTMLAttributes<HTMLElement> {
interface State {
isExpanded: boolean
}

/**
* Helper method to find the element within the app menu at the given offset
* (e.g. previous or next) relative to the specified element.
* The query is limited to the app menu so that arrow navigation is contained within
* (tab navigation is not restricted).
*/
function getEntryRelativeTo(element: EventTarget, offset: 1 | -1): HTMLElement {
const entries = Array.from(
document.querySelectorAll('.app-menu a, .app-menu button')
)
const elementIndex = entries.indexOf(element as HTMLElement)
return entries[elementIndex + offset] as HTMLElement
}
// Argument for document.querySelectorAll to target focusable elements.
const queryId = '.app-menu a, .app-menu button'

/**
* Renders a single entry from the hamburger menu.
Expand All @@ -48,13 +38,13 @@ export default class AppMenuItem extends Component<Props, State> {
subItems && this.setState({ isExpanded: false })
break
case 'ArrowUp':
getEntryRelativeTo(element, -1)?.focus()
getEntryRelativeTo(queryId, element, -1)?.focus()
break
case 'ArrowRight':
subItems && this.setState({ isExpanded: true })
break
case 'ArrowDown':
getEntryRelativeTo(element, 1)?.focus()
getEntryRelativeTo(queryId, element, 1)?.focus()
break
case ' ':
// For links (tagName "A" uppercase), trigger link on space for consistency with buttons.
Expand Down
27 changes: 6 additions & 21 deletions lib/components/map/point-popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import styled from 'styled-components'
import type { Location } from '@opentripplanner/types'

import * as mapActions from '../../actions/map'
import { getEntryRelativeTo } from '../util/get-entry-relative-to'
import { Icon } from '../util/styledIcon'
import { renderCoordinates } from '../../util/i18n'
import { SetLocationHandler, ZoomToPlaceHandler } from '../util/types'
Expand Down Expand Up @@ -54,32 +55,16 @@ function MapPopup({
* Creates a focus trap within map popup that can be dismissed with esc.
* https://medium.com/cstech/achieving-focus-trapping-in-a-react-modal-component-3f28f596f35b
*/
function getEntryRelativeTo(
element: EventTarget,
offset: 1 | -1
): HTMLElement {
const entries = Array.from(
document.querySelectorAll('button#zoom-btn, #from-to button')
)
const firstElement = entries[0]
const lastElement = entries[entries.length - 1]
const elementIndex = entries.indexOf(element as HTMLButtonElement)

if (element === firstElement && offset === -1) {
return lastElement as HTMLElement
}
if (element === lastElement && offset === 1) {
return firstElement as HTMLElement
}
return entries[elementIndex + offset] as HTMLElement
}

/**
* Check to see which keys are down by tracking keyUp and keyDown events
* in order to see when "tab" and "shift" are pressed at the same time.
*/
let keysDown: string[] = useMemo(() => [], [])

// Argument for document.querySelectorAll to target focusable elements.
const queryId = 'button#zoom-btn, #from-to button'

const _handleKeyDown = useCallback(
(e) => {
keysDown.push(e.key)
Expand All @@ -91,10 +76,10 @@ function MapPopup({
case 'Tab':
if (keysDown.includes('Shift')) {
e.preventDefault()
getEntryRelativeTo(element, -1)?.focus()
getEntryRelativeTo(queryId, element, -1)?.focus()
} else {
e.preventDefault()
getEntryRelativeTo(element, 1)?.focus()
getEntryRelativeTo(queryId, element, 1)?.focus()
}
break
case ' ':
Expand Down
30 changes: 8 additions & 22 deletions lib/components/util/dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { NavbarButton } from '../app/nav-item'
import React, {
HTMLAttributes,
KeyboardEvent,
Expand All @@ -7,6 +6,9 @@ import React, {
useRef,
useState
} from 'react'

import { getEntryRelativeTo } from './get-entry-relative-to'
import { NavbarButton } from '../app/nav-item'
import styled from 'styled-components'

interface Props extends HTMLAttributes<HTMLElement> {
Expand Down Expand Up @@ -61,25 +63,6 @@ const DropdownMenu = styled.ul`
}
`

/**
* Helper method to find the element within dropdown menu at the given offset
* (e.g. previous or next) relative to the specified element.
* The query is limited to the dropdown so that arrow navigation is contained within
* (tab navigation is not restricted).
*/
function getEntryRelativeTo(
id: string,
element: EventTarget,
offset: 1 | -1
): HTMLElement {
const entries = Array.from(
document.querySelectorAll(`#${id} button, #${id}-label`)
)
const elementIndex = entries.indexOf(element as HTMLElement)

return entries[elementIndex + offset] as HTMLElement
}

/**
* Renders a dropdown menu. By default, only a passed "name" is rendered. If clicked,
* a floating div is rendered below the "name" with list contents inside. Clicking anywhere
Expand All @@ -100,6 +83,9 @@ export const Dropdown = ({

const toggleOpen = useCallback(() => setOpen(!open), [open, setOpen])

// Argument for document.querySelectorAll to target focusable elements.
const queryId = `#${id} button, #${id}-label`

// Adding document event listeners allows us to close the dropdown
// when the user interacts with any part of the page that isn't the dropdown
useEffect(() => {
Expand All @@ -124,11 +110,11 @@ export const Dropdown = ({
switch (e.key) {
case 'ArrowUp':
e.preventDefault()
getEntryRelativeTo(id, element, -1)?.focus()
getEntryRelativeTo(queryId, element, -1)?.focus()
break
case 'ArrowDown':
e.preventDefault()
getEntryRelativeTo(id, element, 1)?.focus()
getEntryRelativeTo(queryId, element, 1)?.focus()
break
case 'Escape':
setOpen(false)
Expand Down
28 changes: 28 additions & 0 deletions lib/components/util/get-entry-relative-to.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Helper method to find the element within the app menu at the given offset
* (e.g. previous or next) relative to the specified element.
*
* @param {string} query - Argument that gets passed to document.querySelectorAll
* @param {HTMLElement} element - Specified element (e.target)
* @param {1 | -1} offset - Determines direction to move within array of focusable elements (previous or next)
* @returns {HTMLElement} - element to be focused
*/

export function getEntryRelativeTo(
query: string,
element: EventTarget,
offset: 1 | -1
): HTMLElement {
const entries = Array.from(document.querySelectorAll(query))
const firstElement = entries[0]
const lastElement = entries[entries.length - 1]
const elementIndex = entries.indexOf(element as HTMLButtonElement)

if (element === firstElement && offset === -1) {
return lastElement as HTMLElement
}
if (element === lastElement && offset === 1) {
return firstElement as HTMLElement
}
return entries[elementIndex + offset] as HTMLElement
}

0 comments on commit f3fcc11

Please sign in to comment.