From 0280d2034972feccab1fbfd9a8fff2724228a730 Mon Sep 17 00:00:00 2001 From: Antoine BERNIER Date: Sun, 16 Aug 2026 12:20:48 +0200 Subject: [PATCH 1/2] feat: take the MD3 colour layer from pmndrs/design-system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs stops maintaining its own copy of the colour system. The ~60-line hand-written @theme mapping and the 33-line shadcn remap in globals.css are replaced by one registry item, pmndrs/design-system/md3, which carries both plus the config they map — so they can no longer drift from each other. react-mcu gives way to material-theme-builder, which is what that item installs. Mostly a rename: --mcu-* becomes --md-sys-color-*. The --color-* names in the @theme mapping are identical either way, so every bg-surface, bg-primary-container and text-on-surface-variant keeps working untouched. Two components referenced the raw variables rather than a utility — Code and Sandpack — and those are the only component edits. The seed moves to poimandres mint. It also stops being restated: it lived in the layout, in build.yml's input defaults and hard-coded again in ci.yml, three copies free to disagree. Now src/lib/md3.ts holds it and the workflow inputs default to empty, so a site overrides the seed by setting one and inherits the pmndrs default by leaving it alone. Also applies the poimandres preset. It is close to a no-op here by design: radius already matched, the colour half is overridden by the remap, and the font half is deliberately not applied because docs self-hosts Inter and Inconsolata. What it does land is the identity in components.json and a --sidebar* block in oklch rather than hsl. Verified: no token lost — all 67 --color-* names the old mapping defined are in the 191 the new one does — and a full build renders mint, with bg-surface-dim, bg-note-container and text-primary-fixed all emitting. Refs pmndrs/design-system#1 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDvnsrory1aJsoNALn5aLB --- .changeset/md3-tier1.md | 7 + .github/workflows/build.yml | 19 +- .github/workflows/ci.yml | 8 - components.json | 3 + package.json | 2 +- pnpm-lock.yaml | 46 +++-- src/app/globals.css | 222 +++++++++-------------- src/app/layout.tsx | 33 +--- src/components/mdx/Code/Code.tsx | 2 +- src/components/mdx/Sandpack/Sandpack.tsx | 6 +- src/lib/md3.ts | 38 ++++ 11 files changed, 184 insertions(+), 202 deletions(-) create mode 100644 .changeset/md3-tier1.md create mode 100644 src/lib/md3.ts diff --git a/.changeset/md3-tier1.md b/.changeset/md3-tier1.md new file mode 100644 index 000000000..b5ff48b42 --- /dev/null +++ b/.changeset/md3-tier1.md @@ -0,0 +1,7 @@ +--- +'@pmndrs/docs': minor +--- + +Take the Material Design 3 colour layer from `pmndrs/design-system` instead of maintaining it here. The hand-written `@theme` mapping and the shadcn remap — around 105 lines that had to be kept in step with the colour package by hand — are replaced by one registry item, `pmndrs/design-system/md3`, which brings its own Tailwind mapping and the config it maps. `react-mcu` gives way to `material-theme-builder`, so the CSS variables are now `--md-sys-color-*`; every `bg-surface` / `text-on-surface-variant` utility keeps working untouched, because the `--color-*` names on top of them are identical. + +The seed moves to poimandres mint (`#5de4c7`) — this is the visible change — and it now lives in exactly one place, `src/lib/md3.ts`, rather than being restated in the layout and in two workflows. `THEME_PRIMARY`, `THEME_SCHEME`, `THEME_CONTRAST` and the five alert colours still override it per deployment; a site that wants its own seed sets the workflow input, and one that doesn't inherits the pmndrs default. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99cbf4159..fe35e3742 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,30 +34,33 @@ on: type: string discord: type: string + # Left empty on purpose: the pmndrs defaults live in src/lib/md3.ts, installed + # from pmndrs/design-system. Set one of these only to override the seed for a + # site — an empty value falls through to the shared default. theme_primary: type: string - default: '#323e48' + default: '' theme_scheme: type: string - default: 'tonalSpot' + default: '' theme_contrast: type: string - default: '0' + default: '' theme_note: type: string - default: '#1f6feb' + default: '' theme_tip: type: string - default: '#238636' + default: '' theme_important: type: string - default: '#8957e5' + default: '' theme_warning: type: string - default: '#d29922' + default: '' theme_caution: type: string - default: '#da3633' + default: '' docker_tag: type: string default: '3' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d9040247..d512085aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,14 +114,6 @@ jobs: --build-env LOGO=gutenberg.jpg \ --build-env GITHUB="https://github.com/${{ github.repository }}" \ --build-env DISCORD="${{ secrets.DISCORD }}" \ - --build-env THEME_PRIMARY="#323e48" \ - --build-env THEME_SCHEME="tonalSpot" \ - --build-env THEME_CONTRAST="0" \ - --build-env THEME_NOTE="#1f6feb" \ - --build-env THEME_TIP="#238636" \ - --build-env THEME_IMPORTANT="#8957e5" \ - --build-env THEME_WARNING="#d29922" \ - --build-env THEME_CAUTION="#da3633" \ --build-env CONTRIBUTORS_PAT="${{ secrets.GITHUB_TOKEN }}" \ > deployment-url.txt diff --git a/components.json b/components.json index f826c543e..806fffde8 100644 --- a/components.json +++ b/components.json @@ -18,5 +18,8 @@ "lib": "@/lib", "hooks": "@/hooks" }, + "rtl": false, + "menuColor": "default", + "menuAccent": "subtle", "registries": {} } diff --git a/package.json b/package.json index 413dada7a..578df7d8a 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "lodash-es": "^4.17.21", "lucide-react": "^0.563.0", "match-sorter": "^6.3.4", + "material-theme-builder": "^3.2.0", "mcp-handler": "^1.0.7", "mermaid": "^11.12.2", "next": "^16.1.3", @@ -75,7 +76,6 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-icons": "^5.5.0", - "react-mcu": "^1.1.0", "rehype-github-alerts": "^3.0.0", "rehype-prism-plus": "^2.0.0", "remark-gfm": "^4.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d325e8ad1..f1495224b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: match-sorter: specifier: ^6.3.4 version: 6.4.0 + material-theme-builder: + specifier: ^3.2.0 + version: 3.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) mcp-handler: specifier: ^1.0.7 version: 1.0.7(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))(next@16.1.3(@babel/core@7.28.6)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) @@ -86,9 +89,6 @@ importers: react-icons: specifier: ^5.5.0 version: 5.5.0(react@19.2.3) - react-mcu: - specifier: ^1.1.0 - version: 1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rehype-github-alerts: specifier: ^3.0.0 version: 3.0.0 @@ -2923,6 +2923,10 @@ packages: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -4627,6 +4631,13 @@ packages: resolution: {integrity: sha512-d4664ahzdL1QTTvmK1iI0JsrxWeJ6gn33qkYtnPg3mcn+naBLtXSgSPOe+X2vUgtgGwaAk3eiaj7gwKjjMAq+Q==} deprecated: This was arguably a breaking change. Not in API, but more results can be returned. Upgrade to the next major when you are ready for that + material-theme-builder@3.2.0: + resolution: {integrity: sha512-w0mKUYs2u6C3vnZ5lYV4/dpTvutdhhMc6cebhPOiFJvmKb++wCTlvEDk8nvYtcANI2KzM2isupU80A3zx6oKPg==} + hasBin: true + peerDependencies: + react: ^19.2.3 + react-dom: ^19.2.3 + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -5431,12 +5442,6 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-mcu@1.1.0: - resolution: {integrity: sha512-Yf2WCNgGHNhpnYNJsbuzQW5Ka6ke/KZfgBj/MDefWrP6SLaySiIaqoMxnQQNUvHYnG7iNX7BQlL5clYJZlE2og==} - peerDependencies: - react: ^19.2.3 - react-dom: ^19.2.3 - react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -6543,6 +6548,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -9428,6 +9436,8 @@ snapshots: commander@13.1.0: {} + commander@14.0.3: {} + commander@2.20.3: {} commander@7.2.0: {} @@ -11411,6 +11421,15 @@ snapshots: '@babel/runtime': 7.28.6 remove-accents: 0.5.0 + material-theme-builder@3.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@material/material-color-utilities': 0.3.0 + commander: 14.0.3 + lodash-es: 4.17.22 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + zod: 4.4.3 + math-intrinsics@1.1.0: {} mcp-handler@1.0.7(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))(next@16.1.3(@babel/core@7.28.6)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)): @@ -12446,13 +12465,6 @@ snapshots: react-is@17.0.2: {} - react-mcu@1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): - dependencies: - '@material/material-color-utilities': 0.3.0 - lodash-es: 4.17.22 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-remove-scroll-bar@2.3.8(@types/react@19.2.8)(react@19.2.3): dependencies: react: 19.2.3 @@ -13812,4 +13824,6 @@ snapshots: zod@3.25.76: {} + zod@4.4.3: {} + zwitch@2.0.4: {} diff --git a/src/app/globals.css b/src/app/globals.css index 5177d5ac1..7ece39786 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,5 +1,6 @@ @import 'tailwindcss'; @import 'tw-animate-css'; +@import 'material-theme-builder/tailwind.css'; @theme { --font-sans: var(--font-inter), sans-serif; @@ -8,77 +9,6 @@ /* Keyframes */ --animate-collapsible-down: collapsible-down var(--collapsible-down-duration, 0s) ease-out; --animate-collapsible-up: collapsible-up var(--collapsible-up-duration, 0s) ease-out; - - /* Material Design colors from react-mcu */ - --color-background: var(--mcu-background); - --color-on-background: var(--mcu-on-background); - --color-surface: var(--mcu-surface); - --color-surface-dim: var(--mcu-surface-dim); - --color-surface-bright: var(--mcu-surface-bright); - --color-surface-container-lowest: var(--mcu-surface-container-lowest); - --color-surface-container-low: var(--mcu-surface-container-low); - --color-surface-container: var(--mcu-surface-container); - --color-surface-container-high: var(--mcu-surface-container-high); - --color-surface-container-highest: var(--mcu-surface-container-highest); - --color-on-surface: var(--mcu-on-surface); - --color-on-surface-variant: var(--mcu-on-surface-variant); - --color-outline: var(--mcu-outline); - --color-outline-variant: var(--mcu-outline-variant); - --color-inverse-surface: var(--mcu-inverse-surface); - --color-inverse-on-surface: var(--mcu-inverse-on-surface); - --color-primary: var(--mcu-primary); - --color-on-primary: var(--mcu-on-primary); - --color-primary-container: var(--mcu-primary-container); - --color-on-primary-container: var(--mcu-on-primary-container); - --color-primary-fixed: var(--mcu-primary-fixed); - --color-primary-fixed-dim: var(--mcu-primary-fixed-dim); - --color-on-primary-fixed: var(--mcu-on-primary-fixed); - --color-on-primary-fixed-variant: var(--mcu-on-primary-fixed-variant); - --color-inverse-primary: var(--mcu-inverse-primary); - --color-secondary: var(--mcu-secondary); - --color-on-secondary: var(--mcu-on-secondary); - --color-secondary-container: var(--mcu-secondary-container); - --color-on-secondary-container: var(--mcu-on-secondary-container); - --color-secondary-fixed: var(--mcu-secondary-fixed); - --color-secondary-fixed-dim: var(--mcu-secondary-fixed-dim); - --color-on-secondary-fixed: var(--mcu-on-secondary-fixed); - --color-on-secondary-fixed-variant: var(--mcu-on-secondary-fixed-variant); - --color-tertiary: var(--mcu-tertiary); - --color-on-tertiary: var(--mcu-on-tertiary); - --color-tertiary-container: var(--mcu-tertiary-container); - --color-on-tertiary-container: var(--mcu-on-tertiary-container); - --color-tertiary-fixed: var(--mcu-tertiary-fixed); - --color-tertiary-fixed-dim: var(--mcu-tertiary-fixed-dim); - --color-on-tertiary-fixed: var(--mcu-on-tertiary-fixed); - --color-on-tertiary-fixed-variant: var(--mcu-on-tertiary-fixed-variant); - --color-error: var(--mcu-error); - --color-on-error: var(--mcu-on-error); - --color-error-container: var(--mcu-error-container); - --color-on-error-container: var(--mcu-on-error-container); - --color-scrim: var(--mcu-scrim); - --color-shadow: var(--mcu-shadow); - - /* Custom alert colors */ - --color-note: var(--mcu-note); - --color-on-note: var(--mcu-on-note); - --color-note-container: var(--mcu-note-container); - --color-on-note-container: var(--mcu-on-note-container); - --color-tip: var(--mcu-tip); - --color-on-tip: var(--mcu-on-tip); - --color-tip-container: var(--mcu-tip-container); - --color-on-tip-container: var(--mcu-on-tip-container); - --color-important: var(--mcu-important); - --color-on-important: var(--mcu-on-important); - --color-important-container: var(--mcu-important-container); - --color-on-important-container: var(--mcu-on-important-container); - --color-warning: var(--mcu-warning); - --color-on-warning: var(--mcu-on-warning); - --color-warning-container: var(--mcu-warning-container); - --color-on-warning-container: var(--mcu-on-warning-container); - --color-caution: var(--mcu-caution); - --color-on-caution: var(--mcu-on-caution); - --color-caution-container: var(--mcu-caution-container); - --color-on-caution-container: var(--mcu-on-caution-container); } @keyframes collapsible-down { @@ -335,17 +265,16 @@ pre[class*='language-'] { @custom-variant dark (&:is(.dark *)); :root { - --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); --card: oklch(1 0 0); --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); + --primary: oklch(0.511 0.096 186.391); + --primary-foreground: oklch(0.984 0.014 180.72); + --secondary: oklch(0.967 0.001 286.375); + --secondary-foreground: oklch(0.21 0.006 285.885); --muted: oklch(0.97 0 0); --muted-foreground: oklch(0.556 0 0); --accent: oklch(0.97 0 0); @@ -354,19 +283,20 @@ pre[class*='language-'] { --border: oklch(0.922 0 0); --input: oklch(0.922 0 0); --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: hsl(0 0% 98%); - --sidebar-foreground: hsl(240 5.3% 26.1%); - --sidebar-primary: hsl(240 5.9% 10%); - --sidebar-primary-foreground: hsl(0 0% 98%); - --sidebar-accent: hsl(240 4.8% 95.9%); - --sidebar-accent-foreground: hsl(240 5.9% 10%); - --sidebar-border: hsl(220 13% 91%); - --sidebar-ring: hsl(217.2 91.2% 59.8%); + --chart-1: oklch(0.855 0.138 181.071); + --chart-2: oklch(0.704 0.14 182.503); + --chart-3: oklch(0.6 0.118 184.704); + --chart-4: oklch(0.511 0.096 186.391); + --chart-5: oklch(0.437 0.078 188.216); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.6 0.118 184.704); + --sidebar-primary-foreground: oklch(0.984 0.014 180.72); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); } @theme inline { @@ -408,6 +338,26 @@ pre[class*='language-'] { --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); + --color-note: var(--md-sys-color-note); + --color-on-note: var(--md-sys-color-on-note); + --color-note-container: var(--md-sys-color-note-container); + --color-on-note-container: var(--md-sys-color-on-note-container); + --color-tip: var(--md-sys-color-tip); + --color-on-tip: var(--md-sys-color-on-tip); + --color-tip-container: var(--md-sys-color-tip-container); + --color-on-tip-container: var(--md-sys-color-on-tip-container); + --color-important: var(--md-sys-color-important); + --color-on-important: var(--md-sys-color-on-important); + --color-important-container: var(--md-sys-color-important-container); + --color-on-important-container: var(--md-sys-color-on-important-container); + --color-warning: var(--md-sys-color-warning); + --color-on-warning: var(--md-sys-color-on-warning); + --color-warning-container: var(--md-sys-color-warning-container); + --color-on-warning-container: var(--md-sys-color-on-warning-container); + --color-caution: var(--md-sys-color-caution); + --color-on-caution: var(--md-sys-color-on-caution); + --color-caution-container: var(--md-sys-color-caution-container); + --color-on-caution-container: var(--md-sys-color-on-caution-container); } .dark { @@ -417,9 +367,9 @@ pre[class*='language-'] { --card-foreground: oklch(0.985 0 0); --popover: oklch(0.205 0 0); --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); + --primary: oklch(0.437 0.078 188.216); + --primary-foreground: oklch(0.984 0.014 180.72); + --secondary: oklch(0.274 0.006 286.033); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.269 0 0); --muted-foreground: oklch(0.708 0 0); @@ -429,19 +379,19 @@ pre[class*='language-'] { --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.556 0 0); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: hsl(240 5.9% 10%); - --sidebar-foreground: hsl(240 4.8% 95.9%); - --sidebar-primary: hsl(224.3 76.3% 48%); - --sidebar-primary-foreground: hsl(0 0% 100%); - --sidebar-accent: hsl(240 3.7% 15.9%); - --sidebar-accent-foreground: hsl(240 4.8% 95.9%); - --sidebar-border: hsl(240 3.7% 15.9%); - --sidebar-ring: hsl(217.2 91.2% 59.8%); + --chart-1: oklch(0.855 0.138 181.071); + --chart-2: oklch(0.704 0.14 182.503); + --chart-3: oklch(0.6 0.118 184.704); + --chart-4: oklch(0.511 0.096 186.391); + --chart-5: oklch(0.437 0.078 188.216); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.704 0.14 182.503); + --sidebar-primary-foreground: oklch(0.277 0.046 192.524); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); } @layer base { @@ -455,35 +405,35 @@ pre[class*='language-'] { :root, .dark { - --background: var(--mcu-surface); - --foreground: var(--mcu-on-surface); - --card: var(--mcu-surface-container-low); - --card-foreground: var(--mcu-on-surface); - --popover: var(--mcu-surface-container-high); - --popover-foreground: var(--mcu-on-surface); - --primary: var(--mcu-primary); - --primary-foreground: var(--mcu-on-primary); - --secondary: var(--mcu-secondary-container); - --secondary-foreground: var(--mcu-on-secondary-container); - --muted: var(--mcu-surface-container-highest); - --muted-foreground: var(--mcu-on-surface-variant); - --accent: var(--mcu-secondary-container); - --accent-foreground: var(--mcu-on-secondary-container); - --destructive: var(--mcu-error); - --border: var(--mcu-outline-variant); - --input: var(--mcu-outline); - --ring: var(--mcu-primary); - --chart-1: var(--mcu-primary-fixed); - --chart-2: var(--mcu-secondary-fixed); - --chart-3: var(--mcu-tertiary-fixed); - --chart-4: var(--mcu-primary-fixed-dim); - --chart-5: var(--mcu-secondary-fixed-dim); - --sidebar: var(--mcu-surface-container-low); - --sidebar-foreground: var(--mcu-on-surface); - --sidebar-primary: var(--mcu-primary); - --sidebar-primary-foreground: var(--mcu-on-primary); - --sidebar-accent: var(--mcu-secondary-container); - --sidebar-accent-foreground: var(--mcu-on-secondary-container); - --sidebar-border: var(--mcu-outline-variant); - --sidebar-ring: var(--mcu-primary); + --background: var(--md-sys-color-surface); + --foreground: var(--md-sys-color-on-surface); + --card: var(--md-sys-color-surface-container-low); + --card-foreground: var(--md-sys-color-on-surface); + --popover: var(--md-sys-color-surface-container-high); + --popover-foreground: var(--md-sys-color-on-surface); + --primary: var(--md-sys-color-primary); + --primary-foreground: var(--md-sys-color-on-primary); + --secondary: var(--md-sys-color-secondary-container); + --secondary-foreground: var(--md-sys-color-on-secondary-container); + --muted: var(--md-sys-color-surface-container-highest); + --muted-foreground: var(--md-sys-color-on-surface-variant); + --accent: var(--md-sys-color-secondary-container); + --accent-foreground: var(--md-sys-color-on-secondary-container); + --destructive: var(--md-sys-color-error); + --border: var(--md-sys-color-outline-variant); + --input: var(--md-sys-color-outline); + --ring: var(--md-sys-color-primary); + --chart-1: var(--md-sys-color-primary-fixed); + --chart-2: var(--md-sys-color-secondary-fixed); + --chart-3: var(--md-sys-color-tertiary-fixed); + --chart-4: var(--md-sys-color-primary-fixed-dim); + --chart-5: var(--md-sys-color-secondary-fixed-dim); + --sidebar: var(--md-sys-color-surface-container-low); + --sidebar-foreground: var(--md-sys-color-on-surface); + --sidebar-primary: var(--md-sys-color-primary); + --sidebar-primary-foreground: var(--md-sys-color-on-primary); + --sidebar-accent: var(--md-sys-color-secondary-container); + --sidebar-accent-foreground: var(--md-sys-color-on-secondary-container); + --sidebar-border: var(--md-sys-color-outline-variant); + --sidebar-ring: var(--md-sys-color-primary); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 422917eb0..b43cd0032 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,10 +1,11 @@ import cn from '@/lib/cn' +import { pmndrsMtb } from '@/lib/md3' import { svg } from '@/utils/icon' import resolveMdxUrl from '@/utils/resolveMdxUrl' +import { Mtb } from 'material-theme-builder/react' import type { Metadata } from 'next' import { ThemeProvider } from 'next-themes' import localFont from 'next/font/local' -import { Mcu } from 'react-mcu' import './globals.css' import { SandpackCSS } from './sandpack-styles' @@ -102,21 +103,6 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode }>) { - const primary = process.env.THEME_PRIMARY || '#323e48' - const note = process.env.THEME_NOTE || '#1f6feb' - const tip = process.env.THEME_TIP || '#238636' - const important = process.env.THEME_IMPORTANT || '#8957e5' - const warning = process.env.THEME_WARNING || '#d29922' - const caution = process.env.THEME_CAUTION || '#da3633' - const scheme = (process.env.THEME_SCHEME || 'tonalSpot') as - | 'content' - | 'expressive' - | 'fidelity' - | 'monochrome' - | 'neutral' - | 'tonalSpot' - | 'vibrant' - const contrast = Number(process.env.THEME_CONTRAST) || 0 const basePath = process.env.BASE_PATH || '' return ( @@ -131,20 +117,9 @@ export default function RootLayout({ - + {children} - + ) diff --git a/src/components/mdx/Code/Code.tsx b/src/components/mdx/Code/Code.tsx index 01fd2ca2a..4dd4cfab2 100644 --- a/src/components/mdx/Code/Code.tsx +++ b/src/components/mdx/Code/Code.tsx @@ -27,7 +27,7 @@ export const Code = ({ children, className, ...props }: ComponentProps<'pre'>) = className={cn( className, 'my-5 overflow-auto rounded-lg p-(--pad) font-mono text-sm', - 'bg-[oklch(from_var(--mcu-on-primary-fixed)_l_calc(c*0.2)_h)] text-primary-fixed', // using a fixed color to only have 1 theme for prism + 'bg-[oklch(from_var(--md-sys-color-on-primary-fixed)_l_calc(c*0.2)_h)] text-primary-fixed', // using a fixed color to only have 1 theme for prism )} > {children} diff --git a/src/components/mdx/Sandpack/Sandpack.tsx b/src/components/mdx/Sandpack/Sandpack.tsx index dc407ae36..70639c5d2 100644 --- a/src/components/mdx/Sandpack/Sandpack.tsx +++ b/src/components/mdx/Sandpack/Sandpack.tsx @@ -91,9 +91,9 @@ export const Sandpack = async ({ {...props} theme={{ colors: { - surface1: 'var(--mcu-surface-container-low)', - surface2: 'var(--mcu-surface-container)', - surface3: 'var(--mcu-surface-container-high)', + surface1: 'var(--md-sys-color-surface-container-low)', + surface2: 'var(--md-sys-color-surface-container)', + surface3: 'var(--md-sys-color-surface-container-high)', }, font: { mono: 'var(--font-mono)', diff --git a/src/lib/md3.ts b/src/lib/md3.ts new file mode 100644 index 000000000..33200681e --- /dev/null +++ b/src/lib/md3.ts @@ -0,0 +1,38 @@ +import type { MtbConfig } from 'material-theme-builder' + +/** + * The pmndrs Material Design 3 seed. + * + * This is Tier 1: the colours every pmndrs site derives from. The shadcn preset + * carries radius and typography, not colour — moving `source` here is what + * actually moves the rendered palette. + * + * Every value is overridable per deployment through a `THEME_*` env var, so a + * site can reseed without forking the file. + * + * Read from a React Server Component (a Next.js root layout, typically): the + * non-`NEXT_PUBLIC_` vars below are only substituted on the server. + */ +export const pmndrsMtb = { + /** poimandres mint. */ + source: process.env.THEME_PRIMARY || '#5de4c7', + scheme: (process.env.THEME_SCHEME || 'tonalSpot') as MtbConfig['scheme'], + contrast: Number(process.env.THEME_CONTRAST) || 0, + + /** + * GitHub's alert palette, harmonized against the seed (`blend: true`). + * + * Each one emits `--md-sys-color-`, `-on-`, `--container` + * and `-on--container`. The Tailwind `@theme` mapping for those four + * ships in this item's `css` — the package's `tailwind.css` covers standard + * M3 roles only, so custom colours have to be mapped by hand. Add a colour + * here and you must add its four lines there too. + */ + customColors: [ + { name: 'note', hex: process.env.THEME_NOTE || '#1f6feb', blend: true }, + { name: 'tip', hex: process.env.THEME_TIP || '#238636', blend: true }, + { name: 'important', hex: process.env.THEME_IMPORTANT || '#8957e5', blend: true }, + { name: 'warning', hex: process.env.THEME_WARNING || '#d29922', blend: true }, + { name: 'caution', hex: process.env.THEME_CAUTION || '#da3633', blend: true }, + ], +} satisfies MtbConfig From e23cfa4e52c72910d3d2c89c74460555c2914be0 Mon Sep 17 00:00:00 2001 From: Antoine BERNIER Date: Sun, 16 Aug 2026 12:35:45 +0200 Subject: [PATCH 2/2] refactor: keep the alert colours here, take only the seed from Tier 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follows pmndrs/design-system#3. note, tip, important, warning and caution are GitHub's palette, and this generator is the only thing rendering markdown alerts — they were never the design system's to carry. Tier 1 now ships the seed alone, at v0.2.0. src/lib/mtb.ts spreads pmndrsMtb and adds the five, so src/lib/md3.ts stays a verbatim copy of the installed item and updating it remains a clean overwrite. blend: true still harmonizes them against the seed, and THEME_NOTE and its four siblings still override them. Their twenty @theme lines move out of the shadcn-managed block into one of our own, commented, next to nothing else — the config and the mapping have to be read together, since a name declared in one and missing from the other makes Tailwind emit no rule at all, without an error. Refs pmndrs/design-system#1 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EDvnsrory1aJsoNALn5aLB --- .changeset/md3-tier1.md | 4 +++- src/app/globals.css | 51 +++++++++++++++++++++++++---------------- src/app/layout.tsx | 4 ++-- src/lib/md3.ts | 35 ++++++++++++++-------------- src/lib/mtb.ts | 37 ++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 40 deletions(-) create mode 100644 src/lib/mtb.ts diff --git a/.changeset/md3-tier1.md b/.changeset/md3-tier1.md index b5ff48b42..1c5c8798d 100644 --- a/.changeset/md3-tier1.md +++ b/.changeset/md3-tier1.md @@ -4,4 +4,6 @@ Take the Material Design 3 colour layer from `pmndrs/design-system` instead of maintaining it here. The hand-written `@theme` mapping and the shadcn remap — around 105 lines that had to be kept in step with the colour package by hand — are replaced by one registry item, `pmndrs/design-system/md3`, which brings its own Tailwind mapping and the config it maps. `react-mcu` gives way to `material-theme-builder`, so the CSS variables are now `--md-sys-color-*`; every `bg-surface` / `text-on-surface-variant` utility keeps working untouched, because the `--color-*` names on top of them are identical. -The seed moves to poimandres mint (`#5de4c7`) — this is the visible change — and it now lives in exactly one place, `src/lib/md3.ts`, rather than being restated in the layout and in two workflows. `THEME_PRIMARY`, `THEME_SCHEME`, `THEME_CONTRAST` and the five alert colours still override it per deployment; a site that wants its own seed sets the workflow input, and one that doesn't inherits the pmndrs default. +The seed moves to poimandres mint (`#5de4c7`) — this is the visible change — and it now lives in exactly one place, the installed `src/lib/md3.ts`, rather than being restated in the layout and in two workflows. `THEME_PRIMARY`, `THEME_SCHEME` and `THEME_CONTRAST` still override it per deployment; a site that wants its own seed sets the workflow input, and one that doesn't inherits the pmndrs default. + +The five alert colours stay here, in `src/lib/mtb.ts`, which spreads the pmndrs seed and adds them. They are GitHub's palette and only this generator renders markdown alerts, so they are not the design system's to carry — but they are still harmonized against the seed, and `THEME_NOTE`, `THEME_TIP`, `THEME_IMPORTANT`, `THEME_WARNING` and `THEME_CAUTION` still work. diff --git a/src/app/globals.css b/src/app/globals.css index 7ece39786..47ec9f7b4 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -11,6 +11,37 @@ --animate-collapsible-up: collapsible-up var(--collapsible-up-duration, 0s) ease-out; } +/* + * Alert colours — GitHub's palette, harmonized against the pmndrs seed. + * + * One entry here per `customColors` name in src/lib/mtb.ts, four lines each. + * material-theme-builder maps the standard M3 roles only, so these are ours to + * keep in step: drop one and Tailwind emits no rule for `bg-note-container` at + * all — no error, no warning, the element just renders unstyled. + */ +@theme inline { + --color-note: var(--md-sys-color-note); + --color-on-note: var(--md-sys-color-on-note); + --color-note-container: var(--md-sys-color-note-container); + --color-on-note-container: var(--md-sys-color-on-note-container); + --color-tip: var(--md-sys-color-tip); + --color-on-tip: var(--md-sys-color-on-tip); + --color-tip-container: var(--md-sys-color-tip-container); + --color-on-tip-container: var(--md-sys-color-on-tip-container); + --color-important: var(--md-sys-color-important); + --color-on-important: var(--md-sys-color-on-important); + --color-important-container: var(--md-sys-color-important-container); + --color-on-important-container: var(--md-sys-color-on-important-container); + --color-warning: var(--md-sys-color-warning); + --color-on-warning: var(--md-sys-color-on-warning); + --color-warning-container: var(--md-sys-color-warning-container); + --color-on-warning-container: var(--md-sys-color-on-warning-container); + --color-caution: var(--md-sys-color-caution); + --color-on-caution: var(--md-sys-color-on-caution); + --color-caution-container: var(--md-sys-color-caution-container); + --color-on-caution-container: var(--md-sys-color-on-caution-container); +} + @keyframes collapsible-down { from { height: 0; @@ -338,26 +369,6 @@ pre[class*='language-'] { --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); - --color-note: var(--md-sys-color-note); - --color-on-note: var(--md-sys-color-on-note); - --color-note-container: var(--md-sys-color-note-container); - --color-on-note-container: var(--md-sys-color-on-note-container); - --color-tip: var(--md-sys-color-tip); - --color-on-tip: var(--md-sys-color-on-tip); - --color-tip-container: var(--md-sys-color-tip-container); - --color-on-tip-container: var(--md-sys-color-on-tip-container); - --color-important: var(--md-sys-color-important); - --color-on-important: var(--md-sys-color-on-important); - --color-important-container: var(--md-sys-color-important-container); - --color-on-important-container: var(--md-sys-color-on-important-container); - --color-warning: var(--md-sys-color-warning); - --color-on-warning: var(--md-sys-color-on-warning); - --color-warning-container: var(--md-sys-color-warning-container); - --color-on-warning-container: var(--md-sys-color-on-warning-container); - --color-caution: var(--md-sys-color-caution); - --color-on-caution: var(--md-sys-color-on-caution); - --color-caution-container: var(--md-sys-color-caution-container); - --color-on-caution-container: var(--md-sys-color-on-caution-container); } .dark { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b43cd0032..fd67e2937 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,5 @@ import cn from '@/lib/cn' -import { pmndrsMtb } from '@/lib/md3' +import { docsMtb } from '@/lib/mtb' import { svg } from '@/utils/icon' import resolveMdxUrl from '@/utils/resolveMdxUrl' import { Mtb } from 'material-theme-builder/react' @@ -117,7 +117,7 @@ export default function RootLayout({ - + {children} diff --git a/src/lib/md3.ts b/src/lib/md3.ts index 33200681e..c56b7639e 100644 --- a/src/lib/md3.ts +++ b/src/lib/md3.ts @@ -12,27 +12,28 @@ import type { MtbConfig } from 'material-theme-builder' * * Read from a React Server Component (a Next.js root layout, typically): the * non-`NEXT_PUBLIC_` vars below are only substituted on the server. + * + * Need colours M3 has no role for — alert levels, a status palette? Extend this + * rather than editing it, so the next update of this item stays a clean + * overwrite: + * + * ```ts + * export const myMtb = { + * ...pmndrsMtb, + * customColors: [{ name: 'note', hex: '#1f6feb', blend: true }], + * } satisfies MtbConfig + * ``` + * + * `blend: true` harmonizes them against the seed above, so they stay yours and + * still belong to the pmndrs palette. One catch, and it fails silently: the + * package's Tailwind mapping covers standard M3 roles only. Every custom colour + * needs four `@theme` lines of your own — `--color-note`, `--color-on-note`, + * `--color-note-container`, `--color-on-note-container` — or Tailwind emits no + * rule at all for `bg-note-container`, with no error. */ export const pmndrsMtb = { /** poimandres mint. */ source: process.env.THEME_PRIMARY || '#5de4c7', scheme: (process.env.THEME_SCHEME || 'tonalSpot') as MtbConfig['scheme'], contrast: Number(process.env.THEME_CONTRAST) || 0, - - /** - * GitHub's alert palette, harmonized against the seed (`blend: true`). - * - * Each one emits `--md-sys-color-`, `-on-`, `--container` - * and `-on--container`. The Tailwind `@theme` mapping for those four - * ships in this item's `css` — the package's `tailwind.css` covers standard - * M3 roles only, so custom colours have to be mapped by hand. Add a colour - * here and you must add its four lines there too. - */ - customColors: [ - { name: 'note', hex: process.env.THEME_NOTE || '#1f6feb', blend: true }, - { name: 'tip', hex: process.env.THEME_TIP || '#238636', blend: true }, - { name: 'important', hex: process.env.THEME_IMPORTANT || '#8957e5', blend: true }, - { name: 'warning', hex: process.env.THEME_WARNING || '#d29922', blend: true }, - { name: 'caution', hex: process.env.THEME_CAUTION || '#da3633', blend: true }, - ], } satisfies MtbConfig diff --git a/src/lib/mtb.ts b/src/lib/mtb.ts new file mode 100644 index 000000000..15f18439c --- /dev/null +++ b/src/lib/mtb.ts @@ -0,0 +1,37 @@ +import { pmndrsMtb } from '@/lib/md3' +import type { MtbConfig } from 'material-theme-builder' + +/** + * GitHub's alert palette, the colours `rehype-github-alerts` renders. + * + * These are ours, not the design system's: nothing outside this generator draws + * markdown alerts, and the hexes are GitHub's rather than poimandres'. Tier 1 + * owns the seed; this owns what the seed has no M3 role for. + * + * `blend: true` harmonizes each one against that seed, so they shift with the + * theme instead of sitting on top of it. + * + * Each name here needs four `@theme` lines in globals.css — `--color-note`, + * `--color-on-note`, `--color-note-container`, `--color-on-note-container`. + * material-theme-builder maps standard M3 roles only, and a name declared here + * but unmapped there fails silently: Tailwind emits no rule for + * `bg-note-container` at all, no error, no warning. + */ +const alertColors = [ + { name: 'note', hex: process.env.THEME_NOTE || '#1f6feb', blend: true }, + { name: 'tip', hex: process.env.THEME_TIP || '#238636', blend: true }, + { name: 'important', hex: process.env.THEME_IMPORTANT || '#8957e5', blend: true }, + { name: 'warning', hex: process.env.THEME_WARNING || '#d29922', blend: true }, + { name: 'caution', hex: process.env.THEME_CAUTION || '#da3633', blend: true }, +] + +/** + * The theme this site mounts: the pmndrs seed, plus our alert colours. + * + * Spread rather than edited, so `src/lib/md3.ts` stays a verbatim copy of the + * Tier 1 item and re-installing it is a clean overwrite. + */ +export const docsMtb = { + ...pmndrsMtb, + customColors: alertColors, +} satisfies MtbConfig