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
Binary file removed assets/images/search/copilot-action.png
Binary file not shown.

This file was deleted.

This file was deleted.

3 changes: 2 additions & 1 deletion src/fixtures/tests/playwright-rendering.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ test('open search, and select a general search article', async ({ page }) => {

await page.getByTestId('overlay-search-input').fill('serve playwright')
// Let new suggestions load
await page.waitForTimeout(1000)
const searchOverlay = page.getByTestId('general-autocomplete-suggestions')
await expect(searchOverlay.getByText('For Playwright')).toBeVisible()
// Navigate to general search item, "For Playwright"
await page.keyboard.press('ArrowDown')
// Select the general search item, "For Playwright"
Expand Down
8 changes: 8 additions & 0 deletions src/frame/lib/frontmatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,14 @@ export const schema: Schema = {
maxItems: 9,
description: 'Array of articles to feature in the carousel section',
},
// Included categories for article grid filtering
includedCategories: {
type: 'array',
items: {
type: 'string',
},
description: 'Array of category names to include in the article grid dropdown filter',
},
},
}

Expand Down
17 changes: 15 additions & 2 deletions src/landings/components/bespoke/BespokeLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ import { UtmPreserver } from '@/frame/components/UtmPreserver'
import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'

export const BespokeLanding = () => {
const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext()
const {
title,
intro,
heroImage,
introLinks,
tocItems,
recommended,
includedCategories,
landingType,
} = useLandingContext()

return (
<DefaultLayout>
Expand All @@ -16,7 +25,11 @@ export const BespokeLanding = () => {

<div className="container-xl px-3 px-md-6 mt-6 mb-4">
<LandingCarousel recommended={recommended} />
<ArticleGrid tocItems={tocItems} />
<ArticleGrid
tocItems={tocItems}
includedCategories={includedCategories}
landingType={landingType}
/>
</div>
</div>
</DefaultLayout>
Expand Down
17 changes: 15 additions & 2 deletions src/landings/components/discovery/DiscoveryLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
import { UtmPreserver } from '@/frame/components/UtmPreserver'

export const DiscoveryLanding = () => {
const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext()
const {
title,
intro,
heroImage,
introLinks,
tocItems,
recommended,
includedCategories,
landingType,
} = useLandingContext()

return (
<DefaultLayout>
Expand All @@ -15,7 +24,11 @@ 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 tocItems={tocItems} />
<ArticleGrid
tocItems={tocItems}
includedCategories={includedCategories}
landingType={landingType}
/>
</div>
</div>
</DefaultLayout>
Expand Down
36 changes: 30 additions & 6 deletions src/landings/components/shared/LandingArticleGridWithFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import cx from 'classnames'
import { Link } from '@/frame/components/Link'
import { useTranslation } from '@/languages/components/useTranslation'
import { ArticleCardItems, ChildTocItem, TocItem } from '@/landings/types'
import { LandingType } from '@/landings/context/LandingContext'

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

type ArticleGridProps = {
tocItems: TocItem[]
includedCategories?: string[]
landingType: LandingType
}

const ALL_CATEGORIES = 'all_categories'
Expand Down Expand Up @@ -66,7 +69,7 @@ const useResponsiveArticlesPerPage = () => {
return articlesPerPage
}

export const ArticleGrid = ({ tocItems }: ArticleGridProps) => {
export const ArticleGrid = ({ tocItems, includedCategories, landingType }: ArticleGridProps) => {
const { t } = useTranslation('product_landing')
const [searchQuery, setSearchQuery] = useState('')
const [selectedCategory, setSelectedCategory] = useState(ALL_CATEGORIES)
Expand All @@ -83,21 +86,42 @@ export const ArticleGrid = ({ tocItems }: ArticleGridProps) => {
[tocItems],
)

// Filter articles based on includedCategories for discovery landing pages
// For bespoke landing pages, show all articles regardless of includedCategories
const filteredArticlesByLandingType = useMemo(() => {
if (landingType === 'discovery' && includedCategories && includedCategories.length > 0) {
// For discovery pages, only include articles that have at least one matching category
return allArticles.filter((article) => {
if (!article.category || article.category.length === 0) return false
return article.category.some((cat) =>
includedCategories.some((included) => included.toLowerCase() === cat.toLowerCase()),
)
})
}
// For bespoke pages or when includedCategories is empty/undefined, return all articles
return allArticles
}, [allArticles, includedCategories, landingType])

// Reset to first page when articlesPerPage changes (screen size changes)
useEffect(() => {
setCurrentPage(1)
}, [articlesPerPage])

// Extract unique categories from the articles
// Extract unique categories from the filtered articles, filtering dropdown by includedCategories if provided
const categories: string[] = [
ALL_CATEGORIES,
...Array.from(new Set(allArticles.flatMap((item) => item.category || []))).sort((a, b) =>
a.localeCompare(b),
),
...Array.from(new Set(filteredArticlesByLandingType.flatMap((item) => item.category || [])))
.filter((category) => {
if (!includedCategories || includedCategories.length === 0) return true
// Case-insensitive comparison for dropdown filtering
const lowerCategory = category.toLowerCase()
return includedCategories.some((included) => included.toLowerCase() === lowerCategory)
})
.sort((a, b) => a.localeCompare(b)),
]

const applyFilters = () => {
let results = allArticles
let results = filteredArticlesByLandingType

if (searchQuery) {
results = results.filter((token) => {
Expand Down
3 changes: 3 additions & 0 deletions src/landings/context/LandingContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export type LandingContextT = {
introLinks?: Record<string, string>
// For journey landing pages
journeyTracks?: JourneyTrack[]
// For article grid category filtering
includedCategories?: string[]
}

export const LandingContext = createContext<LandingContextT | null>(null)
Expand Down Expand Up @@ -83,5 +85,6 @@ export const getLandingContextFromRequest = async (
introLinks: page.introLinks || null,
recommended,
journeyTracks,
includedCategories: page.includedCategories || [],
}
}
Loading