mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-24 06:27:02 +02:00
feat(theme-classic): show blog sidebar on mobile (#7012)
* feat(theme-classic): show blog sidebar on mobile * fix * oops * docs * add a little margin * Update display.tsx * Update content.tsx * reformat
This commit is contained in:
parent
1f77fc93bb
commit
2e79597f83
8 changed files with 260 additions and 188 deletions
|
@ -8,17 +8,21 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
|
import {translate} from '@docusaurus/Translate';
|
||||||
|
import {
|
||||||
|
NavbarSecondaryMenuFiller,
|
||||||
|
useWindowSize,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
import type {Props} from '@theme/BlogSidebar';
|
import type {Props} from '@theme/BlogSidebar';
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
import {translate} from '@docusaurus/Translate';
|
|
||||||
|
|
||||||
export default function BlogSidebar({sidebar}: Props): JSX.Element | null {
|
function BlogSidebarContent({
|
||||||
if (sidebar.items.length === 0) {
|
sidebar,
|
||||||
return null;
|
className,
|
||||||
}
|
}: Props & {className?: string}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
className={clsx(styles.sidebar, 'thin-scrollbar')}
|
className={clsx(styles.sidebar, 'thin-scrollbar', className)}
|
||||||
aria-label={translate({
|
aria-label={translate({
|
||||||
id: 'theme.blog.sidebar.navAriaLabel',
|
id: 'theme.blog.sidebar.navAriaLabel',
|
||||||
message: 'Blog recent posts navigation',
|
message: 'Blog recent posts navigation',
|
||||||
|
@ -43,3 +47,20 @@ export default function BlogSidebar({sidebar}: Props): JSX.Element | null {
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function BlogSidebar(props: Props): JSX.Element | null {
|
||||||
|
const windowSize = useWindowSize();
|
||||||
|
if (props.sidebar.items.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Mobile sidebar doesn't need to be server-rendered
|
||||||
|
if (windowSize === 'mobile') {
|
||||||
|
return (
|
||||||
|
<NavbarSecondaryMenuFiller
|
||||||
|
component={BlogSidebarContent}
|
||||||
|
props={{...props, className: 'margin-left--md'}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <BlogSidebarContent {...props} className={styles.sidebarDesktop} />;
|
||||||
|
}
|
||||||
|
|
|
@ -40,7 +40,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 996px) {
|
@media (max-width: 996px) {
|
||||||
.sidebar {
|
.sidebarDesktop {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,9 @@ import React, {
|
||||||
useMemo,
|
useMemo,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import {useNavbarSecondaryMenuContent} from './navbarSecondaryMenu/content';
|
||||||
import {useWindowSize} from '../hooks/useWindowSize';
|
import {useWindowSize} from '../hooks/useWindowSize';
|
||||||
import {useHistoryPopHandler} from '../utils/historyUtils';
|
import {useHistoryPopHandler} from '../utils/historyUtils';
|
||||||
import {useActivePlugin} from '@docusaurus/plugin-content-docs/client';
|
|
||||||
import {useThemeConfig} from '../utils/useThemeConfig';
|
import {useThemeConfig} from '../utils/useThemeConfig';
|
||||||
import {ReactContextError} from '../utils/reactUtils';
|
import {ReactContextError} from '../utils/reactUtils';
|
||||||
|
|
||||||
|
@ -40,9 +40,9 @@ type ContextValue = {
|
||||||
const Context = React.createContext<ContextValue | undefined>(undefined);
|
const Context = React.createContext<ContextValue | undefined>(undefined);
|
||||||
|
|
||||||
function useIsNavbarMobileSidebarDisabled() {
|
function useIsNavbarMobileSidebarDisabled() {
|
||||||
const activeDocPlugin = useActivePlugin();
|
const secondaryMenuContent = useNavbarSecondaryMenuContent();
|
||||||
const {items} = useThemeConfig().navbar;
|
const {items} = useThemeConfig().navbar;
|
||||||
return items.length === 0 && !activeDocPlugin;
|
return items.length === 0 && !secondaryMenuContent.component;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useContextValue(): ContextValue {
|
function useContextValue(): ContextValue {
|
||||||
|
|
|
@ -1,170 +0,0 @@
|
||||||
/**
|
|
||||||
* 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, {
|
|
||||||
useState,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useCallback,
|
|
||||||
type ReactNode,
|
|
||||||
type ComponentType,
|
|
||||||
} from 'react';
|
|
||||||
import {ReactContextError, usePrevious} from '../utils/reactUtils';
|
|
||||||
import {useNavbarMobileSidebar} from './navbarMobileSidebar';
|
|
||||||
|
|
||||||
export type NavbarSecondaryMenuComponent<Props> = ComponentType<Props>;
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
shown: boolean;
|
|
||||||
content:
|
|
||||||
| {
|
|
||||||
component: NavbarSecondaryMenuComponent<object>;
|
|
||||||
props: object;
|
|
||||||
}
|
|
||||||
| {component: null; props: null};
|
|
||||||
};
|
|
||||||
|
|
||||||
const InitialState: State = {
|
|
||||||
shown: false,
|
|
||||||
content: {component: null, props: null},
|
|
||||||
};
|
|
||||||
|
|
||||||
type ContextValue = [
|
|
||||||
state: State,
|
|
||||||
setState: React.Dispatch<React.SetStateAction<State>>,
|
|
||||||
];
|
|
||||||
|
|
||||||
const Context = React.createContext<ContextValue | null>(null);
|
|
||||||
|
|
||||||
function useContextValue(): ContextValue {
|
|
||||||
const mobileSidebar = useNavbarMobileSidebar();
|
|
||||||
|
|
||||||
const [state, setState] = useState<State>(InitialState);
|
|
||||||
|
|
||||||
const setShown = (shown: boolean) => setState((s) => ({...s, shown}));
|
|
||||||
|
|
||||||
const hasContent = state.content?.component !== null;
|
|
||||||
const previousHasContent = usePrevious(state.content?.component !== null);
|
|
||||||
|
|
||||||
// When content is become available for the first time (set in useEffect)
|
|
||||||
// we set this content to be shown!
|
|
||||||
useEffect(() => {
|
|
||||||
const contentBecameAvailable = hasContent && !previousHasContent;
|
|
||||||
if (contentBecameAvailable) {
|
|
||||||
setShown(true);
|
|
||||||
}
|
|
||||||
}, [hasContent, previousHasContent]);
|
|
||||||
|
|
||||||
// On sidebar close, secondary menu is set to be shown on next re-opening
|
|
||||||
// (if any secondary menu content available)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!hasContent) {
|
|
||||||
setShown(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!mobileSidebar.shown) {
|
|
||||||
setShown(true);
|
|
||||||
}
|
|
||||||
}, [mobileSidebar.shown, hasContent]);
|
|
||||||
|
|
||||||
return [state, setState];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NavbarSecondaryMenuProvider({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: ReactNode;
|
|
||||||
}): JSX.Element {
|
|
||||||
const value = useContextValue();
|
|
||||||
return <Context.Provider value={value}>{children}</Context.Provider>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function useNavbarSecondaryMenuContext(): ContextValue {
|
|
||||||
const value = useContext(Context);
|
|
||||||
if (value === null) {
|
|
||||||
throw new ReactContextError('MobileSecondaryMenuProvider');
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function useShallowMemoizedObject<O>(obj: O) {
|
|
||||||
return useMemo(
|
|
||||||
() => obj,
|
|
||||||
// Is this safe?
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[...Object.keys(obj), ...Object.values(obj)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component renders nothing by itself, but it fills the placeholder in the
|
|
||||||
* generic secondary menu layout. This reduces coupling between the main layout
|
|
||||||
* and the specific page.
|
|
||||||
*
|
|
||||||
* This kind of feature is often called portal/teleport/gateway/outlet...
|
|
||||||
* Various unmaintained React libs exist. Most up-to-date one:
|
|
||||||
* https://github.com/gregberge/react-teleporter
|
|
||||||
* Not sure any of those is safe regarding concurrent mode.
|
|
||||||
*/
|
|
||||||
export function NavbarSecondaryMenuFiller<P extends object>({
|
|
||||||
component,
|
|
||||||
props,
|
|
||||||
}: {
|
|
||||||
component: NavbarSecondaryMenuComponent<P>;
|
|
||||||
props: P;
|
|
||||||
}): JSX.Element | null {
|
|
||||||
const [, setState] = useNavbarSecondaryMenuContext();
|
|
||||||
|
|
||||||
// To avoid useless context re-renders, props are memoized shallowly
|
|
||||||
const memoizedProps = useShallowMemoizedObject(props);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// @ts-expect-error: context is not 100% type-safe but it's ok
|
|
||||||
setState((s) => ({...s, content: {component, props: memoizedProps}}));
|
|
||||||
}, [setState, component, memoizedProps]);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => () => setState((s) => ({...s, component: null, props: null})),
|
|
||||||
[setState],
|
|
||||||
);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderElement(state: State): JSX.Element | undefined {
|
|
||||||
if (state.content?.component) {
|
|
||||||
const Comp = state.content.component;
|
|
||||||
return <Comp {...state.content.props} />;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Wires the logic for rendering the mobile navbar secondary menu. */
|
|
||||||
export function useNavbarSecondaryMenu(): {
|
|
||||||
/** Whether secondary menu is displayed. */
|
|
||||||
shown: boolean;
|
|
||||||
/**
|
|
||||||
* Hide the secondary menu; fired either when hiding the entire sidebar, or
|
|
||||||
* when going back to the primary menu.
|
|
||||||
*/
|
|
||||||
hide: () => void;
|
|
||||||
/** The content returned from the current secondary menu filler. */
|
|
||||||
content: JSX.Element | undefined;
|
|
||||||
} {
|
|
||||||
const [state, setState] = useNavbarSecondaryMenuContext();
|
|
||||||
|
|
||||||
const hide = useCallback(
|
|
||||||
() => setState((s) => ({...s, shown: false})),
|
|
||||||
[setState],
|
|
||||||
);
|
|
||||||
|
|
||||||
return useMemo(
|
|
||||||
() => ({shown: state.shown, hide, content: renderElement(state)}),
|
|
||||||
[hide, state],
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
/**
|
||||||
|
* 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, {
|
||||||
|
useState,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
type ReactNode,
|
||||||
|
type ComponentType,
|
||||||
|
} from 'react';
|
||||||
|
import {ReactContextError} from '../../utils/reactUtils';
|
||||||
|
|
||||||
|
// This context represents a "global layout store". A component (usually a
|
||||||
|
// layout component) can request filling this store through
|
||||||
|
// `NavbarSecondaryMenuFiller`. It doesn't actually control rendering by itself,
|
||||||
|
// and this context should be considered internal implementation. The user-
|
||||||
|
// facing value comes from `display.tsx`, which takes the `component` and
|
||||||
|
// `props` stored here and renders the actual element.
|
||||||
|
|
||||||
|
export type NavbarSecondaryMenuComponent<Props> = ComponentType<Props>;
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export type Content =
|
||||||
|
| {
|
||||||
|
component: NavbarSecondaryMenuComponent<object>;
|
||||||
|
props: object;
|
||||||
|
}
|
||||||
|
| {component: null; props: null};
|
||||||
|
|
||||||
|
type ContextValue = [
|
||||||
|
content: Content,
|
||||||
|
setContent: React.Dispatch<React.SetStateAction<Content>>,
|
||||||
|
];
|
||||||
|
|
||||||
|
const Context = React.createContext<ContextValue | null>(null);
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function NavbarSecondaryMenuContentProvider({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
}): JSX.Element {
|
||||||
|
const value = useState({component: null, props: null});
|
||||||
|
return (
|
||||||
|
// @ts-expect-error: this context is hard to type
|
||||||
|
<Context.Provider value={value}>{children}</Context.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function useNavbarSecondaryMenuContent(): Content {
|
||||||
|
const value = useContext(Context);
|
||||||
|
if (!value) {
|
||||||
|
throw new ReactContextError('NavbarSecondaryMenuContentProvider');
|
||||||
|
}
|
||||||
|
return value[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function useShallowMemoizedObject<O>(obj: O) {
|
||||||
|
return useMemo(
|
||||||
|
() => obj,
|
||||||
|
// Is this safe?
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[...Object.keys(obj), ...Object.values(obj)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component renders nothing by itself, but it fills the placeholder in the
|
||||||
|
* generic secondary menu layout. This reduces coupling between the main layout
|
||||||
|
* and the specific page.
|
||||||
|
*
|
||||||
|
* This kind of feature is often called portal/teleport/gateway/outlet...
|
||||||
|
* Various unmaintained React libs exist. Most up-to-date one:
|
||||||
|
* https://github.com/gregberge/react-teleporter
|
||||||
|
* Not sure any of those is safe regarding concurrent mode.
|
||||||
|
*/
|
||||||
|
export function NavbarSecondaryMenuFiller<P extends object>({
|
||||||
|
component,
|
||||||
|
props,
|
||||||
|
}: {
|
||||||
|
component: NavbarSecondaryMenuComponent<P>;
|
||||||
|
props: P;
|
||||||
|
}): JSX.Element | null {
|
||||||
|
const context = useContext(Context);
|
||||||
|
if (!context) {
|
||||||
|
throw new ReactContextError('NavbarSecondaryMenuContentProvider');
|
||||||
|
}
|
||||||
|
const [, setContent] = context;
|
||||||
|
|
||||||
|
// To avoid useless context re-renders, props are memoized shallowly
|
||||||
|
const memoizedProps = useShallowMemoizedObject(props);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// @ts-expect-error: this context is hard to type
|
||||||
|
setContent({component, props: memoizedProps});
|
||||||
|
}, [setContent, component, memoizedProps]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => () => setContent({component: null, props: null}),
|
||||||
|
[setContent],
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
/**
|
||||||
|
* 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, {
|
||||||
|
useState,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useCallback,
|
||||||
|
type ReactNode,
|
||||||
|
} from 'react';
|
||||||
|
import {ReactContextError, usePrevious} from '../../utils/reactUtils';
|
||||||
|
import {useNavbarMobileSidebar} from '../navbarMobileSidebar';
|
||||||
|
import {useNavbarSecondaryMenuContent, type Content} from './content';
|
||||||
|
|
||||||
|
type ContextValue = [
|
||||||
|
shown: boolean,
|
||||||
|
setShown: React.Dispatch<React.SetStateAction<boolean>>,
|
||||||
|
];
|
||||||
|
|
||||||
|
const Context = React.createContext<ContextValue | null>(null);
|
||||||
|
|
||||||
|
function useContextValue(): ContextValue {
|
||||||
|
const mobileSidebar = useNavbarMobileSidebar();
|
||||||
|
const content = useNavbarSecondaryMenuContent();
|
||||||
|
|
||||||
|
const [shown, setShown] = useState(false);
|
||||||
|
|
||||||
|
const hasContent = content.component !== null;
|
||||||
|
const previousHasContent = usePrevious(hasContent);
|
||||||
|
|
||||||
|
// When content is become available for the first time (set in useEffect)
|
||||||
|
// we set this content to be shown!
|
||||||
|
useEffect(() => {
|
||||||
|
const contentBecameAvailable = hasContent && !previousHasContent;
|
||||||
|
if (contentBecameAvailable) {
|
||||||
|
setShown(true);
|
||||||
|
}
|
||||||
|
}, [hasContent, previousHasContent]);
|
||||||
|
|
||||||
|
// On sidebar close, secondary menu is set to be shown on next re-opening
|
||||||
|
// (if any secondary menu content available)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!hasContent) {
|
||||||
|
setShown(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mobileSidebar.shown) {
|
||||||
|
setShown(true);
|
||||||
|
}
|
||||||
|
}, [mobileSidebar.shown, hasContent]);
|
||||||
|
|
||||||
|
return useMemo(() => [shown, setShown], [shown]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function NavbarSecondaryMenuDisplayProvider({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
}): JSX.Element {
|
||||||
|
const value = useContextValue();
|
||||||
|
return <Context.Provider value={value}>{children}</Context.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderElement(content: Content): JSX.Element | undefined {
|
||||||
|
if (content.component) {
|
||||||
|
const Comp = content.component;
|
||||||
|
return <Comp {...content.props} />;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wires the logic for rendering the mobile navbar secondary menu. */
|
||||||
|
export function useNavbarSecondaryMenu(): {
|
||||||
|
/** Whether secondary menu is displayed. */
|
||||||
|
shown: boolean;
|
||||||
|
/**
|
||||||
|
* Hide the secondary menu; fired either when hiding the entire sidebar, or
|
||||||
|
* when going back to the primary menu.
|
||||||
|
*/
|
||||||
|
hide: () => void;
|
||||||
|
/** The content returned from the current secondary menu filler. */
|
||||||
|
content: JSX.Element | undefined;
|
||||||
|
} {
|
||||||
|
const value = useContext(Context);
|
||||||
|
if (!value) {
|
||||||
|
throw new ReactContextError('NavbarSecondaryMenuDisplayProvider');
|
||||||
|
}
|
||||||
|
const [shown, setShown] = value;
|
||||||
|
const hide = useCallback(() => setShown(false), [setShown]);
|
||||||
|
const content = useNavbarSecondaryMenuContent();
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({shown, hide, content: renderElement(content)}),
|
||||||
|
[hide, content, shown],
|
||||||
|
);
|
||||||
|
}
|
|
@ -141,10 +141,10 @@ export {
|
||||||
|
|
||||||
export {useNavbarMobileSidebar} from './contexts/navbarMobileSidebar';
|
export {useNavbarMobileSidebar} from './contexts/navbarMobileSidebar';
|
||||||
export {
|
export {
|
||||||
useNavbarSecondaryMenu,
|
|
||||||
NavbarSecondaryMenuFiller,
|
NavbarSecondaryMenuFiller,
|
||||||
type NavbarSecondaryMenuComponent,
|
type NavbarSecondaryMenuComponent,
|
||||||
} from './contexts/navbarSecondaryMenu';
|
} from './contexts/navbarSecondaryMenu/content';
|
||||||
|
export {useNavbarSecondaryMenu} from './contexts/navbarSecondaryMenu/display';
|
||||||
|
|
||||||
export {useBackToTopButton} from './hooks/useBackToTopButton';
|
export {useBackToTopButton} from './hooks/useBackToTopButton';
|
||||||
export {useHideableNavbar} from './hooks/useHideableNavbar';
|
export {useHideableNavbar} from './hooks/useHideableNavbar';
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
import React, {type ReactNode} from 'react';
|
import React, {type ReactNode} from 'react';
|
||||||
import {NavbarMobileSidebarProvider} from '../contexts/navbarMobileSidebar';
|
import {NavbarMobileSidebarProvider} from '../contexts/navbarMobileSidebar';
|
||||||
import {NavbarSecondaryMenuProvider} from '../contexts/navbarSecondaryMenu';
|
import {NavbarSecondaryMenuContentProvider} from '../contexts/navbarSecondaryMenu/content';
|
||||||
|
import {NavbarSecondaryMenuDisplayProvider} from '../contexts/navbarSecondaryMenu/display';
|
||||||
|
|
||||||
const DefaultNavItemPosition = 'right';
|
const DefaultNavItemPosition = 'right';
|
||||||
|
|
||||||
|
@ -28,13 +29,17 @@ export function splitNavbarItems<T extends {position?: 'left' | 'right'}>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composes the `NavbarMobileSidebarProvider` and `NavbarSecondaryMenuProvider`.
|
* Composes multiple navbar state providers that are mutually dependent and
|
||||||
* Because the latter depends on the former, they can't be re-ordered.
|
* hence can't be re-ordered.
|
||||||
*/
|
*/
|
||||||
export function NavbarProvider({children}: {children: ReactNode}): JSX.Element {
|
export function NavbarProvider({children}: {children: ReactNode}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
|
<NavbarSecondaryMenuContentProvider>
|
||||||
<NavbarMobileSidebarProvider>
|
<NavbarMobileSidebarProvider>
|
||||||
<NavbarSecondaryMenuProvider>{children}</NavbarSecondaryMenuProvider>
|
<NavbarSecondaryMenuDisplayProvider>
|
||||||
|
{children}
|
||||||
|
</NavbarSecondaryMenuDisplayProvider>
|
||||||
</NavbarMobileSidebarProvider>
|
</NavbarMobileSidebarProvider>
|
||||||
|
</NavbarSecondaryMenuContentProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue