facebook / docusaurus

Easy to maintain open source documentation websites.
https://docusaurus.io
MIT License
54.67k stars 8.2k forks source link

Theming: use custom components as navbar/sidebar/footer items #7227

Open jlvandenhout opened 2 years ago

jlvandenhout commented 2 years ago

Edit from @slorber:

Temporary recommended workaround

Until we have first-class support and a convenient API to support this, here's the recommended way to add custom navbar items to your site (see also https://github.com/facebook/docusaurus/pull/7231)

Create a file in src/theme/NavbarItem/ComponentTypes.js to add a custom navbar item type to the existing mapping:

import ComponentTypes from '@theme-original/NavbarItem/ComponentTypes';
import MyAwesomeNavbarItem from '@site/src/components/NavbarItems/MyAwesomeNavbarItem';

export default {
  ...ComponentTypes,
  'custom-myAwesomeNavbarItem': MyAwesomeNavbarItem,
};

Use it in your config:

module.exports = {
  themeConfig: {
    navbar: {
      items: [
        {
          type: 'custom-myAwesomeNavbarItem', 
          position: "left",
          itemProp: 44, 
          anotherProp: "xyz"
        },
        //... other navbar items
      ]
    }
  }
}

Note: using the custom- prefix is important: the config validation schema will only allow item types with this prefix.


Original issue

Have you read the Contributing Guidelines on issues?

Description

We'd like to propose a way to add custom navbar item components through the navbar items configuration, so they remain part of the regular layout of items in the navbar and mobile primary menu. To our understanding three things need to change:

  1. Adjust the navbar items configuration validation to allow objects with a custom type property and a custom component property, similar to how the docs plugin allows a custom docItemComponent property to override the default rendering component. This object should allow arbitrary properties, which are passed as React properties to the custom component.

  2. Adjust the NavbarItem component to allow for custom types, require their configured custom component and passing any additional custom properties to the custom component.

  3. Improve API documentation on how navbar items should distinguish between if they are rendered for mobile or desktop, so the user can properly implement the component in case such a distinction is needed.

Open questions:

  1. Do we regard any unknown type as custom type or do we require a certain pattern?
  2. Do we require the custom component property to be configured or do we not? I guess in case the user swizzles the NavbarItem component and associates the type with a custom component there, requiring the component in the navbar items configuration is not strictly necessary.

Has this been requested on Canny?

No response

Motivation

We've implemented a custom dropdown component, which currently abuses the Docusaurus provided dropdown component configuration to bypass the configuration validation. This worked until beta.17, but from beta.18 onwards our implementation breaks, because of a major refactor in the navbar, which brought us to the point where we would like to propose a properly supported way of adding custom components to the navbar.

API design

Configuration of the custom component in the navbar items configuration:

navbar: {
  items: [
    {
      type: 'custom-type',
      component: '@theme/CustomComponent',
      customProperty: ...
    },
    ...
  ],
  ...
}

Have you tried building it?

We tried swizzling the NavbarItem component, but we ran into multiple issues, including correctly typing the custom component in the type to component map and configuration validation warnings. We'd be happy to create a PR after discussing the implementation proposed above.

Self-service

Josh-Cena commented 2 years ago

I mentioned on Discord we should allow type: /custom-.*/ as a pattern, because we'd still like to catch potential mistakes

slorber commented 2 years ago

Hey

We definitively want to support that, and we've discussed it in a few issues already. This is not the navbar, but we'd also want custom items in other places too: doc sidebar, blog sidebar, footer...

This is an important feature requiring a bit of new infra and that we should design well, for which there are various implications (item schema validation, code-splitting, tree-shaking and lazy loading of unused custom item types, server-side-rendering, the ability for a plugin to register item types...).

As we want to launch 2.0 soon, all this is likely to be implemented later.


I mentioned on Discord we should allow type: /custom-.*/ as a pattern, because we'd still like to catch potential mistakes

Agree: in the short term the easiest workaround would be to allow pass-through of a type that starts with custom-

We should extract the component map to @theme/NavbarItem/Components or something so that it can be easily swizzled and that you can easily add your own components to this map.

And exposing a new hook like useCurrentLayout() === "mobile" can also be convenient to use in many places where we currently forward a mobile prop. (useCurrentLayout() is probably a bad name as even mobile renders both regular navbar (responsive) + mobile navbar (hamburger))

Can this be good enough for now?

jlvandenhout commented 2 years ago

we'd also want custom items in other places too

Even better!

in the short term the easiest workaround would be to allow pass-through of a type that starts with custom- We should extract the component map to @theme/NavbarItem/Components or something so that it can be easily swizzled and that you can easily add your own components to this map.

I'll happily start working on a PR for this, I'm not strong on the schema validation logic, but I'll see how far I get. For the name of the map, what do you think of @theme/NavbarItem/ComponentTypeMap or is that too verbose?

And exposing a new hook like useCurrentLayout() === "mobile" can also be convenient to use in many places where we currently forward a mobile prop. (useCurrentLayout() is probably a bad name as even mobile renders both regular navbar (responsive) + mobile navbar (hamburger))

Looking into the codebase it looks like this is already pretty much covered by the useWindowSize hook. What do you think? Never mind, that hook won't help. We need a way to determine if we are in a mobile layout or a desktop layout, not if we are currently on mobile or desktop. So yes such a hook would be a nice addition, but how would the hook determine the local layout? What about calling it useLayoutContext()?

slorber commented 2 years ago

I'll happily start working on a PR for this

I'd rather do this myself asap there are a few things that will end up becoming part of the API surface and I'd like the whole thing (including naming) to be consistent. Sometimes it's faster for me to do things directly instead of delegating and reviewing.

what do you think of @theme/NavbarItem/ComponentTypeMap

If we adopt this naming convenient we'd rather apply it as well in other places (now and future places). See for example MDXComponents

What about calling it useLayoutContext()?

That's the initial name I thought using but maybe this will be confusing considering "context" name is associated with React and we already have a Layout component πŸ˜… As this API is likely to be adopted by power users (like you) asap, and become official later once we officially have a config api for custom navbar items, we'd rather find a good name

slorber commented 2 years ago

Here's an initial version to support a custom item type: https://github.com/facebook/docusaurus/pull/7231

I didn't add a useLayoutContext() API for now. Considering the item receives a mobile?: boolean prop, I guess you don't really need it and would be just a convenience.

We'll keep this PR open until we have a proper API to register custom components

slorber commented 2 years ago

Note that Ionic doc devs managed to implement custom navbar item types in their site, including validation of custom items: https://github.com/facebook/docusaurus/pull/7231#issuecomment-1112387165

It's a bit hacky but can also be another useful workaround to extend our classic theme

lukedukeus commented 1 year ago

As far as I can tell, this feature has been merged, but I can't find any documention on how to use it. Can anyone share what works for them?

jlvandenhout commented 1 year ago

@lukedukeus Have a look at the top post. Sebastian updated it with a recommended workaround. Hope that helps!

lukedukeus commented 1 year ago

Oh, that makes sense, thanks. I didn't think to try that because I assumed it was just a suggestion of how it should work. This can be closed then?

slorber commented 1 year ago

We'll keep this issue open until we provide a first-class API/documentation to achieve this

Until then, the suggested workaround is good enough

medmin commented 1 year ago

There is a bug right now.

if i use custom-navbar, somehow, in the "position:right" navbar items, if they are links, the build shows all navbar items have the last link item's href. but if you run "yarn start", all is good.

slorber commented 1 year ago

@medmin it's really difficult to understand what you mean. Please add a repro, at least a screenshot of dev vs prod + a config sample.

yanni4night commented 8 months ago

Is this feature still in plan? I want to display an custom avatar component at the most right, but it's not supported yet.

slorber commented 8 months ago

Feature is still planned and one of next features we plan to work on.

The plan is to use a separate client-side file for themeConfig and let you import React components from there.

In the meantime you shouldn't be blocked, because you can swizzle.

timothymcmackin commented 6 months ago

Is there a workaround for a custom doc sidebar component like the custom navbar component in your workaround?

slorber commented 6 months ago

Is there a workaround for a custom doc sidebar component like the custom navbar component in your workaround?

@timothymcmackin all our theme components can be swizzled so technically you can render the sidebar tree the way you want, including hardcoding your own components anywhere in that tree.

If you want to control where your custom components will render in a tree through the sidebars.js file, I can suggest using the sidebar items custom props alongside swizzling DocSidebarItem and wrapping it with extra logic:

import React from 'react';
import DocSidebarItem from '@theme-original/DocSidebarItem';

const CustomComponents = {
  'my-custom-component': (props) => (
    <div style={{ border: 'solid' }}>{JSON.stringify(props)}</div>
  ),
};

export default function DocSidebarItemWrapper(props) {
  const CustomComponent = CustomComponents[props.item?.customProps?.type];
  if (CustomComponent) {
    return <CustomComponent {...props.item?.customProps?.props} />;
  }

  return (
    <>
      <DocSidebarItem {...props} />
    </>
  );
}
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
  tutorialSidebar: [
    'intro',
    {
      type: 'doc',
      id: 'intro',
      customProps: {
        type: 'my-custom-component',
        props: { name: 'Hello World', age: 42 },
      },
    },
    {
      type: 'category',
      label: 'Tutorial',
      items: ['tutorial-basics/create-a-document'],
    },
  ],
};

export default sidebars;

CleanShot 2024-02-01 at 12 24 25

Live demo: https://stackblitz.com/edit/github-rvozvb

Yes, this is awkward to use type: 'doc' for injecting your own custom component, but at least that works and already passes validation. Note in reality you can use any sidebar item type you want, as long as the validation accepts it. In the end you'll remap the rendering of that component to your own custom component so this will be ignored.


Note: we are exploring moving themeConfig to a separate browser-based file (https://github.com/facebook/docusaurus/pull/9619), which would solve this problem properly. But we are facing some challenges to make it happen. Follow that issue to track progress.

octogonz commented 2 months ago

Temporary recommended workaround

Until we have first-class support and a convenient API to support this, here's the recommended way to add custom navbar items to your site (see also #7231)

Create a file in src/theme/NavbarItem/ComponentTypes.js to add a custom navbar item type to the existing mapping:

This was very helpful. πŸ™

It would be great if this was included in the website docs somewhere, since it took some hunting to find this issue. πŸ˜„