Skip to content

Commit

Permalink
simplify and optimize a bit the sidebar code
Browse files Browse the repository at this point in the history
  • Loading branch information
slorber committed Apr 15, 2021
1 parent de81c0c commit 67b87e0
Showing 1 changed file with 144 additions and 108 deletions.
252 changes: 144 additions & 108 deletions packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import React, {useState, useCallback, useEffect, useRef, useMemo} from 'react';
import React, {useState, useCallback, useEffect, useRef, memo} from 'react';
import clsx from 'clsx';
import {useThemeConfig, isSamePath} from '@docusaurus/theme-common';
import useUserPreferencesContext from '@theme/hooks/useUserPreferencesContext';
Expand Down Expand Up @@ -44,6 +44,32 @@ const isActiveSidebarItem = (item, activePath) => {
return false;
};

// Optimize sidebar at each "level"
// TODO this item should probably not receive the "activePath" props
// TODO this triggers whole sidebar re-renders on navigation
const DocSidebarItems = memo(function DocSidebarItems({
items,
...props
}: any): JSX.Element {
return items.map((item, index) => (
<DocSidebarItem
key={index} // sidebar is static, the index does not change
item={item}
{...props}
/>
));
});

function DocSidebarItem(props): JSX.Element {
switch (props.item.type) {
case 'category':
return <DocSidebarItemCategory {...props} />;
case 'link':
default:
return <DocSidebarItemLink {...props} />;
}
}

function DocSidebarItemCategory({
item,
onItemClick,
Expand Down Expand Up @@ -168,47 +194,24 @@ function DocSidebarItemLink({
);
}

function DocSidebarItem(props): JSX.Element {
switch (props.item.type) {
case 'category':
return <DocSidebarItemCategory {...props} />;
case 'link':
default:
return <DocSidebarItemLink {...props} />;
}
}

function DocSidebarItems({items, ...props}): JSX.Element {
return items.map((item, index) => (
<DocSidebarItem
key={index} // sidebar is static, the index does not change
item={item}
{...props}
/>
));
}

function DocSidebar({
path,
sidebar,
sidebarCollapsible = true,
onCollapse,
isHidden,
}: Props): JSX.Element | null {
const [showResponsiveSidebar, setShowResponsiveSidebar] = useState(false);
const [showAnnouncementBar, setShowAnnouncementBar] = useState(true);
const {
navbar: {hideOnScroll},
hideableSidebar,
} = useThemeConfig();
function useShowAnnouncementBar() {
const {isAnnouncementBarClosed} = useUserPreferencesContext();
const [showAnnouncementBar, setShowAnnouncementBar] = useState(
!isAnnouncementBarClosed,
);
useScrollPosition(({scrollY}) => {
setShowAnnouncementBar(scrollY === 0);
if (!isAnnouncementBarClosed) {
setShowAnnouncementBar(scrollY === 0);
}
});
return showAnnouncementBar;
}

function useResponsiveSidebar() {
const [showResponsiveSidebar, setShowResponsiveSidebar] = useState(false);
useLockBodyScroll(showResponsiveSidebar);
const windowSize = useWindowSize();

const windowSize = useWindowSize();
useEffect(() => {
if (windowSize === windowSizes.desktop) {
setShowResponsiveSidebar(false);
Expand All @@ -222,17 +225,99 @@ function DocSidebar({
},
[setShowResponsiveSidebar],
);
const sidebarItems = useMemo(
() => (
<DocSidebarItems
items={sidebar}
onItemClick={closeResponsiveSidebar}
collapsible={sidebarCollapsible}
activePath={path}
/>
),
[sidebar, sidebarCollapsible, path, closeResponsiveSidebar],

const toggleResponsiveSidebar = useCallback(() => {
setShowResponsiveSidebar(!showResponsiveSidebar);
}, [setShowResponsiveSidebar]);

return {
showResponsiveSidebar,
closeResponsiveSidebar,
toggleResponsiveSidebar,
};
}

function HideableSidebarButton({onClick}) {
return (
<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>
);
}

function ResponsiveSidebarButton({responsiveSidebarOpened, onClick}) {
return (
<button
aria-label={
responsiveSidebarOpened
? translate({
id: 'theme.docs.sidebar.responsiveCloseButtonLabel',
message: 'Close menu',
description:
'The ARIA label for close button of mobile doc sidebar',
})
: translate({
id: 'theme.docs.sidebar.responsiveOpenButtonLabel',
message: 'Open menu',
description:
'The ARIA label for open button of mobile doc sidebar',
})
}
aria-haspopup="true"
className="button button--secondary button--sm menu__button"
type="button"
onClick={onClick}>
{responsiveSidebarOpened ? (
<span
className={clsx(styles.sidebarMenuIcon, styles.sidebarMenuCloseIcon)}>
&times;
</span>
) : (
<IconMenu
className={styles.sidebarMenuIcon}
height={MOBILE_TOGGLE_SIZE}
width={MOBILE_TOGGLE_SIZE}
/>
)}
</button>
);
}

function DocSidebar({
path,
sidebar,
sidebarCollapsible = true,
onCollapse,
isHidden,
}: Props): JSX.Element | null {
const showAnnouncementBar = useShowAnnouncementBar();
const {
navbar: {hideOnScroll},
hideableSidebar,
} = useThemeConfig();
const {isAnnouncementBarClosed} = useUserPreferencesContext();

const {
showResponsiveSidebar,
closeResponsiveSidebar,
toggleResponsiveSidebar,
} = useResponsiveSidebar();

return (
<div
Expand All @@ -253,69 +338,20 @@ function DocSidebar({
!isAnnouncementBarClosed && showAnnouncementBar,
},
)}>
<button
aria-label={
showResponsiveSidebar
? translate({
id: 'theme.docs.sidebar.responsiveCloseButtonLabel',
message: 'Close menu',
description:
'The ARIA label for close button of mobile doc sidebar',
})
: translate({
id: 'theme.docs.sidebar.responsiveOpenButtonLabel',
message: 'Open menu',
description:
'The ARIA label for open button of mobile doc sidebar',
})
}
aria-haspopup="true"
className="button button--secondary button--sm menu__button"
type="button"
onClick={() => {
setShowResponsiveSidebar(!showResponsiveSidebar);
}}>
{showResponsiveSidebar ? (
<span
className={clsx(
styles.sidebarMenuIcon,
styles.sidebarMenuCloseIcon,
)}>
&times;
</span>
) : (
<IconMenu
className={styles.sidebarMenuIcon}
height={MOBILE_TOGGLE_SIZE}
width={MOBILE_TOGGLE_SIZE}
/>
)}
</button>
<ul className="menu__list">{sidebarItems}</ul>
<ResponsiveSidebarButton
responsiveSidebarOpened={showResponsiveSidebar}
onClick={toggleResponsiveSidebar}
/>
<ul className="menu__list">
<DocSidebarItems
items={sidebar}
onItemClick={closeResponsiveSidebar}
collapsible={sidebarCollapsible}
activePath={path}
/>
</ul>
</div>
{hideableSidebar && (
<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={onCollapse}>
<IconArrow className={styles.collapseSidebarButtonIcon} />
</button>
)}
{hideableSidebar && <HideableSidebarButton onClick={onCollapse} />}
</div>
);
}
Expand Down

0 comments on commit 67b87e0

Please sign in to comment.