1
0
mirror of synced 2025-12-19 09:57:42 -05:00

article grid updates (#58167)

This commit is contained in:
Evan Bonsignori
2025-10-23 12:49:11 -07:00
committed by GitHub
parent 717ad8c404
commit d313b81bfd
5 changed files with 44 additions and 27 deletions

View File

@@ -35,7 +35,10 @@ export const CategoryLanding = () => {
if (typeof value === 'string') {
return value.toLowerCase().includes(searchQuery.toLowerCase())
} else if (Array.isArray(value)) {
return value.some((item) => item.toLowerCase().includes(searchQuery.toLowerCase()))
return value.some(
(item) =>
typeof item === 'string' && item.toLowerCase().includes(searchQuery.toLowerCase()),
)
}
return false
})

View File

@@ -1,5 +1,3 @@
import { useMemo } from 'react'
import { DefaultLayout } from '@/frame/components/DefaultLayout'
import { useLandingContext } from '@/landings/context/LandingContext'
import { LandingHero } from '@/landings/components/shared/LandingHero'
@@ -7,16 +5,9 @@ import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWith
import { UtmPreserver } from '@/frame/components/UtmPreserver'
import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
import type { ArticleCardItems } from '@/landings/types'
export const BespokeLanding = () => {
const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext()
const flatArticles: ArticleCardItems = useMemo(
() => tocItems.flatMap((item) => item.childTocItems || []),
[tocItems],
)
return (
<DefaultLayout>
<UtmPreserver />
@@ -25,7 +16,7 @@ export const BespokeLanding = () => {
<div className="container-xl px-3 px-md-6 mt-6 mb-4">
<LandingCarousel recommended={recommended} />
<ArticleGrid flatArticles={flatArticles} />
<ArticleGrid tocItems={tocItems} />
</div>
</div>
</DefaultLayout>

View File

@@ -1,5 +1,3 @@
import { useMemo } from 'react'
import { DefaultLayout } from '@/frame/components/DefaultLayout'
import { useLandingContext } from '@/landings/context/LandingContext'
import { LandingHero } from '@/landings/components/shared/LandingHero'
@@ -7,16 +5,9 @@ import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWith
import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
import { UtmPreserver } from '@/frame/components/UtmPreserver'
import type { ArticleCardItems } from '@/landings/types'
export const DiscoveryLanding = () => {
const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext()
const flatArticles: ArticleCardItems = useMemo(
() => tocItems.flatMap((item) => item.childTocItems || []),
[tocItems],
)
return (
<DefaultLayout>
<UtmPreserver />
@@ -24,7 +15,7 @@ export const DiscoveryLanding = () => {
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
<div className="container-xl px-3 px-md-6 mt-6 mb-4">
<LandingCarousel recommended={recommended} />
<ArticleGrid flatArticles={flatArticles} />
<ArticleGrid tocItems={tocItems} />
</div>
</div>
</DefaultLayout>

View File

@@ -1,20 +1,44 @@
import { useState, useRef, useEffect } from 'react'
import { useState, useRef, useEffect, useMemo } from 'react'
import { TextInput, ActionMenu, ActionList, Token, Pagination } from '@primer/react'
import { SearchIcon } from '@primer/octicons-react'
import cx from 'classnames'
import { Link } from '@/frame/components/Link'
import { useTranslation } from '@/languages/components/useTranslation'
import { ArticleCardItems, ChildTocItem } from '@/landings/types'
import { ArticleCardItems, ChildTocItem, TocItem } from '@/landings/types'
import styles from './LandingArticleGridWithFilter.module.scss'
type ArticleGridProps = {
flatArticles: ArticleCardItems
tocItems: TocItem[]
}
const ALL_CATEGORIES = 'all_categories'
// Helper function to recursively flatten nested articles
// Excludes index pages (pages with childTocItems)
const flattenArticlesRecursive = (articles: ArticleCardItems): ArticleCardItems => {
const flattened: ArticleCardItems = []
for (const article of articles) {
// If the article has children, recursively process them but don't include the parent (index page)
if (article.childTocItems && article.childTocItems.length > 0) {
flattened.push(...flattenArticlesRecursive(article.childTocItems))
} else {
// Only add articles that don't have children (actual article pages, not index pages)
flattened.push(article)
}
}
return flattened
}
// Wrapper function that flattens and sorts alphabetically by title (only once)
const flattenArticles = (articles: ArticleCardItems): ArticleCardItems => {
const flattened = flattenArticlesRecursive(articles)
return flattened.sort((a, b) => a.title.localeCompare(b.title))
}
// Hook to get current articles per page based on screen size
const useResponsiveArticlesPerPage = () => {
const [articlesPerPage, setArticlesPerPage] = useState(9) // Default to desktop
@@ -42,7 +66,7 @@ const useResponsiveArticlesPerPage = () => {
return articlesPerPage
}
export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
export const ArticleGrid = ({ tocItems }: ArticleGridProps) => {
const { t } = useTranslation('product_landing')
const [searchQuery, setSearchQuery] = useState('')
const [selectedCategory, setSelectedCategory] = useState(ALL_CATEGORIES)
@@ -53,6 +77,12 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
const inputRef = useRef<HTMLInputElement>(null)
const headingRef = useRef<HTMLHeadingElement>(null)
// Extract child items from tocItems and recursively flatten nested articles to ensure we get all articles with categories
const allArticles = useMemo(
() => flattenArticles(tocItems.flatMap((item) => item.childTocItems || [])),
[tocItems],
)
// Reset to first page when articlesPerPage changes (screen size changes)
useEffect(() => {
setCurrentPage(1)
@@ -61,13 +91,13 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
// Extract unique categories from the articles
const categories: string[] = [
ALL_CATEGORIES,
...Array.from(new Set(flatArticles.flatMap((item) => item.category || []))).sort((a, b) =>
...Array.from(new Set(allArticles.flatMap((item) => item.category || []))).sort((a, b) =>
a.localeCompare(b),
),
]
const applyFilters = () => {
let results = flatArticles
let results = allArticles
if (searchQuery) {
results = results.filter((token) => {

View File

@@ -12,11 +12,13 @@ export type BaseTocItem = {
}
// Extended type for child TOC items with additional metadata
// This is recursive - children can also have their own children
export type ChildTocItem = BaseTocItem & {
octicon?: ValidOcticon | null
category?: string[] | null
complexity?: string[] | null
industry?: string[] | null
childTocItems?: ChildTocItem[]
}
// Main TOC item type that can contain children