@brickflow/ui 0.0.27 → 0.0.29

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.
Files changed (52) hide show
  1. package/dist/module.d.mts +4 -1
  2. package/dist/module.json +2 -2
  3. package/dist/module.mjs +409 -11
  4. package/dist/runtime/components/Button/icons.demo.d.vue.ts +8 -0
  5. package/dist/runtime/components/Button/icons.demo.vue +25 -0
  6. package/dist/runtime/components/Button/icons.demo.vue.d.ts +8 -0
  7. package/dist/runtime/components/Button/index.d.vue.ts +45 -0
  8. package/dist/runtime/components/Button/index.vue +162 -0
  9. package/dist/runtime/components/Button/index.vue.d.ts +45 -0
  10. package/dist/runtime/components/Button/size.demo.d.vue.ts +7 -0
  11. package/dist/runtime/components/Button/size.demo.vue +78 -0
  12. package/dist/runtime/components/Button/size.demo.vue.d.ts +7 -0
  13. package/dist/runtime/components/Button/slots.demo.d.vue.ts +7 -0
  14. package/dist/runtime/components/Button/slots.demo.vue +23 -0
  15. package/dist/runtime/components/Button/slots.demo.vue.d.ts +7 -0
  16. package/dist/runtime/components/Button/state.demo.d.vue.ts +7 -0
  17. package/dist/runtime/components/Button/state.demo.vue +18 -0
  18. package/dist/runtime/components/Button/state.demo.vue.d.ts +7 -0
  19. package/dist/runtime/components/Button/variants.demo.d.vue.ts +7 -0
  20. package/dist/runtime/components/Button/variants.demo.vue +207 -0
  21. package/dist/runtime/components/Button/variants.demo.vue.d.ts +7 -0
  22. package/dist/runtime/components/Icon/basic.demo.d.vue.ts +8 -0
  23. package/dist/runtime/components/Icon/basic.demo.vue +44 -0
  24. package/dist/runtime/components/Icon/basic.demo.vue.d.ts +8 -0
  25. package/dist/runtime/components/Icon/index.d.vue.ts +7 -0
  26. package/dist/runtime/components/Icon/index.vue +18 -0
  27. package/dist/runtime/components/Icon/index.vue.d.ts +7 -0
  28. package/dist/runtime/components/Link/basic.demo.d.vue.ts +8 -0
  29. package/dist/runtime/components/Link/basic.demo.vue +27 -0
  30. package/dist/runtime/components/Link/basic.demo.vue.d.ts +8 -0
  31. package/dist/runtime/components/Link/index.d.vue.ts +51 -0
  32. package/dist/runtime/components/Link/index.vue +62 -0
  33. package/dist/runtime/components/Link/index.vue.d.ts +51 -0
  34. package/dist/runtime/composables/useScreen.d.ts +18 -0
  35. package/dist/runtime/composables/useScreen.js +68 -0
  36. package/dist/runtime/composables/useTheme.d.ts +17 -0
  37. package/dist/runtime/composables/useTheme.js +77 -0
  38. package/dist/runtime/composables/useTranslate.d.ts +20 -0
  39. package/dist/runtime/composables/useTranslate.js +17 -0
  40. package/dist/runtime/icon-font.d.ts +4 -0
  41. package/dist/runtime/pages/ui.d.vue.ts +4 -0
  42. package/dist/runtime/pages/ui.vue +717 -0
  43. package/dist/runtime/pages/ui.vue.d.ts +4 -0
  44. package/dist/runtime/ui-catalog.d.ts +39 -0
  45. package/dist/runtime/ui-options.d.ts +3 -0
  46. package/dist/types.d.mts +2 -0
  47. package/package.json +10 -10
  48. package/dist/runtime/components/Button.d.vue.ts +0 -23
  49. package/dist/runtime/components/Button.vue +0 -29
  50. package/dist/runtime/components/Button.vue.d.ts +0 -23
  51. package/dist/runtime/composables/useBrickme.d.ts +0 -9
  52. package/dist/runtime/composables/useBrickme.js +0 -14
