Skip to content

Commit

Permalink
feat: add breadcrumbs hook, component to classic theme
Browse files Browse the repository at this point in the history
  • Loading branch information
jodyheavener committed Jan 30, 2022
1 parent 196c8ea commit 14bd0e8
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ declare module '@theme/DocVersionBadge' {
export default function DocVersionBadge(props: Props): JSX.Element;
}

declare module '@theme/DocBreadcrumbs' {
export default function DocBreadcrumbs(): JSX.Element;
}

declare module '@theme/DocPage' {
import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs';
import type {DocumentRoute} from '@theme/DocItem';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -701,4 +701,14 @@ 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,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* 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 {
ThemeClassNames,
useDocsBreadcrumbs,
useThemeConfig,
} from '@docusaurus/theme-common';
import styles from './styles.module.css';
import clsx from 'clsx';
import {useLocation} from '@docusaurus/router';

export default function DocBreadcrumbs(): JSX.Element | null {
const {pathname} = useLocation();
const breadcrumbs = useDocsBreadcrumbs();
const {breadcrumbs: enabled} = useThemeConfig();

function isExact(
items: {
href?: string;
}[],
) {
return (
items.length === 1 &&
items[0].href?.replace(/\/$/, '') === pathname.replace(/\/$/, '')
);
}

if (
!breadcrumbs.length ||
enabled === false ||
(enabled === 'nested' && isExact(breadcrumbs))
) {
return null;
}

return (
<nav
className={clsx(
ThemeClassNames.docs.docBreadcrumbs,
styles.breadcrumbsContainer,
)}
aria-label="breadcrumbs">
<ul className="breadcrumbs">
{breadcrumbs.map((breadcrumb, idx) => (
<li key={idx} className="breadcrumbs__item">
{breadcrumb.href ? (
<a
className={clsx('breadcrumbs__link', styles.breadcrumbItem)}
href={breadcrumb.href}>
{breadcrumb.label}
</a>
) : (
<span
className={clsx('breadcrumbs__link', styles.breadcrumbItem)}>
{breadcrumb.label}
</span>
)}
</li>
))}
</ul>
</nav>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* 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.
*/

.breadcrumbsContainer {
margin-bottom: 1rem;
}

.breadcrumbItem {
--ifm-breadcrumb-size-multiplier: 0.75;
margin-bottom: 0.5rem;
background: var(--ifm-color-gray-100);
}

html[data-theme='dark'] .breadcrumbItem {
background-color: var(--ifm-color-gray-900);
}

@media (min-width: 997px) {
.breadcrumbItem {
--ifm-breadcrumb-size-multiplier: 0.8;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import DocPaginator from '@theme/DocPaginator';
import Seo from '@theme/Seo';
import DocVersionBanner from '@theme/DocVersionBanner';
import DocVersionBadge from '@theme/DocVersionBadge';
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
import Heading from '@theme/Heading';
import useBaseUrl from '@docusaurus/useBaseUrl';

Expand All @@ -33,6 +34,7 @@ export default function DocCategoryGeneratedIndexPage({
/>
<div className={styles.generatedIndexPage}>
<DocVersionBanner />
<DocBreadcrumbs />
<DocVersionBadge />
<header>
<Heading as="h1" className={styles.title}>
Expand Down
2 changes: 2 additions & 0 deletions packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import TOCCollapsible from '@theme/TOCCollapsible';
import Heading from '@theme/Heading';
import styles from './styles.module.css';
import {ThemeClassNames, useWindowSize} from '@docusaurus/theme-common';
import DocBreadcrumbs from '@theme/DocBreadcrumbs';

export default function DocItem(props: Props): JSX.Element {
const {content: DocContent} = props;
Expand Down Expand Up @@ -58,6 +59,7 @@ export default function DocItem(props: Props): JSX.Element {
<DocVersionBanner />
<div className={styles.docItemContainer}>
<article>
<DocBreadcrumbs />
<DocVersionBadge />

{canRenderTOC && (
Expand Down
5 changes: 5 additions & 0 deletions packages/docusaurus-theme-classic/src/validateThemeConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const DEFAULT_CONFIG = {
minHeadingLevel: 2,
maxHeadingLevel: 3,
},
breadcrumbs: 'nested',
};

const NavbarItemPosition = Joi.string().equal('left', 'right').default('left');
Expand Down Expand Up @@ -380,6 +381,10 @@ 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};
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-theme-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export {
findFirstCategoryLink,
useCurrentSidebarCategory,
isActiveSidebarItem,
useDocsBreadcrumbs,
} from './utils/docsUtils';

export {isSamePath} from './utils/pathUtils';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const ThemeClassNames = {
docs: {
docVersionBanner: 'theme-doc-version-banner',
docVersionBadge: 'theme-doc-version-badge',
docBreadcrumbs: 'theme-doc-breadcrumbs',
docMarkdown: 'theme-doc-markdown',
docTocMobile: 'theme-doc-toc-mobile',
docTocDesktop: 'theme-doc-toc-desktop',
Expand Down
34 changes: 34 additions & 0 deletions packages/docusaurus-theme-common/src/utils/docsUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import type {
import {isSamePath} from './pathUtils';
import {useLocation} from '@docusaurus/router';

type BreadcrumbItem = {
label: string;
href?: string;
};

// TODO not ideal, see also "useDocs"
export const isDocsPluginEnabled: boolean = !!useAllDocsData;

Expand Down Expand Up @@ -183,3 +188,32 @@ export function isActiveSidebarItem(

return false;
}

export function useDocsBreadcrumbs(): BreadcrumbItem[] {
const sidebar = useDocsSidebar();
const {pathname} = useLocation();
const breadcrumbs: BreadcrumbItem[] = [];

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({label: item.label, href: item.href});
return true;
} else if (
item.href?.replace(/\/$/, '') === pathname.replace(/\/$/, '')
) {
breadcrumbs.push({label: item.label, href: item.href});
return true;
}
}

return false;
}

if (sidebar) {
extract(sidebar);
}

return breadcrumbs.reverse();
}
3 changes: 3 additions & 0 deletions packages/docusaurus-theme-common/src/utils/useThemeConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ export type TableOfContents = {
maxHeadingLevel: number;
};

export type Breadcrumbs = boolean | 'nested';

// Theme config after validation/normalization
export type ThemeConfig = {
docs: {
Expand All @@ -127,6 +129,7 @@ export type ThemeConfig = {
metadata: Array<Record<string, string>>;
sidebarCollapsible: boolean;
tableOfContents: TableOfContents;
breadcrumbs: Breadcrumbs;
};

// User-provided theme config, unnormalized
Expand Down
25 changes: 25 additions & 0 deletions website/docs/api/themes/theme-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,31 @@ module.exports = {
};
```

### Breadcrumbs {#breadcrumbs}

Breadcrumbs can be rendered on each document page, based on sidebar items.

Accepted fields:

<APITable>

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `breadcrumbs` | <code>boolean \| 'nested'</code> | `nested` | Use boolean value to strictly enable or disable all breadcrumbs, or use `'nested'` when you only want breadcrumbs visible in nested pages. |

</APITable>

Example configuration:

```js title="docusaurus.config.js"
module.exports = {
themeConfig: {
// highlight-next-line
breadcrumbs: 'nested',
},
};
```

## Navbar {#navbar}

Accepted fields:
Expand Down

0 comments on commit 14bd0e8

Please sign in to comment.