1
0
mirror of synced 2025-12-19 18:10:59 -05:00

Update docs to use Primer React 37 (#57265)

Co-authored-by: Evan Bonsignori <evanabonsignori@gmail.com>
This commit is contained in:
Mardav Wala
2025-08-28 16:29:07 -04:00
committed by GitHub
parent 2c3fdf1990
commit 9b0d2beb9a
34 changed files with 353 additions and 1137 deletions

View File

@@ -15,6 +15,9 @@ const { data } = frontmatter(fs.readFileSync(homepage, 'utf8'))
const productIds = data.children
export default {
// Transpile @primer/react so Next's webpack can process its CSS and other assets
// This ensures CSS in node_modules/@primer/react is handled by the app's loaders.
transpilePackages: ['@primer/react'],
// speed up production `next build` by ignoring typechecking during that step of build.
// type-checking still occurs in the Dockerfile build
typescript: {

1019
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -166,7 +166,7 @@
"@primer/live-region-element": "^0.7.2",
"@primer/octicons": "^19.15.5",
"@primer/octicons-react": "^19.14.0",
"@primer/react": "36.27.0",
"@primer/react": "^37.31.0",
"accept-language-parser": "^1.5.0",
"ajv": "^8.17.1",
"ajv-errors": "^3.0.0",
@@ -177,6 +177,7 @@
"cheerio": "^1.0.0-rc.12",
"cheerio-to-text": "0.2.4",
"classnames": "^2.5.1",
"clsx": "^2.1.1",
"connect-timeout": "1.9.1",
"cookie-parser": "^1.4.7",
"cuss": "2.2.0",
@@ -219,8 +220,8 @@
"ora": "^8.0.1",
"parse5": "7.1.2",
"quick-lru": "7.0.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^10.1.0",
"rehype-highlight": "^7.0.2",
"rehype-raw": "^7.0.0",

View File

@@ -526,7 +526,7 @@ test.describe('test nav at different viewports', () => {
// 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()
await expect(page.locator('[role="dialog"][class*="Header_dialog"]')).toBeVisible()
})
test('medium viewports - 768-1011', async ({ page }) => {
@@ -555,7 +555,7 @@ test.describe('test nav at different viewports', () => {
// 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()
await expect(page.locator('[role="dialog"][class*="Header_dialog"]')).toBeVisible()
})
test('small viewports - 544-767', async ({ page }) => {
@@ -588,7 +588,7 @@ test.describe('test nav at different viewports', () => {
// 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()
await expect(page.locator('[role="dialog"][class*="Header_dialog"]')).toBeVisible()
})
test('x-small viewports - 0-544', async ({ page }) => {
@@ -627,7 +627,7 @@ test.describe('test nav at different viewports', () => {
// 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()
await expect(page.locator('[role="dialog"][class*="Header_dialog"]')).toBeVisible()
})
test('do a search when the viewport is x-small', async ({ page }) => {

View File

@@ -24,7 +24,7 @@ describe('sidebar', () => {
const $: cheerio.Root = await getDOM('/get-started/start-your-journey/hello-world')
expect(
$(
'[data-testid=sidebar] [data-testid=product-sidebar] a[aria-current="page"] div span',
'[data-testid=sidebar] [data-testid=product-sidebar] a[aria-current="page"] span span',
).text(),
).toBe('Hello World')
})
@@ -35,7 +35,7 @@ describe('sidebar', () => {
// from its regular title.
expect(
$(
'[data-testid=sidebar] [data-testid=product-sidebar] a[href*="/get-started/foo/bar"] div span',
'[data-testid=sidebar] [data-testid=product-sidebar] a[href*="/get-started/foo/bar"] span span',
).text(),
).toBe('Bar')
})

View File

@@ -1,5 +1,6 @@
@import "@primer/css/support/variables/layout.scss";
@import "@primer/css/support/mixins/layout.scss";
@import "src/frame/stylesheets/breakpoint-xxl.scss";
.header {
display: unset;
@@ -51,3 +52,13 @@
visibility: visible !important;
}
}
.dialog {
@include breakpoint-xxl {
display: none;
}
[class*="prc-Dialog-Body"] {
padding: 0 !important;
}
}

View File

@@ -13,7 +13,6 @@ import { useTranslation } from '@/languages/components/useTranslation'
import { Breadcrumbs } from '@/frame/components/page-header/Breadcrumbs'
import { VersionPicker } from '@/versions/components/VersionPicker'
import { SidebarNav } from '@/frame/components/sidebar/SidebarNav'
import { AllProductsLink } from '@/frame/components/sidebar/AllProductsLink'
import { SearchBarButton } from '@/search/components/input/SearchBarButton'
import { HeaderSearchAndWidgets } from './HeaderSearchAndWidgets'
import { useInnerWindowWidth } from './hooks/useInnerWindowWidth'
@@ -34,8 +33,8 @@ export const Header = () => {
const { params, updateParams } = useMultiQueryParams()
const [scroll, setScroll] = useState(false)
const [isSidebarOpen, setIsSidebarOpen] = useState(false)
const openSidebar = useCallback(() => setIsSidebarOpen(true), [isSidebarOpen])
const closeSidebar = useCallback(() => setIsSidebarOpen(false), [isSidebarOpen])
const openSidebar = useCallback(() => setIsSidebarOpen(true), [])
const closeSidebar = useCallback(() => setIsSidebarOpen(false), [])
const isMounted = useRef(false)
const menuButtonRef = useRef<HTMLButtonElement>(null)
const { asPath } = useRouter()
@@ -204,45 +203,23 @@ export const Header = () => {
onClick={openSidebar}
ref={returnFocusRef}
/>
<Dialog
returnFocusRef={returnFocusRef}
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(--borderColor-default, var(--color-border-default))',
}}
>
<Dialog.Header
style={{ paddingTop: '0px', background: 'none' }}
id="sidebar-overlay-header"
sx={{ display: 'block' }}
{isSidebarOpen && (
<Dialog
returnFocusRef={returnFocusRef}
onClose={closeSidebar}
className={cx(styles.dialog, 'd-xxl-none')}
position="left"
title={
error === '404' || !currentProduct || isSearchResultsPage
? null
: currentProductName || currentProduct.name
}
subtitle={isRestPage && <ApiVersionPicker />}
width="medium"
>
<AllProductsLink />
{error === '404' || !currentProduct || isSearchResultsPage ? null : (
<h2 className="mt-3">
<Link
data-testid="sidebar-product-dialog"
href={currentProduct.href}
className="d-block pl-1 mb-2 h3 color-fg-default no-underline"
>
{currentProductName || currentProduct.name}
</Link>
</h2>
)}
{isRestPage && <ApiVersionPicker />}
</Dialog.Header>
<SidebarNav variant="overlay" />
</Dialog>
<SidebarNav variant="overlay" />
</Dialog>
)}
</div>
)}
<div className="mr-auto width-full" data-search="breadcrumbs">

View File

@@ -33,7 +33,9 @@ export const SidebarNav = ({ variant = 'full' }: Props) => {
<div
data-container="nav"
className={cx(variant === 'full' ? 'position-sticky d-none border-right d-xxl-block' : '')}
style={{ width: 326, height: 'calc(100vh - 65px)', top: '65px' }}
style={
variant === 'full' ? { width: 326, height: 'calc(100vh - 65px)', top: '65px' } : undefined
}
>
<nav
aria-labelledby="allproducts-menu"
@@ -62,10 +64,16 @@ export const SidebarNav = ({ variant = 'full' }: Props) => {
)}
<div
className={cx(
variant === 'overlay' ? 'd-xxl-none' : 'border-right d-none d-xxl-block',
'bg-primary overflow-y-auto flex-shrink-0',
variant === 'overlay'
? 'width-full d-xxl-none'
: 'border-right d-none d-xxl-block overflow-y-auto',
'bg-primary flex-shrink-0',
)}
style={{ width: 326, height: 'calc(100vh - 175px)', paddingBottom: sidebarPaddingBottom }}
style={
variant === 'overlay'
? { paddingBottom: sidebarPaddingBottom }
: { width: 326, height: 'calc(100vh - 175px)', paddingBottom: sidebarPaddingBottom }
}
role="region"
aria-label="Page navigation content"
>

View File

@@ -0,0 +1,12 @@
@import "breakpoint-xxl.scss";
#__primerPortalRoot__ [class*="prc-Dialog-Backdrop"] {
/* Make sure the dialog backdrop is hidden on large screens */
display: flex;
visibility: visible;
@include breakpoint-xxl {
display: none !important;
visibility: hidden !important;
}
}

View File

@@ -11,6 +11,7 @@
@import "headings.scss";
@import "scroll-top.scss";
@import "utilities.scss";
@import "dialog-overrides.scss";
@import "src/content-render/stylesheets/index.scss";
@import "src/links/stylesheets/hover-card.scss";

View File

@@ -1,5 +1,5 @@
import path from 'path'
import { readFile } from 'fs/promises'
import fs from 'fs'
import { readCompressedJsonFileFallback } from '@/frame/lib/read-json-file'
import { getOpenApiVersion } from '@/versions/lib/all-versions'
@@ -10,7 +10,7 @@ const githubAppsData = new Map()
// Initialize the Map with the page type keys listed under `pages`
// in the config.json file.
const appsDataConfig = JSON.parse(await readFile('src/github-apps/lib/config.json', 'utf8'))
const appsDataConfig = JSON.parse(fs.readFileSync('src/github-apps/lib/config.json', 'utf8'))
for (const pageType of Object.keys(appsDataConfig.pages)) {
githubAppsData.set(pageType, new Map())
}

View File

@@ -0,0 +1,11 @@
.linkItem {
border-radius: 0;
&:hover {
border-radius: 0;
}
h3 {
color: var(--fgColor-accent, var(--color-accent-fg));
}
}

View File

@@ -1,11 +1,11 @@
import cx from 'classnames'
import dayjs from 'dayjs'
import { ActionList } from '@primer/react'
import { useTranslation } from '@/languages/components/useTranslation'
import { Link } from '@/frame/components/Link'
import { ArrowRightIcon } from '@primer/octicons-react'
import { FeaturedLink } from '@/landings/components/ProductLandingContext'
import { BumpLink } from '@/frame/components/ui/BumpLink'
import { useTranslation } from '@/languages/components/useTranslation'
import { ArrowRightIcon } from '@primer/octicons-react'
import { ActionList } from '@primer/react'
import { clsx } from 'clsx'
import dayjs from 'dayjs'
import styles from './ArticleList.module.css'
export type ArticleListPropsT = {
title?: string
@@ -29,7 +29,7 @@ export const ArticleList = ({
<>
{title && (
<div className="mb-4 d-flex flex-items-baseline">
<h2 className={cx('f4 text-semibold')}>{title}</h2>
<h2 className={clsx('f4', 'text-semibold')}>{title}</h2>
{viewAllHref && (
<Link
href={viewAllHref}
@@ -44,37 +44,26 @@ export const ArticleList = ({
</div>
)}
<ActionList as="ul" data-testid="article-list" variant="full">
<ActionList data-testid="article-list" variant="full">
{articles.map((link) => {
return (
<ActionList.Item
as="li"
<ActionList.LinkItem
as="a"
key={link.href}
className={cx('width-full border-top')}
sx={{
borderRadius: 0,
':hover': {
borderRadius: 0,
},
}}
tabIndex={undefined}
href={link.href}
data-testid="bump-link"
className={clsx(styles.linkItem, 'width-full', 'border-top', 'py-3')}
>
<BumpLink
as={Link}
href={link.href}
className="py-3"
title={
link.intro ? (
<h3 className="f4" data-testid="link-with-intro-title">
<span>{link.fullTitle ? link.fullTitle : link.title}</span>
</h3>
) : (
<span className="f4 text-bold d-block" data-testid="link-with-intro-title">
{link.fullTitle ? link.fullTitle : link.title}
</span>
)
}
>
<div>
{link.intro ? (
<h3 className="f4" data-testid="link-with-intro-title">
<span>{link.fullTitle ? link.fullTitle : link.title}</span>
</h3>
) : (
<span className="f4 text-bold d-block" data-testid="link-with-intro-title">
{link.fullTitle ? link.fullTitle : link.title}
</span>
)}
{link.intro && (
<p className="color-fg-muted mb-0 mt-1" data-testid="link-with-intro-intro">
{link.intro}
@@ -88,8 +77,8 @@ export const ArticleList = ({
{dayjs(link.date).format('MMMM DD')}
</time>
)}
</BumpLink>
</ActionList.Item>
</div>
</ActionList.LinkItem>
)
})}
</ActionList>

View File

@@ -56,7 +56,7 @@ export const CookBookArticleCard = ({
/>
)}
<div>
<h3 className="h4">
<h3 className="h4 fgColor-accent">
<Link href={url}>{title}</Link>
</h3>
<div className="fgColor-muted mb-3 mt-2">{description}</div>

View File

@@ -0,0 +1,14 @@
.linkItem {
border-radius: 0;
&:hover {
border-radius: 0;
}
a {
span {
color: var(--fgColor-accent, var(--color-accent-fg));
text-decoration: underline;
}
}
}

View File

@@ -1,9 +1,9 @@
import cx from 'classnames'
import { ActionList } from '@primer/react'
import { ProductTreeNode, useMainContext } from '@/frame/components/context/MainContext'
import { Link } from '@/frame/components/Link'
import clsx from 'clsx'
import styles from './ProductArticlesList.module.css'
export const ProductArticlesList = () => {
const { currentProductTree } = useMainContext()
@@ -35,28 +35,19 @@ const ProductTreeNodeList = ({ treeNode }: { treeNode: ProductTreeNode }) => {
<ActionList variant="full">
{treeNode.childPages.map((childNode, index) => {
return (
<ActionList.Item
as="li"
<ActionList.LinkItem
as="a"
key={childNode.href + index}
className={cx('width-full pl-0')}
sx={{
borderRadius: 0,
':hover': {
borderRadius: 0,
},
}}
tabIndex={undefined}
aria-labelledby={undefined}
href={childNode.href}
className={clsx(styles.linkItem, 'width-full', 'pl-0', 'd-block')}
>
<Link className="d-block width-full text-underline" href={childNode.href}>
{childNode.title}
{childNode.childPages.length > 0 ? (
<small className="color-fg-muted d-inline-block">
&nbsp;&bull; {childNode.childPages.length} articles
</small>
) : null}
</Link>
</ActionList.Item>
{childNode.title}
{childNode.childPages.length > 0 ? (
<small className="color-fg-muted d-inline-block">
&nbsp;&bull; {childNode.childPages.length} articles
</small>
) : null}
</ActionList.LinkItem>
)
})}
</ActionList>

View File

@@ -0,0 +1,3 @@
.sidebar * {
font-size: var(--h5-size, 14px) !important;
}

View File

@@ -7,6 +7,8 @@ import { ProductTreeNode, useMainContext } from '@/frame/components/context/Main
import { useAutomatedPageContext } from '@/automated-pipelines/components/AutomatedPageContext'
import { nonAutomatedRestPaths } from '@/rest/lib/config'
import styles from './SidebarProduct.module.css'
export const SidebarProduct = () => {
const router = useRouter()
const {
@@ -32,7 +34,7 @@ export const SidebarProduct = () => {
}
const productSection = () => (
<div className="ml-3" data-testid="product-sidebar">
<div data-testid="product-sidebar">
<NavList aria-label="Product sidebar" role="navigation">
{sidebarTree &&
sidebarTree.childPages.map((childPage) => (
@@ -50,7 +52,7 @@ export const SidebarProduct = () => {
nonAutomatedRestPaths.every((item: string) => !page.href.includes(item)),
)
return (
<div className="ml-3">
<div>
<NavList aria-label="REST sidebar overview articles" role="navigation">
{conceptualPages.map((childPage) => (
<NavListItem key={childPage.href} childPage={childPage} />
@@ -69,7 +71,7 @@ export const SidebarProduct = () => {
}
return (
<div data-testid="sidebar" style={{ overflowY: 'auto' }} className="pt-3">
<div data-testid="sidebar" className={styles.sidebar}>
{isRestPage ? restSection() : productSection()}
</div>
)
@@ -90,7 +92,7 @@ function NavListItem({ childPage }: { childPage: ProductTreeNode }) {
>
{childPage.title}
{childPage.childPages.length > 0 && (
<NavList.SubNav aria-label={`${childPage.title} submenu`} sx={{ '*': { fontSize: 1 } }}>
<NavList.SubNav aria-label={`${childPage.title} submenu`}>
{childPage.sidebarLink && (
<NavList.Item
href={childPage.sidebarLink.href}
@@ -166,7 +168,7 @@ function RestNavListItem({ category }: { category: ProductTreeNode }) {
>
{category.title}
{category.childPages.length > 0 && (
<NavList.SubNav aria-label={`${category.title} submenu`} sx={{ '*': { fontSize: 1 } }}>
<NavList.SubNav aria-label={`${category.title} submenu`}>
{category.childPages.map((childPage) => {
return (
<NavList.Item

View File

@@ -1,9 +1,9 @@
import React from 'react'
import cx from 'classnames'
import React from 'react'
import { ActionList } from '@primer/react'
import { Link } from '@/frame/components/Link'
import type { TocItem } from '@/landings/types'
import { ActionList } from '@primer/react'
type Props = {
items: Array<TocItem>
@@ -45,9 +45,13 @@ export const TableOfContents = (props: Props) => {
const { fullPath, title, childTocItems } = item
return (
<React.Fragment key={fullPath}>
<ActionList.Item className="f4 color-fg-accent d-list-item d-block width-full text-underline">
<Link href={fullPath}>{title}</Link>
</ActionList.Item>
<ActionList.LinkItem
href={fullPath}
as="a"
className="f4 color-fg-accent d-list-item d-block width-full text-underline"
>
{title}
</ActionList.LinkItem>
{(childTocItems || []).length > 0 && (
<li className="f4 color-fg-accent d-list-item d-block width-full text-underline">
<ActionList
@@ -57,12 +61,14 @@ export const TableOfContents = (props: Props) => {
>
{(childTocItems || []).filter(Boolean).map((childItem) => {
return (
<ActionList.Item
<ActionList.LinkItem
key={childItem.fullPath}
href={childItem.fullPath}
as="a"
className="f4 color-fg-accent d-list-item d-block width-full text-underline"
>
<Link href={childItem.fullPath}>{childItem.title}</Link>
</ActionList.Item>
{childItem.title}
</ActionList.LinkItem>
)
})}
</ActionList>

View File

@@ -8,7 +8,9 @@ describe('curated homepage links', () => {
test('English', async () => {
const $ = await getDOM('/en')
const $links = $('[data-testid=bump-link]')
// Update selector to find actual link elements within article list
const $links = $('[data-testid=article-list] a')
expect($links.length).toBeGreaterThanOrEqual(6)
// Check that each link is localized and includes a title and intro

View File

@@ -7,7 +7,7 @@ describe('featuredLinks', () => {
vi.setConfig({ testTimeout: 60 * 1000 })
test('non-TOC pages do not have intro links', async () => {
const $ = await getDOM('/en/get-started/start-your-journey')
const $ = await getDOM('/en/get-started/start-your-journey/hello-world')
expect($('[data-testid=article-list]')).toHaveLength(0)
})
@@ -16,19 +16,27 @@ describe('featuredLinks', () => {
const $featuredLinks = $('[data-testid=article-list] a')
expect($featuredLinks).toHaveLength(7)
expect($featuredLinks.eq(0).attr('href')).toBe('/en/get-started/start-your-journey/hello-world')
expect($featuredLinks.eq(0).children('h3').text()).toMatch('Hello World')
expect($featuredLinks.eq(0).children('p').text()).toMatch('Follow this Hello World exercise')
expect($featuredLinks.eq(0).find('[data-testid=link-with-intro-title]').text()).toMatch(
'Hello World',
)
expect($featuredLinks.eq(0).find('[data-testid=link-with-intro-intro]').text()).toMatch(
/follow.+this.+hello.+world.+exercise/i,
)
})
test('Enterprise intro links have expected values', async () => {
const $ = await getDOM('/enterprise-server@latest/get-started')
const $ = await getDOM('/en/enterprise-server@latest/get-started')
const $featuredLinks = $('[data-testid=article-list] a')
expect($featuredLinks.length).toBeGreaterThan(0)
// Fixture content expectations (CI environment)
expect($featuredLinks.eq(0).attr('href')).toBe(
`/en/enterprise-server@${enterpriseServerReleases.latestStable}/get-started/foo/bar`,
)
expect($featuredLinks.eq(0).children('h3').text()).toMatch('Bar Usually Comes After Foo')
expect($featuredLinks.eq(0).children('p').text()).toMatch(
expect($featuredLinks.eq(0).find('[data-testid=link-with-intro-title]').text()).toMatch(
'Bar Usually Comes After Foo',
)
expect($featuredLinks.eq(0).find('[data-testid=link-with-intro-intro]').text()).toMatch(
"This page doesn't really have an intro",
)
})

View File

@@ -1,10 +1,10 @@
import { useRouter } from 'next/router'
import { GlobeIcon } from '@primer/octicons-react'
import { useRouter } from 'next/router'
import { useLanguages } from '@/languages/components/LanguagesContext'
import { useTranslation } from '@/languages/components/useTranslation'
import { useUserLanguage } from '@/languages/components/useUserLanguage'
import { ActionList, ActionMenu, IconButton, Link } from '@primer/react'
import { ActionList, ActionMenu, IconButton } from '@primer/react'
type Props = {
xs?: boolean
@@ -38,16 +38,16 @@ export const LanguagePicker = ({ xs, mediumOrLower }: Props) => {
// in a "denormalized" way.
const routerPath = router.asPath.split('#')[0]
// languageList is specifically <ActionList.Item>'s which are reused
// languageList is specifically ActionList items which are reused
// for menus that behave differently at the breakpoints.
const languageList = langs.map((lang) => (
<ActionList.Item
<ActionList.LinkItem
key={`/${lang.code}${routerPath}`}
selected={lang === selectedLang}
as={Link}
as="a"
active={lang === selectedLang}
lang={lang.code}
href={`/${lang.code}${routerPath}`}
onSelect={() => {
onClick={() => {
if (lang.code) {
try {
setUserLanguageCookie(lang.code)
@@ -63,7 +63,7 @@ export const LanguagePicker = ({ xs, mediumOrLower }: Props) => {
}}
>
{lang.nativeName || lang.name}
</ActionList.Item>
</ActionList.LinkItem>
))
// At large breakpoints, we return the full <ActionMenu> with just the languages,

View File

@@ -1,4 +1,4 @@
import fs from 'fs/promises'
import fs from 'fs'
import path from 'path'
import frontmatter from '@/frame/lib/read-frontmatter'
import getApplicableVersions from '@/versions/lib/get-applicable-versions'
@@ -40,7 +40,7 @@ export interface ProductMap {
// Both internal and external products are specified in content/index.md
const homepage = path.posix.join(ROOT, 'content/index.md')
export const { data } = frontmatter(await fs.readFile(homepage, 'utf8'))
export const { data } = frontmatter(fs.readFileSync(homepage, 'utf8'))
export const productIds: string[] = data?.children || []
@@ -53,13 +53,13 @@ for (const productId of productIds) {
// Early Access may not exist in the current checkout
try {
await fs.readdir(dir)
fs.readdirSync(dir)
} catch {
continue
}
const toc = path.posix.join(dir, 'index.md')
const fileContent = await fs.readFile(toc, 'utf8')
const fileContent = fs.readFileSync(toc, 'utf8')
const { data: tocData } = frontmatter(fileContent)
if (tocData) {
const applicableVersions = getApplicableVersions(tocData.versions, toc)

View File

@@ -79,31 +79,29 @@ export const ApiVersionPicker = () => {
// This only shows the REST Version picker if it's calendar date versioned
return allVersions[currentVersion].apiVersions.length > 0 ? (
<div className="mb-3">
<div data-testid="api-version-picker">
<Picker
defaultText={currentDateDisplayText}
items={apiVersionLinks}
pickerLabel="API Version: "
alignment="start"
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 data-testid="api-version-picker">
<Picker
defaultText={currentDateDisplayText}
items={apiVersionLinks}
pickerLabel="API Version: "
alignment="start"
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>
) : null
}

View File

@@ -31,3 +31,13 @@
.responseCodeBlock {
min-height: 120px;
}
.segmentedControl {
padding: 0 !important;
margin-bottom: 0.5rem !important;
li + li {
// Same as the Primer CSS selector for segmented control
margin-top: -1px !important;
}
}

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useRef, FormEvent } from 'react'
import { FormControl, IconButton, Select, TabNav } from '@primer/react'
import { FormControl, IconButton, Select, SegmentedControl } from '@primer/react'
import { CheckIcon, CopyIcon, InfoIcon } from '@primer/octicons-react'
import { announce } from '@primer/live-region-element'
import Cookies from '@/frame/components/lib/cookies'
@@ -260,26 +260,28 @@ export function RestCodeSamples({ operation, slug, heading }: Props) {
</div>
<div className="border-top d-inline-flex flex-justify-between width-full flex-items-center pt-2">
<div className="d-inline-flex ml-2">
<TabNav aria-label={`Example language selector for ${operation.title}`}>
<SegmentedControl
className={styles.segmentedControl}
aria-label={`Example language selector for ${operation.title}`}
>
{languageSelectOptions.map((optionKey) => (
<TabNav.Link
<SegmentedControl.Button
key={optionKey}
selected={optionKey === selectedLanguage}
onClick={(e) => {
onClick={(e: React.MouseEvent) => {
e.preventDefault()
handleLanguageSelection(optionKey)
}}
onKeyDown={(event) => {
onKeyDown={(event: React.KeyboardEvent) => {
if (event.key === 'Enter') {
handleLanguageSelection(optionKey)
}
}}
href="#"
>
{t(`code_sample_options.${optionKey}`)}
</TabNav.Link>
</SegmentedControl.Button>
))}
</TabNav>
</SegmentedControl>
</div>
<div className="mr-2">
<IconButton
@@ -316,31 +318,30 @@ export function RestCodeSamples({ operation, slug, heading }: Props) {
__html: displayedExample.response.description || t('response'),
}}
></h4>
<div className="border rounded-1">
<div className="border rounded-1 pt-2">
{displayedExample.response.schema ? (
<TabNav
className="pt-2 mx-2"
<SegmentedControl
className={cx(styles.segmentedControl, 'mx-2')}
aria-label={`Example response format selector for ${operation.title}`}
>
{responseSelectOptions.map((optionKey) => (
<TabNav.Link
<SegmentedControl.Button
key={optionKey}
selected={optionKey === selectedResponse}
onClick={(e) => {
onClick={(e: React.MouseEvent) => {
e.preventDefault()
handleResponseSelection(optionKey)
}}
onKeyDown={(event) => {
onKeyDown={(event: React.KeyboardEvent) => {
if (event.key === 'Enter') {
handleResponseSelection(optionKey)
}
}}
href="#"
>
{t(`response_options.${optionKey}`)}
</TabNav.Link>
</SegmentedControl.Button>
))}
</TabNav>
</SegmentedControl>
) : null}
<div className="">
{/* Status code */}

View File

@@ -30,6 +30,7 @@ $mutedTextColor: var(--fgColor-muted, var(--color-fg-muted, #656d76));
ul,
ol {
list-style-type: decimal !important;
padding-inline-start: 2em !important;
}
}
@@ -39,7 +40,7 @@ $mutedTextColor: var(--fgColor-muted, var(--color-fg-muted, #656d76));
}
.referencesList {
padding-left: 16px !important;
margin-top: 12px !important;
}
.loadingContainer {

View File

@@ -500,9 +500,6 @@ export function AskAIResults({
const refIndex = index + referencesIndexOffset
return (
<ActionList.Item
sx={{
marginLeft: '0px',
}}
key={`reference-${index}`}
id={`search-option-reference-${index + referencesIndexOffset}`}
tabIndex={-1}

View File

@@ -111,10 +111,11 @@ $mutedTextColor: var(--fgColor-muted, var(--color-fg-muted, #656d76));
}
.viewAllSearchResults {
color: var(--color-accent-emphasis) !important;
padding-left: 32px !important;
span {
font-weight: 500 !important;
button {
span {
color: var(--color-accent-emphasis) !important;
font-weight: 500 !important;
}
}
}

View File

@@ -29,7 +29,7 @@ import {
executeGeneralSearch,
GENERAL_SEARCH_CONTEXT,
} from '../helpers/execute-search-actions'
import { Banner } from '@primer/react/drafts'
import { Banner } from '@primer/react/experimental'
import { useCombinedSearchResults } from '@/search/components/hooks/useAISearchAutocomplete'
import { AskAIResults } from './AskAIResults'
import { sendEvent, uuidv4 } from '@/events/components/events'
@@ -948,8 +948,11 @@ function renderSearchGroups(
}
}}
>
{!option.isViewAllResults && !option.isNoResultsFound && (
<ActionList.LeadingVisual aria-hidden>
{!option.isNoResultsFound && (
<ActionList.LeadingVisual
aria-hidden
sx={{ visibility: option.isViewAllResults ? 'hidden' : 'visible' }}
>
<FileIcon />
</ActionList.LeadingVisual>
)}

View File

@@ -0,0 +1,3 @@
.aggregations label {
font-weight: var(--base-text-weight-semibold, 600);
}

View File

@@ -5,6 +5,7 @@ import Link from 'next/link'
import { useTranslation } from '@/languages/components/useTranslation'
import type { SearchResultAggregations } from '@/search/types'
import styles from './Aggregations.module.scss'
type Props = {
aggregations: SearchResultAggregations
@@ -46,7 +47,7 @@ export function SearchResultsAggregations({ aggregations }: Props) {
if (aggregations.toplevel && aggregations.toplevel.length > 0) {
return (
<div>
<div className={styles.aggregations}>
<CheckboxGroup>
<CheckboxGroup.Label>
{t('filter')}{' '}

View File

@@ -1,8 +1,7 @@
import { ReactNode } from 'react'
import { ActionList } from '@primer/react'
import { ReactNode } from 'react'
import { PickerItem } from './Picker'
import { Link } from '@/frame/components/Link'
import styles from './Fields.module.scss'
@@ -21,11 +20,11 @@ export const Fields = (fieldProps: {
item.divider ? (
<ActionList.Divider key={`divider${i}`} />
) : (
<ActionList.Item
as={Link}
<ActionList.LinkItem
as="a"
key={item.text}
href={item.href}
selected={item.selected === true}
active={item.selected === true}
onSelect={() => {
if (onSelect) onSelect(item)
setOpen(!open)
@@ -45,7 +44,7 @@ export const Fields = (fieldProps: {
role={item.extra?.arrow || item.extra?.info ? 'menuitem' : 'menuitemradio'}
>
{renderItem ? renderItem(item) : item.text}
</ActionList.Item>
</ActionList.LinkItem>
),
)}
</ActionList>

View File

@@ -1,9 +1,9 @@
import fs from 'fs/promises'
import fs from 'fs'
import semver from 'semver'
import versionSatisfiesRange from './version-satisfies-range'
const rawDates = JSON.parse(await fs.readFile('src/ghes-releases/lib/enterprise-dates.json'))
const rawDates = JSON.parse(fs.readFileSync('src/ghes-releases/lib/enterprise-dates.json', 'utf8'))
// ============================================================================
// STATICALLY DEFINED VALUES