Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(theme): add versions attribute to docsVersionDropdown navbar item #10852

Merged
merged 27 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
544045f
feat(theme-classic): implemented version configurations for "docsVers…
hrumhurum Jan 18, 2025
36a35e5
docs(theme-classic): added docs for versions configuration of "docsVe…
hrumhurum Jan 18, 2025
5e8032b
feat(theme-classic): preserve order of version configurations in "doc…
hrumhurum Jan 18, 2025
0a02b47
docs(theme-classic): fixed docs formatting issue for versions configu…
hrumhurum Jan 18, 2025
c791a47
docs(theme-classic): improved wording in docs for versions configurat…
hrumhurum Jan 18, 2025
8e21202
docs(theme-classic): more precise docs for versions configuration of …
hrumhurum Jan 18, 2025
fd8f2db
docs(theme-classic): fixed anchor for versions configuration docs of …
hrumhurum Jan 19, 2025
10976ad
docs(theme-classic): simplified example code for versions configurati…
hrumhurum Jan 19, 2025
bcae6cb
feat(theme-classic): use formal version identifiers for version confi…
hrumhurum Jan 19, 2025
9e166c3
feat(theme-classic): implementation refactoring of version configurat…
hrumhurum Jan 19, 2025
a5afe09
feat(theme-classic): version configurations in "docsVersionDropdown" …
hrumhurum Jan 19, 2025
88e306c
feat(theme): add versions attribute to docsVersionDropdown navbar ite…
hrumhurum Jan 20, 2025
9088e6c
docs(theme): added "configuring versioning representation" section to…
hrumhurum Jan 20, 2025
f74b638
docs(theme): add versions attribute to docsVersionDropdown navbar ite…
hrumhurum Jan 20, 2025
1f3ec76
docs(theme): add versions attribute to docsVersionDropdown navbar ite…
hrumhurum Jan 20, 2025
8cbdf68
docs(theme): add versions attribute to docsVersionDropdown navbar ite…
hrumhurum Jan 26, 2025
afd9b83
feat(theme): add versions attribute to docsVersionDropdown navbar ite…
hrumhurum Jan 27, 2025
70f367a
feat(theme): add versions attribute to docsVersionDropdown navbar ite…
hrumhurum Jan 27, 2025
578bcca
feat(theme): add versions attribute to docsVersionDropdown navbar ite…
hrumhurum Jan 28, 2025
76b8062
simplify docsVersionDropdown docs
slorber Jan 30, 2025
98a5b4f
simplify docsVersionDropdown docs
slorber Jan 30, 2025
2654ab9
extract useVersionItems() hook
slorber Jan 30, 2025
ad24578
simplify DocsVersionDropdown impl, fail fast
slorber Jan 30, 2025
38b581d
import fix
slorber Jan 30, 2025
ec22efa
docs version dropdown: versions should have at least one version
slorber Jan 30, 2025
9bda5d7
docs version dropdown should not display a version that is not in the…
slorber Jan 30, 2025
5475e3b
simpler impl for useDisplayedVersionItem
slorber Jan 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions packages/docusaurus-theme-classic/src/__tests__/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
15 changes: 15 additions & 0 deletions packages/docusaurus-theme-classic/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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({
Expand Down
11 changes: 11 additions & 0 deletions packages/docusaurus-theme-classic/src/theme-classic.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,68 @@ 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,
GlobalDoc,
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)!;
}
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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']),
Expand Down
12 changes: 12 additions & 0 deletions website/docs/api/themes/theme-configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
48 changes: 47 additions & 1 deletion website/docs/guides/docs/versioning.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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}
Expand Down
Loading