mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-28 17:57:48 +02:00
refactor(theme-classic): split sidebar into smaller parts (#6844)
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
This commit is contained in:
parent
e412d367a0
commit
e97dc0d37e
12 changed files with 366 additions and 228 deletions
|
@ -180,6 +180,42 @@ declare module '@theme/DocSidebar' {
|
|||
export default function DocSidebar(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/DocSidebar/Mobile' {
|
||||
import type {Props as DocSidebarProps} from '@theme/DocSidebar';
|
||||
|
||||
export interface Props extends DocSidebarProps {}
|
||||
|
||||
export default function DocSidebarMobile(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/DocSidebar/Desktop' {
|
||||
import type {Props as DocSidebarProps} from '@theme/DocSidebar';
|
||||
|
||||
export interface Props extends DocSidebarProps {}
|
||||
|
||||
export default function DocSidebarDesktop(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/DocSidebar/Desktop/Content' {
|
||||
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
|
||||
|
||||
export interface Props {
|
||||
readonly className?: string;
|
||||
readonly path: string;
|
||||
readonly sidebar: readonly PropSidebarItem[];
|
||||
}
|
||||
|
||||
export default function CollapseButton(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/DocSidebar/Desktop/CollapseButton' {
|
||||
export interface Props {
|
||||
onClick: React.MouseEventHandler;
|
||||
}
|
||||
|
||||
export default function CollapseButton(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/DocSidebarItem' {
|
||||
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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 from 'react';
|
||||
import clsx from 'clsx';
|
||||
import IconArrow from '@theme/IconArrow';
|
||||
import {translate} from '@docusaurus/Translate';
|
||||
import type {Props} from '@theme/DocSidebar/Desktop/CollapseButton';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export default function CollapseButton({onClick}: Props): JSX.Element {
|
||||
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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--collapse-button-bg-color-dark: #2e333a;
|
||||
}
|
||||
|
||||
@media (min-width: 997px) {
|
||||
.collapseSidebarButton {
|
||||
display: block !important;
|
||||
background-color: var(--ifm-button-background-color);
|
||||
height: 40px;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
border-radius: 0;
|
||||
border: 1px solid var(--ifm-toc-border-color);
|
||||
}
|
||||
|
||||
.collapseSidebarButtonIcon {
|
||||
transform: rotate(180deg);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
[dir='rtl'] .collapseSidebarButtonIcon {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .collapseSidebarButton {
|
||||
background-color: var(--collapse-button-bg-color-dark);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .collapseSidebarButton:hover,
|
||||
[data-theme='dark'] .collapseSidebarButton:focus {
|
||||
background-color: var(--ifm-color-emphasis-200);
|
||||
}
|
||||
}
|
||||
|
||||
.collapseSidebarButton {
|
||||
display: none;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* 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} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
ThemeClassNames,
|
||||
useAnnouncementBar,
|
||||
useScrollPosition,
|
||||
} from '@docusaurus/theme-common';
|
||||
import DocSidebarItems from '@theme/DocSidebarItems';
|
||||
import type {Props} from '@theme/DocSidebar/Desktop/Content';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
function useShowAnnouncementBar() {
|
||||
const {isActive} = useAnnouncementBar();
|
||||
const [showAnnouncementBar, setShowAnnouncementBar] = useState(isActive);
|
||||
|
||||
useScrollPosition(
|
||||
({scrollY}) => {
|
||||
if (isActive) {
|
||||
setShowAnnouncementBar(scrollY === 0);
|
||||
}
|
||||
},
|
||||
[isActive],
|
||||
);
|
||||
return isActive && showAnnouncementBar;
|
||||
}
|
||||
|
||||
export default function DocSidebarDesktopContent({
|
||||
path,
|
||||
sidebar,
|
||||
className,
|
||||
}: Props): JSX.Element {
|
||||
const showAnnouncementBar = useShowAnnouncementBar();
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={clsx(
|
||||
'menu thin-scrollbar',
|
||||
styles.menu,
|
||||
showAnnouncementBar && styles.menuWithAnnouncementBar,
|
||||
className,
|
||||
)}>
|
||||
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}>
|
||||
<DocSidebarItems items={sidebar} activePath={path} level={1} />
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@media (min-width: 997px) {
|
||||
.menu {
|
||||
flex-grow: 1;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.menuWithAnnouncementBar {
|
||||
margin-bottom: var(--docusaurus-announcement-bar-height);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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 from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {useThemeConfig} from '@docusaurus/theme-common';
|
||||
import Logo from '@theme/Logo';
|
||||
import CollapseButton from '@theme/DocSidebar/Desktop/CollapseButton';
|
||||
import Content from '@theme/DocSidebar/Desktop/Content';
|
||||
import type {Props} from '@theme/DocSidebar/Desktop';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
function DocSidebarDesktop({path, sidebar, onCollapse, isHidden}: Props) {
|
||||
const {
|
||||
navbar: {hideOnScroll},
|
||||
hideableSidebar,
|
||||
} = useThemeConfig();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
styles.sidebar,
|
||||
hideOnScroll && styles.sidebarWithHideableNavbar,
|
||||
isHidden && styles.sidebarHidden,
|
||||
)}>
|
||||
{hideOnScroll && <Logo tabIndex={-1} className={styles.sidebarLogo} />}
|
||||
<Content path={path} sidebar={sidebar} />
|
||||
{hideableSidebar && <CollapseButton onClick={onCollapse} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(DocSidebarDesktop);
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@media (min-width: 997px) {
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100vh;
|
||||
height: 100%;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding-top: var(--ifm-navbar-height);
|
||||
width: var(--doc-sidebar-width);
|
||||
transition: opacity 50ms ease;
|
||||
}
|
||||
|
||||
.sidebarWithHideableNavbar {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.sidebarHidden {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.sidebarLogo {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
margin: 0 var(--ifm-navbar-padding-horizontal);
|
||||
min-height: var(--ifm-navbar-height);
|
||||
max-height: var(--ifm-navbar-height);
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.sidebarLogo img {
|
||||
margin-right: 0.5rem;
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebarLogo {
|
||||
display: none;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* 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 from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
MobileSecondaryMenuFiller,
|
||||
type MobileSecondaryMenuComponent,
|
||||
ThemeClassNames,
|
||||
} from '@docusaurus/theme-common';
|
||||
import DocSidebarItems from '@theme/DocSidebarItems';
|
||||
import type {Props} from '@theme/DocSidebar/Mobile';
|
||||
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
const DocSidebarMobileSecondaryMenu: MobileSecondaryMenuComponent<Props> = ({
|
||||
toggleSidebar,
|
||||
sidebar,
|
||||
path,
|
||||
}) => (
|
||||
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}>
|
||||
<DocSidebarItems
|
||||
items={sidebar}
|
||||
activePath={path}
|
||||
onItemClick={(item) => {
|
||||
// Mobile sidebar should only be closed if the category has a link
|
||||
if (item.type === 'category' && item.href) {
|
||||
toggleSidebar();
|
||||
}
|
||||
if (item.type === 'link') {
|
||||
toggleSidebar();
|
||||
}
|
||||
}}
|
||||
level={1}
|
||||
/>
|
||||
</ul>
|
||||
);
|
||||
|
||||
function DocSidebarMobile(props: Props) {
|
||||
return (
|
||||
<MobileSecondaryMenuFiller
|
||||
component={DocSidebarMobileSecondaryMenu}
|
||||
props={props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(DocSidebarMobile);
|
|
@ -5,126 +5,11 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {useState} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
useThemeConfig,
|
||||
useAnnouncementBar,
|
||||
MobileSecondaryMenuFiller,
|
||||
type MobileSecondaryMenuComponent,
|
||||
ThemeClassNames,
|
||||
useScrollPosition,
|
||||
useWindowSize,
|
||||
} from '@docusaurus/theme-common';
|
||||
import Logo from '@theme/Logo';
|
||||
import IconArrow from '@theme/IconArrow';
|
||||
import {translate} from '@docusaurus/Translate';
|
||||
import DocSidebarItems from '@theme/DocSidebarItems';
|
||||
import React from 'react';
|
||||
import {useWindowSize} from '@docusaurus/theme-common';
|
||||
import type {Props} from '@theme/DocSidebar';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
function useShowAnnouncementBar() {
|
||||
const {isActive} = useAnnouncementBar();
|
||||
const [showAnnouncementBar, setShowAnnouncementBar] = useState(isActive);
|
||||
|
||||
useScrollPosition(
|
||||
({scrollY}) => {
|
||||
if (isActive) {
|
||||
setShowAnnouncementBar(scrollY === 0);
|
||||
}
|
||||
},
|
||||
[isActive],
|
||||
);
|
||||
return isActive && showAnnouncementBar;
|
||||
}
|
||||
|
||||
function HideableSidebarButton({onClick}: {onClick: React.MouseEventHandler}) {
|
||||
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 DocSidebarDesktop({path, sidebar, onCollapse, isHidden}: Props) {
|
||||
const showAnnouncementBar = useShowAnnouncementBar();
|
||||
const {
|
||||
navbar: {hideOnScroll},
|
||||
hideableSidebar,
|
||||
} = useThemeConfig();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.sidebar, {
|
||||
[styles.sidebarWithHideableNavbar!]: hideOnScroll,
|
||||
[styles.sidebarHidden!]: isHidden,
|
||||
})}>
|
||||
{hideOnScroll && <Logo tabIndex={-1} className={styles.sidebarLogo} />}
|
||||
<nav
|
||||
className={clsx('menu thin-scrollbar', styles.menu, {
|
||||
[styles.menuWithAnnouncementBar!]: showAnnouncementBar,
|
||||
})}>
|
||||
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}>
|
||||
<DocSidebarItems items={sidebar} activePath={path} level={1} />
|
||||
</ul>
|
||||
</nav>
|
||||
{hideableSidebar && <HideableSidebarButton onClick={onCollapse} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
const DocSidebarMobileSecondaryMenu: MobileSecondaryMenuComponent<Props> = ({
|
||||
toggleSidebar,
|
||||
sidebar,
|
||||
path,
|
||||
}) => (
|
||||
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}>
|
||||
<DocSidebarItems
|
||||
items={sidebar}
|
||||
activePath={path}
|
||||
onItemClick={(item) => {
|
||||
// Mobile sidebar should only be closed if the category has a link
|
||||
if (item.type === 'category' && item.href) {
|
||||
toggleSidebar();
|
||||
}
|
||||
if (item.type === 'link') {
|
||||
toggleSidebar();
|
||||
}
|
||||
}}
|
||||
level={1}
|
||||
/>
|
||||
</ul>
|
||||
);
|
||||
|
||||
function DocSidebarMobile(props: Props) {
|
||||
return (
|
||||
<MobileSecondaryMenuFiller
|
||||
component={DocSidebarMobileSecondaryMenu}
|
||||
props={props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const DocSidebarDesktopMemo = React.memo(DocSidebarDesktop);
|
||||
const DocSidebarMobileMemo = React.memo(DocSidebarMobile);
|
||||
import DocSidebarDesktop from '@theme/DocSidebar/Desktop';
|
||||
import DocSidebarMobile from '@theme/DocSidebar/Mobile';
|
||||
|
||||
export default function DocSidebar(props: Props): JSX.Element {
|
||||
const windowSize = useWindowSize();
|
||||
|
@ -138,8 +23,8 @@ export default function DocSidebar(props: Props): JSX.Element {
|
|||
|
||||
return (
|
||||
<>
|
||||
{shouldRenderSidebarDesktop && <DocSidebarDesktopMemo {...props} />}
|
||||
{shouldRenderSidebarMobile && <DocSidebarMobileMemo {...props} />}
|
||||
{shouldRenderSidebarDesktop && <DocSidebarDesktop {...props} />}
|
||||
{shouldRenderSidebarMobile && <DocSidebarMobile {...props} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--collapse-button-bg-color-dark: #2e333a;
|
||||
}
|
||||
|
||||
@media (min-width: 997px) {
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100vh;
|
||||
height: 100%;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding-top: var(--ifm-navbar-height);
|
||||
width: var(--doc-sidebar-width);
|
||||
transition: opacity 50ms ease;
|
||||
}
|
||||
|
||||
.sidebarWithHideableNavbar {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.sidebarHidden {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.sidebarLogo {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
margin: 0 var(--ifm-navbar-padding-horizontal);
|
||||
min-height: var(--ifm-navbar-height);
|
||||
max-height: var(--ifm-navbar-height);
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.sidebarLogo img {
|
||||
margin-right: 0.5rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.menu {
|
||||
flex-grow: 1;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.menuWithAnnouncementBar {
|
||||
margin-bottom: var(--docusaurus-announcement-bar-height);
|
||||
}
|
||||
|
||||
.collapseSidebarButton {
|
||||
display: block !important;
|
||||
background-color: var(--ifm-button-background-color);
|
||||
height: 40px;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
border-radius: 0;
|
||||
border: 1px solid var(--ifm-toc-border-color);
|
||||
}
|
||||
|
||||
.collapseSidebarButtonIcon {
|
||||
transform: rotate(180deg);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
[dir='rtl'] .collapseSidebarButtonIcon {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .collapseSidebarButton {
|
||||
background-color: var(--collapse-button-bg-color-dark);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .collapseSidebarButton:hover,
|
||||
[data-theme='dark'] .collapseSidebarButton:focus {
|
||||
background-color: var(--ifm-color-emphasis-200);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebarLogo,
|
||||
.collapseSidebarButton {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebarMenuIcon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.sidebarMenuCloseIcon {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
font-size: 1.5rem;
|
||||
font-weight: var(--ifm-font-weight-bold);
|
||||
line-height: 0.9;
|
||||
width: 24px;
|
||||
}
|
|
@ -38,6 +38,7 @@ chedeau
|
|||
cheng
|
||||
clément
|
||||
clsx
|
||||
codeql
|
||||
codespaces
|
||||
codesandbox
|
||||
contravariance
|
||||
|
|
30
website/src/theme/DocSidebar/Desktop/Content/index.js
Normal file
30
website/src/theme/DocSidebar/Desktop/Content/index.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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 from 'react';
|
||||
import Content from '@theme-original/DocSidebar/Desktop/Content';
|
||||
import {useLocation} from '@docusaurus/router';
|
||||
|
||||
function SidebarAd() {
|
||||
return (
|
||||
<div style={{border: 'solid thin red', padding: 10, textAlign: 'center'}}>
|
||||
Sidebar Ad
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ContentWrapper(props) {
|
||||
const {pathname} = useLocation();
|
||||
const shouldShowSidebarAd = pathname.includes('/tests/');
|
||||
return (
|
||||
<>
|
||||
{shouldShowSidebarAd && <SidebarAd />}
|
||||
<Content {...props} />
|
||||
{shouldShowSidebarAd && <SidebarAd />}
|
||||
</>
|
||||
);
|
||||
}
|
Loading…
Add table
Reference in a new issue