-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor(v2): various dropdown improvements #3585
Changes from all commits
746b75c
447bf98
4721e3d
14ae9db
255bfdb
d1b43ca
5b34e75
4e1f5b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,13 +5,12 @@ | |
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import React, {useState} from 'react'; | ||
import React, {useState, useRef, useEffect} from 'react'; | ||
import clsx from 'clsx'; | ||
import Link from '@docusaurus/Link'; | ||
import useBaseUrl from '@docusaurus/useBaseUrl'; | ||
import {useLocation} from '@docusaurus/router'; | ||
import {isSamePath} from '../../utils'; | ||
import useOnClickOutside from 'use-onclickoutside'; | ||
import type { | ||
NavLinkProps, | ||
DesktopOrMobileNavBarItemProps, | ||
|
@@ -67,21 +66,28 @@ function NavItemDesktop({ | |
className, | ||
...props | ||
}: DesktopOrMobileNavBarItemProps) { | ||
const dropDownRef = React.useRef<HTMLDivElement>(null); | ||
const dropDownMenuRef = React.useRef<HTMLUListElement>(null); | ||
const [showDropDown, setShowDropDown] = useState(false); | ||
useOnClickOutside(dropDownRef, () => toggle(false)); | ||
function toggle(state: boolean) { | ||
if (state) { | ||
const firstNavLinkOfULElement = | ||
dropDownMenuRef?.current?.firstChild?.firstChild; | ||
|
||
if (firstNavLinkOfULElement) { | ||
(firstNavLinkOfULElement as HTMLElement).focus(); | ||
const dropdownRef = useRef<HTMLDivElement>(null); | ||
const dropdownMenuRef = useRef<HTMLUListElement>(null); | ||
const [showDropdown, setShowDropdown] = useState(false); | ||
|
||
useEffect(() => { | ||
const handleClickOutside = (event) => { | ||
if (!dropdownRef.current || dropdownRef.current.contains(event.target)) { | ||
return; | ||
} | ||
} | ||
setShowDropDown(state); | ||
} | ||
|
||
setShowDropdown(false); | ||
}; | ||
|
||
document.addEventListener('mousedown', handleClickOutside); | ||
document.addEventListener('touchstart', handleClickOutside); | ||
|
||
return () => { | ||
document.removeEventListener('mousedown', handleClickOutside); | ||
document.removeEventListener('touchstart', handleClickOutside); | ||
}; | ||
}, [dropdownRef]); | ||
|
||
const navLinkClassNames = (extraClassName?: string, isDropdownItem = false) => | ||
clsx( | ||
{ | ||
|
@@ -97,11 +103,11 @@ function NavItemDesktop({ | |
|
||
return ( | ||
<div | ||
ref={dropDownRef} | ||
ref={dropdownRef} | ||
className={clsx('navbar__item', 'dropdown', 'dropdown--hoverable', { | ||
'dropdown--left': position === 'left', | ||
'dropdown--right': position === 'right', | ||
'dropdown--show': showDropDown, | ||
'dropdown--show': showDropdown, | ||
})}> | ||
<NavLink | ||
className={navLinkClassNames(className)} | ||
|
@@ -110,27 +116,27 @@ function NavItemDesktop({ | |
onKeyDown={(e) => { | ||
if (e.key === 'Enter') { | ||
e.preventDefault(); | ||
toggle(true); | ||
setShowDropdown(!showDropdown); | ||
} | ||
}}> | ||
{props.label} | ||
</NavLink> | ||
<ul ref={dropDownMenuRef} className="dropdown__menu"> | ||
<ul ref={dropdownMenuRef} className="dropdown__menu"> | ||
{items.map(({className: childItemClassName, ...childItemProps}, i) => ( | ||
<li key={i}> | ||
<NavLink | ||
onKeyDown={(e) => { | ||
if (i === items.length - 1 && e.key === 'Tab') { | ||
e.preventDefault(); | ||
toggle(false); | ||
|
||
const nextNavbarItem = | ||
dropDownRef.current && | ||
(dropDownRef.current as HTMLElement).nextElementSibling; | ||
|
||
setShowDropdown(false); | ||
|
||
const nextNavbarItem = (dropdownRef.current as HTMLElement) | ||
.nextElementSibling; | ||
|
||
if (nextNavbarItem) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wonder if it is possible to shorten this code down to one-line? const nextNavbarItem = (dropdownRef.current as HTMLElement).nextElementSibling;
if (nextNavbarItem) {
(nextNavbarItem as HTMLElement).focus();
} I have tried using optional chaining, but I ran into the type error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Afaik it's annoying but we can't do anything about the types not being the proper ones. The shortest is probably: ((dropDownRef.current as HTMLElement)
?.nextElementSibling as HTMLElement)?.focus(); Not much better |
||
(nextNavbarItem as HTMLElement).focus(); | ||
} | ||
} | ||
} | ||
}} | ||
activeClassName="dropdown__link--active" | ||
|
@@ -146,16 +152,15 @@ function NavItemDesktop({ | |
|
||
function NavItemMobile({ | ||
items, | ||
position: _position, | ||
className, | ||
position: _position, // Need to destructure position from props so that it doesn't get passed on. | ||
...props | ||
}: DesktopOrMobileNavBarItemProps) { | ||
const {pathname} = useLocation(); | ||
const [collapsed, setCollapsed] = useState( | ||
() => !items?.some((item) => isSamePath(item.to, pathname)) ?? true, | ||
); | ||
|
||
// Need to destructure position from props so that it doesn't get passed on. | ||
const navLinkClassNames = (extraClassName?: string, isSubList = false) => | ||
clsx( | ||
'menu__link', | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't like much making the code a bit more complex, but as it should reduce a bit the bundle size, let's do this.