diff --git a/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts b/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts
index 72356f78e7c7..29f89dbb4903 100644
--- a/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts
+++ b/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts
@@ -28,6 +28,14 @@ export default function getSwizzleConfig(): SwizzleConfig {
description:
'The color mode toggle to switch between light and dark mode.',
},
+ DocCardList: {
+ actions: {
+ eject: 'safe',
+ wrap: 'safe',
+ },
+ description:
+ 'The component responsible for rendering a list of sidebar items cards.\nNotable used on the category generated-index pages.',
+ },
DocSidebar: {
actions: {
eject: 'unsafe', // Too much technical code in sidebar, not very safe atm
diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
index 8cf475b8400c..86a25cb4e811 100644
--- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts
+++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
@@ -336,7 +336,7 @@ declare module '@theme/DocCardList' {
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
export interface Props {
- readonly items: PropSidebarItem[];
+ readonly items?: PropSidebarItem[];
readonly className?: string;
}
diff --git a/packages/docusaurus-theme-classic/src/theme/DocCardList/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocCardList/index.tsx
index c4ace5f5ee6c..cde4b22a74da 100644
--- a/packages/docusaurus-theme-classic/src/theme/DocCardList/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/DocCardList/index.tsx
@@ -7,25 +7,27 @@
import React from 'react';
import clsx from 'clsx';
-import {findFirstCategoryLink} from '@docusaurus/theme-common/internal';
+import {
+ useCurrentSidebarCategory,
+ filterDocCardListItems,
+} from '@docusaurus/theme-common';
import DocCard from '@theme/DocCard';
import type {Props} from '@theme/DocCardList';
-import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
-// Filter categories that don't have a link.
-function filterItems(items: PropSidebarItem[]): PropSidebarItem[] {
- return items.filter((item) => {
- if (item.type === 'category') {
- return !!findFirstCategoryLink(item);
- }
- return true;
- });
+function DocCardListForCurrentSidebarCategory({className}: Props) {
+ const category = useCurrentSidebarCategory();
+ return ;
}
-export default function DocCardList({items, className}: Props): JSX.Element {
+export default function DocCardList(props: Props): JSX.Element {
+ const {items, className} = props;
+ if (!items) {
+ return ;
+ }
+ const filteredItems = filterDocCardListItems(items);
return (
- {filterItems(items).map((item, index) => (
+ {filteredItems.map((item, index) => (
diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts
index 1b450b8182b3..7f7427c41409 100644
--- a/packages/docusaurus-theme-common/src/index.ts
+++ b/packages/docusaurus-theme-common/src/index.ts
@@ -28,7 +28,10 @@ export {createStorageSlot, listStorageKeys} from './utils/storageUtils';
export {useContextualSearchFilters} from './utils/searchUtils';
-export {useCurrentSidebarCategory} from './utils/docsUtils';
+export {
+ useCurrentSidebarCategory,
+ filterDocCardListItems,
+} from './utils/docsUtils';
export {usePluralForm} from './utils/usePluralForm';
diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx b/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx
index 8f4c48011299..45724b2b2ec5 100644
--- a/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx
+++ b/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx
@@ -441,26 +441,87 @@ describe('useCurrentSidebarCategory', () => {
),
}).result.current;
- it('works', () => {
- const category: PropSidebarItemCategory = {
- type: 'category',
- label: 'Category',
+
+ it('works for sidebar category', () => {
+ const category: PropSidebarItemCategory = testCategory({
href: '/cat',
- collapsible: true,
- collapsed: false,
- items: [
- {type: 'link', href: '/cat/foo', label: 'Foo'},
- {type: 'link', href: '/cat/bar', label: 'Bar'},
- {type: 'link', href: '/baz', label: 'Baz'},
- ],
- };
- const mockUseCurrentSidebarCategory = createUseCurrentSidebarCategoryMock([
- {type: 'link', href: '/cat/fake', label: 'Fake'},
+ });
+ const sidebar: PropSidebar = [
+ testLink(),
+ testLink(),
category,
- ]);
+ testCategory(),
+ ];
+
+ const mockUseCurrentSidebarCategory =
+ createUseCurrentSidebarCategoryMock(sidebar);
+
expect(mockUseCurrentSidebarCategory('/cat')).toEqual(category);
});
+ it('works for nested sidebar category', () => {
+ const category2: PropSidebarItemCategory = testCategory({
+ href: '/cat2',
+ });
+ const category1: PropSidebarItemCategory = testCategory({
+ href: '/cat1',
+ items: [testLink(), testLink(), category2, testCategory()],
+ });
+ const sidebar: PropSidebar = [
+ testLink(),
+ testLink(),
+ category1,
+ testCategory(),
+ ];
+
+ const mockUseCurrentSidebarCategory =
+ createUseCurrentSidebarCategoryMock(sidebar);
+
+ expect(mockUseCurrentSidebarCategory('/cat2')).toEqual(category2);
+ });
+
+ it('works for category link item', () => {
+ const link = testLink({href: '/my/link/path'});
+ const category: PropSidebarItemCategory = testCategory({
+ href: '/cat1',
+ items: [testLink(), testLink(), link, testCategory()],
+ });
+ const sidebar: PropSidebar = [
+ testLink(),
+ testLink(),
+ category,
+ testCategory(),
+ ];
+
+ const mockUseCurrentSidebarCategory =
+ createUseCurrentSidebarCategoryMock(sidebar);
+
+ expect(mockUseCurrentSidebarCategory('/my/link/path')).toEqual(category);
+ });
+
+ it('works for nested category link item', () => {
+ const link = testLink({href: '/my/link/path'});
+ const category2: PropSidebarItemCategory = testCategory({
+ href: '/cat2',
+ items: [testLink(), testLink(), link, testCategory()],
+ });
+ const category1: PropSidebarItemCategory = testCategory({
+ href: '/cat1',
+ items: [testLink(), testLink(), category2, testCategory()],
+ });
+ const sidebar: PropSidebar = [
+ testLink(),
+ testLink(),
+ category1,
+ testCategory(),
+ ];
+
+ const mockUseCurrentSidebarCategory =
+ createUseCurrentSidebarCategoryMock(sidebar);
+
+ expect(mockUseCurrentSidebarCategory('/my/link/path')).toEqual(category2);
+ });
+
it('throws for non-category index page', () => {
const category: PropSidebarItemCategory = {
type: 'category',
diff --git a/packages/docusaurus-theme-common/src/utils/docsUtils.tsx b/packages/docusaurus-theme-common/src/utils/docsUtils.tsx
index d480172b87c8..3c476d16231d 100644
--- a/packages/docusaurus-theme-common/src/utils/docsUtils.tsx
+++ b/packages/docusaurus-theme-common/src/utils/docsUtils.tsx
@@ -110,15 +110,18 @@ export function useCurrentSidebarCategory(): PropSidebarItemCategory {
if (!sidebar) {
throw new Error('Unexpected: cant find current sidebar in context');
}
- const category = findSidebarCategory(sidebar.items, (item) =>
- isSamePath(item.href, pathname),
- );
- if (!category) {
+ const categoryBreadcrumbs = getSidebarBreadcrumbs({
+ sidebarItems: sidebar.items,
+ pathname,
+ onlyCategories: true,
+ });
+ const deepestCategory = categoryBreadcrumbs.slice(-1)[0];
+ if (!deepestCategory) {
throw new Error(
`${pathname} is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.`,
);
}
- return category;
+ return deepestCategory;
}
const isActive = (testedPath: string | undefined, activePath: string) =>
@@ -149,29 +152,43 @@ export function isActiveSidebarItem(
return false;
}
-/**
- * Gets the breadcrumbs of the current doc page, based on its sidebar location.
- * Returns `null` if there's no sidebar or breadcrumbs are disabled.
- */
-export function useSidebarBreadcrumbs(): PropSidebarBreadcrumbsItem[] | null {
- const sidebar = useDocsSidebar();
- const {pathname} = useLocation();
- const breadcrumbsOption = useActivePlugin()?.pluginData.breadcrumbs;
+function getSidebarBreadcrumbs(param: {
+ sidebarItems: PropSidebar;
+ pathname: string;
+ onlyCategories: true;
+}): PropSidebarItemCategory[];
- if (breadcrumbsOption === false || !sidebar) {
- return null;
- }
+function getSidebarBreadcrumbs(param: {
+ sidebarItems: PropSidebar;
+ pathname: string;
+}): PropSidebarBreadcrumbsItem[];
+/**
+ * Get the sidebar the breadcrumbs for a given pathname
+ * Ordered from top to bottom
+ */
+function getSidebarBreadcrumbs({
+ sidebarItems,
+ pathname,
+ onlyCategories = false,
+}: {
+ sidebarItems: PropSidebar;
+ pathname: string;
+ onlyCategories?: boolean;
+}): PropSidebarBreadcrumbsItem[] {
const breadcrumbs: PropSidebarBreadcrumbsItem[] = [];
- function extract(items: PropSidebar) {
+ function extract(items: PropSidebarItem[]) {
for (const item of items) {
if (
(item.type === 'category' &&
(isSamePath(item.href, pathname) || extract(item.items))) ||
(item.type === 'link' && isSamePath(item.href, pathname))
) {
- breadcrumbs.push(item);
+ const filtered = onlyCategories && item.type !== 'category';
+ if (!filtered) {
+ breadcrumbs.unshift(item);
+ }
return true;
}
}
@@ -179,9 +196,23 @@ export function useSidebarBreadcrumbs(): PropSidebarBreadcrumbsItem[] | null {
return false;
}
- extract(sidebar.items);
+ extract(sidebarItems);
+
+ return breadcrumbs;
+}
- return breadcrumbs.reverse();
+/**
+ * Gets the breadcrumbs of the current doc page, based on its sidebar location.
+ * Returns `null` if there's no sidebar or breadcrumbs are disabled.
+ */
+export function useSidebarBreadcrumbs(): PropSidebarBreadcrumbsItem[] | null {
+ const sidebar = useDocsSidebar();
+ const {pathname} = useLocation();
+ const breadcrumbsOption = useActivePlugin()?.pluginData.breadcrumbs;
+ if (breadcrumbsOption === false || !sidebar) {
+ return null;
+ }
+ return getSidebarBreadcrumbs({sidebarItems: sidebar.items, pathname});
}
/**
@@ -330,3 +361,18 @@ export function useDocRootMetadata({route}: DocRootProps): null | {
sidebarItems,
};
}
+
+/**
+ * Filter categories that don't have a link.
+ * @param items
+ */
+export function filterDocCardListItems(
+ items: PropSidebarItem[],
+): PropSidebarItem[] {
+ return items.filter((item) => {
+ if (item.type === 'category') {
+ return !!findFirstCategoryLink(item);
+ }
+ return true;
+ });
+}
diff --git a/website/docs/advanced/index.md b/website/docs/advanced/index.md
index afe7f1a57050..47fc3f8bc902 100644
--- a/website/docs/advanced/index.md
+++ b/website/docs/advanced/index.md
@@ -4,9 +4,8 @@ This section is not going to be very structured, but we will cover the following
```mdx-code-block
import DocCardList from '@theme/DocCardList';
-import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
-
+
```
We will assume that you have finished the guides, and know the basics like how to configure plugins, how to write React components, etc. These sections will have plugin authors and code contributors in mind, so we may occasionally refer to [plugin APIs](../api/plugin-methods/README.md) or other architecture details. Don't panic if you don't understand everything😉
diff --git a/website/docs/guides/docs/sidebar/index.md b/website/docs/guides/docs/sidebar/index.md
index 06eb5a91b3b9..8e8817b54375 100644
--- a/website/docs/guides/docs/sidebar/index.md
+++ b/website/docs/guides/docs/sidebar/index.md
@@ -35,9 +35,8 @@ This section serves as an overview of miscellaneous features of the doc sidebar.
```mdx-code-block
import DocCardList from '@theme/DocCardList';
-import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
-
+
```
## Default sidebar {#default-sidebar}
diff --git a/website/docs/guides/docs/sidebar/items.md b/website/docs/guides/docs/sidebar/items.md
index fa7af72ca9be..1e5d8ffdf9ab 100644
--- a/website/docs/guides/docs/sidebar/items.md
+++ b/website/docs/guides/docs/sidebar/items.md
@@ -8,6 +8,7 @@ slug: /sidebar/items
```mdx-code-block
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
+import BrowserWindow from '@site/src/components/BrowserWindow';
```
We have introduced three types of item types in the example in the previous section: `doc`, `category`, and `link`, whose usages are fairly intuitive. We will formally introduce their APIs. There's also a fourth type: `autogenerated`, which we will explain in detail later.
@@ -291,18 +292,23 @@ See it in action on the [i18n introduction page](../../../i18n/i18n-introduction
#### Embedding generated index in doc page {#embedding-generated-index-in-doc-page}
-You can embed the generated cards list in a normal doc page as well, as long as the doc is used as a category index page. To do so, you need to use the `DocCardList` component, paired with the `useCurrentSidebarCategory` hook.
+You can embed the generated cards list in a normal doc page as well with the `DocCardList` component. It will display all the sidebar items of the parent category of the current document.
-```jsx title="a-category-index-page.md"
+```md title="docs/sidebar/index.md"
import DocCardList from '@theme/DocCardList';
-import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
-In this section, we will introduce the following concepts:
-
-
+
```
-See this in action on the [sidebar guides page](index.md).
+```mdx-code-block
+
+
+import DocCardList from '@theme/DocCardList';
+
+
+
+
+```
### Collapsible categories {#collapsible-categories}