Update docs to use Primer React 37 (#57265)
Co-authored-by: Evan Bonsignori <evanabonsignori@gmail.com>
This commit is contained in:
@@ -15,6 +15,9 @@ const { data } = frontmatter(fs.readFileSync(homepage, 'utf8'))
|
|||||||
const productIds = data.children
|
const productIds = data.children
|
||||||
|
|
||||||
export default {
|
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.
|
// speed up production `next build` by ignoring typechecking during that step of build.
|
||||||
// type-checking still occurs in the Dockerfile build
|
// type-checking still occurs in the Dockerfile build
|
||||||
typescript: {
|
typescript: {
|
||||||
|
|||||||
1019
package-lock.json
generated
1019
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -166,7 +166,7 @@
|
|||||||
"@primer/live-region-element": "^0.7.2",
|
"@primer/live-region-element": "^0.7.2",
|
||||||
"@primer/octicons": "^19.15.5",
|
"@primer/octicons": "^19.15.5",
|
||||||
"@primer/octicons-react": "^19.14.0",
|
"@primer/octicons-react": "^19.14.0",
|
||||||
"@primer/react": "36.27.0",
|
"@primer/react": "^37.31.0",
|
||||||
"accept-language-parser": "^1.5.0",
|
"accept-language-parser": "^1.5.0",
|
||||||
"ajv": "^8.17.1",
|
"ajv": "^8.17.1",
|
||||||
"ajv-errors": "^3.0.0",
|
"ajv-errors": "^3.0.0",
|
||||||
@@ -177,6 +177,7 @@
|
|||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"cheerio-to-text": "0.2.4",
|
"cheerio-to-text": "0.2.4",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"connect-timeout": "1.9.1",
|
"connect-timeout": "1.9.1",
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"cuss": "2.2.0",
|
"cuss": "2.2.0",
|
||||||
@@ -219,8 +220,8 @@
|
|||||||
"ora": "^8.0.1",
|
"ora": "^8.0.1",
|
||||||
"parse5": "7.1.2",
|
"parse5": "7.1.2",
|
||||||
"quick-lru": "7.0.1",
|
"quick-lru": "7.0.1",
|
||||||
"react": "18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"rehype-highlight": "^7.0.2",
|
"rehype-highlight": "^7.0.2",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
|
|||||||
@@ -526,7 +526,7 @@ test.describe('test nav at different viewports', () => {
|
|||||||
// hamburger button for sidebar overlay is visible
|
// hamburger button for sidebar overlay is visible
|
||||||
await expect(page.getByTestId('sidebar-hamburger')).toBeVisible()
|
await expect(page.getByTestId('sidebar-hamburger')).toBeVisible()
|
||||||
await page.getByTestId('sidebar-hamburger').click()
|
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 }) => {
|
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
|
// hamburger button for sidebar overlay is visible
|
||||||
await expect(page.getByTestId('sidebar-hamburger')).toBeVisible()
|
await expect(page.getByTestId('sidebar-hamburger')).toBeVisible()
|
||||||
await page.getByTestId('sidebar-hamburger').click()
|
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 }) => {
|
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
|
// hamburger button for sidebar overlay is visible
|
||||||
await expect(page.getByTestId('sidebar-hamburger')).toBeVisible()
|
await expect(page.getByTestId('sidebar-hamburger')).toBeVisible()
|
||||||
await page.getByTestId('sidebar-hamburger').click()
|
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 }) => {
|
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
|
// hamburger button for sidebar overlay is visible
|
||||||
await expect(page.getByTestId('sidebar-hamburger')).toBeVisible()
|
await expect(page.getByTestId('sidebar-hamburger')).toBeVisible()
|
||||||
await page.getByTestId('sidebar-hamburger').click()
|
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 }) => {
|
test('do a search when the viewport is x-small', async ({ page }) => {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ describe('sidebar', () => {
|
|||||||
const $: cheerio.Root = await getDOM('/get-started/start-your-journey/hello-world')
|
const $: cheerio.Root = await getDOM('/get-started/start-your-journey/hello-world')
|
||||||
expect(
|
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(),
|
).text(),
|
||||||
).toBe('Hello World')
|
).toBe('Hello World')
|
||||||
})
|
})
|
||||||
@@ -35,7 +35,7 @@ describe('sidebar', () => {
|
|||||||
// from its regular title.
|
// from its regular title.
|
||||||
expect(
|
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(),
|
).text(),
|
||||||
).toBe('Bar')
|
).toBe('Bar')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@import "@primer/css/support/variables/layout.scss";
|
@import "@primer/css/support/variables/layout.scss";
|
||||||
@import "@primer/css/support/mixins/layout.scss";
|
@import "@primer/css/support/mixins/layout.scss";
|
||||||
|
@import "src/frame/stylesheets/breakpoint-xxl.scss";
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: unset;
|
display: unset;
|
||||||
@@ -51,3 +52,13 @@
|
|||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
@include breakpoint-xxl {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="prc-Dialog-Body"] {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { useTranslation } from '@/languages/components/useTranslation'
|
|||||||
import { Breadcrumbs } from '@/frame/components/page-header/Breadcrumbs'
|
import { Breadcrumbs } from '@/frame/components/page-header/Breadcrumbs'
|
||||||
import { VersionPicker } from '@/versions/components/VersionPicker'
|
import { VersionPicker } from '@/versions/components/VersionPicker'
|
||||||
import { SidebarNav } from '@/frame/components/sidebar/SidebarNav'
|
import { SidebarNav } from '@/frame/components/sidebar/SidebarNav'
|
||||||
import { AllProductsLink } from '@/frame/components/sidebar/AllProductsLink'
|
|
||||||
import { SearchBarButton } from '@/search/components/input/SearchBarButton'
|
import { SearchBarButton } from '@/search/components/input/SearchBarButton'
|
||||||
import { HeaderSearchAndWidgets } from './HeaderSearchAndWidgets'
|
import { HeaderSearchAndWidgets } from './HeaderSearchAndWidgets'
|
||||||
import { useInnerWindowWidth } from './hooks/useInnerWindowWidth'
|
import { useInnerWindowWidth } from './hooks/useInnerWindowWidth'
|
||||||
@@ -34,8 +33,8 @@ export const Header = () => {
|
|||||||
const { params, updateParams } = useMultiQueryParams()
|
const { params, updateParams } = useMultiQueryParams()
|
||||||
const [scroll, setScroll] = useState(false)
|
const [scroll, setScroll] = useState(false)
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false)
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false)
|
||||||
const openSidebar = useCallback(() => setIsSidebarOpen(true), [isSidebarOpen])
|
const openSidebar = useCallback(() => setIsSidebarOpen(true), [])
|
||||||
const closeSidebar = useCallback(() => setIsSidebarOpen(false), [isSidebarOpen])
|
const closeSidebar = useCallback(() => setIsSidebarOpen(false), [])
|
||||||
const isMounted = useRef(false)
|
const isMounted = useRef(false)
|
||||||
const menuButtonRef = useRef<HTMLButtonElement>(null)
|
const menuButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
const { asPath } = useRouter()
|
const { asPath } = useRouter()
|
||||||
@@ -204,45 +203,23 @@ export const Header = () => {
|
|||||||
onClick={openSidebar}
|
onClick={openSidebar}
|
||||||
ref={returnFocusRef}
|
ref={returnFocusRef}
|
||||||
/>
|
/>
|
||||||
<Dialog
|
{isSidebarOpen && (
|
||||||
returnFocusRef={returnFocusRef}
|
<Dialog
|
||||||
isOpen={isSidebarOpen}
|
returnFocusRef={returnFocusRef}
|
||||||
onDismiss={closeSidebar}
|
onClose={closeSidebar}
|
||||||
aria-labelledby="menu-title"
|
className={cx(styles.dialog, 'd-xxl-none')}
|
||||||
sx={{
|
position="left"
|
||||||
position: 'fixed',
|
title={
|
||||||
top: '0',
|
error === '404' || !currentProduct || isSearchResultsPage
|
||||||
left: '0',
|
? null
|
||||||
marginTop: '0',
|
: currentProductName || currentProduct.name
|
||||||
maxHeight: '100vh',
|
}
|
||||||
width: 'auto !important',
|
subtitle={isRestPage && <ApiVersionPicker />}
|
||||||
transform: 'none',
|
width="medium"
|
||||||
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' }}
|
|
||||||
>
|
>
|
||||||
<AllProductsLink />
|
<SidebarNav variant="overlay" />
|
||||||
{error === '404' || !currentProduct || isSearchResultsPage ? null : (
|
</Dialog>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mr-auto width-full" data-search="breadcrumbs">
|
<div className="mr-auto width-full" data-search="breadcrumbs">
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ export const SidebarNav = ({ variant = 'full' }: Props) => {
|
|||||||
<div
|
<div
|
||||||
data-container="nav"
|
data-container="nav"
|
||||||
className={cx(variant === 'full' ? 'position-sticky d-none border-right d-xxl-block' : '')}
|
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
|
<nav
|
||||||
aria-labelledby="allproducts-menu"
|
aria-labelledby="allproducts-menu"
|
||||||
@@ -62,10 +64,16 @@ export const SidebarNav = ({ variant = 'full' }: Props) => {
|
|||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
variant === 'overlay' ? 'd-xxl-none' : 'border-right d-none d-xxl-block',
|
variant === 'overlay'
|
||||||
'bg-primary overflow-y-auto flex-shrink-0',
|
? '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"
|
role="region"
|
||||||
aria-label="Page navigation content"
|
aria-label="Page navigation content"
|
||||||
>
|
>
|
||||||
|
|||||||
12
src/frame/stylesheets/dialog-overrides.scss
Normal file
12
src/frame/stylesheets/dialog-overrides.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
@import "headings.scss";
|
@import "headings.scss";
|
||||||
@import "scroll-top.scss";
|
@import "scroll-top.scss";
|
||||||
@import "utilities.scss";
|
@import "utilities.scss";
|
||||||
|
@import "dialog-overrides.scss";
|
||||||
|
|
||||||
@import "src/content-render/stylesheets/index.scss";
|
@import "src/content-render/stylesheets/index.scss";
|
||||||
@import "src/links/stylesheets/hover-card.scss";
|
@import "src/links/stylesheets/hover-card.scss";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { readFile } from 'fs/promises'
|
import fs from 'fs'
|
||||||
|
|
||||||
import { readCompressedJsonFileFallback } from '@/frame/lib/read-json-file'
|
import { readCompressedJsonFileFallback } from '@/frame/lib/read-json-file'
|
||||||
import { getOpenApiVersion } from '@/versions/lib/all-versions'
|
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`
|
// Initialize the Map with the page type keys listed under `pages`
|
||||||
// in the config.json file.
|
// 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)) {
|
for (const pageType of Object.keys(appsDataConfig.pages)) {
|
||||||
githubAppsData.set(pageType, new Map())
|
githubAppsData.set(pageType, new Map())
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/landings/components/ArticleList.module.css
Normal file
11
src/landings/components/ArticleList.module.css
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.linkItem {
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: var(--fgColor-accent, var(--color-accent-fg));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 { Link } from '@/frame/components/Link'
|
||||||
import { ArrowRightIcon } from '@primer/octicons-react'
|
|
||||||
import { FeaturedLink } from '@/landings/components/ProductLandingContext'
|
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 = {
|
export type ArticleListPropsT = {
|
||||||
title?: string
|
title?: string
|
||||||
@@ -29,7 +29,7 @@ export const ArticleList = ({
|
|||||||
<>
|
<>
|
||||||
{title && (
|
{title && (
|
||||||
<div className="mb-4 d-flex flex-items-baseline">
|
<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 && (
|
{viewAllHref && (
|
||||||
<Link
|
<Link
|
||||||
href={viewAllHref}
|
href={viewAllHref}
|
||||||
@@ -44,37 +44,26 @@ export const ArticleList = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ActionList as="ul" data-testid="article-list" variant="full">
|
<ActionList data-testid="article-list" variant="full">
|
||||||
{articles.map((link) => {
|
{articles.map((link) => {
|
||||||
return (
|
return (
|
||||||
<ActionList.Item
|
<ActionList.LinkItem
|
||||||
as="li"
|
as="a"
|
||||||
key={link.href}
|
key={link.href}
|
||||||
className={cx('width-full border-top')}
|
href={link.href}
|
||||||
sx={{
|
data-testid="bump-link"
|
||||||
borderRadius: 0,
|
className={clsx(styles.linkItem, 'width-full', 'border-top', 'py-3')}
|
||||||
':hover': {
|
|
||||||
borderRadius: 0,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
tabIndex={undefined}
|
|
||||||
>
|
>
|
||||||
<BumpLink
|
<div>
|
||||||
as={Link}
|
{link.intro ? (
|
||||||
href={link.href}
|
<h3 className="f4" data-testid="link-with-intro-title">
|
||||||
className="py-3"
|
<span>{link.fullTitle ? link.fullTitle : link.title}</span>
|
||||||
title={
|
</h3>
|
||||||
link.intro ? (
|
) : (
|
||||||
<h3 className="f4" data-testid="link-with-intro-title">
|
<span className="f4 text-bold d-block" data-testid="link-with-intro-title">
|
||||||
<span>{link.fullTitle ? link.fullTitle : link.title}</span>
|
{link.fullTitle ? link.fullTitle : link.title}
|
||||||
</h3>
|
</span>
|
||||||
) : (
|
)}
|
||||||
<span className="f4 text-bold d-block" data-testid="link-with-intro-title">
|
|
||||||
{link.fullTitle ? link.fullTitle : link.title}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{link.intro && (
|
{link.intro && (
|
||||||
<p className="color-fg-muted mb-0 mt-1" data-testid="link-with-intro-intro">
|
<p className="color-fg-muted mb-0 mt-1" data-testid="link-with-intro-intro">
|
||||||
{link.intro}
|
{link.intro}
|
||||||
@@ -88,8 +77,8 @@ export const ArticleList = ({
|
|||||||
{dayjs(link.date).format('MMMM DD')}
|
{dayjs(link.date).format('MMMM DD')}
|
||||||
</time>
|
</time>
|
||||||
)}
|
)}
|
||||||
</BumpLink>
|
</div>
|
||||||
</ActionList.Item>
|
</ActionList.LinkItem>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ActionList>
|
</ActionList>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const CookBookArticleCard = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="h4">
|
<h3 className="h4 fgColor-accent">
|
||||||
<Link href={url}>{title}</Link>
|
<Link href={url}>{title}</Link>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="fgColor-muted mb-3 mt-2">{description}</div>
|
<div className="fgColor-muted mb-3 mt-2">{description}</div>
|
||||||
|
|||||||
14
src/landings/components/ProductArticlesList.module.css
Normal file
14
src/landings/components/ProductArticlesList.module.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import cx from 'classnames'
|
|
||||||
|
|
||||||
import { ActionList } from '@primer/react'
|
import { ActionList } from '@primer/react'
|
||||||
|
|
||||||
import { ProductTreeNode, useMainContext } from '@/frame/components/context/MainContext'
|
import { ProductTreeNode, useMainContext } from '@/frame/components/context/MainContext'
|
||||||
import { Link } from '@/frame/components/Link'
|
import { Link } from '@/frame/components/Link'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import styles from './ProductArticlesList.module.css'
|
||||||
|
|
||||||
export const ProductArticlesList = () => {
|
export const ProductArticlesList = () => {
|
||||||
const { currentProductTree } = useMainContext()
|
const { currentProductTree } = useMainContext()
|
||||||
@@ -35,28 +35,19 @@ const ProductTreeNodeList = ({ treeNode }: { treeNode: ProductTreeNode }) => {
|
|||||||
<ActionList variant="full">
|
<ActionList variant="full">
|
||||||
{treeNode.childPages.map((childNode, index) => {
|
{treeNode.childPages.map((childNode, index) => {
|
||||||
return (
|
return (
|
||||||
<ActionList.Item
|
<ActionList.LinkItem
|
||||||
as="li"
|
as="a"
|
||||||
key={childNode.href + index}
|
key={childNode.href + index}
|
||||||
className={cx('width-full pl-0')}
|
href={childNode.href}
|
||||||
sx={{
|
className={clsx(styles.linkItem, 'width-full', 'pl-0', 'd-block')}
|
||||||
borderRadius: 0,
|
|
||||||
':hover': {
|
|
||||||
borderRadius: 0,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
tabIndex={undefined}
|
|
||||||
aria-labelledby={undefined}
|
|
||||||
>
|
>
|
||||||
<Link className="d-block width-full text-underline" href={childNode.href}>
|
{childNode.title}
|
||||||
{childNode.title}
|
{childNode.childPages.length > 0 ? (
|
||||||
{childNode.childPages.length > 0 ? (
|
<small className="color-fg-muted d-inline-block">
|
||||||
<small className="color-fg-muted d-inline-block">
|
• {childNode.childPages.length} articles
|
||||||
• {childNode.childPages.length} articles
|
</small>
|
||||||
</small>
|
) : null}
|
||||||
) : null}
|
</ActionList.LinkItem>
|
||||||
</Link>
|
|
||||||
</ActionList.Item>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ActionList>
|
</ActionList>
|
||||||
|
|||||||
3
src/landings/components/SidebarProduct.module.css
Normal file
3
src/landings/components/SidebarProduct.module.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.sidebar * {
|
||||||
|
font-size: var(--h5-size, 14px) !important;
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import { ProductTreeNode, useMainContext } from '@/frame/components/context/Main
|
|||||||
import { useAutomatedPageContext } from '@/automated-pipelines/components/AutomatedPageContext'
|
import { useAutomatedPageContext } from '@/automated-pipelines/components/AutomatedPageContext'
|
||||||
import { nonAutomatedRestPaths } from '@/rest/lib/config'
|
import { nonAutomatedRestPaths } from '@/rest/lib/config'
|
||||||
|
|
||||||
|
import styles from './SidebarProduct.module.css'
|
||||||
|
|
||||||
export const SidebarProduct = () => {
|
export const SidebarProduct = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const {
|
const {
|
||||||
@@ -32,7 +34,7 @@ export const SidebarProduct = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const productSection = () => (
|
const productSection = () => (
|
||||||
<div className="ml-3" data-testid="product-sidebar">
|
<div data-testid="product-sidebar">
|
||||||
<NavList aria-label="Product sidebar" role="navigation">
|
<NavList aria-label="Product sidebar" role="navigation">
|
||||||
{sidebarTree &&
|
{sidebarTree &&
|
||||||
sidebarTree.childPages.map((childPage) => (
|
sidebarTree.childPages.map((childPage) => (
|
||||||
@@ -50,7 +52,7 @@ export const SidebarProduct = () => {
|
|||||||
nonAutomatedRestPaths.every((item: string) => !page.href.includes(item)),
|
nonAutomatedRestPaths.every((item: string) => !page.href.includes(item)),
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<div className="ml-3">
|
<div>
|
||||||
<NavList aria-label="REST sidebar overview articles" role="navigation">
|
<NavList aria-label="REST sidebar overview articles" role="navigation">
|
||||||
{conceptualPages.map((childPage) => (
|
{conceptualPages.map((childPage) => (
|
||||||
<NavListItem key={childPage.href} childPage={childPage} />
|
<NavListItem key={childPage.href} childPage={childPage} />
|
||||||
@@ -69,7 +71,7 @@ export const SidebarProduct = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="sidebar" style={{ overflowY: 'auto' }} className="pt-3">
|
<div data-testid="sidebar" className={styles.sidebar}>
|
||||||
{isRestPage ? restSection() : productSection()}
|
{isRestPage ? restSection() : productSection()}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -90,7 +92,7 @@ function NavListItem({ childPage }: { childPage: ProductTreeNode }) {
|
|||||||
>
|
>
|
||||||
{childPage.title}
|
{childPage.title}
|
||||||
{childPage.childPages.length > 0 && (
|
{childPage.childPages.length > 0 && (
|
||||||
<NavList.SubNav aria-label={`${childPage.title} submenu`} sx={{ '*': { fontSize: 1 } }}>
|
<NavList.SubNav aria-label={`${childPage.title} submenu`}>
|
||||||
{childPage.sidebarLink && (
|
{childPage.sidebarLink && (
|
||||||
<NavList.Item
|
<NavList.Item
|
||||||
href={childPage.sidebarLink.href}
|
href={childPage.sidebarLink.href}
|
||||||
@@ -166,7 +168,7 @@ function RestNavListItem({ category }: { category: ProductTreeNode }) {
|
|||||||
>
|
>
|
||||||
{category.title}
|
{category.title}
|
||||||
{category.childPages.length > 0 && (
|
{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) => {
|
{category.childPages.map((childPage) => {
|
||||||
return (
|
return (
|
||||||
<NavList.Item
|
<NavList.Item
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react'
|
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
import { ActionList } from '@primer/react'
|
|
||||||
import { Link } from '@/frame/components/Link'
|
import { Link } from '@/frame/components/Link'
|
||||||
import type { TocItem } from '@/landings/types'
|
import type { TocItem } from '@/landings/types'
|
||||||
|
import { ActionList } from '@primer/react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
items: Array<TocItem>
|
items: Array<TocItem>
|
||||||
@@ -45,9 +45,13 @@ export const TableOfContents = (props: Props) => {
|
|||||||
const { fullPath, title, childTocItems } = item
|
const { fullPath, title, childTocItems } = item
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={fullPath}>
|
<React.Fragment key={fullPath}>
|
||||||
<ActionList.Item className="f4 color-fg-accent d-list-item d-block width-full text-underline">
|
<ActionList.LinkItem
|
||||||
<Link href={fullPath}>{title}</Link>
|
href={fullPath}
|
||||||
</ActionList.Item>
|
as="a"
|
||||||
|
className="f4 color-fg-accent d-list-item d-block width-full text-underline"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</ActionList.LinkItem>
|
||||||
{(childTocItems || []).length > 0 && (
|
{(childTocItems || []).length > 0 && (
|
||||||
<li className="f4 color-fg-accent d-list-item d-block width-full text-underline">
|
<li className="f4 color-fg-accent d-list-item d-block width-full text-underline">
|
||||||
<ActionList
|
<ActionList
|
||||||
@@ -57,12 +61,14 @@ export const TableOfContents = (props: Props) => {
|
|||||||
>
|
>
|
||||||
{(childTocItems || []).filter(Boolean).map((childItem) => {
|
{(childTocItems || []).filter(Boolean).map((childItem) => {
|
||||||
return (
|
return (
|
||||||
<ActionList.Item
|
<ActionList.LinkItem
|
||||||
key={childItem.fullPath}
|
key={childItem.fullPath}
|
||||||
|
href={childItem.fullPath}
|
||||||
|
as="a"
|
||||||
className="f4 color-fg-accent d-list-item d-block width-full text-underline"
|
className="f4 color-fg-accent d-list-item d-block width-full text-underline"
|
||||||
>
|
>
|
||||||
<Link href={childItem.fullPath}>{childItem.title}</Link>
|
{childItem.title}
|
||||||
</ActionList.Item>
|
</ActionList.LinkItem>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ActionList>
|
</ActionList>
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ describe('curated homepage links', () => {
|
|||||||
|
|
||||||
test('English', async () => {
|
test('English', async () => {
|
||||||
const $ = await getDOM('/en')
|
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)
|
expect($links.length).toBeGreaterThanOrEqual(6)
|
||||||
|
|
||||||
// Check that each link is localized and includes a title and intro
|
// Check that each link is localized and includes a title and intro
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ describe('featuredLinks', () => {
|
|||||||
vi.setConfig({ testTimeout: 60 * 1000 })
|
vi.setConfig({ testTimeout: 60 * 1000 })
|
||||||
|
|
||||||
test('non-TOC pages do not have intro links', async () => {
|
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)
|
expect($('[data-testid=article-list]')).toHaveLength(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -16,19 +16,27 @@ describe('featuredLinks', () => {
|
|||||||
const $featuredLinks = $('[data-testid=article-list] a')
|
const $featuredLinks = $('[data-testid=article-list] a')
|
||||||
expect($featuredLinks).toHaveLength(7)
|
expect($featuredLinks).toHaveLength(7)
|
||||||
expect($featuredLinks.eq(0).attr('href')).toBe('/en/get-started/start-your-journey/hello-world')
|
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).find('[data-testid=link-with-intro-title]').text()).toMatch(
|
||||||
expect($featuredLinks.eq(0).children('p').text()).toMatch('Follow this Hello World exercise')
|
'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 () => {
|
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')
|
const $featuredLinks = $('[data-testid=article-list] a')
|
||||||
expect($featuredLinks.length).toBeGreaterThan(0)
|
expect($featuredLinks.length).toBeGreaterThan(0)
|
||||||
|
|
||||||
|
// Fixture content expectations (CI environment)
|
||||||
expect($featuredLinks.eq(0).attr('href')).toBe(
|
expect($featuredLinks.eq(0).attr('href')).toBe(
|
||||||
`/en/enterprise-server@${enterpriseServerReleases.latestStable}/get-started/foo/bar`,
|
`/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).find('[data-testid=link-with-intro-title]').text()).toMatch(
|
||||||
expect($featuredLinks.eq(0).children('p').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",
|
"This page doesn't really have an intro",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useRouter } from 'next/router'
|
|
||||||
import { GlobeIcon } from '@primer/octicons-react'
|
import { GlobeIcon } from '@primer/octicons-react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
import { useLanguages } from '@/languages/components/LanguagesContext'
|
import { useLanguages } from '@/languages/components/LanguagesContext'
|
||||||
import { useTranslation } from '@/languages/components/useTranslation'
|
import { useTranslation } from '@/languages/components/useTranslation'
|
||||||
import { useUserLanguage } from '@/languages/components/useUserLanguage'
|
import { useUserLanguage } from '@/languages/components/useUserLanguage'
|
||||||
import { ActionList, ActionMenu, IconButton, Link } from '@primer/react'
|
import { ActionList, ActionMenu, IconButton } from '@primer/react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
xs?: boolean
|
xs?: boolean
|
||||||
@@ -38,16 +38,16 @@ export const LanguagePicker = ({ xs, mediumOrLower }: Props) => {
|
|||||||
// in a "denormalized" way.
|
// in a "denormalized" way.
|
||||||
const routerPath = router.asPath.split('#')[0]
|
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.
|
// for menus that behave differently at the breakpoints.
|
||||||
const languageList = langs.map((lang) => (
|
const languageList = langs.map((lang) => (
|
||||||
<ActionList.Item
|
<ActionList.LinkItem
|
||||||
key={`/${lang.code}${routerPath}`}
|
key={`/${lang.code}${routerPath}`}
|
||||||
selected={lang === selectedLang}
|
as="a"
|
||||||
as={Link}
|
active={lang === selectedLang}
|
||||||
lang={lang.code}
|
lang={lang.code}
|
||||||
href={`/${lang.code}${routerPath}`}
|
href={`/${lang.code}${routerPath}`}
|
||||||
onSelect={() => {
|
onClick={() => {
|
||||||
if (lang.code) {
|
if (lang.code) {
|
||||||
try {
|
try {
|
||||||
setUserLanguageCookie(lang.code)
|
setUserLanguageCookie(lang.code)
|
||||||
@@ -63,7 +63,7 @@ export const LanguagePicker = ({ xs, mediumOrLower }: Props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{lang.nativeName || lang.name}
|
{lang.nativeName || lang.name}
|
||||||
</ActionList.Item>
|
</ActionList.LinkItem>
|
||||||
))
|
))
|
||||||
|
|
||||||
// At large breakpoints, we return the full <ActionMenu> with just the languages,
|
// At large breakpoints, we return the full <ActionMenu> with just the languages,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import frontmatter from '@/frame/lib/read-frontmatter'
|
import frontmatter from '@/frame/lib/read-frontmatter'
|
||||||
import getApplicableVersions from '@/versions/lib/get-applicable-versions'
|
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
|
// Both internal and external products are specified in content/index.md
|
||||||
const homepage = path.posix.join(ROOT, '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 || []
|
export const productIds: string[] = data?.children || []
|
||||||
|
|
||||||
@@ -53,13 +53,13 @@ for (const productId of productIds) {
|
|||||||
|
|
||||||
// Early Access may not exist in the current checkout
|
// Early Access may not exist in the current checkout
|
||||||
try {
|
try {
|
||||||
await fs.readdir(dir)
|
fs.readdirSync(dir)
|
||||||
} catch {
|
} catch {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const toc = path.posix.join(dir, 'index.md')
|
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)
|
const { data: tocData } = frontmatter(fileContent)
|
||||||
if (tocData) {
|
if (tocData) {
|
||||||
const applicableVersions = getApplicableVersions(tocData.versions, toc)
|
const applicableVersions = getApplicableVersions(tocData.versions, toc)
|
||||||
|
|||||||
@@ -79,31 +79,29 @@ export const ApiVersionPicker = () => {
|
|||||||
|
|
||||||
// This only shows the REST Version picker if it's calendar date versioned
|
// This only shows the REST Version picker if it's calendar date versioned
|
||||||
return allVersions[currentVersion].apiVersions.length > 0 ? (
|
return allVersions[currentVersion].apiVersions.length > 0 ? (
|
||||||
<div className="mb-3">
|
<div data-testid="api-version-picker">
|
||||||
<div data-testid="api-version-picker">
|
<Picker
|
||||||
<Picker
|
defaultText={currentDateDisplayText}
|
||||||
defaultText={currentDateDisplayText}
|
items={apiVersionLinks}
|
||||||
items={apiVersionLinks}
|
pickerLabel="API Version: "
|
||||||
pickerLabel="API Version: "
|
alignment="start"
|
||||||
alignment="start"
|
buttonBorder={true}
|
||||||
buttonBorder={true}
|
dataTestId="version"
|
||||||
dataTestId="version"
|
ariaLabel="Select API Version"
|
||||||
ariaLabel="Select API Version"
|
onSelect={(item) => {
|
||||||
onSelect={(item) => {
|
if (item.extra?.currentDate) rememberApiVersion(item.extra.currentDate)
|
||||||
if (item.extra?.currentDate) rememberApiVersion(item.extra.currentDate)
|
}}
|
||||||
}}
|
renderItem={(item) => {
|
||||||
renderItem={(item) => {
|
return item.extra?.info ? (
|
||||||
return item.extra?.info ? (
|
<div className="f6">
|
||||||
<div className="f6">
|
{item.text}
|
||||||
{item.text}
|
<InfoIcon verticalAlign="middle" size={15} className="ml-1" />
|
||||||
<InfoIcon verticalAlign="middle" size={15} className="ml-1" />
|
</div>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
item.text
|
||||||
item.text
|
)
|
||||||
)
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,3 +31,13 @@
|
|||||||
.responseCodeBlock {
|
.responseCodeBlock {
|
||||||
min-height: 120px;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useRef, FormEvent } from 'react'
|
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 { CheckIcon, CopyIcon, InfoIcon } from '@primer/octicons-react'
|
||||||
import { announce } from '@primer/live-region-element'
|
import { announce } from '@primer/live-region-element'
|
||||||
import Cookies from '@/frame/components/lib/cookies'
|
import Cookies from '@/frame/components/lib/cookies'
|
||||||
@@ -260,26 +260,28 @@ export function RestCodeSamples({ operation, slug, heading }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="border-top d-inline-flex flex-justify-between width-full flex-items-center pt-2">
|
<div className="border-top d-inline-flex flex-justify-between width-full flex-items-center pt-2">
|
||||||
<div className="d-inline-flex ml-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) => (
|
{languageSelectOptions.map((optionKey) => (
|
||||||
<TabNav.Link
|
<SegmentedControl.Button
|
||||||
key={optionKey}
|
key={optionKey}
|
||||||
selected={optionKey === selectedLanguage}
|
selected={optionKey === selectedLanguage}
|
||||||
onClick={(e) => {
|
onClick={(e: React.MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
handleLanguageSelection(optionKey)
|
handleLanguageSelection(optionKey)
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event: React.KeyboardEvent) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
handleLanguageSelection(optionKey)
|
handleLanguageSelection(optionKey)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
href="#"
|
|
||||||
>
|
>
|
||||||
{t(`code_sample_options.${optionKey}`)}
|
{t(`code_sample_options.${optionKey}`)}
|
||||||
</TabNav.Link>
|
</SegmentedControl.Button>
|
||||||
))}
|
))}
|
||||||
</TabNav>
|
</SegmentedControl>
|
||||||
</div>
|
</div>
|
||||||
<div className="mr-2">
|
<div className="mr-2">
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -316,31 +318,30 @@ export function RestCodeSamples({ operation, slug, heading }: Props) {
|
|||||||
__html: displayedExample.response.description || t('response'),
|
__html: displayedExample.response.description || t('response'),
|
||||||
}}
|
}}
|
||||||
></h4>
|
></h4>
|
||||||
<div className="border rounded-1">
|
<div className="border rounded-1 pt-2">
|
||||||
{displayedExample.response.schema ? (
|
{displayedExample.response.schema ? (
|
||||||
<TabNav
|
<SegmentedControl
|
||||||
className="pt-2 mx-2"
|
className={cx(styles.segmentedControl, 'mx-2')}
|
||||||
aria-label={`Example response format selector for ${operation.title}`}
|
aria-label={`Example response format selector for ${operation.title}`}
|
||||||
>
|
>
|
||||||
{responseSelectOptions.map((optionKey) => (
|
{responseSelectOptions.map((optionKey) => (
|
||||||
<TabNav.Link
|
<SegmentedControl.Button
|
||||||
key={optionKey}
|
key={optionKey}
|
||||||
selected={optionKey === selectedResponse}
|
selected={optionKey === selectedResponse}
|
||||||
onClick={(e) => {
|
onClick={(e: React.MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
handleResponseSelection(optionKey)
|
handleResponseSelection(optionKey)
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event: React.KeyboardEvent) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
handleResponseSelection(optionKey)
|
handleResponseSelection(optionKey)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
href="#"
|
|
||||||
>
|
>
|
||||||
{t(`response_options.${optionKey}`)}
|
{t(`response_options.${optionKey}`)}
|
||||||
</TabNav.Link>
|
</SegmentedControl.Button>
|
||||||
))}
|
))}
|
||||||
</TabNav>
|
</SegmentedControl>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="">
|
<div className="">
|
||||||
{/* Status code */}
|
{/* Status code */}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ $mutedTextColor: var(--fgColor-muted, var(--color-fg-muted, #656d76));
|
|||||||
ul,
|
ul,
|
||||||
ol {
|
ol {
|
||||||
list-style-type: decimal !important;
|
list-style-type: decimal !important;
|
||||||
|
padding-inline-start: 2em !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ $mutedTextColor: var(--fgColor-muted, var(--color-fg-muted, #656d76));
|
|||||||
}
|
}
|
||||||
|
|
||||||
.referencesList {
|
.referencesList {
|
||||||
padding-left: 16px !important;
|
margin-top: 12px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loadingContainer {
|
.loadingContainer {
|
||||||
|
|||||||
@@ -500,9 +500,6 @@ export function AskAIResults({
|
|||||||
const refIndex = index + referencesIndexOffset
|
const refIndex = index + referencesIndexOffset
|
||||||
return (
|
return (
|
||||||
<ActionList.Item
|
<ActionList.Item
|
||||||
sx={{
|
|
||||||
marginLeft: '0px',
|
|
||||||
}}
|
|
||||||
key={`reference-${index}`}
|
key={`reference-${index}`}
|
||||||
id={`search-option-reference-${index + referencesIndexOffset}`}
|
id={`search-option-reference-${index + referencesIndexOffset}`}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
|
|||||||
@@ -111,10 +111,11 @@ $mutedTextColor: var(--fgColor-muted, var(--color-fg-muted, #656d76));
|
|||||||
}
|
}
|
||||||
|
|
||||||
.viewAllSearchResults {
|
.viewAllSearchResults {
|
||||||
color: var(--color-accent-emphasis) !important;
|
button {
|
||||||
padding-left: 32px !important;
|
span {
|
||||||
span {
|
color: var(--color-accent-emphasis) !important;
|
||||||
font-weight: 500 !important;
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
executeGeneralSearch,
|
executeGeneralSearch,
|
||||||
GENERAL_SEARCH_CONTEXT,
|
GENERAL_SEARCH_CONTEXT,
|
||||||
} from '../helpers/execute-search-actions'
|
} 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 { useCombinedSearchResults } from '@/search/components/hooks/useAISearchAutocomplete'
|
||||||
import { AskAIResults } from './AskAIResults'
|
import { AskAIResults } from './AskAIResults'
|
||||||
import { sendEvent, uuidv4 } from '@/events/components/events'
|
import { sendEvent, uuidv4 } from '@/events/components/events'
|
||||||
@@ -948,8 +948,11 @@ function renderSearchGroups(
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!option.isViewAllResults && !option.isNoResultsFound && (
|
{!option.isNoResultsFound && (
|
||||||
<ActionList.LeadingVisual aria-hidden>
|
<ActionList.LeadingVisual
|
||||||
|
aria-hidden
|
||||||
|
sx={{ visibility: option.isViewAllResults ? 'hidden' : 'visible' }}
|
||||||
|
>
|
||||||
<FileIcon />
|
<FileIcon />
|
||||||
</ActionList.LeadingVisual>
|
</ActionList.LeadingVisual>
|
||||||
)}
|
)}
|
||||||
|
|||||||
3
src/search/components/results/Aggregations.module.scss
Normal file
3
src/search/components/results/Aggregations.module.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.aggregations label {
|
||||||
|
font-weight: var(--base-text-weight-semibold, 600);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import Link from 'next/link'
|
|||||||
import { useTranslation } from '@/languages/components/useTranslation'
|
import { useTranslation } from '@/languages/components/useTranslation'
|
||||||
|
|
||||||
import type { SearchResultAggregations } from '@/search/types'
|
import type { SearchResultAggregations } from '@/search/types'
|
||||||
|
import styles from './Aggregations.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
aggregations: SearchResultAggregations
|
aggregations: SearchResultAggregations
|
||||||
@@ -46,7 +47,7 @@ export function SearchResultsAggregations({ aggregations }: Props) {
|
|||||||
|
|
||||||
if (aggregations.toplevel && aggregations.toplevel.length > 0) {
|
if (aggregations.toplevel && aggregations.toplevel.length > 0) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={styles.aggregations}>
|
||||||
<CheckboxGroup>
|
<CheckboxGroup>
|
||||||
<CheckboxGroup.Label>
|
<CheckboxGroup.Label>
|
||||||
{t('filter')}{' '}
|
{t('filter')}{' '}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { ReactNode } from 'react'
|
|
||||||
import { ActionList } from '@primer/react'
|
import { ActionList } from '@primer/react'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
import { PickerItem } from './Picker'
|
import { PickerItem } from './Picker'
|
||||||
import { Link } from '@/frame/components/Link'
|
|
||||||
|
|
||||||
import styles from './Fields.module.scss'
|
import styles from './Fields.module.scss'
|
||||||
|
|
||||||
@@ -21,11 +20,11 @@ export const Fields = (fieldProps: {
|
|||||||
item.divider ? (
|
item.divider ? (
|
||||||
<ActionList.Divider key={`divider${i}`} />
|
<ActionList.Divider key={`divider${i}`} />
|
||||||
) : (
|
) : (
|
||||||
<ActionList.Item
|
<ActionList.LinkItem
|
||||||
as={Link}
|
as="a"
|
||||||
key={item.text}
|
key={item.text}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
selected={item.selected === true}
|
active={item.selected === true}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (onSelect) onSelect(item)
|
if (onSelect) onSelect(item)
|
||||||
setOpen(!open)
|
setOpen(!open)
|
||||||
@@ -45,7 +44,7 @@ export const Fields = (fieldProps: {
|
|||||||
role={item.extra?.arrow || item.extra?.info ? 'menuitem' : 'menuitemradio'}
|
role={item.extra?.arrow || item.extra?.info ? 'menuitem' : 'menuitemradio'}
|
||||||
>
|
>
|
||||||
{renderItem ? renderItem(item) : item.text}
|
{renderItem ? renderItem(item) : item.text}
|
||||||
</ActionList.Item>
|
</ActionList.LinkItem>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</ActionList>
|
</ActionList>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs'
|
||||||
import semver from 'semver'
|
import semver from 'semver'
|
||||||
|
|
||||||
import versionSatisfiesRange from './version-satisfies-range'
|
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
|
// STATICALLY DEFINED VALUES
|
||||||
|
|||||||
Reference in New Issue
Block a user