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 */}
- ( - - )} - open={isMenuOpen} - onOpen={openMenuOverlay} - onClose={closeMenuOverlay} - aria-labelledby="menu-title" - > -
- - {t('menu')} - - - - - - - - {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()