diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
index a5653f8edc..e6b4359b7a 100644
--- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts
+++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
@@ -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';
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/index.tsx
new file mode 100644
index 0000000000..c902ac90d6
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/index.tsx
@@ -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 (
+
+ );
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css
new file mode 100644
index 0000000000..4e225b0bdd
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css
@@ -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;
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/index.tsx
new file mode 100644
index 0000000000..f72a9c27a3
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/index.tsx
@@ -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 (
+
+ );
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/styles.module.css
new file mode 100644
index 0000000000..1ee104b73a
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/styles.module.css
@@ -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);
+ }
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/index.tsx
new file mode 100644
index 0000000000..b2b9357d91
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/index.tsx
@@ -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 (
+
+ {hideOnScroll && }
+
+ {hideableSidebar && }
+
+ );
+}
+
+export default React.memo(DocSidebarDesktop);
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/styles.module.css
new file mode 100644
index 0000000000..8d7734af15
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/styles.module.css
@@ -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;
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Mobile/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Mobile/index.tsx
new file mode 100644
index 0000000000..30f23afbf8
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Mobile/index.tsx
@@ -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 = ({
+ toggleSidebar,
+ sidebar,
+ path,
+}) => (
+
+ {
+ // 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}
+ />
+
+);
+
+function DocSidebarMobile(props: Props) {
+ return (
+
+ );
+}
+
+export default React.memo(DocSidebarMobile);
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx
index 5e727e2c46..5bbd9e2eb7 100644
--- a/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx
@@ -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 (
-
- );
-}
-
-function DocSidebarDesktop({path, sidebar, onCollapse, isHidden}: Props) {
- const showAnnouncementBar = useShowAnnouncementBar();
- const {
- navbar: {hideOnScroll},
- hideableSidebar,
- } = useThemeConfig();
-
- return (
-
- {
- // 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}
- />
-