@agent-scope/site 1.20.0 → 1.20.1
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/README.md +11 -1
- package/dist/index.cjs +2590 -218
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2590 -218
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,9 +8,26 @@ function normalizeOptions(options) {
|
|
|
8
8
|
outputDir: options?.outputDir ?? ".reactscope/site",
|
|
9
9
|
basePath: options?.basePath ?? "/",
|
|
10
10
|
compliancePath: options?.compliancePath ?? "",
|
|
11
|
-
|
|
11
|
+
tokenFilePath: options?.tokenFilePath ?? "",
|
|
12
|
+
title: options?.title ?? "Scope \u2014 Component Gallery",
|
|
13
|
+
iconPatterns: options?.iconPatterns ?? []
|
|
12
14
|
};
|
|
13
15
|
}
|
|
16
|
+
function flattenTokens(obj, prefix, result) {
|
|
17
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
18
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
19
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && "value" in value && "type" in value) {
|
|
20
|
+
const v = value;
|
|
21
|
+
result.push({
|
|
22
|
+
path,
|
|
23
|
+
value: String(v.value),
|
|
24
|
+
type: String(v.type)
|
|
25
|
+
});
|
|
26
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
27
|
+
flattenTokens(value, path, result);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
14
31
|
async function readJsonFile(filePath) {
|
|
15
32
|
const content = await readFile(filePath, "utf-8");
|
|
16
33
|
return JSON.parse(content);
|
|
@@ -25,7 +42,7 @@ async function readSiteData(options) {
|
|
|
25
42
|
const jsonFiles = entries.filter((f) => f.endsWith(".json"));
|
|
26
43
|
await Promise.all(
|
|
27
44
|
jsonFiles.map(async (file) => {
|
|
28
|
-
const componentName = file.replace(
|
|
45
|
+
const componentName = file.replace(/(?:\.error)?\.json$/, "");
|
|
29
46
|
const filePath = join(rendersDir, file);
|
|
30
47
|
try {
|
|
31
48
|
const renderData = await readJsonFile(filePath);
|
|
@@ -51,15 +68,63 @@ async function readSiteData(options) {
|
|
|
51
68
|
);
|
|
52
69
|
}
|
|
53
70
|
}
|
|
71
|
+
let tokenEntries;
|
|
72
|
+
let tokenMeta;
|
|
73
|
+
let siteTokenThemes;
|
|
74
|
+
if (options.tokenFilePath) {
|
|
75
|
+
try {
|
|
76
|
+
const tokenFile = await readJsonFile(options.tokenFilePath);
|
|
77
|
+
const entries = [];
|
|
78
|
+
flattenTokens(tokenFile.tokens, "", entries);
|
|
79
|
+
tokenEntries = entries;
|
|
80
|
+
tokenMeta = tokenFile.meta;
|
|
81
|
+
if (tokenFile.themes && Object.keys(tokenFile.themes).length > 0) {
|
|
82
|
+
siteTokenThemes = tokenFile.themes;
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.warn(
|
|
86
|
+
`[scope/site] Warning: could not read token file ${options.tokenFilePath}:`,
|
|
87
|
+
err instanceof Error ? err.message : String(err)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
let playgroundDefaults;
|
|
92
|
+
try {
|
|
93
|
+
const defaultsPath = join(options.inputDir, "playground-defaults.json");
|
|
94
|
+
const defaultsRaw = await readJsonFile(defaultsPath);
|
|
95
|
+
playgroundDefaults = new Map(Object.entries(defaultsRaw));
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
54
98
|
return {
|
|
55
99
|
manifest,
|
|
56
100
|
renders,
|
|
57
101
|
complianceBatch,
|
|
58
|
-
|
|
102
|
+
tokenEntries,
|
|
103
|
+
tokenThemes: siteTokenThemes,
|
|
104
|
+
tokenMeta,
|
|
105
|
+
options,
|
|
106
|
+
playgroundDefaults
|
|
59
107
|
};
|
|
60
108
|
}
|
|
61
109
|
|
|
62
110
|
// src/utils.ts
|
|
111
|
+
function matchGlob(pattern, value) {
|
|
112
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
113
|
+
const regexStr = escaped.replace(/\*\*/g, "\xA7GLOBSTAR\xA7").replace(/\*/g, "[^/]*").replace(/§GLOBSTAR§/g, ".*");
|
|
114
|
+
return new RegExp(`^${regexStr}$`, "i").test(value);
|
|
115
|
+
}
|
|
116
|
+
function domTreeToSvg(node) {
|
|
117
|
+
if (node.tag === "#text") {
|
|
118
|
+
return node.text ?? "";
|
|
119
|
+
}
|
|
120
|
+
const attrs = Object.entries(node.attrs).map(([k, v]) => ` ${k}="${escapeHtml(v)}"`).join("");
|
|
121
|
+
const children = node.children.map((child) => domTreeToSvg(child)).join("");
|
|
122
|
+
const textContent = node.text?.trim() ?? "";
|
|
123
|
+
if (!children && !textContent) {
|
|
124
|
+
return `<${node.tag}${attrs} />`;
|
|
125
|
+
}
|
|
126
|
+
return `<${node.tag}${attrs}>${textContent}${children}</${node.tag}>`;
|
|
127
|
+
}
|
|
63
128
|
function escapeHtml(str) {
|
|
64
129
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
65
130
|
}
|
|
@@ -88,6 +153,73 @@ ${indent}<summary><span class="dom-tag-open"><${escapeHtml(node.tag)}${attrsH
|
|
|
88
153
|
${textHtml}${childrenHtml}${indent}<span class="dom-tag-close"></${escapeHtml(node.tag)}></span>
|
|
89
154
|
</details>`;
|
|
90
155
|
}
|
|
156
|
+
function propControlHtml(name, prop, overrideDefault) {
|
|
157
|
+
const escapedName = escapeHtml(name);
|
|
158
|
+
const dataAttrs = `data-prop-name="${escapedName}" data-prop-type="${escapeHtml(prop.type)}"`;
|
|
159
|
+
const rawDefault = overrideDefault ?? prop.default;
|
|
160
|
+
const defaultVal = rawDefault !== void 0 ? rawDefault.replace(/^['"]|['"]$/g, "") : void 0;
|
|
161
|
+
switch (prop.type) {
|
|
162
|
+
case "boolean": {
|
|
163
|
+
const checked = defaultVal === "true" ? " checked" : "";
|
|
164
|
+
return `<div class="pg-control-row">
|
|
165
|
+
<label class="pg-control-label" for="pg-${escapedName}">${escapedName}</label>
|
|
166
|
+
<input class="pg-checkbox" type="checkbox" id="pg-${escapedName}" ${dataAttrs}${checked} />
|
|
167
|
+
</div>`;
|
|
168
|
+
}
|
|
169
|
+
case "number": {
|
|
170
|
+
const numVal = defaultVal ?? (prop.required ? "0" : "");
|
|
171
|
+
return `<div class="pg-control-row">
|
|
172
|
+
<label class="pg-control-label" for="pg-${escapedName}">${escapedName}</label>
|
|
173
|
+
<input class="pg-input" type="number" id="pg-${escapedName}" ${dataAttrs} value="${escapeHtml(numVal)}" />
|
|
174
|
+
</div>`;
|
|
175
|
+
}
|
|
176
|
+
case "union": {
|
|
177
|
+
if (prop.values && prop.values.length > 0) {
|
|
178
|
+
const options = prop.values.map((v) => {
|
|
179
|
+
const sel = defaultVal === v ? " selected" : "";
|
|
180
|
+
return `<option value="${escapeHtml(v)}"${sel}>${escapeHtml(v)}</option>`;
|
|
181
|
+
}).join("");
|
|
182
|
+
const emptyOption = prop.required ? "" : '<option value="">\u2014</option>';
|
|
183
|
+
return `<div class="pg-control-row">
|
|
184
|
+
<label class="pg-control-label" for="pg-${escapedName}">${escapedName}</label>
|
|
185
|
+
<select class="pg-select" id="pg-${escapedName}" ${dataAttrs}>${emptyOption}${options}</select>
|
|
186
|
+
</div>`;
|
|
187
|
+
}
|
|
188
|
+
const textVal = defaultVal ?? "";
|
|
189
|
+
return `<div class="pg-control-row">
|
|
190
|
+
<label class="pg-control-label" for="pg-${escapedName}">${escapedName}</label>
|
|
191
|
+
<input class="pg-input" type="text" id="pg-${escapedName}" ${dataAttrs} value="${escapeHtml(textVal)}" placeholder="${escapeHtml(prop.rawType)}" />
|
|
192
|
+
</div>`;
|
|
193
|
+
}
|
|
194
|
+
case "string": {
|
|
195
|
+
const strVal = defaultVal ?? (prop.required ? "" : "");
|
|
196
|
+
return `<div class="pg-control-row">
|
|
197
|
+
<label class="pg-control-label" for="pg-${escapedName}">${escapedName}</label>
|
|
198
|
+
<input class="pg-input" type="text" id="pg-${escapedName}" ${dataAttrs} value="${escapeHtml(strVal)}" />
|
|
199
|
+
</div>`;
|
|
200
|
+
}
|
|
201
|
+
case "function":
|
|
202
|
+
return `<div class="pg-control-row">
|
|
203
|
+
<label class="pg-control-label">${escapedName}</label>
|
|
204
|
+
<span class="pg-control-readonly">${escapeHtml(prop.rawType)}</span>
|
|
205
|
+
</div>`;
|
|
206
|
+
case "node":
|
|
207
|
+
case "element": {
|
|
208
|
+
const nodeVal = defaultVal ?? "";
|
|
209
|
+
return `<div class="pg-control-row">
|
|
210
|
+
<label class="pg-control-label" for="pg-${escapedName}">${escapedName}</label>
|
|
211
|
+
<input class="pg-input" type="text" id="pg-${escapedName}" ${dataAttrs} value="${escapeHtml(nodeVal)}" placeholder="text content" />
|
|
212
|
+
</div>`;
|
|
213
|
+
}
|
|
214
|
+
default: {
|
|
215
|
+
const otherVal = defaultVal ?? "";
|
|
216
|
+
return `<div class="pg-control-row">
|
|
217
|
+
<label class="pg-control-label" for="pg-${escapedName}">${escapedName}</label>
|
|
218
|
+
<input class="pg-input" type="text" id="pg-${escapedName}" ${dataAttrs} value="${escapeHtml(otherVal)}" placeholder="JSON" />
|
|
219
|
+
</div>`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
91
223
|
function propTableRow(name, prop) {
|
|
92
224
|
const valuesHtml = prop.values && prop.values.length > 0 ? `<br><span style="color:var(--color-muted);font-size:11px">${prop.values.map((v) => escapeHtml(v)).join(" | ")}</span>` : "";
|
|
93
225
|
const defaultHtml = prop.default !== void 0 ? escapeHtml(prop.default) : "\u2014";
|
|
@@ -104,6 +236,7 @@ function generateCSS() {
|
|
|
104
236
|
const css = `@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=JetBrains+Mono:wght@400;700&display=swap');
|
|
105
237
|
|
|
106
238
|
:root {
|
|
239
|
+
color-scheme: light;
|
|
107
240
|
--color-text: #0f0f0f;
|
|
108
241
|
--color-muted: #6b7280;
|
|
109
242
|
--color-border: #e5e7eb;
|
|
@@ -114,6 +247,31 @@ function generateCSS() {
|
|
|
114
247
|
--color-success: #16a34a;
|
|
115
248
|
--color-warn: #d97706;
|
|
116
249
|
--color-error: #dc2626;
|
|
250
|
+
--color-on-accent: #fff;
|
|
251
|
+
--color-preview-bg: #f8f8f8;
|
|
252
|
+
--color-code-text: #e2e8f0;
|
|
253
|
+
--badge-complex-border: #fbbf24;
|
|
254
|
+
--badge-complex-color: #92400e;
|
|
255
|
+
--badge-complex-bg: #fffbeb;
|
|
256
|
+
--badge-simple-border: #6ee7b7;
|
|
257
|
+
--badge-simple-color: #065f46;
|
|
258
|
+
--badge-simple-bg: #ecfdf5;
|
|
259
|
+
--badge-memo-border: #a5b4fc;
|
|
260
|
+
--badge-memo-color: #3730a3;
|
|
261
|
+
--badge-memo-bg: #eef2ff;
|
|
262
|
+
--badge-internal-border: #d1d5db;
|
|
263
|
+
--badge-internal-color: #9ca3af;
|
|
264
|
+
--badge-internal-bg: #f3f4f6;
|
|
265
|
+
--pill-on-bg: #dcfce7;
|
|
266
|
+
--pill-on-color: #166534;
|
|
267
|
+
--pill-off-bg: #fef3c7;
|
|
268
|
+
--pill-off-color: #92400e;
|
|
269
|
+
--violation-bg: #fef2f2;
|
|
270
|
+
--violation-border: #fecaca;
|
|
271
|
+
--violation-color: #991b1b;
|
|
272
|
+
--color-bg-selected: #eef2ff;
|
|
273
|
+
--overlay-bg: rgba(0,0,0,0.2);
|
|
274
|
+
--slideout-shadow: -4px 0 24px rgba(0,0,0,0.12);
|
|
117
275
|
--font-body: 'Inter', system-ui, -apple-system, sans-serif;
|
|
118
276
|
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
|
119
277
|
--radius: 6px;
|
|
@@ -121,6 +279,47 @@ function generateCSS() {
|
|
|
121
279
|
--shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);
|
|
122
280
|
}
|
|
123
281
|
|
|
282
|
+
[data-theme="dark"] {
|
|
283
|
+
color-scheme: dark;
|
|
284
|
+
--color-text: #f3f4f6;
|
|
285
|
+
--color-muted: #9ca3af;
|
|
286
|
+
--color-border: #1f2937;
|
|
287
|
+
--color-bg: #111827;
|
|
288
|
+
--color-bg-subtle: #1f2937;
|
|
289
|
+
--color-bg-code: #0d1117;
|
|
290
|
+
--color-accent: #60a5fa;
|
|
291
|
+
--color-success: #4ade80;
|
|
292
|
+
--color-warn: #fbbf24;
|
|
293
|
+
--color-error: #f87171;
|
|
294
|
+
--color-on-accent: #fff;
|
|
295
|
+
--color-preview-bg: #1f2937;
|
|
296
|
+
--color-code-text: #e2e8f0;
|
|
297
|
+
--badge-complex-border: #92400e;
|
|
298
|
+
--badge-complex-color: #fcd34d;
|
|
299
|
+
--badge-complex-bg: rgba(120,53,15,0.3);
|
|
300
|
+
--badge-simple-border: #065f46;
|
|
301
|
+
--badge-simple-color: #6ee7b7;
|
|
302
|
+
--badge-simple-bg: rgba(6,78,59,0.3);
|
|
303
|
+
--badge-memo-border: #4338ca;
|
|
304
|
+
--badge-memo-color: #a5b4fc;
|
|
305
|
+
--badge-memo-bg: rgba(67,56,202,0.2);
|
|
306
|
+
--badge-internal-border: #4b5563;
|
|
307
|
+
--badge-internal-color: #9ca3af;
|
|
308
|
+
--badge-internal-bg: #374151;
|
|
309
|
+
--pill-on-bg: rgba(22,101,52,0.3);
|
|
310
|
+
--pill-on-color: #86efac;
|
|
311
|
+
--pill-off-bg: rgba(146,64,14,0.3);
|
|
312
|
+
--pill-off-color: #fde68a;
|
|
313
|
+
--violation-bg: rgba(153,27,27,0.2);
|
|
314
|
+
--violation-border: #991b1b;
|
|
315
|
+
--violation-color: #fca5a5;
|
|
316
|
+
--color-bg-selected: rgba(99,102,241,0.15);
|
|
317
|
+
--overlay-bg: rgba(0,0,0,0.5);
|
|
318
|
+
--slideout-shadow: -4px 0 24px rgba(0,0,0,0.4);
|
|
319
|
+
--shadow-sm: 0 1px 2px rgba(0,0,0,0.4);
|
|
320
|
+
--shadow: 0 2px 4px rgba(0,0,0,0.3), 0 1px 2px rgba(0,0,0,0.2);
|
|
321
|
+
}
|
|
322
|
+
|
|
124
323
|
*, *::before, *::after { box-sizing: border-box; }
|
|
125
324
|
|
|
126
325
|
body {
|
|
@@ -145,7 +344,13 @@ body {
|
|
|
145
344
|
padding: 0 24px;
|
|
146
345
|
gap: 16px;
|
|
147
346
|
}
|
|
148
|
-
.top-nav .site-title { font-weight: 600; font-size: 15px; text-decoration: none; color: var(--color-text); }
|
|
347
|
+
.top-nav .site-title { font-weight: 600; font-size: 15px; text-decoration: none; color: var(--color-text); display: flex; align-items: center; gap: 8px; }
|
|
348
|
+
.top-nav .site-logo { flex-shrink: 0; }
|
|
349
|
+
.top-nav .breadcrumbs { display: flex; align-items: center; gap: 0; font-size: 14px; color: var(--color-text-muted); }
|
|
350
|
+
.top-nav .breadcrumbs a, .top-nav .breadcrumbs span { text-decoration: none; color: var(--color-text-muted); }
|
|
351
|
+
.top-nav .breadcrumbs a:hover { color: var(--color-text); }
|
|
352
|
+
.top-nav .breadcrumbs span:last-child { color: var(--color-text); font-weight: 500; }
|
|
353
|
+
.top-nav .breadcrumbs a::before, .top-nav .breadcrumbs span::before { content: "/"; margin: 0 8px; color: var(--color-border); }
|
|
149
354
|
.top-nav .spacer { flex: 1; }
|
|
150
355
|
.search-box {
|
|
151
356
|
border: 1px solid var(--color-border);
|
|
@@ -159,6 +364,24 @@ body {
|
|
|
159
364
|
}
|
|
160
365
|
.search-box:focus { border-color: var(--color-accent); background: var(--color-bg); }
|
|
161
366
|
|
|
367
|
+
/* Theme toggle */
|
|
368
|
+
.theme-toggle {
|
|
369
|
+
display: flex;
|
|
370
|
+
align-items: center;
|
|
371
|
+
justify-content: center;
|
|
372
|
+
width: 32px;
|
|
373
|
+
height: 32px;
|
|
374
|
+
border: 1px solid var(--color-border);
|
|
375
|
+
border-radius: var(--radius);
|
|
376
|
+
background: var(--color-bg-subtle);
|
|
377
|
+
color: var(--color-muted);
|
|
378
|
+
cursor: pointer;
|
|
379
|
+
transition: color 0.15s, border-color 0.15s;
|
|
380
|
+
padding: 0;
|
|
381
|
+
flex-shrink: 0;
|
|
382
|
+
}
|
|
383
|
+
.theme-toggle:hover { color: var(--color-text); border-color: var(--color-muted); }
|
|
384
|
+
|
|
162
385
|
/* Page layout */
|
|
163
386
|
.page-layout {
|
|
164
387
|
display: flex;
|
|
@@ -256,9 +479,9 @@ body {
|
|
|
256
479
|
color: var(--color-muted);
|
|
257
480
|
background: var(--color-bg-subtle);
|
|
258
481
|
}
|
|
259
|
-
.badge.complex { border-color:
|
|
260
|
-
.badge.simple { border-color:
|
|
261
|
-
.badge.memoized { border-color:
|
|
482
|
+
.badge.complex { border-color: var(--badge-complex-border); color: var(--badge-complex-color); background: var(--badge-complex-bg); }
|
|
483
|
+
.badge.simple { border-color: var(--badge-simple-border); color: var(--badge-simple-color); background: var(--badge-simple-bg); }
|
|
484
|
+
.badge.memoized { border-color: var(--badge-memo-border); color: var(--badge-memo-color); background: var(--badge-memo-bg); }
|
|
262
485
|
|
|
263
486
|
.filepath {
|
|
264
487
|
font-family: var(--font-mono);
|
|
@@ -281,6 +504,17 @@ body {
|
|
|
281
504
|
.section-header p { color: var(--color-muted); margin: 0; font-size: 13px; }
|
|
282
505
|
|
|
283
506
|
/* Not generated placeholder */
|
|
507
|
+
.render-failure {
|
|
508
|
+
background: var(--violation-bg);
|
|
509
|
+
border: 1px solid var(--violation-border);
|
|
510
|
+
border-radius: var(--radius);
|
|
511
|
+
padding: 12px 16px;
|
|
512
|
+
color: var(--violation-color);
|
|
513
|
+
font-size: 13px;
|
|
514
|
+
margin-bottom: 16px;
|
|
515
|
+
}
|
|
516
|
+
.matrix-cell-error { color: var(--color-error); font-size: 11px; font-weight: 600; }
|
|
517
|
+
|
|
284
518
|
.not-generated {
|
|
285
519
|
background: var(--color-bg-subtle);
|
|
286
520
|
border: 1px dashed var(--color-border);
|
|
@@ -296,7 +530,7 @@ body {
|
|
|
296
530
|
border: 1px solid var(--color-border);
|
|
297
531
|
border-radius: var(--radius);
|
|
298
532
|
overflow: hidden;
|
|
299
|
-
background:
|
|
533
|
+
background: var(--color-preview-bg);
|
|
300
534
|
display: inline-block;
|
|
301
535
|
max-width: 100%;
|
|
302
536
|
}
|
|
@@ -326,10 +560,50 @@ body {
|
|
|
326
560
|
.prop-required { color: var(--color-error); font-size: 11px; }
|
|
327
561
|
.prop-default { font-family: var(--font-mono); font-size: 11px; color: var(--color-muted); }
|
|
328
562
|
|
|
563
|
+
/* Inherited props section */
|
|
564
|
+
.inherited-props-section { margin-top: 20px; }
|
|
565
|
+
.inherited-props-heading {
|
|
566
|
+
font-size: 11px;
|
|
567
|
+
font-weight: 600;
|
|
568
|
+
text-transform: uppercase;
|
|
569
|
+
letter-spacing: 0.05em;
|
|
570
|
+
color: var(--color-muted);
|
|
571
|
+
padding-bottom: 8px;
|
|
572
|
+
border-bottom: 1px solid var(--color-border);
|
|
573
|
+
margin-bottom: 8px;
|
|
574
|
+
}
|
|
575
|
+
.inherited-group {
|
|
576
|
+
border: 1px solid var(--color-border);
|
|
577
|
+
border-radius: 6px;
|
|
578
|
+
margin-bottom: 6px;
|
|
579
|
+
}
|
|
580
|
+
.inherited-group summary {
|
|
581
|
+
padding: 6px 12px;
|
|
582
|
+
font-size: 12px;
|
|
583
|
+
font-weight: 500;
|
|
584
|
+
color: var(--color-muted);
|
|
585
|
+
cursor: pointer;
|
|
586
|
+
user-select: none;
|
|
587
|
+
display: flex;
|
|
588
|
+
align-items: center;
|
|
589
|
+
gap: 6px;
|
|
590
|
+
}
|
|
591
|
+
.inherited-group summary:hover { color: var(--color-text); }
|
|
592
|
+
.inherited-group[open] summary { border-bottom: 1px solid var(--color-border); }
|
|
593
|
+
.inherited-group-count {
|
|
594
|
+
font-size: 10px;
|
|
595
|
+
font-weight: 400;
|
|
596
|
+
color: var(--color-muted);
|
|
597
|
+
opacity: 0.7;
|
|
598
|
+
}
|
|
599
|
+
.inherited-group .props-table { font-size: 12px; }
|
|
600
|
+
.inherited-group .props-table td,
|
|
601
|
+
.inherited-group .props-table th { padding: 4px 12px; }
|
|
602
|
+
|
|
329
603
|
/* Code blocks */
|
|
330
604
|
pre.code-block {
|
|
331
605
|
background: var(--color-bg-code);
|
|
332
|
-
color:
|
|
606
|
+
color: var(--color-code-text);
|
|
333
607
|
border-radius: var(--radius);
|
|
334
608
|
padding: 16px 20px;
|
|
335
609
|
overflow-x: auto;
|
|
@@ -389,13 +663,13 @@ pre.code-block {
|
|
|
389
663
|
.pill-on {
|
|
390
664
|
display: inline-flex; align-items: center; gap: 4px;
|
|
391
665
|
padding: 2px 8px; border-radius: 999px;
|
|
392
|
-
background:
|
|
666
|
+
background: var(--pill-on-bg); color: var(--pill-on-color);
|
|
393
667
|
font-size: 11px; font-weight: 500;
|
|
394
668
|
}
|
|
395
669
|
.pill-off {
|
|
396
670
|
display: inline-flex; align-items: center; gap: 4px;
|
|
397
671
|
padding: 2px 8px; border-radius: 999px;
|
|
398
|
-
background:
|
|
672
|
+
background: var(--pill-off-bg); color: var(--pill-off-color);
|
|
399
673
|
font-size: 11px; font-weight: 500;
|
|
400
674
|
}
|
|
401
675
|
|
|
@@ -422,12 +696,12 @@ pre.code-block {
|
|
|
422
696
|
.violation-list { list-style: none; padding: 0; margin: 0; }
|
|
423
697
|
.violation-list li {
|
|
424
698
|
padding: 8px 12px;
|
|
425
|
-
background:
|
|
426
|
-
border: 1px solid
|
|
699
|
+
background: var(--violation-bg);
|
|
700
|
+
border: 1px solid var(--violation-border);
|
|
427
701
|
border-radius: var(--radius);
|
|
428
702
|
margin-bottom: 6px;
|
|
429
703
|
font-size: 13px;
|
|
430
|
-
color:
|
|
704
|
+
color: var(--violation-color);
|
|
431
705
|
}
|
|
432
706
|
.a11y-role-badge { font-family: var(--font-mono); font-size: 11px; background: var(--color-bg-subtle); border: 1px solid var(--color-border); padding: 2px 6px; border-radius: 4px; }
|
|
433
707
|
|
|
@@ -489,7 +763,7 @@ details.dom-node > summary::-webkit-details-marker { display: none; }
|
|
|
489
763
|
}
|
|
490
764
|
.component-card:hover { box-shadow: var(--shadow); }
|
|
491
765
|
.card-preview {
|
|
492
|
-
background:
|
|
766
|
+
background: var(--color-preview-bg);
|
|
493
767
|
height: 160px;
|
|
494
768
|
overflow: hidden;
|
|
495
769
|
display: flex;
|
|
@@ -555,6 +829,72 @@ details.dom-node > summary::-webkit-details-marker { display: none; }
|
|
|
555
829
|
margin: 0;
|
|
556
830
|
}
|
|
557
831
|
|
|
832
|
+
/* Collection gallery (index page) */
|
|
833
|
+
.collection-gallery-section { margin-bottom: 32px; }
|
|
834
|
+
.gallery-section-title {
|
|
835
|
+
font-size: 16px;
|
|
836
|
+
font-weight: 600;
|
|
837
|
+
margin: 0 0 12px;
|
|
838
|
+
color: var(--color-text);
|
|
839
|
+
}
|
|
840
|
+
.collection-gallery {
|
|
841
|
+
display: grid;
|
|
842
|
+
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
843
|
+
gap: 12px;
|
|
844
|
+
}
|
|
845
|
+
.collection-tile {
|
|
846
|
+
display: flex;
|
|
847
|
+
flex-direction: column;
|
|
848
|
+
border: 1px solid var(--color-border);
|
|
849
|
+
border-radius: var(--radius);
|
|
850
|
+
overflow: hidden;
|
|
851
|
+
text-decoration: none;
|
|
852
|
+
color: var(--color-text);
|
|
853
|
+
transition: box-shadow 0.15s, border-color 0.15s;
|
|
854
|
+
}
|
|
855
|
+
.collection-tile:hover {
|
|
856
|
+
box-shadow: var(--shadow);
|
|
857
|
+
border-color: var(--color-accent);
|
|
858
|
+
}
|
|
859
|
+
.coll-tile-thumbs {
|
|
860
|
+
display: grid;
|
|
861
|
+
grid-template-columns: 1fr 1fr;
|
|
862
|
+
gap: 1px;
|
|
863
|
+
background: var(--color-border);
|
|
864
|
+
height: 100px;
|
|
865
|
+
overflow: hidden;
|
|
866
|
+
}
|
|
867
|
+
.coll-thumb {
|
|
868
|
+
display: block;
|
|
869
|
+
width: 100%;
|
|
870
|
+
height: 100%;
|
|
871
|
+
object-fit: cover;
|
|
872
|
+
background: var(--color-preview-bg);
|
|
873
|
+
}
|
|
874
|
+
.coll-thumb-empty { background: var(--color-bg-subtle); }
|
|
875
|
+
.coll-tile-body { padding: 10px 14px; }
|
|
876
|
+
.coll-tile-name { font-weight: 600; font-size: 14px; margin-bottom: 2px; }
|
|
877
|
+
.coll-tile-desc {
|
|
878
|
+
font-size: 12px;
|
|
879
|
+
color: var(--color-muted);
|
|
880
|
+
margin-bottom: 4px;
|
|
881
|
+
display: -webkit-box;
|
|
882
|
+
-webkit-line-clamp: 2;
|
|
883
|
+
-webkit-box-orient: vertical;
|
|
884
|
+
overflow: hidden;
|
|
885
|
+
}
|
|
886
|
+
.coll-tile-count {
|
|
887
|
+
font-size: 11px;
|
|
888
|
+
color: var(--color-muted);
|
|
889
|
+
font-weight: 500;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/* Sidebar overview link */
|
|
893
|
+
.sidebar a:first-child {
|
|
894
|
+
font-weight: 500;
|
|
895
|
+
margin-bottom: 4px;
|
|
896
|
+
}
|
|
897
|
+
|
|
558
898
|
/* Internal component badge */
|
|
559
899
|
.badge-internal {
|
|
560
900
|
display: inline-flex;
|
|
@@ -563,153 +903,1479 @@ details.dom-node > summary::-webkit-details-marker { display: none; }
|
|
|
563
903
|
border-radius: 999px;
|
|
564
904
|
font-size: 10px;
|
|
565
905
|
font-weight: 500;
|
|
566
|
-
border: 1px solid
|
|
567
|
-
color:
|
|
568
|
-
background:
|
|
906
|
+
border: 1px solid var(--badge-internal-border);
|
|
907
|
+
color: var(--badge-internal-color);
|
|
908
|
+
background: var(--badge-internal-bg);
|
|
569
909
|
margin-left: 4px;
|
|
570
910
|
vertical-align: middle;
|
|
571
911
|
line-height: 1.4;
|
|
572
|
-
}`;
|
|
573
|
-
return `<style>
|
|
574
|
-
${css}
|
|
575
|
-
</style>`;
|
|
576
912
|
}
|
|
577
913
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
const visibleEntries = Object.entries(components).filter(([, c]) => !c.internal);
|
|
584
|
-
const collectionMap = /* @__PURE__ */ new Map();
|
|
585
|
-
for (const col of collections) {
|
|
586
|
-
collectionMap.set(col.name, []);
|
|
587
|
-
}
|
|
588
|
-
const ungrouped = [];
|
|
589
|
-
for (const [name, component] of visibleEntries) {
|
|
590
|
-
if (component.collection) {
|
|
591
|
-
if (!collectionMap.has(component.collection)) {
|
|
592
|
-
collectionMap.set(component.collection, []);
|
|
593
|
-
}
|
|
594
|
-
collectionMap.get(component.collection)?.push(name);
|
|
595
|
-
} else {
|
|
596
|
-
ungrouped.push(name);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
const hasAnyCollections = collectionMap.size > 0 || ungrouped.length < visibleEntries.length;
|
|
600
|
-
function renderLinks(names) {
|
|
601
|
-
return names.sort().map((name) => {
|
|
602
|
-
const slug = slugify(name);
|
|
603
|
-
const isActive = slug === currentSlug;
|
|
604
|
-
return `<a href="${basePath}${slug}.html" class="${isActive ? "active" : ""}">${escapeHtml(name)}</a>`;
|
|
605
|
-
}).join("\n");
|
|
606
|
-
}
|
|
607
|
-
if (!hasAnyCollections) {
|
|
608
|
-
const links = renderLinks(visibleEntries.map(([name]) => name));
|
|
609
|
-
return `<div class="sidebar-heading">Components</div>
|
|
610
|
-
${links}`;
|
|
611
|
-
}
|
|
612
|
-
const sections = [];
|
|
613
|
-
for (const [colName, names] of collectionMap) {
|
|
614
|
-
if (names.length === 0) continue;
|
|
615
|
-
sections.push(
|
|
616
|
-
`<div class="sidebar-collection-header">${escapeHtml(colName)}</div>
|
|
617
|
-
${renderLinks(names)}`
|
|
618
|
-
);
|
|
619
|
-
}
|
|
620
|
-
if (ungrouped.length > 0) {
|
|
621
|
-
sections.push(
|
|
622
|
-
`<div class="sidebar-collection-header">Ungrouped</div>
|
|
623
|
-
${renderLinks(ungrouped)}`
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
return sections.join("\n");
|
|
914
|
+
/* Playground */
|
|
915
|
+
.pg-container {
|
|
916
|
+
display: flex;
|
|
917
|
+
flex-direction: row;
|
|
918
|
+
gap: 16px;
|
|
627
919
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
920
|
+
.pg-controls {
|
|
921
|
+
width: 260px;
|
|
922
|
+
flex-shrink: 0;
|
|
923
|
+
border: 1px solid var(--color-border);
|
|
924
|
+
border-radius: var(--radius);
|
|
925
|
+
background: var(--color-bg-subtle);
|
|
926
|
+
padding: 12px 16px;
|
|
927
|
+
max-height: 500px;
|
|
928
|
+
overflow-y: auto;
|
|
929
|
+
}
|
|
930
|
+
.pg-controls-header {
|
|
931
|
+
font-size: 11px;
|
|
932
|
+
font-weight: 600;
|
|
933
|
+
text-transform: uppercase;
|
|
934
|
+
letter-spacing: 0.05em;
|
|
935
|
+
color: var(--color-muted);
|
|
936
|
+
margin-bottom: 10px;
|
|
937
|
+
}
|
|
938
|
+
.pg-control-row {
|
|
939
|
+
display: flex;
|
|
940
|
+
flex-direction: column;
|
|
941
|
+
gap: 4px;
|
|
942
|
+
margin-bottom: 10px;
|
|
943
|
+
}
|
|
944
|
+
.pg-control-row:last-child { margin-bottom: 0; }
|
|
945
|
+
.pg-control-label {
|
|
946
|
+
font-family: var(--font-mono);
|
|
947
|
+
font-size: 10px;
|
|
948
|
+
font-weight: 600;
|
|
949
|
+
color: var(--color-muted);
|
|
950
|
+
letter-spacing: 0.03em;
|
|
951
|
+
}
|
|
952
|
+
.pg-input, .pg-select {
|
|
953
|
+
width: 100%;
|
|
954
|
+
border: 1px solid var(--color-border);
|
|
955
|
+
border-radius: var(--radius);
|
|
956
|
+
padding: 4px 6px;
|
|
957
|
+
font-family: var(--font-body);
|
|
958
|
+
font-size: 12px;
|
|
959
|
+
background: var(--color-bg);
|
|
960
|
+
color: var(--color-text);
|
|
961
|
+
outline: none;
|
|
962
|
+
}
|
|
963
|
+
.pg-select {
|
|
964
|
+
appearance: none;
|
|
965
|
+
-webkit-appearance: none;
|
|
966
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
967
|
+
background-repeat: no-repeat;
|
|
968
|
+
background-position: right 6px center;
|
|
969
|
+
padding-right: 22px;
|
|
970
|
+
padding-left: 4px;
|
|
971
|
+
}
|
|
972
|
+
.pg-input:focus, .pg-select:focus {
|
|
973
|
+
border-color: var(--color-accent);
|
|
974
|
+
}
|
|
975
|
+
.pg-checkbox {
|
|
976
|
+
width: 16px;
|
|
977
|
+
height: 16px;
|
|
978
|
+
accent-color: var(--color-accent);
|
|
979
|
+
cursor: pointer;
|
|
980
|
+
}
|
|
981
|
+
.pg-control-readonly {
|
|
982
|
+
font-family: var(--font-mono);
|
|
983
|
+
font-size: 11px;
|
|
984
|
+
color: var(--color-muted);
|
|
985
|
+
font-style: italic;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/* Preview wrapper with checkerboard background */
|
|
989
|
+
.pg-preview-wrapper {
|
|
990
|
+
flex: 1;
|
|
991
|
+
min-width: 0;
|
|
992
|
+
border: 1px solid var(--color-border);
|
|
993
|
+
border-radius: var(--radius);
|
|
994
|
+
overflow: hidden;
|
|
995
|
+
position: relative;
|
|
996
|
+
display: flex;
|
|
997
|
+
flex-direction: column;
|
|
998
|
+
}
|
|
999
|
+
.pg-iframe-area {
|
|
1000
|
+
flex: 1;
|
|
1001
|
+
background-color: #fff;
|
|
1002
|
+
background-image:
|
|
1003
|
+
linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
|
|
1004
|
+
linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),
|
|
1005
|
+
linear-gradient(45deg, transparent 75%, #f0f0f0 75%),
|
|
1006
|
+
linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
|
|
1007
|
+
background-size: 16px 16px;
|
|
1008
|
+
background-position: 0 0, 0 8px, 8px -8px, -8px 0;
|
|
1009
|
+
}
|
|
1010
|
+
.pg-preview-wrapper.pg-dark .pg-iframe-area {
|
|
1011
|
+
background-color: #1a1a1a;
|
|
1012
|
+
background-image:
|
|
1013
|
+
linear-gradient(45deg, #252525 25%, transparent 25%),
|
|
1014
|
+
linear-gradient(-45deg, #252525 25%, transparent 25%),
|
|
1015
|
+
linear-gradient(45deg, transparent 75%, #252525 75%),
|
|
1016
|
+
linear-gradient(-45deg, transparent 75%, #252525 75%);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/* Toolbar */
|
|
1020
|
+
.pg-toolbar {
|
|
1021
|
+
display: flex;
|
|
1022
|
+
align-items: center;
|
|
1023
|
+
gap: 2px;
|
|
1024
|
+
padding: 4px 6px;
|
|
1025
|
+
background: var(--color-bg-subtle);
|
|
1026
|
+
border-bottom: 1px solid var(--color-border);
|
|
1027
|
+
flex-shrink: 0;
|
|
1028
|
+
}
|
|
1029
|
+
.pg-toolbar-btn {
|
|
1030
|
+
display: inline-flex;
|
|
1031
|
+
align-items: center;
|
|
1032
|
+
justify-content: center;
|
|
1033
|
+
width: 28px;
|
|
1034
|
+
height: 28px;
|
|
1035
|
+
border: none;
|
|
1036
|
+
border-radius: var(--radius);
|
|
1037
|
+
background: none;
|
|
1038
|
+
color: var(--color-muted);
|
|
1039
|
+
cursor: pointer;
|
|
1040
|
+
transition: background 0.1s, color 0.1s;
|
|
1041
|
+
padding: 0;
|
|
1042
|
+
flex-shrink: 0;
|
|
1043
|
+
}
|
|
1044
|
+
.pg-toolbar-btn:hover { background: var(--color-border); color: var(--color-text); }
|
|
1045
|
+
.pg-toolbar-btn.active { background: var(--color-accent); color: var(--color-on-accent); }
|
|
1046
|
+
.pg-toolbar-sep {
|
|
1047
|
+
width: 1px;
|
|
1048
|
+
height: 16px;
|
|
1049
|
+
background: var(--color-border);
|
|
1050
|
+
margin: 0 4px;
|
|
1051
|
+
flex-shrink: 0;
|
|
1052
|
+
}
|
|
1053
|
+
.pg-toolbar-spacer { flex: 1; }
|
|
1054
|
+
|
|
1055
|
+
/* iframe */
|
|
1056
|
+
.pg-iframe {
|
|
1057
|
+
display: block;
|
|
1058
|
+
width: 100%;
|
|
1059
|
+
min-height: 300px;
|
|
1060
|
+
height: 300px;
|
|
1061
|
+
border: none;
|
|
1062
|
+
background: transparent;
|
|
1063
|
+
color-scheme: normal;
|
|
1064
|
+
transition: max-width 0.2s ease;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/* Fullscreen overlay */
|
|
1068
|
+
.pg-preview-wrapper.pg-fullscreen {
|
|
1069
|
+
position: fixed;
|
|
1070
|
+
inset: 0;
|
|
1071
|
+
z-index: 999;
|
|
1072
|
+
border-radius: 0;
|
|
1073
|
+
border: none;
|
|
681
1074
|
}
|
|
1075
|
+
.pg-preview-wrapper.pg-fullscreen .pg-iframe-area { flex: 1; overflow: auto; }
|
|
1076
|
+
.pg-preview-wrapper.pg-fullscreen .pg-iframe { height: 100%; min-height: 100%; }
|
|
1077
|
+
|
|
1078
|
+
.pg-static-fallback {
|
|
1079
|
+
margin-top: 16px;
|
|
1080
|
+
}
|
|
1081
|
+
.pg-static-fallback summary {
|
|
1082
|
+
font-size: 12px;
|
|
1083
|
+
color: var(--color-muted);
|
|
1084
|
+
cursor: pointer;
|
|
1085
|
+
user-select: none;
|
|
1086
|
+
margin-bottom: 8px;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
@media (max-width: 768px) {
|
|
1090
|
+
.pg-container { flex-direction: column; }
|
|
1091
|
+
.pg-controls { width: 100%; max-height: none; }
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/* Full-width content body (no on-this-page nav) */
|
|
1095
|
+
.content-body-full { max-width: none; padding: 0; }
|
|
1096
|
+
|
|
1097
|
+
/* Two-column detail layout */
|
|
1098
|
+
.detail-columns {
|
|
1099
|
+
display: grid;
|
|
1100
|
+
grid-template-columns: 1fr 1fr;
|
|
1101
|
+
gap: 0;
|
|
1102
|
+
}
|
|
1103
|
+
.detail-main {
|
|
1104
|
+
min-width: 0;
|
|
1105
|
+
padding: 32px 32px 32px 40px;
|
|
1106
|
+
}
|
|
1107
|
+
.detail-inspector {
|
|
1108
|
+
position: sticky;
|
|
1109
|
+
top: 52px;
|
|
1110
|
+
align-self: start;
|
|
1111
|
+
height: calc(100vh - 52px);
|
|
1112
|
+
overflow-y: auto;
|
|
1113
|
+
border-left: 1px solid var(--color-border);
|
|
1114
|
+
padding: 16px 24px 32px;
|
|
1115
|
+
background: var(--color-bg-subtle);
|
|
1116
|
+
}
|
|
1117
|
+
.detail-inspector .section { padding: 20px 0; }
|
|
1118
|
+
.detail-inspector .section-header { margin-bottom: 12px; }
|
|
1119
|
+
.detail-inspector .section-header h2 { font-size: 15px; }
|
|
1120
|
+
.detail-inspector .section-header p { font-size: 12px; }
|
|
1121
|
+
.detail-inspector .stats-grid { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 8px; }
|
|
1122
|
+
.detail-inspector .stat-card { padding: 10px; }
|
|
1123
|
+
.detail-inspector .stat-card .stat-value { font-size: 18px; }
|
|
1124
|
+
.detail-inspector .analysis-grid { grid-template-columns: 1fr; gap: 8px; }
|
|
1125
|
+
.detail-inspector .composition-lists { grid-template-columns: 1fr; gap: 8px; }
|
|
1126
|
+
|
|
1127
|
+
@media (max-width: 1200px) {
|
|
1128
|
+
.detail-columns { grid-template-columns: 1fr; }
|
|
1129
|
+
.detail-main { padding: 20px 16px; }
|
|
1130
|
+
.detail-inspector {
|
|
1131
|
+
position: static;
|
|
1132
|
+
height: auto;
|
|
1133
|
+
border-left: none;
|
|
1134
|
+
border-top: 1px solid var(--color-border);
|
|
1135
|
+
padding: 20px 16px;
|
|
1136
|
+
background: none;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/* Icon browser */
|
|
1141
|
+
.icon-browser-header { margin-bottom: 24px; }
|
|
1142
|
+
.icon-browser-header h1 { font-size: 28px; font-weight: 700; margin: 0 0 4px; }
|
|
1143
|
+
.icon-browser-header p { color: var(--color-muted); font-size: 14px; margin: 0; }
|
|
1144
|
+
|
|
1145
|
+
.icon-grid {
|
|
1146
|
+
display: grid;
|
|
1147
|
+
grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
|
|
1148
|
+
gap: 2px;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.icon-cell {
|
|
1152
|
+
display: flex;
|
|
1153
|
+
flex-direction: column;
|
|
1154
|
+
align-items: center;
|
|
1155
|
+
justify-content: center;
|
|
1156
|
+
gap: 6px;
|
|
1157
|
+
padding: 12px 4px 8px;
|
|
1158
|
+
border: 1px solid transparent;
|
|
1159
|
+
border-radius: var(--radius);
|
|
1160
|
+
cursor: pointer;
|
|
1161
|
+
background: none;
|
|
1162
|
+
font-family: var(--font-body);
|
|
1163
|
+
transition: background 0.1s, border-color 0.1s;
|
|
1164
|
+
}
|
|
1165
|
+
.icon-cell:hover {
|
|
1166
|
+
background: var(--color-bg-subtle);
|
|
1167
|
+
border-color: var(--color-border);
|
|
1168
|
+
}
|
|
1169
|
+
.icon-cell.selected {
|
|
1170
|
+
background: var(--color-bg-selected);
|
|
1171
|
+
border-color: var(--color-accent);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
.icon-cell-preview {
|
|
1175
|
+
width: 40px;
|
|
1176
|
+
height: 40px;
|
|
1177
|
+
display: flex;
|
|
1178
|
+
align-items: center;
|
|
1179
|
+
justify-content: center;
|
|
1180
|
+
overflow: hidden;
|
|
1181
|
+
}
|
|
1182
|
+
.icon-cell-svg { display: flex; align-items: center; justify-content: center; }
|
|
1183
|
+
.icon-cell-svg svg { width: 28px; height: 28px; }
|
|
1184
|
+
.icon-no-preview {
|
|
1185
|
+
font-size: 16px;
|
|
1186
|
+
font-weight: 600;
|
|
1187
|
+
color: var(--color-muted);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
.icon-cell-name {
|
|
1191
|
+
font-size: 10px;
|
|
1192
|
+
color: var(--color-muted);
|
|
1193
|
+
text-align: center;
|
|
1194
|
+
line-height: 1.2;
|
|
1195
|
+
max-width: 100%;
|
|
1196
|
+
overflow: hidden;
|
|
1197
|
+
text-overflow: ellipsis;
|
|
1198
|
+
white-space: nowrap;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/* Icon slide-out panel */
|
|
1202
|
+
.icon-slideout-overlay {
|
|
1203
|
+
position: fixed;
|
|
1204
|
+
inset: 0;
|
|
1205
|
+
background: var(--overlay-bg);
|
|
1206
|
+
z-index: 200;
|
|
1207
|
+
}
|
|
1208
|
+
.icon-slideout {
|
|
1209
|
+
position: fixed;
|
|
1210
|
+
top: 0;
|
|
1211
|
+
right: 0;
|
|
1212
|
+
width: 380px;
|
|
1213
|
+
max-width: 100vw;
|
|
1214
|
+
height: 100vh;
|
|
1215
|
+
background: var(--color-bg);
|
|
1216
|
+
border-left: 1px solid var(--color-border);
|
|
1217
|
+
box-shadow: var(--slideout-shadow);
|
|
1218
|
+
z-index: 201;
|
|
1219
|
+
display: flex;
|
|
1220
|
+
flex-direction: column;
|
|
1221
|
+
overflow-y: auto;
|
|
1222
|
+
animation: icon-slide-in 0.2s ease-out;
|
|
1223
|
+
}
|
|
1224
|
+
.icon-slideout[hidden] { display: none; }
|
|
1225
|
+
.icon-slideout-overlay[hidden] { display: none; }
|
|
1226
|
+
@keyframes icon-slide-in {
|
|
1227
|
+
from { transform: translateX(100%); }
|
|
1228
|
+
to { transform: translateX(0); }
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
.icon-slideout-header {
|
|
1232
|
+
display: flex;
|
|
1233
|
+
align-items: center;
|
|
1234
|
+
justify-content: space-between;
|
|
1235
|
+
padding: 20px 24px 16px;
|
|
1236
|
+
border-bottom: 1px solid var(--color-border);
|
|
1237
|
+
flex-shrink: 0;
|
|
1238
|
+
}
|
|
1239
|
+
.icon-slideout-name {
|
|
1240
|
+
font-size: 18px;
|
|
1241
|
+
font-weight: 600;
|
|
1242
|
+
margin: 0;
|
|
1243
|
+
word-break: break-word;
|
|
1244
|
+
}
|
|
1245
|
+
.icon-slideout-close {
|
|
1246
|
+
background: none;
|
|
1247
|
+
border: none;
|
|
1248
|
+
color: var(--color-muted);
|
|
1249
|
+
cursor: pointer;
|
|
1250
|
+
padding: 4px;
|
|
1251
|
+
border-radius: var(--radius);
|
|
1252
|
+
display: flex;
|
|
1253
|
+
align-items: center;
|
|
1254
|
+
justify-content: center;
|
|
1255
|
+
transition: background 0.1s, color 0.1s;
|
|
1256
|
+
}
|
|
1257
|
+
.icon-slideout-close:hover {
|
|
1258
|
+
background: var(--color-bg-subtle);
|
|
1259
|
+
color: var(--color-text);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
.icon-slideout-body {
|
|
1263
|
+
flex: 1;
|
|
1264
|
+
padding: 0 24px 24px;
|
|
1265
|
+
overflow-y: auto;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.icon-slideout-preview-lg {
|
|
1269
|
+
display: flex;
|
|
1270
|
+
align-items: center;
|
|
1271
|
+
justify-content: center;
|
|
1272
|
+
padding: 40px 32px;
|
|
1273
|
+
background: var(--color-bg-subtle);
|
|
1274
|
+
border: 1px solid var(--color-border);
|
|
1275
|
+
border-radius: var(--radius);
|
|
1276
|
+
margin: 20px 0;
|
|
1277
|
+
}
|
|
1278
|
+
.icon-slideout-preview-lg svg {
|
|
1279
|
+
width: 100%;
|
|
1280
|
+
max-width: 120px;
|
|
1281
|
+
height: auto;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
.icon-detail-section {
|
|
1285
|
+
padding: 16px 0;
|
|
1286
|
+
border-bottom: 1px solid var(--color-border);
|
|
1287
|
+
}
|
|
1288
|
+
.icon-detail-section:last-child { border-bottom: none; }
|
|
1289
|
+
.icon-detail-section-title {
|
|
1290
|
+
font-size: 11px;
|
|
1291
|
+
font-weight: 600;
|
|
1292
|
+
letter-spacing: 0.06em;
|
|
1293
|
+
text-transform: uppercase;
|
|
1294
|
+
color: var(--color-muted);
|
|
1295
|
+
margin-bottom: 10px;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/* Size previews */
|
|
1299
|
+
.icon-size-row {
|
|
1300
|
+
display: flex;
|
|
1301
|
+
gap: 16px;
|
|
1302
|
+
align-items: flex-end;
|
|
1303
|
+
}
|
|
1304
|
+
.icon-size-cell {
|
|
1305
|
+
display: flex;
|
|
1306
|
+
flex-direction: column;
|
|
1307
|
+
align-items: center;
|
|
1308
|
+
gap: 6px;
|
|
1309
|
+
}
|
|
1310
|
+
.icon-size-cell span {
|
|
1311
|
+
font-size: 10px;
|
|
1312
|
+
color: var(--color-muted);
|
|
1313
|
+
font-weight: 500;
|
|
1314
|
+
}
|
|
1315
|
+
.icon-size-preview {
|
|
1316
|
+
display: flex;
|
|
1317
|
+
align-items: center;
|
|
1318
|
+
justify-content: center;
|
|
1319
|
+
padding: 8px;
|
|
1320
|
+
background: var(--color-bg-subtle);
|
|
1321
|
+
border: 1px solid var(--color-border);
|
|
1322
|
+
border-radius: var(--radius);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/* Action buttons */
|
|
1326
|
+
.icon-slideout-actions {
|
|
1327
|
+
display: flex;
|
|
1328
|
+
flex-wrap: wrap;
|
|
1329
|
+
gap: 8px;
|
|
1330
|
+
}
|
|
1331
|
+
.icon-action-btn {
|
|
1332
|
+
display: inline-flex;
|
|
1333
|
+
align-items: center;
|
|
1334
|
+
justify-content: center;
|
|
1335
|
+
padding: 7px 14px;
|
|
1336
|
+
border-radius: var(--radius);
|
|
1337
|
+
border: 1px solid var(--color-border);
|
|
1338
|
+
background: var(--color-bg);
|
|
1339
|
+
color: var(--color-text);
|
|
1340
|
+
text-decoration: none;
|
|
1341
|
+
font-family: var(--font-body);
|
|
1342
|
+
font-size: 12px;
|
|
1343
|
+
font-weight: 500;
|
|
1344
|
+
cursor: pointer;
|
|
1345
|
+
transition: background 0.1s, border-color 0.1s;
|
|
1346
|
+
}
|
|
1347
|
+
.icon-action-btn:hover {
|
|
1348
|
+
background: var(--color-bg-subtle);
|
|
1349
|
+
border-color: var(--color-muted);
|
|
1350
|
+
}
|
|
1351
|
+
.icon-action-primary {
|
|
1352
|
+
background: var(--color-accent);
|
|
1353
|
+
border-color: var(--color-accent);
|
|
1354
|
+
color: var(--color-on-accent);
|
|
1355
|
+
}
|
|
1356
|
+
.icon-action-primary:hover {
|
|
1357
|
+
opacity: 0.9;
|
|
1358
|
+
background: var(--color-accent);
|
|
1359
|
+
border-color: var(--color-accent);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
/* Compiled size */
|
|
1363
|
+
.icon-compiled-size {
|
|
1364
|
+
font-family: var(--font-mono);
|
|
1365
|
+
font-size: 14px;
|
|
1366
|
+
font-weight: 600;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
/* Props */
|
|
1370
|
+
.icon-prop-row {
|
|
1371
|
+
display: flex;
|
|
1372
|
+
flex-wrap: wrap;
|
|
1373
|
+
gap: 4px 12px;
|
|
1374
|
+
align-items: baseline;
|
|
1375
|
+
padding: 6px 0;
|
|
1376
|
+
border-bottom: 1px solid var(--color-border);
|
|
1377
|
+
}
|
|
1378
|
+
.icon-prop-row:last-child { border-bottom: none; }
|
|
1379
|
+
.icon-prop-name {
|
|
1380
|
+
font-family: var(--font-mono);
|
|
1381
|
+
font-size: 12px;
|
|
1382
|
+
font-weight: 600;
|
|
1383
|
+
}
|
|
1384
|
+
.icon-prop-type {
|
|
1385
|
+
font-family: var(--font-mono);
|
|
1386
|
+
font-size: 11px;
|
|
1387
|
+
color: var(--color-accent);
|
|
1388
|
+
}
|
|
1389
|
+
.icon-prop-meta {
|
|
1390
|
+
font-size: 11px;
|
|
1391
|
+
color: var(--color-muted);
|
|
1392
|
+
}
|
|
1393
|
+
.icon-prop-required { color: var(--color-error); }
|
|
1394
|
+
.icon-prop-optional { color: var(--color-muted); }
|
|
1395
|
+
.icon-prop-default {
|
|
1396
|
+
font-family: var(--font-mono);
|
|
1397
|
+
font-size: 10px;
|
|
1398
|
+
color: var(--color-muted);
|
|
1399
|
+
margin-left: 4px;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/* Keywords */
|
|
1403
|
+
.icon-keywords {
|
|
1404
|
+
display: flex;
|
|
1405
|
+
flex-wrap: wrap;
|
|
1406
|
+
gap: 4px;
|
|
1407
|
+
}
|
|
1408
|
+
.icon-keyword-tag {
|
|
1409
|
+
display: inline-block;
|
|
1410
|
+
padding: 2px 8px;
|
|
1411
|
+
background: var(--color-bg-subtle);
|
|
1412
|
+
border: 1px solid var(--color-border);
|
|
1413
|
+
border-radius: 999px;
|
|
1414
|
+
font-size: 11px;
|
|
1415
|
+
color: var(--color-muted);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/* Usage list */
|
|
1419
|
+
.icon-usage-list {
|
|
1420
|
+
display: flex;
|
|
1421
|
+
flex-direction: column;
|
|
1422
|
+
gap: 4px;
|
|
1423
|
+
}
|
|
1424
|
+
.icon-usage-link {
|
|
1425
|
+
display: block;
|
|
1426
|
+
padding: 4px 0;
|
|
1427
|
+
font-size: 13px;
|
|
1428
|
+
color: var(--color-accent);
|
|
1429
|
+
text-decoration: none;
|
|
1430
|
+
}
|
|
1431
|
+
.icon-usage-link:hover { text-decoration: underline; }
|
|
1432
|
+
|
|
1433
|
+
@media (max-width: 768px) {
|
|
1434
|
+
.icon-slideout { width: 100vw; }
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/* CMD-K trigger button */
|
|
1438
|
+
.cmdk-trigger {
|
|
1439
|
+
display: inline-flex;
|
|
1440
|
+
align-items: center;
|
|
1441
|
+
gap: 8px;
|
|
1442
|
+
border: 1px solid var(--color-border);
|
|
1443
|
+
border-radius: var(--radius);
|
|
1444
|
+
padding: 6px 10px 6px 12px;
|
|
1445
|
+
font-family: var(--font-body);
|
|
1446
|
+
font-size: 13px;
|
|
1447
|
+
color: var(--color-muted);
|
|
1448
|
+
background: var(--color-bg-subtle);
|
|
1449
|
+
cursor: pointer;
|
|
1450
|
+
transition: border-color 0.15s, color 0.15s;
|
|
1451
|
+
height: 34px;
|
|
1452
|
+
min-width: 200px;
|
|
1453
|
+
}
|
|
1454
|
+
.cmdk-trigger:hover {
|
|
1455
|
+
border-color: var(--color-muted);
|
|
1456
|
+
color: var(--color-text);
|
|
1457
|
+
}
|
|
1458
|
+
.cmdk-trigger svg { flex-shrink: 0; opacity: 0.5; }
|
|
1459
|
+
.cmdk-trigger span { flex: 1; text-align: left; }
|
|
1460
|
+
.cmdk-trigger kbd {
|
|
1461
|
+
font-family: var(--font-body);
|
|
1462
|
+
font-size: 10px;
|
|
1463
|
+
font-weight: 500;
|
|
1464
|
+
padding: 2px 5px;
|
|
1465
|
+
border: 1px solid var(--color-border);
|
|
1466
|
+
border-radius: 4px;
|
|
1467
|
+
background: var(--color-bg);
|
|
1468
|
+
color: var(--color-muted);
|
|
1469
|
+
line-height: 1;
|
|
1470
|
+
flex-shrink: 0;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
/* CMD-K overlay */
|
|
1474
|
+
.cmdk-overlay {
|
|
1475
|
+
position: fixed;
|
|
1476
|
+
inset: 0;
|
|
1477
|
+
background: var(--overlay-bg);
|
|
1478
|
+
z-index: 300;
|
|
1479
|
+
animation: cmdk-fade-in 0.1s ease-out;
|
|
1480
|
+
}
|
|
1481
|
+
.cmdk-overlay[hidden] { display: none; }
|
|
1482
|
+
|
|
1483
|
+
@keyframes cmdk-fade-in {
|
|
1484
|
+
from { opacity: 0; }
|
|
1485
|
+
to { opacity: 1; }
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
/* CMD-K dialog */
|
|
1489
|
+
.cmdk-dialog {
|
|
1490
|
+
position: fixed;
|
|
1491
|
+
top: min(20vh, 140px);
|
|
1492
|
+
left: 50%;
|
|
1493
|
+
transform: translateX(-50%);
|
|
1494
|
+
width: 520px;
|
|
1495
|
+
max-width: calc(100vw - 32px);
|
|
1496
|
+
background: var(--color-bg);
|
|
1497
|
+
border: 1px solid var(--color-border);
|
|
1498
|
+
border-radius: 12px;
|
|
1499
|
+
box-shadow: 0 16px 70px rgba(0,0,0,0.2);
|
|
1500
|
+
z-index: 301;
|
|
1501
|
+
display: flex;
|
|
1502
|
+
flex-direction: column;
|
|
1503
|
+
overflow: hidden;
|
|
1504
|
+
animation: cmdk-dialog-in 0.15s ease-out;
|
|
1505
|
+
}
|
|
1506
|
+
.cmdk-dialog[hidden] { display: none; }
|
|
1507
|
+
|
|
1508
|
+
@keyframes cmdk-dialog-in {
|
|
1509
|
+
from { opacity: 0; transform: translateX(-50%) scale(0.96); }
|
|
1510
|
+
to { opacity: 1; transform: translateX(-50%) scale(1); }
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/* CMD-K input */
|
|
1514
|
+
.cmdk-input-wrapper {
|
|
1515
|
+
display: flex;
|
|
1516
|
+
align-items: center;
|
|
1517
|
+
gap: 10px;
|
|
1518
|
+
padding: 14px 16px;
|
|
1519
|
+
border-bottom: 1px solid var(--color-border);
|
|
1520
|
+
}
|
|
1521
|
+
.cmdk-input-wrapper svg { flex-shrink: 0; color: var(--color-muted); }
|
|
1522
|
+
.cmdk-input {
|
|
1523
|
+
flex: 1;
|
|
1524
|
+
border: none;
|
|
1525
|
+
outline: none;
|
|
1526
|
+
background: none;
|
|
1527
|
+
font-family: var(--font-body);
|
|
1528
|
+
font-size: 15px;
|
|
1529
|
+
color: var(--color-text);
|
|
1530
|
+
padding: 0;
|
|
1531
|
+
}
|
|
1532
|
+
.cmdk-input::placeholder { color: var(--color-muted); }
|
|
1533
|
+
|
|
1534
|
+
/* CMD-K results list */
|
|
1535
|
+
.cmdk-list {
|
|
1536
|
+
max-height: 340px;
|
|
1537
|
+
overflow-y: auto;
|
|
1538
|
+
padding: 6px;
|
|
1539
|
+
}
|
|
1540
|
+
.cmdk-list:empty { display: none; }
|
|
1541
|
+
|
|
1542
|
+
.cmdk-item {
|
|
1543
|
+
display: flex;
|
|
1544
|
+
align-items: center;
|
|
1545
|
+
gap: 10px;
|
|
1546
|
+
padding: 8px 10px;
|
|
1547
|
+
border-radius: var(--radius);
|
|
1548
|
+
text-decoration: none;
|
|
1549
|
+
color: var(--color-text);
|
|
1550
|
+
cursor: pointer;
|
|
1551
|
+
transition: background 0.06s;
|
|
1552
|
+
}
|
|
1553
|
+
.cmdk-item-active {
|
|
1554
|
+
background: var(--color-bg-selected);
|
|
1555
|
+
}
|
|
1556
|
+
.cmdk-item-icon {
|
|
1557
|
+
display: flex;
|
|
1558
|
+
align-items: center;
|
|
1559
|
+
justify-content: center;
|
|
1560
|
+
width: 28px;
|
|
1561
|
+
height: 28px;
|
|
1562
|
+
border-radius: var(--radius);
|
|
1563
|
+
background: var(--color-bg-subtle);
|
|
1564
|
+
border: 1px solid var(--color-border);
|
|
1565
|
+
color: var(--color-muted);
|
|
1566
|
+
flex-shrink: 0;
|
|
1567
|
+
}
|
|
1568
|
+
.cmdk-item-name {
|
|
1569
|
+
flex: 1;
|
|
1570
|
+
font-size: 13px;
|
|
1571
|
+
font-weight: 500;
|
|
1572
|
+
min-width: 0;
|
|
1573
|
+
overflow: hidden;
|
|
1574
|
+
text-overflow: ellipsis;
|
|
1575
|
+
white-space: nowrap;
|
|
1576
|
+
}
|
|
1577
|
+
.cmdk-item-collection {
|
|
1578
|
+
font-size: 11px;
|
|
1579
|
+
color: var(--color-muted);
|
|
1580
|
+
flex-shrink: 0;
|
|
1581
|
+
}
|
|
1582
|
+
.cmdk-item-type {
|
|
1583
|
+
font-size: 10px;
|
|
1584
|
+
font-weight: 500;
|
|
1585
|
+
text-transform: uppercase;
|
|
1586
|
+
letter-spacing: 0.04em;
|
|
1587
|
+
color: var(--color-muted);
|
|
1588
|
+
opacity: 0.7;
|
|
1589
|
+
flex-shrink: 0;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
/* CMD-K empty state */
|
|
1593
|
+
.cmdk-empty {
|
|
1594
|
+
padding: 32px 16px;
|
|
1595
|
+
text-align: center;
|
|
1596
|
+
color: var(--color-muted);
|
|
1597
|
+
font-size: 13px;
|
|
1598
|
+
}
|
|
1599
|
+
.cmdk-empty[hidden] { display: none; }
|
|
1600
|
+
|
|
1601
|
+
/* CMD-K footer */
|
|
1602
|
+
.cmdk-footer {
|
|
1603
|
+
display: flex;
|
|
1604
|
+
align-items: center;
|
|
1605
|
+
gap: 16px;
|
|
1606
|
+
padding: 8px 16px;
|
|
1607
|
+
border-top: 1px solid var(--color-border);
|
|
1608
|
+
font-size: 11px;
|
|
1609
|
+
color: var(--color-muted);
|
|
1610
|
+
}
|
|
1611
|
+
.cmdk-footer kbd {
|
|
1612
|
+
font-family: var(--font-body);
|
|
1613
|
+
font-size: 10px;
|
|
1614
|
+
font-weight: 500;
|
|
1615
|
+
padding: 1px 4px;
|
|
1616
|
+
border: 1px solid var(--color-border);
|
|
1617
|
+
border-radius: 3px;
|
|
1618
|
+
background: var(--color-bg-subtle);
|
|
1619
|
+
margin-right: 2px;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
@media (max-width: 768px) {
|
|
1623
|
+
.cmdk-trigger { min-width: 0; }
|
|
1624
|
+
.cmdk-trigger span { display: none; }
|
|
1625
|
+
.cmdk-dialog { top: 16px; }
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/* Token browser */
|
|
1629
|
+
.tb-header { margin-bottom: 24px; }
|
|
1630
|
+
.tb-header h1 { font-size: 28px; font-weight: 700; margin: 0 0 6px; }
|
|
1631
|
+
.tb-header p { color: var(--color-muted); font-size: 14px; margin: 0; }
|
|
1632
|
+
.tb-updated { font-size: 12px; color: var(--color-muted); }
|
|
1633
|
+
|
|
1634
|
+
.tb-section {
|
|
1635
|
+
padding: 28px 0;
|
|
1636
|
+
border-bottom: 1px solid var(--color-border);
|
|
1637
|
+
}
|
|
1638
|
+
.tb-section:last-child { border-bottom: none; }
|
|
1639
|
+
.tb-section-title {
|
|
1640
|
+
font-size: 18px;
|
|
1641
|
+
font-weight: 600;
|
|
1642
|
+
margin: 0 0 16px;
|
|
1643
|
+
text-transform: capitalize;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/* Color scales */
|
|
1647
|
+
.tb-scale { margin-bottom: 20px; }
|
|
1648
|
+
.tb-scale-name {
|
|
1649
|
+
font-size: 12px;
|
|
1650
|
+
font-weight: 600;
|
|
1651
|
+
text-transform: capitalize;
|
|
1652
|
+
color: var(--color-muted);
|
|
1653
|
+
margin-bottom: 6px;
|
|
1654
|
+
letter-spacing: 0.03em;
|
|
1655
|
+
}
|
|
1656
|
+
.tb-scale-row {
|
|
1657
|
+
display: flex;
|
|
1658
|
+
gap: 0;
|
|
1659
|
+
border-radius: var(--radius);
|
|
1660
|
+
overflow: hidden;
|
|
1661
|
+
border: 1px solid var(--color-border);
|
|
1662
|
+
}
|
|
1663
|
+
.tb-swatch {
|
|
1664
|
+
flex: 1;
|
|
1665
|
+
min-width: 0;
|
|
1666
|
+
padding: 24px 6px 8px;
|
|
1667
|
+
display: flex;
|
|
1668
|
+
flex-direction: column;
|
|
1669
|
+
align-items: center;
|
|
1670
|
+
gap: 2px;
|
|
1671
|
+
cursor: default;
|
|
1672
|
+
transition: transform 0.1s, box-shadow 0.1s;
|
|
1673
|
+
position: relative;
|
|
1674
|
+
}
|
|
1675
|
+
.tb-swatch:hover {
|
|
1676
|
+
transform: scaleY(1.08);
|
|
1677
|
+
z-index: 1;
|
|
1678
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
|
1679
|
+
}
|
|
1680
|
+
.tb-swatch-label {
|
|
1681
|
+
font-size: 10px;
|
|
1682
|
+
font-weight: 600;
|
|
1683
|
+
opacity: 0.85;
|
|
1684
|
+
}
|
|
1685
|
+
.tb-swatch-value {
|
|
1686
|
+
font-size: 9px;
|
|
1687
|
+
font-family: var(--font-mono);
|
|
1688
|
+
opacity: 0.7;
|
|
1689
|
+
white-space: nowrap;
|
|
1690
|
+
overflow: hidden;
|
|
1691
|
+
text-overflow: ellipsis;
|
|
1692
|
+
max-width: 100%;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/* Swatch pair (light + dark) */
|
|
1696
|
+
.tb-swatch-pair {
|
|
1697
|
+
flex: 1;
|
|
1698
|
+
min-width: 0;
|
|
1699
|
+
display: flex;
|
|
1700
|
+
flex-direction: column;
|
|
1701
|
+
}
|
|
1702
|
+
.tb-swatch-pair .tb-swatch {
|
|
1703
|
+
border-radius: 0;
|
|
1704
|
+
border: none;
|
|
1705
|
+
}
|
|
1706
|
+
.tb-swatch-dark {
|
|
1707
|
+
padding: 4px 6px;
|
|
1708
|
+
display: flex;
|
|
1709
|
+
align-items: center;
|
|
1710
|
+
justify-content: center;
|
|
1711
|
+
min-height: 24px;
|
|
1712
|
+
}
|
|
1713
|
+
.tb-swatch-dark .tb-swatch-value {
|
|
1714
|
+
font-size: 8px;
|
|
1715
|
+
font-family: var(--font-mono);
|
|
1716
|
+
opacity: 0.8;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
.tb-standalone-colors {
|
|
1720
|
+
display: flex;
|
|
1721
|
+
flex-wrap: wrap;
|
|
1722
|
+
gap: 8px;
|
|
1723
|
+
margin-top: 12px;
|
|
1724
|
+
}
|
|
1725
|
+
.tb-swatch-pair-single {
|
|
1726
|
+
display: flex;
|
|
1727
|
+
flex-direction: column;
|
|
1728
|
+
width: 80px;
|
|
1729
|
+
border-radius: var(--radius);
|
|
1730
|
+
overflow: hidden;
|
|
1731
|
+
border: 1px solid var(--color-border);
|
|
1732
|
+
}
|
|
1733
|
+
.tb-swatch-pair-single .tb-swatch-single {
|
|
1734
|
+
border: none;
|
|
1735
|
+
border-radius: 0;
|
|
1736
|
+
width: auto;
|
|
1737
|
+
min-width: auto;
|
|
1738
|
+
flex: none;
|
|
1739
|
+
}
|
|
1740
|
+
.tb-swatch-dark-single {
|
|
1741
|
+
padding: 4px 6px;
|
|
1742
|
+
display: flex;
|
|
1743
|
+
align-items: center;
|
|
1744
|
+
justify-content: center;
|
|
1745
|
+
min-height: 22px;
|
|
1746
|
+
}
|
|
1747
|
+
.tb-swatch-dark-single .tb-swatch-value {
|
|
1748
|
+
font-size: 8px;
|
|
1749
|
+
font-family: var(--font-mono);
|
|
1750
|
+
opacity: 0.8;
|
|
1751
|
+
}
|
|
1752
|
+
.tb-swatch-single {
|
|
1753
|
+
width: 80px;
|
|
1754
|
+
min-width: 80px;
|
|
1755
|
+
flex: 0 0 80px;
|
|
1756
|
+
height: 72px;
|
|
1757
|
+
border-radius: var(--radius);
|
|
1758
|
+
border: 1px solid var(--color-border);
|
|
1759
|
+
justify-content: flex-end;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
/* Scale badge */
|
|
1763
|
+
.tb-scale-badge {
|
|
1764
|
+
display: inline-block;
|
|
1765
|
+
font-size: 9px;
|
|
1766
|
+
font-weight: 500;
|
|
1767
|
+
text-transform: uppercase;
|
|
1768
|
+
letter-spacing: 0.06em;
|
|
1769
|
+
padding: 1px 5px;
|
|
1770
|
+
border-radius: 3px;
|
|
1771
|
+
background: var(--color-bg-code);
|
|
1772
|
+
color: var(--color-code-text);
|
|
1773
|
+
margin-left: 6px;
|
|
1774
|
+
vertical-align: middle;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
/* Spacing */
|
|
1778
|
+
.tb-spacing-row {
|
|
1779
|
+
display: grid;
|
|
1780
|
+
grid-template-columns: 140px 80px 1fr;
|
|
1781
|
+
align-items: center;
|
|
1782
|
+
gap: 12px;
|
|
1783
|
+
padding: 8px 0;
|
|
1784
|
+
border-bottom: 1px solid var(--color-border);
|
|
1785
|
+
}
|
|
1786
|
+
.tb-spacing-row:last-child { border-bottom: none; }
|
|
1787
|
+
.tb-spacing-label {
|
|
1788
|
+
font-family: var(--font-mono);
|
|
1789
|
+
font-size: 12px;
|
|
1790
|
+
font-weight: 500;
|
|
1791
|
+
color: var(--color-text);
|
|
1792
|
+
}
|
|
1793
|
+
.tb-spacing-value {
|
|
1794
|
+
font-family: var(--font-mono);
|
|
1795
|
+
font-size: 12px;
|
|
1796
|
+
color: var(--color-muted);
|
|
1797
|
+
}
|
|
1798
|
+
.tb-spacing-bar-wrapper {
|
|
1799
|
+
height: 20px;
|
|
1800
|
+
background: var(--color-bg-subtle);
|
|
1801
|
+
border-radius: 3px;
|
|
1802
|
+
overflow: hidden;
|
|
1803
|
+
}
|
|
1804
|
+
.tb-spacing-bar {
|
|
1805
|
+
height: 100%;
|
|
1806
|
+
background: var(--color-accent);
|
|
1807
|
+
border-radius: 3px;
|
|
1808
|
+
opacity: 0.5;
|
|
1809
|
+
min-width: 2px;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
/* Typography */
|
|
1813
|
+
.tb-typo-row {
|
|
1814
|
+
display: grid;
|
|
1815
|
+
grid-template-columns: 160px 1fr auto;
|
|
1816
|
+
align-items: baseline;
|
|
1817
|
+
gap: 16px;
|
|
1818
|
+
padding: 10px 0;
|
|
1819
|
+
border-bottom: 1px solid var(--color-border);
|
|
1820
|
+
}
|
|
1821
|
+
.tb-typo-row:last-child { border-bottom: none; }
|
|
1822
|
+
.tb-typo-label {
|
|
1823
|
+
font-family: var(--font-mono);
|
|
1824
|
+
font-size: 12px;
|
|
1825
|
+
font-weight: 500;
|
|
1826
|
+
}
|
|
1827
|
+
.tb-typo-value {
|
|
1828
|
+
font-family: var(--font-mono);
|
|
1829
|
+
font-size: 12px;
|
|
1830
|
+
color: var(--color-muted);
|
|
1831
|
+
}
|
|
1832
|
+
.tb-typo-sample {
|
|
1833
|
+
font-size: 18px;
|
|
1834
|
+
color: var(--color-text);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
/* Generic table */
|
|
1838
|
+
.tb-generic-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
1839
|
+
.tb-generic-table th {
|
|
1840
|
+
text-align: left;
|
|
1841
|
+
font-weight: 600;
|
|
1842
|
+
font-size: 11px;
|
|
1843
|
+
text-transform: uppercase;
|
|
1844
|
+
letter-spacing: 0.05em;
|
|
1845
|
+
color: var(--color-muted);
|
|
1846
|
+
padding: 8px 12px;
|
|
1847
|
+
border-bottom: 2px solid var(--color-border);
|
|
1848
|
+
}
|
|
1849
|
+
.tb-generic-table td {
|
|
1850
|
+
padding: 8px 12px;
|
|
1851
|
+
border-bottom: 1px solid var(--color-border);
|
|
1852
|
+
vertical-align: middle;
|
|
1853
|
+
}
|
|
1854
|
+
.tb-generic-table tr:last-child td { border-bottom: none; }
|
|
1855
|
+
.tb-generic-path {
|
|
1856
|
+
font-family: var(--font-mono);
|
|
1857
|
+
font-size: 12px;
|
|
1858
|
+
color: var(--color-accent);
|
|
1859
|
+
}
|
|
1860
|
+
.tb-generic-value {
|
|
1861
|
+
font-family: var(--font-mono);
|
|
1862
|
+
font-size: 12px;
|
|
1863
|
+
}
|
|
1864
|
+
.tb-generic-type {
|
|
1865
|
+
font-size: 11px;
|
|
1866
|
+
color: var(--color-muted);
|
|
1867
|
+
text-transform: capitalize;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
@media (max-width: 768px) {
|
|
1871
|
+
.tb-scale-row { flex-wrap: wrap; }
|
|
1872
|
+
.tb-swatch { min-width: 48px; }
|
|
1873
|
+
.tb-spacing-row { grid-template-columns: 1fr 1fr; }
|
|
1874
|
+
.tb-spacing-bar-wrapper { display: none; }
|
|
1875
|
+
.tb-typo-row { grid-template-columns: 1fr 1fr; }
|
|
1876
|
+
}`;
|
|
1877
|
+
return `<style>
|
|
1878
|
+
${css}
|
|
1879
|
+
</style>`;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
// src/templates/layout.ts
|
|
1883
|
+
function buildSearchItems(data) {
|
|
1884
|
+
const items = [];
|
|
1885
|
+
const basePath = data.options.basePath;
|
|
1886
|
+
const iconPatterns = data.options.iconPatterns;
|
|
1887
|
+
for (const [name, component] of Object.entries(data.manifest.components)) {
|
|
1888
|
+
const isIcon = iconPatterns.length > 0 && iconPatterns.some(
|
|
1889
|
+
(p) => matchGlob(p, component.filePath) || matchGlob(p, component.displayName)
|
|
1890
|
+
);
|
|
1891
|
+
if (component.internal && !isIcon) continue;
|
|
1892
|
+
items.push({
|
|
1893
|
+
name,
|
|
1894
|
+
type: isIcon ? "icon" : "component",
|
|
1895
|
+
url: `${basePath}${slugify(name)}.html`,
|
|
1896
|
+
collection: component.collection,
|
|
1897
|
+
keywords: (component.keywords ?? []).join(" ")
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
return items.sort((a, b) => a.name.localeCompare(b.name));
|
|
1901
|
+
}
|
|
1902
|
+
function sidebarLinks(data, currentSlug) {
|
|
1903
|
+
const { components } = data.manifest;
|
|
1904
|
+
const collections = data.manifest.collections ?? [];
|
|
1905
|
+
const basePath = data.options.basePath;
|
|
1906
|
+
const hasIcons = data.options.iconPatterns.length > 0;
|
|
1907
|
+
const visibleEntries = Object.entries(components).filter(([, c]) => !c.internal);
|
|
1908
|
+
const collectionMap = /* @__PURE__ */ new Map();
|
|
1909
|
+
for (const col of collections) {
|
|
1910
|
+
collectionMap.set(col.name, []);
|
|
1911
|
+
}
|
|
1912
|
+
const ungrouped = [];
|
|
1913
|
+
for (const [name, component] of visibleEntries) {
|
|
1914
|
+
if (component.collection) {
|
|
1915
|
+
if (!collectionMap.has(component.collection)) {
|
|
1916
|
+
collectionMap.set(component.collection, []);
|
|
1917
|
+
}
|
|
1918
|
+
collectionMap.get(component.collection)?.push(name);
|
|
1919
|
+
} else {
|
|
1920
|
+
ungrouped.push(name);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
const hasAnyCollections = collectionMap.size > 0 || ungrouped.length < visibleEntries.length;
|
|
1924
|
+
function renderLinks(names) {
|
|
1925
|
+
return names.sort().map((name) => {
|
|
1926
|
+
const slug = slugify(name);
|
|
1927
|
+
const isActive = slug === currentSlug;
|
|
1928
|
+
return `<a href="${basePath}${slug}.html" class="${isActive ? "active" : ""}">${escapeHtml(name)}</a>`;
|
|
1929
|
+
}).join("\n");
|
|
1930
|
+
}
|
|
1931
|
+
const hasTokens = (data.tokenEntries?.length ?? 0) > 0;
|
|
1932
|
+
const overviewLink = `<a href="${basePath}index.html" class="${currentSlug === null ? "active" : ""}">Overview</a>`;
|
|
1933
|
+
const tokensLink = hasTokens ? `<a href="${basePath}tokens.html" class="${currentSlug === "tokens" ? "active" : ""}">Tokens</a>` : "";
|
|
1934
|
+
const iconsLink = hasIcons ? `<a href="${basePath}icons.html" class="${currentSlug === "icons" ? "active" : ""}">Icons</a>` : "";
|
|
1935
|
+
if (!hasAnyCollections) {
|
|
1936
|
+
const links = renderLinks(visibleEntries.map(([name]) => name));
|
|
1937
|
+
return `${overviewLink}
|
|
1938
|
+
${tokensLink}
|
|
1939
|
+
${iconsLink}
|
|
1940
|
+
<div class="sidebar-heading">Components</div>
|
|
1941
|
+
${links}`;
|
|
1942
|
+
}
|
|
1943
|
+
const sections = [overviewLink];
|
|
1944
|
+
if (tokensLink) sections.push(tokensLink);
|
|
1945
|
+
if (iconsLink) sections.push(iconsLink);
|
|
1946
|
+
for (const [colName, names] of collectionMap) {
|
|
1947
|
+
if (names.length === 0) continue;
|
|
1948
|
+
sections.push(
|
|
1949
|
+
`<div class="sidebar-collection-header">${escapeHtml(colName)}</div>
|
|
1950
|
+
${renderLinks(names)}`
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
if (ungrouped.length > 0) {
|
|
1954
|
+
sections.push(
|
|
1955
|
+
`<div class="sidebar-collection-header">Ungrouped</div>
|
|
1956
|
+
${renderLinks(ungrouped)}`
|
|
1957
|
+
);
|
|
1958
|
+
}
|
|
1959
|
+
return sections.join("\n");
|
|
1960
|
+
}
|
|
1961
|
+
function htmlShell(options) {
|
|
1962
|
+
const {
|
|
1963
|
+
title,
|
|
1964
|
+
body,
|
|
1965
|
+
sidebar,
|
|
1966
|
+
onThisPage,
|
|
1967
|
+
basePath,
|
|
1968
|
+
searchItems = [],
|
|
1969
|
+
breadcrumbs = []
|
|
1970
|
+
} = options;
|
|
1971
|
+
const searchItemsJson = JSON.stringify(searchItems).replace(/<\/script>/gi, "<\\/script>");
|
|
1972
|
+
const logoSvg = `<svg class="site-logo" width="22" height="22" viewBox="0 0 1200 1200" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="m627.03 392.79c93.695 14.414 169.37 86.488 180.18 183.79h90.09c18.02 0 18.02 50.449 0 50.449h-90.09c-10.812 93.695-86.488 169.37-180.18 180.18v90.09c0 18.02-50.449 18.02-50.449 0v-90.09c-97.297-10.812-169.37-86.488-183.79-180.18h-86.488c-18.02 0-18.02-50.449 0-50.449h86.488c14.414-97.297 86.488-169.37 183.79-183.79v-86.488c0-18.02 50.449-21.621 50.449 0zm486.49-313.51h-201.8c-18.02 0-18.02-50.449 0-50.449h237.84c7.207 0 14.414 7.207 14.414 14.414v237.84c0 18.02-50.449 18.02-50.449 0zm7.207 1034.2v-201.8c0-18.02 50.449-18.02 50.449 0v237.84c0 7.207-3.6055 14.414-10.812 14.414h-241.44c-18.02 0-18.02-50.449 0-50.449zm-1030.6 7.207h198.2c21.621 0 21.621 50.449 0 50.449h-237.84c-7.207 0-10.812-3.6055-10.812-10.812v-241.44c0-18.02 50.449-18.02 50.449 0zm-10.812-1030.6v198.2c0 21.621-50.449 21.621-50.449 0v-237.84c0-7.207 7.207-10.812 14.414-10.812h237.84c18.02 0 18.02 50.449 0 50.449zm547.75 356.76v129.73h129.73c-10.812-68.469-64.863-118.92-129.73-129.73zm129.73 180.18h-129.73v129.73c64.863-10.812 118.92-64.863 129.73-129.73zm-180.18 129.73v-129.73h-129.73c10.812 64.863 61.262 118.92 129.73 129.73zm-129.73-180.18h129.73v-129.73c-68.469 10.812-118.92 61.262-129.73 129.73z" fill-rule="evenodd"/></svg>`;
|
|
1973
|
+
const searchSvg = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`;
|
|
1974
|
+
const compSvg = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/></svg>`;
|
|
1975
|
+
const iconSvg = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`;
|
|
1976
|
+
return `<!DOCTYPE html>
|
|
1977
|
+
<html lang="en">
|
|
1978
|
+
<head>
|
|
1979
|
+
<meta charset="UTF-8" />
|
|
1980
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1981
|
+
<script>(function(){var t=localStorage.getItem('scope-theme');if(t==='dark'||t==='light'){document.documentElement.setAttribute('data-theme',t)}else if(window.matchMedia&&window.matchMedia('(prefers-color-scheme:dark)').matches){document.documentElement.setAttribute('data-theme','dark')}else{document.documentElement.setAttribute('data-theme','light')}})();</script>
|
|
1982
|
+
<title>${escapeHtml(title)}</title>
|
|
1983
|
+
${generateCSS()}
|
|
1984
|
+
</head>
|
|
1985
|
+
<body>
|
|
1986
|
+
<nav class="top-nav">
|
|
1987
|
+
<a class="site-title" href="${basePath}index.html">${logoSvg}<span>Scope</span></a>${breadcrumbs.length > 0 ? `<nav class="breadcrumbs" aria-label="Breadcrumb">${breadcrumbs.map((b) => b.url ? `<a href="${b.url}">${escapeHtml(b.label)}</a>` : `<span>${escapeHtml(b.label)}</span>`).join("")}</nav>` : ""}
|
|
1988
|
+
<div class="spacer"></div>
|
|
1989
|
+
<button class="cmdk-trigger" id="cmdk-trigger" type="button">
|
|
1990
|
+
${searchSvg}
|
|
1991
|
+
<span>Search\u2026</span>
|
|
1992
|
+
<kbd>\u2318K</kbd>
|
|
1993
|
+
</button>
|
|
1994
|
+
<button class="theme-toggle" id="theme-toggle" type="button" aria-label="Toggle theme" title="Toggle theme"></button>
|
|
1995
|
+
</nav>
|
|
1996
|
+
|
|
1997
|
+
<div class="cmdk-overlay" id="cmdk-overlay" hidden></div>
|
|
1998
|
+
<div class="cmdk-dialog" id="cmdk-dialog" hidden role="dialog" aria-label="Search components">
|
|
1999
|
+
<div class="cmdk-input-wrapper">
|
|
2000
|
+
${searchSvg}
|
|
2001
|
+
<input class="cmdk-input" id="cmdk-input" placeholder="Jump to component or icon\u2026" autocomplete="off" spellcheck="false" />
|
|
2002
|
+
</div>
|
|
2003
|
+
<div class="cmdk-list" id="cmdk-list"></div>
|
|
2004
|
+
<div class="cmdk-empty" id="cmdk-empty" hidden>No results found.</div>
|
|
2005
|
+
<div class="cmdk-footer">
|
|
2006
|
+
<span><kbd>\u2191\u2193</kbd> navigate</span>
|
|
2007
|
+
<span><kbd>\u21B5</kbd> open</span>
|
|
2008
|
+
<span><kbd>esc</kbd> close</span>
|
|
2009
|
+
</div>
|
|
2010
|
+
</div>
|
|
2011
|
+
|
|
2012
|
+
<div class="page-layout">
|
|
2013
|
+
<nav class="sidebar" id="sidebar">
|
|
2014
|
+
${sidebar}
|
|
2015
|
+
</nav>
|
|
2016
|
+
<div class="main-content">
|
|
2017
|
+
<div class="content-body${onThisPage ? "" : " content-body-full"}">
|
|
2018
|
+
${body}
|
|
2019
|
+
</div>
|
|
2020
|
+
${onThisPage ? `<nav class="on-this-page">
|
|
2021
|
+
<h4>On this page</h4>
|
|
2022
|
+
${onThisPage}
|
|
2023
|
+
</nav>` : ""}
|
|
2024
|
+
</div>
|
|
2025
|
+
</div>
|
|
2026
|
+
<script>
|
|
2027
|
+
(function () {
|
|
2028
|
+
var ITEMS = ${searchItemsJson};
|
|
2029
|
+
var overlay = document.getElementById('cmdk-overlay');
|
|
2030
|
+
var dialog = document.getElementById('cmdk-dialog');
|
|
2031
|
+
var input = document.getElementById('cmdk-input');
|
|
2032
|
+
var list = document.getElementById('cmdk-list');
|
|
2033
|
+
var trigger = document.getElementById('cmdk-trigger');
|
|
2034
|
+
var emptyEl = document.getElementById('cmdk-empty');
|
|
2035
|
+
if (!overlay || !dialog || !input || !list || !trigger) return;
|
|
2036
|
+
|
|
2037
|
+
var activeIdx = 0;
|
|
2038
|
+
var filtered = ITEMS.slice();
|
|
2039
|
+
var compIcon = ${JSON.stringify(compSvg)};
|
|
2040
|
+
var icoIcon = ${JSON.stringify(iconSvg)};
|
|
2041
|
+
|
|
2042
|
+
function render() {
|
|
2043
|
+
if (filtered.length === 0) {
|
|
2044
|
+
list.innerHTML = '';
|
|
2045
|
+
list.style.display = 'none';
|
|
2046
|
+
if (emptyEl) emptyEl.hidden = false;
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
if (emptyEl) emptyEl.hidden = true;
|
|
2050
|
+
list.style.display = '';
|
|
2051
|
+
list.innerHTML = filtered.map(function (item, i) {
|
|
2052
|
+
var cls = 'cmdk-item' + (i === activeIdx ? ' cmdk-item-active' : '');
|
|
2053
|
+
var icon = item.type === 'icon' ? icoIcon : compIcon;
|
|
2054
|
+
var col = item.collection ? '<span class="cmdk-item-collection">' + item.collection + '</span>' : '';
|
|
2055
|
+
return '<a class="' + cls + '" href="' + item.url + '" data-idx="' + i + '">' +
|
|
2056
|
+
'<span class="cmdk-item-icon">' + icon + '</span>' +
|
|
2057
|
+
'<span class="cmdk-item-name">' + item.name + '</span>' +
|
|
2058
|
+
col +
|
|
2059
|
+
'<span class="cmdk-item-type">' + item.type + '</span>' +
|
|
2060
|
+
'</a>';
|
|
2061
|
+
}).join('');
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
function filter(q) {
|
|
2065
|
+
if (!q) {
|
|
2066
|
+
filtered = ITEMS.slice();
|
|
2067
|
+
} else {
|
|
2068
|
+
filtered = ITEMS.filter(function (item) {
|
|
2069
|
+
var haystack = item.name.toLowerCase();
|
|
2070
|
+
if (item.collection) haystack += ' ' + item.collection.toLowerCase();
|
|
2071
|
+
if (item.keywords) haystack += ' ' + item.keywords.toLowerCase();
|
|
2072
|
+
return haystack.includes(q);
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
activeIdx = 0;
|
|
2076
|
+
render();
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
function open() {
|
|
2080
|
+
overlay.hidden = false;
|
|
2081
|
+
dialog.hidden = false;
|
|
2082
|
+
input.value = '';
|
|
2083
|
+
filter('');
|
|
2084
|
+
input.focus();
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
function close() {
|
|
2088
|
+
overlay.hidden = true;
|
|
2089
|
+
dialog.hidden = true;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
function scrollActive() {
|
|
2093
|
+
var el = list.querySelector('.cmdk-item-active');
|
|
2094
|
+
if (el) el.scrollIntoView({ block: 'nearest' });
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
document.addEventListener('keydown', function (e) {
|
|
2098
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
2099
|
+
e.preventDefault();
|
|
2100
|
+
if (dialog.hidden) open(); else close();
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
if (dialog.hidden) return;
|
|
2104
|
+
if (e.key === 'Escape') { e.preventDefault(); close(); }
|
|
2105
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); activeIdx = Math.min(activeIdx + 1, filtered.length - 1); render(); scrollActive(); }
|
|
2106
|
+
if (e.key === 'ArrowUp') { e.preventDefault(); activeIdx = Math.max(activeIdx - 1, 0); render(); scrollActive(); }
|
|
2107
|
+
if (e.key === 'Enter') { e.preventDefault(); if (filtered[activeIdx]) window.location.href = filtered[activeIdx].url; }
|
|
2108
|
+
});
|
|
2109
|
+
|
|
2110
|
+
overlay.addEventListener('click', close);
|
|
2111
|
+
trigger.addEventListener('click', open);
|
|
2112
|
+
input.addEventListener('input', function () { filter(input.value.toLowerCase()); });
|
|
2113
|
+
|
|
2114
|
+
list.addEventListener('mousemove', function (e) {
|
|
2115
|
+
var item = e.target.closest('.cmdk-item');
|
|
2116
|
+
if (item) {
|
|
2117
|
+
var idx = parseInt(item.getAttribute('data-idx'), 10);
|
|
2118
|
+
if (idx !== activeIdx) { activeIdx = idx; render(); }
|
|
2119
|
+
}
|
|
2120
|
+
});
|
|
2121
|
+
|
|
2122
|
+
list.addEventListener('click', function (e) {
|
|
2123
|
+
var item = e.target.closest('.cmdk-item');
|
|
2124
|
+
if (item) { e.preventDefault(); window.location.href = item.getAttribute('href'); }
|
|
2125
|
+
});
|
|
2126
|
+
})();
|
|
2127
|
+
(function () {
|
|
2128
|
+
var btn = document.getElementById('theme-toggle');
|
|
2129
|
+
if (!btn) return;
|
|
2130
|
+
var sunSvg = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
|
|
2131
|
+
var moonSvg = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
|
|
2132
|
+
function setIcon() {
|
|
2133
|
+
btn.innerHTML = document.documentElement.getAttribute('data-theme') === 'dark' ? moonSvg : sunSvg;
|
|
2134
|
+
}
|
|
2135
|
+
setIcon();
|
|
2136
|
+
btn.addEventListener('click', function () {
|
|
2137
|
+
var current = document.documentElement.getAttribute('data-theme');
|
|
2138
|
+
var next = current === 'dark' ? 'light' : 'dark';
|
|
2139
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
2140
|
+
localStorage.setItem('scope-theme', next);
|
|
2141
|
+
setIcon();
|
|
2142
|
+
});
|
|
2143
|
+
})();
|
|
2144
|
+
</script>
|
|
2145
|
+
</body>
|
|
2146
|
+
</html>`;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// src/templates/component-detail.ts
|
|
2150
|
+
function notGenerated(message = "Not generated") {
|
|
2151
|
+
return `<div class="not-generated">${escapeHtml(message)}</div>`;
|
|
2152
|
+
}
|
|
2153
|
+
function renderFailure(message, heuristicFlags = []) {
|
|
2154
|
+
const hintHtml = heuristicFlags.length > 0 ? `<div class="render-failure-hints"><strong>Failure hints:</strong> ${escapeHtml(heuristicFlags.join(", "))}</div>` : "";
|
|
2155
|
+
return `<div class="render-failure"><strong>Render failed:</strong> ${escapeHtml(message)}${hintHtml}</div>`;
|
|
2156
|
+
}
|
|
2157
|
+
function sectionWrap(id, title, description, content) {
|
|
2158
|
+
return `<section class="section" id="${id}">
|
|
2159
|
+
<div class="section-header">
|
|
2160
|
+
<h2>${escapeHtml(title)}</h2>
|
|
2161
|
+
<p>${escapeHtml(description)}</p>
|
|
2162
|
+
</div>
|
|
2163
|
+
${content}
|
|
2164
|
+
</section>`;
|
|
2165
|
+
}
|
|
2166
|
+
function renderPlayground(name, data) {
|
|
2167
|
+
const component = data.manifest.components[name];
|
|
2168
|
+
const render = data.renders.get(name);
|
|
2169
|
+
const slug = slugify(name);
|
|
2170
|
+
const allProps = component ? Object.entries(component.props) : [];
|
|
2171
|
+
const ownProps = allProps.filter(([, p]) => p.source !== "inherited");
|
|
2172
|
+
const editableTypes = /* @__PURE__ */ new Set([
|
|
2173
|
+
"string",
|
|
2174
|
+
"number",
|
|
2175
|
+
"boolean",
|
|
2176
|
+
"union",
|
|
2177
|
+
"array",
|
|
2178
|
+
"any",
|
|
2179
|
+
"unknown",
|
|
2180
|
+
"other",
|
|
2181
|
+
"node",
|
|
2182
|
+
"element"
|
|
2183
|
+
]);
|
|
2184
|
+
const editableProps = ownProps.filter(([, p]) => editableTypes.has(p.type));
|
|
2185
|
+
const hasChildren = allProps.some(([n]) => n === "children");
|
|
2186
|
+
const childrenInEditable = editableProps.some(([n]) => n === "children");
|
|
2187
|
+
if (hasChildren && !childrenInEditable) {
|
|
2188
|
+
const childrenProp = allProps.find(([n]) => n === "children");
|
|
2189
|
+
if (childrenProp) editableProps.unshift(childrenProp);
|
|
2190
|
+
}
|
|
2191
|
+
const scopeDefaults = data.playgroundDefaults?.get(name) ?? {};
|
|
2192
|
+
function getOverrideDefault(propName, prop) {
|
|
2193
|
+
const scopeVal = scopeDefaults[propName];
|
|
2194
|
+
if (scopeVal !== void 0) return String(scopeVal);
|
|
2195
|
+
if (propName === "children" && (prop.type === "node" || prop.type === "element")) return name;
|
|
2196
|
+
return void 0;
|
|
2197
|
+
}
|
|
2198
|
+
const controlsHtml = editableProps.length > 0 ? `<div class="pg-controls" id="pg-controls-${escapeHtml(slug)}">
|
|
2199
|
+
<div class="pg-controls-header">Props</div>
|
|
2200
|
+
${editableProps.map(([n, p]) => propControlHtml(n, p, getOverrideDefault(n, p))).join("\n ")}
|
|
2201
|
+
</div>` : "";
|
|
2202
|
+
const iframeSrc = `${data.options.basePath}playground/${slug}.html`;
|
|
2203
|
+
const svgSun = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
|
|
2204
|
+
const svgMoon = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
|
|
2205
|
+
const svgMonitor = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>';
|
|
2206
|
+
const svgPhone = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>';
|
|
2207
|
+
const svgTablet = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="2" width="16" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>';
|
|
2208
|
+
const svgExpand = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>';
|
|
2209
|
+
const svgClose = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
|
|
2210
|
+
const es = escapeHtml(slug);
|
|
2211
|
+
const playgroundHtml = render?.errorMessage ? "" : `<div class="pg-container" id="pg-container-${es}">
|
|
2212
|
+
${controlsHtml}
|
|
2213
|
+
<div class="pg-preview-wrapper" id="pg-preview-${es}">
|
|
2214
|
+
<div class="pg-toolbar" id="pg-toolbar-${es}">
|
|
2215
|
+
<button class="pg-toolbar-btn pg-theme-btn" id="pg-theme-${es}" title="Toggle dark mode">${svgSun}</button>
|
|
2216
|
+
<span class="pg-toolbar-sep"></span>
|
|
2217
|
+
<button class="pg-toolbar-btn pg-device-btn active" data-width="" title="Auto">${svgMonitor}</button>
|
|
2218
|
+
<button class="pg-toolbar-btn pg-device-btn" data-width="375" title="Mobile (375px)">${svgPhone}</button>
|
|
2219
|
+
<button class="pg-toolbar-btn pg-device-btn" data-width="768" title="Tablet (768px)">${svgTablet}</button>
|
|
2220
|
+
<span class="pg-toolbar-sep"></span>
|
|
2221
|
+
<span class="pg-toolbar-spacer"></span>
|
|
2222
|
+
<button class="pg-toolbar-btn pg-fullscreen-btn" id="pg-fs-${es}" title="Fullscreen">${svgExpand}</button>
|
|
2223
|
+
</div>
|
|
2224
|
+
<div class="pg-iframe-area" id="pg-iframe-area-${es}">
|
|
2225
|
+
<iframe
|
|
2226
|
+
class="pg-iframe"
|
|
2227
|
+
id="pg-iframe-${es}"
|
|
2228
|
+
src="${escapeHtml(iframeSrc)}"
|
|
2229
|
+
loading="lazy"
|
|
2230
|
+
allowtransparency="true"
|
|
2231
|
+
></iframe>
|
|
2232
|
+
</div>
|
|
2233
|
+
</div>
|
|
2234
|
+
</div>
|
|
2235
|
+
<script>
|
|
2236
|
+
(function() {
|
|
2237
|
+
var iframe = document.getElementById("pg-iframe-${es}");
|
|
2238
|
+
var container = document.getElementById("pg-container-${es}");
|
|
2239
|
+
var preview = document.getElementById("pg-preview-${es}");
|
|
2240
|
+
var toolbar = document.getElementById("pg-toolbar-${es}");
|
|
2241
|
+
var iframeArea = document.getElementById("pg-iframe-area-${es}");
|
|
2242
|
+
var themeBtn = document.getElementById("pg-theme-${es}");
|
|
2243
|
+
var fsBtn = document.getElementById("pg-fs-${es}");
|
|
2244
|
+
if (!iframe || !container || !preview) return;
|
|
2245
|
+
|
|
2246
|
+
// --- Props ---
|
|
2247
|
+
function collectProps() {
|
|
2248
|
+
var props = {};
|
|
2249
|
+
var controls = container.querySelectorAll("[data-prop-name]");
|
|
2250
|
+
for (var i = 0; i < controls.length; i++) {
|
|
2251
|
+
var el = controls[i];
|
|
2252
|
+
var propName = el.getAttribute("data-prop-name");
|
|
2253
|
+
if (!propName) continue;
|
|
2254
|
+
if (el.type === "checkbox") {
|
|
2255
|
+
props[propName] = el.checked;
|
|
2256
|
+
} else {
|
|
2257
|
+
var v = (el.value || "").trim();
|
|
2258
|
+
if (v === "") continue;
|
|
2259
|
+
try { props[propName] = JSON.parse(v); } catch(_) { props[propName] = v; }
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
return props;
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
function sendProps() {
|
|
2266
|
+
if (!iframe.contentWindow) return;
|
|
2267
|
+
iframe.contentWindow.postMessage({ type: "scope-playground-props", props: collectProps() }, "*");
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
var propsSent = false;
|
|
2271
|
+
function sendOnce() {
|
|
2272
|
+
if (!propsSent) {
|
|
2273
|
+
propsSent = true;
|
|
2274
|
+
setTimeout(function() { sendProps(); applyTheme(); }, 50);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
iframe.addEventListener("load", sendOnce);
|
|
2278
|
+
try { if (iframe.contentDocument && iframe.contentDocument.readyState === "complete") sendOnce(); } catch(e) {}
|
|
2279
|
+
container.addEventListener("input", sendProps);
|
|
2280
|
+
container.addEventListener("change", sendProps);
|
|
2281
|
+
|
|
2282
|
+
window.addEventListener("message", function(e) {
|
|
2283
|
+
if (e.data && e.data.type === "scope-playground-height" && e.source === iframe.contentWindow) {
|
|
2284
|
+
var h = Math.max(300, e.data.height + 32);
|
|
2285
|
+
iframe.style.height = h + "px";
|
|
2286
|
+
}
|
|
2287
|
+
});
|
|
682
2288
|
|
|
683
|
-
//
|
|
684
|
-
var
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
2289
|
+
// --- Dark / Light mode ---
|
|
2290
|
+
var isDark = document.documentElement.getAttribute("data-theme") === "dark";
|
|
2291
|
+
var userOverride = false;
|
|
2292
|
+
|
|
2293
|
+
function applyTheme() {
|
|
2294
|
+
themeBtn.innerHTML = isDark ? ${JSON.stringify(svgMoon)} : ${JSON.stringify(svgSun)};
|
|
2295
|
+
themeBtn.classList.toggle("active", isDark);
|
|
2296
|
+
preview.classList.toggle("pg-dark", isDark);
|
|
2297
|
+
if (iframe.contentWindow) {
|
|
2298
|
+
iframe.contentWindow.postMessage({ type: "scope-playground-theme", theme: isDark ? "dark" : "light" }, "*");
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
// Apply initial theme from global setting
|
|
2303
|
+
applyTheme();
|
|
2304
|
+
|
|
2305
|
+
themeBtn.addEventListener("click", function() {
|
|
2306
|
+
userOverride = true;
|
|
2307
|
+
isDark = !isDark;
|
|
2308
|
+
applyTheme();
|
|
2309
|
+
});
|
|
2310
|
+
|
|
2311
|
+
// Follow global theme changes unless the user has manually toggled
|
|
2312
|
+
new MutationObserver(function() {
|
|
2313
|
+
if (userOverride) return;
|
|
2314
|
+
isDark = document.documentElement.getAttribute("data-theme") === "dark";
|
|
2315
|
+
applyTheme();
|
|
2316
|
+
}).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
|
|
2317
|
+
|
|
2318
|
+
// --- Device size ---
|
|
2319
|
+
var deviceBtns = toolbar.querySelectorAll(".pg-device-btn");
|
|
2320
|
+
for (var d = 0; d < deviceBtns.length; d++) {
|
|
2321
|
+
deviceBtns[d].addEventListener("click", function() {
|
|
2322
|
+
for (var j = 0; j < deviceBtns.length; j++) deviceBtns[j].classList.remove("active");
|
|
2323
|
+
this.classList.add("active");
|
|
2324
|
+
var w = this.getAttribute("data-width");
|
|
2325
|
+
iframe.style.maxWidth = w ? w + "px" : "";
|
|
2326
|
+
iframe.style.margin = w ? "0 auto" : "";
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
// --- Fullscreen ---
|
|
2331
|
+
function exitFullscreen() {
|
|
2332
|
+
preview.classList.remove("pg-fullscreen");
|
|
2333
|
+
fsBtn.innerHTML = ${JSON.stringify(svgExpand)};
|
|
2334
|
+
fsBtn.title = "Fullscreen";
|
|
2335
|
+
}
|
|
2336
|
+
fsBtn.addEventListener("click", function() {
|
|
2337
|
+
var isFs = preview.classList.toggle("pg-fullscreen");
|
|
2338
|
+
fsBtn.innerHTML = isFs ? ${JSON.stringify(svgClose)} : ${JSON.stringify(svgExpand)};
|
|
2339
|
+
fsBtn.title = isFs ? "Exit fullscreen" : "Fullscreen";
|
|
2340
|
+
});
|
|
2341
|
+
document.addEventListener("keydown", function(e) {
|
|
2342
|
+
if (e.key === "Escape" && preview.classList.contains("pg-fullscreen")) exitFullscreen();
|
|
2343
|
+
});
|
|
2344
|
+
})();
|
|
2345
|
+
</script>`;
|
|
2346
|
+
const renderW = render?.width != null ? render.width : void 0;
|
|
2347
|
+
const renderH = render?.height != null ? render.height : void 0;
|
|
2348
|
+
const renderSizeAttr = renderW != null && renderH != null ? ` width="${renderW}" height="${renderH}"` : "";
|
|
2349
|
+
const playgroundError = render?.errorMessage ? renderFailure(
|
|
2350
|
+
`${render.errorMessage}
|
|
2351
|
+
Inspect .reactscope/renders/${name}.error.json and rerun scope render component ${name}.`,
|
|
2352
|
+
render.heuristicFlags ?? []
|
|
2353
|
+
) : render && !render.screenshot && !render.svgContent ? renderFailure(
|
|
2354
|
+
`No static preview was generated for ${name}. Check .reactscope/run-summary.json and rerun scope render all.`
|
|
2355
|
+
) : "";
|
|
2356
|
+
const screenshotFallback = render?.screenshot ? `<details class="pg-static-fallback">
|
|
2357
|
+
<summary>Static Preview</summary>
|
|
2358
|
+
<div class="render-preview">
|
|
2359
|
+
<img src="data:image/png;base64,${render.screenshot}" alt="${escapeHtml(name)} render"${renderSizeAttr} />
|
|
704
2360
|
</div>
|
|
705
|
-
|
|
706
|
-
|
|
2361
|
+
</details>` : "";
|
|
2362
|
+
return sectionWrap(
|
|
2363
|
+
"playground",
|
|
2364
|
+
"Playground",
|
|
2365
|
+
"Interactive component preview with prop controls.",
|
|
2366
|
+
`${playgroundError}${playgroundHtml}${screenshotFallback}`
|
|
2367
|
+
);
|
|
707
2368
|
}
|
|
708
|
-
function
|
|
2369
|
+
function renderProps(name, data) {
|
|
709
2370
|
const component = data.manifest.components[name];
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
2371
|
+
if (!component) {
|
|
2372
|
+
return sectionWrap("props", "Props", "Component prop definitions.", notGenerated());
|
|
2373
|
+
}
|
|
2374
|
+
const allProps = Object.entries(component.props);
|
|
2375
|
+
const ownProps = allProps.filter(([, p]) => p.source !== "inherited");
|
|
2376
|
+
const inheritedProps = allProps.filter(([, p]) => p.source === "inherited");
|
|
2377
|
+
function propsTableHtml(rows) {
|
|
2378
|
+
return `<table class="props-table">
|
|
713
2379
|
<thead>
|
|
714
2380
|
<tr>
|
|
715
2381
|
<th>Prop</th>
|
|
@@ -719,21 +2385,59 @@ function renderPlayground(name, data) {
|
|
|
719
2385
|
</tr>
|
|
720
2386
|
</thead>
|
|
721
2387
|
<tbody>
|
|
722
|
-
${
|
|
2388
|
+
${rows.map(([n, p]) => propTableRow(n, p)).join("\n ")}
|
|
723
2389
|
</tbody>
|
|
724
|
-
</table
|
|
725
|
-
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
2390
|
+
</table>`;
|
|
2391
|
+
}
|
|
2392
|
+
const GROUP_LABELS = {
|
|
2393
|
+
html: "HTML Attributes",
|
|
2394
|
+
react: "React Props",
|
|
2395
|
+
aria: "ARIA Attributes"
|
|
2396
|
+
};
|
|
2397
|
+
function renderInheritedGroup(groupName, rows) {
|
|
2398
|
+
const label = GROUP_LABELS[groupName] ?? groupName;
|
|
2399
|
+
return `<details class="inherited-group">
|
|
2400
|
+
<summary>${escapeHtml(label)} <span class="inherited-group-count">${rows.length}</span></summary>
|
|
2401
|
+
${propsTableHtml(rows)}
|
|
2402
|
+
</details>`;
|
|
2403
|
+
}
|
|
2404
|
+
if (allProps.length === 0) {
|
|
2405
|
+
return sectionWrap(
|
|
2406
|
+
"props",
|
|
2407
|
+
"Props",
|
|
2408
|
+
"Component prop definitions.",
|
|
2409
|
+
`<p style="color:var(--color-muted);font-size:13px">No props defined.</p>`
|
|
2410
|
+
);
|
|
2411
|
+
}
|
|
2412
|
+
let content;
|
|
2413
|
+
if (inheritedProps.length === 0) {
|
|
2414
|
+
content = propsTableHtml(allProps);
|
|
2415
|
+
} else {
|
|
2416
|
+
const ownTable = ownProps.length > 0 ? propsTableHtml(ownProps) : `<p style="color:var(--color-muted);font-size:13px">No component-specific props.</p>`;
|
|
2417
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2418
|
+
for (const entry of inheritedProps) {
|
|
2419
|
+
const group = entry[1].sourceGroup ?? "other";
|
|
2420
|
+
const list = groups.get(group) ?? [];
|
|
2421
|
+
list.push(entry);
|
|
2422
|
+
groups.set(group, list);
|
|
2423
|
+
}
|
|
2424
|
+
const order = ["html", "react", "aria"];
|
|
2425
|
+
const sortedKeys = [
|
|
2426
|
+
...order.filter((k) => groups.has(k)),
|
|
2427
|
+
...[...groups.keys()].filter((k) => !order.includes(k)).sort()
|
|
2428
|
+
];
|
|
2429
|
+
const groupHtml = sortedKeys.map((key) => renderInheritedGroup(key, groups.get(key) ?? [])).join("\n");
|
|
2430
|
+
content = `${ownTable}
|
|
2431
|
+
<div class="inherited-props-section">
|
|
2432
|
+
<div class="inherited-props-heading">${inheritedProps.length} inherited prop${inheritedProps.length === 1 ? "" : "s"}</div>
|
|
2433
|
+
${groupHtml}
|
|
2434
|
+
</div>`;
|
|
2435
|
+
}
|
|
731
2436
|
return sectionWrap(
|
|
732
|
-
"
|
|
733
|
-
"
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
<div style="margin-top:24px">${renderHtml}</div>`
|
|
2437
|
+
"props",
|
|
2438
|
+
"Props",
|
|
2439
|
+
`${allProps.length} prop${allProps.length === 1 ? "" : "s"} defined.`,
|
|
2440
|
+
content
|
|
737
2441
|
);
|
|
738
2442
|
}
|
|
739
2443
|
function renderMatrix(name, data) {
|
|
@@ -743,14 +2447,20 @@ function renderMatrix(name, data) {
|
|
|
743
2447
|
"matrix",
|
|
744
2448
|
"Matrix",
|
|
745
2449
|
"Prop combination matrix renders.",
|
|
746
|
-
|
|
2450
|
+
render?.errorMessage ? renderFailure(
|
|
2451
|
+
`${render.errorMessage}
|
|
2452
|
+
Matrix renders unavailable because the base render failed. Inspect .reactscope/renders/${name}.error.json.`,
|
|
2453
|
+
render.heuristicFlags ?? []
|
|
2454
|
+
) : notGenerated(
|
|
2455
|
+
"Matrix renders not generated. Run scope render matrix <Component> to produce a matrix."
|
|
2456
|
+
)
|
|
747
2457
|
);
|
|
748
2458
|
}
|
|
749
2459
|
const cells = render.cells;
|
|
750
2460
|
const cols = Math.ceil(Math.sqrt(cells.length));
|
|
751
2461
|
const cellsHtml = cells.map((cell) => {
|
|
752
2462
|
const label = cell.axisValues.join(" / ");
|
|
753
|
-
const imgHtml = cell.screenshot ? `<img class="scope-screenshot" src="data:image/png;base64,${cell.screenshot}" alt="${escapeHtml(label)}" />` : `<span
|
|
2463
|
+
const imgHtml = cell.screenshot ? `<img class="scope-screenshot" src="data:image/png;base64,${cell.screenshot}" alt="${escapeHtml(label)}" />` : `<span class="matrix-cell-error">${cell.error ? escapeHtml(cell.error) : "failed"}</span>`;
|
|
754
2464
|
return `<div class="matrix-cell">${imgHtml}<div class="cell-label">${escapeHtml(label)}</div></div>`;
|
|
755
2465
|
}).join("\n");
|
|
756
2466
|
const grid = `<div class="matrix-grid" style="grid-template-columns: repeat(${cols}, 1fr)">
|
|
@@ -847,7 +2557,13 @@ function renderXRay(name, data) {
|
|
|
847
2557
|
"x-ray",
|
|
848
2558
|
"X-Ray",
|
|
849
2559
|
"DOM structure and computed styles.",
|
|
850
|
-
|
|
2560
|
+
render?.errorMessage ? renderFailure(
|
|
2561
|
+
`${render.errorMessage}
|
|
2562
|
+
X-Ray data unavailable because render failed. Inspect .reactscope/renders/${name}.error.json.`,
|
|
2563
|
+
render.heuristicFlags ?? []
|
|
2564
|
+
) : notGenerated(
|
|
2565
|
+
"X-Ray data not generated. Run scope render component <Component> with DOM capture enabled."
|
|
2566
|
+
)
|
|
851
2567
|
);
|
|
852
2568
|
}
|
|
853
2569
|
const dom = render.dom;
|
|
@@ -858,11 +2574,14 @@ function renderXRay(name, data) {
|
|
|
858
2574
|
if (m?.[1] !== void 0) nodeStylesMap[parseInt(m[1], 10)] = styles;
|
|
859
2575
|
}
|
|
860
2576
|
}
|
|
861
|
-
const nodeStylesJson =
|
|
2577
|
+
const nodeStylesJson = JSON.stringify(JSON.stringify(nodeStylesMap)).replace(
|
|
2578
|
+
/<\/script>/gi,
|
|
2579
|
+
"<\\/script>"
|
|
2580
|
+
);
|
|
862
2581
|
const treeHtml = `<div class="dom-tree">${renderDOMTree(dom.tree)}</div>`;
|
|
863
2582
|
const stylesPanelHtml = `
|
|
864
2583
|
<div id="xray-styles-panel-${escapeHtml(name)}" class="xray-styles-panel" style="display:none;margin-top:16px;border:1px solid var(--color-border);border-radius:6px;overflow:hidden">
|
|
865
|
-
<div class="xray-styles-header" style="padding:8px 12px;background:var(--color-
|
|
2584
|
+
<div class="xray-styles-header" style="padding:8px 12px;background:var(--color-bg-subtle);font-size:12px;font-weight:600;color:var(--color-muted);display:flex;justify-content:space-between;align-items:center">
|
|
866
2585
|
<span id="xray-styles-label-${escapeHtml(name)}">\u2014 no node selected \u2014</span>
|
|
867
2586
|
<button onclick="document.getElementById('xray-styles-panel-${escapeHtml(name)}').style.display='none'" style="background:none;border:none;cursor:pointer;color:var(--color-muted);font-size:16px;line-height:1">\xD7</button>
|
|
868
2587
|
</div>
|
|
@@ -887,7 +2606,7 @@ function renderXRay(name, data) {
|
|
|
887
2606
|
var tag = el.tagName === "DETAILS" ? el.querySelector("summary .dom-tag-open") : el;
|
|
888
2607
|
label.textContent = tag ? tag.textContent.trim().slice(0, 60) : "node #" + id;
|
|
889
2608
|
body.innerHTML = Object.entries(styles).map(function(e) {
|
|
890
|
-
return "<tr><td>" + e[0] + "</td><td style=
|
|
2609
|
+
return "<tr><td>" + e[0] + "</td><td style='font-family:monospace'>" + e[1] + "</td></tr>";
|
|
891
2610
|
}).join("");
|
|
892
2611
|
panel.style.display = "block";
|
|
893
2612
|
// Highlight selected node
|
|
@@ -1015,15 +2734,10 @@ function renderComponentDetail(name, data) {
|
|
|
1015
2734
|
<div class="meta">${complexityBadge}${memoizedBadge}${exportBadge}</div>
|
|
1016
2735
|
${filepath}
|
|
1017
2736
|
</div>`;
|
|
1018
|
-
const
|
|
2737
|
+
const mainSections = [
|
|
1019
2738
|
renderPlayground(name, data),
|
|
1020
2739
|
renderMatrix(name, data),
|
|
1021
2740
|
renderDocs(),
|
|
1022
|
-
renderAnalysis(name, data),
|
|
1023
|
-
renderXRay(name, data),
|
|
1024
|
-
renderTokens(name, data),
|
|
1025
|
-
renderAccessibility(name, data),
|
|
1026
|
-
renderComposition(name, data),
|
|
1027
2741
|
sectionWrap(
|
|
1028
2742
|
"responsive",
|
|
1029
2743
|
"Responsive",
|
|
@@ -1039,18 +2753,41 @@ function renderComponentDetail(name, data) {
|
|
|
1039
2753
|
notGenerated("Stress tests not generated. Stress render runs are a future feature.")
|
|
1040
2754
|
)
|
|
1041
2755
|
];
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
2756
|
+
const inspectorSections = [
|
|
2757
|
+
renderProps(name, data),
|
|
2758
|
+
renderXRay(name, data),
|
|
2759
|
+
renderTokens(name, data),
|
|
2760
|
+
renderAccessibility(name, data),
|
|
2761
|
+
renderAnalysis(name, data),
|
|
2762
|
+
renderComposition(name, data)
|
|
2763
|
+
];
|
|
2764
|
+
const body = `<div class="detail-columns">
|
|
2765
|
+
<div class="detail-main">
|
|
2766
|
+
${header}
|
|
2767
|
+
${mainSections.join("\n")}
|
|
2768
|
+
</div>
|
|
2769
|
+
<div class="detail-inspector">
|
|
2770
|
+
${inspectorSections.join("\n")}
|
|
2771
|
+
</div>
|
|
2772
|
+
</div>`;
|
|
1047
2773
|
const sidebar = sidebarLinks(data, slug);
|
|
2774
|
+
const basePath = data.options.basePath;
|
|
2775
|
+
const breadcrumbs = [];
|
|
2776
|
+
if (component?.collection) {
|
|
2777
|
+
breadcrumbs.push({
|
|
2778
|
+
label: component.collection,
|
|
2779
|
+
url: `${basePath}index.html#collection-${slugify(component.collection)}`
|
|
2780
|
+
});
|
|
2781
|
+
}
|
|
2782
|
+
breadcrumbs.push({ label: name });
|
|
1048
2783
|
return htmlShell({
|
|
1049
2784
|
title: `${name} \u2014 ${data.options.title}`,
|
|
1050
2785
|
body,
|
|
1051
2786
|
sidebar,
|
|
1052
|
-
onThisPage,
|
|
1053
|
-
basePath
|
|
2787
|
+
onThisPage: "",
|
|
2788
|
+
basePath,
|
|
2789
|
+
searchItems: buildSearchItems(data),
|
|
2790
|
+
breadcrumbs
|
|
1054
2791
|
});
|
|
1055
2792
|
}
|
|
1056
2793
|
|
|
@@ -1060,7 +2797,11 @@ function renderCard(name, data) {
|
|
|
1060
2797
|
if (!component) return "";
|
|
1061
2798
|
const slug = slugify(name);
|
|
1062
2799
|
const render = data.renders.get(name);
|
|
1063
|
-
const
|
|
2800
|
+
const allPropEntries = Object.entries(component.props);
|
|
2801
|
+
const ownPropCount = allPropEntries.filter(([, p]) => p.source !== "inherited").length;
|
|
2802
|
+
const totalPropCount = allPropEntries.length;
|
|
2803
|
+
const hasInherited = ownPropCount < totalPropCount;
|
|
2804
|
+
const propCount = ownPropCount;
|
|
1064
2805
|
const hookCount = component.detectedHooks.length;
|
|
1065
2806
|
const previewHtml = render?.screenshot ? `<img class="scope-screenshot" src="data:image/png;base64,${render.screenshot}" alt="${escapeHtml(name)}" />` : `<span class="no-preview">No preview</span>`;
|
|
1066
2807
|
return `<a class="component-card" href="${data.options.basePath}${slug}.html" data-name="${escapeHtml(name.toLowerCase())}">
|
|
@@ -1068,7 +2809,7 @@ function renderCard(name, data) {
|
|
|
1068
2809
|
<div class="card-body">
|
|
1069
2810
|
<div class="card-name">${escapeHtml(name)}</div>
|
|
1070
2811
|
<div class="card-meta">
|
|
1071
|
-
<span>${propCount}
|
|
2812
|
+
<span>${propCount} prop${propCount === 1 ? "" : "s"}${hasInherited ? ` <span style="color:var(--color-muted)">(+${totalPropCount - ownPropCount})</span>` : ""}</span>
|
|
1072
2813
|
<span class="badge ${component.complexityClass}" style="font-size:10px">${escapeHtml(component.complexityClass)}</span>
|
|
1073
2814
|
${hookCount > 0 ? `<span>${hookCount} hooks</span>` : ""}
|
|
1074
2815
|
</div>
|
|
@@ -1079,9 +2820,26 @@ function renderComponentIndex(data) {
|
|
|
1079
2820
|
const allComponents = Object.entries(data.manifest.components);
|
|
1080
2821
|
const collections = data.manifest.collections ?? [];
|
|
1081
2822
|
const visibleComponents = allComponents.filter(([, c]) => !c.internal);
|
|
2823
|
+
const collectionMap = /* @__PURE__ */ new Map();
|
|
2824
|
+
for (const col of collections) {
|
|
2825
|
+
collectionMap.set(col.name, []);
|
|
2826
|
+
}
|
|
2827
|
+
const ungrouped = [];
|
|
2828
|
+
for (const [name, component] of visibleComponents) {
|
|
2829
|
+
if (component.collection) {
|
|
2830
|
+
if (!collectionMap.has(component.collection)) {
|
|
2831
|
+
collectionMap.set(component.collection, []);
|
|
2832
|
+
}
|
|
2833
|
+
collectionMap.get(component.collection)?.push(name);
|
|
2834
|
+
} else {
|
|
2835
|
+
ungrouped.push(name);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
1082
2838
|
const totalCount = allComponents.length;
|
|
1083
2839
|
const visibleCount = visibleComponents.length;
|
|
1084
|
-
const
|
|
2840
|
+
const nonEmptyCollections = [...collectionMap.values()].filter(
|
|
2841
|
+
(names) => names.length > 0
|
|
2842
|
+
).length;
|
|
1085
2843
|
const simpleCount = visibleComponents.filter(([, c]) => c.complexityClass === "simple").length;
|
|
1086
2844
|
const complexCount = visibleComponents.filter(([, c]) => c.complexityClass === "complex").length;
|
|
1087
2845
|
const memoizedCount = visibleComponents.filter(([, c]) => c.memoized).length;
|
|
@@ -1092,7 +2850,7 @@ function renderComponentIndex(data) {
|
|
|
1092
2850
|
</div>
|
|
1093
2851
|
<div class="stat-card">
|
|
1094
2852
|
<div class="stat-label">Collections</div>
|
|
1095
|
-
<div class="stat-value">${
|
|
2853
|
+
<div class="stat-value">${nonEmptyCollections}</div>
|
|
1096
2854
|
</div>
|
|
1097
2855
|
<div class="stat-card">
|
|
1098
2856
|
<div class="stat-label">Simple</div>
|
|
@@ -1107,37 +2865,59 @@ function renderComponentIndex(data) {
|
|
|
1107
2865
|
<div class="stat-value">${memoizedCount}</div>
|
|
1108
2866
|
</div>
|
|
1109
2867
|
</div>`;
|
|
1110
|
-
const
|
|
2868
|
+
const hasCollections = collectionMap.size > 0 || visibleComponents.some(([, c]) => c.collection);
|
|
2869
|
+
const collectionDescriptions = /* @__PURE__ */ new Map();
|
|
1111
2870
|
for (const col of collections) {
|
|
1112
|
-
|
|
2871
|
+
if (col.description) {
|
|
2872
|
+
collectionDescriptions.set(col.name, col.description);
|
|
2873
|
+
}
|
|
1113
2874
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
2875
|
+
function renderCollectionTile(colName, names, description) {
|
|
2876
|
+
const slug = colName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
2877
|
+
const descHtml = description ? `<div class="coll-tile-desc">${escapeHtml(description)}</div>` : "";
|
|
2878
|
+
const thumbs = names.slice(0, 4).map((n) => {
|
|
2879
|
+
const render = data.renders.get(n);
|
|
2880
|
+
return render?.screenshot ? `<img class="coll-thumb" src="data:image/png;base64,${render.screenshot}" alt="${escapeHtml(n)}" />` : `<span class="coll-thumb coll-thumb-empty"></span>`;
|
|
2881
|
+
}).join("");
|
|
2882
|
+
return `<a class="collection-tile" href="#collection-${slug}">
|
|
2883
|
+
<div class="coll-tile-thumbs">${thumbs}</div>
|
|
2884
|
+
<div class="coll-tile-body">
|
|
2885
|
+
<div class="coll-tile-name">${escapeHtml(colName)}</div>
|
|
2886
|
+
${descHtml}
|
|
2887
|
+
<div class="coll-tile-count">${names.length} component${names.length === 1 ? "" : "s"}</div>
|
|
2888
|
+
</div>
|
|
2889
|
+
</a>`;
|
|
2890
|
+
}
|
|
2891
|
+
let collectionGallery = "";
|
|
2892
|
+
if (hasCollections) {
|
|
2893
|
+
const tileParts = [];
|
|
2894
|
+
for (const [colName, names] of collectionMap) {
|
|
2895
|
+
if (names.length === 0) continue;
|
|
2896
|
+
tileParts.push(renderCollectionTile(colName, names, collectionDescriptions.get(colName)));
|
|
2897
|
+
}
|
|
2898
|
+
if (ungrouped.length > 0) {
|
|
2899
|
+
tileParts.push(renderCollectionTile("Ungrouped", ungrouped));
|
|
1123
2900
|
}
|
|
2901
|
+
collectionGallery = `<div class="collection-gallery-section">
|
|
2902
|
+
<h2 class="gallery-section-title">Collections</h2>
|
|
2903
|
+
<div class="collection-gallery">${tileParts.join("\n")}</div>
|
|
2904
|
+
</div>`;
|
|
1124
2905
|
}
|
|
1125
|
-
const hasCollections = collectionCount > 0 || visibleComponents.some(([, c]) => c.collection);
|
|
1126
2906
|
let cardSections;
|
|
1127
2907
|
if (!hasCollections) {
|
|
1128
2908
|
const cards = visibleComponents.sort(([a], [b]) => a.localeCompare(b)).map(([name]) => renderCard(name, data)).join("\n");
|
|
1129
2909
|
cardSections = `<div class="component-grid" id="component-grid">${cards}</div>`;
|
|
1130
2910
|
} else {
|
|
1131
2911
|
const sectionParts = [];
|
|
1132
|
-
for (const
|
|
1133
|
-
const names = collectionMap.get(col.name) ?? [];
|
|
2912
|
+
for (const [colName, names] of collectionMap) {
|
|
1134
2913
|
if (names.length === 0) continue;
|
|
1135
|
-
const
|
|
1136
|
-
const
|
|
2914
|
+
const slug = colName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
2915
|
+
const description = collectionDescriptions.get(colName);
|
|
2916
|
+
const descHtml = description ? `<p>${escapeHtml(description)}</p>` : "";
|
|
1137
2917
|
const cards = names.sort().map((name) => renderCard(name, data)).join("\n");
|
|
1138
|
-
sectionParts.push(`<div class="collection-section">
|
|
2918
|
+
sectionParts.push(`<div class="collection-section" id="collection-${slug}">
|
|
1139
2919
|
<div class="collection-section-header">
|
|
1140
|
-
<h2>${escapeHtml(
|
|
2920
|
+
<h2>${escapeHtml(colName)}</h2>
|
|
1141
2921
|
${descHtml}
|
|
1142
2922
|
</div>
|
|
1143
2923
|
<div class="component-grid">${cards}</div>
|
|
@@ -1145,7 +2925,7 @@ function renderComponentIndex(data) {
|
|
|
1145
2925
|
}
|
|
1146
2926
|
if (ungrouped.length > 0) {
|
|
1147
2927
|
const cards = ungrouped.sort().map((name) => renderCard(name, data)).join("\n");
|
|
1148
|
-
sectionParts.push(`<div class="collection-section">
|
|
2928
|
+
sectionParts.push(`<div class="collection-section" id="collection-ungrouped">
|
|
1149
2929
|
<div class="collection-section-header">
|
|
1150
2930
|
<h2>Ungrouped</h2>
|
|
1151
2931
|
<p>Components not assigned to a collection.</p>
|
|
@@ -1158,9 +2938,8 @@ function renderComponentIndex(data) {
|
|
|
1158
2938
|
const filterScript = `<script>
|
|
1159
2939
|
(function () {
|
|
1160
2940
|
var grid = document.getElementById('component-grid');
|
|
1161
|
-
var search = document.getElementById('sidebar-search');
|
|
1162
|
-
if (!grid || !search) return;
|
|
1163
2941
|
var indexSearch = document.getElementById('index-search');
|
|
2942
|
+
if (!grid || !indexSearch) return;
|
|
1164
2943
|
function filter(q) {
|
|
1165
2944
|
var cards = grid.querySelectorAll('.component-card');
|
|
1166
2945
|
cards.forEach(function (card) {
|
|
@@ -1168,10 +2947,7 @@ function renderComponentIndex(data) {
|
|
|
1168
2947
|
card.style.display = name.includes(q) ? '' : 'none';
|
|
1169
2948
|
});
|
|
1170
2949
|
}
|
|
1171
|
-
|
|
1172
|
-
if (indexSearch) {
|
|
1173
|
-
indexSearch.addEventListener('input', function () { filter(indexSearch.value.toLowerCase()); });
|
|
1174
|
-
}
|
|
2950
|
+
indexSearch.addEventListener('input', function () { filter(indexSearch.value.toLowerCase()); });
|
|
1175
2951
|
})();
|
|
1176
2952
|
</script>`;
|
|
1177
2953
|
const header = `<div class="index-header">
|
|
@@ -1179,16 +2955,21 @@ function renderComponentIndex(data) {
|
|
|
1179
2955
|
<p>${visibleCount} component${visibleCount === 1 ? "" : "s"} analysed</p>
|
|
1180
2956
|
<input class="search-box" type="search" id="index-search" placeholder="Filter components\u2026" style="margin-top:12px" />
|
|
1181
2957
|
</div>`;
|
|
1182
|
-
const body = `${header}${statsGrid}${cardSections}${filterScript}`;
|
|
2958
|
+
const body = `${header}${statsGrid}${collectionGallery}${cardSections}${filterScript}`;
|
|
1183
2959
|
const sidebar = sidebarLinks(data, null);
|
|
1184
|
-
const
|
|
1185
|
-
|
|
2960
|
+
const onThisPageParts = [`<a href="#top">Overview</a>`];
|
|
2961
|
+
if (hasCollections) {
|
|
2962
|
+
onThisPageParts.push(`<a href="#collection-gallery">Collections</a>`);
|
|
2963
|
+
}
|
|
2964
|
+
onThisPageParts.push(`<a href="#component-grid">Components</a>`);
|
|
2965
|
+
const onThisPage = onThisPageParts.join("\n");
|
|
1186
2966
|
return htmlShell({
|
|
1187
2967
|
title: data.options.title,
|
|
1188
2968
|
body,
|
|
1189
2969
|
sidebar,
|
|
1190
2970
|
onThisPage,
|
|
1191
|
-
basePath: data.options.basePath
|
|
2971
|
+
basePath: data.options.basePath,
|
|
2972
|
+
searchItems: buildSearchItems(data)
|
|
1192
2973
|
});
|
|
1193
2974
|
}
|
|
1194
2975
|
|
|
@@ -1300,7 +3081,585 @@ ${data.complianceBatch ? '<a href="#compliance">Compliance</a>' : ""}`;
|
|
|
1300
3081
|
body,
|
|
1301
3082
|
sidebar,
|
|
1302
3083
|
onThisPage,
|
|
1303
|
-
basePath: data.options.basePath
|
|
3084
|
+
basePath: data.options.basePath,
|
|
3085
|
+
searchItems: buildSearchItems(data),
|
|
3086
|
+
breadcrumbs: [{ label: "Dashboard" }]
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
// src/templates/icon-browser.ts
|
|
3091
|
+
function isIconComponent(filePath, displayName, patterns) {
|
|
3092
|
+
return patterns.some((p) => matchGlob(p, filePath) || matchGlob(p, displayName));
|
|
3093
|
+
}
|
|
3094
|
+
function getIconSvg(name, data) {
|
|
3095
|
+
const render = data.renders.get(name);
|
|
3096
|
+
if (render?.svgContent) return render.svgContent;
|
|
3097
|
+
if (render?.dom?.tree) return domTreeToSvg(render.dom.tree);
|
|
3098
|
+
return void 0;
|
|
3099
|
+
}
|
|
3100
|
+
function generateJsxSnippet(name, component) {
|
|
3101
|
+
const ownProps = Object.entries(component.props).filter(([, p]) => p.source !== "inherited");
|
|
3102
|
+
const propStr = ownProps.filter(([, p]) => p.required).map(([pName, p]) => {
|
|
3103
|
+
if (p.type === "string") return `${pName}=""`;
|
|
3104
|
+
if (p.type === "number") return `${pName}={0}`;
|
|
3105
|
+
if (p.type === "boolean") return pName;
|
|
3106
|
+
return `${pName}={\u2026}`;
|
|
3107
|
+
}).join(" ");
|
|
3108
|
+
const propsSection = propStr ? ` ${propStr}` : "";
|
|
3109
|
+
return `import { ${name} } from "${component.filePath.replace(/\.tsx?$/, "")}";
|
|
3110
|
+
|
|
3111
|
+
<${name}${propsSection} />`;
|
|
3112
|
+
}
|
|
3113
|
+
function renderIconBrowser(data) {
|
|
3114
|
+
const patterns = data.options.iconPatterns;
|
|
3115
|
+
const allComponents = Object.entries(data.manifest.components);
|
|
3116
|
+
const icons = allComponents.filter(([, c]) => isIconComponent(c.filePath, c.displayName, patterns)).sort(([a], [b]) => a.localeCompare(b));
|
|
3117
|
+
const iconDataEntries = icons.map(([name, component]) => {
|
|
3118
|
+
const svg = getIconSvg(name, data);
|
|
3119
|
+
const svgBytes = svg ? new TextEncoder().encode(svg).length : 0;
|
|
3120
|
+
const slug = slugify(name);
|
|
3121
|
+
const keywords = component.keywords ?? [];
|
|
3122
|
+
const usedBy = component.composedBy ?? [];
|
|
3123
|
+
const jsx = generateJsxSnippet(name, component);
|
|
3124
|
+
const ownProps = Object.entries(component.props).filter(([, p]) => p.source !== "inherited").map(([pName, p]) => ({
|
|
3125
|
+
name: pName,
|
|
3126
|
+
type: p.type,
|
|
3127
|
+
rawType: p.rawType,
|
|
3128
|
+
required: p.required,
|
|
3129
|
+
default: p.default,
|
|
3130
|
+
values: p.values
|
|
3131
|
+
}));
|
|
3132
|
+
return {
|
|
3133
|
+
name,
|
|
3134
|
+
slug,
|
|
3135
|
+
svg: svg ?? "",
|
|
3136
|
+
svgBytes,
|
|
3137
|
+
keywords,
|
|
3138
|
+
usedBy,
|
|
3139
|
+
jsx,
|
|
3140
|
+
props: ownProps,
|
|
3141
|
+
filePath: component.filePath
|
|
3142
|
+
};
|
|
3143
|
+
});
|
|
3144
|
+
const iconCards = icons.map(([name]) => {
|
|
3145
|
+
const svg = getIconSvg(name, data);
|
|
3146
|
+
const keywords = (data.manifest.components[name]?.keywords ?? []).join(" ");
|
|
3147
|
+
const slug = slugify(name);
|
|
3148
|
+
const previewHtml = svg ? `<div class="icon-cell-svg">${svg}</div>` : `<span class="icon-no-preview">${escapeHtml(name.slice(0, 2))}</span>`;
|
|
3149
|
+
return `<button class="icon-cell" data-name="${escapeHtml(name.toLowerCase())}" data-slug="${slug}" data-keywords="${escapeHtml(keywords.toLowerCase())}" type="button">
|
|
3150
|
+
<div class="icon-cell-preview">${previewHtml}</div>
|
|
3151
|
+
<div class="icon-cell-name">${escapeHtml(name)}</div>
|
|
3152
|
+
</button>`;
|
|
3153
|
+
}).join("\n");
|
|
3154
|
+
const slideOutPanel = `<div class="icon-slideout-overlay" id="icon-slideout-overlay" hidden></div>
|
|
3155
|
+
<div class="icon-slideout" id="icon-slideout" hidden>
|
|
3156
|
+
<div class="icon-slideout-header">
|
|
3157
|
+
<h2 class="icon-slideout-name" id="icon-slideout-name"></h2>
|
|
3158
|
+
<button class="icon-slideout-close" id="icon-slideout-close" type="button" aria-label="Close">
|
|
3159
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
3160
|
+
</button>
|
|
3161
|
+
</div>
|
|
3162
|
+
|
|
3163
|
+
<div class="icon-slideout-body">
|
|
3164
|
+
<div class="icon-slideout-preview-lg" id="icon-slideout-preview-lg"></div>
|
|
3165
|
+
|
|
3166
|
+
<div class="icon-detail-section">
|
|
3167
|
+
<div class="icon-detail-section-title">Sizes</div>
|
|
3168
|
+
<div class="icon-size-row">
|
|
3169
|
+
<div class="icon-size-cell"><div class="icon-size-preview" id="icon-size-lg"></div><span>lg (48)</span></div>
|
|
3170
|
+
<div class="icon-size-cell"><div class="icon-size-preview" id="icon-size-md"></div><span>md (32)</span></div>
|
|
3171
|
+
<div class="icon-size-cell"><div class="icon-size-preview" id="icon-size-sm"></div><span>sm (24)</span></div>
|
|
3172
|
+
<div class="icon-size-cell"><div class="icon-size-preview" id="icon-size-xs"></div><span>xs (16)</span></div>
|
|
3173
|
+
</div>
|
|
3174
|
+
</div>
|
|
3175
|
+
|
|
3176
|
+
<div class="icon-detail-section">
|
|
3177
|
+
<div class="icon-slideout-actions">
|
|
3178
|
+
<a class="icon-action-btn icon-action-primary" id="icon-slideout-link" href="#">View component page</a>
|
|
3179
|
+
<button class="icon-action-btn" id="icon-copy-svg" type="button">Copy SVG</button>
|
|
3180
|
+
<button class="icon-action-btn" id="icon-copy-jsx" type="button">Copy JSX</button>
|
|
3181
|
+
</div>
|
|
3182
|
+
</div>
|
|
3183
|
+
|
|
3184
|
+
<div class="icon-detail-section">
|
|
3185
|
+
<div class="icon-detail-section-title">Compiled size</div>
|
|
3186
|
+
<div class="icon-compiled-size" id="icon-compiled-size"></div>
|
|
3187
|
+
</div>
|
|
3188
|
+
|
|
3189
|
+
<div class="icon-detail-section" id="icon-props-section" hidden>
|
|
3190
|
+
<div class="icon-detail-section-title">Props</div>
|
|
3191
|
+
<div id="icon-props-list"></div>
|
|
3192
|
+
</div>
|
|
3193
|
+
|
|
3194
|
+
<div class="icon-detail-section" id="icon-keywords-section" hidden>
|
|
3195
|
+
<div class="icon-detail-section-title">Keywords</div>
|
|
3196
|
+
<div class="icon-keywords" id="icon-keywords"></div>
|
|
3197
|
+
</div>
|
|
3198
|
+
|
|
3199
|
+
<div class="icon-detail-section" id="icon-usage-section" hidden>
|
|
3200
|
+
<div class="icon-detail-section-title">Used by</div>
|
|
3201
|
+
<div class="icon-usage-list" id="icon-usage-list"></div>
|
|
3202
|
+
</div>
|
|
3203
|
+
</div>
|
|
3204
|
+
</div>`;
|
|
3205
|
+
const body = `<div class="icon-browser">
|
|
3206
|
+
<div class="icon-browser-header">
|
|
3207
|
+
<h1>Icons</h1>
|
|
3208
|
+
<p>${icons.length} icon${icons.length === 1 ? "" : "s"}</p>
|
|
3209
|
+
<input class="search-box" type="search" id="icon-search" placeholder="Search icons\u2026" style="margin-top:12px" />
|
|
3210
|
+
</div>
|
|
3211
|
+
<div class="icon-grid" id="icon-grid">
|
|
3212
|
+
${iconCards}
|
|
3213
|
+
</div>
|
|
3214
|
+
</div>
|
|
3215
|
+
${slideOutPanel}
|
|
3216
|
+
<script>
|
|
3217
|
+
(function () {
|
|
3218
|
+
var ICON_DATA = ${JSON.stringify(iconDataEntries)};
|
|
3219
|
+
var grid = document.getElementById('icon-grid');
|
|
3220
|
+
var search = document.getElementById('icon-search');
|
|
3221
|
+
var slideout = document.getElementById('icon-slideout');
|
|
3222
|
+
var overlay = document.getElementById('icon-slideout-overlay');
|
|
3223
|
+
var closeBtn = document.getElementById('icon-slideout-close');
|
|
3224
|
+
var nameEl = document.getElementById('icon-slideout-name');
|
|
3225
|
+
var previewLg = document.getElementById('icon-slideout-preview-lg');
|
|
3226
|
+
var sizeLg = document.getElementById('icon-size-lg');
|
|
3227
|
+
var sizeMd = document.getElementById('icon-size-md');
|
|
3228
|
+
var sizeSm = document.getElementById('icon-size-sm');
|
|
3229
|
+
var sizeXs = document.getElementById('icon-size-xs');
|
|
3230
|
+
var linkEl = document.getElementById('icon-slideout-link');
|
|
3231
|
+
var copySvgBtn = document.getElementById('icon-copy-svg');
|
|
3232
|
+
var copyJsxBtn = document.getElementById('icon-copy-jsx');
|
|
3233
|
+
var compiledSizeEl = document.getElementById('icon-compiled-size');
|
|
3234
|
+
var propsSection = document.getElementById('icon-props-section');
|
|
3235
|
+
var propsList = document.getElementById('icon-props-list');
|
|
3236
|
+
var keywordsSection = document.getElementById('icon-keywords-section');
|
|
3237
|
+
var keywordsEl = document.getElementById('icon-keywords');
|
|
3238
|
+
var usageSection = document.getElementById('icon-usage-section');
|
|
3239
|
+
var usageList = document.getElementById('icon-usage-list');
|
|
3240
|
+
var basePath = ${JSON.stringify(data.options.basePath)};
|
|
3241
|
+
var currentData = null;
|
|
3242
|
+
|
|
3243
|
+
if (!grid) return;
|
|
3244
|
+
|
|
3245
|
+
function filter(q) {
|
|
3246
|
+
var cells = grid.querySelectorAll('.icon-cell');
|
|
3247
|
+
cells.forEach(function (cell) {
|
|
3248
|
+
var name = cell.getAttribute('data-name') || '';
|
|
3249
|
+
var kw = cell.getAttribute('data-keywords') || '';
|
|
3250
|
+
var show = name.includes(q) || kw.includes(q);
|
|
3251
|
+
cell.style.display = show ? '' : 'none';
|
|
3252
|
+
});
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
if (search) {
|
|
3256
|
+
search.addEventListener('input', function () { filter(search.value.toLowerCase()); });
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
function setSvgSize(container, svg, px) {
|
|
3260
|
+
if (!container || !svg) return;
|
|
3261
|
+
container.innerHTML = svg;
|
|
3262
|
+
var svgEl = container.querySelector('svg');
|
|
3263
|
+
if (svgEl) { svgEl.setAttribute('width', String(px)); svgEl.setAttribute('height', String(px)); }
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
function openSlideout(slug) {
|
|
3267
|
+
var iconData = ICON_DATA.find(function (d) { return d.slug === slug; });
|
|
3268
|
+
if (!iconData || !slideout) return;
|
|
3269
|
+
currentData = iconData;
|
|
3270
|
+
|
|
3271
|
+
if (nameEl) nameEl.textContent = iconData.name;
|
|
3272
|
+
if (previewLg) previewLg.innerHTML = iconData.svg;
|
|
3273
|
+
|
|
3274
|
+
setSvgSize(sizeLg, iconData.svg, 48);
|
|
3275
|
+
setSvgSize(sizeMd, iconData.svg, 32);
|
|
3276
|
+
setSvgSize(sizeSm, iconData.svg, 24);
|
|
3277
|
+
setSvgSize(sizeXs, iconData.svg, 16);
|
|
3278
|
+
|
|
3279
|
+
if (linkEl) linkEl.href = basePath + iconData.slug + '.html';
|
|
3280
|
+
|
|
3281
|
+
if (compiledSizeEl) {
|
|
3282
|
+
var bytes = iconData.svgBytes;
|
|
3283
|
+
var label = bytes < 1024 ? bytes + ' B' : (bytes / 1024).toFixed(1) + ' KB';
|
|
3284
|
+
compiledSizeEl.textContent = label;
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
if (propsSection && propsList) {
|
|
3288
|
+
if (iconData.props.length > 0) {
|
|
3289
|
+
propsSection.hidden = false;
|
|
3290
|
+
propsList.innerHTML = iconData.props.map(function (p) {
|
|
3291
|
+
var typeStr = p.values && p.values.length > 0 ? p.values.join(' | ') : p.rawType;
|
|
3292
|
+
var reqStr = p.required ? '<span class="icon-prop-required">required</span>' : '<span class="icon-prop-optional">optional</span>';
|
|
3293
|
+
var defStr = p.default ? ' <span class="icon-prop-default">' + p.default + '</span>' : '';
|
|
3294
|
+
return '<div class="icon-prop-row"><div class="icon-prop-name">' + p.name + '</div><div class="icon-prop-type">' + typeStr + '</div><div class="icon-prop-meta">' + reqStr + defStr + '</div></div>';
|
|
3295
|
+
}).join('');
|
|
3296
|
+
} else {
|
|
3297
|
+
propsSection.hidden = true;
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
if (keywordsSection && keywordsEl) {
|
|
3302
|
+
if (iconData.keywords.length > 0) {
|
|
3303
|
+
keywordsSection.hidden = false;
|
|
3304
|
+
keywordsEl.innerHTML = iconData.keywords.map(function (kw) {
|
|
3305
|
+
return '<span class="icon-keyword-tag">' + kw + '</span>';
|
|
3306
|
+
}).join('');
|
|
3307
|
+
} else {
|
|
3308
|
+
keywordsSection.hidden = true;
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
if (usageSection && usageList) {
|
|
3313
|
+
if (iconData.usedBy.length > 0) {
|
|
3314
|
+
usageSection.hidden = false;
|
|
3315
|
+
usageList.innerHTML = iconData.usedBy.map(function (u) {
|
|
3316
|
+
var uSlug = u.replace(/([A-Z])/g, function (m) { return '-' + m.toLowerCase(); }).replace(/^-/, '').replace(/[^a-z0-9-]/g, '-');
|
|
3317
|
+
return '<a class="icon-usage-link" href="' + basePath + uSlug + '.html">' + u + '</a>';
|
|
3318
|
+
}).join('');
|
|
3319
|
+
} else {
|
|
3320
|
+
usageSection.hidden = true;
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
grid.querySelectorAll('.icon-cell.selected').forEach(function (el) { el.classList.remove('selected'); });
|
|
3325
|
+
var activeCell = grid.querySelector('[data-slug="' + slug + '"]');
|
|
3326
|
+
if (activeCell) activeCell.classList.add('selected');
|
|
3327
|
+
|
|
3328
|
+
slideout.hidden = false;
|
|
3329
|
+
if (overlay) overlay.hidden = false;
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
function closeSlideout() {
|
|
3333
|
+
if (slideout) slideout.hidden = true;
|
|
3334
|
+
if (overlay) overlay.hidden = true;
|
|
3335
|
+
currentData = null;
|
|
3336
|
+
grid.querySelectorAll('.icon-cell.selected').forEach(function (el) { el.classList.remove('selected'); });
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
grid.addEventListener('click', function (e) {
|
|
3340
|
+
var cell = e.target.closest('.icon-cell');
|
|
3341
|
+
if (!cell) return;
|
|
3342
|
+
var slug = cell.getAttribute('data-slug');
|
|
3343
|
+
if (slug) openSlideout(slug);
|
|
3344
|
+
});
|
|
3345
|
+
|
|
3346
|
+
if (closeBtn) closeBtn.addEventListener('click', closeSlideout);
|
|
3347
|
+
if (overlay) overlay.addEventListener('click', closeSlideout);
|
|
3348
|
+
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeSlideout(); });
|
|
3349
|
+
|
|
3350
|
+
function copyToClipboard(text, btn) {
|
|
3351
|
+
navigator.clipboard.writeText(text).then(function () {
|
|
3352
|
+
var orig = btn.textContent;
|
|
3353
|
+
btn.textContent = 'Copied!';
|
|
3354
|
+
setTimeout(function () { btn.textContent = orig; }, 1500);
|
|
3355
|
+
});
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
if (copySvgBtn) {
|
|
3359
|
+
copySvgBtn.addEventListener('click', function () {
|
|
3360
|
+
if (currentData && currentData.svg) copyToClipboard(currentData.svg, copySvgBtn);
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
if (copyJsxBtn) {
|
|
3364
|
+
copyJsxBtn.addEventListener('click', function () {
|
|
3365
|
+
if (currentData && currentData.jsx) copyToClipboard(currentData.jsx, copyJsxBtn);
|
|
3366
|
+
});
|
|
3367
|
+
}
|
|
3368
|
+
})();
|
|
3369
|
+
</script>`;
|
|
3370
|
+
const sidebar = sidebarLinks(data, "icons");
|
|
3371
|
+
return htmlShell({
|
|
3372
|
+
title: `Icons \u2014 ${data.options.title}`,
|
|
3373
|
+
body,
|
|
3374
|
+
sidebar,
|
|
3375
|
+
onThisPage: '<a href="#top">Icon Browser</a>',
|
|
3376
|
+
basePath: data.options.basePath,
|
|
3377
|
+
searchItems: buildSearchItems(data),
|
|
3378
|
+
breadcrumbs: [{ label: "Icons" }]
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
// src/templates/token-browser.ts
|
|
3383
|
+
var HEX_RE = /^#(?:[0-9a-fA-F]{3,8})$/;
|
|
3384
|
+
var COLOR_FN_RE = /^(?:rgba?|hsla?|oklch|oklab|lch|lab|color|hwb)\(/;
|
|
3385
|
+
function isColorValue(value) {
|
|
3386
|
+
return HEX_RE.test(value) || COLOR_FN_RE.test(value);
|
|
3387
|
+
}
|
|
3388
|
+
function estimateLuminance(value) {
|
|
3389
|
+
const v = value.trim();
|
|
3390
|
+
if (HEX_RE.test(v)) {
|
|
3391
|
+
const c = v.replace("#", "");
|
|
3392
|
+
const full = c.length <= 4 ? c.slice(0, 3).split("").map((ch) => ch + ch).join("") : c.slice(0, 6);
|
|
3393
|
+
const r = parseInt(full.slice(0, 2), 16);
|
|
3394
|
+
const g = parseInt(full.slice(2, 4), 16);
|
|
3395
|
+
const b = parseInt(full.slice(4, 6), 16);
|
|
3396
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return null;
|
|
3397
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
3398
|
+
}
|
|
3399
|
+
const rgbMatch = v.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/);
|
|
3400
|
+
if (rgbMatch) {
|
|
3401
|
+
const r = parseFloat(rgbMatch[1] ?? "0");
|
|
3402
|
+
const g = parseFloat(rgbMatch[2] ?? "0");
|
|
3403
|
+
const b = parseFloat(rgbMatch[3] ?? "0");
|
|
3404
|
+
const alphaMatch = v.match(/,\s*([\d.]+)\s*\)$/);
|
|
3405
|
+
const alpha = alphaMatch ? parseFloat(alphaMatch[1] ?? "1") : 1;
|
|
3406
|
+
const blendR = r * alpha + 255 * (1 - alpha);
|
|
3407
|
+
const blendG = g * alpha + 255 * (1 - alpha);
|
|
3408
|
+
const blendB = b * alpha + 255 * (1 - alpha);
|
|
3409
|
+
return (0.299 * blendR + 0.587 * blendG + 0.114 * blendB) / 255;
|
|
3410
|
+
}
|
|
3411
|
+
const oklchMatch = v.match(/^oklch\(\s*([\d.]+)/);
|
|
3412
|
+
if (oklchMatch) {
|
|
3413
|
+
const L = parseFloat(oklchMatch[1] ?? "0.5");
|
|
3414
|
+
return L;
|
|
3415
|
+
}
|
|
3416
|
+
const oklabMatch = v.match(/^oklab\(\s*([\d.]+)/);
|
|
3417
|
+
if (oklabMatch) {
|
|
3418
|
+
return parseFloat(oklabMatch[1] ?? "0.5");
|
|
3419
|
+
}
|
|
3420
|
+
const hslMatch = v.match(/^hsla?\(\s*[\d.]+\s*,\s*[\d.]+%\s*,\s*([\d.]+)%/);
|
|
3421
|
+
if (hslMatch) {
|
|
3422
|
+
return parseFloat(hslMatch[1] ?? "50") / 100;
|
|
3423
|
+
}
|
|
3424
|
+
const colorMixMatch = v.match(/^color-mix\(.*black\s+([\d.]+)%/);
|
|
3425
|
+
if (colorMixMatch) {
|
|
3426
|
+
const blackPct = parseFloat(colorMixMatch[1] ?? "50") / 100;
|
|
3427
|
+
return 1 - blackPct;
|
|
3428
|
+
}
|
|
3429
|
+
return null;
|
|
3430
|
+
}
|
|
3431
|
+
function contrastColor(value) {
|
|
3432
|
+
const lum = estimateLuminance(value);
|
|
3433
|
+
if (lum === null) return "#fff";
|
|
3434
|
+
return lum > 0.55 ? "#000" : "#fff";
|
|
3435
|
+
}
|
|
3436
|
+
function groupByTopLevel(tokens) {
|
|
3437
|
+
const map = /* @__PURE__ */ new Map();
|
|
3438
|
+
for (const token of tokens) {
|
|
3439
|
+
const top = token.path.split(".")[0] ?? "other";
|
|
3440
|
+
if (!map.has(top)) map.set(top, []);
|
|
3441
|
+
map.get(top)?.push(token);
|
|
3442
|
+
}
|
|
3443
|
+
return Array.from(map.entries()).map(([name, toks]) => ({ name, tokens: toks }));
|
|
3444
|
+
}
|
|
3445
|
+
function renderSwatch(token, darkValue) {
|
|
3446
|
+
const label = token.path.split(".").pop() ?? "";
|
|
3447
|
+
const fg = contrastColor(token.value);
|
|
3448
|
+
const darkHtml = darkValue ? (() => {
|
|
3449
|
+
const darkFg = contrastColor(darkValue);
|
|
3450
|
+
return `<div class="tb-swatch-dark" style="background:${escapeHtml(darkValue)};color:${darkFg}" title="dark: ${escapeHtml(darkValue)}">
|
|
3451
|
+
<span class="tb-swatch-value">${escapeHtml(darkValue)}</span>
|
|
3452
|
+
</div>`;
|
|
3453
|
+
})() : "";
|
|
3454
|
+
return `<div class="tb-swatch-pair" title="${escapeHtml(token.path)}: ${escapeHtml(token.value)}">
|
|
3455
|
+
<div class="tb-swatch" style="background:${escapeHtml(token.value)};color:${fg}">
|
|
3456
|
+
<span class="tb-swatch-label">${escapeHtml(label)}</span>
|
|
3457
|
+
<span class="tb-swatch-value">${escapeHtml(token.value)}</span>
|
|
3458
|
+
</div>${darkHtml}
|
|
3459
|
+
</div>`;
|
|
3460
|
+
}
|
|
3461
|
+
function renderColorGroup(group, darkOverrides) {
|
|
3462
|
+
const subgroups = /* @__PURE__ */ new Map();
|
|
3463
|
+
const standalone = [];
|
|
3464
|
+
for (const token of group.tokens) {
|
|
3465
|
+
const parts = token.path.split(".");
|
|
3466
|
+
if (parts.length >= 3) {
|
|
3467
|
+
const sub = parts.slice(0, 2).join(".");
|
|
3468
|
+
if (!subgroups.has(sub)) subgroups.set(sub, []);
|
|
3469
|
+
subgroups.get(sub)?.push(token);
|
|
3470
|
+
} else {
|
|
3471
|
+
standalone.push(token);
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
const scaleHtml = Array.from(subgroups.entries()).map(([scaleName, tokens]) => {
|
|
3475
|
+
const hasDark = tokens.some((t) => darkOverrides?.[t.path] !== void 0);
|
|
3476
|
+
const swatches = tokens.map((t) => renderSwatch(t, darkOverrides?.[t.path])).join("\n");
|
|
3477
|
+
const displayName = scaleName.split(".").pop() ?? scaleName;
|
|
3478
|
+
const darkLabel = hasDark ? `<span class="tb-scale-badge">dark</span>` : "";
|
|
3479
|
+
return `<div class="tb-scale">
|
|
3480
|
+
<div class="tb-scale-name">${escapeHtml(displayName)}${darkLabel}</div>
|
|
3481
|
+
<div class="tb-scale-row">${swatches}</div>
|
|
3482
|
+
</div>`;
|
|
3483
|
+
}).join("\n");
|
|
3484
|
+
const standaloneHtml = standalone.length > 0 ? `<div class="tb-standalone-colors">${standalone.map((t) => {
|
|
3485
|
+
const label = t.path.split(".").pop() ?? t.path;
|
|
3486
|
+
const darkVal = darkOverrides?.[t.path];
|
|
3487
|
+
const fg = contrastColor(t.value);
|
|
3488
|
+
let darkPart = "";
|
|
3489
|
+
if (darkVal) {
|
|
3490
|
+
const darkFg = contrastColor(darkVal);
|
|
3491
|
+
darkPart = `<div class="tb-swatch-dark-single" style="background:${escapeHtml(darkVal)};color:${darkFg}" title="dark: ${escapeHtml(darkVal)}"><span class="tb-swatch-value">${escapeHtml(darkVal)}</span></div>`;
|
|
3492
|
+
}
|
|
3493
|
+
return `<div class="tb-swatch-pair-single">
|
|
3494
|
+
<div class="tb-swatch tb-swatch-single" style="background:${escapeHtml(t.value)};color:${fg}" title="${escapeHtml(t.path)}: ${escapeHtml(t.value)}">
|
|
3495
|
+
<span class="tb-swatch-label">${escapeHtml(label)}</span>
|
|
3496
|
+
<span class="tb-swatch-value">${escapeHtml(t.value)}</span>
|
|
3497
|
+
</div>${darkPart}
|
|
3498
|
+
</div>`;
|
|
3499
|
+
}).join("\n")}</div>` : "";
|
|
3500
|
+
return `<div class="tb-section" id="tb-${escapeHtml(group.name)}">
|
|
3501
|
+
<h2 class="tb-section-title">${escapeHtml(group.name)}</h2>
|
|
3502
|
+
${scaleHtml}${standaloneHtml}
|
|
3503
|
+
</div>`;
|
|
3504
|
+
}
|
|
3505
|
+
function renderSpacingGroup(group) {
|
|
3506
|
+
const rows = group.tokens.map((t) => {
|
|
3507
|
+
const label = t.path.split(".").slice(1).join(".") || t.path;
|
|
3508
|
+
return `<div class="tb-spacing-row">
|
|
3509
|
+
<div class="tb-spacing-label">${escapeHtml(label)}</div>
|
|
3510
|
+
<div class="tb-spacing-value">${escapeHtml(t.value)}</div>
|
|
3511
|
+
<div class="tb-spacing-bar-wrapper"><div class="tb-spacing-bar" style="width:min(${escapeHtml(t.value)}, 100%)"></div></div>
|
|
3512
|
+
</div>`;
|
|
3513
|
+
}).join("\n");
|
|
3514
|
+
return `<div class="tb-section" id="tb-${escapeHtml(group.name)}">
|
|
3515
|
+
<h2 class="tb-section-title">${escapeHtml(group.name)}</h2>
|
|
3516
|
+
${rows}
|
|
3517
|
+
</div>`;
|
|
3518
|
+
}
|
|
3519
|
+
function renderTypographyGroup(group) {
|
|
3520
|
+
const rows = group.tokens.map((t) => {
|
|
3521
|
+
const label = t.path.split(".").slice(1).join(".") || t.path;
|
|
3522
|
+
const sampleStyle = t.type === "fontFamily" ? `font-family:${escapeHtml(t.value)}` : t.type === "dimension" ? `font-size:${escapeHtml(t.value)}` : "";
|
|
3523
|
+
return `<div class="tb-typo-row">
|
|
3524
|
+
<div class="tb-typo-label">${escapeHtml(label)}</div>
|
|
3525
|
+
<div class="tb-typo-value">${escapeHtml(t.value)}</div>
|
|
3526
|
+
${sampleStyle ? `<div class="tb-typo-sample" style="${sampleStyle}">Aa</div>` : ""}
|
|
3527
|
+
</div>`;
|
|
3528
|
+
}).join("\n");
|
|
3529
|
+
return `<div class="tb-section" id="tb-${escapeHtml(group.name)}">
|
|
3530
|
+
<h2 class="tb-section-title">${escapeHtml(group.name)}</h2>
|
|
3531
|
+
${rows}
|
|
3532
|
+
</div>`;
|
|
3533
|
+
}
|
|
3534
|
+
function renderGenericGroup(group, darkOverrides) {
|
|
3535
|
+
const isAllColors = group.tokens.every((t) => isColorValue(t.value));
|
|
3536
|
+
if (isAllColors) return renderColorGroup(group, darkOverrides);
|
|
3537
|
+
const hasDarkColumn = group.tokens.some((t) => darkOverrides?.[t.path] !== void 0);
|
|
3538
|
+
const rows = group.tokens.map((t) => {
|
|
3539
|
+
const label = t.path.split(".").slice(1).join(".") || t.path;
|
|
3540
|
+
const swatchHtml = isColorValue(t.value) ? `<span class="token-value-swatch" style="background:${escapeHtml(t.value)}"></span>` : "";
|
|
3541
|
+
const darkVal = darkOverrides?.[t.path];
|
|
3542
|
+
const darkCell = hasDarkColumn ? `<td class="tb-generic-value">${darkVal ? `${isColorValue(darkVal) ? `<span class="token-value-swatch" style="background:${escapeHtml(darkVal)}"></span>` : ""}${escapeHtml(darkVal)}` : `<span style="color:var(--color-muted)">\u2014</span>`}</td>` : "";
|
|
3543
|
+
return `<tr>
|
|
3544
|
+
<td class="tb-generic-path">${escapeHtml(label)}</td>
|
|
3545
|
+
<td class="tb-generic-value">${swatchHtml}${escapeHtml(t.value)}</td>
|
|
3546
|
+
${darkCell}
|
|
3547
|
+
<td class="tb-generic-type">${escapeHtml(t.type)}</td>
|
|
3548
|
+
</tr>`;
|
|
3549
|
+
}).join("\n");
|
|
3550
|
+
const darkHeader = hasDarkColumn ? "<th>Dark</th>" : "";
|
|
3551
|
+
return `<div class="tb-section" id="tb-${escapeHtml(group.name)}">
|
|
3552
|
+
<h2 class="tb-section-title">${escapeHtml(group.name)}</h2>
|
|
3553
|
+
<table class="tb-generic-table">
|
|
3554
|
+
<thead><tr><th>Token</th><th>Value</th>${darkHeader}<th>Type</th></tr></thead>
|
|
3555
|
+
<tbody>${rows}</tbody>
|
|
3556
|
+
</table>
|
|
3557
|
+
</div>`;
|
|
3558
|
+
}
|
|
3559
|
+
function renderGroup(group, darkOverrides) {
|
|
3560
|
+
const allTypes = new Set(group.tokens.map((t) => t.type));
|
|
3561
|
+
const allColors = group.tokens.every((t) => t.type === "color" || isColorValue(t.value));
|
|
3562
|
+
const allDimensions = group.tokens.every((t) => t.type === "dimension");
|
|
3563
|
+
const hasTypography = allTypes.has("fontFamily") || allTypes.has("fontWeight");
|
|
3564
|
+
if (allColors && group.tokens.length > 0) return renderColorGroup(group, darkOverrides);
|
|
3565
|
+
if (allDimensions && group.tokens.length > 0) return renderSpacingGroup(group);
|
|
3566
|
+
if (hasTypography) return renderTypographyGroup(group);
|
|
3567
|
+
return renderGenericGroup(group, darkOverrides);
|
|
3568
|
+
}
|
|
3569
|
+
function renderTokenBrowser(data) {
|
|
3570
|
+
const tokens = data.tokenEntries ?? [];
|
|
3571
|
+
if (tokens.length === 0) {
|
|
3572
|
+
const body2 = `<div class="tb-header">
|
|
3573
|
+
<h1>Design Tokens</h1>
|
|
3574
|
+
<p>No tokens loaded. Run <code>scope tokens init</code> to generate a token file, then pass <code>--tokens</code> to <code>scope site build</code>.</p>
|
|
3575
|
+
</div>`;
|
|
3576
|
+
return htmlShell({
|
|
3577
|
+
title: `Tokens \u2014 ${data.options.title}`,
|
|
3578
|
+
body: body2,
|
|
3579
|
+
sidebar: sidebarLinks(data, "tokens"),
|
|
3580
|
+
onThisPage: "",
|
|
3581
|
+
basePath: data.options.basePath,
|
|
3582
|
+
searchItems: buildSearchItems(data),
|
|
3583
|
+
breadcrumbs: [{ label: "Tokens" }]
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
3586
|
+
const groups = groupByTopLevel(tokens);
|
|
3587
|
+
const themes = data.tokenThemes ?? {};
|
|
3588
|
+
const darkOverrides = {
|
|
3589
|
+
...themes["dark"],
|
|
3590
|
+
...themes["dark-high-contrast"]
|
|
3591
|
+
};
|
|
3592
|
+
const darkCount = Object.keys(darkOverrides).length;
|
|
3593
|
+
const colorGroups = groups.filter(
|
|
3594
|
+
(g) => g.tokens.every((t) => t.type === "color" || isColorValue(t.value))
|
|
3595
|
+
);
|
|
3596
|
+
const otherGroups = groups.filter(
|
|
3597
|
+
(g) => !g.tokens.every((t) => t.type === "color" || isColorValue(t.value))
|
|
3598
|
+
);
|
|
3599
|
+
const totalColors = colorGroups.reduce((sum, g) => sum + g.tokens.length, 0);
|
|
3600
|
+
const totalOther = otherGroups.reduce((sum, g) => sum + g.tokens.length, 0);
|
|
3601
|
+
const meta = data.tokenMeta;
|
|
3602
|
+
const subtitle = meta?.name ? ` \u2014 ${escapeHtml(meta.name)}` : "";
|
|
3603
|
+
const lastUpdated = meta?.lastUpdated ? `<span class="tb-updated">Last updated: ${escapeHtml(meta.lastUpdated)}</span>` : "";
|
|
3604
|
+
const darkStatCard = darkCount > 0 ? `<div class="stat-card"><div class="stat-label">Dark Overrides</div><div class="stat-value">${darkCount}</div></div>` : "";
|
|
3605
|
+
const statsHtml = `<div class="stats-grid" style="margin-bottom:32px">
|
|
3606
|
+
<div class="stat-card"><div class="stat-label">Total Tokens</div><div class="stat-value">${tokens.length}</div></div>
|
|
3607
|
+
<div class="stat-card"><div class="stat-label">Colors</div><div class="stat-value">${totalColors}</div></div>
|
|
3608
|
+
<div class="stat-card"><div class="stat-label">Other</div><div class="stat-value">${totalOther}</div></div>
|
|
3609
|
+
<div class="stat-card"><div class="stat-label">Groups</div><div class="stat-value">${groups.length}</div></div>
|
|
3610
|
+
${darkStatCard}
|
|
3611
|
+
</div>`;
|
|
3612
|
+
const searchHtml = `<input class="search-box" type="search" id="tb-search" placeholder="Search tokens\u2026" style="margin-bottom:24px;width:100%;max-width:400px" />`;
|
|
3613
|
+
const sectionsHtml = groups.map((g) => renderGroup(g, darkCount > 0 ? darkOverrides : void 0)).join("\n");
|
|
3614
|
+
const onThisPage = groups.map(
|
|
3615
|
+
(g) => `<a href="#tb-${escapeHtml(g.name)}">${escapeHtml(g.name)} <span style="opacity:0.5;font-size:10px">(${g.tokens.length})</span></a>`
|
|
3616
|
+
).join("\n");
|
|
3617
|
+
const body = `<div class="tb-header">
|
|
3618
|
+
<h1>Design Tokens${subtitle}</h1>
|
|
3619
|
+
<p>${tokens.length} tokens across ${groups.length} groups${lastUpdated ? ` \xB7 ${lastUpdated}` : ""}</p>
|
|
3620
|
+
</div>
|
|
3621
|
+
${statsHtml}
|
|
3622
|
+
${searchHtml}
|
|
3623
|
+
<div id="tb-sections">
|
|
3624
|
+
${sectionsHtml}
|
|
3625
|
+
</div>
|
|
3626
|
+
<script>
|
|
3627
|
+
(function () {
|
|
3628
|
+
var search = document.getElementById('tb-search');
|
|
3629
|
+
var sections = document.getElementById('tb-sections');
|
|
3630
|
+
if (!search || !sections) return;
|
|
3631
|
+
|
|
3632
|
+
search.addEventListener('input', function () {
|
|
3633
|
+
var q = search.value.toLowerCase();
|
|
3634
|
+
var swatches = sections.querySelectorAll('.tb-swatch, .tb-spacing-row, .tb-typo-row, .tb-generic-table tr');
|
|
3635
|
+
var sectionEls = sections.querySelectorAll('.tb-section');
|
|
3636
|
+
|
|
3637
|
+
if (!q) {
|
|
3638
|
+
swatches.forEach(function (el) { el.style.display = ''; });
|
|
3639
|
+
sectionEls.forEach(function (el) { el.style.display = ''; });
|
|
3640
|
+
return;
|
|
3641
|
+
}
|
|
3642
|
+
|
|
3643
|
+
swatches.forEach(function (el) {
|
|
3644
|
+
var title = (el.getAttribute('title') || el.textContent || '').toLowerCase();
|
|
3645
|
+
el.style.display = title.includes(q) ? '' : 'none';
|
|
3646
|
+
});
|
|
3647
|
+
|
|
3648
|
+
sectionEls.forEach(function (sec) {
|
|
3649
|
+
var visible = sec.querySelectorAll('.tb-swatch:not([style*="display: none"]), .tb-spacing-row:not([style*="display: none"]), .tb-typo-row:not([style*="display: none"]), .tb-generic-table tr:not([style*="display: none"])');
|
|
3650
|
+
sec.style.display = visible.length > 0 ? '' : 'none';
|
|
3651
|
+
});
|
|
3652
|
+
});
|
|
3653
|
+
})();
|
|
3654
|
+
</script>`;
|
|
3655
|
+
return htmlShell({
|
|
3656
|
+
title: `Tokens \u2014 ${data.options.title}`,
|
|
3657
|
+
body,
|
|
3658
|
+
sidebar: sidebarLinks(data, "tokens"),
|
|
3659
|
+
onThisPage,
|
|
3660
|
+
basePath: data.options.basePath,
|
|
3661
|
+
searchItems: buildSearchItems(data),
|
|
3662
|
+
breadcrumbs: [{ label: "Tokens" }]
|
|
1304
3663
|
});
|
|
1305
3664
|
}
|
|
1306
3665
|
|
|
@@ -1325,8 +3684,21 @@ async function buildSite(options) {
|
|
|
1325
3684
|
const dashboardHtml = renderDashboard(data);
|
|
1326
3685
|
await writeFile(join(normalizedOptions.outputDir, "dashboard.html"), dashboardHtml, "utf-8");
|
|
1327
3686
|
console.log(`[scope/site] \u2713 dashboard.html`);
|
|
3687
|
+
let extraPages = 0;
|
|
3688
|
+
if (normalizedOptions.iconPatterns.length > 0) {
|
|
3689
|
+
const iconsHtml = renderIconBrowser(data);
|
|
3690
|
+
await writeFile(join(normalizedOptions.outputDir, "icons.html"), iconsHtml, "utf-8");
|
|
3691
|
+
console.log(`[scope/site] \u2713 icons.html`);
|
|
3692
|
+
extraPages++;
|
|
3693
|
+
}
|
|
3694
|
+
if (data.tokenEntries && data.tokenEntries.length > 0) {
|
|
3695
|
+
const tokensHtml = renderTokenBrowser(data);
|
|
3696
|
+
await writeFile(join(normalizedOptions.outputDir, "tokens.html"), tokensHtml, "utf-8");
|
|
3697
|
+
console.log(`[scope/site] \u2713 tokens.html (${data.tokenEntries.length} tokens)`);
|
|
3698
|
+
extraPages++;
|
|
3699
|
+
}
|
|
1328
3700
|
console.log(
|
|
1329
|
-
`[scope/site] Done. Site written to ${normalizedOptions.outputDir} (${componentNames.length + 2} files).`
|
|
3701
|
+
`[scope/site] Done. Site written to ${normalizedOptions.outputDir} (${componentNames.length + 2 + extraPages} files).`
|
|
1330
3702
|
);
|
|
1331
3703
|
}
|
|
1332
3704
|
|