article grid updates (#58167)
This commit is contained in:
@@ -35,7 +35,10 @@ export const CategoryLanding = () => {
|
|||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
return value.toLowerCase().includes(searchQuery.toLowerCase())
|
return value.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
} else if (Array.isArray(value)) {
|
} 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
|
return false
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { useMemo } from 'react'
|
|
||||||
|
|
||||||
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
||||||
import { useLandingContext } from '@/landings/context/LandingContext'
|
import { useLandingContext } from '@/landings/context/LandingContext'
|
||||||
import { LandingHero } from '@/landings/components/shared/LandingHero'
|
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 { UtmPreserver } from '@/frame/components/UtmPreserver'
|
||||||
import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
|
import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
|
||||||
|
|
||||||
import type { ArticleCardItems } from '@/landings/types'
|
|
||||||
|
|
||||||
export const BespokeLanding = () => {
|
export const BespokeLanding = () => {
|
||||||
const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext()
|
const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext()
|
||||||
|
|
||||||
const flatArticles: ArticleCardItems = useMemo(
|
|
||||||
() => tocItems.flatMap((item) => item.childTocItems || []),
|
|
||||||
[tocItems],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
<UtmPreserver />
|
<UtmPreserver />
|
||||||
@@ -25,7 +16,7 @@ export const BespokeLanding = () => {
|
|||||||
|
|
||||||
<div className="container-xl px-3 px-md-6 mt-6 mb-4">
|
<div className="container-xl px-3 px-md-6 mt-6 mb-4">
|
||||||
<LandingCarousel recommended={recommended} />
|
<LandingCarousel recommended={recommended} />
|
||||||
<ArticleGrid flatArticles={flatArticles} />
|
<ArticleGrid tocItems={tocItems} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { useMemo } from 'react'
|
|
||||||
|
|
||||||
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
||||||
import { useLandingContext } from '@/landings/context/LandingContext'
|
import { useLandingContext } from '@/landings/context/LandingContext'
|
||||||
import { LandingHero } from '@/landings/components/shared/LandingHero'
|
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 { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
|
||||||
import { UtmPreserver } from '@/frame/components/UtmPreserver'
|
import { UtmPreserver } from '@/frame/components/UtmPreserver'
|
||||||
|
|
||||||
import type { ArticleCardItems } from '@/landings/types'
|
|
||||||
|
|
||||||
export const DiscoveryLanding = () => {
|
export const DiscoveryLanding = () => {
|
||||||
const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext()
|
const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext()
|
||||||
|
|
||||||
const flatArticles: ArticleCardItems = useMemo(
|
|
||||||
() => tocItems.flatMap((item) => item.childTocItems || []),
|
|
||||||
[tocItems],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
<UtmPreserver />
|
<UtmPreserver />
|
||||||
@@ -24,7 +15,7 @@ export const DiscoveryLanding = () => {
|
|||||||
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
|
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
|
||||||
<div className="container-xl px-3 px-md-6 mt-6 mb-4">
|
<div className="container-xl px-3 px-md-6 mt-6 mb-4">
|
||||||
<LandingCarousel recommended={recommended} />
|
<LandingCarousel recommended={recommended} />
|
||||||
<ArticleGrid flatArticles={flatArticles} />
|
<ArticleGrid tocItems={tocItems} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DefaultLayout>
|
</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 { TextInput, ActionMenu, ActionList, Token, Pagination } from '@primer/react'
|
||||||
import { SearchIcon } from '@primer/octicons-react'
|
import { SearchIcon } from '@primer/octicons-react'
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
|
|
||||||
import { Link } from '@/frame/components/Link'
|
import { Link } from '@/frame/components/Link'
|
||||||
import { useTranslation } from '@/languages/components/useTranslation'
|
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'
|
import styles from './LandingArticleGridWithFilter.module.scss'
|
||||||
|
|
||||||
type ArticleGridProps = {
|
type ArticleGridProps = {
|
||||||
flatArticles: ArticleCardItems
|
tocItems: TocItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALL_CATEGORIES = 'all_categories'
|
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
|
// Hook to get current articles per page based on screen size
|
||||||
const useResponsiveArticlesPerPage = () => {
|
const useResponsiveArticlesPerPage = () => {
|
||||||
const [articlesPerPage, setArticlesPerPage] = useState(9) // Default to desktop
|
const [articlesPerPage, setArticlesPerPage] = useState(9) // Default to desktop
|
||||||
@@ -42,7 +66,7 @@ const useResponsiveArticlesPerPage = () => {
|
|||||||
return articlesPerPage
|
return articlesPerPage
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
|
export const ArticleGrid = ({ tocItems }: ArticleGridProps) => {
|
||||||
const { t } = useTranslation('product_landing')
|
const { t } = useTranslation('product_landing')
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [selectedCategory, setSelectedCategory] = useState(ALL_CATEGORIES)
|
const [selectedCategory, setSelectedCategory] = useState(ALL_CATEGORIES)
|
||||||
@@ -53,6 +77,12 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
|
|||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
const headingRef = useRef<HTMLHeadingElement>(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)
|
// Reset to first page when articlesPerPage changes (screen size changes)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1)
|
setCurrentPage(1)
|
||||||
@@ -61,13 +91,13 @@ export const ArticleGrid = ({ flatArticles }: ArticleGridProps) => {
|
|||||||
// Extract unique categories from the articles
|
// Extract unique categories from the articles
|
||||||
const categories: string[] = [
|
const categories: string[] = [
|
||||||
ALL_CATEGORIES,
|
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),
|
a.localeCompare(b),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
const applyFilters = () => {
|
const applyFilters = () => {
|
||||||
let results = flatArticles
|
let results = allArticles
|
||||||
|
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
results = results.filter((token) => {
|
results = results.filter((token) => {
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ export type BaseTocItem = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extended type for child TOC items with additional metadata
|
// Extended type for child TOC items with additional metadata
|
||||||
|
// This is recursive - children can also have their own children
|
||||||
export type ChildTocItem = BaseTocItem & {
|
export type ChildTocItem = BaseTocItem & {
|
||||||
octicon?: ValidOcticon | null
|
octicon?: ValidOcticon | null
|
||||||
category?: string[] | null
|
category?: string[] | null
|
||||||
complexity?: string[] | null
|
complexity?: string[] | null
|
||||||
industry?: string[] | null
|
industry?: string[] | null
|
||||||
|
childTocItems?: ChildTocItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main TOC item type that can contain children
|
// Main TOC item type that can contain children
|
||||||
|
|||||||
Reference in New Issue
Block a user