diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts
index 3d3898e0cf..ad361efcb4 100644
--- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts
+++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts
@@ -134,6 +134,8 @@ declare module '@docusaurus/plugin-content-docs' {
export type PropSidebarItemLink =
import('./sidebars/types').PropSidebarItemLink;
+ export type PropSidebarItemHtml =
+ import('./sidebars/types').PropSidebarItemHtml;
export type PropSidebarItemCategory =
import('./sidebars/types').PropSidebarItemCategory;
export type PropSidebarItem = import('./sidebars/types').PropSidebarItem;
diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
index 440eb8bbfc..bb3e031376 100644
--- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts
+++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
@@ -235,6 +235,40 @@ declare module '@theme/DocSidebarItem' {
export default function DocSidebarItem(props: Props): JSX.Element;
}
+declare module '@theme/DocSidebarItem/Link' {
+ import type {Props as DocSidebarItemProps} from '@theme/DocSidebarItem';
+
+ import type {PropSidebarItemLink} from '@docusaurus/plugin-content-docs';
+
+ export interface Props extends DocSidebarItemProps {
+ item: PropSidebarItemLink;
+ }
+
+ export default function DocSidebarItemLink(props: Props): JSX.Element;
+}
+
+declare module '@theme/DocSidebarItem/Html' {
+ import type {Props as DocSidebarItemProps} from '@theme/DocSidebarItem';
+ import type {PropSidebarItemHtml} from '@docusaurus/plugin-content-docs';
+
+ export interface Props extends DocSidebarItemProps {
+ item: PropSidebarItemHtml;
+ }
+
+ export default function DocSidebarItemHtml(props: Props): JSX.Element;
+}
+
+declare module '@theme/DocSidebarItem/Category' {
+ import type {Props as DocSidebarItemProps} from '@theme/DocSidebarItem';
+ import type {PropSidebarItemCategory} from '@docusaurus/plugin-content-docs';
+
+ export interface Props extends DocSidebarItemProps {
+ item: PropSidebarItemCategory;
+ }
+
+ export default function DocSidebarItemCategory(props: Props): JSX.Element;
+}
+
declare module '@theme/DocSidebarItems' {
import type {Props as DocSidebarItemProps} from '@theme/DocSidebarItem';
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Category.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Category.tsx
new file mode 100644
index 0000000000..c75cf9d7c9
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Category.tsx
@@ -0,0 +1,212 @@
+/**
+ * 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, {type ComponentProps, useEffect, useMemo} from 'react';
+import clsx from 'clsx';
+import {
+ isActiveSidebarItem,
+ usePrevious,
+ Collapsible,
+ useCollapsible,
+ findFirstCategoryLink,
+ ThemeClassNames,
+ useThemeConfig,
+ useDocSidebarItemsExpandedState,
+ isSamePath,
+} from '@docusaurus/theme-common';
+import Link from '@docusaurus/Link';
+import {translate} from '@docusaurus/Translate';
+
+import DocSidebarItems from '@theme/DocSidebarItems';
+import type {Props} from '@theme/DocSidebarItem/Category';
+
+import useIsBrowser from '@docusaurus/useIsBrowser';
+
+// If we navigate to a category and it becomes active, it should automatically
+// expand itself
+function useAutoExpandActiveCategory({
+ isActive,
+ collapsed,
+ setCollapsed,
+}: {
+ isActive: boolean;
+ collapsed: boolean;
+ setCollapsed: (b: boolean) => void;
+}) {
+ const wasActive = usePrevious(isActive);
+ useEffect(() => {
+ const justBecameActive = isActive && !wasActive;
+ if (justBecameActive && collapsed) {
+ setCollapsed(false);
+ }
+ }, [isActive, wasActive, collapsed, setCollapsed]);
+}
+
+/**
+ * When a collapsible category has no link, we still link it to its first child
+ * during SSR as a temporary fallback. This allows to be able to navigate inside
+ * the category even when JS fails to load, is delayed or simply disabled
+ * React hydration becomes an optional progressive enhancement
+ * see https://github.com/facebookincubator/infima/issues/36#issuecomment-772543188
+ * see https://github.com/facebook/docusaurus/issues/3030
+ */
+function useCategoryHrefWithSSRFallback(
+ item: Props['item'],
+): string | undefined {
+ const isBrowser = useIsBrowser();
+ return useMemo(() => {
+ if (item.href) {
+ return item.href;
+ }
+ // In these cases, it's not necessary to render a fallback
+ // We skip the "findFirstCategoryLink" computation
+ if (isBrowser || !item.collapsible) {
+ return undefined;
+ }
+ return findFirstCategoryLink(item);
+ }, [item, isBrowser]);
+}
+
+function CollapseButton({
+ categoryLabel,
+ onClick,
+}: {
+ categoryLabel: string;
+ onClick: ComponentProps<'button'>['onClick'];
+}) {
+ return (
+
+ );
+}
+
+export default function DocSidebarItemCategory({
+ item,
+ onItemClick,
+ activePath,
+ level,
+ index,
+ ...props
+}: Props): JSX.Element {
+ const {items, label, collapsible, className, href} = item;
+ const hrefWithSSRFallback = useCategoryHrefWithSSRFallback(item);
+
+ const isActive = isActiveSidebarItem(item, activePath);
+ const isCurrentPage = isSamePath(href, activePath);
+
+ const {collapsed, setCollapsed} = useCollapsible({
+ // active categories are always initialized as expanded
+ // the default (item.collapsed) is only used for non-active categories
+ initialState: () => {
+ if (!collapsible) {
+ return false;
+ }
+ return isActive ? false : item.collapsed;
+ },
+ });
+
+ useAutoExpandActiveCategory({isActive, collapsed, setCollapsed});
+ const {expandedItem, setExpandedItem} = useDocSidebarItemsExpandedState();
+ function updateCollapsed(toCollapsed: boolean = !collapsed) {
+ setExpandedItem(toCollapsed ? null : index);
+ setCollapsed(toCollapsed);
+ }
+ const {autoCollapseSidebarCategories} = useThemeConfig();
+ useEffect(() => {
+ if (
+ collapsible &&
+ expandedItem &&
+ expandedItem !== index &&
+ autoCollapseSidebarCategories
+ ) {
+ setCollapsed(true);
+ }
+ }, [
+ collapsible,
+ expandedItem,
+ index,
+ setCollapsed,
+ autoCollapseSidebarCategories,
+ ]);
+
+ return (
+
+
+ {
+ onItemClick?.(item);
+ if (href) {
+ updateCollapsed(false);
+ } else {
+ e.preventDefault();
+ updateCollapsed();
+ }
+ }
+ : () => {
+ onItemClick?.(item);
+ }
+ }
+ aria-current={isCurrentPage ? 'page' : undefined}
+ aria-expanded={collapsible ? !collapsed : undefined}
+ href={collapsible ? hrefWithSSRFallback ?? '#' : hrefWithSSRFallback}
+ {...props}>
+ {label}
+
+ {href && collapsible && (
+ {
+ e.preventDefault();
+ updateCollapsed();
+ }}
+ />
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Html.module.css
similarity index 88%
rename from packages/docusaurus-theme-classic/src/theme/DocSidebarItem/styles.module.css
rename to packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Html.module.css
index e8a2d6c960..2bb6934d33 100644
--- a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/styles.module.css
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Html.module.css
@@ -11,7 +11,3 @@
var(--ifm-menu-link-padding-horizontal);
}
}
-
-.menuExternalLink {
- align-items: center;
-}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Html.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Html.tsx
new file mode 100644
index 0000000000..0e97ec2778
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Html.tsx
@@ -0,0 +1,34 @@
+/**
+ * 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 {ThemeClassNames} from '@docusaurus/theme-common';
+import type {Props} from '@theme/DocSidebarItem/Html';
+
+import styles from './Html.module.css';
+
+export default function DocSidebarItemHtml({
+ item,
+ level,
+ index,
+}: Props): JSX.Element {
+ const {value, defaultStyle, className} = item;
+ return (
+
+ );
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Link.module.css b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Link.module.css
new file mode 100644
index 0000000000..4abcb56762
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Link.module.css
@@ -0,0 +1,10 @@
+/**
+ * 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.
+ */
+
+.menuExternalLink {
+ align-items: center;
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Link.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Link.tsx
new file mode 100644
index 0000000000..28c481eed4
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Link.tsx
@@ -0,0 +1,58 @@
+/**
+ * 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 {isActiveSidebarItem, ThemeClassNames} from '@docusaurus/theme-common';
+import Link from '@docusaurus/Link';
+import isInternalUrl from '@docusaurus/isInternalUrl';
+import IconExternalLink from '@theme/IconExternalLink';
+
+import type {Props} from '@theme/DocSidebarItem/Link';
+
+import styles from './Link.module.css';
+
+export default function DocSidebarItemLink({
+ item,
+ onItemClick,
+ activePath,
+ level,
+ index,
+ ...props
+}: Props): JSX.Element {
+ const {href, label, className} = item;
+ const isActive = isActiveSidebarItem(item, activePath);
+ const isInternalLink = isInternalUrl(href);
+ return (
+
+ onItemClick(item) : undefined,
+ })}
+ {...props}>
+ {label}
+ {!isInternalLink && }
+
+
+ );
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/index.tsx
index eca309416a..34c40c18d8 100644
--- a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/index.tsx
@@ -5,34 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
-import React, {useEffect, useMemo} from 'react';
-import clsx from 'clsx';
-import {
- isActiveSidebarItem,
- usePrevious,
- Collapsible,
- useCollapsible,
- findFirstCategoryLink,
- ThemeClassNames,
- useThemeConfig,
- useDocSidebarItemsExpandedState,
- isSamePath,
-} from '@docusaurus/theme-common';
-import Link from '@docusaurus/Link';
-import isInternalUrl from '@docusaurus/isInternalUrl';
-import {translate} from '@docusaurus/Translate';
-import IconExternalLink from '@theme/IconExternalLink';
-
-import DocSidebarItems from '@theme/DocSidebarItems';
+import React from 'react';
+import DocSidebarItemCategory from '@theme/DocSidebarItem/Category';
+import DocSidebarItemLink from '@theme/DocSidebarItem/Link';
+import DocSidebarItemHtml from '@theme/DocSidebarItem/Html';
import type {Props} from '@theme/DocSidebarItem';
-import type {
- PropSidebarItemCategory,
- PropSidebarItemLink,
-} from '@docusaurus/plugin-content-docs';
-
-import styles from './styles.module.css';
-import useIsBrowser from '@docusaurus/useIsBrowser';
-import type {SidebarItemHtml} from '@docusaurus/plugin-content-docs/src/sidebars/types';
export default function DocSidebarItem({
item,
@@ -48,235 +25,3 @@ export default function DocSidebarItem({
return ;
}
}
-
-// If we navigate to a category and it becomes active, it should automatically
-// expand itself
-function useAutoExpandActiveCategory({
- isActive,
- collapsed,
- setCollapsed,
-}: {
- isActive: boolean;
- collapsed: boolean;
- setCollapsed: (b: boolean) => void;
-}) {
- const wasActive = usePrevious(isActive);
- useEffect(() => {
- const justBecameActive = isActive && !wasActive;
- if (justBecameActive && collapsed) {
- setCollapsed(false);
- }
- }, [isActive, wasActive, collapsed, setCollapsed]);
-}
-
-/**
- * When a collapsible category has no link, we still link it to its first child
- * during SSR as a temporary fallback. This allows to be able to navigate inside
- * the category even when JS fails to load, is delayed or simply disabled
- * React hydration becomes an optional progressive enhancement
- * see https://github.com/facebookincubator/infima/issues/36#issuecomment-772543188
- * see https://github.com/facebook/docusaurus/issues/3030
- */
-function useCategoryHrefWithSSRFallback(
- item: PropSidebarItemCategory,
-): string | undefined {
- const isBrowser = useIsBrowser();
- return useMemo(() => {
- if (item.href) {
- return item.href;
- }
- // In these cases, it's not necessary to render a fallback
- // We skip the "findFirstCategoryLink" computation
- if (isBrowser || !item.collapsible) {
- return undefined;
- }
- return findFirstCategoryLink(item);
- }, [item, isBrowser]);
-}
-
-function DocSidebarItemCategory({
- item,
- onItemClick,
- activePath,
- level,
- index,
- ...props
-}: Props & {item: PropSidebarItemCategory}) {
- const {items, label, collapsible, className, href} = item;
- const hrefWithSSRFallback = useCategoryHrefWithSSRFallback(item);
-
- const isActive = isActiveSidebarItem(item, activePath);
- const isCurrentPage = isSamePath(href, activePath);
-
- const {collapsed, setCollapsed} = useCollapsible({
- // active categories are always initialized as expanded
- // the default (item.collapsed) is only used for non-active categories
- initialState: () => {
- if (!collapsible) {
- return false;
- }
- return isActive ? false : item.collapsed;
- },
- });
-
- useAutoExpandActiveCategory({isActive, collapsed, setCollapsed});
- const {expandedItem, setExpandedItem} = useDocSidebarItemsExpandedState();
- function updateCollapsed(toCollapsed: boolean = !collapsed) {
- setExpandedItem(toCollapsed ? null : index);
- setCollapsed(toCollapsed);
- }
- const {autoCollapseSidebarCategories} = useThemeConfig();
- useEffect(() => {
- if (
- collapsible &&
- expandedItem &&
- expandedItem !== index &&
- autoCollapseSidebarCategories
- ) {
- setCollapsed(true);
- }
- }, [
- collapsible,
- expandedItem,
- index,
- setCollapsed,
- autoCollapseSidebarCategories,
- ]);
-
- return (
-
-
- {
- onItemClick?.(item);
- if (href) {
- updateCollapsed(false);
- } else {
- e.preventDefault();
- updateCollapsed();
- }
- }
- : () => {
- onItemClick?.(item);
- }
- }
- aria-current={isCurrentPage ? 'page' : undefined}
- aria-expanded={collapsible ? !collapsed : undefined}
- href={collapsible ? hrefWithSSRFallback ?? '#' : hrefWithSSRFallback}
- {...props}>
- {label}
-
- {href && collapsible && (
- {
- e.preventDefault();
- updateCollapsed();
- }}
- />
- )}
-
-
-
-
-
-
- );
-}
-
-function DocSidebarItemHtml({
- item,
- level,
- index,
-}: Props & {item: SidebarItemHtml}) {
- const {value, defaultStyle, className} = item;
- return (
-
- );
-}
-
-function DocSidebarItemLink({
- item,
- onItemClick,
- activePath,
- level,
- index,
- ...props
-}: Props & {item: PropSidebarItemLink}) {
- const {href, label, className} = item;
- const isActive = isActiveSidebarItem(item, activePath);
- const isInternalLink = isInternalUrl(href);
- return (
-
- onItemClick(item) : undefined,
- })}
- {...props}>
- {label}
- {!isInternalLink && }
-
-
- );
-}