Skip to content

Commit

Permalink
Feature Branch: Global Nav Phase 2 (github#34359)
Browse files Browse the repository at this point in the history
Co-authored-by: Grace Park <gracepark@github.com>
Co-authored-by: Joe Oak <41307427+joeoak@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 13, 2023
1 parent d4baaae commit e5370a5
Show file tree
Hide file tree
Showing 20 changed files with 340 additions and 199 deletions.
2 changes: 1 addition & 1 deletion components/DefaultLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const DefaultLayout = (props: Props) => {
</Head>

{/* For local site search indexing */}
<div data-search="breadcrumbs">
<div className="d-none d-xl-block" data-search="breadcrumbs">
<Breadcrumbs />
</div>

Expand Down
6 changes: 3 additions & 3 deletions components/article/ArticleGridLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const ArticleGridLayout = ({ intro, topper, toc, children, className }: P
<SidebarContent
gridArea="sidebar"
alignSelf="flex-start"
className="border-bottom border-xl-0 pb-4 mb-5 pb-xl-0 mb-xl-0"
className="border-bottom border-lg-0 pb-4 mb-5 pb-xl-0 mb-xl-0"
>
{toc}
</SidebarContent>
Expand All @@ -44,7 +44,7 @@ const Container = styled(Box)`
row-gap: ${themeGet('space.2')};
@media (min-width: ${themeGet('breakpoints.3')}) {
@media (min-width: ${themeGet('breakpoints.2')}) {
max-width: none;
padding-top: ${themeGet('space.4')};
grid-template-rows: auto 1fr;
Expand All @@ -59,7 +59,7 @@ const Container = styled(Box)`
`

const SidebarContent = styled(Box)`
@media (min-width: ${themeGet('breakpoints.3')}) {
@media (min-width: ${themeGet('breakpoints.2')}) {
position: sticky;
padding-top: ${themeGet('space.4')};
top: 5em;
Expand Down
2 changes: 1 addition & 1 deletion components/article/ArticlePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const ArticlePage = () => {
<ClientSideHighlight />
{router.pathname.includes('/rest/') && <RestRedirect />}
<div className="container-xl px-3 px-md-6 my-4">
<div className={cx('my-3 mr-auto width-full')}>
<div className={cx('d-none d-xl-block mt-3 mr-auto width-full')}>
<Breadcrumbs />
</div>
<ArticleGridLayout
Expand Down
2 changes: 1 addition & 1 deletion components/landing/ProductLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const ProductLanding = () => {
<div data-search="article-body">
{router.query.productId === 'rest' && <RestRedirect />}
<LandingSection className="pt-3">
<div className={cx('my-3 mr-auto width-full')}>
<div className={cx('d-none d-xl-block my-3 mr-auto width-full')}>
<Breadcrumbs />
</div>
<LandingHero />
Expand Down
2 changes: 1 addition & 1 deletion components/landing/TocLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const TocLanding = () => {
<ClientSideRedirects />

<div className="container-xl px-3 px-md-6 my-4">
<div className={cx('my-3 mr-auto width-full')}>
<div className={cx('d-none d-xl-block mt-3 mr-auto width-full')}>
<Breadcrumbs />
</div>
<ArticleGridLayout>
Expand Down
34 changes: 17 additions & 17 deletions components/page-header/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import cx from 'classnames'
import { useRouter } from 'next/router'

import { useMainContext } from '../context/MainContext'
import { Link } from 'components/Link'

import styles from './Breadcrumbs.module.scss'

type Props = {
inHeader?: boolean
}

export type BreadcrumbT = {
title: string
href?: string
}

export const Breadcrumbs = () => {
const router = useRouter()
// remove query string and hash
const pathWithLocale = `/${router.locale}${router.asPath.split('?')[0].split('#')[0]}`
export const Breadcrumbs = ({ inHeader }: Props) => {
const { breadcrumbs } = useMainContext()

return (
Expand All @@ -23,7 +25,7 @@ export const Breadcrumbs = () => {
updating script/search/parse-page-sections-into-records.js.
*/
<nav
data-testid="breadcrumbs"
data-testid={inHeader ? 'breadcrumbs-header' : 'breadcrumbs-in-article'}
className={cx('f5 breadcrumbs', styles.breadcrumbs)}
aria-label="Breadcrumb"
>
Expand All @@ -44,21 +46,19 @@ export const Breadcrumbs = () => {
href={breadcrumb.href}
title={title}
className={cx(
'pr-3',
// Always show first and last, show middle on XL size
i === 0 || i === arr.length - 1
? 'd-inline-block'
: 'd-none d-xl-inline-block',
pathWithLocale === breadcrumb.href && 'color-fg-muted'
'Link--primary mr-2 color-fg-muted',
// Show the last breadcrumb if it's in the header, but not if it's in the article
// If there's only 1 breadcrumb, show it
!inHeader && i === arr.length - 1 && arr.length !== 1 && 'd-none'
)}
>
{breadcrumb.title}
{i !== arr.length - 1 ? (
<span className="color-fg-muted pl-3" key={`${i}-slash`}>
/
</span>
) : null}
</Link>
{i !== arr.length - 1 ? (
<span className="color-fg-muted pr-2" key={`${i}-slash`}>
/
</span>
) : null}
</li>
),
]
Expand Down
121 changes: 113 additions & 8 deletions components/page-header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import cx from 'classnames'
import { useRouter } from 'next/router'
import { AnchoredOverlay, IconButton } from '@primer/react'
import { AnchoredOverlay, Dialog, IconButton } from '@primer/react'
import {
KebabHorizontalIcon,
LinkExternalIcon,
MarkGithubIcon,
SearchIcon,
ThreeBarsIcon,
XIcon,
} from '@primer/octicons-react'

Expand All @@ -19,14 +20,17 @@ import { HeaderNotifications } from 'components/page-header/HeaderNotifications'
import { ApiVersionPicker } from 'components/sidebar/ApiVersionPicker'
import { useTranslation } from 'components/hooks/useTranslation'
import { Search } from '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 { currentProduct, allVersions } = useMainContext()
const { isHomepageVersion, currentProduct, currentProductTree, allVersions } = useMainContext()
const { currentVersion } = useVersion()
const { t } = useTranslation(['header'])
const isRestPage = currentProduct && currentProduct.id === 'rest'
Expand All @@ -36,12 +40,21 @@ export const Header = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false)
const openMenuOverlay = useCallback(() => setIsMenuOpen(true), [setIsMenuOpen])
const closeMenuOverlay = useCallback(() => setIsMenuOpen(false), [setIsMenuOpen])
const [isSidebarOpen, setIsSidebarOpen] = useState(false)
const openSidebar = useCallback(() => setIsSidebarOpen(true), [isSidebarOpen])
const closeSidebar = useCallback(() => setIsSidebarOpen(false), [isSidebarOpen])
const isMounted = useRef(false)
const menuButtonRef = useRef<HTMLButtonElement>(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 productTitle = currentProductTree?.shortTitle || currentProductTree?.title
const [windowSize, setWindowSize] = useState(0)
const handleWindowResize = useCallback(() => {
setWindowSize(window.innerWidth)
}, [])

useEffect(() => {
function onScroll() {
Expand Down Expand Up @@ -75,6 +88,40 @@ export const Header = () => {
}
}, [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 && windowSize < 1280 ? 'hidden' : 'auto'
}
window.addEventListener('resize', handleWindowResize)
return () => window.removeEventListener('resize', handleWindowResize)
}, [isSidebarOpen, windowSize])

// 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)
}
}, [])

return (
<>
<div
Expand All @@ -86,12 +133,12 @@ export const Header = () => {
{error !== '404' && <HeaderNotifications />}
<header
className={cx(
'color-bg-default px-3 pt-3 pb-3 position-sticky top-0 z-1 border-bottom',
'color-bg-default p-2 position-sticky top-0 z-1 border-bottom',
scroll && 'color-shadow-small'
)}
>
<div
className="d-flex flex-justify-between flex-items-center flex-wrap"
className="d-flex flex-justify-between p-2 flex-items-center flex-wrap"
data-testid="desktop-header"
>
<div
Expand Down Expand Up @@ -170,8 +217,9 @@ export const Header = () => {
/>

{/* The ... navigation menu at medium and smaller widths */}
<nav>
<div>
<AnchoredOverlay
anchorRef={menuButtonRef}
renderAnchor={(anchorProps) => (
<IconButton
data-testid="mobile-menu"
Expand Down Expand Up @@ -205,7 +253,7 @@ export const Header = () => {
</span>
{isRestPage && allVersions[currentVersion].apiVersions.length > 0 && (
<span className="pb-2 m-2 d-block">
<ApiVersionPicker mediumOrLower={true} />
<ApiVersionPicker />
</span>
)}
{signupCTAVisible && (
Expand All @@ -222,9 +270,66 @@ export const Header = () => {
)}
</div>
</AnchoredOverlay>
</nav>
</div>
</div>
</div>
{!isHomepageVersion && !isSearchResultsPage && (
<div className="d-flex flex-items-center d-xl-none mt-2">
<div className={cx(styles.sidebarOverlayCloseButtonContainer, 'mr-2')}>
<IconButton
data-testid="sidebar-hamburger"
className="color-fg-muted"
variant="invisible"
icon={ThreeBarsIcon}
aria-label="Open Sidebar"
onClick={openSidebar}
/>
<Dialog
isOpen={isSidebarOpen}
onDismiss={closeSidebar}
aria-labelledby="menu-title"
sx={{
position: 'fixed',
top: '0',
left: '0',
marginTop: '0',
maxHeight: '100vh',
width: 'auto !important',
transform: 'none',
borderRadius: '0',
borderRight: '1px solid var(--color-border-default)',
}}
>
<Dialog.Header
style={{ paddingTop: '0px', background: 'none' }}
id="sidebar-overlay-header"
sx={{ display: 'block' }}
>
<AllProductsLink />
{error === '404' ||
!currentProduct ||
isSearchResultsPage ||
!currentProductTree ? null : (
<div className="mt-3">
<Link
data-testid="sidebar-product-dialog"
href={currentProductTree.href}
className="d-block pl-1 mb-2 h3 color-fg-default no-underline"
>
{productTitle}
</Link>
</div>
)}
{isRestPage && <ApiVersionPicker />}
</Dialog.Header>
<SidebarNav variant="overlay" />
</Dialog>
</div>
<div className="mr-auto width-full" data-search="breadcrumbs">
<Breadcrumbs inHeader={true} />
</div>
</div>
)}
</header>
</div>
</>
Expand Down
2 changes: 1 addition & 1 deletion components/page-header/HeaderNotifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const HeaderNotifications = () => {
className={cx(
'flash flash-banner',
styles.container,
'text-center f5 color-fg-default py-4 px-6',
'text-center f5 color-fg-default py-4 px-6 z-1',
type === NotificationType.TRANSLATION && 'color-bg-accent',
type === NotificationType.RELEASE && 'color-bg-accent',
type === NotificationType.EARLY_ACCESS && 'color-bg-danger',
Expand Down
2 changes: 1 addition & 1 deletion components/page-header/LanguagePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const LanguagePicker = ({ mediumOrLower }: Props) => {
}}
buttonBorder={mediumOrLower}
dataTestId="default-language"
ariaLabel="Select language"
ariaLabel={`Select language: current language is ${selectedLang.name}`}
alignment={mediumOrLower ? 'start' : 'end'}
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion components/page-header/VersionPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const VersionPicker = ({ mediumOrLower }: Props) => {
pickerLabel="Version"
dataTestId="field"
buttonBorder={mediumOrLower}
ariaLabel="Select GitHub product version"
ariaLabel={`Select GitHub product version: current version is ${currentVersion}`}
renderItem={(item) => {
return (
<div data-testid="version-picker-item" className={cx(styles.itemsWidth)}>
Expand Down
2 changes: 2 additions & 0 deletions components/rest/RestCodeSamples.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
background-color: transparent;
padding: 8px 8px 16px;
white-space: pre;
max-width: 90vw;

@include breakpoint(lg) {
max-width: 40vw;
}
Expand Down
6 changes: 3 additions & 3 deletions components/sidebar/AllProductsLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ export const AllProductsLink = () => {
const currentVersionPathSegment = currentVersion === DEFAULT_VERSION ? '' : `/${currentVersion}`

return (
<li className="mt-3">
<div className="mt-3">
<Link
href={`/${router.locale}${currentVersionPathSegment}`}
className="f6 pl-4 pr-5 ml-n1 pb-1 color-fg-default"
className="f6 pl-2 pr-5 ml-n1 pb-1 Link--primary color-fg-default"
>
<ArrowLeftIcon size="small" className="mr-1" />
All products
</Link>
</li>
</div>
)
}
Loading

0 comments on commit e5370a5

Please sign in to comment.