mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-06 10:20:09 +02:00
feat(theme): add versions
attribute to docsVersionDropdown
navbar item (#10852)
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
This commit is contained in:
parent
8bc3e8a092
commit
4d7a28963a
6 changed files with 276 additions and 10 deletions
|
@ -827,6 +827,110 @@ describe('themeConfig', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('docsVersionDropdown', () => {
|
||||
describe('versions', () => {
|
||||
it('accepts array of strings', () => {
|
||||
const config = {
|
||||
navbar: {
|
||||
items: [
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
versions: ['current', '1.0'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
testValidateThemeConfig(config);
|
||||
});
|
||||
|
||||
it('rejects empty array of strings', () => {
|
||||
const config = {
|
||||
navbar: {
|
||||
items: [
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
versions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(() =>
|
||||
testValidateThemeConfig(config),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`""navbar.items[0].versions" must contain at least 1 items"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects array of non-strings', () => {
|
||||
const config = {
|
||||
navbar: {
|
||||
items: [
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
versions: [1, 2],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(() =>
|
||||
testValidateThemeConfig(config),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`""navbar.items[0].versions[0]" must be a string"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts dictionary of version objects', () => {
|
||||
const config = {
|
||||
navbar: {
|
||||
items: [
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
versions: {current: {}, '1.0': {label: '1.x'}},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
testValidateThemeConfig(config);
|
||||
});
|
||||
|
||||
it('rejects empty dictionary of objects', () => {
|
||||
const config = {
|
||||
navbar: {
|
||||
items: [
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
versions: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(() =>
|
||||
testValidateThemeConfig(config),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`""navbar.items[0].versions" must have at least 1 key"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects dictionary of invalid objects', () => {
|
||||
const config = {
|
||||
navbar: {
|
||||
items: [
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
versions: {current: {}, '1.0': {invalid: '1.x'}},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(() =>
|
||||
testValidateThemeConfig(config),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`""navbar.items[0].versions.1.0.invalid" is not allowed"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateOptions', () => {
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
import {themes} from 'prism-react-renderer';
|
||||
import {Joi, URISchema} from '@docusaurus/utils-validation';
|
||||
import type {
|
||||
PropVersionItem,
|
||||
PropVersionItems,
|
||||
} from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
|
||||
import type {Options, PluginOptions} from '@docusaurus/theme-classic';
|
||||
import type {ThemeConfig} from '@docusaurus/theme-common';
|
||||
import type {
|
||||
|
@ -210,6 +214,17 @@ const DocsVersionDropdownNavbarItemSchema = NavbarItemBaseSchema.append({
|
|||
dropdownActiveClassDisabled: Joi.boolean(),
|
||||
dropdownItemsBefore: Joi.array().items(DropdownSubitemSchema).default([]),
|
||||
dropdownItemsAfter: Joi.array().items(DropdownSubitemSchema).default([]),
|
||||
versions: Joi.alternatives().try(
|
||||
Joi.array().items(Joi.string().min(1)).min(1),
|
||||
Joi.object<PropVersionItems>()
|
||||
.pattern(
|
||||
Joi.string().min(1),
|
||||
Joi.object<PropVersionItem>({
|
||||
label: Joi.string().min(1),
|
||||
}),
|
||||
)
|
||||
.min(1),
|
||||
),
|
||||
});
|
||||
|
||||
const LocaleDropdownNavbarItemSchema = NavbarItemBaseSchema.append({
|
||||
|
|
|
@ -1257,11 +1257,22 @@ declare module '@theme/NavbarItem/DocsVersionDropdownNavbarItem' {
|
|||
import type {Props as DropdownNavbarItemProps} from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem';
|
||||
|
||||
type PropVersionItem = {
|
||||
readonly label?: string;
|
||||
};
|
||||
|
||||
type PropVersionItems = {
|
||||
readonly [version: string]: PropVersionItem;
|
||||
};
|
||||
|
||||
type PropVersions = string[] | PropVersionItems;
|
||||
|
||||
export interface Props extends DropdownNavbarItemProps {
|
||||
readonly docsPluginId?: string;
|
||||
readonly dropdownActiveClassDisabled?: boolean;
|
||||
readonly dropdownItemsBefore: LinkLikeNavbarItemProps[];
|
||||
readonly dropdownItemsAfter: LinkLikeNavbarItemProps[];
|
||||
readonly versions?: PropVersions;
|
||||
}
|
||||
|
||||
export default function DocsVersionDropdownNavbarItem(
|
||||
|
|
|
@ -16,7 +16,11 @@ import {translate} from '@docusaurus/Translate';
|
|||
import {useLocation} from '@docusaurus/router';
|
||||
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import type {Props} from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
|
||||
import type {
|
||||
Props,
|
||||
PropVersions,
|
||||
PropVersionItem,
|
||||
} from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
|
||||
import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem';
|
||||
import type {
|
||||
GlobalVersion,
|
||||
|
@ -24,6 +28,56 @@ import type {
|
|||
ActiveDocContext,
|
||||
} from '@docusaurus/plugin-content-docs/client';
|
||||
|
||||
type VersionItem = {
|
||||
version: GlobalVersion;
|
||||
label: string;
|
||||
};
|
||||
|
||||
function getVersionItems(
|
||||
versions: GlobalVersion[],
|
||||
configs?: PropVersions,
|
||||
): VersionItem[] {
|
||||
if (configs) {
|
||||
// Collect all the versions we have
|
||||
const versionMap = new Map<string, GlobalVersion>(
|
||||
versions.map((version) => [version.name, version]),
|
||||
);
|
||||
|
||||
const toVersionItem = (
|
||||
name: string,
|
||||
config?: PropVersionItem,
|
||||
): VersionItem => {
|
||||
const version = versionMap.get(name);
|
||||
if (!version) {
|
||||
throw new Error(`No docs version exist for name '${name}', please verify your 'docsVersionDropdown' navbar item versions config.
|
||||
Available version names:\n- ${versions.map((v) => `${v.name}`).join('\n- ')}`);
|
||||
}
|
||||
return {version, label: config?.label ?? version.label};
|
||||
};
|
||||
|
||||
if (Array.isArray(configs)) {
|
||||
return configs.map((name) => toVersionItem(name, undefined));
|
||||
} else {
|
||||
return Object.entries(configs).map(([name, config]) =>
|
||||
toVersionItem(name, config),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return versions.map((version) => ({version, label: version.label}));
|
||||
}
|
||||
}
|
||||
|
||||
function useVersionItems({
|
||||
docsPluginId,
|
||||
configs,
|
||||
}: {
|
||||
docsPluginId: Props['docsPluginId'];
|
||||
configs: Props['versions'];
|
||||
}): VersionItem[] {
|
||||
const versions = useVersions(docsPluginId);
|
||||
return getVersionItems(versions, configs);
|
||||
}
|
||||
|
||||
function getVersionMainDoc(version: GlobalVersion): GlobalDoc {
|
||||
return version.docs.find((doc) => doc.id === version.mainDocId)!;
|
||||
}
|
||||
|
@ -40,23 +94,47 @@ function getVersionTargetDoc(
|
|||
);
|
||||
}
|
||||
|
||||
// The version item to use for the "dropdown button"
|
||||
function useDisplayedVersionItem({
|
||||
docsPluginId,
|
||||
versionItems,
|
||||
}: {
|
||||
docsPluginId: Props['docsPluginId'];
|
||||
versionItems: VersionItem[];
|
||||
}): VersionItem {
|
||||
// The order of the candidates matters!
|
||||
const candidates = useDocsVersionCandidates(docsPluginId);
|
||||
const candidateItems = candidates
|
||||
.map((candidate) => versionItems.find((vi) => vi.version === candidate))
|
||||
.filter((vi) => vi !== undefined);
|
||||
return candidateItems[0] ?? versionItems[0]!;
|
||||
}
|
||||
|
||||
export default function DocsVersionDropdownNavbarItem({
|
||||
mobile,
|
||||
docsPluginId,
|
||||
dropdownActiveClassDisabled,
|
||||
dropdownItemsBefore,
|
||||
dropdownItemsAfter,
|
||||
versions: configs,
|
||||
...props
|
||||
}: Props): ReactNode {
|
||||
const {search, hash} = useLocation();
|
||||
const activeDocContext = useActiveDocContext(docsPluginId);
|
||||
const versions = useVersions(docsPluginId);
|
||||
const {savePreferredVersionName} = useDocsPreferredVersion(docsPluginId);
|
||||
const versionItems = useVersionItems({docsPluginId, configs});
|
||||
const displayedVersionItem = useDisplayedVersionItem({
|
||||
docsPluginId,
|
||||
versionItems,
|
||||
});
|
||||
|
||||
function versionToLink(version: GlobalVersion): LinkLikeNavbarItemProps {
|
||||
function versionItemToLink({
|
||||
version,
|
||||
label,
|
||||
}: VersionItem): LinkLikeNavbarItemProps {
|
||||
const targetDoc = getVersionTargetDoc(version, activeDocContext);
|
||||
return {
|
||||
label: version.label,
|
||||
label,
|
||||
// preserve ?search#hash suffix on version switches
|
||||
to: `${targetDoc.path}${search}${hash}`,
|
||||
isActive: () => version === activeDocContext.activeVersion,
|
||||
|
@ -66,12 +144,10 @@ export default function DocsVersionDropdownNavbarItem({
|
|||
|
||||
const items: LinkLikeNavbarItemProps[] = [
|
||||
...dropdownItemsBefore,
|
||||
...versions.map(versionToLink),
|
||||
...versionItems.map(versionItemToLink),
|
||||
...dropdownItemsAfter,
|
||||
];
|
||||
|
||||
const dropdownVersion = useDocsVersionCandidates(docsPluginId)[0];
|
||||
|
||||
// Mobile dropdown is handled a bit differently
|
||||
const dropdownLabel =
|
||||
mobile && items.length > 1
|
||||
|
@ -81,11 +157,13 @@ export default function DocsVersionDropdownNavbarItem({
|
|||
description:
|
||||
'The label for the navbar versions dropdown on mobile view',
|
||||
})
|
||||
: dropdownVersion.label;
|
||||
: displayedVersionItem.label;
|
||||
|
||||
const dropdownTo =
|
||||
mobile && items.length > 1
|
||||
? undefined
|
||||
: getVersionTargetDoc(dropdownVersion, activeDocContext).path;
|
||||
: getVersionTargetDoc(displayedVersionItem.version, activeDocContext)
|
||||
.path;
|
||||
|
||||
// We don't want to render a version dropdown with 0 or 1 item. If we build
|
||||
// the site with a single docs version (onlyIncludeVersions: ['1.0.0']),
|
||||
|
|
|
@ -597,11 +597,23 @@ Accepted fields:
|
|||
| `dropdownItemsAfter` | <code>[LinkLikeItem](#navbar-dropdown)[]</code> | `[]` | Add additional dropdown items at the end of the dropdown. |
|
||||
| `docsPluginId` | `string` | `'default'` | The ID of the docs plugin that the doc versioning belongs to. |
|
||||
| `dropdownActiveClassDisabled` | `boolean` | `false` | Do not add the link active class when browsing docs. |
|
||||
| `versions` | `DropdownVersions` | `undefined` | Specify a custom list of versions to include in the dropdown. See [the versioning guide](../../guides/docs/versioning.mdx#docsVersionDropdown) for details. |
|
||||
|
||||
```mdx-code-block
|
||||
</APITable>
|
||||
```
|
||||
|
||||
Types:
|
||||
|
||||
```ts
|
||||
type DropdownVersion = {
|
||||
/** Allows you to provide a custom display label for each version. */
|
||||
label?: string;
|
||||
};
|
||||
|
||||
type DropdownVersions = string[] | {[versionName: string]: DropdownVersion};
|
||||
```
|
||||
|
||||
Example configuration:
|
||||
|
||||
```js title="docusaurus.config.js"
|
||||
|
|
|
@ -258,7 +258,7 @@ See [docs plugin configuration](../../api/plugins/plugin-content-docs.mdx#config
|
|||
|
||||
## Navbar items {#navbar-items}
|
||||
|
||||
We offer several navbar items to help you quickly set up navigation without worrying about versioned routes.
|
||||
We offer several docs navbar items to help you quickly set up navigation without worrying about versioned routes.
|
||||
|
||||
- [`doc`](../../api/themes/theme-configuration.mdx#navbar-doc-link): a link to a doc.
|
||||
- [`docSidebar`](../../api/themes/theme-configuration.mdx#navbar-doc-sidebar): a link to the first item in a sidebar.
|
||||
|
@ -271,6 +271,52 @@ These links would all look for an appropriate version to link to, in the followi
|
|||
2. **Preferred version**: the version that the user last viewed. If there's no history, fall back to...
|
||||
3. **Latest version**: the default version that we navigate to, configured by the `lastVersion` option.
|
||||
|
||||
## `docsVersionDropdown` {#docsVersionDropdown}
|
||||
|
||||
By default, the [`docsVersionDropdown`](../../api/themes/theme-configuration.mdx#navbar-docs-version-dropdown) displays a dropdown with all the available docs versions.
|
||||
|
||||
The `versions` attribute allows you to display a subset of the available docs versions in a given order:
|
||||
|
||||
```js title="docusaurus.config.js"
|
||||
export default {
|
||||
themeConfig: {
|
||||
navbar: {
|
||||
items: [
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
// highlight-start
|
||||
versions: ['current', '3.0', '2.0'],
|
||||
// highlight-end
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Passing a `versions` object, lets you override the display label of each version:
|
||||
|
||||
```js title="docusaurus.config.js"
|
||||
export default {
|
||||
themeConfig: {
|
||||
navbar: {
|
||||
items: [
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
// highlight-start
|
||||
versions: {
|
||||
current: {label: 'Version 4.0'},
|
||||
'3.0': {label: 'Version 3.0'},
|
||||
'2.0': {label: 'Version 2.0'},
|
||||
},
|
||||
// highlight-end
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Recommended practices {#recommended-practices}
|
||||
|
||||
### Version your documentation only when needed {#version-your-documentation-only-when-needed}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue