diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap index bba2e20e8e5a..88fb0433601c 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap @@ -242,6 +242,7 @@ exports[`simple website content 5`] = ` Object { "pluginName": Object { "pluginId": Object { + "breadcrumbs": "nested", "path": "/docs", "versions": Array [ Object { @@ -955,6 +956,7 @@ exports[`simple website content: global data 1`] = ` Object { "pluginName": Object { "pluginId": Object { + "breadcrumbs": "nested", "path": "/docs", "versions": Array [ Object { @@ -2411,6 +2413,7 @@ exports[`versioned website (community) content: global data 1`] = ` Object { "pluginName": Object { "pluginId": Object { + "breadcrumbs": "nested", "path": "/community", "versions": Array [ Object { @@ -3450,6 +3453,7 @@ exports[`versioned website content: global data 1`] = ` Object { "pluginName": Object { "pluginId": Object { + "breadcrumbs": "nested", "path": "/docs", "versions": Array [ Object { diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts index a4d94d4c36a5..5ef88f9e26ba 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts @@ -56,6 +56,7 @@ describe('normalizeDocsPluginOptions', () => { rehypePlugins: [markdownPluginsFunctionStub], beforeDefaultRehypePlugins: [], beforeDefaultRemarkPlugins: [], + breadcrumbs: 'nested', showLastUpdateTime: true, showLastUpdateAuthor: true, admonitions: {}, diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts index df71049e602b..9aeac134bdd9 100644 --- a/packages/docusaurus-plugin-content-docs/src/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/index.ts @@ -217,6 +217,7 @@ export default async function pluginContentDocs( docLayoutComponent, docItemComponent, docCategoryGeneratedIndexComponent, + breadcrumbs, } = options; const {addRoute, createData, setGlobalData} = actions; @@ -295,6 +296,7 @@ export default async function pluginContentDocs( setGlobalData({ path: normalizeUrl([baseUrl, options.routeBasePath]), versions: loadedVersions.map(toGlobalDataVersion), + breadcrumbs, }); }, diff --git a/packages/docusaurus-plugin-content-docs/src/options.ts b/packages/docusaurus-plugin-content-docs/src/options.ts index 7c79e5ac02d5..520652578c2b 100644 --- a/packages/docusaurus-plugin-content-docs/src/options.ts +++ b/packages/docusaurus-plugin-content-docs/src/options.ts @@ -55,6 +55,7 @@ export const DEFAULT_OPTIONS: Omit = { editLocalizedFiles: false, sidebarCollapsible: true, sidebarCollapsed: true, + breadcrumbs: 'nested', }; const VersionOptionsSchema = Joi.object({ @@ -139,6 +140,10 @@ export const OptionsSchema = Joi.object({ disableVersioning: Joi.bool().default(DEFAULT_OPTIONS.disableVersioning), lastVersion: Joi.string().optional(), versions: VersionsOptionsSchema, + breadcrumbs: Joi.alternatives( + Joi.bool(), + Joi.string().valid('nested'), + ).default(DEFAULT_OPTIONS.breadcrumbs), }); export function validateOptions({ diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts index 29f721f5eea3..e140b05c95d3 100644 --- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts +++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts @@ -38,6 +38,7 @@ declare module '@docusaurus/plugin-content-docs' { showLastUpdateTime?: boolean; showLastUpdateAuthor?: boolean; numberPrefixParser: NumberPrefixParser; + breadcrumbs: boolean | 'nested'; }; export type PathOptions = { @@ -298,6 +299,7 @@ declare module '@docusaurus/plugin-content-docs/client' { export type GlobalPluginData = { path: string; versions: GlobalVersion[]; + breadcrumbs: boolean | 'nested'; }; export type DocVersionSuggestions = { // suggest the latest version diff --git a/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.ts b/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.ts index d8a7de49b923..67a2ec87a285 100644 --- a/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.ts +++ b/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.ts @@ -701,14 +701,4 @@ describe('themeConfig tableOfContents', () => { `"\\"tableOfContents.minHeadingLevel\\" must be less than or equal to ref:maxHeadingLevel"`, ); }); - - test('should accept nested breadcrumb config', () => { - const breadcrumbConfig = { - breadcrumbs: 'nested', - }; - expect(testValidateThemeConfig(breadcrumbConfig)).toEqual({ - ...DEFAULT_CONFIG, - ...breadcrumbConfig, - }); - }); }); diff --git a/packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/index.tsx index c707a0a1bb38..bcd994419fb8 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/index.tsx @@ -6,36 +6,14 @@ */ import React from 'react'; -import { - isSamePath, - ThemeClassNames, - useSidebarBreadcrumbs, - useThemeConfig, -} from '@docusaurus/theme-common'; +import {ThemeClassNames, useSidebarBreadcrumbs} from '@docusaurus/theme-common'; import styles from './styles.module.css'; import clsx from 'clsx'; -import {useLocation} from '@docusaurus/router'; -import type {PropSidebar} from '@docusaurus/plugin-content-docs'; export default function DocBreadcrumbs(): JSX.Element | null { - const {pathname} = useLocation(); const breadcrumbs = useSidebarBreadcrumbs(); - const {breadcrumbs: enabled} = useThemeConfig(); - function isExact(items: PropSidebar) { - const singleItem = items[0]; - return ( - items.length === 1 && - singleItem.type === 'link' && - isSamePath(singleItem.href, pathname) - ); - } - - if ( - !breadcrumbs.length || - enabled === false || - (enabled === 'nested' && isExact(breadcrumbs)) - ) { + if (!breadcrumbs) { return null; } diff --git a/packages/docusaurus-theme-classic/src/validateThemeConfig.ts b/packages/docusaurus-theme-classic/src/validateThemeConfig.ts index 753d8956d820..a3c9a3bbc368 100644 --- a/packages/docusaurus-theme-classic/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-classic/src/validateThemeConfig.ts @@ -46,7 +46,6 @@ const DEFAULT_CONFIG = { minHeadingLevel: 2, maxHeadingLevel: 3, }, - breadcrumbs: 'nested', }; const NavbarItemPosition = Joi.string().equal('left', 'right').default('left'); @@ -385,10 +384,6 @@ const ThemeConfigSchema = Joi.object({ .max(6) .default(DEFAULT_CONFIG.tableOfContents.maxHeadingLevel), }).default(DEFAULT_CONFIG.tableOfContents), - breadcrumbs: Joi.alternatives( - Joi.bool(), - Joi.string().valid('nested'), - ).default(DEFAULT_CONFIG.breadcrumbs), }); export {DEFAULT_CONFIG, ThemeConfigSchema}; diff --git a/packages/docusaurus-theme-common/src/utils/docsUtils.tsx b/packages/docusaurus-theme-common/src/utils/docsUtils.tsx index 64e319a075e9..8932fb29a32e 100644 --- a/packages/docusaurus-theme-common/src/utils/docsUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/docsUtils.tsx @@ -6,7 +6,10 @@ */ import React, {createContext, type ReactNode, useContext} from 'react'; -import {useAllDocsData} from '@docusaurus/plugin-content-docs/client'; +import { + useActivePlugin, + useAllDocsData, +} from '@docusaurus/plugin-content-docs/client'; import type { PropSidebar, PropSidebarItem, @@ -182,13 +185,13 @@ export function isActiveSidebarItem( return false; } -export function useSidebarBreadcrumbs(): PropSidebar { +export function useSidebarBreadcrumbs(): PropSidebar | null { const sidebar = useDocsSidebar(); const {pathname} = useLocation(); const breadcrumbs: PropSidebar = []; + const enabled = useActivePlugin()?.pluginData?.breadcrumbs; function extract(items: PropSidebar) { - // eslint-disable-next-line no-restricted-syntax for (const item of items) { if (item.type === 'category' && extract(item.items)) { breadcrumbs.push(item); @@ -202,9 +205,27 @@ export function useSidebarBreadcrumbs(): PropSidebar { return false; } + // Check if the only breadcrumb item + // is a link to the current page + function onlyPageLink() { + return ( + breadcrumbs.length === 1 && + breadcrumbs[0].type === 'link' && + isSamePath(breadcrumbs[0].href, pathname) + ); + } + if (sidebar) { extract(sidebar); } + if ( + !breadcrumbs.length || + enabled === false || + (enabled === 'nested' && onlyPageLink()) + ) { + return null; + } + return breadcrumbs.reverse(); } diff --git a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts index 4a336f203263..26975d2563ce 100644 --- a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts +++ b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts @@ -105,8 +105,6 @@ export type TableOfContents = { maxHeadingLevel: number; }; -export type Breadcrumbs = boolean | 'nested'; - // Theme config after validation/normalization export type ThemeConfig = { docs: { @@ -129,7 +127,6 @@ export type ThemeConfig = { metadata: Array>; sidebarCollapsible: boolean; tableOfContents: TableOfContents; - breadcrumbs: Breadcrumbs; }; // User-provided theme config, unnormalized