@@ -0,0 +1,717 @@
1
+ <script setup>
2
+ import { useHead, useRoute, useRouter } from "nuxt/app";
3
+ import { computed, nextTick, ref, watch } from "vue";
4
+ import { uiComponents } from "#brickflow-ui-catalog";
5
+ import { uiThemeEnabled } from "#brickflow-ui-options";
6
+ import { useTheme } from "../composables/useTheme";
7
+ useHead({
8
+ meta: [
9
+ {
10
+ content: "noindex, nofollow, noarchive",
11
+ name: "robots"
12
+ }
13
+ ],
14
+ title: "UI playground"
15
+ });
16
+ const route = useRoute();
17
+ const router = useRouter();
18
+ const expandedDemoCode = ref(/* @__PURE__ */ new Set());
19
+ const isNavigationOpen = ref(false);
20
+ const mobileNavigation = ref();
21
+ const stylesExpanded = ref(false);
22
+ const theme = uiThemeEnabled ? useTheme() : void 0;
23
+ const isDark = computed(() => theme?.isDark.value ?? false);
24
+ const toggleTheme = () => theme?.toggleTheme();
25
+ const activeComponent = computed(() => {
26
+ const component = route.query.component;
27
+ const id = typeof component === "string" ? component : void 0;
28
+ return uiComponents.find((item) => item.id === id) ?? uiComponents[0];
29
+ });
30
+ const visibleStyles = computed(() => {
31
+ const styles = activeComponent.value?.styles ?? [];
32
+ return stylesExpanded.value ? styles : styles.slice(0, 5);
33
+ });
34
+ const getDemoCodeKey = (demoId) => `${activeComponent.value?.id}:${demoId}`;
35
+ const escapeHtml = (value) => value.replace(
36
+ /[&<>"']/g,
37
+ (character) => ({
38
+ '"': "&quot;",
39
+ "&": "&amp;",
40
+ "'": "&#39;",
41
+ "<": "&lt;",
42
+ ">": "&gt;"
43
+ })[character] ?? character
44
+ );
45
+ const token = (className, value) => `<span class="${className}">${value}</span>`;
46
+ const highlightTypeScript = (source) => {
47
+ const pattern = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|`(?:\\[\s\S]|[^`])*`|'(?:\\[\s\S]|[^'])*'|"(?:\\[\s\S]|[^"])*"|\b(?:as|async|await|const|export|from|function|import|interface|let|return|type)\b|\b\d+(?:\.\d+)?\b/g;
48
+ let html = "";
49
+ let lastIndex = 0;
50
+ for (const match of source.matchAll(pattern)) {
51
+ const index = match.index ?? 0;
52
+ const value = match[0];
53
+ html += escapeHtml(source.slice(lastIndex, index));
54
+ if (value.startsWith("//") || value.startsWith("/*")) {
55
+ html += token("ui-code-comment", escapeHtml(value));
56
+ } else if (value.startsWith("'") || value.startsWith('"') || value.startsWith("`")) {
57
+ html += token("ui-code-string", escapeHtml(value));
58
+ } else if (/^\d/.test(value)) {
59
+ html += token("ui-code-number", value);
60
+ } else {
61
+ html += token("ui-code-keyword", value);
62
+ }
63
+ lastIndex = index + value.length;
64
+ }
65
+ return html + escapeHtml(source.slice(lastIndex));
66
+ };
67
+ const highlightPropType = (source) => {
68
+ const pattern = /`(?:\\[\s\S]|[^`])*`|'(?:\\[\s\S]|[^'])*'|"(?:\\[\s\S]|[^"])*"|\b(?:any|bigint|boolean|never|null|number|object|string|symbol|undefined|unknown|void)\b|\b[A-Z]\w*\b|\|/g;
69
+ let html = "";
70
+ let lastIndex = 0;
71
+ for (const match of source.matchAll(pattern)) {
72
+ const index = match.index ?? 0;
73
+ const value = match[0];
74
+ html += escapeHtml(source.slice(lastIndex, index));
75
+ if (value.startsWith("'") || value.startsWith('"') || value.startsWith("`")) {
76
+ html += token("ui-code-string", escapeHtml(value));
77
+ } else if (value === "|") {
78
+ html += token("ui-code-punctuation", value);
79
+ } else if (/^[A-Z]/.test(value)) {
80
+ html += token("ui-code-tag", escapeHtml(value));
81
+ } else {
82
+ html += token("ui-code-keyword", value);
83
+ }
84
+ lastIndex = index + value.length;
85
+ }
86
+ return html + escapeHtml(source.slice(lastIndex));
87
+ };
88
+ const highlightTag = (source) => {
89
+ const opening = source.match(/^<(\/)?([\w.-]+)/);
90
+ const closing = source.match(/\/?>\s*$/);
91
+ if (!opening || !closing) {
92
+ return escapeHtml(source);
93
+ }
94
+ const attributes = source.slice(opening[0].length, source.length - closing[0].length);
95
+ let html = token("ui-code-punctuation", "&lt;");
96
+ let attributeIndex = 0;
97
+ if (opening[1]) {
98
+ html += token("ui-code-punctuation", "/");
99
+ }
100
+ html += token("ui-code-tag", escapeHtml(opening[2] ?? ""));
101
+ while (attributeIndex < attributes.length) {
102
+ const character = attributes[attributeIndex] ?? "";
103
+ if (/\s/.test(character)) {
104
+ html += escapeHtml(character);
105
+ attributeIndex += 1;
106
+ continue;
107
+ }
108
+ const nameStart = attributeIndex;
109
+ while (attributeIndex < attributes.length && !/[\s=]/.test(attributes[attributeIndex] ?? "")) {
110
+ attributeIndex += 1;
111
+ }
112
+ const name = attributes.slice(nameStart, attributeIndex);
113
+ html += token("ui-code-attribute", escapeHtml(name));
114
+ const whitespaceStart = attributeIndex;
115
+ while (/\s/.test(attributes[attributeIndex] ?? "")) {
116
+ attributeIndex += 1;
117
+ }
118
+ html += escapeHtml(attributes.slice(whitespaceStart, attributeIndex));
119
+ if (attributes[attributeIndex] !== "=") {
120
+ continue;
121
+ }
122
+ html += token("ui-code-punctuation", "=");
123
+ attributeIndex += 1;
124
+ const valueWhitespaceStart = attributeIndex;
125
+ while (/\s/.test(attributes[attributeIndex] ?? "")) {
126
+ attributeIndex += 1;
127
+ }
128
+ html += escapeHtml(attributes.slice(valueWhitespaceStart, attributeIndex));
129
+ const quote = attributes[attributeIndex];
130
+ const valueStart = attributeIndex;
131
+ if (quote === '"' || quote === "'") {
132
+ attributeIndex += 1;
133
+ while (attributeIndex < attributes.length && attributes[attributeIndex] !== quote) {
134
+ attributeIndex += attributes[attributeIndex] === "\\" ? 2 : 1;
135
+ }
136
+ attributeIndex += 1;
137
+ } else {
138
+ while (attributeIndex < attributes.length && !/\s/.test(attributes[attributeIndex] ?? "")) {
139
+ attributeIndex += 1;
140
+ }
141
+ }
142
+ html += token("ui-code-string", escapeHtml(attributes.slice(valueStart, attributeIndex)));
143
+ }
144
+ html += token("ui-code-punctuation", escapeHtml(closing[0]));
145
+ return html;
146
+ };
147
+ const highlightTemplate = (source) => {
148
+ let html = "";
149
+ let index = 0;
150
+ while (index < source.length) {
151
+ const commentStart = source.indexOf("<!--", index);
152
+ const interpolationStart = source.indexOf("{{", index);
153
+ const tagStart = source.indexOf("<", index);
154
+ const starts = [commentStart, interpolationStart, tagStart].filter((start2) => start2 !== -1);
155
+ const start = Math.min(...starts);
156
+ if (!Number.isFinite(start)) {
157
+ return html + escapeHtml(source.slice(index));
158
+ }
159
+ html += escapeHtml(source.slice(index, start));
160
+ if (start === commentStart) {
161
+ const end = source.indexOf("-->", start + 4);
162
+ if (end === -1) {
163
+ return html + token("ui-code-comment", escapeHtml(source.slice(start)));
164
+ }
165
+ html += token("ui-code-comment", escapeHtml(source.slice(start, end + 3)));
166
+ index = end + 3;
167
+ } else if (start === interpolationStart) {
168
+ const end = source.indexOf("}}", start + 2);
169
+ if (end === -1) {
170
+ return html + escapeHtml(source.slice(start));
171
+ }
172
+ html += token("ui-code-punctuation", "{{");
173
+ html += highlightTypeScript(source.slice(start + 2, end));
174
+ html += token("ui-code-punctuation", "}}");
175
+ index = end + 2;
176
+ } else {
177
+ let tagEnd = start + 1;
178
+ let quote = "";
179
+ while (tagEnd < source.length) {
180
+ const character = source[tagEnd] ?? "";
181
+ if (quote) {
182
+ if (character === "\\") {
183
+ tagEnd += 2;
184
+ continue;
185
+ }
186
+ if (character === quote) {
187
+ quote = "";
188
+ }
189
+ } else if (character === '"' || character === "'") {
190
+ quote = character;
191
+ } else if (character === ">") {
192
+ break;
193
+ }
194
+ tagEnd += 1;
195
+ }
196
+ const tag = source.slice(start, tagEnd + 1);
197
+ if (/^<\/?[\w.-]/.test(tag)) {
198
+ html += highlightTag(tag);
199
+ } else {
200
+ html += escapeHtml(tag);
201
+ }
202
+ index = tagEnd + 1;
203
+ }
204
+ }
205
+ return html;
206
+ };
207
+ const highlightDemoCode = (source) => {
208
+ const scriptPattern = /(<script\b[^>]*>)([\s\S]*?)(<\/script>)/g;
209
+ let html = "";
210
+ let lastIndex = 0;
211
+ for (const match of source.matchAll(scriptPattern)) {
212
+ const index = match.index ?? 0;
213
+ html += highlightTemplate(source.slice(lastIndex, index));
214
+ html += highlightTag(match[1] ?? "");
215
+ html += highlightTypeScript(match[2] ?? "");
216
+ html += highlightTag(match[3] ?? "");
217
+ lastIndex = index + match[0].length;
218
+ }
219
+ return html + highlightTemplate(source.slice(lastIndex));
220
+ };
221
+ const highlightTailwindValue = (value) => {
222
+ const pattern = /\d+(?:\.\d+)?/g;
223
+ let html = "";
224
+ let lastIndex = 0;
225
+ for (const match of value.matchAll(pattern)) {
226
+ const index = match.index ?? 0;
227
+ html += token("ui-code-string", escapeHtml(value.slice(lastIndex, index)));
228
+ html += token("ui-code-number", match[0]);
229
+ lastIndex = index + match[0].length;
230
+ }
231
+ return html + token("ui-code-string", escapeHtml(value.slice(lastIndex)));
232
+ };
233
+ const getTailwindColorSwatch = (utility) => {
234
+ const themeColor = utility.match(
235
+ /(?:^|-)(alt|danger|info|main|plain|warn|win)-(50|100|200|300|400|500|600|700|800|900|950)(?:\/(\d{1,3}))?(?:$|-)/
236
+ );
237
+ const neutralColor = utility.match(/(?:^|-)(black|white)(?:\/(\d{1,3}))?$/);
238
+ const match = themeColor ?? neutralColor;
239
+ if (!match) {
240
+ return "";
241
+ }
242
+ const [, name, shade, opacity] = match;
243
+ const color = shade ? `var(--color-${name}-${shade})` : `var(--color-${name})`;
244
+ const background = opacity ? `color-mix(in srgb, ${color} ${opacity}%, transparent)` : color;
245
+ return `<span aria-hidden="true" class="ui-color-swatch" style="--ui-color-swatch: ${background}"></span>`;
246
+ };
247
+ const highlightTailwindUtility = (utility) => {
248
+ let source = utility;
249
+ let html = "";
250
+ for (const prefix of ["!", "-"]) {
251
+ if (source.startsWith(prefix)) {
252
+ html += token("ui-code-punctuation", prefix);
253
+ source = source.slice(prefix.length);
254
+ }
255
+ }
256
+ html += getTailwindColorSwatch(source);
257
+ const separatorIndex = source.indexOf("-");
258
+ if (separatorIndex === -1) {
259
+ return html + token("ui-code-attribute", escapeHtml(source));
260
+ }
261
+ html += token("ui-code-attribute", escapeHtml(source.slice(0, separatorIndex)));
262
+ html += token("ui-code-punctuation", "-");
263
+ return html + highlightTailwindValue(source.slice(separatorIndex + 1));
264
+ };
265
+ const highlightTailwindClass = (className) => {
266
+ const segments = [];
267
+ let start = 0;
268
+ let bracketDepth = 0;
269
+ for (let index = 0; index < className.length; index += 1) {
270
+ const character = className[index];
271
+ if (character === "[") {
272
+ bracketDepth += 1;
273
+ } else if (character === "]") {
274
+ bracketDepth = Math.max(0, bracketDepth - 1);
275
+ } else if (character === ":" && bracketDepth === 0) {
276
+ segments.push(className.slice(start, index));
277
+ start = index + 1;
278
+ }
279
+ }
280
+ const utility = className.slice(start);
281
+ const variants = segments.map(
282
+ (segment) => `${token("ui-code-keyword", escapeHtml(segment))}${token("ui-code-punctuation", ":")}`
283
+ );
284
+ return variants.join("") + highlightTailwindUtility(utility);
285
+ };
286
+ const highlightStyleValue = (value) => {
287
+ if (value === void 0) {
288
+ return token("ui-code-comment", "\u2014");
289
+ }
290
+ if (value === "") {
291
+ return token("ui-code-string", "&quot;&quot;");
292
+ }
293
+ return value.split(/(\s+)/).map((part) => /\s/.test(part) ? escapeHtml(part) : highlightTailwindClass(part)).join("");
294
+ };
295
+ const isDemoCodeExpanded = (demoId) => expandedDemoCode.value.has(getDemoCodeKey(demoId));
296
+ const toggleDemoCode = (demoId) => {
297
+ const key = getDemoCodeKey(demoId);
298
+ const next = new Set(expandedDemoCode.value);
299
+ next.has(key) ? next.delete(key) : next.add(key);
300
+ expandedDemoCode.value = next;
301
+ };
302
+ const closeNavigation = () => {
303
+ isNavigationOpen.value = false;
304
+ };
305
+ watch(isNavigationOpen, async (isOpen) => {
306
+ if (isOpen) {
307
+ await nextTick();
308
+ mobileNavigation.value?.focus();
309
+ }
310
+ });
311
+ watch(
312
+ () => route.query.component,
313
+ (component) => {
314
+ closeNavigation();
315
+ window.scrollTo({ behavior: "smooth", top: 0 });
316
+ if (typeof component === "string" && uiComponents.some((item) => item.id === component)) {
317
+ return;
318
+ }
319
+ const firstComponent = uiComponents[0];
320
+ if (!firstComponent) {
321
+ return;
322
+ }
323
+ router.replace({
324
+ path: "/ui",
325
+ query: {
326
+ ...route.query,
327
+ component: firstComponent.id
328
+ }
329
+ });
330
+ },
331
+ { immediate: true }
332
+ );
333
+ watch(
334
+ () => activeComponent.value?.id,
335
+ () => {
336
+ stylesExpanded.value = false;
337
+ }
338
+ );
339
+ </script>
340
+
341
+ <template>
342
+ <main class="min-h-screen">
343
+ <header
344
+ class="sticky top-0 z-30 flex items-center justify-between gap-4 border-b border-plain-800 bg-plain-950/95 px-4 py-3 backdrop-blur md:hidden"
345
+ >
346
+ <div class="min-w-0">
347
+ <p class="text-xs font-medium tracking-widest text-plain-500 uppercase">Components</p>
348
+ <p class="truncate text-sm font-medium text-plain-100">{{ activeComponent?.name }}</p>
349
+ </div>
350
+ <button
351
+ type="button"
352
+ class="flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-lg border border-plain-700 text-plain-200 transition hover:border-main-700 hover:bg-main-900 hover:text-main-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-main-400"
353
+ aria-controls="ui-mobile-navigation"
354
+ :aria-expanded="isNavigationOpen"
355
+ aria-label="Open components menu"
356
+ @click="isNavigationOpen = true"
357
+ >
358
+ <svg
359
+ class="size-5"
360
+ aria-hidden="true"
361
+ fill="none"
362
+ stroke="currentColor"
363
+ stroke-linecap="round"
364
+ stroke-width="2"
365
+ viewBox="0 0 24 24"
366
+ >
367
+ <path d="M4 7h16M4 12h16M4 17h16" />
368
+ </svg>
369
+ </button>
370
+ </header>
371
+
372
+ <div
373
+ v-if="isNavigationOpen"
374
+ class="fixed inset-0 z-40 md:hidden"
375
+ @keydown.esc="closeNavigation"
376
+ >
377
+ <button
378
+ class="absolute inset-0 cursor-default bg-black/60"
379
+ aria-label="Close components menu"
380
+ type="button"
381
+ @click="closeNavigation"
382
+ />
383
+ <aside
384
+ id="ui-mobile-navigation"
385
+ ref="mobileNavigation"
386
+ class="relative flex h-full flex-col border-r border-plain-800 bg-plain-950 p-4 shadow-2xl"
387
+ :class="$style.mobileNavigation"
388
+ aria-label="UI components"
389
+ tabindex="-1"
390
+ >
391
+ <div class="flex items-center justify-between gap-4 px-2 py-1">
392
+ <p class="text-xs font-medium tracking-widest text-plain-500 uppercase">Components</p>
393
+ <button
394
+ type="button"
395
+ class="flex size-8 cursor-pointer items-center justify-center rounded-lg text-plain-400 transition hover:bg-plain-900 hover:text-plain-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-main-400"
396
+ aria-label="Close components menu"
397
+ @click="closeNavigation"
398
+ >
399
+ <svg
400
+ class="size-5"
401
+ aria-hidden="true"
402
+ fill="none"
403
+ stroke="currentColor"
404
+ stroke-linecap="round"
405
+ stroke-width="2"
406
+ viewBox="0 0 24 24"
407
+ >
408
+ <path d="m6 6 12 12M18 6 6 18" />
409
+ </svg>
410
+ </button>
411
+ </div>
412
+ <nav
413
+ class="mt-3 space-y-1 overflow-y-auto"
414
+ aria-label="UI components"
415
+ >
416
+ <NuxtLink
417
+ v-for="component in uiComponents"
418
+ :key="component.id"
419
+ :aria-current="activeComponent?.id === component.id ? 'page' : void 0"
420
+ :class="[
421
+ 'block rounded-lg px-3 py-2.5 text-sm transition',
422
+ activeComponent?.id === component.id ? 'bg-main-900 text-main-100' : 'text-plain-400 hover:bg-plain-900 hover:text-plain-100'
423
+ ]"
424
+ :to="{
425
+ path: '/ui',
426
+ query: {
427
+ ...route.query,
428
+ component: component.id
429
+ }
430
+ }"
431
+ @click="closeNavigation"
432
+ >
433
+ {{ component.name }}
434
+ </NuxtLink>
435
+ </nav>
436
+ </aside>
437
+ </div>
438
+
439
+ <div class="mx-auto flex min-h-screen max-w-7xl">
440
+ <aside class="hidden w-64 shrink-0 border-r border-plain-800 px-4 py-6 md:block">
441
+ <p class="px-3 text-xs font-medium tracking-widest text-plain-500 uppercase">Components</p>
442
+
443
+ <nav
444
+ class="sticky top-2 mt-4 space-y-1"
445
+ aria-label="UI components"
446
+ >
447
+ <NuxtLink
448
+ v-for="component in uiComponents"
449
+ :key="component.id"
450
+ :aria-current="activeComponent?.id === component.id ? 'page' : void 0"
451
+ :class="[
452
+ 'block rounded-lg px-3 py-2 text-sm transition',
453
+ activeComponent?.id === component.id ? 'bg-main-900 text-main-100' : 'text-plain-400 hover:bg-plain-900 hover:text-plain-100'
454
+ ]"
455
+ :to="{
456
+ path: '/ui',
457
+ query: {
458
+ ...route.query,
459
+ component: component.id
460
+ }
461
+ }"
462
+ >
463
+ {{ component.name }}
464
+ </NuxtLink>
465
+ </nav>
466
+ </aside>
467
+
468
+ <section class="min-w-0 flex-1 px-4 py-6 sm:px-6 md:px-10">
469
+ <template v-if="activeComponent">
470
+ <div class="flex items-center gap-3">
471
+ <h1 class="text-2xl leading-1 font-semibold sm:text-3xl">{{ activeComponent.name }}</h1>
472
+ <button
473
+ v-if="uiThemeEnabled"
474
+ type="button"
475
+ class="flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-lg border border-plain-700 text-plain-400 transition hover:border-main-700 hover:bg-main-900 hover:text-main-200 focus-visible:outline-2 focus-visible:outline-offset-2"
476
+ :aria-label="isDark ? 'Switch to light theme' : 'Switch to dark theme'"
477
+ :title="isDark ? 'Switch to light theme' : 'Switch to dark theme'"
478
+ @click="toggleTheme"
479
+ >
480
+ <svg
481
+ v-if="isDark"
482
+ class="size-3"
483
+ aria-hidden="true"
484
+ fill="none"
485
+ stroke="currentColor"
486
+ stroke-linecap="round"
487
+ stroke-linejoin="round"
488
+ stroke-width="2"
489
+ viewBox="0 0 24 24"
490
+ >
491
+ <circle
492
+ cx="12"
493
+ cy="12"
494
+ r="4"
495
+ />
496
+ <path
497
+ d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"
498
+ />
499
+ </svg>
500
+ <svg
501
+ v-else
502
+ class="size-3"
503
+ aria-hidden="true"
504
+ fill="none"
505
+ stroke="currentColor"
506
+ stroke-linecap="round"
507
+ stroke-linejoin="round"
508
+ stroke-width="2"
509
+ viewBox="0 0 24 24"
510
+ >
511
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z" />
512
+ </svg>
513
+ </button>
514
+ </div>
515
+ <section
516
+ v-for="demo in activeComponent.demos"
517
+ :key="demo.title"
518
+ class="mt-6 overflow-hidden rounded-2xl border border-plain-800 bg-plain-900/50 first:mt-8"
519
+ >
520
+ <header
521
+ class="flex items-start justify-between gap-3 border-b border-plain-800 bg-plain-900 px-4 py-3 sm:gap-4 sm:px-8 sm:py-4"
522
+ >
523
+ <div>
524
+ <h2 class="text-base font-medium text-plain-100">{{ demo.title }}</h2>
525
+ <p
526
+ v-if="demo.description"
527
+ class="mt-1 text-sm text-plain-400"
528
+ >
529
+ {{ demo.description }}
530
+ </p>
531
+ </div>
532
+ <button
533
+ type="button"
534
+ :aria-label="isDemoCodeExpanded(demo.id) ? 'Hide code' : 'View code'"
535
+ :class="[
536
+ 'flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md border transition focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-main-400',
537
+ isDemoCodeExpanded(demo.id) ? 'border-main-700 bg-main-900 text-main-200' : 'border-plain-700 text-plain-400 hover:border-main-700 hover:text-main-200'
538
+ ]"
539
+ :title="isDemoCodeExpanded(demo.id) ? 'Hide code' : 'View code'"
540
+ @click="toggleDemoCode(demo.id)"
541
+ >
542
+ <svg
543
+ class="size-4"
544
+ aria-hidden="true"
545
+ fill="none"
546
+ stroke="currentColor"
547
+ stroke-linecap="round"
548
+ stroke-linejoin="round"
549
+ stroke-width="2"
550
+ viewBox="0 0 24 24"
551
+ >
552
+ <path d="m8 9-3 3 3 3" />
553
+ <path d="m16 9 3 3-3 3" />
554
+ <path d="m14 5-4 14" />
555
+ </svg>
556
+ </button>
557
+ </header>
558
+ <div class="p-4 sm:p-8">
559
+ <component :is="demo.component" />
560
+ </div>
561
+ <div
562
+ v-if="isDemoCodeExpanded(demo.id)"
563
+ class="border-t border-plain-800 bg-plain-950/80 p-4 sm:p-8"
564
+ >
565
+ <pre
566
+ class="overflow-x-auto text-sm leading-6 text-plain-300"
567
+ ><code v-html="highlightDemoCode(demo.code.trim())" /></pre>
568
+ </div>
569
+ </section>
570
+ <p
571
+ v-if="activeComponent.demos.length === 0"
572
+ class="mt-8 text-plain-400"
573
+ >
574
+ Add a
575
+ <code class="text-plain-200">*.demo.vue</code>
576
+ file to this component folder.
577
+ </p>
578
+ <section class="mt-8 overflow-hidden rounded-2xl border border-plain-800 bg-plain-900/50">
579
+ <header class="border-b border-plain-800 bg-plain-900 px-4 py-3 sm:px-8 sm:py-4">
580
+ <h2 class="text-base font-medium text-plain-100">Component API</h2>
581
+ </header>
582
+
583
+ <div class="grid divide-y divide-plain-800 lg:grid-cols-2 lg:divide-x lg:divide-y-0">
584
+ <section class="px-4 py-5 sm:px-6">
585
+ <h3 class="text-sm font-medium text-plain-200">Props</h3>
586
+ <div class="mt-4 overflow-x-auto">
587
+ <table class="w-full min-w-80 text-left text-sm sm:min-w-96">
588
+ <!-- <thead class="text-xs tracking-wider text-plain-500 uppercase">
589
+ <tr>
590
+ <th class="pb-3 font-medium">Name</th>
591
+ <th class="pb-3 font-medium">Type</th>
592
+ <th class="pb-3 text-right font-medium">Required</th>
593
+ </tr>
594
+ </thead> -->
595
+ <tbody class="divide-y divide-plain-800 text-plain-300">
596
+ <tr
597
+ v-for="prop in activeComponent.props"
598
+ :key="prop.name"
599
+ >
600
+ <td class="py-3 pr-4 font-mono text-plain-400">{{ prop.name }}</td>
601
+ <td
602
+ class="py-3 pr-4 font-mono text-xs break-all"
603
+ v-html="highlightPropType(prop.type)"
604
+ />
605
+ <td
606
+ class="py-3 text-right text-xs"
607
+ :class="prop.required ? 'text-danger-600' : 'text-plain-500'"
608
+ >
609
+ {{ prop.required ? "*" : "-" }}
610
+ </td>
611
+ </tr>
612
+ <tr v-if="activeComponent.props.length === 0">
613
+ <td
614
+ colspan="3"
615
+ class="py-3 text-plain-500"
616
+ >
617
+ No props declared.
618
+ </td>
619
+ </tr>
620
+ </tbody>
621
+ </table>
622
+ </div>
623
+ </section>
624
+
625
+ <section class="px-4 py-5 sm:px-6">
626
+ <h3 class="text-sm font-medium text-plain-200">Slots</h3>
627
+ <div
628
+ v-if="activeComponent.slots.length"
629
+ class="mt-4 flex flex-wrap gap-2"
630
+ >
631
+ <code
632
+ v-for="slot in activeComponent.slots"
633
+ :key="slot"
634
+ class="rounded-md border border-plain-800 bg-plain-950 px-2.5 py-1.5 text-xs"
635
+ :class="slot === 'default' ? 'text-plain-500' : 'text-plain-400'"
636
+ >
637
+ {{ slot }}
638
+ </code>
639
+ </div>
640
+ <p
641
+ v-else
642
+ class="mt-4 text-sm text-plain-500"
643
+ >
644
+ No slots detected.
645
+ </p>
646
+ </section>
647
+ </div>
648
+ </section>
649
+
650
+ <section
651
+ v-if="activeComponent.styles.length"
652
+ class="mt-6 overflow-hidden rounded-2xl border border-plain-800 bg-plain-900/50"
653
+ >
654
+ <header
655
+ class="flex items-center justify-between gap-3 border-b border-plain-800 bg-plain-900 px-4 py-3 sm:gap-4 sm:px-8 sm:py-4"
656
+ >
657
+ <div class="flex items-center gap-3">
658
+ <div>
659
+ <h2 class="text-base font-medium text-plain-100">UI styles</h2>
660
+ </div>
661
+ </div>
662
+ <span class="rounded-full border border-plain-700 px-2.5 py-1 text-xs text-plain-400">
663
+ {{ activeComponent.styles.length }} fields
664
+ </span>
665
+ </header>
666
+
667
+ <div class="px-4 py-2 sm:px-8">
668
+ <div class="divide-y divide-plain-800">
669
+ <div
670
+ v-for="style in visibleStyles"
671
+ :key="style.path"
672
+ class="grid gap-2 py-4 sm:gap-6"
673
+ :class="$style.styleGrid"
674
+ >
675
+ <code class="font-mono text-xs text-main-300">{{ style.path }}</code>
676
+ <code
677
+ class="font-mono text-xs wrap-break-word text-plain-300"
678
+ :class="$style.styleValue"
679
+ v-html="highlightStyleValue(style.value)"
680
+ />
681
+ </div>
682
+ </div>
683
+ </div>
684
+
685
+ <footer
686
+ v-if="activeComponent.styles.length > 5"
687
+ class="sticky bottom-0 z-10 border-t border-plain-800 bg-plain-900/95 px-4 py-3 backdrop-blur sm:px-8"
688
+ >
689
+ <button
690
+ type="button"
691
+ class="flex w-full cursor-pointer items-center justify-center text-sm font-medium text-info-500 transition hover:text-info-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info-400"
692
+ @click="stylesExpanded = !stylesExpanded"
693
+ >
694
+ {{ stylesExpanded ? "Collapse styles" : `Show all ${activeComponent.styles.length} styles` }}
695
+ </button>
696
+ </footer>
697
+ </section>
698
+ </template>
699
+
700
+ <p
701
+ v-else
702
+ class="text-plain-400"
703
+ >
704
+ Add
705
+ <code class="text-plain-200">Component/index.vue</code>
706
+ and a
707
+ <code class="text-plain-200">*.demo.vue</code>
708
+ file to show it here.
709
+ </p>
710
+ </section>
711
+ </div>
712
+ </main>
713
+ </template>
714
+
715
+ <style module>
716
+ :global(.ui-code-comment){color:var(--color-plain-500)}:global(.ui-code-string){color:var(--color-win-400)}:global(.ui-code-keyword){color:var(--color-main-400)}:global(.ui-code-number){color:var(--color-warn-300)}:global(.ui-code-tag){color:var(--color-info-500)}:global(.ui-code-attribute){color:var(--color-alt-300)}:global(.ui-code-punctuation){color:var(--color-plain-400)}:global(.ui-color-swatch){background:var(--ui-color-swatch);border:1px solid hsla(0,0%,100%,.1);border-radius:.2rem;display:inline-block;height:.65rem;margin-right:.25rem;opacity:.85;vertical-align:-.1rem;width:.65rem}.mobileNavigation{width:min(18rem,calc(100vw - 3rem))}@media (width >= 40rem){.styleGrid{grid-template-columns:minmax(12rem,.8fr) minmax(0,1.8fr)}}.styleValue :global(.ui-code-keyword){color:var(--color-plain-500)}.styleValue :global(.ui-code-attribute){color:var(--color-plain-200)}.styleValue :global(.ui-code-string){color:var(--color-plain-300)}.styleValue :global(.ui-code-number){color:var(--color-plain-400)}.styleValue :global(.ui-code-punctuation){color:var(--color-plain-500)}
717
+ </style>