synthesisui 0.1.5 → 0.1.7
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 +102 -27
- 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,12 +68,18 @@ ${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
|
? `
|
|
@@ -73,7 +93,7 @@ Import \`theme.css\` after \`tailwindcss\` and \`tokens.css\`:
|
|
|
73
93
|
\`\`\`
|
|
74
94
|
This maps the DS tokens onto Tailwind's theme, so inside \`[data-ds="${slug}"]\` you get utilities
|
|
75
95
|
backed by the design system: \`bg-*\`/\`text-*\`/\`border-*\` (semantic colors), \`p-*\`/\`m-*\`/\`gap-*\`
|
|
76
|
-
(spacing), \`rounded-*\`, \`shadow-*\`, \`font
|
|
96
|
+
(spacing), \`rounded-*\`, \`shadow-*\`, \`font-*\` (families **and** weights), \`text-*\` (type scale), \`ease-*\`.
|
|
77
97
|
|
|
78
98
|
**Prefer these utilities for layout and new composition** — they are this project's idiom and read
|
|
79
99
|
far better than inline \`style\`. Reach for inline \`var(--ds-*)\` only when no utility fits.
|
|
@@ -99,13 +119,76 @@ those, not the versioned ones. The pinned files for this version — ${artifactL
|
|
|
99
119
|
|
|
100
120
|
---
|
|
101
121
|
|
|
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
|
+
### Interactive recipes — the behavior contract
|
|
167
|
+
Several recipes are **static surfaces**: they ship the styling for every state, but never any
|
|
168
|
+
JavaScript. You own the interaction and drive each state by toggling the documented \`data-*\`
|
|
169
|
+
attributes (listed per component below). The recipe restyles itself; you wire the logic.
|
|
170
|
+
- **Open / close** (menu, select, modal, tooltip, popover): render the surface, then handle show/hide,
|
|
171
|
+
outside-click, focus trap, positioning and \`Esc\` yourself (or with a headless lib).
|
|
172
|
+
- **Selection / active** (tabs, sidebar, pagination): set \`data-active="true"\` on the chosen item from
|
|
173
|
+
your own state/router — the recipe lifts it onto a surface.
|
|
174
|
+
- **On / off** (switch): toggle \`data-state="on"\` on the track **and** its thumb together.
|
|
175
|
+
- **Command bar / ⌘K** (if your system ships one): the recipe is only the styled input row — wire the
|
|
176
|
+
shortcut, the palette list and filtering yourself.
|
|
177
|
+
- **Select** (native vs custom): \`.ds-select\` strips native chrome (\`appearance:none\`). On a real
|
|
178
|
+
\`<select>\`, wrap it and overlay your own chevron; on a custom trigger, nest \`.ds-select-chevron\`.
|
|
179
|
+
|
|
180
|
+
Pair these with the right ARIA (\`aria-expanded\`, \`role="dialog"\`, \`aria-current\`, …) — the system
|
|
181
|
+
styles it, you make it work.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
102
185
|
## Rules (follow them when creating components)
|
|
103
186
|
${hasTailwind
|
|
104
187
|
? `
|
|
105
188
|
- **Styling mechanism:** prefer Tailwind utilities backed by the DS (\`bg-primary\`, \`p-md\`,
|
|
106
|
-
\`font-display\`, …) for layout and new composition, and reuse the \`.ds-*\` recipes
|
|
107
|
-
the DS already covers. Use inline \`style\` with \`var(--ds-*)\` only as a last resort.
|
|
108
|
-
names below are the source vocabulary — every utility derives from them.`
|
|
189
|
+
\`font-display\`, \`font-medium\`, …) for layout and new composition, and reuse the \`.ds-*\` recipes
|
|
190
|
+
for components the DS already covers. Use inline \`style\` with \`var(--ds-*)\` only as a last resort.
|
|
191
|
+
The token names below are the source vocabulary — every utility derives from them.`
|
|
109
192
|
: ""}
|
|
110
193
|
- **Always use semantic tokens**, never raw values nor primitives directly.
|
|
111
194
|
Color: \`var(--ds-color-semantic-<role>)\`${hasTailwind ? " (utility: `bg-<role>`/`text-<role>`)" : ""}. The roles are: ${list(semanticRoles)}.
|
|
@@ -115,7 +198,8 @@ ${hasTailwind
|
|
|
115
198
|
- Radius → \`var(--ds-radius-<key>)\`: ${list(Object.keys(foundations.radius))}.
|
|
116
199
|
- Shadow → \`var(--ds-shadow-<key>)\`: ${list(Object.keys(foundations.shadow))}.
|
|
117
200
|
- Typography: families \`--ds-typography-families-{display,body,mono}\` (${foundations.typography.families.display}, ${foundations.typography.families.body}, ${foundations.typography.families.mono});
|
|
118
|
-
|
|
201
|
+
weights${hasTailwind ? " (utility: `font-<key>`)" : ""}: ${list(weights)};
|
|
202
|
+
scale \`--ds-typography-scale-<key>-font-size\`${hasTailwind ? " (utility: `text-<key>`)" : ""}: ${list(Object.keys(foundations.typography.scale))}.
|
|
119
203
|
- Motion: durations \`--ds-motion-durations-<key>\` (${list(Object.keys(motion.durations))}) and
|
|
120
204
|
easings \`--ds-motion-easings-<key>\` (${list(Object.keys(motion.easings))}).
|
|
121
205
|
- When **creating a new component** the DS does not cover yet: compose it from these semantic
|
|
@@ -123,20 +207,11 @@ ${hasTailwind
|
|
|
123
207
|
|
|
124
208
|
---
|
|
125
209
|
|
|
126
|
-
## Where to preview
|
|
127
|
-
|
|
128
|
-
**Preview in isolation, never on a real page.** When creating or demoing a component, generate a
|
|
129
|
-
dedicated sample page — \`app/synthesisui-samples/<component>/\` in the Next.js App Router (or the
|
|
130
|
-
equivalent samples route/folder in the project's stack). **Do not** apply the component to real
|
|
131
|
-
production pages (home, layout, existing routes) unless explicitly asked. Samples let you review the
|
|
132
|
-
component in the context of the design system without touching the app.
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
210
|
## Ready-made components
|
|
137
211
|
|
|
138
|
-
Each recipe becomes a \`.ds-<name>\` class (inside the \`[data-ds="${slug}"]\` scope).
|
|
139
|
-
|
|
212
|
+
Each recipe becomes a \`.ds-<name>\` class (inside the \`[data-ds="${slug}"]\` scope). Variants are
|
|
213
|
+
\`data-<axis>="<option>"\` attributes; states (hover/focus/active/disabled) ship in the CSS;
|
|
214
|
+
multi-part components expose \`.ds-<name>-<part>\` classes (listed under each).
|
|
140
215
|
|
|
141
216
|
${componentLines.join("\n\n")}
|
|
142
217
|
|