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' ;
import useThemeContext from '@theme/hooks/useThemeContext' ;
2021-09-07 11:34:59 +02:00
import {
useThemeConfig ,
useMobileSecondaryMenuRenderer ,
usePrevious ,
} from '@docusaurus/theme-common' ;
2021-08-16 17:52:53 +02:00
import useHideableNavbar from '@theme/hooks/useHideableNavbar' ;
import useLockBodyScroll from '@theme/hooks/useLockBodyScroll' ;
2021-09-07 11:34:59 +02:00
import useWindowSize from '@theme/hooks/useWindowSize' ;
import { useActivePlugin } from '@theme/hooks/useDocs' ;
2021-08-16 17:52:53 +02:00
import NavbarItem from '@theme/NavbarItem' ;
import Logo from '@theme/Logo' ;
import IconMenu from '@theme/IconMenu' ;
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';
const [ shown , setShown ] = useState ( false ) ;
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 ( ) ;
const { isDarkTheme , setLightTheme , setDarkTheme } = useThemeContext ( ) ;
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 ) ;
const [ shown , setShown ] = useState ( ( ) => {
// /!\ content is set with useEffect,
// so it's not available on mount anyway
// "return !!content" => always returns false
return false ;
} ) ; // When content is become available for the first time (set in useEffect)
// 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"
/ >
{ ! colorModeToggle . disabled && sidebarShown && (
< Toggle
checked = { colorModeToggle . isDarkTheme }
onChange = { colorModeToggle . toggle }
/ >
) }
< / 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 ;