-
-
Notifications
You must be signed in to change notification settings - Fork 8.5k
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
Dynamic navbar: navbar item activation strategies #4389
Comments
Hi, I'm not totally sure to understand your usecase, a better description would be appreciated, and also if you can put a deploy preview of your doc site with 3 docs instance online on Netlify/Vercel/whatever, that would help to figure out the UX you try to achieve. This looks kind-of related to #3930 My idea was we could build a generic system where each navbar item has an "activation strategy". This means the navbar items could be different on a per-route basis, for both mobile and desktop. This "strategy config" must be:
Here's a badly designed example: const navbarItems = [
{
to: "/guides/",
label: "Guides",
when: { routeMatch: "/guides/.*" }
},
{
to: "/api/ios",
label: "iOS API",
when: { plugin: { name: "@docusaurus/plugin-content-docs", id: "ios" } }
},
{
to: "/api/android",
label: "Android API",
when: {
type: "and",
items: [
{ plugin: { name: "@docusaurus/plugin-content-docs", id: "ios" } },
{ routeMatch: "/guides/.*" }
]
}
}
]; We could work on defining a few useful activation conditions and the API surface, and also enable composition with AND/OR logic. Is this what you are looking for? |
This would address my issue perfectly, I would love it if this were implemented |
@slorber Hi, thank you for a great tool ;) do you have this feature on 2.0.0 roadmap ? |
@dswiecki this has been requested quite often so we'll build it, however this is not the simplest one to design, and we have more important features to work on in the short term, cf the beta blog post https://docusaurus.io/blog/2021/05/12/announcing-docusaurus-two-beta#whats-next |
Hey, multiple people have asked me whether it's possible to only display certain navbar items when user is logged in (by sending HTTP requests). I like the activeWhen: {strategy: string, params: any} And then have a custom |
We are a static site generator and not a hybrid framework like Next.js. Having navbar items that require user authentication means that there will be navbar layout shifts happening after React hydration, and I'm not sure it's a good pattern to encourage: the strategies should rather work on the server to avoid layout shifts. But if we allow custom user-provided strategies we can't really prevent the user to do that anyway. This post section explains the problem with gifs: https://www.joshwcomeau.com/react/the-perils-of-rehydration/#schrodingers-user:
My plan was more to have a map of strategies in We should probably add something similar to allow providing custom Navbar Items. . I can support you if you want to work on this |
I can't think of many use cases besides Agree that there will be layout shifts, but that doesn't sound like what we can help anyways (I've seen this behavior on a lot of sites using REST API for login). We can allow the user to set a default render state to minimize the impact, maybe?
Makes sense. Strategies that we offer should conform to this, but we probably can't limit the users too much either In any case, I might set my hands on this if no-one will be working on this soon I imagine the API surface to be like: export default function useNavbarItemStatus({type, params}): 'active' | 'disabled' | 'hidden' {
return NavbarStrategies[type](params);
} And then called in export default function NavbarItem({type, activeWhen, ...props}) {
const status = useNavbarItemStatus(activeWhen);
switch (status) {
case 'hidden': return null;
case 'disabled': return <NavbarItemComponent disabled {...props} />;
case 'active': return <NavbarItemComponent {...props} />;
}
} So that you can do something like: const navbarItems = [
{
to: "/guides/",
label: "Guides",
activeWhen: { type: 'routeMatch', params: "/guides/.*" }
},
{
to: "/concepts/",
label: "Concepts",
activeWhen: { type: 'routeMatch', params: "/concepts/.*" }
},
{
to: "/secret/",
label: "Secrets",
activeWhen: { type: 'loggedIn' }
},
]; We can even have the syntactic sugar type Strategy = { type: string, params: any} | {[type: string]: any} to make the API close to what you have |
This doesn't look like an explainable / intuitive API to me... Is this an attempt to improve SSR? |
Just updated this to showcase that it can cover the need of #5756, it is not at all a final/definitive API |
React-Native is using a conditional version dropdown, that is only displayed on docs pages. The implementation can likely be greatly simplified, just using Until we have proper support for this, I suggest the following workaround: import React from "react";
import {useActiveDocContext} from '@docusaurus/plugin-content-docs/client';
import DocsVersionDropdownNavbarItem from '@theme-original/NavbarItem/DocsVersionDropdownNavbarItem';
export default function DocsVersionDropdownNavbarItemWrapper(props) {
// do not display this navbar item if current page is not a doc
const activeDocContext = useActiveDocContext(props.docsPluginId);
if (!activeDocContext) {
return null;
}
return <DocsVersionDropdownNavbarItem {...props} />;
} |
I don't know if it helps, but I added some CSS in .navbar.navbar--fixed-top .navbar__items.navbar__items--right > .navbar__item:nth-child(3),
.navbar.navbar--fixed-top .navbar__items.navbar__items--right > .navbar__item:nth-child(4) {
display: none;
}
html.docs-wrapper.plugin-id-default .navbar.navbar--fixed-top .navbar__items.navbar__items--right > .navbar__item:nth-child(3) {
display: block;
}
html.docs-wrapper.plugin-id-docsFormio .navbar.navbar--fixed-top .navbar__items.navbar__items--right > .navbar__item:nth-child(4) {
display: block;
} If only we could add some custom CSS into the dropdown elements, the problem would be solved peacefully. As a suggestion, It can be done by implementing a parameter called type: 'docsVersionDropdown',
position: 'right',
docsPluginId: 'default',
customCss: 'menu-2'
},
{
type: 'docsVersionDropdown',
position: 'right',
docsPluginId: 'docsFormio',
customCss: 'menu-3'
}, This way, we can simply do anything to the dropdown elements by using our custom CSS classes. |
@al1re2a we support passing a Maybe it's not documented well enough? However I think it's applied to the link instead of the parent dropdown container so we should probably apply this class to the parent instead, or give a way to pass a custom class to both elements independently. Note with the new I'm thinking of this: html.docs-wrapper.plugin-id-default .navbar__item:has(> a.my-custom-class) {
display: block;
} |
@slorber Thanks for the tips. It worked for me perfectly.
Unfortunately yes. Although the |
I tried this out but nothing changes really. What's the expected result of this exactly? I'm not sure if this could work but I was hoping I could do something like: get the current route -> show the navbar item if the route is part of the plugin Example: docusaurus.config.js [
'@docusaurus/plugin-content-docs',
{
id: 'charge-controller',
path: 'docs/ChargeController',
routeBasePath: 'charge-controller',
sidebarPath: require.resolve('./docs/ChargeController/sidebars.js'),
},
], navItems: {
type: 'docsVersionDropdown',
docsPluginId: 'charge-controller',
position: 'right',
className: "charge-controller-version-dropdown"
}, current URI = "http://localhost:3000/charge-controller" otherwise, return null. Is this possible? |
Nevermind my previous comment. I got something working with rather simple code. import React from "react";
import DocsVersionDropdownNavbarItem from '@theme-original/NavbarItem/DocsVersionDropdownNavbarItem';
import { useLocation } from '@docusaurus/router';
export default function DocsVersionDropdownNavbarItemWrapper(props) {
const { docsPluginId, className, type } = props
const { pathname } = useLocation()
/* (Custom) check if docsPluginId contains pathname
Given that the docsPluginId is 'charge-controller' and the routeBasePath is 'charge-controller', we can check against the current URI (pathname).
If the pathname contains the docsPluginId, we want to show the version dropdown. Otherwise, we don't want to show it.
This gives us one, global, context-aware version dropdown that works with multi-instance setups.
(Example for a possible configuration)
docusaurus.config.js:
****************************************************************************************************
[
'@docusaurus/plugin-content-docs',
{
id: 'charge-controller',
path: 'docs/ChargeController',
routeBasePath: 'charge-controller',
sidebarPath: require.resolve('./docs/ChargeController/sidebars.js'),
},
],
****************************************************************************************************
navbarItems.js (or as an attribute in docusaurus.config.js):
****************************************************************************************************
{
type: 'docsVersionDropdown',
docsPluginId: 'charge-controller',
position: 'right',
},
{
type: 'docsVersionDropdown',
docsPluginId: 'charge-point',
position: 'right',
},
****************************************************************************************************
*/
const doesPathnameContainDocsPluginId = pathname.includes(docsPluginId)
if (!doesPathnameContainDocsPluginId) {
return null
}
return <DocsVersionDropdownNavbarItem {...props} />;
} |
The solution I suggested is quite similar but more robust. The docs plugin id is not always contained in the URL. const activeDocContext = useActiveDocContext(props.docsPluginId); This returns something if the current docs plugin is active. You can also try using this undocumented hook (more low-level but core, less likely to be refactored) import useRouteContext from '@docusaurus/useRouteContext';
const {plugin: {id, name}} = useRouteContext(); |
I don't know for you but in 2.2.0 import React from "react";
import {useActiveDocContext} from '@docusaurus/plugin-content-docs/client';
import DocsVersionDropdownNavbarItem from '@theme-original/NavbarItem/DocsVersionDropdownNavbarItem';
export default function DocsVersionDropdownNavbarItemWrapper(props) {
// do not display this navbar item if current page is not a doc
const { activeDoc } = useActiveDocContext(props.docsPluginId);
if (!activeDoc) {
return null;
}
return <DocsVersionDropdownNavbarItem {...props} />;
} |
Ah yes, I don't always run the pseudo-code I suggest to use so take this with a grain of salt and adapt it a bit if needed 😄 |
Can this be applied not to the |
I don't know what you mean here, a repro would help. You can pass a pluginId as hook arg so if you have a 2nd docs plugin instance you can use its id here: |
So I have several instances of docs, each with its own sidebar and versioning. The instances are located in separate folders in the root:
To make the versions work, I added the version dropdowns to each versioned item in the navbar, thus making several version dropdowns on the UI. With the code above, I wanted to make a single navbar dropdown that will appear only for versioned instances and contain versions depending on the instance you are currently reading. I was able to achieve that with the following found code: import React from "react";
import DocsVersionDropdownNavbarItem from '@theme-original/NavbarItem/DocsVersionDropdownNavbarItem';
import { useLocation } from '@docusaurus/router';
export default function DocsVersionDropdownNavbarItemWrapper(props) {
const { docsPluginId, className, type } = props
const { pathname } = useLocation()
const doesPathnameContainDocsPluginId = pathname.includes(docsPluginId)
if (!doesPathnameContainDocsPluginId) {
return null
}
return <DocsVersionDropdownNavbarItem {...props} />;
} The only thing, in items: [
{
label: 'Instance-0',
to:'docs/...',
},
{
to: 'path-to-instance-1-id',
label: 'Instance-1',
},
{
to: 'path-to-instance-2-id',
label: 'Instance-2',
},
{
type: 'docsVersionDropdown',
docsPluginId: 'instance-1',
position: 'right',
},
{
type: 'docsVersionDropdown',
docsPluginId: 'instance-2',
position: 'right',
},
] |
For me the use case is being able to render some navbar items only when the user selected a specific docs version. |
Because there are workarounds possible now to achieve this and this is an open-source project with limited volunteer maintainer time to implement things. It's generally considered that if there's a way to achieve something, even if that way is swizzling or custom components that it's not a high priority and that providing a usable base set of features for 75+% of use cases/sites is the goal not making APIs for every single advanced use case someone wants. |
For my site, I wanted to avoid displaying the version dropdown when the active route is a blog post. Here is how I achieved it. I created import React from 'react';
import DocsVersionDropdownNavbarItem from '@theme-original/NavbarItem/DocsVersionDropdownNavbarItem';
import {useLocation} from '@docusaurus/router';
export default function DocsVersionDropdownNavbarItemWrapper(props) {
const location = useLocation();
const unversionedRoutes = [
// any route that starts with `/blog`
/^\/blog(?:\/[\w-]+)?(?:\/#\w+)?$/g
]
function checkPathname(pathname) {
// Check if the provided pathname matches any of the regexes in the list
return unversionedRoutes.some(regex => regex.test(pathname))
}
if (checkPathname(location.pathname)) {
return null;
}
return <DocsVersionDropdownNavbarItem {...props} />;
} Hope this would help someone else! |
@pavinduLakshan using I didn't try but this should work: import React from 'react';
import useRouteContext from '@docusaurus/useRouteContext';
import DocsVersionDropdownNavbarItem from '@theme-original/NavbarItem/DocsVersionDropdownNavbarItem';
export default function DocsVersionDropdownNavbarItemWrapper(props) {
const {plugin} = useRouteContext();
if (plugin.name === "docusaurus-plugin-content-blog") {
return null;
}
return <DocsVersionDropdownNavbarItem {...props} />;
} |
This is a brilliant solution except changing
to
Because If the versioned doc is not the current doc, |
🚀 Feature
An example:
I have three instances of docs, each segmented into three categories with their respective sidebars: "concepts" "guides" and "reference". These docs can be called "V1" "V2" and "V3", but are fixed docs once released so versioning isn't helpful in the canonical sense. When on "V2"-"Concepts" - clicking the Guides button on the navbar will take me to "V2"-"Guides", along with the appropriate sidebar.
Have you read the Contributing Guidelines on issues?
yes
Motivation
I'm trying to make the above work and have been unsuccessful after many days of wrangling
Pitch
It seems useful to have navbar routing that can accommodate multiple doc instances
The text was updated successfully, but these errors were encountered: