article grid updates (#58167)
This commit is contained in:
@@ -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
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user