website/src/theme/DocSidebar/index.tsx (136 lines of code) (raw):

/** * 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 type { FC } from 'react'; import React, { useState } from 'react'; import clsx from 'clsx'; // eslint-disable-next-line import/no-extraneous-dependencies import { useThemeConfig, useAnnouncementBar, MobileSecondaryMenuFiller, ThemeClassNames, } from '@docusaurus/theme-common'; import useWindowSize from '@theme/hooks/useWindowSize'; import useScrollPosition from '@theme/hooks/useScrollPosition'; import Logo from '@theme/Logo'; import IconArrow from '@theme/IconArrow'; import { translate } from '@docusaurus/Translate'; import { DocSidebarItems } from '@theme/DocSidebarItem'; import DocsVersionDropdownNavbarItem from '@theme/NavbarItem/DocsVersionDropdownNavbarItem'; import type { Props } from '@theme/DocSidebar'; import { archivedVersions } from '../../../../config/apisix-versions'; import styles from './styles.module.css'; function useShowAnnouncementBar() { const { isClosed } = useAnnouncementBar(); const [showAnnouncementBar, setShowAnnouncementBar] = useState(!isClosed); useScrollPosition(({ scrollY }) => { if (!isClosed) { setShowAnnouncementBar(scrollY === 0); } }); return showAnnouncementBar; } const HideableSidebarButton = ({ onClick }: {onClick: React.MouseEventHandler}) => ( <button type="button" title={translate({ id: 'theme.docs.sidebar.collapseButtonTitle', message: 'Collapse sidebar', description: 'The title attribute for collapse button of doc sidebar', })} aria-label={translate({ id: 'theme.docs.sidebar.collapseButtonAriaLabel', message: 'Collapse sidebar', description: 'The title attribute for collapse button of doc sidebar', })} className={clsx( 'button button--secondary button--outline', styles.collapseSidebarButton, )} onClick={onClick} > <IconArrow className={styles.collapseSidebarButtonIcon} /> </button> ); const DocsVersionWrapper = (props: {docsPluginId: string}) => { const { docsPluginId } = props; return ( <div className={styles.sidebarVersionSwitch}> Version: <DocsVersionDropdownNavbarItem docsPluginId={docsPluginId} dropdownItemsBefore={[]} dropdownItemsAfter={docsPluginId === 'docs-apisix' ? archivedVersions : []} items={[]} /> </div> ); }; const DocsVersionWrapperMemo = React.memo(DocsVersionWrapper); interface DocSidebarMobileSecondaryMenuProps extends Props { docsPluginId: string, toggleSidebar: () => void } const DocSidebarMobileSecondaryMenu: FC<DocSidebarMobileSecondaryMenuProps> = ({ toggleSidebar, sidebar, path, docsPluginId, }) => ( <> <DocsVersionWrapperMemo docsPluginId={docsPluginId} /> <ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}> <DocSidebarItems items={sidebar} activePath={path} onItemClick={() => toggleSidebar()} /> </ul> </> ); const DocSidebarMobile = (props: Props) => ( <MobileSecondaryMenuFiller component={DocSidebarMobileSecondaryMenu} props={props} /> ); const DocSidebarDesktop = ({ path, sidebar, onCollapse, isHidden, docsPluginId, }: Props & {docsPluginId: string}) => { const showAnnouncementBar = useShowAnnouncementBar(); const { navbar: { hideOnScroll }, hideableSidebar, } = useThemeConfig(); const { isClosed: isAnnouncementBarClosed } = useAnnouncementBar(); return ( <div className={clsx(styles.sidebar, { [styles.sidebarWithHideableNavbar]: hideOnScroll, [styles.sidebarHidden]: isHidden, })} > {hideOnScroll && <Logo tabIndex={-1} className={styles.sidebarLogo} />} <DocsVersionWrapperMemo docsPluginId={docsPluginId} /> <nav className={clsx('menu thin-scrollbar', styles.menu, { [styles.menuWithAnnouncementBar]: !isAnnouncementBarClosed && showAnnouncementBar, })} > <ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}> <DocSidebarItems items={sidebar} activePath={path} /> </ul> </nav> {hideableSidebar && <HideableSidebarButton onClick={onCollapse} />} </div> ); }; const DocSidebarMobileMemo = React.memo(DocSidebarMobile); const DocSidebarDesktopMemo = React.memo(DocSidebarDesktop); const DocSidebar: FC<Props & {docsPluginId: string}> = (props) => { const windowSize = useWindowSize(); // Desktop sidebar visible on hydration: need SSR rendering const shouldRenderSidebarDesktop = windowSize === 'desktop' || windowSize === 'ssr'; // Mobile sidebar not visible on hydration: can avoid SSR rendering const shouldRenderSidebarMobile = windowSize === 'mobile'; return ( <> {shouldRenderSidebarDesktop && <DocSidebarDesktopMemo {...props} />} {shouldRenderSidebarMobile && <DocSidebarMobileMemo {...props} />} </> ); }; export default DocSidebar;