1
0
mirror of synced 2026-01-06 06:02:35 -05:00

Merge pull request #22536 from github/repo-sync

repo sync
This commit is contained in:
Octomerger Bot
2022-12-07 15:34:45 -08:00
committed by GitHub
12 changed files with 222 additions and 132 deletions

View File

@@ -22,16 +22,15 @@ import styles from './Header.module.scss'
export const Header = () => {
const router = useRouter()
const { error } = useMainContext()
const { allVersions } = useMainContext()
const { currentProduct, allVersions } = useMainContext()
const { currentVersion } = useVersion()
const { t } = useTranslation(['header', 'homepage'])
const isRestPage = currentProduct && currentProduct.id === 'rest'
const [isMenuOpen, setIsMenuOpen] = useState(
router.pathname !== '/' && router.query.query && true
)
const [scroll, setScroll] = useState(false)
const { hasAccount } = useHasAccount()
const signupCTAVisible =
hasAccount === false && // don't show if `null`
(currentVersion === DEFAULT_VERSION || currentVersion === 'enterprise-cloud@latest')
@@ -92,8 +91,8 @@ export const Header = () => {
<Breadcrumbs />
</div>
<div className="d-flex flex-items-center">
<VersionPicker />
<LanguagePicker />
<VersionPicker variant="header" />
<LanguagePicker variant="header" />
{signupCTAVisible && (
<a
@@ -161,7 +160,7 @@ export const Header = () => {
<div className="border-top my-2" />
<LanguagePicker variant="inline" />
{allVersions[currentVersion].apiVersions.length > 0 && (
{isRestPage && allVersions[currentVersion].apiVersions.length > 0 && (
<ApiVersionPicker variant="inline" />
)}

View File

@@ -2,8 +2,8 @@ import { useRouter } from 'next/router'
import Cookies from 'js-cookie'
import { useLanguages } from 'components/context/LanguagesContext'
import { Picker } from 'components/ui/Picker'
import { useTranslation } from 'components/hooks/useTranslation'
import { Picker } from 'components/ui/Picker'
import { USER_LANGUAGE_COOKIE_NAME } from '../../lib/constants.js'
function rememberPreferredLanguage(value: string) {
@@ -31,7 +31,7 @@ function rememberPreferredLanguage(value: string) {
}
type Props = {
variant?: 'inline'
variant: 'inline' | 'header'
}
export const LanguagePicker = ({ variant }: Props) => {
@@ -61,13 +61,20 @@ export const LanguagePicker = ({ variant }: Props) => {
<Picker
variant={variant}
defaultText={t('language_picker_default_text')}
options={langs.map((lang) => ({
items={langs.map((lang) => ({
text: lang.nativeName || lang.name,
selected: lang === selectedLang,
locale: lang.code,
href: `${routerPath}`,
onselect: rememberPreferredLanguage,
href: `/${lang.code}${routerPath}`,
extra: {
locale: lang.code,
},
}))}
onSelect={(item) => {
if (item.extra?.locale) rememberPreferredLanguage(item.extra.locale)
}}
dataTestId="field"
ariaLabel="Select field type"
alignment="center"
/>
</div>
)

View File

@@ -1,8 +1,9 @@
import { useRouter } from 'next/router'
import { LinkExternalIcon } from '@primer/octicons-react'
import { useMainContext } from 'components/context/MainContext'
import { Picker } from 'components/ui/Picker'
import { useTranslation } from 'components/hooks/useTranslation'
import { Picker } from 'components/ui/Picker'
export const ProductPicker = () => {
const router = useRouter()
@@ -14,12 +15,27 @@ export const ProductPicker = () => {
<Picker
variant="inline"
defaultText={t('product_picker_default_text')}
options={activeProducts.map((product) => ({
items={activeProducts.map((product) => ({
text: product.name,
selected: product.name === currentProduct?.name,
external: product.external,
href: `${product.external ? '' : `/${router.locale}`}${product.href}`,
extra: {
external: product.external,
},
}))}
alignment="end"
dataTestId="field"
ariaLabel="Select field type"
renderItem={(item) => {
return item.extra?.external ? (
<>
{item.text}
<LinkExternalIcon size="small" className="ml-1" />
</>
) : (
item.text
)
}}
/>
</div>
)

View File

@@ -1,4 +1,5 @@
import { useRouter } from 'next/router'
import { ArrowRightIcon, InfoIcon } from '@primer/octicons-react'
import { useMainContext } from 'components/context/MainContext'
import { DEFAULT_VERSION, useVersion } from 'components/hooks/useVersion'
@@ -6,7 +7,7 @@ import { useTranslation } from 'components/hooks/useTranslation'
import { Picker } from 'components/ui/Picker'
type Props = {
variant?: 'inline'
variant: 'inline' | 'header'
}
export const VersionPicker = ({ variant }: Props) => {
@@ -23,8 +24,10 @@ export const VersionPicker = ({ variant }: Props) => {
text: allVersions[permalink.pageVersion].versionTitle,
selected: currentVersion === permalink.pageVersion,
href: permalink.href,
arrow: false,
info: false,
extra: {
arrow: false,
info: false,
},
}))
const hasEnterpriseVersions = (page.permalinks || []).some((permalink) =>
@@ -35,9 +38,11 @@ export const VersionPicker = ({ variant }: Props) => {
allLinks.push({
text: t('all_enterprise_releases'),
selected: false,
arrow: true,
href: `/${router.locale}/${enterpriseServerVersions[0]}/admin/all-releases`,
info: false,
extra: {
arrow: true,
info: false,
},
})
}
@@ -47,15 +52,35 @@ export const VersionPicker = ({ variant }: Props) => {
allLinks.push({
text: t('about_versions'),
selected: false,
arrow: false,
info: true,
href: `/${router.locale}${currentVersionPathSegment}/get-started/learning-about-github/about-versions-of-github-docs`,
extra: {
arrow: false,
info: true,
},
})
}
return (
<div data-testid="version-picker">
<Picker variant={variant} defaultText={t('version_picker_default_text')} options={allLinks} />
<Picker
variant={variant}
defaultText={t('version_picker_default_text')}
items={allLinks}
alignment="end"
dataTestId="field"
ariaLabel="Select field type"
renderItem={(item) => {
return (
<div className={item.extra?.arrow || item.extra?.info ? 'f6' : undefined}>
{item.text}
{item.extra?.arrow && (
<ArrowRightIcon verticalAlign="middle" size={15} className="ml-1" />
)}
{item.extra?.info && <InfoIcon verticalAlign="middle" size={15} className="ml-1" />}
</div>
)
}}
/>
</div>
)
}

View File

@@ -1,6 +1,7 @@
import { useRouter } from 'next/router'
import cx from 'classnames'
import Cookies from 'js-cookie'
import { InfoIcon } from '@primer/octicons-react'
import { useMainContext } from 'components/context/MainContext'
import { DEFAULT_VERSION, useVersion } from 'components/hooks/useVersion'
@@ -13,7 +14,7 @@ import styles from './SidebarProduct.module.scss'
const API_VERSION_SUFFIX = ' (latest)'
type Props = {
variant?: 'inline'
variant: 'inline' | 'header'
width?: number
}
@@ -67,19 +68,23 @@ export const ApiVersionPicker = ({ variant, width }: Props) => {
text: dateDisplayText,
selected: router.query.apiVersion === date,
href: itemLink,
info: false,
onselect: rememberApiVersion,
extra: {
info: false,
currentDate,
},
}
})
apiVersionLinks.push({
text: t('rest.versioning.about_versions'),
selected: false,
info: true,
href: `/${router.locale}${
currentVersion === DEFAULT_VERSION ? '' : `/${currentVersion}`
}/rest/overview/api-versions`,
onselect: rememberApiVersion,
extra: {
info: true,
currentDate,
},
})
// This only shows the REST Version picker if it's calendar date versioned
@@ -99,9 +104,26 @@ export const ApiVersionPicker = ({ variant, width }: Props) => {
<div data-testid="api-version-picker" className="width-full">
<Picker
variant={variant}
apiVersion={true}
defaultText={currentDateDisplayText}
options={apiVersionLinks}
items={apiVersionLinks}
pickerLabel="Version"
alignment="center"
buttonBorder={true}
dataTestId="version"
ariaLabel="Select API Version"
onSelect={(item) => {
if (item.extra?.currentDate) rememberApiVersion(item.extra.currentDate)
}}
renderItem={(item) => {
return item.extra?.info ? (
<div className="f6">
{item.text}
<InfoIcon verticalAlign="middle" size={15} className="ml-1" />
</div>
) : (
item.text
)
}}
/>
</div>
</div>

View File

@@ -109,7 +109,7 @@ export const SidebarProduct = () => {
)
return (
<>
<ApiVersionPicker width={sidebarWidth} />
<ApiVersionPicker width={sidebarWidth} variant="header" />
<li className="my-3">
<ul className="list-style-none">
{conceptualPages.map((childPage, i) => {

View File

@@ -0,0 +1,33 @@
import { ReactNode } from 'react'
import { ActionList } from '@primer/react'
import { PickerItem } from './Picker'
import { Link } from 'components/Link'
export const Fields = (fieldProps: {
open: boolean
setOpen: React.Dispatch<React.SetStateAction<boolean>>
items: PickerItem[]
onSelect?: (item: PickerItem) => void
renderItem?: (item: PickerItem) => ReactNode | string
}) => {
const { open, setOpen, items, onSelect, renderItem } = fieldProps
return (
<ActionList selectionVariant="single">
{items.map((item) => (
<ActionList.LinkItem
as={Link}
key={item.text}
href={item.href}
onClick={() => {
if (onSelect) onSelect(item)
setOpen(!open)
}}
>
{renderItem ? renderItem(item) : item.text}
</ActionList.LinkItem>
))}
</ActionList>
)
}

View File

@@ -1,105 +1,90 @@
import React, { useState } from 'react'
import { ActionList, ActionMenu, Box, Details, Text, useDetails } from '@primer/react'
import { ArrowRightIcon, ChevronDownIcon, InfoIcon, LinkExternalIcon } from '@primer/octicons-react'
import React, { ReactNode, useState } from 'react'
import cx from 'classnames'
import { ActionMenu, Box, Details, Text, useDetails } from '@primer/react'
import { ChevronDownIcon } from '@primer/octicons-react'
import { AnchorAlignment } from '@primer/behaviors'
import { Link } from 'components/Link'
import { Fields } from './Fields'
export type PickerOptionsTypeT = {
text: string
href: string
locale?: string
external?: boolean
arrow?: boolean
info?: boolean
selected?: boolean
onselect?: Function | void
}
export type PickerPropsT = {
variant?: 'inline'
apiVersion?: boolean
interface Props {
variant: 'inline' | 'header'
items: PickerItem[]
onSelect?: (item: PickerItem) => void
buttonBorder?: boolean
pickerLabel?: string
dataTestId: string
defaultText: string
options: Array<PickerOptionsTypeT>
ariaLabel: string
alignment: AnchorAlignment
renderItem?: (item: PickerItem) => ReactNode | string
}
export function Picker({ variant, apiVersion, defaultText, options }: PickerPropsT) {
export interface PickerItem {
href: string
text: string
selected: boolean
extra?: {
[key: string]: any
}
}
export const Picker = ({
variant,
items,
ariaLabel,
pickerLabel,
dataTestId,
defaultText,
onSelect,
buttonBorder,
alignment,
renderItem,
}: Props) => {
const [open, setOpen] = useState(false)
const { getDetailsProps } = useDetails({ closeOnOutsideClick: true })
const selectedOption = options.find((opt) => opt.selected === true)
const selectedOption = items.find((item) => item.selected === true)
function getFields() {
return (
<ActionList selectionVariant="single">
{options.map((option) => (
<ActionList.LinkItem
as={Link}
className={option.arrow || option.info ? 'f6' : ''}
locale={option.locale}
key={option.text}
href={option.href}
onClick={() => {
if (option.onselect) {
if (apiVersion) {
option.onselect(option.text)
} else {
option.onselect(option.locale)
}
}
setOpen(!open)
}}
>
{option.text}
{option.external && <LinkExternalIcon size="small" className="ml-1" />}
{option.info && <InfoIcon verticalAlign="middle" size={15} className="ml-1" />}
{option.arrow && <ArrowRightIcon verticalAlign="middle" size={15} className="ml-1" />}
</ActionList.LinkItem>
))}
</ActionList>
)
}
function getInlinePicker() {
return (
<Details {...getDetailsProps()} className={cx('position-relative details-reset', 'd-block')}>
<summary
className="d-block btn btn-invisible color-fg-default"
aria-haspopup="true"
aria-label={selectedOption?.text || defaultText}
>
<div className="d-flex flex-items-center flex-justify-between">
<Text>{selectedOption?.text || defaultText}</Text>
<ChevronDownIcon size={24} className="arrow ml-md-1" />
</div>
</summary>
<Box>
<ul>{getFields()}</ul>
</Box>
</Details>
)
}
return (
<React.Fragment>
{variant === 'inline' ? (
getInlinePicker()
) : (
<ActionMenu open={open} onOpenChange={setOpen}>
<ActionMenu.Button
aria-label={apiVersion ? `Select API version` : `Select field type`}
variant={apiVersion ? 'default' : 'invisible'}
sx={{ color: `var(--color-fg-default)`, width: '100%' }}
>
<span style={{ fontWeight: 'normal' }}>{`${apiVersion ? `Version: ` : ''}`}</span>
<span data-testid={apiVersion ? `version` : `field`}>
{selectedOption?.text || defaultText}
</span>
</ActionMenu.Button>
<ActionMenu.Overlay width="auto" align={apiVersion ? 'center' : 'end'}>
{getFields()}
</ActionMenu.Overlay>
</ActionMenu>
)}
</React.Fragment>
return variant === 'inline' ? (
<Details {...getDetailsProps()} className={cx('position-relative details-reset', 'd-block')}>
<summary
className="d-block btn btn-invisible color-fg-default"
aria-haspopup="true"
aria-label={selectedOption?.text || defaultText}
>
<div className="d-flex flex-items-center flex-justify-between">
<Text>{selectedOption?.text || defaultText}</Text>
<ChevronDownIcon size={24} className="arrow ml-md-1" />
</div>
</summary>
<Box>
<Fields
open={open}
setOpen={setOpen}
items={items}
onSelect={onSelect}
renderItem={renderItem}
/>
</Box>
</Details>
) : (
<ActionMenu open={open} onOpenChange={setOpen}>
<ActionMenu.Button
aria-label={ariaLabel}
variant={buttonBorder ? 'default' : 'invisible'}
sx={{ color: `var(--color-fg-default)`, width: '100%' }}
>
{pickerLabel && <span style={{ fontWeight: 'normal' }}>{`${pickerLabel}: `}</span>}
<span data-testid={dataTestId}>{selectedOption?.text || defaultText}</span>
</ActionMenu.Button>
<ActionMenu.Overlay width="auto" align={alignment}>
<Fields
open={open}
setOpen={setOpen}
items={items}
onSelect={onSelect}
renderItem={renderItem}
/>
</ActionMenu.Overlay>
</ActionMenu>
)
}

View File

@@ -1 +1,2 @@
export { Picker } from './Picker'
export { Fields } from './Fields'

13
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"dependencies": {
"@elastic/elasticsearch": "7.11.0",
"@github/failbot": "0.8.0",
"@primer/behaviors": "^1.3.1",
"@primer/css": "^20.2.4",
"@primer/octicons": "17.7.0",
"@primer/octicons-react": "17.7.0",
@@ -4051,9 +4052,9 @@
}
},
"node_modules/@primer/behaviors": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.1.3.tgz",
"integrity": "sha512-WpCcjAkXG7Lv3ZbaCUgASWKHnCi/pmuSEiyTmHHb6f5xhwk1mliixNL5ZZHtDN6RCcT3VnXUsyek4GopG2lbZQ=="
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.3.1.tgz",
"integrity": "sha512-aMRDUQ350lk0FxtL5gJWPFHHOSSzDbJ6uNJVIt8XSqiGe1pxuW5mVVfrEp1uvzZ0pCHkCdm9fycjnfOeMeIrOQ=="
},
"node_modules/@primer/css": {
"version": "20.2.4",
@@ -23127,9 +23128,9 @@
}
},
"@primer/behaviors": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.1.3.tgz",
"integrity": "sha512-WpCcjAkXG7Lv3ZbaCUgASWKHnCi/pmuSEiyTmHHb6f5xhwk1mliixNL5ZZHtDN6RCcT3VnXUsyek4GopG2lbZQ=="
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.3.1.tgz",
"integrity": "sha512-aMRDUQ350lk0FxtL5gJWPFHHOSSzDbJ6uNJVIt8XSqiGe1pxuW5mVVfrEp1uvzZ0pCHkCdm9fycjnfOeMeIrOQ=="
},
"@primer/css": {
"version": "20.2.4",

View File

@@ -11,6 +11,7 @@
"dependencies": {
"@elastic/elasticsearch": "7.11.0",
"@github/failbot": "0.8.0",
"@primer/behaviors": "^1.3.1",
"@primer/css": "^20.2.4",
"@primer/octicons": "17.7.0",
"@primer/octicons-react": "17.7.0",

View File

@@ -11,7 +11,7 @@ describe('header', () => {
const $ = await getDOM(
'/en/get-started/importing-your-projects-to-github/importing-source-code-to-github/about-github-importer'
)
const getStarted = $('div ul ul li a[href="/en/get-started"]')
const getStarted = $('details div li a[href="/en/get-started"]')
expect(getStarted.length).toBe(1)
expect(getStarted.text().trim()).toBe('Get started')