docusaurus/packages/docusaurus-theme-classic/src/theme/NavbarItem/DefaultNavbarItem.tsx
Ali Hosseini ee6dee72b7
fix(v2): navbar dropdown opened with tab, not closing on click outside (#3240)
* package use-onclickoutside added

* fix:dropdown toggle updated and tab behavior fixed

* fix:variable name  and type checker on drop down updated

* fix: optional chaining added to dropdown toggle function

* fix: package.json problem fixed and type of element updated

* fix:type problem fixed
2020-08-11 15:54:33 +02:00

180 lines
5.1 KiB
TypeScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, {ComponentProps, ComponentType, useState} from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import useOnClickOutside from 'use-onclickoutside';
function NavLink({
activeBasePath,
activeBaseRegex,
to,
href,
label,
activeClassName = 'navbar__link--active',
prependBaseUrlToHref,
...props
}: {
activeBasePath?: string;
activeBaseRegex?: string;
to?: string;
href?: string;
label?: string;
activeClassName?: string;
prependBaseUrlToHref?: string;
} & ComponentProps<'a'>) {
// TODO all this seems hacky
// {to: 'version'} should probably be forbidden, in favor of {to: '/version'}
const toUrl = useBaseUrl(to);
const activeBaseUrl = useBaseUrl(activeBasePath);
const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
return (
<Link
{...(href
? {
target: '_blank',
rel: 'noopener noreferrer',
href: prependBaseUrlToHref ? normalizedHref : href,
}
: {
isNavLink: true,
activeClassName,
to: toUrl,
...(activeBasePath || activeBaseRegex
? {
isActive: (_match, location) =>
activeBaseRegex
? new RegExp(activeBaseRegex).test(location.pathname)
: location.pathname.startsWith(activeBaseUrl),
}
: null),
})}
{...props}>
{label}
</Link>
);
}
function NavItemDesktop({items, position, className, ...props}) {
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();
}
}
setShowDropDown(state);
}
const navLinkClassNames = (extraClassName, isDropdownItem = false) =>
clsx(
{
'navbar__item navbar__link': !isDropdownItem,
dropdown__link: isDropdownItem,
},
extraClassName,
);
if (!items) {
return <NavLink className={navLinkClassNames(className)} {...props} />;
}
return (
<div
ref={dropDownRef}
className={clsx('navbar__item', 'dropdown', 'dropdown--hoverable', {
'dropdown--left': position === 'left',
'dropdown--right': position === 'right',
'dropdown--show': showDropDown,
})}>
<NavLink
className={navLinkClassNames(className)}
{...props}
onClick={props.to ? undefined : (e) => e.preventDefault()}
onKeyDown={(e) => {
if ((e.key === 'Enter' && !props.to) || e.key === 'Tab') {
e.preventDefault();
toggle(true);
}
}}>
{props.label}
</NavLink>
<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);
}
}}
activeClassName="dropdown__link--active"
className={navLinkClassNames(childItemClassName, true)}
{...childItemProps}
/>
</li>
))}
</ul>
</div>
);
}
function NavItemMobile({items, position: _position, className, ...props}) {
// Need to destructure position from props so that it doesn't get passed on.
const navLinkClassNames = (extraClassName, isSubList = false) =>
clsx(
'menu__link',
{
'menu__link--sublist': isSubList,
},
extraClassName,
);
if (!items) {
return (
<li className="menu__list-item">
<NavLink className={navLinkClassNames(className)} {...props} />
</li>
);
}
return (
<li className="menu__list-item">
<NavLink className={navLinkClassNames(className, true)} {...props}>
{props.label}
</NavLink>
<ul className="menu__list">
{items.map(({className: childItemClassName, ...childItemProps}, i) => (
<li className="menu__list-item" key={i}>
<NavLink
activeClassName="menu__link--active"
className={navLinkClassNames(childItemClassName)}
{...childItemProps}
onClick={props.onClick}
/>
</li>
))}
</ul>
</li>
);
}
function DefaultNavbarItem({mobile = false, ...props}) {
const Comp: ComponentType<any> = mobile ? NavItemMobile : NavItemDesktop;
return <Comp {...props} />;
}
export default DefaultNavbarItem;