import { useCallback, useEffect, useRef, useState } from 'react' import cx from 'classnames' import { useRouter } from 'next/router' import { ActionList, ActionMenu, Dialog, IconButton } from '@primer/react' import { KebabHorizontalIcon, LinkExternalIcon, MarkGithubIcon, SearchIcon, ThreeBarsIcon, XIcon, } from '@primer/octicons-react' import { DEFAULT_VERSION, useVersion } from 'components/hooks/useVersion' import { Link } from 'components/Link' import { useMainContext } from 'components/context/MainContext' import { useHasAccount } from 'components/hooks/useHasAccount' import { LanguagePicker } from 'src/languages/components/LanguagePicker' import { HeaderNotifications } from 'components/page-header/HeaderNotifications' import { ApiVersionPicker } from 'src/rest/components/ApiVersionPicker' import { useTranslation } from 'src/languages/components/useTranslation' import { Search } from 'src/search/components/Search' import { Breadcrumbs } from 'components/page-header/Breadcrumbs' import { VersionPicker } from 'components/page-header/VersionPicker' import { SidebarNav } from 'components/sidebar/SidebarNav' import { AllProductsLink } from 'components/sidebar/AllProductsLink' import styles from './Header.module.scss' export const Header = () => { const router = useRouter() const { error } = useMainContext() const { isHomepageVersion, currentProduct } = useMainContext() const { currentVersion } = useVersion() const { t } = useTranslation(['header']) const isRestPage = currentProduct && currentProduct.id === 'rest' const [isSearchOpen, setIsSearchOpen] = useState(false) const [scroll, setScroll] = useState(false) const { hasAccount } = useHasAccount() const [isSidebarOpen, setIsSidebarOpen] = useState(false) const openSidebar = useCallback(() => setIsSidebarOpen(true), [isSidebarOpen]) const closeSidebar = useCallback(() => setIsSidebarOpen(false), [isSidebarOpen]) const isMounted = useRef(false) const menuButtonRef = useRef(null) const { asPath } = useRouter() const isSearchResultsPage = router.route === '/search' const signupCTAVisible = hasAccount === false && // don't show if `null` (currentVersion === DEFAULT_VERSION || currentVersion === 'enterprise-cloud@latest') const { width } = useWidth() const returnFocusRef = useRef(null) useEffect(() => { function onScroll() { setScroll(window.scrollY > 10) } window.addEventListener('scroll', onScroll) return () => { window.removeEventListener('scroll', onScroll) } }, []) useEffect(() => { const close = (e: { key: string }) => { if (e.key === 'Escape') { setIsSearchOpen(false) } } window.addEventListener('keydown', close) return () => window.removeEventListener('keydown', close) }, []) // For the UI in smaller browswer widths, and focus the picker menu button when the search // input is closed. useEffect(() => { if (!isSearchOpen && isMounted.current && menuButtonRef.current) { menuButtonRef.current.focus() } if (!isMounted.current) { isMounted.current = true } }, [isSearchOpen]) // When the sidebar overlay is opened, prevent the main content from being // scrollable. useEffect(() => { const bodyDiv = document.querySelector('body div') as HTMLElement const body = document.querySelector('body') if (bodyDiv && body) { // The full sidebar automatically shows at the xl window size so unlock // scrolling if the overlay was opened and the window size is increased to xl. body.style.overflow = isSidebarOpen && width && width < 1280 ? 'hidden' : 'auto' } }, [isSidebarOpen]) // with client side navigation clicking sidebar overlay links doesn't dismiss // the overlay so we close it ourselves when the path changes useEffect(() => { setIsSidebarOpen(false) }, [asPath]) // on REST pages there are sidebar links that are hash anchor links to different // sections on the same page so the sidebar overlay doesn't dismiss. we listen // for hash changes and close the overlay when the hash changes. useEffect(() => { const hashChangeHandler = () => { setIsSidebarOpen(false) } window.addEventListener('hashchange', hashChangeHandler) return () => { window.removeEventListener('hashchange', hashChangeHandler) } }, []) function useWidth() { const hasWindow = typeof window !== 'undefined' function getWidth() { const width = hasWindow ? window.innerWidth : null return { width, } } const [width, setWidth] = useState(getWidth()) useEffect(() => { if (hasWindow) { const handleResize = function () { setWidth(getWidth()) } window.addEventListener('resize', handleResize) return () => window.removeEventListener('resize', handleResize) } }, [hasWindow]) return width } return ( <>
{error !== '404' && }
{/* */} {error !== '404' && (
)}
{signupCTAVisible && ( )} setIsSearchOpen(!isSearchOpen)} aria-label="Open Search Bar" aria-expanded={isSearchOpen ? 'true' : 'false'} icon={SearchIcon} /> setIsSearchOpen(!isSearchOpen)} aria-label="Close Search Bar" aria-expanded={isSearchOpen ? 'true' : 'false'} icon={XIcon} sx={ isSearchOpen ? { // The x button to close the small width search UI when search is open, as the // browser width increases to md and above we no longer show that search UI so // the close search button is hidden as well. // breakpoint(md) '@media (min-width: 768px)': { display: 'none', }, } : { display: 'none', } } /> {/* The ... navigation menu at medium and smaller widths */}
{width && width > 544 ? ( ) : ( )} {width && width < 545 && ( <> )} {signupCTAVisible && ( {t`sign_up_cta`} )}{' '}
{!isHomepageVersion && !isSearchResultsPage && (
{error === '404' || !currentProduct || isSearchResultsPage ? null : (
{currentProduct.name}
)} {isRestPage && }
)}
) }