feat: demonstrate shared component architecture

This commit is contained in:
2026-06-29 12:06:18 +01:00
parent 561e948cb4
commit 7d637c4afe
25 changed files with 1319 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
@reference '../../styles/globals.css';
.page {
@apply mx-auto max-w-3xl px-10 py-12;
}
.title {
@apply font-display text-4xl text-fg;
}
.subtitle {
@apply mb-3 mt-10 font-display text-2xl text-fg;
}
.lead {
@apply mt-4 text-lg text-fg;
}
.body {
@apply mt-4 leading-relaxed text-muted;
}
.inlineCode {
@apply rounded bg-surface2 px-1 py-0.5 text-sm text-fg;
font-family: var(--font-mono);
}
.code {
@apply mt-2 overflow-x-auto rounded bg-surface2 p-4 text-sm text-fg;
font-family: var(--font-mono);
border: 1px solid var(--color-line);
}

63
src/app/about/page.tsx Normal file
View File

@@ -0,0 +1,63 @@
import { store } from '@config/store';
import { stores } from '@config/stores';
import styles from './page.module.css';
const exampleCss = `/* ProductCard.module.css — one file, every storefront */
.cta {
@apply mt-5 inline-block text-sm;
color: var(--color-accent);
}
@store executive { .cta { @apply text-[10px] uppercase tracking-[0.28em]; } }
@store crypto { .cta { @apply w-full rounded-lg bg-accent py-3 font-bold; } }
@store supper { .cta { @apply rounded-full bg-accent px-5 py-2.5; } }
@store collector { .cta { @apply border px-4 py-2; } }`;
const examplePlugin = `// postcss-storestyles keeps only the active store's blocks.
// shopSystem = NEXT_PUBLIC_STORE_CODE.split('_')[0] // e.g. "crypto"
if (atRule.params !== shopSystem && !atRule.params.startsWith(shopSystem + '_')) {
atRule.remove(); // dropped from the build
} else {
atRule.replaceWith(atRule.nodes); // unwrapped into plain CSS
}`;
export default function AboutPage() {
return (
<div className={styles.page}>
<h1 className={styles.title}>One codebase, four storefronts</h1>
<p className={styles.lead}>
You are looking at <strong>{store.brandName}</strong> the{' '}
<code className={styles.inlineCode}>{store.code}</code> storefront. The other three {' '}
{stores
.filter((s) => s.code !== store.code)
.map((s) => s.label)
.join(', ')}{' '}
are <em>the same React components and the same CSS files</em>. No component knows which store it is.
</p>
<p className={styles.body}>
Every visual difference colour, type, spacing, even layout lives inside{' '}
<code className={styles.inlineCode}>@store</code> blocks in the CSS. A small PostCSS plugin,{' '}
<code className={styles.inlineCode}>postcss-storestyles</code>, runs at build time and keeps only the blocks
that match <code className={styles.inlineCode}>NEXT_PUBLIC_STORE_CODE</code>. Every other store&apos;s styles
are stripped before they reach the browser, so each build ships one lean stylesheet with zero runtime branching.
</p>
<h2 className={styles.subtitle}>The styles</h2>
<pre className={styles.code}>
<code>{exampleCss}</code>
</pre>
<h2 className={styles.subtitle}>The plugin that decides</h2>
<pre className={styles.code}>
<code>{examplePlugin}</code>
</pre>
<p className={styles.body}>
Each storefront is its own build, deployed to its own Cloudflare Worker. One codebase, four storefronts, each
maintained in the file it belongs to.
</p>
</div>
);
}

15
src/app/page.tsx Normal file
View File

@@ -0,0 +1,15 @@
import Hero from '@/components/Hero/Hero';
import ProductGrid from '@/components/ProductGrid/ProductGrid';
import FeaturedBlock from '@/components/FeaturedBlock/FeaturedBlock';
import Reviews from '@/components/Reviews/Reviews';
export default function HomePage() {
return (
<>
<Hero />
<ProductGrid />
<FeaturedBlock />
<Reviews />
</>
);
}

View File

@@ -0,0 +1,21 @@
@reference '../../../styles/globals.css';
.page {
@apply mx-auto max-w-6xl px-10 pt-8;
}
.back {
@apply inline-block text-sm text-muted hover:text-fg;
}
@store crypto {
.back {
@apply font-mono;
}
}
@store collector {
.back {
@apply font-mono uppercase tracking-[0.1em];
}
}

View File

@@ -0,0 +1,33 @@
import { notFound } from 'next/navigation';
import Link from 'next/link';
import { catalog, getProduct } from '@/data/catalog';
import ProductDetail from '@/components/ProductDetail/ProductDetail';
import styles from './page.module.css';
export function generateStaticParams() {
return catalog.map((product) => ({ slug: product.slug }));
}
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const product = getProduct(slug);
return { title: product?.name };
}
export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const product = getProduct(slug);
if (!product) {
notFound();
}
return (
<div className={styles.page}>
<Link href="/" className={styles.back}>
Back to shop
</Link>
<ProductDetail product={product} />
</div>
);
}