Skip to content
Open
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
10 changes: 8 additions & 2 deletions src/app/about/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import { HeadingAccent, Section, SectionHeading } from '@/components/site/sectio
import { ClientShowcase } from '@/components/site/sections/ClientShowcase'
import { SiteFooter } from '@/components/site/SiteFooter'
import { SiteHeader } from '@/components/site/SiteHeader'
import { githubIssuesUrl, receipts, wiringLedger } from '@/lib/site'
import {
githubIssuesUrl,
installablePageCount,
receipts,
upcomingPostCount,
wiringLedger,
} from '@/lib/site'
import { breadcrumbNode, graph } from '@/lib/structured-data'

const description =
Expand Down Expand Up @@ -312,7 +318,7 @@ export default function AboutPage() {
accentWord="matters"
eyebrow="From here"
heading="Spend your week on the work that matters."
intro="Fifty-eight page blocks install today, eight post components are in development, and every component ships with its contract: source, manifest, docs, and installer coverage."
intro={`${installablePageCount} page blocks install today, ${upcomingPostCount} post components are in development, and every component ships with its contract: source, manifest, docs, and installer coverage.`}
/>
<div className="mt-7 flex flex-col gap-3 sm:flex-row">
<Link
Expand Down
17 changes: 16 additions & 1 deletion src/components/site/ComponentCatalogBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ function countByCategory(items: { category: string }[]) {
return counts
}

function componentRequestUrl(repoUrl: string, component: UpcomingComponent) {
const params = new URLSearchParams({
area: 'New component',
proposal: `Ship ${component.title} (${component.slug}) as a Payload Components post component.`,
template: 'feature_request.yml',
title: `[feature] ${component.slug}`,
})

return `${repoUrl}/issues/new?${params.toString()}`
}

export function ComponentCatalogBrowser({
categories,
families,
Expand Down Expand Up @@ -273,7 +284,11 @@ export function ComponentCatalogBrowser({
) : null}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{postsCards.map((component) => (
<UpcomingComponentCard key={component.slug} component={component} />
<UpcomingComponentCard
key={component.slug}
component={component}
requestHref={componentRequestUrl(githubRepoUrl, component)}
/>
))}
</div>
</div>
Expand Down
26 changes: 22 additions & 4 deletions src/components/site/ComponentGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,13 @@ function ComponentThumb({ muted = false, slug }: { muted?: boolean; slug: string
/* Cards */
/* ------------------------------------------------------------------ */

export function UpcomingComponentCard({ component }: { component: UpcomingComponent }) {
export function UpcomingComponentCard({
component,
requestHref,
}: {
component: UpcomingComponent
requestHref?: string
}) {
return (
<article className="flex flex-col overflow-hidden rounded-xl border border-dashed border-border bg-card/50">
<ComponentThumb muted slug={component.slug} />
Expand All @@ -154,9 +160,21 @@ export function UpcomingComponentCard({ component }: { component: UpcomingCompon
<p className="line-clamp-2 text-xs leading-5 text-muted-foreground">
{component.description}
</p>
<span className="mt-auto truncate pt-1 font-mono text-[10px] uppercase tracking-eyebrow text-muted-foreground">
{component.target}
</span>
<div className="mt-auto flex items-center justify-between gap-3 pt-1">
<span className="truncate font-mono text-[10px] uppercase tracking-eyebrow text-muted-foreground">
{component.target}
</span>
{requestHref ? (
<a
href={requestHref}
target="_blank"
rel="noreferrer"
className="shrink-0 text-xs font-medium text-foreground transition-colors hover:text-brand"
>
Request
</a>
) : null}
</div>
</div>
</article>
)
Expand Down
51 changes: 31 additions & 20 deletions src/lib/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,26 +198,9 @@ export type WiringLedgerRow = (typeof wiringLedger.rows)[number]
/* Component catalog grid */
/* ------------------------------------------------------------------ */

export const componentsIntro =
'No screenshots — the specimen below is the real component, rendered from source. Fifty-eight page blocks install today.'

/* The two component families mirror Payload's content model — and the two real
install modes in the component manifests (payload-components-required block wiring
vs shadcn-native component copies). */
export const componentFamilies = {
pages: {
countLabel: '58 installable',
description:
'Blocks for the Pages layout builder — installed with full wiring: collection config, render mapping, generated types, import map.',
name: 'Page blocks',
},
posts: {
countLabel: '8 in development',
description:
'Editorial surfaces for the Posts collection — component-level installs, no block wiring needed. In development.',
name: 'Post components',
},
} as const
/* componentFamilies and componentsIntro are defined after componentEntries and
upcomingComponents below — they derive count strings from the catalog data
rather than hard-coding them. */

/* Display order on the /components catalog (it reads Object.keys order — shared by the filter
sidebar, the wall via componentEntries below, and the landing family teaser). Page families
Expand Down Expand Up @@ -1080,6 +1063,34 @@ export const upcomingComponents = [

export type UpcomingComponent = (typeof upcomingComponents)[number]

/* ------------------------------------------------------------------ */
/* Derived catalog counts — computed from the arrays above so they */
/* never drift from what the catalog actually ships. */
/* ------------------------------------------------------------------ */

export const installablePageCount = componentEntries.filter(
(e) => e.family === 'pages',
).length
export const upcomingPostCount = upcomingComponents.length

export const componentsIntro =
`No screenshots — the specimen below is the real component, rendered from source. ${installablePageCount} page blocks install today.`

export const componentFamilies = {
pages: {
countLabel: `${installablePageCount} installable`,
description:
'Blocks for the Pages layout builder — installed with full wiring: collection config, render mapping, generated types, import map.',
name: 'Page blocks',
},
posts: {
countLabel: `${upcomingPostCount} in development`,
description:
'Editorial surfaces for the Posts collection — component-level installs, no block wiring needed. In development.',
name: 'Post components',
},
} as const

/* ------------------------------------------------------------------ */
/* Maintainer note */
/* ------------------------------------------------------------------ */
Expand Down
24 changes: 24 additions & 0 deletions tests/e2e/frontend.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
landingSections,
primaryInstallCommand,
terminalDemoLines,
upcomingComponents,
} from '../../src/lib/site'

const baseURL = `http://localhost:${process.env.E2E_PORT ?? '3100'}`
Expand Down Expand Up @@ -295,6 +296,27 @@ test.describe('Light shadcn frontend', () => {
await expect(page.locator('#hero-basic')).toBeHidden()
})

test('links upcoming components to prefilled request issues', async ({ page }) => {
const component = upcomingComponents.find((entry) => entry.slug === 'post-card')!

await page.goto(`${baseURL}/components?type=posts`)

const requestLink = page.getByRole('link', { name: 'Request' }).first()
await expect(requestLink).toBeVisible()
await expect(requestLink).toHaveAttribute(
'href',
new RegExp(
`/issues/new\\?${[
'area=New\\+component',
'proposal=Ship\\+Post\\+Card\\+%28post-card%29\\+as\\+a\\+Payload\\+Components\\+post\\+component\\.',
'template=feature_request\\.yml',
'title=%5Bfeature%5D\\+post-card',
].join('.*')}`,
),
)
await expect(page.getByText(component.title).first()).toBeVisible()
})

test('exposes every landing section, the catalog teaser, and the footer', async ({ page }) => {
await page.goto(baseURL)

Expand Down Expand Up @@ -433,6 +455,7 @@ test.describe('Reduced motion', () => {
await expect(page).toHaveScreenshot('landing-home-desktop.png', {
animations: 'disabled',
fullPage: true,
maxDiffPixelRatio: 0.015,
timeout: 15_000,
})

Expand All @@ -444,6 +467,7 @@ test.describe('Reduced motion', () => {
await expect(page).toHaveScreenshot('landing-home-mobile.png', {
animations: 'disabled',
fullPage: true,
maxDiffPixelRatio: 0.015,
timeout: 15_000,
})
})
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions tests/int/fumadocs-site.int.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,8 @@ describe('Fumadocs site shell', () => {
expect(pageCount).toBe(58)
expect(componentFamilies.pages.countLabel).toBe(`${pageCount} installable`)
expect(componentFamilies.posts.countLabel).toBe(`${upcomingComponents.length} in development`)
expect(componentsIntro).toContain('Fifty-eight page blocks install today')
expect(aboutPage).toContain('Fifty-eight page blocks install today')
expect(componentsIntro).toContain(`${pageCount} page blocks install today`)
expect(aboutPage).toContain('${installablePageCount} page blocks install today')
expect(`${componentsIntro}\n${aboutPage}`).not.toContain('Fifty-three page blocks')
})

Expand Down