Skip to content

Commit d313b81

Browse files
authored
article grid updates (#58167)
1 parent 717ad8c commit d313b81

File tree

5 files changed

+44
-27
lines changed

5 files changed

+44
-27
lines changed

src/landings/components/CategoryLanding.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ export const CategoryLanding = () => {
3535
if (typeof value === 'string') {
3636
return value.toLowerCase().includes(searchQuery.toLowerCase())
3737
} else if (Array.isArray(value)) {
38-
return value.some((item) => item.toLowerCase().includes(searchQuery.toLowerCase()))
38+
return value.some(
39+
(item) =>
40+
typeof item === 'string' && item.toLowerCase().includes(searchQuery.toLowerCase()),
41+
)
3942
}
4043
return false
4144
})

src/landings/components/bespoke/BespokeLanding.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
1-
import { useMemo } from 'react'
2-
31
import { DefaultLayout } from '@/frame/components/DefaultLayout'
42
import { useLandingContext } from '@/landings/context/LandingContext'
53
import { LandingHero } from '@/landings/components/shared/LandingHero'
64
import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter'
75
import { UtmPreserver } from '@/frame/components/UtmPreserver'
86
import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
97

10-
import type { ArticleCardItems } from '@/landings/types'
11-
128
export const BespokeLanding = () => {
139
const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext()
1410

15-
const flatArticles: ArticleCardItems = useMemo(
16-
() => tocItems.flatMap((item) => item.childTocItems || []),
17-
[tocItems],
18-
)
19-
2011
return (
2112
<DefaultLayout>
2213
<UtmPreserver />
@@ -25,7 +16,7 @@ export const BespokeLanding = () => {
2516

2617
<div className="container-xl px-3 px-md-6 mt-6 mb-4">
2718
<LandingCarousel recommended={recommended} />
28-
<ArticleGrid flatArticles={flatArticles} />
19+
<ArticleGrid tocItems={tocItems} />
2920
</div>
3021
</div>
3122
</DefaultLayout>

src/landings/components/discovery/DiscoveryLanding.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,21 @@
1-
import { useMemo } from 'react'
2-
31
import { DefaultLayout } from '@/frame/components/DefaultLayout'
42
import { useLandingContext } from '@/landings/context/LandingContext'
53
import { LandingHero } from '@/landings/components/shared/LandingHero'
64
import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter'
75
import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
86
import { UtmPreserver } from '@/frame/components/UtmPreserver'
97

10-
import type { ArticleCardItems } from '@/landings/types'
11-
128
export const DiscoveryLanding = () => {
139
const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext()
1410

15-
const flatArticles: ArticleCardItems = useMemo(
16-
() => tocItems.flatMap((item) => item.childTocItems || []),
17-
[tocItems],
18-
)
19-
2011
return (
2112
<DefaultLayout>
2213
<UtmPreserver />
2314
<div>
2415
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
2516
<div className="container-xl px-3 px-md-6 mt-6 mb-4">
2617
<LandingCarousel recommended={recommended} />
27-
<ArticleGrid flatArticles={flatArticles} />
18+
<ArticleGrid tocItems={tocItems} />
2819
</div>
2920
</div>
3021
</DefaultLayout>

src/landings/components/shared/LandingArticleGridWithFilter.tsx

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,44 @@
1-
import { useState, useRef, useEffect } from 'react'
1+
import { useState, useRef, useEffect, useMemo } from 'react'
22
import { TextInput, ActionMenu, ActionList, Token, Pagination } from '@primer/react'
33
import { SearchIcon } from '@primer/octicons-react'
44
import cx from 'classnames'
55

66
import { Link } from '@/frame/components/Link'
77
import { useTranslation } from '@/languages/components/useTranslation'
8-
import { ArticleCardItems, ChildTocItem } from '@/landings/types'
8+
import { ArticleCardItems, ChildTocItem, TocItem } from '@/landings/types'
99

1010
import styles from './LandingArticleGridWithFilter.module.scss'
1111

1212
type ArticleGridProps = {
13-
flatArticles: ArticleCardItems
13+
tocItems: TocItem[]
1414
}
1515

1616
const ALL_CATEGORIES = 'all_categories'
1717

18+
// Helper function to recursively flatten nested articles
19+
// Excludes index pages (pages with childTocItems)
20+
const flattenArticlesRecursive = (articles: ArticleCardItems): ArticleCardItems => {
21+
const flattened: ArticleCardItems = []
22+
23+
for (const article of articles) {
24+
// If the article has children, recursively process them but don't include the parent (index page)
25+
if (article.childTocItems && article.childTocItems.length > 0) {
26+
flattened.push(...flattenArticlesRecursive(article.childTocItems))
27+
} else {
28+
// Only add articles that don't have children (actual article pages, not index pages)
29+
flattened.push(article)
30+
}
31+
}
32+
33+
return flattened
34+
}
35+
36+
// Wrapper function that flattens and sorts alphabetically by title (only once)
37+
const flattenArticles = (articles: ArticleCardItems): ArticleCardItems => {
38+
const flattened = flattenArticlesRecursive(articles)
39+
return flattened.sort((a, b) => a.title.localeCompare(b.title))
40+
}
41+
1842
// Hook to get current articles per page based on screen size
1943
const useResponsiveArticlesPerPage = () => {
2044
const [articlesPerPage, setArticlesPerPage] = useState(9) // Default to desktop
@@ -42,7 +66,7 @@ const useResponsiveArticlesPerPage = () => {
4266
return articlesPerPage
4367
}
4468

45-
export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
69+
export const ArticleGrid = ({ tocItems }: ArticleGridProps) => {
4670
const { t } = useTranslation('product_landing')
4771
const [searchQuery, setSearchQuery] = useState('')
4872
const [selectedCategory, setSelectedCategory] = useState(ALL_CATEGORIES)
@@ -53,6 +77,12 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
5377
const inputRef = useRef<HTMLInputElement>(null)
5478
const headingRef = useRef<HTMLHeadingElement>(null)
5579

80+
// Extract child items from tocItems and recursively flatten nested articles to ensure we get all articles with categories
81+
const allArticles = useMemo(
82+
() => flattenArticles(tocItems.flatMap((item) => item.childTocItems || [])),
83+
[tocItems],
84+
)
85+
5686
// Reset to first page when articlesPerPage changes (screen size changes)
5787
useEffect(() => {
5888
setCurrentPage(1)
@@ -61,13 +91,13 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
6191
// Extract unique categories from the articles
6292
const categories: string[] = [
6393
ALL_CATEGORIES,
64-
...Array.from(new Set(flatArticles.flatMap((item) => item.category || []))).sort((a, b) =>
94+
...Array.from(new Set(allArticles.flatMap((item) => item.category || []))).sort((a, b) =>
6595
a.localeCompare(b),
6696
),
6797
]
6898

6999
const applyFilters = () => {
70-
let results = flatArticles
100+
let results = allArticles
71101

72102
if (searchQuery) {
73103
results = results.filter((token) => {

src/landings/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ export type BaseTocItem = {
1212
}
1313

1414
// Extended type for child TOC items with additional metadata
15+
// This is recursive - children can also have their own children
1516
export type ChildTocItem = BaseTocItem & {
1617
octicon?: ValidOcticon | null
1718
category?: string[] | null
1819
complexity?: string[] | null
1920
industry?: string[] | null
21+
childTocItems?: ChildTocItem[]
2022
}
2123

2224
// Main TOC item type that can contain children

0 commit comments

Comments
 (0)