2
0
Files
bot/apps/landing-page/components/common/Navbar/useNavMenu.ts
2022-02-09 18:52:36 +01:00

128 lines
3.2 KiB
TypeScript
Executable File

import { useDisclosure } from '@chakra-ui/react'
import { isFocusable, getOwnerDocument, isRightClick } from '@chakra-ui/utils'
import * as React from 'react'
const getTarget = (event: React.FocusEvent) =>
(event.relatedTarget || document.activeElement) as HTMLElement
type OmitMotionProps<T> = Omit<
T,
'onAnimationStart' | 'onDragStart' | 'onDragEnd' | 'onDrag'
>
export function useNavMenu() {
const { isOpen, onClose, onToggle, onOpen } = useDisclosure()
const menuRef = React.useRef<HTMLDivElement>(null)
const triggerRef = React.useRef<HTMLAnchorElement>(null)
const timeoutRef = React.useRef<number>()
React.useEffect(() => {
return () => {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current)
}
}
}, [])
const focusMenu = () => {
timeoutRef.current = window.setTimeout(() => {
menuRef.current?.focus({ preventScroll: true })
}, 100)
}
const getTriggerProps = () => {
const triggerProps: React.ComponentPropsWithRef<'a'> = {
ref: triggerRef,
'aria-expanded': isOpen,
'aria-controls': 'menu',
'aria-haspopup': 'true',
}
triggerProps.onClick = (event: React.MouseEvent) => {
if (isRightClick(event)) return
onToggle()
if (!isOpen) {
focusMenu()
}
}
triggerProps.onBlur = (event: React.FocusEvent) => {
const target = getTarget(event)
if (isOpen && !menuRef.current?.contains(target)) {
onClose()
}
}
triggerProps.onKeyDown = (event: React.KeyboardEvent) => {
if (isOpen && event.key === 'Escape') {
onClose()
triggerRef.current?.focus({ preventScroll: true })
}
if (event.key === 'ArrowDown') {
if (!isOpen) onOpen()
focusMenu()
}
}
return triggerProps
}
const getMenuProps = () => {
const menuProps: OmitMotionProps<React.ComponentPropsWithRef<'div'>> = {
ref: menuRef,
id: 'menu',
tabIndex: -1,
}
menuProps.onKeyDown = (event: React.KeyboardEvent) => {
if (!isOpen) return
switch (event.key) {
case 'Escape': {
onClose()
return triggerRef.current?.focus()
}
case 'ArrowDown': {
const doc = getOwnerDocument(menuRef.current)
const next = doc?.activeElement
?.nextElementSibling as HTMLAnchorElement | null
return next?.focus()
}
case 'ArrowUp': {
const doc = getOwnerDocument(menuRef.current)
const prev = doc?.activeElement
?.previousElementSibling as HTMLAnchorElement | null
const el = (prev ?? triggerRef.current) as HTMLElement
return el.focus()
}
default:
break
}
}
menuProps.onBlur = (event: React.FocusEvent) => {
const target = getTarget(event)
const shouldBlur =
isOpen &&
!target.isSameNode(triggerRef.current) &&
!menuRef.current?.contains(target)
if (shouldBlur) {
onClose()
if (!isFocusable(target)) {
triggerRef.current?.focus({ preventScroll: true })
}
}
}
return menuProps
}
return {
isOpen,
onClose,
getTriggerProps,
getMenuProps,
}
}