@nghitrum/dsforge 0.1.5-alpha.8 → 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-JUMR3N5J.js → chunk-5YT3VNE6.js} +6 -26
- package/dist/chunk-A7VW6SII.js +436 -0
- package/dist/{chunk-QHE35QQQ.js → chunk-ZZRPNO6Z.js} +12 -17
- package/dist/cli/index.js +218 -310
- 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-LQHDCSG4.js → html-4DD6GOHE.js} +223 -130
- package/dist/index.js +86 -110
- package/package.json +1 -1
|
@@ -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(
|
|
@@ -907,7 +970,7 @@ function cardDef(config, tokens) {
|
|
|
907
970
|
// src/generators/showcase/components/badge.ts
|
|
908
971
|
function badgeDef(config, tokens) {
|
|
909
972
|
const { ff } = componentTokens(config, tokens);
|
|
910
|
-
const badgeHtml = (label, bg, color) => `<span style="display:inline-flex;align-items:center;font-family:${esc(ff)};font-size:
|
|
973
|
+
const badgeHtml = (label, bg, color) => `<span style="display:inline-flex;align-items:center;font-family:${esc(ff)};font-size:var(--font-size-caption,0.75rem);font-weight:500;padding:2px var(--spacing-2,8px);border-radius:var(--radius-full,9999px);background:${bg};color:${color};white-space:nowrap">${label}</span>`;
|
|
911
974
|
const variants = [
|
|
912
975
|
{ label: "Default", bg: "#f1f5f9", color: "#6b7280" },
|
|
913
976
|
{ label: "Success", bg: "#dcfce7", color: "#16a34a" },
|
|
@@ -931,10 +994,10 @@ function badgeDef(config, tokens) {
|
|
|
931
994
|
<div class="comp-overview-section">
|
|
932
995
|
<div class="comp-overview-label">Sizes</div>
|
|
933
996
|
<div class="comp-preview-row" style="align-items:center">
|
|
934
|
-
<span style="display:inline-flex;align-items:center;font-family:${esc(ff)};font-size:
|
|
935
|
-
<span style="display:inline-flex;align-items:center;font-family:${esc(ff)};font-size:
|
|
936
|
-
<span style="display:inline-flex;align-items:center;font-family:${esc(ff)};font-size:
|
|
937
|
-
<span style="display:inline-flex;width:8px;height:8px;border-radius:50%;background:#16a34a" title="Dot mode"></span>
|
|
997
|
+
<span style="display:inline-flex;align-items:center;font-family:${esc(ff)};font-size:var(--font-size-caption,0.75rem);font-weight:500;padding:1px var(--spacing-1,4px);border-radius:var(--radius-full,9999px);background:#dbeafe;color:#2563eb">Small</span>
|
|
998
|
+
<span style="display:inline-flex;align-items:center;font-family:${esc(ff)};font-size:var(--font-size-caption,0.75rem);font-weight:500;padding:2px var(--spacing-2,8px);border-radius:var(--radius-full,9999px);background:#dbeafe;color:#2563eb">Medium</span>
|
|
999
|
+
<span style="display:inline-flex;align-items:center;font-family:${esc(ff)};font-size:var(--font-size-body,1rem);font-weight:500;padding:var(--spacing-1,4px) var(--spacing-3,12px);border-radius:var(--radius-full,9999px);background:#dbeafe;color:#2563eb">Large</span>
|
|
1000
|
+
<span style="display:inline-flex;width:var(--spacing-2,8px);height:var(--spacing-2,8px);border-radius:50%;background:#16a34a" title="Dot mode"></span>
|
|
938
1001
|
</div>
|
|
939
1002
|
</div>`,
|
|
940
1003
|
props: [
|
|
@@ -1055,11 +1118,11 @@ function checkboxDef(config, tokens) {
|
|
|
1055
1118
|
const fill = checked || indeterminate ? "#2563eb" : C.bg;
|
|
1056
1119
|
const borderColor = checked || indeterminate ? "#2563eb" : C.border;
|
|
1057
1120
|
const mark = checked ? `<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1.5,5 4,7.5 8.5,2.5"/></svg>` : indeterminate ? `<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"><line x1="2" y1="5" x2="8" y2="5"/></svg>` : "";
|
|
1058
|
-
return `<span style="display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:2px;border:2px solid ${borderColor};background:${fill};flex-shrink:0">${mark}</span>`;
|
|
1121
|
+
return `<span style="display:inline-flex;align-items:center;justify-content:center;width:var(--control-size-md,16px);height:var(--control-size-md,16px);border-radius:var(--radius-sm,2px);border:2px solid ${borderColor};background:${fill};flex-shrink:0">${mark}</span>`;
|
|
1059
1122
|
};
|
|
1060
|
-
const checkboxHtml = (label, checked, opts = "", helper = "") => `<label style="display:inline-flex;align-items:flex-start;gap:8px;cursor:pointer;font-family:${esc(ff)};${opts}">
|
|
1123
|
+
const checkboxHtml = (label, checked, opts = "", helper = "") => `<label style="display:inline-flex;align-items:flex-start;gap:var(--spacing-2,8px);cursor:pointer;font-family:${esc(ff)};${opts}">
|
|
1061
1124
|
${boxHtml(checked)}
|
|
1062
|
-
<span style="font-size:
|
|
1125
|
+
<span style="font-size:var(--font-size-small,0.875rem);color:${C.text};line-height:1.4">${label}${helper ? `<br><span style="font-size:var(--font-size-caption,0.75rem);color:${C.textSecondary}">${helper}</span>` : ""}</span>
|
|
1063
1126
|
</label>`;
|
|
1064
1127
|
return {
|
|
1065
1128
|
id: "checkbox",
|
|
@@ -1076,9 +1139,9 @@ function checkboxDef(config, tokens) {
|
|
|
1076
1139
|
<div class="comp-preview-col">
|
|
1077
1140
|
${checkboxHtml("Unchecked", false)}
|
|
1078
1141
|
${checkboxHtml("Checked", true)}
|
|
1079
|
-
<label style="display:inline-flex;align-items:flex-start;gap:8px;cursor:pointer;font-family:${esc(ff)}">
|
|
1142
|
+
<label style="display:inline-flex;align-items:flex-start;gap:var(--spacing-2,8px);cursor:pointer;font-family:${esc(ff)}">
|
|
1080
1143
|
${boxHtml(false, true)}
|
|
1081
|
-
<span style="font-size:
|
|
1144
|
+
<span style="font-size:var(--font-size-small,0.875rem);color:${C.text}">Indeterminate</span>
|
|
1082
1145
|
</label>
|
|
1083
1146
|
${checkboxHtml("Disabled", false, "opacity:0.4;cursor:not-allowed")}
|
|
1084
1147
|
</div>
|
|
@@ -1161,9 +1224,9 @@ function checkboxDef(config, tokens) {
|
|
|
1161
1224
|
label="Select all (3 of 5)"
|
|
1162
1225
|
indeterminate
|
|
1163
1226
|
/>`,
|
|
1164
|
-
previewHtml: `<label style="display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-family:${esc(ff)}">
|
|
1227
|
+
previewHtml: `<label style="display:inline-flex;align-items:center;gap:var(--spacing-2,8px);cursor:pointer;font-family:${esc(ff)}">
|
|
1165
1228
|
${boxHtml(false, true)}
|
|
1166
|
-
<span style="font-size:
|
|
1229
|
+
<span style="font-size:var(--font-size-small,0.875rem);color:${C.text}">Select all (3 of 5)</span>
|
|
1167
1230
|
</label>`
|
|
1168
1231
|
},
|
|
1169
1232
|
{
|
|
@@ -1240,13 +1303,13 @@ function radioDef(config, tokens) {
|
|
|
1240
1303
|
};
|
|
1241
1304
|
const circleHtml = (selected) => {
|
|
1242
1305
|
const borderColor = selected ? C.action : C.border;
|
|
1243
|
-
return `<span style="display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;border:2px solid ${borderColor};background:${C.bg};flex-shrink:0">
|
|
1244
|
-
${selected ? `<span style="width:
|
|
1306
|
+
return `<span style="display:inline-flex;align-items:center;justify-content:center;width:var(--control-size-md,16px);height:var(--control-size-md,16px);border-radius:50%;border:2px solid ${borderColor};background:${C.bg};flex-shrink:0">
|
|
1307
|
+
${selected ? `<span style="width:calc(var(--control-size-md,16px) / 2);height:calc(var(--control-size-md,16px) / 2);border-radius:50%;background:${C.action}"></span>` : ""}
|
|
1245
1308
|
</span>`;
|
|
1246
1309
|
};
|
|
1247
|
-
const radioHtml = (label, selected, opts = "") => `<label style="display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-family:${esc(ff)};${opts}">
|
|
1310
|
+
const radioHtml = (label, selected, opts = "") => `<label style="display:inline-flex;align-items:center;gap:var(--spacing-2,8px);cursor:pointer;font-family:${esc(ff)};${opts}">
|
|
1248
1311
|
${circleHtml(selected)}
|
|
1249
|
-
<span style="font-size:
|
|
1312
|
+
<span style="font-size:var(--font-size-small,0.875rem);color:${C.text}">${label}</span>
|
|
1250
1313
|
</label>`;
|
|
1251
1314
|
return {
|
|
1252
1315
|
id: "radio",
|
|
@@ -1259,7 +1322,7 @@ function radioDef(config, tokens) {
|
|
|
1259
1322
|
<div class="comp-overview-label">RadioGroup (vertical)</div>
|
|
1260
1323
|
<div class="comp-preview-col">
|
|
1261
1324
|
<fieldset style="border:none;padding:0;margin:0;font-family:${esc(ff)}">
|
|
1262
|
-
<legend style="font-size:
|
|
1325
|
+
<legend style="font-size:var(--font-size-small,0.875rem);font-weight:600;color:${C.text};margin-bottom:var(--spacing-2,8px)">Notification preference</legend>
|
|
1263
1326
|
<div class="comp-preview-col">
|
|
1264
1327
|
${radioHtml("Email", true)}
|
|
1265
1328
|
${radioHtml("SMS", false)}
|
|
@@ -1342,8 +1405,8 @@ function radioDef(config, tokens) {
|
|
|
1342
1405
|
<Radio value="annual" label="Annual (save 20%)" />
|
|
1343
1406
|
</RadioGroup>`,
|
|
1344
1407
|
previewHtml: `<fieldset style="border:none;padding:0;margin:0;font-family:${esc(ff)}">
|
|
1345
|
-
<legend style="font-size:
|
|
1346
|
-
<div style="display:flex;flex-direction:column;gap:8px">
|
|
1408
|
+
<legend style="font-size:var(--font-size-small,0.875rem);font-weight:600;color:${C.text};margin-bottom:var(--spacing-2,8px)">Billing cycle</legend>
|
|
1409
|
+
<div style="display:flex;flex-direction:column;gap:var(--spacing-2,8px)">
|
|
1347
1410
|
${radioHtml("Monthly", true)}
|
|
1348
1411
|
${radioHtml("Annual (save 20%)", false)}
|
|
1349
1412
|
</div>
|
|
@@ -1358,8 +1421,8 @@ function radioDef(config, tokens) {
|
|
|
1358
1421
|
<Radio value="lg" label="L" />
|
|
1359
1422
|
</RadioGroup>`,
|
|
1360
1423
|
previewHtml: `<fieldset style="border:none;padding:0;margin:0;font-family:${esc(ff)}">
|
|
1361
|
-
<legend style="font-size:
|
|
1362
|
-
<div style="display:flex;gap:16px">
|
|
1424
|
+
<legend style="font-size:var(--font-size-small,0.875rem);font-weight:600;color:${C.text};margin-bottom:var(--spacing-2,8px)">Size</legend>
|
|
1425
|
+
<div style="display:flex;gap:var(--spacing-4,16px)">
|
|
1363
1426
|
${radioHtml("S", false)}
|
|
1364
1427
|
${radioHtml("M", true)}
|
|
1365
1428
|
${radioHtml("L", false)}
|
|
@@ -1620,11 +1683,11 @@ function toastDef(config, tokens) {
|
|
|
1620
1683
|
danger: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>`,
|
|
1621
1684
|
info: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>`
|
|
1622
1685
|
};
|
|
1623
|
-
const alertHtml = (v, title, body) => `<div role="alert" style="display:flex;gap:12px;padding:12px 16px;border-radius:${r};border:1px solid ${v.border};background:${v.bg};font-family:${esc(ff)};max-width:360px">
|
|
1686
|
+
const alertHtml = (v, title, body) => `<div role="alert" style="display:flex;gap:var(--spacing-3,12px);padding:var(--component-padding-sm,12px) var(--component-padding-md,16px);border-radius:${r};border:1px solid ${v.border};background:${v.bg};font-family:${esc(ff)};max-width:360px">
|
|
1624
1687
|
<span style="color:${v.icon};flex-shrink:0;margin-top:1px">${icons[v.name]}</span>
|
|
1625
1688
|
<div>
|
|
1626
|
-
<p style="margin:0;font-size:
|
|
1627
|
-
<p style="margin:4px 0 0;font-size:
|
|
1689
|
+
<p style="margin:0;font-size:var(--font-size-small,0.875rem);font-weight:600;color:${v.icon}">${title}</p>
|
|
1690
|
+
<p style="margin:var(--spacing-1,4px) 0 0;font-size:var(--font-size-small,0.875rem);color:var(--color-text-secondary,#6b7280)">${body}</p>
|
|
1628
1691
|
</div>
|
|
1629
1692
|
</div>`;
|
|
1630
1693
|
return {
|
|
@@ -1712,11 +1775,11 @@ function toastDef(config, tokens) {
|
|
|
1712
1775
|
>
|
|
1713
1776
|
Upgrade to continue using all features.
|
|
1714
1777
|
</Alert>`,
|
|
1715
|
-
previewHtml: `<div role="alert" style="display:flex;gap:12px;padding:12px 16px;border-radius:${r};border:1px solid ${vWarning.border};background:${vWarning.bg};font-family:${esc(ff)};max-width:360px">
|
|
1778
|
+
previewHtml: `<div role="alert" style="display:flex;gap:var(--spacing-3,12px);padding:var(--component-padding-sm,12px) var(--component-padding-md,16px);border-radius:${r};border:1px solid ${vWarning.border};background:${vWarning.bg};font-family:${esc(ff)};max-width:360px">
|
|
1716
1779
|
<span style="color:${vWarning.icon};flex-shrink:0;margin-top:1px">${icons["warning"]}</span>
|
|
1717
1780
|
<div style="flex:1">
|
|
1718
|
-
<p style="margin:0;font-size:
|
|
1719
|
-
<p style="margin:4px 0 0;font-size:
|
|
1781
|
+
<p style="margin:0;font-size:var(--font-size-small,0.875rem);font-weight:600;color:${vWarning.icon}">Your trial expires in 3 days</p>
|
|
1782
|
+
<p style="margin:var(--spacing-1,4px) 0 0;font-size:var(--font-size-small,0.875rem);color:var(--color-text-secondary,#6b7280)">Upgrade to continue using all features.</p>
|
|
1720
1783
|
</div>
|
|
1721
1784
|
<button style="background:transparent;border:none;cursor:pointer;color:var(--color-text-secondary,#6b7280);padding:2px;flex-shrink:0" aria-label="Dismiss">\u2715</button>
|
|
1722
1785
|
</div>`
|
|
@@ -1740,11 +1803,11 @@ toast.add({
|
|
|
1740
1803
|
});`,
|
|
1741
1804
|
previewHtml: `<div style="position:relative;background:var(--color-bg-subtle,#f8fafc);border:1px dashed var(--color-border-default,#e2e8f0);border-radius:${r};padding:20px;min-height:80px;font-family:${esc(ff)}">
|
|
1742
1805
|
<p style="font-size:12px;color:var(--color-text-secondary,#6b7280);margin:0 0 12px">Bottom-right overlay (fixed position)</p>
|
|
1743
|
-
<div style="display:flex;gap:12px;padding:12px 16px;border-radius:${r};border:1px solid var(--color-success-border,#86efac);background:var(--color-success-subtle,#dcfce7);box-shadow:0 4px 12px rgba(0,0,0,0.12)">
|
|
1806
|
+
<div style="display:flex;gap:var(--spacing-3,12px);padding:var(--component-padding-sm,12px) var(--component-padding-md,16px);border-radius:${r};border:1px solid var(--color-success-border,#86efac);background:var(--color-success-subtle,#dcfce7);box-shadow:0 4px 12px rgba(0,0,0,0.12)">
|
|
1744
1807
|
<span style="color:var(--color-success,#16a34a)">${icons["success"]}</span>
|
|
1745
1808
|
<div>
|
|
1746
|
-
<p style="margin:0;font-size:
|
|
1747
|
-
<p style="margin:4px 0 0;font-size:
|
|
1809
|
+
<p style="margin:0;font-size:var(--font-size-small,0.875rem);font-weight:600;color:var(--color-text-primary,#0f172a)">Saved</p>
|
|
1810
|
+
<p style="margin:var(--spacing-1,4px) 0 0;font-size:var(--font-size-small,0.875rem);color:var(--color-text-secondary,#6b7280)">Your changes have been saved.</p>
|
|
1748
1811
|
</div>
|
|
1749
1812
|
<button style="background:transparent;border:none;cursor:pointer;color:var(--color-text-secondary,#6b7280);padding:2px" aria-label="Dismiss">\u2715</button>
|
|
1750
1813
|
</div>
|
|
@@ -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(
|