feat: custom navbarItem types (workaround) (#7231)

Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
This commit is contained in:
Sébastien Lorber 2022-04-29 11:23:46 +02:00 committed by GitHub
parent 0ffdfe9c22
commit 6265f6dabb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 194 additions and 44 deletions

View file

@ -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: {

View file

@ -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: {

View file

@ -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';

View file

@ -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;

View file

@ -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)} />;
}

View file

@ -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('.', {

View file

@ -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',

View file

@ -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>
);
}

View 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,
};