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 ( +