Skip to content

Commit

Permalink
feat(theme-classic): auto-collapse sibling categories in doc sidebar (#…
Browse files Browse the repository at this point in the history
…3811)

Co-authored-by: Josh-Cena <[email protected]>
  • Loading branch information
softwarecurator and Josh-Cena authored Jan 20, 2022
1 parent c9a6c7b commit 8ce3cee
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 16 deletions.
10 changes: 2 additions & 8 deletions packages/docusaurus-theme-classic/src/theme-classic.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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[];
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
useCollapsible,
findFirstCategoryLink,
ThemeClassNames,
useThemeConfig,
useDocSidebarItemsExpandedState,
} from '@docusaurus/theme-common';
import Link from '@docusaurus/Link';
import isInternalUrl from '@docusaurus/isInternalUrl';
Expand Down Expand Up @@ -92,14 +94,15 @@ function DocSidebarItemCategory({
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 {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: () => {
Expand All @@ -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
Expand All @@ -136,10 +161,10 @@ function DocSidebarItemCategory({
? (e) => {
onItemClick?.(item);
if (href) {
setCollapsed(false);
updateCollapsed(false);
} else {
e.preventDefault();
toggleCollapsed();
updateCollapsed();
}
}
: () => {
Expand All @@ -165,7 +190,7 @@ function DocSidebarItemCategory({
className="clean-btn menu__caret"
onClick={(e) => {
e.preventDefault();
toggleCollapsed();
updateCollapsed();
}}
/>
)}
Expand All @@ -189,6 +214,7 @@ function DocSidebarItemLink({
onItemClick,
activePath,
level,
index,
...props
}: Props & {item: PropSidebarItemLink}) {
const {href, label, className} = item;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@
*/

import React, {memo} from 'react';

import DocSidebarItem from '@theme/DocSidebarItem';
import {DocSidebarItemsExpandedStateProvider} from '@docusaurus/theme-common';

import type {Props} from '@theme/DocSidebarItems';

// TODO this item should probably not receive the "activePath" props
// 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>
);
}

Expand Down
4 changes: 4 additions & 0 deletions packages/docusaurus-theme-classic/src/validateThemeConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const DEFAULT_CONFIG = {
items: [],
},
hideableSidebar: false,
autoCollapseSidebarCategories: false,
tableOfContents: {
minHeadingLevel: 2,
maxHeadingLevel: 3,
Expand Down Expand Up @@ -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',
Expand Down
4 changes: 4 additions & 0 deletions packages/docusaurus-theme-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
*/

export {useThemeConfig} from './utils/useThemeConfig';
export {
DocSidebarItemsExpandedStateProvider,
useDocSidebarItemsExpandedState,
} from './utils/docSidebarItemsExpandedState';

export type {
ThemeConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export type ThemeConfig = {
prism: PrismConfig;
footer?: Footer;
hideableSidebar: boolean;
autoCollapseSidebarCategories: boolean;
image?: string;
metadata: Array<Record<string, string>>;
sidebarCollapsible: boolean;
Expand Down
1 change: 1 addition & 0 deletions website/docs/api/docusaurus.config.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ Example:
module.exports = {
themeConfig: {
hideableSidebar: false,
autoCollapseSidebarCategories: false,
colorMode: {
defaultMode: 'light',
disableSwitch: false,
Expand Down
17 changes: 16 additions & 1 deletion website/docs/guides/docs/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand All @@ -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**.
Expand Down
1 change: 1 addition & 0 deletions website/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ const config = {
playgroundPosition: 'bottom',
},
hideableSidebar: true,
autoCollapseSidebarCategories: true,
colorMode: {
defaultMode: 'light',
disableSwitch: false,
Expand Down

0 comments on commit 8ce3cee

Please sign in to comment.