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

Merge pull request #40903 from github/repo-sync

Repo sync
This commit is contained in:
docs-bot
2025-10-17 20:36:37 -07:00
committed by GitHub
6 changed files with 132 additions and 41 deletions

View File

@@ -1015,7 +1015,7 @@ test.describe('LandingCarousel component', () => {
// Check that article cards are present // Check that article cards are present
const items = page.locator('[data-testid="carousel-items"]') const items = page.locator('[data-testid="carousel-items"]')
const cards = items.locator('div') const cards = items.locator('a')
await expect(cards.first()).toBeVisible() await expect(cards.first()).toBeVisible()
// Verify cards have real titles (not "Unknown Article" when article not found) // Verify cards have real titles (not "Unknown Article" when article not found)
@@ -1190,7 +1190,7 @@ test.describe('LandingArticleGridWithFilter component', () => {
await expect(articleCards.first()).toBeVisible() await expect(articleCards.first()).toBeVisible()
const firstCard = articleCards.first() const firstCard = articleCards.first()
const titleLink = firstCard.locator('h3 a') const titleLink = firstCard.locator('h3 span')
await expect(titleLink).toBeVisible() await expect(titleLink).toBeVisible()
const intro = firstCard.locator('div').last() // cardIntro is the last div const intro = firstCard.locator('div').last() // cardIntro is the last div

View File

@@ -24,6 +24,19 @@
box-shadow: box-shadow:
0 0.0625rem 0.1875rem 0 rgba(31, 35, 40, 0.08), 0 0.0625rem 0.1875rem 0 rgba(31, 35, 40, 0.08),
0 0.0625rem 0 0 rgba(31, 35, 40, 0.06); 0 0.0625rem 0 0 rgba(31, 35, 40, 0.06);
transition: all 0.2s ease-in-out;
cursor: pointer;
text-decoration: none !important;
color: inherit;
&:hover {
box-shadow:
0 0.25rem 0.5rem 0 rgba(31, 35, 40, 0.12),
0 0.125rem 0.25rem 0 rgba(31, 35, 40, 0.08);
transform: translateY(-2px);
background-color: var(--bgColor-muted, var(--color-canvas-subtle));
text-decoration: none !important;
}
} }
.cardHeader { .cardHeader {
@@ -40,10 +53,6 @@
.cardTitleLink { .cardTitleLink {
color: var(--fgColor-accent); color: var(--fgColor-accent);
text-decoration: none; text-decoration: none;
&:hover {
text-decoration: underline;
}
} }
.cardIntro { .cardIntro {
@@ -51,6 +60,7 @@
color: var(--fgColor-muted); color: var(--fgColor-muted);
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.4; line-height: 1.4;
text-decoration: none !important;
} }
.tagsContainer { .tagsContainer {
@@ -145,6 +155,10 @@
margin-left: 0; margin-left: 0;
width: auto; width: auto;
input {
font-size: 1rem;
}
// Medium screens: flexible width with spacing // Medium screens: flexible width with spacing
@include breakpoint(md) { @include breakpoint(md) {
flex: 0 1 25%; flex: 0 1 25%;

View File

@@ -51,6 +51,7 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
const articlesPerPage = useResponsiveArticlesPerPage() const articlesPerPage = useResponsiveArticlesPerPage()
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
const headingRef = useRef<HTMLHeadingElement>(null)
// Reset to first page when articlesPerPage changes (screen size changes) // Reset to first page when articlesPerPage changes (screen size changes)
useEffect(() => { useEffect(() => {
@@ -112,6 +113,14 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
e.preventDefault() e.preventDefault()
if (pageNumber >= 1 && pageNumber <= totalPages) { if (pageNumber >= 1 && pageNumber <= totalPages) {
setCurrentPage(pageNumber) setCurrentPage(pageNumber)
if (headingRef.current) {
const elementPosition = headingRef.current.getBoundingClientRect().top + window.scrollY
const offsetPosition = elementPosition - 140 // 140px offset from top
window.scrollTo({
top: offsetPosition,
behavior: 'smooth',
})
}
} }
} }
@@ -122,7 +131,7 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
{/* Title and Dropdown Row */} {/* Title and Dropdown Row */}
<div className={styles.titleAndDropdownRow}> <div className={styles.titleAndDropdownRow}>
{/* Title */} {/* Title */}
<h2 className={cx(styles.headerTitle, styles.headerTitleText)}> <h2 ref={headingRef} className={cx(styles.headerTitle, styles.headerTitleText)}>
{t('article_grid.heading')} {t('article_grid.heading')}
</h2> </h2>
@@ -156,7 +165,6 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
<form onSubmit={(e) => e.preventDefault()}> <form onSubmit={(e) => e.preventDefault()}>
<TextInput <TextInput
leadingVisual={SearchIcon} leadingVisual={SearchIcon}
sx={{ width: '100%' }}
placeholder={t('article_grid.search_articles')} placeholder={t('article_grid.search_articles')}
ref={inputRef} ref={inputRef}
autoComplete="false" autoComplete="false"
@@ -210,7 +218,8 @@ type ArticleCardProps = {
const ArticleCard = ({ article }: ArticleCardProps) => { const ArticleCard = ({ article }: ArticleCardProps) => {
return ( return (
<div <Link
href={article.fullPath}
className={cx( className={cx(
styles.articleCard, styles.articleCard,
styles.articleCardBox, styles.articleCardBox,
@@ -226,12 +235,10 @@ const ArticleCard = ({ article }: ArticleCardProps) => {
</div> </div>
<h3 className={styles.cardTitle}> <h3 className={styles.cardTitle}>
<Link href={article.fullPath} className={styles.cardTitleLink}> <span className={styles.cardTitleLink}>{article.title}</span>
{article.title}
</Link>
</h3> </h3>
{article.intro && <div className={styles.cardIntro}>{article.intro}</div>} {article.intro && <div className={styles.cardIntro}>{article.intro}</div>}
</div> </Link>
) )
} }

View File

@@ -1,6 +1,6 @@
.carousel { .carousel {
margin-top: 3rem; margin-top: 3rem;
--carousel-transition-duration: 0.3s; --carousel-transition-duration: 0.1s;
} }
.header { .header {
@@ -44,7 +44,7 @@
display: grid; display: grid;
gap: 1.5rem; gap: 1.5rem;
grid-template-columns: 1fr; grid-template-columns: 1fr;
transition: opacity var(--carousel-transition-duration) ease-out; transition: opacity var(--carousel-transition-duration) ease-in-out;
opacity: 1; opacity: 1;
@media (min-width: 768px) { @media (min-width: 768px) {
@@ -56,7 +56,7 @@
} }
&.animating { &.animating {
opacity: 0; opacity: 0.3;
} }
} }
@@ -68,6 +68,19 @@
box-shadow: box-shadow:
0px 1px 3px 0px rgba(31, 35, 40, 0.08), 0px 1px 3px 0px rgba(31, 35, 40, 0.08),
0px 1px 0px 0px rgba(31, 35, 40, 0.06); 0px 1px 0px 0px rgba(31, 35, 40, 0.06);
transition: all 0.2s ease-in-out;
cursor: pointer;
text-decoration: none !important;
color: inherit;
&:hover {
box-shadow:
0 0.25rem 0.5rem 0 rgba(31, 35, 40, 0.12),
0 0.125rem 0.25rem 0 rgba(31, 35, 40, 0.08);
transform: translateY(-2px);
background-color: var(--bgColor-muted, var(--color-canvas-subtle));
text-decoration: none !important;
}
} }
.articleTitle { .articleTitle {
@@ -78,10 +91,6 @@
.articleLink { .articleLink {
color: var(--fgColor-accent); color: var(--fgColor-accent);
text-decoration: none; text-decoration: none;
&:hover {
text-decoration: underline;
}
} }
.articleDescription { .articleDescription {
@@ -89,6 +98,7 @@
color: var(--fgColor-muted); color: var(--fgColor-muted);
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.4; line-height: 1.4;
text-decoration: none !important;
} }
.pagination { .pagination {

View File

@@ -1,6 +1,5 @@
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef } from 'react'
import { ChevronLeftIcon, ChevronRightIcon } from '@primer/octicons-react' import { ChevronLeftIcon, ChevronRightIcon } from '@primer/octicons-react'
import { Token } from '@primer/react'
import cx from 'classnames' import cx from 'classnames'
import type { ResolvedArticle } from '@/types' import type { ResolvedArticle } from '@/types'
import { useTranslation } from '@/languages/components/useTranslation' import { useTranslation } from '@/languages/components/useTranslation'
@@ -78,11 +77,11 @@ export const LandingCarousel = ({ heading = '', recommended }: LandingCarouselPr
setCurrentPage((prev) => Math.max(0, prev - 1)) setCurrentPage((prev) => Math.max(0, prev - 1))
// Set animation state to false after transition completes // Set animation state to false after transition completes
// Duration matches CSS custom property --carousel-transition-duration (300ms) // Duration matches CSS custom property --carousel-transition-duration (100ms)
animationTimeoutRef.current = setTimeout(() => { animationTimeoutRef.current = setTimeout(() => {
setIsAnimating(false) setIsAnimating(false)
animationTimeoutRef.current = null animationTimeoutRef.current = null
}, 300) }, 100)
} }
const goToNext = () => { const goToNext = () => {
@@ -97,11 +96,11 @@ export const LandingCarousel = ({ heading = '', recommended }: LandingCarouselPr
setCurrentPage((prev) => Math.min(totalPages - 1, prev + 1)) setCurrentPage((prev) => Math.min(totalPages - 1, prev + 1))
// Set animation state to false after transition completes // Set animation state to false after transition completes
// Duration matches CSS custom property --carousel-transition-duration (300ms) // Duration matches CSS custom property --carousel-transition-duration (100ms)
animationTimeoutRef.current = setTimeout(() => { animationTimeoutRef.current = setTimeout(() => {
setIsAnimating(false) setIsAnimating(false)
animationTimeoutRef.current = null animationTimeoutRef.current = null
}, 300) }, 100)
} }
// Calculate the start index based on current page // Calculate the start index based on current page
@@ -144,19 +143,13 @@ export const LandingCarousel = ({ heading = '', recommended }: LandingCarouselPr
data-testid="carousel-items" data-testid="carousel-items"
> >
{visibleItems.map((article: ResolvedArticle, index) => ( {visibleItems.map((article: ResolvedArticle, index) => (
<div <a
key={startIndex + index} key={startIndex + index}
href={article.href}
className={cx(styles.articleCard, 'border', 'border-default', 'rounded-2')} className={cx(styles.articleCard, 'border', 'border-default', 'rounded-2')}
> >
<div className="mb-2">
{article.category.map((cat: string) => (
<Token key={cat} text={cat} className="mr-1 mb-2" />
))}
</div>
<h3 className={styles.articleTitle}> <h3 className={styles.articleTitle}>
<a href={article.href} className={styles.articleLink}> <span className={styles.articleLink}>{article.title}</span>
{article.title}
</a>
</h3> </h3>
<div <div
className={styles.articleDescription} className={styles.articleDescription}
@@ -164,7 +157,7 @@ export const LandingCarousel = ({ heading = '', recommended }: LandingCarouselPr
__html: article.intro as TrustedHTML, __html: article.intro as TrustedHTML,
}} }}
/> />
</div> </a>
))} ))}
</div> </div>
</div> </div>

View File

@@ -98,6 +98,14 @@
.heroSecondaryAction { .heroSecondaryAction {
background-color: transparent; background-color: transparent;
color: var(--fgColor-default, var(--color-fg-default, #1f2328)); color: var(--fgColor-default, var(--color-fg-default, #1f2328));
@media (max-width: 865px) {
background-color: var(
--bgColor-muted,
var(--color-canvas-subtle, #f6f8fa)
) !important;
}
border-color: var( border-color: var(
--borderColor-default, --borderColor-default,
var(--color-border-default, #d1d9e0) var(--color-border-default, #d1d9e0)
@@ -121,18 +129,37 @@
@media (max-width: 865px) { @media (max-width: 865px) {
.landingHero { .landingHero {
height: 24rem; height: 28rem;
background-image: none !important;
flex-direction: column; flex-direction: column;
text-align: center; text-align: center;
justify-content: center; justify-content: center;
background-color: var(--bgColor-muted, var(--color-canvas-subtle, #f6f8fa)); background-color: var(--bgColor-muted, var(--color-canvas-subtle, #f6f8fa));
position: relative;
background-position: unset;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: inherit;
background-size: contain;
background-position: bottom;
background-repeat: no-repeat;
opacity: 0.5;
z-index: 0;
}
} }
.heroContent { .heroContent {
width: 100%; width: 100%;
order: 2; order: 2;
padding: 0 1rem; padding: 0 1rem;
position: relative;
z-index: 1;
} }
.heroHeading { .heroHeading {
@@ -144,7 +171,11 @@
} }
.heroActions { .heroActions {
flex-direction: column;
align-self: baseline;
margin-left: 5rem;
justify-content: center; justify-content: center;
align-items: center;
} }
.heroAction { .heroAction {
@@ -162,19 +193,36 @@
@media (max-width: 480px) { @media (max-width: 480px) {
.landingHero { .landingHero {
height: 32rem; height: 28rem;
background-image: none !important;
background-color: var(--bgColor-muted, var(--color-canvas-subtle, #f6f8fa)); background-color: var(--bgColor-muted, var(--color-canvas-subtle, #f6f8fa));
flex-direction: column; flex-direction: column;
text-align: center; text-align: center;
padding: 2rem 0 1rem; padding: 1rem 0 1rem;
justify-content: center; justify-content: center;
position: relative;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: inherit;
background-size: contain;
background-position: bottom;
background-repeat: no-repeat;
opacity: 0.8;
z-index: 0;
}
} }
.heroContent { .heroContent {
width: 100%; width: 100%;
order: 2; order: 2;
padding: 1rem; padding: 0 1rem;
position: relative;
z-index: 1;
} }
.heroText { .heroText {
@@ -184,8 +232,27 @@
align-items: center; align-items: center;
} }
.heroHeading {
font-size: 2.5rem;
line-height: 1;
}
.heroActions { .heroActions {
flex-direction: column;
align-self: baseline;
margin-left: 2rem;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.heroAction {
min-width: 9rem;
padding: 0.5rem 1rem;
}
.heroDescription {
font-size: 1rem;
max-height: 14rem;
text-overflow: ellipsis;
}
} }