diff --git a/components/page-header/Header.tsx b/components/page-header/Header.tsx
index 35dbfe0667..ebee32c4ed 100644
--- a/components/page-header/Header.tsx
+++ b/components/page-header/Header.tsx
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import cx from 'classnames'
import { useRouter } from 'next/router'
-import { AnchoredOverlay, Button, Dialog, IconButton } from '@primer/react'
+import { ActionList, ActionMenu, Dialog, IconButton } from '@primer/react'
import {
KebabHorizontalIcon,
LinkExternalIcon,
@@ -30,16 +30,13 @@ import styles from './Header.module.scss'
export const Header = () => {
const router = useRouter()
const { error } = useMainContext()
- const { isHomepageVersion, currentProduct, allVersions } = 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 [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])
@@ -230,15 +227,12 @@ export const Header = () => {
{/* The ... navigation menu at medium and smaller widths */}
-
(
-
+
+ {
},
}
}
+ />
+
+
+ {/* Mobile Menu at XS browser width */}
+
- {}
-
- )}
- open={isMenuOpen}
- onOpen={openMenuOverlay}
- onClose={closeMenuOverlay}
- aria-labelledby="menu-title"
- >
-
-
-
-
-
-
-
-
- {isRestPage && allVersions[currentVersion].apiVersions.length > 0 && (
-
-
-
- )}
+
+
+
+
+ {signupCTAVisible && (
+ <>
+
+
+ {t`sign_up_cta`}
+
+
+ >
+ )}{' '}
+
+
+
{signupCTAVisible && (
{t`sign_up_cta`}
- )}
-
-
+ )}{' '}
+
+
diff --git a/components/page-header/LanguagePicker.tsx b/components/page-header/LanguagePicker.tsx
index 6000fae2ef..3d73c6e2cf 100644
--- a/components/page-header/LanguagePicker.tsx
+++ b/components/page-header/LanguagePicker.tsx
@@ -1,16 +1,17 @@
import { useRouter } from 'next/router'
-import { GlobeIcon } from '@primer/octicons-react'
+import { GlobeIcon, KebabHorizontalIcon } from '@primer/octicons-react'
import { useLanguages } from 'components/context/LanguagesContext'
import { useTranslation } from 'components/hooks/useTranslation'
import { useUserLanguage } from 'components/hooks/useUserLanguage'
-import { Picker } from 'src/tools/components/Picker'
+import { ActionList, ActionMenu, IconButton, Link } from '@primer/react'
type Props = {
+ xs?: boolean
mediumOrLower?: boolean
}
-export const LanguagePicker = ({ mediumOrLower }: Props) => {
+export const LanguagePicker = ({ xs, mediumOrLower }: Props) => {
const router = useRouter()
const { languages } = useLanguages()
const { setUserLanguageCookie } = useUserLanguage()
@@ -37,39 +38,80 @@ export const LanguagePicker = ({ mediumOrLower }: Props) => {
// in a "denormalized" way.
const routerPath = router.asPath.split('#')[0]
- return (
-
-
({
- text: lang.nativeName || lang.name,
- selected: lang === selectedLang,
- href: `/${lang.code}${routerPath}`,
- extra: {
- locale: lang.code,
- },
- }))}
- pickerLabel={mediumOrLower ? 'Language' : ''}
- iconButton={mediumOrLower ? undefined : GlobeIcon}
- onSelect={(item) => {
- if (item.extra?.locale) {
- try {
- setUserLanguageCookie(item.extra.locale)
- } catch (err) {
- // You can never be too careful because setting a cookie
- // can fail. For example, some browser
- // extensions disallow all setting of cookies and attempts
- // at the `document.cookie` setter could throw. Just swallow
- // and move on.
- console.warn('Unable to set preferred language cookie', err)
- }
+ // languageList is specifically 's which are reused
+ // for menus that behave differently at the breakpoints.
+ const languageList = langs.map((lang) => (
+ {
+ if (lang.code) {
+ try {
+ setUserLanguageCookie(lang.code)
+ } catch (err) {
+ // You can never be too careful because setting a cookie
+ // can fail. For example, some browser
+ // extensions disallow all setting of cookies and attempts
+ // at the `document.cookie` setter could throw. Just swallow
+ // and move on.
+ console.warn('Unable to set preferred language cookie', err)
}
- }}
- buttonBorder={mediumOrLower}
- dataTestId="default-language"
- ariaLabel={`Select language: current language is ${selectedLang.name}`}
- alignment={mediumOrLower ? 'start' : 'end'}
- />
+ }
+ }}
+ >
+ {lang.nativeName || lang.name}
+
+ ))
+
+ // At large breakpoints, we return the full with just the languages,
+ // at smaller breakpoints, we return just the with its items so that
+ // the component can place it inside its own with multiple
+ // groups, language being just one of those groups.
+ return (
+
+ {xs ? (
+ <>
+ {/* XS Mobile Menu */}
+
+
+
+ {t('language_picker_label') + '\n'}
+ {selectedLang.name}
+
+
+
+ {languageList}
+
+
+ >
+ ) : mediumOrLower ? (
+
+ {languageList}
+
+ ) : (
+
+
+
+
+
+ {languageList}
+
+
+ )}
)
}
diff --git a/components/page-header/VersionPicker.tsx b/components/page-header/VersionPicker.tsx
index 5b6ca838b4..41dbfd64b8 100644
--- a/components/page-header/VersionPicker.tsx
+++ b/components/page-header/VersionPicker.tsx
@@ -9,10 +9,10 @@ import { Picker } from 'src/tools/components/Picker'
import styles from './VersionPicker.module.scss'
type Props = {
- mediumOrLower?: boolean
+ xs?: boolean
}
-export const VersionPicker = ({ mediumOrLower }: Props) => {
+export const VersionPicker = ({ xs }: Props) => {
const router = useRouter()
const { currentVersion } = useVersion()
const { allVersions, page, enterpriseServerVersions } = useMainContext()
@@ -81,14 +81,14 @@ export const VersionPicker = ({ mediumOrLower }: Props) => {
}
return (
-
+
{
return (
diff --git a/data/ui.yml b/data/ui.yml
index 682f66621e..6218c315fc 100644
--- a/data/ui.yml
+++ b/data/ui.yml
@@ -17,7 +17,7 @@ header:
sign_up_cta: Sign up
menu: Menu
picker:
- language_picker_default_text: Choose a language
+ language_picker_label: Language
product_picker_default_text: All products
version_picker_default_text: Choose a version
release_notes:
diff --git a/src/rest/components/ApiVersionPicker.tsx b/src/rest/components/ApiVersionPicker.tsx
index 51809d8710..029cb07132 100644
--- a/src/rest/components/ApiVersionPicker.tsx
+++ b/src/rest/components/ApiVersionPicker.tsx
@@ -79,11 +79,11 @@ export const ApiVersionPicker = () => {
// This only shows the REST Version picker if it's calendar date versioned
return allVersions[currentVersion].apiVersions.length > 0 ? (
-
+
void
buttonBorder?: boolean
pickerLabel?: string
@@ -16,6 +14,7 @@ interface Props {
defaultText: string
ariaLabel: string
alignment: AnchorAlignment
+ descriptionFontSize?: number
renderItem?: (item: PickerItem) => ReactNode | string
}
@@ -31,39 +30,38 @@ export interface PickerItem {
export const Picker = ({
items,
- iconButton,
ariaLabel,
pickerLabel,
+ buttonBorder,
dataTestId,
defaultText,
onSelect,
- buttonBorder,
alignment,
+ descriptionFontSize,
renderItem,
}: Props) => {
const [open, setOpen] = useState(false)
const selectedOption = items.find((item) => item.selected === true)
return (
- {iconButton ? (
-
-
-
- ) : (
-
+ {pickerLabel && {`${pickerLabel}`} }
+
- {pickerLabel && {`${pickerLabel}: `} }
- {selectedOption?.text || defaultText}
-
- )}
+ {selectedOption?.text || defaultText}
+
+
{
// language picker is in mobile menu
await page.getByTestId('mobile-menu').click()
- await page.getByRole('button', { name: 'Select language: current language is English' }).click()
+ await page.getByTestId('language-picker')
await expect(page.getByRole('menuitemradio', { name: 'English' })).toBeVisible()
// sign up button is in mobile menu
@@ -346,7 +346,41 @@ test.describe('test nav at different viewports', () => {
test('small viewports - 544-767', async ({ page }) => {
page.setViewportSize({
- width: 500,
+ width: 555,
+ height: 700,
+ })
+ await page.goto('/get-started/foo/bar')
+
+ // header sign-up button is not visible
+ await expect(page.getByTestId('header-signup')).not.toBeVisible()
+
+ // language picker is not visible
+ await expect(page.getByTestId('language-picker')).not.toBeVisible()
+
+ // version picker is visible
+ await expect(
+ page.getByRole('button', {
+ name: 'Select GitHub product version: current version is free-pro-team@latest',
+ }),
+ ).toBeVisible()
+
+ // language picker is in mobile menu
+ await page.getByTestId('mobile-menu').click()
+ await page.getByTestId('language-picker')
+ await expect(page.getByRole('menuitemradio', { name: 'English' })).toBeVisible()
+
+ // sign up button is in mobile menu
+ await expect(page.getByTestId('mobile-signup')).toBeVisible()
+
+ // hamburger button for sidebar overlay is visible
+ await expect(page.getByTestId('sidebar-hamburger')).toBeVisible()
+ await page.getByTestId('sidebar-hamburger').click()
+ await expect(page.getByTestId('sidebar-product-dialog')).toBeVisible()
+ })
+
+ test('x-small viewports - 0-544', async ({ page }) => {
+ page.setViewportSize({
+ width: 345,
height: 700,
})
await page.goto('/get-started/foo/bar')
@@ -367,13 +401,17 @@ test.describe('test nav at different viewports', () => {
// version picker is in mobile menu
await expect(page.getByTestId('version-picker')).not.toBeVisible()
await page.getByTestId('mobile-menu').click()
- await expect(page.getByTestId('open-mobile-menu').getByTestId('version-picker')).toBeVisible()
+ await expect(
+ page.getByTestId('open-xs-mobile-menu').getByTestId('version-picker'),
+ ).toBeVisible()
// language picker is in mobile menu
- await expect(page.getByTestId('open-mobile-menu').getByTestId('language-picker')).toBeVisible()
+ await expect(
+ page.getByTestId('open-xs-mobile-menu').getByTestId('language-picker'),
+ ).toBeVisible()
// sign up button is in mobile menu
- await expect(page.getByTestId('open-mobile-menu').getByTestId('version-picker')).toBeVisible()
+ await expect(page.getByTestId('xs-mobile-signup')).toBeVisible()
// hamburger button for sidebar overlay is visible
await expect(page.getByTestId('sidebar-hamburger')).toBeVisible()