Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/fixtures/tests/playwright-rendering.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1015,7 +1015,7 @@ test.describe('LandingCarousel component', () => {

// Check that article cards are present
const items = page.locator('[data-testid="carousel-items"]')
const cards = items.locator('div')
const cards = items.locator('a')
await expect(cards.first()).toBeVisible()

// Verify cards have real titles (not "Unknown Article" when article not found)
Expand Down Expand Up @@ -1190,7 +1190,7 @@ test.describe('LandingArticleGridWithFilter component', () => {
await expect(articleCards.first()).toBeVisible()

const firstCard = articleCards.first()
const titleLink = firstCard.locator('h3 a')
const titleLink = firstCard.locator('h3 span')
await expect(titleLink).toBeVisible()

const intro = firstCard.locator('div').last() // cardIntro is the last div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@
box-shadow:
0 0.0625rem 0.1875rem 0 rgba(31, 35, 40, 0.08),
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 {
Expand All @@ -40,17 +53,14 @@
.cardTitleLink {
color: var(--fgColor-accent);
text-decoration: none;

&:hover {
text-decoration: underline;
}
}

.cardIntro {
margin: 0;
color: var(--fgColor-muted);
font-size: 0.9rem;
line-height: 1.4;
text-decoration: none !important;
}

.tagsContainer {
Expand Down Expand Up @@ -145,6 +155,10 @@
margin-left: 0;
width: auto;

input {
font-size: 1rem;
}

// Medium screens: flexible width with spacing
@include breakpoint(md) {
flex: 0 1 25%;
Expand Down
21 changes: 14 additions & 7 deletions src/landings/components/shared/LandingArticleGridWithFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
const articlesPerPage = useResponsiveArticlesPerPage()

const inputRef = useRef<HTMLInputElement>(null)
const headingRef = useRef<HTMLHeadingElement>(null)

// Reset to first page when articlesPerPage changes (screen size changes)
useEffect(() => {
Expand Down Expand Up @@ -112,6 +113,14 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
e.preventDefault()
if (pageNumber >= 1 && pageNumber <= totalPages) {
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',
})
}
}
}

Expand All @@ -122,7 +131,7 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
{/* Title and Dropdown Row */}
<div className={styles.titleAndDropdownRow}>
{/* Title */}
<h2 className={cx(styles.headerTitle, styles.headerTitleText)}>
<h2 ref={headingRef} className={cx(styles.headerTitle, styles.headerTitleText)}>
{t('article_grid.heading')}
</h2>

Expand Down Expand Up @@ -156,7 +165,6 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
<form onSubmit={(e) => e.preventDefault()}>
<TextInput
leadingVisual={SearchIcon}
sx={{ width: '100%' }}
placeholder={t('article_grid.search_articles')}
ref={inputRef}
autoComplete="false"
Expand Down Expand Up @@ -210,7 +218,8 @@ type ArticleCardProps = {

const ArticleCard = ({ article }: ArticleCardProps) => {
return (
<div
<Link
href={article.fullPath}
className={cx(
styles.articleCard,
styles.articleCardBox,
Expand All @@ -226,12 +235,10 @@ const ArticleCard = ({ article }: ArticleCardProps) => {
</div>

<h3 className={styles.cardTitle}>
<Link href={article.fullPath} className={styles.cardTitleLink}>
{article.title}
</Link>
<span className={styles.cardTitleLink}>{article.title}</span>
</h3>

{article.intro && <div className={styles.cardIntro}>{article.intro}</div>}
</div>
</Link>
)
}
24 changes: 17 additions & 7 deletions src/landings/components/shared/LandingCarousel.module.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.carousel {
margin-top: 3rem;
--carousel-transition-duration: 0.3s;
--carousel-transition-duration: 0.1s;
}

.header {
Expand Down Expand Up @@ -44,7 +44,7 @@
display: grid;
gap: 1.5rem;
grid-template-columns: 1fr;
transition: opacity var(--carousel-transition-duration) ease-out;
transition: opacity var(--carousel-transition-duration) ease-in-out;
opacity: 1;

@media (min-width: 768px) {
Expand All @@ -56,7 +56,7 @@
}

&.animating {
opacity: 0;
opacity: 0.3;
}
}

Expand All @@ -68,6 +68,19 @@
box-shadow:
0px 1px 3px 0px rgba(31, 35, 40, 0.08),
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 {
Expand All @@ -78,17 +91,14 @@
.articleLink {
color: var(--fgColor-accent);
text-decoration: none;

&:hover {
text-decoration: underline;
}
}

.articleDescription {
margin: 0;
color: var(--fgColor-muted);
font-size: 0.9rem;
line-height: 1.4;
text-decoration: none !important;
}

.pagination {
Expand Down
23 changes: 8 additions & 15 deletions src/landings/components/shared/LandingCarousel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState, useEffect, useRef } from 'react'
import { ChevronLeftIcon, ChevronRightIcon } from '@primer/octicons-react'
import { Token } from '@primer/react'
import cx from 'classnames'
import type { ResolvedArticle } from '@/types'
import { useTranslation } from '@/languages/components/useTranslation'
Expand Down Expand Up @@ -78,11 +77,11 @@ export const LandingCarousel = ({ heading = '', recommended }: LandingCarouselPr
setCurrentPage((prev) => Math.max(0, prev - 1))

// 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(() => {
setIsAnimating(false)
animationTimeoutRef.current = null
}, 300)
}, 100)
}

const goToNext = () => {
Expand All @@ -97,11 +96,11 @@ export const LandingCarousel = ({ heading = '', recommended }: LandingCarouselPr
setCurrentPage((prev) => Math.min(totalPages - 1, prev + 1))

// 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(() => {
setIsAnimating(false)
animationTimeoutRef.current = null
}, 300)
}, 100)
}

// Calculate the start index based on current page
Expand Down Expand Up @@ -144,27 +143,21 @@ export const LandingCarousel = ({ heading = '', recommended }: LandingCarouselPr
data-testid="carousel-items"
>
{visibleItems.map((article: ResolvedArticle, index) => (
<div
<a
key={startIndex + index}
href={article.href}
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}>
<a href={article.href} className={styles.articleLink}>
{article.title}
</a>
<span className={styles.articleLink}>{article.title}</span>
</h3>
<div
className={styles.articleDescription}
dangerouslySetInnerHTML={{
__html: article.intro as TrustedHTML,
}}
/>
</div>
</a>
))}
</div>
</div>
Expand Down
Loading
Loading