mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-11 08:07:26 +02:00
refactor: split DocSidebarItem by item type (#7005)
This commit is contained in:
parent
2dea99b5c8
commit
2964e6f65d
8 changed files with 354 additions and 263 deletions
|
@ -134,6 +134,8 @@ declare module '@docusaurus/plugin-content-docs' {
|
||||||
|
|
||||||
export type PropSidebarItemLink =
|
export type PropSidebarItemLink =
|
||||||
import('./sidebars/types').PropSidebarItemLink;
|
import('./sidebars/types').PropSidebarItemLink;
|
||||||
|
export type PropSidebarItemHtml =
|
||||||
|
import('./sidebars/types').PropSidebarItemHtml;
|
||||||
export type PropSidebarItemCategory =
|
export type PropSidebarItemCategory =
|
||||||
import('./sidebars/types').PropSidebarItemCategory;
|
import('./sidebars/types').PropSidebarItemCategory;
|
||||||
export type PropSidebarItem = import('./sidebars/types').PropSidebarItem;
|
export type PropSidebarItem = import('./sidebars/types').PropSidebarItem;
|
||||||
|
|
|
@ -235,6 +235,40 @@ declare module '@theme/DocSidebarItem' {
|
||||||
export default function DocSidebarItem(props: Props): JSX.Element;
|
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' {
|
declare module '@theme/DocSidebarItems' {
|
||||||
import type {Props as DocSidebarItemProps} from '@theme/DocSidebarItem';
|
import type {Props as DocSidebarItemProps} from '@theme/DocSidebarItem';
|
||||||
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
|
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
|
||||||
|
|
|
@ -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 (
|
||||||
|
<button
|
||||||
|
aria-label={translate(
|
||||||
|
{
|
||||||
|
id: 'theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel',
|
||||||
|
message: "Toggle the collapsible sidebar category '{label}'",
|
||||||
|
description:
|
||||||
|
'The ARIA label to toggle the collapsible sidebar category',
|
||||||
|
},
|
||||||
|
{label: categoryLabel},
|
||||||
|
)}
|
||||||
|
type="button"
|
||||||
|
className="clean-btn menu__caret"
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<li
|
||||||
|
className={clsx(
|
||||||
|
ThemeClassNames.docs.docSidebarItemCategory,
|
||||||
|
ThemeClassNames.docs.docSidebarItemCategoryLevel(level),
|
||||||
|
'menu__list-item',
|
||||||
|
{
|
||||||
|
'menu__list-item--collapsed': collapsed,
|
||||||
|
},
|
||||||
|
className,
|
||||||
|
)}>
|
||||||
|
<div
|
||||||
|
className={clsx('menu__list-item-collapsible', {
|
||||||
|
'menu__list-item-collapsible--active': isCurrentPage,
|
||||||
|
})}>
|
||||||
|
<Link
|
||||||
|
className={clsx('menu__link', {
|
||||||
|
'menu__link--sublist': collapsible,
|
||||||
|
'menu__link--sublist-caret': !href,
|
||||||
|
'menu__link--active': isActive,
|
||||||
|
})}
|
||||||
|
onClick={
|
||||||
|
collapsible
|
||||||
|
? (e) => {
|
||||||
|
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}
|
||||||
|
</Link>
|
||||||
|
{href && collapsible && (
|
||||||
|
<CollapseButton
|
||||||
|
categoryLabel={label}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
updateCollapsed();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>
|
||||||
|
<DocSidebarItems
|
||||||
|
items={items}
|
||||||
|
tabIndex={collapsed ? -1 : 0}
|
||||||
|
onItemClick={onItemClick}
|
||||||
|
activePath={activePath}
|
||||||
|
level={level + 1}
|
||||||
|
/>
|
||||||
|
</Collapsible>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
|
@ -11,7 +11,3 @@
|
||||||
var(--ifm-menu-link-padding-horizontal);
|
var(--ifm-menu-link-padding-horizontal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menuExternalLink {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
|
@ -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 (
|
||||||
|
<li
|
||||||
|
className={clsx(
|
||||||
|
ThemeClassNames.docs.docSidebarItemLink,
|
||||||
|
ThemeClassNames.docs.docSidebarItemLinkLevel(level),
|
||||||
|
defaultStyle && `${styles.menuHtmlItem} menu__list-item`,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
key={index}
|
||||||
|
// eslint-disable-next-line react/no-danger
|
||||||
|
dangerouslySetInnerHTML={{__html: value}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<li
|
||||||
|
className={clsx(
|
||||||
|
ThemeClassNames.docs.docSidebarItemLink,
|
||||||
|
ThemeClassNames.docs.docSidebarItemLinkLevel(level),
|
||||||
|
'menu__list-item',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
key={label}>
|
||||||
|
<Link
|
||||||
|
className={clsx(
|
||||||
|
'menu__link',
|
||||||
|
!isInternalLink && styles.menuExternalLink,
|
||||||
|
{
|
||||||
|
'menu__link--active': isActive,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
aria-current={isActive ? 'page' : undefined}
|
||||||
|
to={href}
|
||||||
|
{...(isInternalLink && {
|
||||||
|
onClick: onItemClick ? () => onItemClick(item) : undefined,
|
||||||
|
})}
|
||||||
|
{...props}>
|
||||||
|
{label}
|
||||||
|
{!isInternalLink && <IconExternalLink />}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,34 +5,11 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useEffect, useMemo} from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import DocSidebarItemCategory from '@theme/DocSidebarItem/Category';
|
||||||
import {
|
import DocSidebarItemLink from '@theme/DocSidebarItem/Link';
|
||||||
isActiveSidebarItem,
|
import DocSidebarItemHtml from '@theme/DocSidebarItem/Html';
|
||||||
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 type {Props} from '@theme/DocSidebarItem';
|
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({
|
export default function DocSidebarItem({
|
||||||
item,
|
item,
|
||||||
|
@ -48,235 +25,3 @@ export default function DocSidebarItem({
|
||||||
return <DocSidebarItemLink item={item} {...props} />;
|
return <DocSidebarItemLink item={item} {...props} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 (
|
|
||||||
<li
|
|
||||||
className={clsx(
|
|
||||||
ThemeClassNames.docs.docSidebarItemCategory,
|
|
||||||
ThemeClassNames.docs.docSidebarItemCategoryLevel(level),
|
|
||||||
'menu__list-item',
|
|
||||||
{
|
|
||||||
'menu__list-item--collapsed': collapsed,
|
|
||||||
},
|
|
||||||
className,
|
|
||||||
)}>
|
|
||||||
<div
|
|
||||||
className={clsx('menu__list-item-collapsible', {
|
|
||||||
'menu__list-item-collapsible--active': isCurrentPage,
|
|
||||||
})}>
|
|
||||||
<Link
|
|
||||||
className={clsx('menu__link', {
|
|
||||||
'menu__link--sublist': collapsible,
|
|
||||||
'menu__link--sublist-caret': !href,
|
|
||||||
'menu__link--active': isActive,
|
|
||||||
})}
|
|
||||||
onClick={
|
|
||||||
collapsible
|
|
||||||
? (e) => {
|
|
||||||
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}
|
|
||||||
</Link>
|
|
||||||
{href && collapsible && (
|
|
||||||
<button
|
|
||||||
aria-label={translate(
|
|
||||||
{
|
|
||||||
id: 'theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel',
|
|
||||||
message: "Toggle the collapsible sidebar category '{label}'",
|
|
||||||
description:
|
|
||||||
'The ARIA label to toggle the collapsible sidebar category',
|
|
||||||
},
|
|
||||||
{label},
|
|
||||||
)}
|
|
||||||
type="button"
|
|
||||||
className="clean-btn menu__caret"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
updateCollapsed();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>
|
|
||||||
<DocSidebarItems
|
|
||||||
items={items}
|
|
||||||
tabIndex={collapsed ? -1 : 0}
|
|
||||||
onItemClick={onItemClick}
|
|
||||||
activePath={activePath}
|
|
||||||
level={level + 1}
|
|
||||||
/>
|
|
||||||
</Collapsible>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function DocSidebarItemHtml({
|
|
||||||
item,
|
|
||||||
level,
|
|
||||||
index,
|
|
||||||
}: Props & {item: SidebarItemHtml}) {
|
|
||||||
const {value, defaultStyle, className} = item;
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
className={clsx(
|
|
||||||
ThemeClassNames.docs.docSidebarItemLink,
|
|
||||||
ThemeClassNames.docs.docSidebarItemLinkLevel(level),
|
|
||||||
defaultStyle && `${styles.menuHtmlItem} menu__list-item`,
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
key={index}
|
|
||||||
// eslint-disable-next-line react/no-danger
|
|
||||||
dangerouslySetInnerHTML={{__html: value}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<li
|
|
||||||
className={clsx(
|
|
||||||
ThemeClassNames.docs.docSidebarItemLink,
|
|
||||||
ThemeClassNames.docs.docSidebarItemLinkLevel(level),
|
|
||||||
'menu__list-item',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
key={label}>
|
|
||||||
<Link
|
|
||||||
className={clsx(
|
|
||||||
'menu__link',
|
|
||||||
!isInternalLink && styles.menuExternalLink,
|
|
||||||
{
|
|
||||||
'menu__link--active': isActive,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
aria-current={isActive ? 'page' : undefined}
|
|
||||||
to={href}
|
|
||||||
{...(isInternalLink && {
|
|
||||||
onClick: onItemClick ? () => onItemClick(item) : undefined,
|
|
||||||
})}
|
|
||||||
{...props}>
|
|
||||||
{label}
|
|
||||||
{!isInternalLink && <IconExternalLink />}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue