Skip to content

Commit 45aee8f

Browse files
chore: sync registry and templates [skip release]
1 parent 8508d29 commit 45aee8f

24 files changed

Lines changed: 1988 additions & 12 deletions

‎templates/plate-playground-template/.agents/skills/shadcn/SKILL.md‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: shadcn
3-
description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".
3+
description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", "switch to --preset", and for building a design system or applying a DESIGN.md or brand spec to shadcn/ui.
44
user-invocable: false
55
allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)
66
---
@@ -193,6 +193,17 @@ npx shadcn@latest docs button dialog select
193193
- **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.
194194
- **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
195195

196+
## Design Systems from DESIGN.md
197+
198+
When the user asks for a design system from a `DESIGN.md` (or a brand spec), with or without a preset, follow [design-system.md](./design-system.md) end to end:
199+
200+
1. `init --preset <code> --template <vite|next> --name <app> --no-monorepo -y`. Without a preset code, pick the closest style and pass `--base base --preset <style>`. Then `add --all -y`.
201+
2. Read the DESIGN.md frontmatter, Do's and Don'ts, and Known Gaps before editing.
202+
3. Map its tokens onto the shadcn variables in `tailwindCssFile`. Pin the radius scale, shadows, fonts and `type-*` utilities.
203+
4. Restyle components by editing their `cva` variants. Change control heights together.
204+
5. Build the design system page per [design-system-page.md](./design-system-page.md): contrast-checked color roles, type specimens, pinned state matrices, edge cases, do/don't pairs, recipes. Check coverage with `scripts/showcase-coverage.mjs`.
205+
6. Verify in a browser: typecheck, no console errors, loads at the top, no horizontal overflow, dark mode.
206+
196207
## Updating Components
197208

198209
When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**
@@ -212,6 +223,11 @@ When the user asks to update a component from upstream while keeping their local
212223
npx shadcn@latest init --name my-app --preset base-nova
213224
npx shadcn@latest init --name my-app --preset a2r6bw --template vite
214225

226+
# Create a design system app (then follow design-system.md).
227+
npx shadcn@latest init --name my-ds --preset b0 --template vite --no-monorepo -y
228+
npx shadcn@latest init --name my-ds --base base --preset maia --template vite --no-monorepo -y
229+
npx shadcn@latest add --all -y
230+
215231
# Create a monorepo project.
216232
npx shadcn@latest init --name my-app --preset base-nova --monorepo
217233
npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo
@@ -275,3 +291,5 @@ npx shadcn@latest view owner/repo/item
275291
- [cli.md](./cli.md) — Commands, flags, presets, templates
276292
- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules
277293
- [customization.md](./customization.md) — Theming, CSS variables, extending components
294+
- [design-system.md](./design-system.md): DESIGN.md (with or without a preset) to a themed app: style picking, token mapping, radius, fonts, type scale, control heights, showcase traps, verification
295+
- [design-system-page.md](./design-system-page.md): What goes on a design system page and how: foundations, component tiers, pinned state matrices, contrast pairs, edge cases, do/don't, recipes
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import * as React from "react"
2+
3+
import { cn } from "@/lib/utils"
4+
5+
// Resolves any CSS color (hex, rgb, oklch, color-mix) to sRGB by painting one pixel.
6+
// Translucent colors are composited over the backdrop, as the browser would.
7+
function toRgb(color: string, backdrop = "#fff") {
8+
const canvas = document.createElement("canvas")
9+
canvas.width = canvas.height = 1
10+
const context = canvas.getContext("2d", { willReadFrequently: true })
11+
if (!context) {
12+
return null
13+
}
14+
context.fillStyle = backdrop
15+
context.fillRect(0, 0, 1, 1)
16+
context.fillStyle = color
17+
context.fillRect(0, 0, 1, 1)
18+
const [r, g, b] = context.getImageData(0, 0, 1, 1).data
19+
return [r, g, b]
20+
}
21+
22+
function toHex(rgb: number[]) {
23+
return `#${rgb.map((value) => value.toString(16).padStart(2, "0")).join("")}`
24+
}
25+
26+
function luminance([r, g, b]: number[]) {
27+
const [R, G, B] = [r, g, b].map((value) => {
28+
const channel = value / 255
29+
return channel <= 0.03928
30+
? channel / 12.92
31+
: ((channel + 0.055) / 1.055) ** 2.4
32+
})
33+
return 0.2126 * R + 0.7152 * G + 0.0722 * B
34+
}
35+
36+
function contrast(a: number[], b: number[]) {
37+
const [light, dark] = [luminance(a), luminance(b)].sort((x, y) => y - x)
38+
return (light + 0.05) / (dark + 0.05)
39+
}
40+
41+
export function ColorPair({
42+
name,
43+
background,
44+
foreground,
45+
className,
46+
}: {
47+
name: string
48+
background: string
49+
foreground: string
50+
className?: string
51+
}) {
52+
const ref = React.useRef<HTMLDivElement>(null)
53+
const [info, setInfo] = React.useState<{ ratio: number; bg: string } | null>(
54+
null
55+
)
56+
57+
React.useEffect(() => {
58+
const element = ref.current
59+
if (!element) {
60+
return
61+
}
62+
// Re-measure when the theme class on <html> changes.
63+
const measure = () => {
64+
const style = getComputedStyle(element)
65+
const page = getComputedStyle(document.body).backgroundColor
66+
const bg = toRgb(style.backgroundColor, page)
67+
if (!bg) {
68+
return
69+
}
70+
const fg = toRgb(style.color, toHex(bg))
71+
if (fg) {
72+
setInfo({ ratio: contrast(bg, fg), bg: toHex(bg) })
73+
}
74+
}
75+
measure()
76+
const observer = new MutationObserver(measure)
77+
observer.observe(document.documentElement, { attributes: true })
78+
return () => observer.disconnect()
79+
}, [])
80+
81+
const level = !info
82+
? ""
83+
: info.ratio >= 7
84+
? "AAA"
85+
: info.ratio >= 4.5
86+
? "AA"
87+
: info.ratio >= 3
88+
? "AA Large"
89+
: "Fail"
90+
91+
return (
92+
<div
93+
className={cn(
94+
"flex flex-col overflow-hidden rounded-xl border",
95+
className
96+
)}
97+
>
98+
<div
99+
ref={ref}
100+
style={{
101+
backgroundColor: `var(--${background})`,
102+
color: `var(--${foreground})`,
103+
}}
104+
className="flex h-24 items-end justify-between p-4"
105+
>
106+
<span className="text-2xl font-medium">Aa</span>
107+
{info && (
108+
<span className="rounded-full border border-current/20 px-2 py-0.5 font-mono text-xs">
109+
{info.ratio.toFixed(2)} {level}
110+
</span>
111+
)}
112+
</div>
113+
<div className="flex flex-col gap-0.5 bg-background p-3">
114+
<span className="text-sm font-medium">{name}</span>
115+
<span className="font-mono text-xs text-muted-foreground">
116+
--{background} / --{foreground}
117+
</span>
118+
{info && (
119+
<span className="font-mono text-xs text-muted-foreground">
120+
{info.bg}
121+
</span>
122+
)}
123+
</div>
124+
</div>
125+
)
126+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* Add to the app's global CSS after the dark variant. */
2+
/* Lets the design system page pin interaction states with data-preview="hover focus active". */
3+
@custom-variant hover {
4+
@media (hover: hover) {
5+
&:hover {
6+
@slot;
7+
}
8+
}
9+
&[data-preview~="hover"] {
10+
@slot;
11+
}
12+
}
13+
@custom-variant focus-visible (&:focus-visible, &[data-preview~="focus"]);
14+
@custom-variant active (&:active, &[data-preview~="active"]);
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import * as React from "react"
2+
3+
import { cn } from "@/lib/utils"
4+
5+
const defaultStates = [
6+
"default",
7+
"hover",
8+
"focus",
9+
"active",
10+
"disabled",
11+
] as const
12+
13+
export function StateMatrix<Row extends string>({
14+
rows,
15+
states = defaultStates,
16+
render,
17+
className,
18+
}: {
19+
rows: readonly Row[]
20+
states?: readonly string[]
21+
render: (row: Row, state: string) => React.ReactNode
22+
className?: string
23+
}) {
24+
return (
25+
<div className={cn("w-full overflow-x-auto", className)}>
26+
<table className="w-full border-separate border-spacing-x-2 border-spacing-y-3">
27+
<thead>
28+
<tr>
29+
<th />
30+
{states.map((state) => (
31+
<th
32+
key={state}
33+
scope="col"
34+
className="text-left font-mono text-xs font-normal text-muted-foreground"
35+
>
36+
{state}
37+
</th>
38+
))}
39+
</tr>
40+
</thead>
41+
<tbody>
42+
{rows.map((row) => (
43+
<tr key={row}>
44+
<th
45+
scope="row"
46+
className="pr-4 text-left font-mono text-xs font-normal text-muted-foreground"
47+
>
48+
{row}
49+
</th>
50+
{states.map((state) => (
51+
<td key={state}>{render(row, state)}</td>
52+
))}
53+
</tr>
54+
))}
55+
</tbody>
56+
</table>
57+
</div>
58+
)
59+
}
60+
61+
// Spread onto a component to pin a state without interaction.
62+
export function previewState(state: string) {
63+
if (state === "disabled") {
64+
return { disabled: true }
65+
}
66+
67+
if (["hover", "focus", "active"].includes(state)) {
68+
return { "data-preview": state }
69+
}
70+
71+
return {}
72+
}

‎templates/plate-playground-template/.agents/skills/shadcn/customization.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ module.exports = {
136136

137137
`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).
138138

139+
When a brand spec defines its own radius scale, pin each step in `@theme inline` (`--radius-sm` … `--radius-4xl`) instead of deriving from `--radius`. See [design-system.md](./design-system.md#radius).
140+
139141
---
140142

141143
## Customizing Components

0 commit comments

Comments
 (0)