synthesisui 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/guide.js +107 -31
- package/package.json +1 -1
package/dist/guide.js
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
const kebab = (v) => v.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
2
2
|
const list = (items) => items.length ? items.map((i) => `\`${i}\``).join(", ") : "_(none)_";
|
|
3
|
+
const dataAttrs = (variants) => Object.entries(variants).map(([axis, opts]) => `data-${kebab(axis)}="${Object.keys(opts).join("|")}"`);
|
|
4
|
+
/** One entry per component: class, variant data-*, states, and multi-part anatomy. */
|
|
5
|
+
function componentEntry(cname, recipe) {
|
|
6
|
+
const cls = `.ds-${kebab(cname)}`;
|
|
7
|
+
const axes = dataAttrs(recipe.variants).map((a) => `\`${a}\``);
|
|
8
|
+
const variantsText = axes.length ? ` — variants: ${axes.join(", ")}` : "";
|
|
9
|
+
const states = Object.keys(recipe.states ?? {});
|
|
10
|
+
const statesText = states.length ? `\n states: ${list(states)}` : "";
|
|
11
|
+
const partEntries = Object.entries(recipe.parts ?? {});
|
|
12
|
+
let partsText = "";
|
|
13
|
+
if (partEntries.length) {
|
|
14
|
+
const items = partEntries.map(([pname, part]) => {
|
|
15
|
+
const pcls = `.ds-${kebab(cname)}-${kebab(pname)}`;
|
|
16
|
+
const paxes = dataAttrs(part.variants ?? {});
|
|
17
|
+
return paxes.length ? `\`${pcls}\` (${paxes.join(", ")})` : `\`${pcls}\``;
|
|
18
|
+
});
|
|
19
|
+
partsText = `\n parts: ${items.join(", ")}`;
|
|
20
|
+
}
|
|
21
|
+
return `- **${cname}** (\`${cls}\`)${variantsText}\n ${recipe.description}${partsText}${statesText}`;
|
|
22
|
+
}
|
|
3
23
|
/**
|
|
4
24
|
* Builds GUIDE.md — instructions *for the agent* on how to build components
|
|
5
25
|
* that follow the design system. This is the piece that makes "I create the
|
|
@@ -10,19 +30,13 @@ export function buildGuide(payload) {
|
|
|
10
30
|
const { document: doc, slug, name, version } = payload;
|
|
11
31
|
const { meta, foundations, motion, components } = doc;
|
|
12
32
|
const semanticRoles = Object.keys(foundations.color.semantic);
|
|
33
|
+
const weights = Object.keys(foundations.typography.weights);
|
|
13
34
|
const hasAlt = foundations.color.semanticAlt &&
|
|
14
35
|
Object.keys(foundations.color.semanticAlt).length > 0;
|
|
15
36
|
const altScheme = meta.scheme === "light" ? "dark" : "light";
|
|
16
37
|
const hasTailwind = "theme.css" in payload.artifacts;
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
const axes = Object.entries(recipe.variants).map(([axis, opts]) => {
|
|
20
|
-
const options = Object.keys(opts);
|
|
21
|
-
return `\`data-${kebab(axis)}="${options.join("|")}"\``;
|
|
22
|
-
});
|
|
23
|
-
const variantsText = axes.length ? ` — variants: ${axes.join(", ")}` : "";
|
|
24
|
-
return `- **${cname}** (\`${cls}\`)${variantsText}\n ${recipe.description}`;
|
|
25
|
-
});
|
|
38
|
+
const hasParts = Object.values(components).some((r) => r.parts && Object.keys(r.parts).length > 0);
|
|
39
|
+
const componentLines = Object.entries(components).map(([cname, recipe]) => componentEntry(cname, recipe));
|
|
26
40
|
const artifactList = Object.keys(payload.artifacts)
|
|
27
41
|
.map((f) => `\`${f}\``)
|
|
28
42
|
.join(", ");
|
|
@@ -54,27 +68,45 @@ ${meta.narrative}
|
|
|
54
68
|
<div data-ds="${slug}">…your UI here…</div>
|
|
55
69
|
\`\`\`
|
|
56
70
|
All \`--ds-*\` custom properties and \`.ds-*\` classes only apply inside that scope.
|
|
71
|
+
Applying \`data-ds="${slug}"\` at the app root (e.g. \`<body>\` or the root layout)
|
|
72
|
+
is the simplest choice — the whole app then wears the system.
|
|
57
73
|
${hasAlt
|
|
58
74
|
? `
|
|
59
75
|
3. Light/dark: an ancestor with \`data-scheme="${altScheme}"\` switches the neutral roles to the opposite mode.
|
|
60
|
-
\`\`\`
|
|
76
|
+
\`\`\`tsx
|
|
61
77
|
<div data-scheme="${altScheme}"><div data-ds="${slug}">…</div></div>
|
|
62
78
|
\`\`\`
|
|
79
|
+
A theme toggle just adds/removes that attribute on the scope element:
|
|
80
|
+
\`\`\`tsx
|
|
81
|
+
root.toggleAttribute("data-scheme"); // present = ${altScheme}, absent = ${meta.scheme}
|
|
82
|
+
\`\`\`
|
|
63
83
|
`
|
|
64
84
|
: ""}${hasTailwind
|
|
65
85
|
? `
|
|
66
|
-
##
|
|
86
|
+
## Styling with Tailwind v4 (preferred in this project)
|
|
67
87
|
|
|
68
|
-
|
|
88
|
+
Import \`theme.css\` after \`tailwindcss\` and \`tokens.css\`:
|
|
69
89
|
\`\`\`css
|
|
70
90
|
@import "tailwindcss";
|
|
71
91
|
@import "./_synthesisui/ds/${slug}/tokens.css";
|
|
72
92
|
@import "./_synthesisui/ds/${slug}/theme.css";
|
|
73
93
|
\`\`\`
|
|
74
|
-
This maps the DS tokens onto Tailwind's theme, so inside \`[data-ds="${slug}"]\` you
|
|
75
|
-
|
|
76
|
-
\`
|
|
77
|
-
|
|
94
|
+
This maps the DS tokens onto Tailwind's theme, so inside \`[data-ds="${slug}"]\` you get utilities
|
|
95
|
+
backed by the design system: \`bg-*\`/\`text-*\`/\`border-*\` (semantic colors), \`p-*\`/\`m-*\`/\`gap-*\`
|
|
96
|
+
(spacing), \`rounded-*\`, \`shadow-*\`, \`font-*\` (families **and** weights), \`text-*\` (type scale), \`ease-*\`.
|
|
97
|
+
|
|
98
|
+
**Prefer these utilities for layout and new composition** — they are this project's idiom and read
|
|
99
|
+
far better than inline \`style\`. Reach for inline \`var(--ds-*)\` only when no utility fits.
|
|
100
|
+
|
|
101
|
+
\`\`\`tsx
|
|
102
|
+
// ✅ preferred — Tailwind utilities backed by the DS
|
|
103
|
+
<main className="bg-canvas text-foreground p-2xl flex flex-col gap-md">
|
|
104
|
+
<button className="ds-button" data-intent="primary">Save</button>
|
|
105
|
+
</main>
|
|
106
|
+
|
|
107
|
+
// ❌ avoid — inline styles with raw var() when a utility exists
|
|
108
|
+
<main style={{ background: "var(--ds-color-semantic-canvas)", padding: "var(--ds-spacing-2xl)" }}>
|
|
109
|
+
\`\`\`
|
|
78
110
|
|
|
79
111
|
---
|
|
80
112
|
`
|
|
@@ -87,17 +119,70 @@ those, not the versioned ones. The pinned files for this version — ${artifactL
|
|
|
87
119
|
|
|
88
120
|
---
|
|
89
121
|
|
|
90
|
-
##
|
|
122
|
+
## Building with the system
|
|
123
|
+
|
|
124
|
+
**This system is for building real product UI** — pages, layouts, dashboards, whole flows.
|
|
125
|
+
Compose the \`.ds-*\` recipes (and their parts) together with the DS-backed utilities to assemble
|
|
126
|
+
actual screens. There is **no "samples only" rule**: build the real app. An
|
|
127
|
+
\`app/synthesisui-samples/<component>/\` page is a fine *optional* scratch space to eyeball a single
|
|
128
|
+
component, but it is never required.
|
|
129
|
+
|
|
130
|
+
### Layout & composition
|
|
131
|
+
The system defines the scale; these are sensible defaults for spending it:
|
|
132
|
+
- **Page gutter / container padding:** a large spacing step — ${list(Object.keys(foundations.spacing).filter((k) => /xl/.test(k)))}.
|
|
133
|
+
- **Section gaps:** \`lg\` (or the nearest large step). **Card/panel padding:** \`md\`.
|
|
134
|
+
- **Field / tight gaps:** \`2xs\`/\`3xs\`.
|
|
135
|
+
- The system imposes no content max-width — cap long-form/text columns yourself for readability.
|
|
136
|
+
${hasParts
|
|
137
|
+
? `
|
|
138
|
+
### Multi-part components
|
|
139
|
+
Components that have **parts** compile to \`.ds-<name>-<part>\` classes you nest yourself; the exact
|
|
140
|
+
part classes and their \`data-*\` are listed per component below. Example — a table:
|
|
141
|
+
\`\`\`tsx
|
|
142
|
+
<table className="ds-table">
|
|
143
|
+
<thead className="ds-table-head">
|
|
144
|
+
<tr>
|
|
145
|
+
<th className="ds-table-cell-head">Name</th>
|
|
146
|
+
<th className="ds-table-cell-head" data-align="end">Updated</th>
|
|
147
|
+
</tr>
|
|
148
|
+
</thead>
|
|
149
|
+
<tbody>
|
|
150
|
+
<tr className="ds-table-row">
|
|
151
|
+
<td className="ds-table-cell">Halogen</td>
|
|
152
|
+
<td className="ds-table-cell" data-align="end">2h ago</td>
|
|
153
|
+
</tr>
|
|
154
|
+
</tbody>
|
|
155
|
+
</table>
|
|
156
|
+
\`\`\`
|
|
157
|
+
`
|
|
158
|
+
: ""}
|
|
159
|
+
### Overlays & portals
|
|
160
|
+
Dialogs, menus and toasts are often rendered through a portal at the end of \`<body>\` — **outside**
|
|
161
|
+
your \`data-ds\` scope. Since \`.ds-*\`/\`--ds-*\` only resolve inside the scope, wrap any portalled UI
|
|
162
|
+
in its own \`<div data-ds="${slug}"${hasAlt ? ` data-scheme="…"` : ""}>\`, or apply \`data-ds\` at the
|
|
163
|
+
app root so everything (portals included) inherits it. Behavior (open/close, focus trap, positioning,
|
|
164
|
+
keyboard) is yours to wire — the system ships the **looks**, not the JavaScript.
|
|
165
|
+
|
|
166
|
+
---
|
|
91
167
|
|
|
168
|
+
## Rules (follow them when creating components)
|
|
169
|
+
${hasTailwind
|
|
170
|
+
? `
|
|
171
|
+
- **Styling mechanism:** prefer Tailwind utilities backed by the DS (\`bg-primary\`, \`p-md\`,
|
|
172
|
+
\`font-display\`, \`font-medium\`, …) for layout and new composition, and reuse the \`.ds-*\` recipes
|
|
173
|
+
for components the DS already covers. Use inline \`style\` with \`var(--ds-*)\` only as a last resort.
|
|
174
|
+
The token names below are the source vocabulary — every utility derives from them.`
|
|
175
|
+
: ""}
|
|
92
176
|
- **Always use semantic tokens**, never raw values nor primitives directly.
|
|
93
|
-
Color: \`var(--ds-color-semantic-<role>)
|
|
177
|
+
Color: \`var(--ds-color-semantic-<role>)\`${hasTailwind ? " (utility: `bg-<role>`/`text-<role>`)" : ""}. The roles are: ${list(semanticRoles)}.
|
|
94
178
|
- Primitives (\`--ds-color-<palette>-<step>\`) exist but should **not** be referenced directly —
|
|
95
179
|
they feed the semantic roles.
|
|
96
180
|
- Spacing → \`var(--ds-spacing-<key>)\`: ${list(Object.keys(foundations.spacing))}.
|
|
97
181
|
- Radius → \`var(--ds-radius-<key>)\`: ${list(Object.keys(foundations.radius))}.
|
|
98
182
|
- Shadow → \`var(--ds-shadow-<key>)\`: ${list(Object.keys(foundations.shadow))}.
|
|
99
183
|
- Typography: families \`--ds-typography-families-{display,body,mono}\` (${foundations.typography.families.display}, ${foundations.typography.families.body}, ${foundations.typography.families.mono});
|
|
100
|
-
|
|
184
|
+
weights${hasTailwind ? " (utility: `font-<key>`)" : ""}: ${list(weights)};
|
|
185
|
+
scale \`--ds-typography-scale-<key>-font-size\`${hasTailwind ? " (utility: `text-<key>`)" : ""}: ${list(Object.keys(foundations.typography.scale))}.
|
|
101
186
|
- Motion: durations \`--ds-motion-durations-<key>\` (${list(Object.keys(motion.durations))}) and
|
|
102
187
|
easings \`--ds-motion-easings-<key>\` (${list(Object.keys(motion.easings))}).
|
|
103
188
|
- When **creating a new component** the DS does not cover yet: compose it from these semantic
|
|
@@ -105,20 +190,11 @@ those, not the versioned ones. The pinned files for this version — ${artifactL
|
|
|
105
190
|
|
|
106
191
|
---
|
|
107
192
|
|
|
108
|
-
## Where to preview
|
|
109
|
-
|
|
110
|
-
**Preview in isolation, never on a real page.** When creating or demoing a component, generate a
|
|
111
|
-
dedicated sample page — \`app/synthesisui-samples/<component>/\` in the Next.js App Router (or the
|
|
112
|
-
equivalent samples route/folder in the project's stack). **Do not** apply the component to real
|
|
113
|
-
production pages (home, layout, existing routes) unless explicitly asked. Samples let you review the
|
|
114
|
-
component in the context of the design system without touching the app.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
193
|
## Ready-made components
|
|
119
194
|
|
|
120
|
-
Each recipe becomes a \`.ds-<name>\` class (inside the \`[data-ds="${slug}"]\` scope).
|
|
121
|
-
|
|
195
|
+
Each recipe becomes a \`.ds-<name>\` class (inside the \`[data-ds="${slug}"]\` scope). Variants are
|
|
196
|
+
\`data-<axis>="<option>"\` attributes; states (hover/focus/active/disabled) ship in the CSS;
|
|
197
|
+
multi-part components expose \`.ds-<name>-<part>\` classes (listed under each).
|
|
122
198
|
|
|
123
199
|
${componentLines.join("\n\n")}
|
|
124
200
|
|