Skip to content

Commit

Permalink
refactor: split DocSidebarItem by item type (#7005)
Browse files Browse the repository at this point in the history
  • Loading branch information
slorber authored Mar 25, 2022
1 parent 2dea99b commit 2964e6f
Show file tree
Hide file tree
Showing 8 changed files with 354 additions and 263 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
34 changes: 34 additions & 0 deletions packages/docusaurus-theme-classic/src/theme-classic.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,3 @@
var(--ifm-menu-link-padding-horizontal);
}
}

.menuExternalLink {
align-items: center;
}
Original file line number Diff line number Diff line change
@@ -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}}
/>
);
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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>
);
}
Loading

0 comments on commit 2964e6f

Please sign in to comment.