feat(theme-classic): auto-collapse sibling categories in doc sidebar (#3811)

Co-authored-by: Josh-Cena <sidachen2003@gmail.com>
This commit is contained in:
Joseph 2022-01-20 07:38:16 -08:00 committed by GitHub
parent c9a6c7b6fb
commit 8ce3cee400
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 104 additions and 16 deletions

View file

@ -151,19 +151,13 @@ declare module '@theme/DocSidebar' {
declare module '@theme/DocSidebarItem' {
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
export type DocSidebarPropsBase = {
readonly activePath: string;
readonly onItemClick?: (item: PropSidebarItem) => void;
readonly level: number;
readonly tabIndex?: number;
};
export interface Props {
readonly activePath: string;
readonly onItemClick?: (item: PropSidebarItem) => void;
readonly level: number;
readonly tabIndex?: number;
readonly item: PropSidebarItem;
readonly index: number;
}
export default function DocSidebarItem(props: Props): JSX.Element;
@ -173,7 +167,7 @@ declare module '@theme/DocSidebarItems' {
import type {Props as DocSidebarItemProps} from '@theme/DocSidebarItem';
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
export type Props = Omit<DocSidebarItemProps, 'item'> & {
export type Props = Omit<DocSidebarItemProps, 'item' | 'index'> & {
readonly items: readonly PropSidebarItem[];
};

View file

@ -14,6 +14,8 @@ import {
useCollapsible,
findFirstCategoryLink,
ThemeClassNames,
useThemeConfig,
useDocSidebarItemsExpandedState,
} from '@docusaurus/theme-common';
import Link from '@docusaurus/Link';
import isInternalUrl from '@docusaurus/isInternalUrl';
@ -92,6 +94,7 @@ function DocSidebarItemCategory({
onItemClick,
activePath,
level,
index,
...props
}: Props & {item: PropSidebarItemCategory}) {
const {items, label, collapsible, className, href} = item;
@ -99,7 +102,7 @@ function DocSidebarItemCategory({
const isActive = isActiveSidebarItem(item, activePath);
const {collapsed, setCollapsed, toggleCollapsed} = useCollapsible({
const {collapsed, setCollapsed} = useCollapsible({
// active categories are always initialized as expanded
// the default (item.collapsed) is only used for non-active categories
initialState: () => {
@ -111,6 +114,28 @@ function DocSidebarItemCategory({
});
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
@ -136,10 +161,10 @@ function DocSidebarItemCategory({
? (e) => {
onItemClick?.(item);
if (href) {
setCollapsed(false);
updateCollapsed(false);
} else {
e.preventDefault();
toggleCollapsed();
updateCollapsed();
}
}
: () => {
@ -165,7 +190,7 @@ function DocSidebarItemCategory({
className="clean-btn menu__caret"
onClick={(e) => {
e.preventDefault();
toggleCollapsed();
updateCollapsed();
}}
/>
)}
@ -189,6 +214,7 @@ function DocSidebarItemLink({
onItemClick,
activePath,
level,
index,
...props
}: Props & {item: PropSidebarItemLink}) {
const {href, label, className} = item;

View file

@ -6,8 +6,8 @@
*/
import React, {memo} from 'react';
import DocSidebarItem from '@theme/DocSidebarItem';
import {DocSidebarItemsExpandedStateProvider} from '@docusaurus/theme-common';
import type {Props} from '@theme/DocSidebarItems';
@ -15,15 +15,16 @@ import type {Props} from '@theme/DocSidebarItems';
// TODO this triggers whole sidebar re-renders on navigation
function DocSidebarItems({items, ...props}: Props): JSX.Element {
return (
<>
<DocSidebarItemsExpandedStateProvider>
{items.map((item, index) => (
<DocSidebarItem
key={index} // sidebar is static, the index does not change
item={item}
index={index}
{...props}
/>
))}
</>
</DocSidebarItemsExpandedStateProvider>
);
}

View file

@ -41,6 +41,7 @@ const DEFAULT_CONFIG = {
items: [],
},
hideableSidebar: false,
autoCollapseSidebarCategories: false,
tableOfContents: {
minHeadingLevel: 2,
maxHeadingLevel: 3,
@ -352,6 +353,9 @@ const ThemeConfigSchema = Joi.object({
.default(DEFAULT_CONFIG.prism)
.unknown(),
hideableSidebar: Joi.bool().default(DEFAULT_CONFIG.hideableSidebar),
autoCollapseSidebarCategories: Joi.bool().default(
DEFAULT_CONFIG.autoCollapseSidebarCategories,
),
sidebarCollapsible: Joi.forbidden().messages({
'any.unknown':
'The themeConfig.sidebarCollapsible has been moved to docs plugin options. See: https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-docs',

View file

@ -6,6 +6,10 @@
*/
export {useThemeConfig} from './utils/useThemeConfig';
export {
DocSidebarItemsExpandedStateProvider,
useDocSidebarItemsExpandedState,
} from './utils/docSidebarItemsExpandedState';
export type {
ThemeConfig,

View file

@ -0,0 +1,41 @@
/**
* 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 ReactNode, useMemo, useState, useContext} from 'react';
const EmptyContext: unique symbol = Symbol('EmptyContext');
const Context = React.createContext<
DocSidebarItemsExpandedState | typeof EmptyContext
>(EmptyContext);
type DocSidebarItemsExpandedState = {
expandedItem: number | null;
setExpandedItem: (a: number | null) => void;
};
export function DocSidebarItemsExpandedStateProvider({
children,
}: {
children: ReactNode;
}): JSX.Element {
const [expandedItem, setExpandedItem] = useState<number | null>(null);
const contextValue = useMemo(
() => ({expandedItem, setExpandedItem}),
[expandedItem],
);
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}
export function useDocSidebarItemsExpandedState(): DocSidebarItemsExpandedState {
const contextValue = useContext(Context);
if (contextValue === EmptyContext) {
throw new Error(
'This hook requires usage of <DocSidebarItemsExpandedStateProvider>',
);
}
return contextValue;
}

View file

@ -122,6 +122,7 @@ export type ThemeConfig = {
prism: PrismConfig;
footer?: Footer;
hideableSidebar: boolean;
autoCollapseSidebarCategories: boolean;
image?: string;
metadata: Array<Record<string, string>>;
sidebarCollapsible: boolean;

View file

@ -279,6 +279,7 @@ Example:
module.exports = {
themeConfig: {
hideableSidebar: false,
autoCollapseSidebarCategories: false,
colorMode: {
defaultMode: 'light',
disableSwitch: false,

View file

@ -913,7 +913,9 @@ module.exports = {
:::
## Hideable sidebar {#hideable-sidebar}
## Theme configuration
### Hideable sidebar {#hideable-sidebar}
By enabling the `themeConfig.hideableSidebar` option, you can make the entire sidebar hideable, allowing users to better focus on the content. This is especially useful when content is consumed on medium-sized screens (e.g. tablets).
@ -927,6 +929,19 @@ module.exports = {
};
```
### Auto-collapse sidebar categories
The `themeConfig.autoCollapseSidebarCategories` option would collapse all sibling categories when expanding one category. This saves the user from having too many categories open and helps them focus on the selected section.
```js title="docusaurus.config.js"
module.exports = {
themeConfig: {
// highlight-next-line
autoCollapseSidebarCategories: true,
},
};
```
## Using multiple sidebars {#using-multiple-sidebars}
You can create a sidebar for each **set of Markdown files** that you want to **group together**.

View file

@ -316,6 +316,7 @@ const config = {
playgroundPosition: 'bottom',
},
hideableSidebar: true,
autoCollapseSidebarCategories: true,
colorMode: {
defaultMode: 'light',
disableSwitch: false,