mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-29 18:27:56 +02:00
feat(v2): allow any type of NavbarItem
to be passed in a navbar dropdown (#5072)
* Initial work Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * More elegant `eslint-disable` Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Fix typing Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Allow doc links in dropdown Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Allow more dropdowns to use linklike items Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Finalize Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Dogfood Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Disallow nested dropdowns Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Better typing Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Complete type fix Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Use flatmap Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Patch Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Test Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Try fix Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Style change Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Revert (to test) Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Hmmm Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * I know what's wrong Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Does this work? Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Nope Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Wrong class name Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Pass prop to render dropdown item differently Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * This looks better Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Tests passed Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Separate dropdown from default Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Pourquois? Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Better prop typing Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Make code simpler Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * There's some extra className Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Test Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * More test Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * A-ha! Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Test again? * Add backward compatibility Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Incorporate my type fix Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * Resolve conflict Signed-off-by: Josh-Cena <sidachen2003@gmail.com> * minor refactor * minor refactors * allow usage of ES2019 in browser code * revert NavLink rename Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
parent
38c6cb5faa
commit
007e901354
13 changed files with 298 additions and 238 deletions
|
@ -8,7 +8,7 @@
|
|||
/* eslint-disable camelcase */
|
||||
|
||||
declare module '@docusaurus/plugin-content-docs-types' {
|
||||
import type {VersionBanner} from './types';
|
||||
type VersionBanner = import('./types').VersionBanner;
|
||||
|
||||
export type PropVersionMetadata = {
|
||||
pluginId: string;
|
||||
|
|
|
@ -58,7 +58,7 @@ function UnmaintainedVersionLabel({
|
|||
}
|
||||
|
||||
const BannerLabelComponents: Record<
|
||||
Props['versionMetadata']['banner'],
|
||||
Exclude<Props['versionMetadata']['banner'], 'none'>,
|
||||
ComponentType<BannerLabelComponentProps>
|
||||
> = {
|
||||
unreleased: UnreleasedVersionLabel,
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
import useHideableNavbar from '@theme/hooks/useHideableNavbar';
|
||||
import useLockBodyScroll from '@theme/hooks/useLockBodyScroll';
|
||||
import useWindowSize from '@theme/hooks/useWindowSize';
|
||||
import NavbarItem from '@theme/NavbarItem';
|
||||
import NavbarItem, {Props as NavbarItemConfig} from '@theme/NavbarItem';
|
||||
import Logo from '@theme/Logo';
|
||||
import IconMenu from '@theme/IconMenu';
|
||||
|
||||
|
@ -28,9 +28,14 @@ import styles from './styles.module.css';
|
|||
// retrocompatible with v1
|
||||
const DefaultNavItemPosition = 'right';
|
||||
|
||||
function useNavbarItems() {
|
||||
// TODO temporary casting until ThemeConfig type is improved
|
||||
return useThemeConfig().navbar.items as NavbarItemConfig[];
|
||||
}
|
||||
|
||||
// If split links by left/right
|
||||
// if position is unspecified, fallback to right (as v1)
|
||||
function splitNavItemsByPosition(items) {
|
||||
function splitNavItemsByPosition(items: NavbarItemConfig[]) {
|
||||
const leftItems = items.filter(
|
||||
(item) => (item.position ?? DefaultNavItemPosition) === 'left',
|
||||
);
|
||||
|
@ -132,9 +137,7 @@ function NavbarMobileSidebar({
|
|||
toggleSidebar,
|
||||
}: NavbarMobileSidebarProps) {
|
||||
useLockBodyScroll(sidebarShown);
|
||||
const {
|
||||
navbar: {items},
|
||||
} = useThemeConfig();
|
||||
const items = useNavbarItems();
|
||||
|
||||
const colorModeToggle = useColorModeToggle();
|
||||
|
||||
|
@ -166,12 +169,7 @@ function NavbarMobileSidebar({
|
|||
<div className="menu">
|
||||
<ul className="menu__list">
|
||||
{items.map((item, i) => (
|
||||
<NavbarItem
|
||||
mobile
|
||||
{...(item as any)} // TODO fix typing
|
||||
onClick={toggleSidebar}
|
||||
key={i}
|
||||
/>
|
||||
<NavbarItem mobile {...item} onClick={toggleSidebar} key={i} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -196,7 +194,7 @@ function NavbarMobileSidebar({
|
|||
|
||||
function Navbar(): JSX.Element {
|
||||
const {
|
||||
navbar: {items, hideOnScroll, style},
|
||||
navbar: {hideOnScroll, style},
|
||||
} = useThemeConfig();
|
||||
|
||||
const mobileSidebar = useMobileSidebar();
|
||||
|
@ -204,6 +202,7 @@ function Navbar(): JSX.Element {
|
|||
|
||||
const {navbarRef, isNavbarVisible} = useHideableNavbar(hideOnScroll);
|
||||
|
||||
const items = useNavbarItems();
|
||||
const hasSearchNavbarItem = items.some((item) => item.type === 'search');
|
||||
const {leftItems, rightItems} = splitNavItemsByPosition(items);
|
||||
|
||||
|
|
|
@ -5,16 +5,10 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {useState, useRef, useEffect} from 'react';
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import {useLocation} from '@docusaurus/router';
|
||||
import {
|
||||
isSamePath,
|
||||
useCollapsible,
|
||||
Collapsible,
|
||||
} from '@docusaurus/theme-common';
|
||||
import type {
|
||||
NavLinkProps,
|
||||
DesktopOrMobileNavBarItemProps,
|
||||
|
@ -25,7 +19,7 @@ import isInternalUrl from '@docusaurus/isInternalUrl';
|
|||
|
||||
const dropdownLinkActiveClass = 'dropdown__link--active';
|
||||
|
||||
function NavLink({
|
||||
export function NavLink({
|
||||
activeBasePath,
|
||||
activeBaseRegex,
|
||||
to,
|
||||
|
@ -34,7 +28,7 @@ function NavLink({
|
|||
activeClassName = 'navbar__link--active',
|
||||
prependBaseUrlToHref,
|
||||
...props
|
||||
}: NavLinkProps) {
|
||||
}: NavLinkProps): JSX.Element {
|
||||
// TODO all this seems hacky
|
||||
// {to: 'version'} should probably be forbidden, in favor of {to: '/version'}
|
||||
const toUrl = useBaseUrl(to);
|
||||
|
@ -75,159 +69,36 @@ function NavLink({
|
|||
);
|
||||
}
|
||||
|
||||
function NavItemDesktop({
|
||||
items,
|
||||
position,
|
||||
function DefaultNavbarItemDesktop({
|
||||
className,
|
||||
isDropdownItem = false,
|
||||
...props
|
||||
}: DesktopOrMobileNavBarItemProps) {
|
||||
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(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(
|
||||
{
|
||||
'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') {
|
||||
e.preventDefault();
|
||||
setShowDropdown(!showDropdown);
|
||||
}
|
||||
}}>
|
||||
{props.children ?? 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();
|
||||
|
||||
setShowDropdown(false);
|
||||
|
||||
const nextNavbarItem = (dropdownRef.current as HTMLElement)
|
||||
.nextElementSibling;
|
||||
|
||||
if (nextNavbarItem) {
|
||||
(nextNavbarItem as HTMLElement).focus();
|
||||
}
|
||||
}
|
||||
}}
|
||||
activeClassName={dropdownLinkActiveClass}
|
||||
className={navLinkClassNames(childItemClassName, true)}
|
||||
{...childItemProps}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<NavLink
|
||||
className={clsx(
|
||||
isDropdownItem ? 'dropdown__link' : 'navbar__item navbar__link',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function NavItemMobile({
|
||||
items,
|
||||
function DefaultNavbarItemMobile({
|
||||
className,
|
||||
position: _position, // Need to destructure position from props so that it doesn't get passed on.
|
||||
isDropdownItem: _isDropdownItem,
|
||||
...props
|
||||
}: DesktopOrMobileNavBarItemProps) {
|
||||
const {pathname} = useLocation();
|
||||
const {collapsed, toggleCollapsed} = useCollapsible({
|
||||
initialState: () =>
|
||||
!items?.some((item) => isSamePath(item.to, pathname)) ?? true,
|
||||
});
|
||||
|
||||
const navLinkClassNames = (extraClassName?: string, 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={clsx('menu__list-item', {
|
||||
'menu__list-item--collapsed': collapsed,
|
||||
})}>
|
||||
<NavLink
|
||||
role="button"
|
||||
className={navLinkClassNames(className, true)}
|
||||
{...props}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
toggleCollapsed();
|
||||
}}>
|
||||
{props.children ?? props.label}
|
||||
</NavLink>
|
||||
|
||||
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>
|
||||
{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>
|
||||
))}
|
||||
</Collapsible>
|
||||
<li className="menu__list-item">
|
||||
<NavLink className={clsx('menu__link', className)} {...props} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function DefaultNavbarItem({mobile = false, ...props}: Props): JSX.Element {
|
||||
const Comp = mobile ? NavItemMobile : NavItemDesktop;
|
||||
const Comp = mobile ? DefaultNavbarItemMobile : DefaultNavbarItemDesktop;
|
||||
return <Comp {...props} />;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,17 +12,10 @@ import clsx from 'clsx';
|
|||
import type {Props} from '@theme/NavbarItem/DocNavbarItem';
|
||||
import {useDocsPreferredVersion} from '@docusaurus/theme-common';
|
||||
import {uniq} from '@docusaurus/utils-common';
|
||||
import type {
|
||||
GlobalDataVersion,
|
||||
GlobalDataDoc,
|
||||
} from '@docusaurus/plugin-content-docs-types';
|
||||
import type {GlobalDataVersion} from '@docusaurus/plugin-content-docs-types';
|
||||
|
||||
function getDocInVersions(versions: GlobalDataVersion[], docId: string) {
|
||||
// vanilla-js flatten, TODO replace soon by ES flat() / flatMap()
|
||||
const allDocs: GlobalDataDoc[] = [].concat(
|
||||
...versions.map((version) => version.docs),
|
||||
);
|
||||
|
||||
const allDocs = versions.flatMap((version) => version.docs);
|
||||
const doc = allDocs.find((versionDoc) => versionDoc.id === docId);
|
||||
if (!doc) {
|
||||
const docIds = allDocs.map((versionDoc) => versionDoc.id).join('\n- ');
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import {
|
||||
useVersions,
|
||||
useLatestVersion,
|
||||
|
@ -52,20 +53,7 @@ export default function DocsVersionDropdownNavbarItem({
|
|||
};
|
||||
});
|
||||
|
||||
const items = [
|
||||
...dropdownItemsBefore,
|
||||
...versionLinks,
|
||||
...dropdownItemsAfter,
|
||||
];
|
||||
|
||||
// We don't want to render a version dropdown with 0 or 1 item
|
||||
// If we build the site with a single docs version (onlyIncludeVersions: ['1.0.0'])
|
||||
// We'd rather render a button instead of a dropdown
|
||||
if (items.length <= 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return items;
|
||||
return [...dropdownItemsBefore, ...versionLinks, ...dropdownItemsAfter];
|
||||
}
|
||||
|
||||
const items = getItems();
|
||||
|
@ -78,8 +66,23 @@ export default function DocsVersionDropdownNavbarItem({
|
|||
const dropdownTo =
|
||||
mobile && items ? undefined : getVersionMainDoc(dropdownVersion).path;
|
||||
|
||||
// We don't want to render a version dropdown with 0 or 1 item
|
||||
// If we build the site with a single docs version (onlyIncludeVersions: ['1.0.0'])
|
||||
// We'd rather render a button instead of a dropdown
|
||||
if (items.length <= 1) {
|
||||
return (
|
||||
<DefaultNavbarItem
|
||||
{...props}
|
||||
mobile={mobile}
|
||||
label={dropdownLabel}
|
||||
to={dropdownTo}
|
||||
isActive={dropdownActiveClassDisabled ? () => false : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DefaultNavbarItem
|
||||
<DropdownNavbarItem
|
||||
{...props}
|
||||
mobile={mobile}
|
||||
label={dropdownLabel}
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* 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, {useState, useRef, useEffect} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {useLocation} from '@docusaurus/router';
|
||||
import {
|
||||
isSamePath,
|
||||
useCollapsible,
|
||||
Collapsible,
|
||||
} from '@docusaurus/theme-common';
|
||||
import type {
|
||||
DesktopOrMobileNavBarItemProps,
|
||||
Props,
|
||||
} from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import {NavLink} from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import NavbarItem from '@theme/NavbarItem';
|
||||
|
||||
const dropdownLinkActiveClass = 'dropdown__link--active';
|
||||
|
||||
function DropdownNavbarItemDesktop({
|
||||
items,
|
||||
position,
|
||||
className,
|
||||
...props
|
||||
}: DesktopOrMobileNavBarItemProps) {
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const dropdownMenuRef = useRef<HTMLUListElement>(null);
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
|
||||
if (
|
||||
!dropdownRef.current ||
|
||||
dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setShowDropdown(false);
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener('touchstart', handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
document.removeEventListener('touchstart', handleClickOutside);
|
||||
};
|
||||
}, [dropdownRef]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
className={clsx('navbar__item', 'dropdown', 'dropdown--hoverable', {
|
||||
'dropdown--left': position === 'left',
|
||||
'dropdown--right': position === 'right',
|
||||
'dropdown--show': showDropdown,
|
||||
})}>
|
||||
<NavLink
|
||||
className={clsx('navbar__item navbar__link', className)}
|
||||
{...props}
|
||||
onClick={props.to ? undefined : (e) => e.preventDefault()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
setShowDropdown(!showDropdown);
|
||||
}
|
||||
}}>
|
||||
{props.children ?? props.label}
|
||||
</NavLink>
|
||||
<ul ref={dropdownMenuRef} className="dropdown__menu">
|
||||
{items.map((childItemProps, i) => (
|
||||
<NavbarItem
|
||||
isDropdownItem
|
||||
onKeyDown={(e) => {
|
||||
if (i === items.length - 1 && e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
setShowDropdown(false);
|
||||
const nextNavbarItem = dropdownRef.current!.nextElementSibling;
|
||||
if (nextNavbarItem) {
|
||||
(nextNavbarItem as HTMLElement).focus();
|
||||
}
|
||||
}
|
||||
}}
|
||||
activeClassName={dropdownLinkActiveClass}
|
||||
{...childItemProps}
|
||||
key={i}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownNavbarItemMobile({
|
||||
items,
|
||||
className,
|
||||
position: _position, // Need to destructure position from props so that it doesn't get passed on.
|
||||
...props
|
||||
}: DesktopOrMobileNavBarItemProps) {
|
||||
const {pathname} = useLocation();
|
||||
const {collapsed, toggleCollapsed} = useCollapsible({
|
||||
initialState: () =>
|
||||
!items?.some((item) => isSamePath(item.to, pathname)) ?? true,
|
||||
});
|
||||
|
||||
return (
|
||||
<li
|
||||
className={clsx('menu__list-item', {
|
||||
'menu__list-item--collapsed': collapsed,
|
||||
})}>
|
||||
<NavLink
|
||||
role="button"
|
||||
className={clsx('menu__link menu__link--sublist', className)}
|
||||
{...props}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
toggleCollapsed();
|
||||
}}>
|
||||
{props.children ?? props.label}
|
||||
</NavLink>
|
||||
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>
|
||||
{items.map((childItemProps, i) => (
|
||||
<NavbarItem
|
||||
mobile
|
||||
isDropdownItem
|
||||
onClick={props.onClick}
|
||||
activeClassName="menu__link--active"
|
||||
{...childItemProps}
|
||||
key={i}
|
||||
/>
|
||||
))}
|
||||
</Collapsible>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function DropdownNavbarItem({mobile = false, ...props}: Props): JSX.Element {
|
||||
const Comp = mobile ? DropdownNavbarItemMobile : DropdownNavbarItemDesktop;
|
||||
return <Comp {...props} />;
|
||||
}
|
||||
|
||||
export default DropdownNavbarItem;
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import IconLanguage from '@theme/IconLanguage';
|
||||
import type {Props} from '@theme/NavbarItem/LocaleDropdownNavbarItem';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
|
@ -49,7 +49,7 @@ export default function LocaleDropdownNavbarItem({
|
|||
const dropdownLabel = mobile ? 'Languages' : getLocaleLabel(currentLocale);
|
||||
|
||||
return (
|
||||
<DefaultNavbarItem
|
||||
<DropdownNavbarItem
|
||||
{...props}
|
||||
href="#"
|
||||
mobile={mobile}
|
||||
|
|
|
@ -7,39 +7,54 @@
|
|||
|
||||
import React from 'react';
|
||||
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import DropdownNavbarItem, {
|
||||
Props as DropdownNavbarItemProps,
|
||||
} from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
|
||||
import SearchNavbarItem from '@theme/NavbarItem/SearchNavbarItem';
|
||||
import type {Props} from '@theme/NavbarItem';
|
||||
import type {Types, Props} from '@theme/NavbarItem';
|
||||
|
||||
const NavbarItemComponents = {
|
||||
const NavbarItemComponents: Record<
|
||||
Exclude<Types, undefined>,
|
||||
() => (props) => JSX.Element
|
||||
> = {
|
||||
default: () => DefaultNavbarItem,
|
||||
localeDropdown: () => LocaleDropdownNavbarItem,
|
||||
search: () => SearchNavbarItem,
|
||||
dropdown: () => DropdownNavbarItem,
|
||||
|
||||
// Need to lazy load these items as we don't know for sure the docs plugin is loaded
|
||||
// See https://github.com/facebook/docusaurus/issues/3360
|
||||
docsVersion: () =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
||||
require('@theme/NavbarItem/DocsVersionNavbarItem').default,
|
||||
/* eslint-disable @typescript-eslint/no-var-requires, global-require */
|
||||
docsVersion: () => require('@theme/NavbarItem/DocsVersionNavbarItem').default,
|
||||
docsVersionDropdown: () =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
||||
require('@theme/NavbarItem/DocsVersionDropdownNavbarItem').default,
|
||||
doc: () =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
||||
require('@theme/NavbarItem/DocNavbarItem').default,
|
||||
doc: () => require('@theme/NavbarItem/DocNavbarItem').default,
|
||||
/* eslint-enable @typescript-eslint/no-var-requires, global-require */
|
||||
} as const;
|
||||
|
||||
const getNavbarItemComponent = (
|
||||
type: keyof typeof NavbarItemComponents = 'default',
|
||||
) => {
|
||||
const navbarItemComponent = NavbarItemComponents[type];
|
||||
if (!navbarItemComponent) {
|
||||
type NavbarItemComponentType = keyof typeof NavbarItemComponents;
|
||||
|
||||
const getNavbarItemComponent = (type: NavbarItemComponentType) => {
|
||||
const navbarItemComponentFn = NavbarItemComponents[type];
|
||||
if (!navbarItemComponentFn) {
|
||||
throw new Error(`No NavbarItem component found for type "${type}".`);
|
||||
}
|
||||
return navbarItemComponent();
|
||||
return navbarItemComponentFn();
|
||||
};
|
||||
|
||||
export default function NavbarItem({type, ...props}: Props): JSX.Element {
|
||||
const NavbarItemComponent = getNavbarItemComponent(type);
|
||||
function getComponentType({type, ...props}: Props): NavbarItemComponentType {
|
||||
// Backward compatibility: navbar item with no type set
|
||||
// but containing dropdown items should use the type "dropdown"
|
||||
if (!type || type === 'default') {
|
||||
const isDropdown = (props as DropdownNavbarItemProps).items !== undefined;
|
||||
return isDropdown ? 'dropdown' : 'default';
|
||||
}
|
||||
return type as NavbarItemComponentType;
|
||||
}
|
||||
|
||||
export default function NavbarItem(props: Props): JSX.Element {
|
||||
const componentType = getComponentType(props);
|
||||
const NavbarItemComponent = getNavbarItemComponent(componentType);
|
||||
return <NavbarItemComponent {...props} />;
|
||||
}
|
||||
|
|
96
packages/docusaurus-theme-classic/src/types.d.ts
vendored
96
packages/docusaurus-theme-classic/src/types.d.ts
vendored
|
@ -334,23 +334,18 @@ declare module '@theme/Navbar' {
|
|||
}
|
||||
|
||||
declare module '@theme/NavbarItem/DefaultNavbarItem' {
|
||||
import type {ComponentProps, ReactNode} from 'react';
|
||||
import type {LinkProps} from '@docusaurus/Link';
|
||||
|
||||
export type NavLinkProps = {
|
||||
activeBasePath?: string;
|
||||
activeBaseRegex?: string;
|
||||
to?: string;
|
||||
exact?: boolean;
|
||||
href?: string;
|
||||
label?: ReactNode;
|
||||
activeClassName?: string;
|
||||
prependBaseUrlToHref?: string;
|
||||
isActive?: () => boolean;
|
||||
} & ComponentProps<'a'>;
|
||||
export type NavLinkProps = LinkProps & {
|
||||
readonly activeBasePath?: string;
|
||||
readonly activeBaseRegex?: string;
|
||||
readonly exact?: boolean;
|
||||
readonly label?: ReactNode;
|
||||
readonly prependBaseUrlToHref?: string;
|
||||
};
|
||||
|
||||
export type DesktopOrMobileNavBarItemProps = NavLinkProps & {
|
||||
readonly items?: readonly NavLinkProps[];
|
||||
readonly position?: 'left' | 'right';
|
||||
readonly isDropdownItem?: boolean;
|
||||
readonly className?: string;
|
||||
};
|
||||
|
||||
|
@ -358,10 +353,30 @@ declare module '@theme/NavbarItem/DefaultNavbarItem' {
|
|||
readonly mobile?: boolean;
|
||||
};
|
||||
|
||||
export const NavLink: (props: NavLinkProps) => JSX.Element;
|
||||
|
||||
const DefaultNavbarItem: (props: Props) => JSX.Element;
|
||||
export default DefaultNavbarItem;
|
||||
}
|
||||
|
||||
declare module '@theme/NavbarItem/DropdownNavbarItem' {
|
||||
import type {NavLinkProps} from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem';
|
||||
|
||||
export type DesktopOrMobileNavBarItemProps = NavLinkProps & {
|
||||
readonly position?: 'left' | 'right';
|
||||
readonly items: readonly LinkLikeNavbarItemProps[];
|
||||
readonly className?: string;
|
||||
};
|
||||
|
||||
export type Props = DesktopOrMobileNavBarItemProps & {
|
||||
readonly mobile?: boolean;
|
||||
};
|
||||
|
||||
const DropdownNavbarItem: (props: Props) => JSX.Element;
|
||||
export default DropdownNavbarItem;
|
||||
}
|
||||
|
||||
declare module '@theme/NavbarItem/SearchNavbarItem' {
|
||||
export type Props = {readonly mobile?: boolean};
|
||||
|
||||
|
@ -370,12 +385,12 @@ declare module '@theme/NavbarItem/SearchNavbarItem' {
|
|||
}
|
||||
|
||||
declare module '@theme/NavbarItem/LocaleDropdownNavbarItem' {
|
||||
import type {Props as DefaultNavbarItemProps} from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import type {NavLinkProps} from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import type {Props as DropdownNavbarItemProps} from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem';
|
||||
|
||||
export type Props = DefaultNavbarItemProps & {
|
||||
readonly dropdownItemsBefore: NavLinkProps[];
|
||||
readonly dropdownItemsAfter: NavLinkProps[];
|
||||
export type Props = DropdownNavbarItemProps & {
|
||||
readonly dropdownItemsBefore: LinkLikeNavbarItemProps[];
|
||||
readonly dropdownItemsAfter: LinkLikeNavbarItemProps[];
|
||||
};
|
||||
|
||||
const LocaleDropdownNavbarItem: (props: Props) => JSX.Element;
|
||||
|
@ -383,14 +398,14 @@ declare module '@theme/NavbarItem/LocaleDropdownNavbarItem' {
|
|||
}
|
||||
|
||||
declare module '@theme/NavbarItem/DocsVersionDropdownNavbarItem' {
|
||||
import type {Props as DefaultNavbarItemProps} from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import type {NavLinkProps} from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import type {Props as DropdownNavbarItemProps} from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem';
|
||||
|
||||
export type Props = DefaultNavbarItemProps & {
|
||||
export type Props = DropdownNavbarItemProps & {
|
||||
readonly docsPluginId?: string;
|
||||
readonly dropdownActiveClassDisabled?: boolean;
|
||||
readonly dropdownItemsBefore: NavLinkProps[];
|
||||
readonly dropdownItemsAfter: NavLinkProps[];
|
||||
readonly dropdownItemsBefore: LinkLikeNavbarItemProps[];
|
||||
readonly dropdownItemsAfter: LinkLikeNavbarItemProps[];
|
||||
};
|
||||
|
||||
const DocsVersionDropdownNavbarItem: (props: Props) => JSX.Element;
|
||||
|
@ -420,18 +435,35 @@ declare module '@theme/NavbarItem/DocNavbarItem' {
|
|||
}
|
||||
|
||||
declare module '@theme/NavbarItem' {
|
||||
import type {ComponentProps} from 'react';
|
||||
import type {Props as DefaultNavbarItemProps} from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import type {Props as DocsVersionDropdownNavbarItemProps} from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
|
||||
import type {Props as DocNavbarItemProps} from '@theme/NavbarItem/DocNavbarItem';
|
||||
import type {Props as DocsVersionNavbarItemProps} from '@theme/NavbarItem/DocsVersionNavbarItem';
|
||||
import type {Props as DropdownNavbarItemProps} from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import type {Props as DocsVersionDropdownNavbarItemProps} from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
|
||||
import type {Props as LocaleDropdownNavbarItemProps} from '@theme/NavbarItem/LocaleDropdownNavbarItem';
|
||||
import type {Props as SearchNavbarItemProps} from '@theme/NavbarItem/SearchNavbarItem';
|
||||
|
||||
export type Props =
|
||||
| ({readonly type?: 'default' | undefined} & DefaultNavbarItemProps)
|
||||
| ({
|
||||
readonly type: 'docsVersionDropdown';
|
||||
} & DocsVersionDropdownNavbarItemProps)
|
||||
| ({readonly type: 'docsVersion'} & DocsVersionNavbarItemProps)
|
||||
| ({readonly type: 'search'} & SearchNavbarItemProps);
|
||||
export type LinkLikeNavbarItemProps =
|
||||
| ({readonly type?: 'default'} & DefaultNavbarItemProps)
|
||||
| ({readonly type: 'doc'} & DocNavbarItemProps)
|
||||
| ({readonly type: 'docsVersion'} & DocsVersionNavbarItemProps);
|
||||
|
||||
export type Props = ComponentProps<'a'> & {
|
||||
readonly position?: 'left' | 'right';
|
||||
} & (
|
||||
| LinkLikeNavbarItemProps
|
||||
| ({readonly type?: 'dropdown'} & DropdownNavbarItemProps)
|
||||
| ({
|
||||
readonly type: 'docsVersionDropdown';
|
||||
} & DocsVersionDropdownNavbarItemProps)
|
||||
| ({readonly type: 'localeDropdown'} & LocaleDropdownNavbarItemProps)
|
||||
| ({
|
||||
readonly type: 'search';
|
||||
} & SearchNavbarItemProps)
|
||||
);
|
||||
|
||||
export type Types = Props['type'];
|
||||
|
||||
const NavbarItem: (props: Props) => JSX.Element;
|
||||
export default NavbarItem;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM"],
|
||||
"lib": ["DOM", "ES2019"],
|
||||
"module": "esnext",
|
||||
"noEmit": true,
|
||||
"noImplicitAny": false,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"lib": ["DOM"],
|
||||
"lib": ["DOM", "ES2019"],
|
||||
"module": "esnext",
|
||||
"tsBuildInfoFile": "./lib/client/.tsbuildinfo",
|
||||
"outDir": "lib/client",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"lib": ["DOM"],
|
||||
"lib": ["DOM", "ES2019"],
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo",
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
|
|
Loading…
Add table
Reference in a new issue