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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
36 changes: 18 additions & 18 deletions .syncpackrc.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"versionGroups": [
{
"label": "React is pinned for all demos (19.x is required by @react-three/fiber 9)",
"label": "React is pinned for all examples (19.x is required by @react-three/fiber 9)",
"packages": [
"@demo/**"
"@example/**"
],
"dependencies": [
"react",
Expand All @@ -12,19 +12,19 @@
"pinVersion": "19.2.8"
},
{
"label": "React types are pinned for all demos",
"label": "React types are pinned for all examples",
"packages": [
"@demo/**"
"@example/**"
],
"dependencies": [
"@types/react"
],
"pinVersion": "19.2.17"
},
{
"label": "React DOM types are pinned for all demos",
"label": "React DOM types are pinned for all examples",
"packages": [
"@demo/**"
"@example/**"
],
"dependencies": [
"@types/react-dom"
Expand All @@ -34,7 +34,7 @@
{
"label": "three is pinned: the @splinetool/loader patch is keyed to r165, and postprocessing 6.36.6 needs < 0.173",
"packages": [
"@demo/**"
"@example/**"
],
"dependencies": [
"three"
Expand All @@ -44,7 +44,7 @@
{
"label": "@react-three/fiber is pinned: 9.x is the React 19 line, and a caret could raise the three floor",
"packages": [
"@demo/**"
"@example/**"
],
"dependencies": [
"@react-three/fiber"
Expand All @@ -54,17 +54,17 @@
{
"label": "@react-three/drei is pinned: 10.x is the line matching @react-three/fiber 9",
"packages": [
"@demo/**"
"@example/**"
],
"dependencies": [
"@react-three/drei"
],
"pinVersion": "10.7.7"
},
{
"label": "Update lamina to 1.2.2 for all demos.",
"label": "Update lamina to 1.2.2 for all examples.",
"packages": [
"@demo/**"
"@example/**"
],
"dependencies": [
"lamina"
Expand All @@ -74,17 +74,17 @@
{
"label": "postprocessing is pinned to the last release accepting three 0.165 (6.36.7+ needs >= 0.174); pnpm.overrides pins the transitive copy too",
"packages": [
"@demo/**"
"@example/**"
],
"dependencies": [
"postprocessing"
],
"pinVersion": "6.36.6"
},
{
"label": "suspend-react is pinned for all demos (the suspend(promise) pattern requires >= 0.1.3)",
"label": "suspend-react is pinned for all examples (the suspend(promise) pattern requires >= 0.1.3)",
"packages": [
"@demo/**"
"@example/**"
],
"dependencies": [
"suspend-react"
Expand All @@ -94,17 +94,17 @@
{
"label": "@splinetool/loader is pinned to the last release that parses the repo's 2022-era .splinecode scenes (0.9.156+ silently fall back to a default gray material or crash; the three-r165 pnpm patch is keyed to this exact version)",
"packages": [
"@demo/**"
"@example/**"
],
"dependencies": [
"@splinetool/loader"
],
"pinVersion": "0.9.153"
},
{
"label": "Demos must use identical versions of shared dependencies",
"label": "Examples must use identical versions of shared dependencies",
"packages": [
"@demo/**"
"@example/**"
],
"dependencies": [
"@react-three/postprocessing",
Expand Down Expand Up @@ -145,7 +145,7 @@
{
"label": "Workspace deps use workspace protocol",
"dependencies": [
"@demo/**"
"@example/**"
],
"dependencyTypes": [
"prod"
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ Single-context: one `CONTEXT.md` + `docs/adr/` at the repo root (created lazily

**`components/ui/*` is vendored, not ours — never edit it.** Those files must stay what the registry emits (modulo `prettier`, which the repo runs over everything), so that `shadcn@latest add <component> --overwrite` is always a safe no-op and any of them can be swapped for the stock version tomorrow. If a component doesn't do what you need, the fix goes at the call site — `className` for layout, the built-in `variant`/`size` props for looks, composition (wrap it, or use `asChild`) for behaviour — or into the theme tokens in `app/globals.css`. Never into the component file. If you genuinely cannot express it from outside, write your own component next to it under `components/` rather than forking the vendored one.

**The colour tokens are Material Design 3.** Every shadcn token in `app/globals.css` reads an `--md-sys-color-*` role, and [`material-theme-builder`](https://github.com/abernier/material-theme-builder) derives all of them from one source hex. Retuning the palette means changing `MCU_SOURCE` in `app/layout.tsx` — or the `scheme` / `contrast` / core-colour overrides in the `builder()` call next to it — never editing a token by hand. The site runs `scheme: "monochrome"`, which derives every role from the source's _tone_ alone and discards its hue: the chrome is greyscale on purpose, so that the only colour on a demo page is the demo. `MCU_SOURCE` still matters — swap the scheme and the mint comes straight back. Anything the m3 roles don't cover belongs in that call's `customColors`, which mints `--md-sys-color-<name>` and a matching `-on-` foreground.
**The colour tokens are Material Design 3.** Every shadcn token in `app/globals.css` reads an `--md-sys-color-*` role, and [`material-theme-builder`](https://github.com/abernier/material-theme-builder) derives all of them from one source hex. Retuning the palette means changing `MCU_SOURCE` in `app/layout.tsx` — or the `scheme` / `contrast` / core-colour overrides in the `builder()` call next to it — never editing a token by hand. The site runs `scheme: "monochrome"`, which derives every role from the source's _tone_ alone and discards its hue: the chrome is greyscale on purpose, so that the only colour on a example page is the example. `MCU_SOURCE` still matters — swap the scheme and the mint comes straight back. Anything the m3 roles don't cover belongs in that call's `customColors`, which mints `--md-sys-color-<name>` and a matching `-on-` foreground.

**It has to stay a build-time call.** `layout.tsx` is a server component, so `builder(...).toCss()` runs once at build and the CSS ships inside the prerendered HTML. Don't move it into a client component, and don't reach for the package's `<Mcu>`: this app is `output: "export"`, so the browser paints the whole page well before hydration, and anything that supplies the colours later gives you a frame with none of them. Verify a change here by grepping the built `out/index.html` for `--md-sys-color-surface:` — not just by looking at the running app, where hydration hides the gap.

Two things to know before styling:

- **The whole app is on Tailwind.** There is no `<Style>` component and no `@scope` block left anywhere in `apps/website` — every rule is a utility at its call site, a `components/ui/*` variant, or a token in `app/globals.css`. Don't reintroduce injected `<style>`: it lands unlayered, so it outranks Preflight _and_ every utility, and a rule that always wins is a rule nobody can override from a call site. The two things utilities can't express — the source-of-truth palette, and Preflight itself — already have homes in `globals.css`.
- **Sources are declared explicitly.** Tailwind's automatic source detection finds nothing in this app, so `app/globals.css` lists `@source` entries. Add one if you put components somewhere new.
- **A repeated group of controls is one tab stop, not N.** `hooks/use-roving-tabindex.ts` gives the demo list and the demo bar a roving tabindex — arrows move within the group, Tab moves past it. The whole site is six tab stops; if you add a control, check it is not a seventh hiding inside one of those groups. Hand the hook the container's ref and spread what it returns.
- **A repeated group of controls is one tab stop, not N.** `hooks/use-roving-tabindex.ts` gives the example list and the example bar a roving tabindex — arrows move within the group, Tab moves past it. The whole site is six tab stops; if you add a control, check it is not a seventh hiding inside one of those groups. Hand the hook the container's ref and spread what it returns.

`demos/` is deliberately Tailwind-free; don't introduce it there.
`examples/` is deliberately Tailwind-free; don't introduce it there.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
index: [demos](demos)
index: [examples](examples)

To use a given [`basic-demo`](demos/basic-demo) as a template for a new
To use a given [`basic-example`](examples/basic-example) as a template for a new
`myproject`:

```sh
$ npx degit pmndrs/examples/demos/basic-demo myproject
$ npx degit pmndrs/examples/examples/basic-example myproject
$ code myproject
```

## Demo metadata
## Example metadata

Every demo has a `pmndrs.json` file containing the catalog metadata used by the
Every example has a `pmndrs.json` file containing the catalog metadata used by the
website:

```json
{
"$schema": "../../schemas/pmndrs.schema.json",
"title": "Basic Demo",
"title": "Basic Example",
"description": "Shows how to form self-contained components with their own state and user interaction.",
"tags": ["interaction", "pointer-events"],
"authors": [],
Expand All @@ -27,7 +27,7 @@ website:
```

Use package names for `libraries`; each entry must also be a dependency of the
demo. `publishedAt` is optional and uses `YYYY-MM-DD` when the original
example. `publishedAt` is optional and uses `YYYY-MM-DD` when the original
publication date is known. Add externally sourced models, textures, fonts,
audio, and other assets to `assets` with their creator, source, and license when
available.
Expand Down Expand Up @@ -73,7 +73,7 @@ Then `npx serve out`.

This will:

1. execute `^build2` which will `vite build` each `demos/*` with:
1. execute `^build2` which will `vite build` each `examples/*` with:

- a `--base` set to `${BASE_PATH}/${app_name}`
- a custom vite `--config`, whith a `monkey()` plugin that will:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import { useRouter } from "next/navigation";

export function Dev({ demoname }: { demoname: string }) {
export function Dev({ examplename }: { examplename: string }) {
const { refresh } = useRouter();

const cmd = `pnpm --filter @demo/${demoname} dev3`;
const cmd = `pnpm --filter @example/${examplename} dev3`;

return (
/* Was two rules in two files: the box (`h-full`, the padding, the
Expand All @@ -21,7 +21,7 @@ export function Dev({ demoname }: { demoname: string }) {
that it sits against the left edge. */
<div className="mx-auto grid h-full w-[min(100%,46rem)] place-items-center p-4">
<div className="grid justify-items-start gap-4">
<p>Start this demo with :</p>
<p>Start this example with :</p>
{/* `relative` for the copy link, which hangs off the top-right corner
rather than sitting in the flow above it. */}
<pre
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ReactNode } from "react";
import { RxInfoCircled } from "react-icons/rx";

import { getLibraryLabel } from "@/const/libraries";
import type { Demo } from "@/lib/helper";
import type { Example } from "@/lib/helper";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
Expand Down Expand Up @@ -48,9 +48,9 @@ function Section({ title, children }: { title: string; children: ReactNode }) {
);
}

export function Info({ demo }: { demo: Demo }) {
export function Info({ example }: { example: Example }) {
const libraryLabels = Array.from(
new Set(demo.libraries.map(getLibraryLabel)),
new Set(example.libraries.map(getLibraryLabel)),
);

return (
Expand All @@ -68,7 +68,7 @@ export function Info({ demo }: { demo: Demo }) {
plain `lg` would add the text sizes' horizontal padding and
stretch the disc into a lozenge. */
size="icon-lg"
aria-label="Show demo info"
aria-label="Show example info"
/* `secondary` reads the same open or shut; the panel is
anchored to this button, so it should look pressed while the
panel is up. */
Expand Down Expand Up @@ -96,28 +96,28 @@ export function Info({ demo }: { demo: Demo }) {
className="max-h-(--radix-popover-content-available-height) w-[min(24rem,calc(100dvw-1.5rem))] overflow-y-auto overscroll-contain rounded-xl border border-border backdrop-blur-sm duration-200"
>
<PopoverHeader>
<PopoverTitle>{demo.title}</PopoverTitle>
{demo.description && (
<PopoverDescription>{demo.description}</PopoverDescription>
<PopoverTitle>{example.title}</PopoverTitle>
{example.description && (
<PopoverDescription>{example.description}</PopoverDescription>
)}
</PopoverHeader>

{demo.authors.length > 0 && (
<Section title={demo.authors.length > 1 ? "Authors" : "Author"}>
<p>{demo.authors.join(", ")}</p>
{example.authors.length > 0 && (
<Section title={example.authors.length > 1 ? "Authors" : "Author"}>
<p>{example.authors.join(", ")}</p>
</Section>
)}

{demo.publishedAt && (
{example.publishedAt && (
<Section title="Published">
<p>{formatDate(demo.publishedAt)}</p>
<p>{formatDate(example.publishedAt)}</p>
</Section>
)}

{demo.tags.length > 0 && (
{example.tags.length > 0 && (
<Section title="Tags">
<ul className="flex flex-wrap gap-1">
{demo.tags.map((tag) => (
{example.tags.map((tag) => (
<Badge key={tag} variant="secondary" asChild>
<li>{tag}</li>
</Badge>
Expand All @@ -138,15 +138,15 @@ export function Info({ demo }: { demo: Demo }) {
</Section>
)}

{demo.assets.length > 0 && (
{example.assets.length > 0 && (
<Section title="Assets">
{/* The one part of the panel that is a list of *things* rather than
a value: each asset has a name, someone it is by, and terms it
comes under. `ItemGroup` carries the `role="list"` the `<ul>`
used to. `muted` rather than `outline` — the panel is already a
surface, and a border inside it would read as a second one. */}
<ItemGroup>
{demo.assets.map((asset) => (
{example.assets.map((asset) => (
<Item key={asset.name} variant="muted" size="xs">
<ItemContent>
<ItemTitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ import {
import { useRovingTabIndex } from "@/hooks/use-roving-tabindex";

export function Social({
demoname,
examplename,
embed_url,
}: {
demoname: string;
examplename: string;
embed_url: string;
}) {
const groupRef = useRef<HTMLDivElement>(null);
/* The bar reads as one object, so it answers to Tab as one: five icon
buttons would otherwise be five stops for what is visibly a single pill. */
const roving = useRovingTabIndex(groupRef, { orientation: "horizontal" });

const command = `npx -y degit pmndrs/examples/demos/${demoname} ${demoname} && cd ${demoname} && npm i && npm run dev`;
const command = `npx -y degit pmndrs/examples/examples/${examplename} ${examplename} && cd ${examplename} && npm i && npm run dev`;

const handleClick = async () => {
await navigator.clipboard.writeText(command);
Expand All @@ -36,7 +36,7 @@ export function Social({
description: (
<div className="flex min-w-0 flex-col gap-2">
<span>
Paste in your terminal to install <strong>{demoname}</strong>{" "}
Paste in your terminal to install <strong>{examplename}</strong>{" "}
locally.
</span>
<code className="rounded-sm bg-muted px-2 py-1 font-mono text-[0.7rem] leading-relaxed break-all whitespace-pre-wrap">
Expand All @@ -55,25 +55,25 @@ export function Social({
{
label: "code",
icon: <SiGithub />,
href: `https://github.com/pmndrs/examples/tree/main/demos/${demoname}`,
href: `https://github.com/pmndrs/examples/tree/main/examples/${examplename}`,
},
{
label: "stackblitz",
icon: <SiStackblitz />,
href: `https://stackblitz.com/github/pmndrs/examples/tree/main/demos/${demoname}`,
href: `https://stackblitz.com/github/pmndrs/examples/tree/main/examples/${examplename}`,
},
{
label: "codesandbox",
icon: <SiCodesandbox />,
href: `https://codesandbox.io/s/github/pmndrs/examples/tree/main/demos/${demoname}`,
href: `https://codesandbox.io/s/github/pmndrs/examples/tree/main/examples/${examplename}`,
},
{ label: "degit", icon: <GoCommandPalette /> },
];

return (
/* `ButtonGroup` is a div, so the landmark has to come from somewhere: four
of the five entries leave the page. */
<nav aria-label="Demo links">
<nav aria-label="Example links">
{/* The group draws no box of its own — the buttons, sitting flush, are
the pill. It only needs the matching radius so the shadow traces that
pill rather than the rectangle around it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ export default function Layout({
params: ComponentProps<typeof Page>["params"];
children: React.ReactNode;
}>) {
// const { demoname } = params;
// const { examplename } = params;

return (
/* `min-h-0`/`min-w-0` against the grid item's `auto` minimum: without them
a demo taller than the viewport grows this box instead of being scaled
a example taller than the viewport grows this box instead of being scaled
down inside it. */
<div className="grid h-full min-h-0 w-full min-w-0">{children}</div>
);
Expand Down
Loading
Loading