2021-08-16 17:52:53 +02:00
/ * *
* 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 , { useCallback , useState , useEffect } from 'react' ;
import clsx from 'clsx' ;
2021-09-07 11:34:59 +02:00
import Translate from '@docusaurus/Translate' ;
2021-08-16 17:52:53 +02:00
import SearchBar from '@theme/SearchBar' ;
import Toggle from '@theme/Toggle' ;
2021-09-07 11:34:59 +02:00
import {
useThemeConfig ,
useMobileSecondaryMenuRenderer ,
usePrevious ,
2022-02-07 11:47:35 +01:00
useHistoryPopHandler ,
useHideableNavbar ,
useLockBodyScroll ,
useWindowSize ,
useColorMode ,
2021-09-07 11:34:59 +02:00
} from '@docusaurus/theme-common' ;
2022-02-07 11:47:35 +01:00
import { useActivePlugin } from '@docusaurus/plugin-content-docs/client' ;
2021-08-16 17:52:53 +02:00
import NavbarItem from '@theme/NavbarItem' ;
import Logo from '@theme/Logo' ;
import IconMenu from '@theme/IconMenu' ;
2022-02-07 11:47:35 +01:00
import IconClose from '@theme/IconClose' ;
2021-08-16 17:52:53 +02:00
import styles from './styles.module.css' ; // retrocompatible with v1
2021-09-07 11:34:59 +02:00
const DefaultNavItemPosition = 'right' ;
function useNavbarItems ( ) {
// TODO temporary casting until ThemeConfig type is improved
return useThemeConfig ( ) . navbar . items ;
} // If split links by left/right
2021-08-16 17:52:53 +02:00
// if position is unspecified, fallback to right (as v1)
function splitNavItemsByPosition ( items ) {
const leftItems = items . filter (
( item ) => ( item . position ? ? DefaultNavItemPosition ) === 'left' ,
) ;
const rightItems = items . filter (
( item ) => ( item . position ? ? DefaultNavItemPosition ) === 'right' ,
) ;
return {
leftItems ,
rightItems ,
} ;
}
2021-09-07 11:34:59 +02:00
function useMobileSidebar ( ) {
const windowSize = useWindowSize ( ) ; // Mobile sidebar not visible on hydration: can avoid SSR rendering
const shouldRender = windowSize === 'mobile' ; // || windowSize === 'ssr';
2022-02-07 11:47:35 +01:00
const [ shown , setShown ] = useState ( false ) ; // Close mobile sidebar on navigation pop
// Most likely firing when using the Android back button (but not only)
useHistoryPopHandler ( ( ) => {
if ( shown ) {
setShown ( false ) ; // Should we prevent the navigation here?
// See https://github.com/facebook/docusaurus/pull/5462#issuecomment-911699846
return false ; // prevent pop navigation
}
return undefined ;
} ) ;
2021-09-07 11:34:59 +02:00
const toggle = useCallback ( ( ) => {
setShown ( ( s ) => ! s ) ;
} , [ ] ) ;
useEffect ( ( ) => {
if ( windowSize === 'desktop' ) {
setShown ( false ) ;
}
} , [ windowSize ] ) ;
return {
shouldRender ,
toggle ,
shown ,
} ;
}
function useColorModeToggle ( ) {
2021-08-16 17:52:53 +02:00
const {
2021-09-07 11:34:59 +02:00
colorMode : { disableSwitch } ,
2021-08-16 17:52:53 +02:00
} = useThemeConfig ( ) ;
2022-02-07 11:47:35 +01:00
const { isDarkTheme , setLightTheme , setDarkTheme } = useColorMode ( ) ;
2021-09-07 11:34:59 +02:00
const toggle = useCallback (
2021-08-16 17:52:53 +02:00
( e ) => ( e . target . checked ? setDarkTheme ( ) : setLightTheme ( ) ) ,
[ setLightTheme , setDarkTheme ] ,
) ;
2021-09-07 11:34:59 +02:00
return {
isDarkTheme ,
toggle ,
disabled : disableSwitch ,
} ;
}
function useSecondaryMenu ( { sidebarShown , toggleSidebar } ) {
const content = useMobileSecondaryMenuRenderer ( ) ? . ( {
toggleSidebar ,
} ) ;
const previousContent = usePrevious ( content ) ;
2022-02-07 11:47:35 +01:00
const [ shown , setShown ] = useState (
( ) =>
// /!\ content is set with useEffect,
// so it's not available on mount anyway
// "return !!content" => always returns false
false ,
) ; // When content is become available for the first time (set in useEffect)
2021-09-07 11:34:59 +02:00
// we set this content to be shown!
2021-08-16 17:52:53 +02:00
useEffect ( ( ) => {
2021-09-07 11:34:59 +02:00
const contentBecameAvailable = content && ! previousContent ;
if ( contentBecameAvailable ) {
setShown ( true ) ;
2021-08-16 17:52:53 +02:00
}
2021-09-07 11:34:59 +02:00
} , [ content , previousContent ] ) ;
const hasContent = ! ! content ; // 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 ( ! sidebarShown ) {
setShown ( true ) ;
}
} , [ sidebarShown , hasContent ] ) ;
const hide = useCallback ( ( ) => {
setShown ( false ) ;
} , [ ] ) ;
return {
shown ,
hide ,
content ,
} ;
}
function NavbarMobileSidebar ( { sidebarShown , toggleSidebar } ) {
useLockBodyScroll ( sidebarShown ) ;
const items = useNavbarItems ( ) ;
const colorModeToggle = useColorModeToggle ( ) ;
const secondaryMenu = useSecondaryMenu ( {
sidebarShown ,
toggleSidebar ,
} ) ;
return (
< div className = "navbar-sidebar" >
< div className = "navbar-sidebar__brand" >
< Logo
className = "navbar__brand"
imageClassName = "navbar__logo"
titleClassName = "navbar__title"
/ >
2022-02-07 11:47:35 +01:00
{ ! colorModeToggle . disabled && (
2021-09-07 11:34:59 +02:00
< Toggle
2022-02-07 11:47:35 +01:00
className = { styles . navbarSidebarToggle }
2021-09-07 11:34:59 +02:00
checked = { colorModeToggle . isDarkTheme }
onChange = { colorModeToggle . toggle }
/ >
) }
2022-02-07 11:47:35 +01:00
< button
type = "button"
className = "clean-btn navbar-sidebar__close"
onClick = { toggleSidebar } >
< IconClose
color = "var(--ifm-color-emphasis-600)"
className = { styles . navbarSidebarCloseSvg }
/ >
< / b u t t o n >
2021-09-07 11:34:59 +02:00
< / d i v >
< div
className = { clsx ( 'navbar-sidebar__items' , {
'navbar-sidebar__items--show-secondary' : secondaryMenu . shown ,
} ) } >
< div className = "navbar-sidebar__item menu" >
< ul className = "menu__list" >
{ items . map ( ( item , i ) => (
< NavbarItem mobile { ... item } onClick = { toggleSidebar } key = { i } / >
) ) }
< / u l >
< / d i v >
< div className = "navbar-sidebar__item menu" >
{ items . length > 0 && (
< button
type = "button"
className = "clean-btn navbar-sidebar__back"
onClick = { secondaryMenu . hide } >
< Translate
id = "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel"
description = "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)" >
← Back to main menu
< / T r a n s l a t e >
< / b u t t o n >
) }
{ secondaryMenu . content }
< / d i v >
< / d i v >
< / d i v >
) ;
}
function Navbar ( ) {
const {
navbar : { hideOnScroll , style } ,
} = useThemeConfig ( ) ;
const mobileSidebar = useMobileSidebar ( ) ;
const colorModeToggle = useColorModeToggle ( ) ;
const activeDocPlugin = useActivePlugin ( ) ;
const { navbarRef , isNavbarVisible } = useHideableNavbar ( hideOnScroll ) ;
const items = useNavbarItems ( ) ;
2021-08-16 17:52:53 +02:00
const hasSearchNavbarItem = items . some ( ( item ) => item . type === 'search' ) ;
const { leftItems , rightItems } = splitNavItemsByPosition ( items ) ;
return (
< nav
ref = { navbarRef }
className = { clsx ( 'navbar' , 'navbar--fixed-top' , {
'navbar--dark' : style === 'dark' ,
'navbar--primary' : style === 'primary' ,
2021-09-07 11:34:59 +02:00
'navbar-sidebar--show' : mobileSidebar . shown ,
2021-08-16 17:52:53 +02:00
[ styles . navbarHideable ] : hideOnScroll ,
[ styles . navbarHidden ] : hideOnScroll && ! isNavbarVisible ,
} ) } >
< div className = "navbar__inner" >
< div className = "navbar__items" >
2021-09-07 11:34:59 +02:00
{ ( items ? . length > 0 || activeDocPlugin ) && (
2021-08-16 17:52:53 +02:00
< button
aria - label = "Navigation bar toggle"
className = "navbar__toggle clean-btn"
type = "button"
tabIndex = { 0 }
2021-09-07 11:34:59 +02:00
onClick = { mobileSidebar . toggle }
onKeyDown = { mobileSidebar . toggle } >
2021-08-16 17:52:53 +02:00
< IconMenu / >
< / b u t t o n >
) }
< Logo
className = "navbar__brand"
imageClassName = "navbar__logo"
titleClassName = "navbar__title"
/ >
{ leftItems . map ( ( item , i ) => (
< NavbarItem { ... item } key = { i } / >
) ) }
< / d i v >
< div className = "navbar__items navbar__items--right" >
{ ! hasSearchNavbarItem && < SearchBar / > }
2021-09-07 11:34:59 +02:00
{ rightItems . map ( ( item , i ) => {
if ( item . type === "search" ) {
return (
< React . Fragment key = { i } >
< NavbarItem { ... item } / >
{ ! colorModeToggle . disabled && (
< Toggle
className = { styles . toggle }
checked = { colorModeToggle . isDarkTheme }
onChange = { colorModeToggle . toggle }
/ >
) }
< / R e a c t . F r a g m e n t >
)
} else {
return < NavbarItem { ... item } key = { i } / >
}
} ) }
2021-08-16 17:52:53 +02:00
< / d i v >
< / d i v >
2021-09-07 11:34:59 +02:00
2021-08-16 17:52:53 +02:00
< div
role = "presentation"
className = "navbar-sidebar__backdrop"
2021-09-07 11:34:59 +02:00
onClick = { mobileSidebar . toggle }
2021-08-16 17:52:53 +02:00
/ >
2021-09-07 11:34:59 +02:00
{ mobileSidebar . shouldRender && (
< NavbarMobileSidebar
sidebarShown = { mobileSidebar . shown }
toggleSidebar = { mobileSidebar . toggle }
/ >
) }
2021-08-16 17:52:53 +02:00
< / n a v >
) ;
}
export default Navbar ;