docusaurus icon indicating copy to clipboard operation
docusaurus copied to clipboard

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

Open jlvandenhout opened this issue 3 years ago • 17 comments
trafficstars

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

  • [x] I'd be willing to contribute this feature to Docusaurus myself.

jlvandenhout avatar Apr 22 '22 09:04 jlvandenhout

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

Josh-Cena avatar Apr 22 '22 09:04 Josh-Cena

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?

slorber avatar Apr 22 '22 10:04 slorber

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()?

jlvandenhout avatar Apr 22 '22 12:04 jlvandenhout

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 avatar Apr 22 '22 12:04 slorber

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 avatar Apr 22 '22 18:04 slorber

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

slorber avatar Apr 28 '22 16:04 slorber

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?

lukedukeus avatar Aug 29 '22 17:08 lukedukeus

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

jlvandenhout avatar Aug 29 '22 18:08 jlvandenhout

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?

lukedukeus avatar Aug 30 '22 02:08 lukedukeus

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

slorber avatar Aug 31 '22 12:08 slorber

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.

medmin avatar Nov 13 '22 19:11 medmin

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

slorber avatar Nov 16 '22 16:11 slorber

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

yanni4night avatar Dec 05 '23 14:12 yanni4night

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.

slorber avatar Dec 05 '23 15:12 slorber

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

timothymcmackin avatar Jan 30 '24 18:01 timothymcmackin

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.

slorber avatar Feb 01 '24 11:02 slorber