@nghitrum/dsforge 0.1.5-alpha.9 → 0.2.0
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/LICENSE.md +21 -0
- package/README.md +0 -1
- package/dist/{chunk-YUPXTQZ5.js → chunk-5YT3VNE6.js} +0 -26
- package/dist/chunk-A7VW6SII.js +436 -0
- package/dist/{chunk-QHE35QQQ.js → chunk-ZZRPNO6Z.js} +12 -17
- package/dist/cli/index.js +185 -302
- package/dist/componentDefinitions-5LFCNFQY.js +8 -0
- package/dist/{emitter-KNYIQTS5.js → emitter-IC77G4QF.js} +1 -1
- package/dist/generateAiFolder-3OOFWBH7.js +70 -0
- package/dist/generateComponentJson-XBEUWCW6.js +16 -0
- package/dist/generateComponentMetadata-2L5VNERD.js +13 -0
- package/dist/generateRegistry-3MEZDJAJ.js +19 -0
- package/dist/{html-DJJSDXRX.js → html-4DD6GOHE.js} +193 -100
- package/dist/index.js +68 -110
- package/package.json +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/adapters/react/generateAiFolder.ts
|
|
2
|
+
function generateSystemPrompt(systemName, tokens, componentNames) {
|
|
3
|
+
const tokenLines = Object.entries(tokens).map(([cssVar, values]) => `${cssVar.padEnd(35)} ${values.light} (dark: ${values.dark})`).join("\n");
|
|
4
|
+
const pkgName = systemName.toLowerCase().replace(/\s+/g, "-");
|
|
5
|
+
return `# ${systemName} \u2014 AI Context
|
|
6
|
+
|
|
7
|
+
You are helping a developer build UI using the ${systemName} design system, generated by dsforge.
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
- Never hardcode colours, spacing, or typography values
|
|
11
|
+
- Always use CSS custom properties from this design system
|
|
12
|
+
- Always import components from '${pkgName}'
|
|
13
|
+
- Never install or suggest third-party component libraries
|
|
14
|
+
|
|
15
|
+
## Tokens
|
|
16
|
+
${tokenLines}
|
|
17
|
+
|
|
18
|
+
## Available Components
|
|
19
|
+
${componentNames.join(", ")}
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
import { ${componentNames.slice(0, 3).join(", ")} } from '${pkgName}'
|
|
23
|
+
import '${pkgName}/tokens/base.css'
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
26
|
+
function generateComponentsJson(systemName, components, metadatas) {
|
|
27
|
+
const metaMap = Object.fromEntries(metadatas.map((m) => [m.name, m]));
|
|
28
|
+
const pkgName = systemName.toLowerCase().replace(/\s+/g, "-");
|
|
29
|
+
const combined = components.map((c) => ({
|
|
30
|
+
name: c.name,
|
|
31
|
+
import: `import { ${c.name} } from '${pkgName}'`,
|
|
32
|
+
description: c.description,
|
|
33
|
+
props: c.props,
|
|
34
|
+
examples: c.examples.slice(0, 2),
|
|
35
|
+
tokens: Object.keys(c.cssVars.light),
|
|
36
|
+
aiGuidance: metaMap[c.name]?.aiGuidance ?? []
|
|
37
|
+
}));
|
|
38
|
+
return JSON.stringify({ system: systemName, components: combined }, null, 2);
|
|
39
|
+
}
|
|
40
|
+
function generateCursorContext(systemName) {
|
|
41
|
+
return `# Design System Context for Cursor
|
|
42
|
+
|
|
43
|
+
This project uses the ${systemName} design system. When generating any UI code:
|
|
44
|
+
|
|
45
|
+
1. Import components from '${systemName.toLowerCase().replace(/\s+/g, "-")}'
|
|
46
|
+
2. Use CSS variables from tokens/base.css for all visual values
|
|
47
|
+
3. Refer to ai/components.json for the full component API and usage rules
|
|
48
|
+
|
|
49
|
+
@file ../ai/system-prompt.md
|
|
50
|
+
@file ../ai/components.json
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
53
|
+
function generateCopilotInstructions(systemName) {
|
|
54
|
+
const pkgName = systemName.toLowerCase().replace(/\s+/g, "-");
|
|
55
|
+
return `# GitHub Copilot Instructions
|
|
56
|
+
|
|
57
|
+
This project uses the ${systemName} design system.
|
|
58
|
+
|
|
59
|
+
- Always import components from '${pkgName}'
|
|
60
|
+
- Never hardcode colour, spacing, or typography values \u2014 use CSS custom properties
|
|
61
|
+
- Refer to ai/components.json for component APIs, props, and usage guidance
|
|
62
|
+
- Refer to ai/system-prompt.md for design system rules
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
generateComponentsJson,
|
|
67
|
+
generateCopilotInstructions,
|
|
68
|
+
generateCursorContext,
|
|
69
|
+
generateSystemPrompt
|
|
70
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COMPONENT_JSON_DEFINITIONS
|
|
3
|
+
} from "./chunk-A7VW6SII.js";
|
|
4
|
+
|
|
5
|
+
// src/adapters/react/generateComponentJson.ts
|
|
6
|
+
function generateComponentJson(name, resolvedCssVars) {
|
|
7
|
+
const definition = COMPONENT_JSON_DEFINITIONS[name];
|
|
8
|
+
if (!definition) throw new Error(`No JSON definition found for component: ${name}`);
|
|
9
|
+
return {
|
|
10
|
+
...definition,
|
|
11
|
+
cssVars: resolvedCssVars
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
generateComponentJson
|
|
16
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COMPONENT_METADATA_DEFINITIONS
|
|
3
|
+
} from "./chunk-A7VW6SII.js";
|
|
4
|
+
|
|
5
|
+
// src/adapters/react/generateComponentMetadata.ts
|
|
6
|
+
function generateComponentMetadata(name) {
|
|
7
|
+
const definition = COMPONENT_METADATA_DEFINITIONS[name];
|
|
8
|
+
if (!definition) throw new Error(`No metadata definition found for component: ${name}`);
|
|
9
|
+
return definition;
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
generateComponentMetadata
|
|
13
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/adapters/react/generateRegistry.ts
|
|
2
|
+
function generateRegistry(systemName, version, components) {
|
|
3
|
+
return {
|
|
4
|
+
$schema: "https://ui.shadcn.com/schema/registry.json",
|
|
5
|
+
name: systemName,
|
|
6
|
+
version,
|
|
7
|
+
description: `Design system generated by dsforge`,
|
|
8
|
+
items: components.map((c) => ({
|
|
9
|
+
name: c.name.toLowerCase(),
|
|
10
|
+
type: "registry:ui",
|
|
11
|
+
description: c.description,
|
|
12
|
+
files: [{ path: `components/${c.name}/${c.name}.tsx`, type: "registry:component" }],
|
|
13
|
+
cssVars: c.cssVars
|
|
14
|
+
}))
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
generateRegistry
|
|
19
|
+
};
|
|
@@ -2,9 +2,12 @@ import {
|
|
|
2
2
|
PRESETS,
|
|
3
3
|
RADIUS_PRESETS,
|
|
4
4
|
SPACING_PRESETS,
|
|
5
|
-
buildSemanticSpacing
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
buildSemanticSpacing
|
|
6
|
+
} from "./chunk-5YT3VNE6.js";
|
|
7
|
+
import {
|
|
8
|
+
COMPONENT_JSON_DEFINITIONS,
|
|
9
|
+
COMPONENT_METADATA_DEFINITIONS
|
|
10
|
+
} from "./chunk-A7VW6SII.js";
|
|
8
11
|
|
|
9
12
|
// src/generators/showcase/types.ts
|
|
10
13
|
function esc(s) {
|
|
@@ -211,20 +214,9 @@ function buildMotionSection(config) {
|
|
|
211
214
|
}
|
|
212
215
|
|
|
213
216
|
// src/generators/showcase/page.ts
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
<div class="locked-title">${label} \u2014 dsforge Pro</div>
|
|
218
|
-
<p class="locked-desc">This tab is available with a dsforge Pro license.</p>
|
|
219
|
-
<p class="locked-hint">Set the <code>DSFORGE_KEY</code> environment variable to unlock.</p>
|
|
220
|
-
</div>`;
|
|
221
|
-
function buildComponentPage(def, isPro) {
|
|
222
|
-
const tabId = (tab) => `${def.id}-tab-${tab}`;
|
|
223
|
-
const panelId = (tab) => `${def.id}-panel-${tab}`;
|
|
224
|
-
const overviewHtml = `
|
|
225
|
-
<div class="comp-overview">${def.overviewHtml}</div>
|
|
226
|
-
<p class="component-description">${esc(def.description)}</p>`;
|
|
227
|
-
const propsTable = `
|
|
217
|
+
function buildPropsTable(props) {
|
|
218
|
+
const sorted = [...props].sort((a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0));
|
|
219
|
+
return `
|
|
228
220
|
<table class="props-table">
|
|
229
221
|
<thead>
|
|
230
222
|
<tr>
|
|
@@ -236,7 +228,7 @@ function buildComponentPage(def, isPro) {
|
|
|
236
228
|
</tr>
|
|
237
229
|
</thead>
|
|
238
230
|
<tbody>
|
|
239
|
-
${
|
|
231
|
+
${sorted.map(
|
|
240
232
|
(p) => `
|
|
241
233
|
<tr>
|
|
242
234
|
<td><code class="prop-name">${esc(p.name)}</code></td>
|
|
@@ -250,26 +242,83 @@ function buildComponentPage(def, isPro) {
|
|
|
250
242
|
).join("")}
|
|
251
243
|
</tbody>
|
|
252
244
|
</table>`;
|
|
253
|
-
|
|
245
|
+
}
|
|
246
|
+
function buildExamplesHtml(id, examples) {
|
|
247
|
+
return examples.map(
|
|
254
248
|
(ex, i) => `
|
|
255
249
|
<div class="example-block">
|
|
256
250
|
<div class="example-header">
|
|
257
251
|
<div class="example-label">${esc(ex.label)}</div>
|
|
258
|
-
|
|
252
|
+
${ex.description ? `<div class="example-desc">${esc(ex.description)}</div>` : ""}
|
|
259
253
|
</div>
|
|
260
|
-
|
|
254
|
+
${ex.previewHtml ? `<div class="example-preview">${ex.previewHtml}</div>` : ""}
|
|
261
255
|
<div class="example-code-wrap">
|
|
262
256
|
<div class="example-code-bar">
|
|
263
257
|
<span>TSX</span>
|
|
264
|
-
<button class="copy-btn" onclick="copyCode('${
|
|
258
|
+
<button class="copy-btn" onclick="copyCode('${id}-ex-${i}', this)">Copy</button>
|
|
265
259
|
</div>
|
|
266
|
-
<pre class="example-code" id="${
|
|
260
|
+
<pre class="example-code" id="${id}-ex-${i}">${esc(ex.code)}</pre>
|
|
267
261
|
</div>
|
|
268
262
|
</div>`
|
|
269
263
|
).join("");
|
|
270
|
-
|
|
264
|
+
}
|
|
265
|
+
function requirementBadge(val) {
|
|
266
|
+
if (typeof val === "boolean") {
|
|
267
|
+
return val ? '<span class="a11y-badge a11y-badge-aa">Yes</span>' : '<span class="a11y-badge" style="background:var(--color-bg-overlay,#f1f5f9);color:var(--color-text-secondary,#64748b);border:1px solid var(--color-border-default,#e2e8f0)">No</span>';
|
|
268
|
+
}
|
|
269
|
+
return `<span class="a11y-badge a11y-badge-aa">${esc(val)}</span>`;
|
|
270
|
+
}
|
|
271
|
+
function buildA11yHtml(contract, wcagItems) {
|
|
272
|
+
const requirements = [
|
|
273
|
+
{
|
|
274
|
+
label: "Keyboard operable",
|
|
275
|
+
value: contract.keyboard,
|
|
276
|
+
desc: "Component can be fully operated with keyboard alone. No mouse required."
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
label: "Focus ring",
|
|
280
|
+
value: contract.focusRing,
|
|
281
|
+
desc: "Visible focus indicator requirement when the element receives keyboard focus."
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
label: "aria-label",
|
|
285
|
+
value: contract.ariaLabel,
|
|
286
|
+
desc: "When a programmatic accessible label must be provided by the consumer."
|
|
287
|
+
}
|
|
288
|
+
];
|
|
289
|
+
const requirementsHtml = `
|
|
290
|
+
<div class="a11y-requirements">
|
|
291
|
+
${requirements.map(
|
|
292
|
+
(r) => `
|
|
293
|
+
<div class="a11y-req-row">
|
|
294
|
+
<div class="a11y-req-meta">
|
|
295
|
+
<span class="a11y-req-label">${esc(r.label)}</span>
|
|
296
|
+
${requirementBadge(r.value)}
|
|
297
|
+
</div>
|
|
298
|
+
<p class="a11y-req-desc">${esc(r.desc)}</p>
|
|
299
|
+
</div>`
|
|
300
|
+
).join("")}
|
|
301
|
+
${contract.roles.length > 0 ? `<div class="a11y-req-row">
|
|
302
|
+
<div class="a11y-req-meta">
|
|
303
|
+
<span class="a11y-req-label">ARIA roles</span>
|
|
304
|
+
<span style="display:flex;gap:4px;flex-wrap:wrap">
|
|
305
|
+
${contract.roles.map((r) => `<code class="a11y-role-chip">${esc(r)}</code>`).join("")}
|
|
306
|
+
</span>
|
|
307
|
+
</div>
|
|
308
|
+
<p class="a11y-req-desc">The semantic roles this component exposes to assistive technology.</p>
|
|
309
|
+
</div>` : ""}
|
|
310
|
+
${contract.notes.map(
|
|
311
|
+
(note) => `
|
|
312
|
+
<div class="a11y-note">
|
|
313
|
+
<span class="a11y-note-icon">\u21B3</span>
|
|
314
|
+
<p class="a11y-note-text">${esc(note)}</p>
|
|
315
|
+
</div>`
|
|
316
|
+
).join("")}
|
|
317
|
+
</div>`;
|
|
318
|
+
const wcagHtml = wcagItems.length > 0 ? `
|
|
319
|
+
<div class="group-title" style="margin-top:32px">WCAG Criteria</div>
|
|
271
320
|
<div class="a11y-list">
|
|
272
|
-
${
|
|
321
|
+
${wcagItems.map(
|
|
273
322
|
(item) => `
|
|
274
323
|
<div class="a11y-item">
|
|
275
324
|
<div class="a11y-header">
|
|
@@ -279,57 +328,71 @@ function buildComponentPage(def, isPro) {
|
|
|
279
328
|
<p class="a11y-desc">${esc(item.description)}</p>
|
|
280
329
|
</div>`
|
|
281
330
|
).join("")}
|
|
282
|
-
</div
|
|
283
|
-
|
|
284
|
-
|
|
331
|
+
</div>` : "";
|
|
332
|
+
return `
|
|
333
|
+
<div class="group-title">Requirements</div>
|
|
334
|
+
${requirementsHtml}
|
|
335
|
+
${wcagHtml}`;
|
|
336
|
+
}
|
|
337
|
+
function buildAiMetaHtml(id, meta) {
|
|
338
|
+
const metaJson = JSON.stringify(meta, null, 2);
|
|
339
|
+
return `
|
|
285
340
|
<div class="ai-meta-intro">
|
|
286
|
-
<p>This JSON contract is emitted to <code>
|
|
341
|
+
<p>This JSON contract is emitted to <code>components/${esc(meta.name)}/${esc(meta.name)}.metadata.json</code>.
|
|
287
342
|
AI coding assistants use it to understand when and how to use this component correctly.</p>
|
|
288
343
|
</div>
|
|
289
344
|
<div class="example-code-wrap" style="margin-top:16px">
|
|
290
345
|
<div class="example-code-bar">
|
|
291
346
|
<span>JSON</span>
|
|
292
|
-
<button class="copy-btn" onclick="copyCode('${
|
|
347
|
+
<button class="copy-btn" onclick="copyCode('${id}-ai-meta', this)">Copy</button>
|
|
293
348
|
</div>
|
|
294
|
-
<pre class="example-code" id="${
|
|
349
|
+
<pre class="example-code" id="${id}-ai-meta">${esc(metaJson)}</pre>
|
|
295
350
|
</div>
|
|
296
351
|
<div class="ai-guidance">
|
|
297
352
|
<div class="group-title" style="margin-top:24px">AI usage guidance</div>
|
|
298
353
|
<ul class="ai-guidance-list">
|
|
299
|
-
${
|
|
354
|
+
${meta.aiGuidance.map((g) => `<li>${esc(g)}</li>`).join("")}
|
|
300
355
|
</ul>
|
|
301
356
|
</div>`;
|
|
357
|
+
}
|
|
358
|
+
function buildComponentPage(input) {
|
|
359
|
+
const { id, description, overviewHtml, json, showcaseExamples, a11yContract, a11yItems, metadata } = input;
|
|
360
|
+
const tabId = (tab) => `${id}-tab-${tab}`;
|
|
361
|
+
const panelId = (tab) => `${id}-panel-${tab}`;
|
|
362
|
+
const overviewContent = `
|
|
363
|
+
<div class="comp-overview">${overviewHtml}</div>
|
|
364
|
+
<p class="component-description">${esc(description)}</p>`;
|
|
365
|
+
const propsContent = buildPropsTable(json.props);
|
|
366
|
+
const examplesContent = buildExamplesHtml(id, showcaseExamples);
|
|
367
|
+
const a11yContent = a11yContract ? buildA11yHtml(a11yContract, a11yItems) : `<div class="a11y-list">${a11yItems.map((item) => `
|
|
368
|
+
<div class="a11y-item">
|
|
369
|
+
<div class="a11y-header">
|
|
370
|
+
<span class="a11y-criterion">${esc(item.criterion)}</span>
|
|
371
|
+
<span class="a11y-badge a11y-badge-${item.level.toLowerCase()}">WCAG ${item.level}</span>
|
|
372
|
+
</div>
|
|
373
|
+
<p class="a11y-desc">${esc(item.description)}</p>
|
|
374
|
+
</div>`).join("")}</div>`;
|
|
375
|
+
const aiContent = metadata ? buildAiMetaHtml(id, metadata) : "";
|
|
302
376
|
const tabs = [
|
|
303
|
-
{ id: "overview", label: "Overview", content:
|
|
304
|
-
{ id: "props", label: "Props", content:
|
|
305
|
-
{ id: "examples", label: "Examples", content:
|
|
306
|
-
{
|
|
307
|
-
|
|
308
|
-
label: "Accessibility",
|
|
309
|
-
content: isPro ? a11yHtml : lockedPanel("Accessibility"),
|
|
310
|
-
locked: !isPro
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
id: "ai-metadata",
|
|
314
|
-
label: "AI Metadata",
|
|
315
|
-
content: isPro ? aiHtml : lockedPanel("AI Metadata"),
|
|
316
|
-
locked: !isPro
|
|
317
|
-
}
|
|
377
|
+
{ id: "overview", label: "Overview", content: overviewContent },
|
|
378
|
+
{ id: "props", label: "Props", content: propsContent },
|
|
379
|
+
{ id: "examples", label: "Examples", content: examplesContent },
|
|
380
|
+
{ id: "accessibility", label: "Accessibility", content: a11yContent },
|
|
381
|
+
{ id: "ai-metadata", label: "AI Metadata", content: aiContent }
|
|
318
382
|
];
|
|
319
383
|
return `
|
|
320
|
-
<div class="comp-tabs" id="${
|
|
384
|
+
<div class="comp-tabs" id="${id}-tabs">
|
|
321
385
|
<div class="comp-tab-bar" role="tablist">
|
|
322
386
|
${tabs.map(
|
|
323
387
|
(t, i) => `
|
|
324
388
|
<button
|
|
325
|
-
class="comp-tab${i === 0 ? " active" : ""}
|
|
389
|
+
class="comp-tab${i === 0 ? " active" : ""}"
|
|
326
390
|
id="${tabId(t.id)}"
|
|
327
391
|
role="tab"
|
|
328
392
|
aria-selected="${i === 0}"
|
|
329
393
|
aria-controls="${panelId(t.id)}"
|
|
330
|
-
onclick="
|
|
331
|
-
|
|
332
|
-
>${esc(t.label)}${t.locked ? " 🔒" : ""}</button>`
|
|
394
|
+
onclick="switchTab('${id}', '${t.id}', this)"
|
|
395
|
+
>${esc(t.label)}</button>`
|
|
333
396
|
).join("")}
|
|
334
397
|
</div>
|
|
335
398
|
${tabs.map(
|
|
@@ -2027,7 +2090,23 @@ function generateShowcase(config, resolution) {
|
|
|
2027
2090
|
];
|
|
2028
2091
|
const componentItems = SHOWCASE_COMPONENTS.map(({ id, label }) => ({ id, label }));
|
|
2029
2092
|
const allItems = [...foundationItems, ...componentItems];
|
|
2030
|
-
const
|
|
2093
|
+
const flatTokens = Object.fromEntries(
|
|
2094
|
+
Object.entries(tokens).map(([k, v]) => [
|
|
2095
|
+
k.replace(/^(global|semantic|component)\./, ""),
|
|
2096
|
+
v
|
|
2097
|
+
])
|
|
2098
|
+
);
|
|
2099
|
+
const lightTheme = config.themes?.["light"] ?? {};
|
|
2100
|
+
const darkTheme = config.themes?.["dark"] ?? {};
|
|
2101
|
+
const lightCssVars = {
|
|
2102
|
+
...Object.fromEntries(Object.entries(flatTokens).map(([k, v]) => [`--${k}`, v])),
|
|
2103
|
+
...Object.fromEntries(Object.entries(lightTheme).map(([k, v]) => [`--${k}`, String(v)]))
|
|
2104
|
+
};
|
|
2105
|
+
const darkCssVars = {
|
|
2106
|
+
...Object.fromEntries(Object.entries(flatTokens).map(([k, v]) => [`--${k}`, v])),
|
|
2107
|
+
...Object.fromEntries(Object.entries(darkTheme).map(([k, v]) => [`--${k}`, String(v)]))
|
|
2108
|
+
};
|
|
2109
|
+
const resolvedCssVars = { light: lightCssVars, dark: darkCssVars };
|
|
2031
2110
|
const sections = {
|
|
2032
2111
|
colors: buildColorSection(config, tokens),
|
|
2033
2112
|
typography: buildTypographySection(config),
|
|
@@ -2036,23 +2115,52 @@ function generateShowcase(config, resolution) {
|
|
|
2036
2115
|
elevation: buildElevationSection(config),
|
|
2037
2116
|
motion: buildMotionSection(config),
|
|
2038
2117
|
...Object.fromEntries(
|
|
2039
|
-
SHOWCASE_COMPONENTS.map((entry) =>
|
|
2040
|
-
entry.
|
|
2041
|
-
|
|
2042
|
-
|
|
2118
|
+
SHOWCASE_COMPONENTS.map((entry) => {
|
|
2119
|
+
const defData = entry.def(config, tokens);
|
|
2120
|
+
const jsonDef = COMPONENT_JSON_DEFINITIONS[entry.label];
|
|
2121
|
+
const metaDef = COMPONENT_METADATA_DEFINITIONS[entry.label] ?? null;
|
|
2122
|
+
const componentJson = jsonDef ? { ...jsonDef, cssVars: resolvedCssVars } : {
|
|
2123
|
+
name: entry.label,
|
|
2124
|
+
description: entry.pageDescription,
|
|
2125
|
+
props: defData.props,
|
|
2126
|
+
examples: defData.examples.map((ex) => ({ label: ex.label, code: ex.code })),
|
|
2127
|
+
cssVars: resolvedCssVars
|
|
2128
|
+
};
|
|
2129
|
+
return [
|
|
2130
|
+
entry.id,
|
|
2131
|
+
buildComponentPage({
|
|
2132
|
+
id: entry.id,
|
|
2133
|
+
label: entry.label,
|
|
2134
|
+
description: componentJson.description,
|
|
2135
|
+
overviewHtml: defData.overviewHtml,
|
|
2136
|
+
json: componentJson,
|
|
2137
|
+
showcaseExamples: defData.examples,
|
|
2138
|
+
a11yContract: metaDef?.accessibilityContract ?? null,
|
|
2139
|
+
a11yItems: defData.a11y,
|
|
2140
|
+
metadata: metaDef
|
|
2141
|
+
})
|
|
2142
|
+
];
|
|
2143
|
+
})
|
|
2043
2144
|
)
|
|
2044
2145
|
};
|
|
2045
|
-
const flatTokens = Object.fromEntries(
|
|
2046
|
-
Object.entries(tokens).map(([k, v]) => [
|
|
2047
|
-
k.replace(/^(global|semantic|component)\./, ""),
|
|
2048
|
-
v
|
|
2049
|
-
])
|
|
2050
|
-
);
|
|
2051
|
-
const lightTheme = config.themes?.["light"] ?? {};
|
|
2052
|
-
const darkTheme = config.themes?.["dark"] ?? {};
|
|
2053
2146
|
const themeCssLight = Object.entries({ ...flatTokens, ...lightTheme }).map(([k, v]) => ` --${k}: ${v};`).join("\n");
|
|
2054
2147
|
const themeCssDark = Object.entries({ ...flatTokens, ...darkTheme }).map(([k, v]) => ` --${k}: ${v};`).join("\n");
|
|
2055
2148
|
const densityCss = buildDensityCss();
|
|
2149
|
+
const showcaseData = {
|
|
2150
|
+
systemName: name,
|
|
2151
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2152
|
+
components: SHOWCASE_COMPONENTS.map((entry) => {
|
|
2153
|
+
const jsonDef = COMPONENT_JSON_DEFINITIONS[entry.label];
|
|
2154
|
+
const metaDef = COMPONENT_METADATA_DEFINITIONS[entry.label] ?? null;
|
|
2155
|
+
return {
|
|
2156
|
+
json: jsonDef ? { ...jsonDef, cssVars: resolvedCssVars } : null,
|
|
2157
|
+
metadata: metaDef
|
|
2158
|
+
};
|
|
2159
|
+
})
|
|
2160
|
+
};
|
|
2161
|
+
const dataScript = `<script>
|
|
2162
|
+
window.__DSFORGE__ = ${JSON.stringify(showcaseData)};
|
|
2163
|
+
</script>`;
|
|
2056
2164
|
return `<!DOCTYPE html>
|
|
2057
2165
|
<html lang="en" data-theme="light" data-density="${defaultDensity}">
|
|
2058
2166
|
<head>
|
|
@@ -2143,27 +2251,17 @@ ${densityCss}
|
|
|
2143
2251
|
border: 1px solid var(--color-border-default, #e2e8f0);
|
|
2144
2252
|
border-radius: 7px; padding: 3px;
|
|
2145
2253
|
}
|
|
2146
|
-
.density-toggle.locked { opacity: 0.5; cursor: not-allowed; }
|
|
2147
2254
|
.density-btn {
|
|
2148
2255
|
padding: 3px 10px; border-radius: 4px; border: none;
|
|
2149
2256
|
background: transparent; font-size: 12px; cursor: pointer;
|
|
2150
2257
|
color: var(--color-text-secondary, #64748b);
|
|
2151
2258
|
transition: background 120ms, color 120ms;
|
|
2152
2259
|
}
|
|
2153
|
-
.density-btn:disabled { cursor: not-allowed; }
|
|
2154
2260
|
.density-btn.active {
|
|
2155
2261
|
background: var(--color-bg-default, #fff);
|
|
2156
2262
|
color: var(--color-text-primary, #0f172a); font-weight: 500;
|
|
2157
2263
|
box-shadow: 0 1px 2px rgb(0 0 0 / 0.06);
|
|
2158
2264
|
}
|
|
2159
|
-
.density-lock {
|
|
2160
|
-
font-size: 10px; font-weight: 600; letter-spacing: 0.04em;
|
|
2161
|
-
color: var(--color-text-secondary, #64748b);
|
|
2162
|
-
padding: 2px 6px; border-radius: 4px;
|
|
2163
|
-
background: var(--color-bg-overlay, #f1f5f9);
|
|
2164
|
-
border: 1px solid var(--color-border-default, #e2e8f0);
|
|
2165
|
-
white-space: nowrap;
|
|
2166
|
-
}
|
|
2167
2265
|
.content { padding: 36px 40px 80px; max-width: 860px; }
|
|
2168
2266
|
.page { display: none; }
|
|
2169
2267
|
.page.active { display: block; }
|
|
@@ -2334,6 +2432,20 @@ ${densityCss}
|
|
|
2334
2432
|
}
|
|
2335
2433
|
|
|
2336
2434
|
/* \u2500\u2500 Accessibility \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
2435
|
+
/* Requirements grid */
|
|
2436
|
+
.a11y-requirements { display: flex; flex-direction: column; gap: 0; border: 1px solid var(--color-border-default, #e2e8f0); border-radius: 10px; overflow: hidden; margin-bottom: 4px; }
|
|
2437
|
+
.a11y-req-row { padding: 14px 16px; border-bottom: 1px solid var(--color-border-default, #e2e8f0); background: var(--color-bg-default, #fff); }
|
|
2438
|
+
.a11y-req-row:last-child { border-bottom: none; }
|
|
2439
|
+
.a11y-req-meta { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; flex-wrap: wrap; }
|
|
2440
|
+
.a11y-req-label { font-size: 13px; font-weight: 600; color: var(--color-text-primary, #0f172a); }
|
|
2441
|
+
.a11y-req-desc { font-size: 12px; color: var(--color-text-secondary, #64748b); line-height: 1.55; margin: 0; }
|
|
2442
|
+
.a11y-role-chip { font-family: monospace; font-size: 12px; background: var(--color-bg-overlay, #f1f5f9); border: 1px solid var(--color-border-default, #e2e8f0); border-radius: 4px; padding: 1px 7px; color: var(--color-action, #2563eb); }
|
|
2443
|
+
/* Notes */
|
|
2444
|
+
.a11y-note { display: flex; gap: 8px; padding: 10px 16px; border-bottom: 1px solid var(--color-border-default, #e2e8f0); background: var(--color-bg-subtle, #f8fafc); }
|
|
2445
|
+
.a11y-note:last-child { border-bottom: none; }
|
|
2446
|
+
.a11y-note-icon { font-size: 12px; color: var(--color-text-secondary, #64748b); flex-shrink: 0; padding-top: 1px; }
|
|
2447
|
+
.a11y-note-text { font-size: 12px; color: var(--color-text-secondary, #64748b); line-height: 1.55; margin: 0; }
|
|
2448
|
+
/* WCAG criteria list */
|
|
2337
2449
|
.a11y-list { display: flex; flex-direction: column; gap: 1px; }
|
|
2338
2450
|
.a11y-item { padding: 16px; border: 1px solid var(--color-border-default, #e2e8f0); border-radius: 8px; margin-bottom: 10px; background: var(--color-bg-default, #fff); }
|
|
2339
2451
|
.a11y-header { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
|
|
@@ -2351,21 +2463,6 @@ ${densityCss}
|
|
|
2351
2463
|
.ai-guidance-list { padding-left: 20px; display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
|
|
2352
2464
|
.ai-guidance-list li { font-size: 13px; color: var(--color-text-secondary, #64748b); line-height: 1.6; }
|
|
2353
2465
|
|
|
2354
|
-
/* \u2500\u2500 Locked tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
2355
|
-
.comp-tab.locked { opacity: 0.45; cursor: default; }
|
|
2356
|
-
.comp-tab.locked:hover { color: var(--color-text-secondary, #64748b); }
|
|
2357
|
-
.locked-panel {
|
|
2358
|
-
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
2359
|
-
padding: 64px 32px; text-align: center;
|
|
2360
|
-
border: 1px dashed var(--color-border-default, #e2e8f0); border-radius: 10px;
|
|
2361
|
-
background: var(--color-bg-subtle, #f8fafc);
|
|
2362
|
-
}
|
|
2363
|
-
.locked-icon { font-size: 28px; color: var(--color-text-secondary, #64748b); margin-bottom: 12px; }
|
|
2364
|
-
.locked-title { font-size: 14px; font-weight: 600; color: var(--color-text-primary, #0f172a); margin-bottom: 8px; }
|
|
2365
|
-
.locked-desc { font-size: 13px; color: var(--color-text-secondary, #64748b); max-width: 360px; line-height: 1.6; margin-bottom: 6px; }
|
|
2366
|
-
.locked-hint { font-size: 12px; color: var(--color-text-secondary, #64748b); font-family: monospace; }
|
|
2367
|
-
.locked-hint code { background: var(--color-bg-overlay, #f1f5f9); padding: 1px 6px; border-radius: 4px; border: 1px solid var(--color-border-default, #e2e8f0); }
|
|
2368
|
-
|
|
2369
2466
|
/* \u2500\u2500 Component primitives \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
2370
2467
|
.ds-btn { border: none; cursor: pointer; font-size: 14px; font-weight: 500; padding: var(--component-padding-sm, 8px) var(--component-padding-md, 16px); border-radius: var(--radius-md, 4px); transition: filter 120ms; }
|
|
2371
2468
|
.ds-btn:hover:not(:disabled) { filter: brightness(0.92); }
|
|
@@ -2403,6 +2500,7 @@ ${densityCss}
|
|
|
2403
2500
|
@keyframes dsforge-spin { to { transform: rotate(360deg); } }
|
|
2404
2501
|
@media (prefers-reduced-motion: reduce) { @keyframes dsforge-spin { to { transform: none; } } }
|
|
2405
2502
|
</style>
|
|
2503
|
+
${dataScript}
|
|
2406
2504
|
</head>
|
|
2407
2505
|
<body>
|
|
2408
2506
|
|
|
@@ -2435,16 +2533,11 @@ ${densityCss}
|
|
|
2435
2533
|
${esc(name)} / <span id="topbar-current">Colors</span>
|
|
2436
2534
|
</div>
|
|
2437
2535
|
<div class="topbar-actions">
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
${PRESETS.map((p) => `
|
|
2444
|
-
<button class="density-btn${p === defaultDensity ? " active" : ""}" disabled>${p.charAt(0).toUpperCase() + p.slice(1)}</button>
|
|
2445
|
-
`).join("")}
|
|
2446
|
-
<span class="density-lock">\u2298 Pro</span>
|
|
2447
|
-
</div>`}
|
|
2536
|
+
<div class="density-toggle" id="density-toggle">
|
|
2537
|
+
${PRESETS.map((p) => `
|
|
2538
|
+
<button class="density-btn${p === defaultDensity ? " active" : ""}" onclick="setDensity('${p}', this)">${p.charAt(0).toUpperCase() + p.slice(1)}</button>
|
|
2539
|
+
`).join("")}
|
|
2540
|
+
</div>
|
|
2448
2541
|
${themes.length >= 2 ? `
|
|
2449
2542
|
<div class="theme-toggle">
|
|
2450
2543
|
${themes.map(
|