mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-28 05:58:38 +02:00
feat: custom navbarItem types (workaround) (#7231)
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
This commit is contained in:
parent
0ffdfe9c22
commit
6265f6dabb
9 changed files with 194 additions and 44 deletions
|
@ -227,6 +227,44 @@ describe('themeConfig', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('accept "custom-" prefixed custom navbar item type', () => {
|
||||
const config = {
|
||||
navbar: {
|
||||
items: [
|
||||
{
|
||||
type: 'custom-x',
|
||||
position: 'left',
|
||||
xyz: 42,
|
||||
},
|
||||
{
|
||||
label: 'Dropdown with custom item',
|
||||
position: 'right',
|
||||
items: [
|
||||
{
|
||||
label: 'Facebook',
|
||||
href: 'https://.facebook.com/',
|
||||
target: '_self',
|
||||
},
|
||||
{
|
||||
type: 'custom-y',
|
||||
any: new Date(),
|
||||
prop: 42,
|
||||
isAccepted: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(testValidateThemeConfig(config)).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
navbar: {
|
||||
...DEFAULT_CONFIG.navbar,
|
||||
...config.navbar,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects unknown navbar item type', () => {
|
||||
const config = {
|
||||
navbar: {
|
||||
|
|
|
@ -206,6 +206,14 @@ export default function getSwizzleConfig(): SwizzleConfig {
|
|||
description:
|
||||
'A component wrapping all MDX content and providing the MDXComponents to the MDX context',
|
||||
},
|
||||
'NavbarItem/ComponentTypes': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'forbidden',
|
||||
},
|
||||
description:
|
||||
'The Navbar item components mapping. Can be ejected to add custom navbar item types. See https://github.com/facebook/docusaurus/issues/7227.',
|
||||
},
|
||||
// TODO should probably not even appear here
|
||||
'NavbarItem/utils': {
|
||||
actions: {
|
||||
|
|
|
@ -893,6 +893,37 @@ declare module '@theme/NavbarItem/HtmlNavbarItem' {
|
|||
export default function HtmlNavbarItem(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/NavbarItem/ComponentTypes' {
|
||||
import type {ComponentType} from 'react';
|
||||
|
||||
import type DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import type DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import type LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
|
||||
import type SearchNavbarItem from '@theme/NavbarItem/SearchNavbarItem';
|
||||
import type HtmlNavbarItem from '@theme/NavbarItem/HtmlNavbarItem';
|
||||
import type DocNavbarItem from '@theme/NavbarItem/DocNavbarItem';
|
||||
import type DocSidebarNavbarItem from '@theme/NavbarItem/DocSidebarNavbarItem';
|
||||
import type DocsVersionNavbarItem from '@theme/NavbarItem/DocsVersionNavbarItem';
|
||||
import type DocsVersionDropdownNavbarItem from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
|
||||
|
||||
export type ComponentTypesObject = {
|
||||
readonly default: typeof DefaultNavbarItem;
|
||||
readonly localeDropdown: typeof LocaleDropdownNavbarItem;
|
||||
readonly search: typeof SearchNavbarItem;
|
||||
readonly dropdown: typeof DropdownNavbarItem;
|
||||
readonly html: typeof HtmlNavbarItem;
|
||||
readonly doc: typeof DocNavbarItem;
|
||||
readonly docSidebar: typeof DocSidebarNavbarItem;
|
||||
readonly docsVersion: typeof DocsVersionNavbarItem;
|
||||
readonly docsVersionDropdown: typeof DocsVersionDropdownNavbarItem;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[customComponentType: string]: ComponentType<any>;
|
||||
};
|
||||
|
||||
const ComponentTypes: ComponentTypesObject;
|
||||
export default ComponentTypes;
|
||||
}
|
||||
|
||||
declare module '@theme/NavbarItem' {
|
||||
import type {ComponentProps} from 'react';
|
||||
import type {Props as DefaultNavbarItemProps} from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* 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 DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
|
||||
import SearchNavbarItem from '@theme/NavbarItem/SearchNavbarItem';
|
||||
import HtmlNavbarItem from '@theme/NavbarItem/HtmlNavbarItem';
|
||||
import DocNavbarItem from '@theme/NavbarItem/DocNavbarItem';
|
||||
import DocSidebarNavbarItem from '@theme/NavbarItem/DocSidebarNavbarItem';
|
||||
import DocsVersionNavbarItem from '@theme/NavbarItem/DocsVersionNavbarItem';
|
||||
import DocsVersionDropdownNavbarItem from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
|
||||
|
||||
import type {ComponentTypesObject} from '@theme/NavbarItem/ComponentTypes';
|
||||
|
||||
const ComponentTypes: ComponentTypesObject = {
|
||||
default: DefaultNavbarItem,
|
||||
localeDropdown: LocaleDropdownNavbarItem,
|
||||
search: SearchNavbarItem,
|
||||
dropdown: DropdownNavbarItem,
|
||||
html: HtmlNavbarItem,
|
||||
doc: DocNavbarItem,
|
||||
docSidebar: DocSidebarNavbarItem,
|
||||
docsVersion: DocsVersionNavbarItem,
|
||||
docsVersionDropdown: DocsVersionDropdownNavbarItem,
|
||||
};
|
||||
|
||||
export default ComponentTypes;
|
|
@ -6,57 +6,26 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
|
||||
import DropdownNavbarItem, {
|
||||
type Props as DropdownNavbarItemProps,
|
||||
} from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
|
||||
import SearchNavbarItem from '@theme/NavbarItem/SearchNavbarItem';
|
||||
import HtmlNavbarItem from '@theme/NavbarItem/HtmlNavbarItem';
|
||||
import {type Props as DropdownNavbarItemProps} from '@theme/NavbarItem/DropdownNavbarItem';
|
||||
import type {Types, Props} from '@theme/NavbarItem';
|
||||
|
||||
const NavbarItemComponents: {
|
||||
// Not really worth typing, as we pass all props down immediately
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[type in Exclude<Types, undefined>]: () => (props: any) => JSX.Element;
|
||||
} = {
|
||||
default: () => DefaultNavbarItem,
|
||||
localeDropdown: () => LocaleDropdownNavbarItem,
|
||||
search: () => SearchNavbarItem,
|
||||
dropdown: () => DropdownNavbarItem,
|
||||
html: () => HtmlNavbarItem,
|
||||
import ComponentTypes from '@theme/NavbarItem/ComponentTypes';
|
||||
|
||||
// Need to lazy load these items as we don't know for sure the docs plugin is
|
||||
// loaded. See https://github.com/facebook/docusaurus/issues/3360
|
||||
/* eslint-disable @typescript-eslint/no-var-requires, global-require */
|
||||
docsVersion: () => require('@theme/NavbarItem/DocsVersionNavbarItem').default,
|
||||
docsVersionDropdown: () =>
|
||||
require('@theme/NavbarItem/DocsVersionDropdownNavbarItem').default,
|
||||
doc: () => require('@theme/NavbarItem/DocNavbarItem').default,
|
||||
docSidebar: () => require('@theme/NavbarItem/DocSidebarNavbarItem').default,
|
||||
/* eslint-enable @typescript-eslint/no-var-requires, global-require */
|
||||
} as const;
|
||||
|
||||
type NavbarItemComponentType = keyof typeof NavbarItemComponents;
|
||||
|
||||
const getNavbarItemComponent = (type: NavbarItemComponentType) => {
|
||||
const navbarItemComponentFn = NavbarItemComponents[type];
|
||||
if (!navbarItemComponentFn) {
|
||||
const getNavbarItemComponent = (type: NonNullable<Types>) => {
|
||||
const component = ComponentTypes[type];
|
||||
if (!component) {
|
||||
throw new Error(`No NavbarItem component found for type "${type}".`);
|
||||
}
|
||||
return navbarItemComponentFn();
|
||||
return component;
|
||||
};
|
||||
|
||||
function getComponentType(
|
||||
type: Types,
|
||||
isDropdown: boolean,
|
||||
): NavbarItemComponentType {
|
||||
function getComponentType(type: Types, isDropdown: boolean) {
|
||||
// Backward compatibility: navbar item with no type set
|
||||
// but containing dropdown items should use the type "dropdown"
|
||||
if (!type || type === 'default') {
|
||||
return isDropdown ? 'dropdown' : 'default';
|
||||
}
|
||||
return type as NavbarItemComponentType;
|
||||
return type as NonNullable<Types>;
|
||||
}
|
||||
|
||||
export default function NavbarItem({type, ...props}: Props): JSX.Element {
|
||||
|
@ -65,5 +34,5 @@ export default function NavbarItem({type, ...props}: Props): JSX.Element {
|
|||
(props as DropdownNavbarItemProps).items !== undefined,
|
||||
);
|
||||
const NavbarItemComponent = getNavbarItemComponent(componentType);
|
||||
return <NavbarItemComponent {...props} />;
|
||||
return <NavbarItemComponent {...(props as never)} />;
|
||||
}
|
||||
|
|
|
@ -99,9 +99,20 @@ const HtmlNavbarItemSchema = Joi.object({
|
|||
value: Joi.string().required(),
|
||||
});
|
||||
|
||||
const itemWithType = (type: string | undefined) => {
|
||||
// A temporary workaround to allow users to add custom navbar items
|
||||
// See https://github.com/facebook/docusaurus/issues/7227
|
||||
const CustomNavbarItemRegexp = /custom-.*/;
|
||||
const CustomNavbarItemSchema = Joi.object({
|
||||
type: Joi.string().regex(CustomNavbarItemRegexp).required(),
|
||||
}).unknown();
|
||||
|
||||
const itemWithType = (type: string | RegExp | undefined) => {
|
||||
// Because equal(undefined) is not supported :/
|
||||
const typeSchema = type
|
||||
const typeSchema =
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
type instanceof RegExp
|
||||
? Joi.string().required().regex(type)
|
||||
: type
|
||||
? Joi.string().required().equal(type)
|
||||
: Joi.string().forbidden();
|
||||
return Joi.object({
|
||||
|
@ -135,6 +146,10 @@ const DropdownSubitemSchema = Joi.object({
|
|||
is: itemWithType('html'),
|
||||
then: HtmlNavbarItemSchema,
|
||||
},
|
||||
{
|
||||
is: itemWithType(CustomNavbarItemRegexp),
|
||||
then: CustomNavbarItemSchema,
|
||||
},
|
||||
{
|
||||
is: Joi.alternatives().try(
|
||||
itemWithType('dropdown'),
|
||||
|
@ -210,6 +225,10 @@ const NavbarItemSchema = Joi.object({
|
|||
is: itemWithType('html'),
|
||||
then: HtmlNavbarItemSchema,
|
||||
},
|
||||
{
|
||||
is: itemWithType(CustomNavbarItemRegexp),
|
||||
then: CustomNavbarItemSchema,
|
||||
},
|
||||
{
|
||||
is: itemWithType(undefined),
|
||||
then: Joi.object().when('.', {
|
||||
|
|
|
@ -409,12 +409,18 @@ const config = {
|
|||
position: 'left',
|
||||
activeBaseRegex: `/community/`,
|
||||
},
|
||||
// This item links to a draft doc: only displayed in dev
|
||||
{
|
||||
type: 'doc',
|
||||
docId: 'test-draft',
|
||||
label: 'Tests',
|
||||
docsPluginId: 'docs-tests',
|
||||
},
|
||||
// Custom item for dogfooding: only displayed in /tests/ routes
|
||||
{
|
||||
type: 'custom-dogfood-navbar-item',
|
||||
content: '😉',
|
||||
},
|
||||
// Right
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* 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 {useLocation} from '@docusaurus/router';
|
||||
|
||||
// used to dogfood custom navbar elements are possible
|
||||
// see https://github.com/facebook/docusaurus/issues/7227
|
||||
export default function CustomDogfoodNavbarItem(props: {
|
||||
content: string;
|
||||
mobile?: boolean;
|
||||
}): JSX.Element | null {
|
||||
const {pathname} = useLocation();
|
||||
const shouldRender = pathname.includes('/tests/');
|
||||
if (!shouldRender) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<button
|
||||
onClick={() => {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert("I'm a custom navbar item type example");
|
||||
}}
|
||||
type="button">
|
||||
{props.content}
|
||||
{props.mobile ? ' (mobile)' : ''}
|
||||
</button>
|
||||
);
|
||||
}
|
14
website/src/theme/NavbarItem/ComponentTypes.tsx
Normal file
14
website/src/theme/NavbarItem/ComponentTypes.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* 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 ComponentTypes from '@theme-original/NavbarItem/ComponentTypes';
|
||||
import CustomDogfoodNavbarItem from '@site/src/components/NavbarItems/CustomDogfoodNavbarItem';
|
||||
|
||||
export default {
|
||||
...ComponentTypes,
|
||||
'custom-dogfood-navbar-item': CustomDogfoodNavbarItem,
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue