@refrakt-md/lumina 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/base.css +16 -0
- package/contracts/structures.json +1317 -3
- package/dist/config.d.ts +2 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -229
- package/dist/config.js.map +1 -1
- package/dist/transform.d.ts +2 -0
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +2 -0
- package/dist/transform.js.map +1 -1
- package/index.css +11 -0
- package/package.json +18 -11
- package/styles/elements/blockquote.css +8 -4
- package/styles/global.css +0 -7
- package/styles/layouts/blog.css +255 -0
- package/styles/layouts/default.css +11 -3
- package/styles/layouts/docs.css +67 -13
- package/styles/layouts/mobile.css +84 -0
- package/styles/runes/bento.css +2 -0
- package/styles/runes/codegroup.css +7 -2
- package/styles/runes/design-context.css +25 -0
- package/styles/runes/feature.css +20 -14
- package/styles/runes/form.css +1 -2
- package/styles/runes/grid.css +25 -7
- package/styles/runes/hero.css +15 -0
- package/styles/runes/map.css +113 -0
- package/styles/runes/palette.css +86 -0
- package/styles/runes/preview.css +187 -0
- package/styles/runes/sandbox.css +23 -0
- package/styles/runes/spacing.css +105 -0
- package/styles/runes/steps.css +7 -1
- package/styles/runes/swatch.css +28 -0
- package/styles/runes/symbol.css +164 -0
- package/styles/runes/tabs.css +6 -0
- package/styles/runes/testimonial.css +2 -3
- package/styles/runes/timeline.css +43 -24
- package/styles/runes/typography.css +91 -0
- package/svelte/elements.ts +1 -0
- package/{sveltekit → svelte}/index.ts +0 -8
- package/svelte/layouts/BlogLayout.svelte +173 -0
- package/svelte/layouts/DefaultLayout.svelte +67 -0
- package/svelte/layouts/DocsLayout.svelte +155 -0
- package/{sveltekit → svelte}/manifest.json +1 -1
- package/svelte/registry.ts +2 -0
- package/svelte/tokens.css +6 -0
- package/sveltekit/components/Accordion.svelte +0 -26
- package/sveltekit/components/Bento.svelte +0 -50
- package/sveltekit/components/Chart.svelte +0 -121
- package/sveltekit/components/CodeGroup.svelte +0 -88
- package/sveltekit/components/Comparison.svelte +0 -209
- package/sveltekit/components/DataTable.svelte +0 -154
- package/sveltekit/components/Details.svelte +0 -23
- package/sveltekit/components/Diagram.svelte +0 -45
- package/sveltekit/components/Embed.svelte +0 -36
- package/sveltekit/components/Form.svelte +0 -194
- package/sveltekit/components/Grid.svelte +0 -42
- package/sveltekit/components/Nav.svelte +0 -62
- package/sveltekit/components/Pricing.svelte +0 -20
- package/sveltekit/components/Reveal.svelte +0 -62
- package/sveltekit/components/Storyboard.svelte +0 -41
- package/sveltekit/components/Tabs.svelte +0 -75
- package/sveltekit/components/Testimonial.svelte +0 -26
- package/sveltekit/elements/Blockquote.svelte +0 -37
- package/sveltekit/elements/Pre.svelte +0 -77
- package/sveltekit/elements/Table.svelte +0 -40
- package/sveltekit/elements.ts +0 -11
- package/sveltekit/layouts/BlogLayout.svelte +0 -382
- package/sveltekit/layouts/DefaultLayout.svelte +0 -70
- package/sveltekit/layouts/DocsLayout.svelte +0 -133
- package/sveltekit/registry.ts +0 -59
- package/sveltekit/tokens.css +0 -71
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Renderer } from '@refrakt-md/svelte';
|
|
3
|
+
|
|
4
|
+
let { title, regions, renderable, url, pages }: {
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
regions: Record<string, { name: string; mode: string; content: any[] }>;
|
|
8
|
+
renderable: any;
|
|
9
|
+
url: string;
|
|
10
|
+
pages: Array<{ url: string; title: string; draft: boolean }>;
|
|
11
|
+
} = $props();
|
|
12
|
+
|
|
13
|
+
const hasNav = $derived(!!regions.nav);
|
|
14
|
+
|
|
15
|
+
// Mobile panel state
|
|
16
|
+
let menuOpen = $state(false);
|
|
17
|
+
let navOpen = $state(false);
|
|
18
|
+
|
|
19
|
+
// Helpers to walk the serialized nav tree (same patterns as Nav.svelte)
|
|
20
|
+
function isTag(node: any): node is { $$mdtype: 'Tag'; name: string; attributes: Record<string, any>; children: any[] } {
|
|
21
|
+
return node !== null && typeof node === 'object' && !Array.isArray(node) && node.$$mdtype === 'Tag';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getTextContent(node: any): string {
|
|
25
|
+
if (typeof node === 'string') return node;
|
|
26
|
+
if (typeof node === 'number') return String(node);
|
|
27
|
+
if (isTag(node)) return node.children.map(getTextContent).join('');
|
|
28
|
+
if (Array.isArray(node)) return node.map(getTextContent).join('');
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Build slug → group title map from nav region
|
|
33
|
+
function buildNavMap(content: any[]): Map<string, string> {
|
|
34
|
+
const map = new Map<string, string>();
|
|
35
|
+
function walk(nodes: any[], groupTitle: string) {
|
|
36
|
+
for (const node of nodes) {
|
|
37
|
+
if (!isTag(node)) continue;
|
|
38
|
+
if (node.attributes.typeof === 'NavGroup') {
|
|
39
|
+
const heading = node.children.find((c: any) => isTag(c) && /^h[1-6]$/.test(c.name));
|
|
40
|
+
walk(node.children, heading ? getTextContent(heading) : '');
|
|
41
|
+
} else if (node.attributes.typeof === 'NavItem') {
|
|
42
|
+
const slugSpan = node.children.find((c: any) => isTag(c) && c.name === 'span' && c.attributes.property === 'slug');
|
|
43
|
+
if (slugSpan && groupTitle) map.set(getTextContent(slugSpan), groupTitle);
|
|
44
|
+
} else if (node.children) {
|
|
45
|
+
walk(node.children, groupTitle);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
walk(content, '');
|
|
50
|
+
return map;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Breadcrumb: look up current page slug in nav group headings
|
|
54
|
+
const pageSlug = $derived((url || '').split('/').filter(Boolean).pop() || '');
|
|
55
|
+
const navMap = $derived(regions.nav ? buildNavMap(regions.nav.content) : new Map());
|
|
56
|
+
const breadcrumbCategory = $derived(navMap.get(pageSlug) || '');
|
|
57
|
+
|
|
58
|
+
function closeMenu() { menuOpen = false; }
|
|
59
|
+
|
|
60
|
+
function onKeydown(e: KeyboardEvent) {
|
|
61
|
+
if (e.key === 'Escape') {
|
|
62
|
+
menuOpen = false;
|
|
63
|
+
navOpen = false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Close panels on navigation
|
|
68
|
+
$effect(() => {
|
|
69
|
+
url;
|
|
70
|
+
menuOpen = false;
|
|
71
|
+
navOpen = false;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Lock body scroll when a panel is open
|
|
75
|
+
$effect(() => {
|
|
76
|
+
document.body.style.overflow = (menuOpen || navOpen) ? 'hidden' : '';
|
|
77
|
+
});
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<svelte:window onkeydown={onKeydown} />
|
|
81
|
+
|
|
82
|
+
{#if regions.header}
|
|
83
|
+
<header class="rf-docs-header">
|
|
84
|
+
<div class="rf-docs-header__inner">
|
|
85
|
+
<Renderer node={regions.header.content} />
|
|
86
|
+
<button class="rf-mobile-menu-btn" onclick={() => { navOpen = false; menuOpen = true; }} aria-label="Open menu">
|
|
87
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
|
|
88
|
+
<circle cx="10" cy="4" r="1.5"/>
|
|
89
|
+
<circle cx="10" cy="10" r="1.5"/>
|
|
90
|
+
<circle cx="10" cy="16" r="1.5"/>
|
|
91
|
+
</svg>
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
</header>
|
|
95
|
+
{/if}
|
|
96
|
+
|
|
97
|
+
<!-- Mobile header menu panel -->
|
|
98
|
+
{#if menuOpen}
|
|
99
|
+
<div class="rf-mobile-panel" role="dialog" aria-label="Navigation menu">
|
|
100
|
+
<div class="rf-mobile-panel__header">
|
|
101
|
+
<span class="rf-mobile-panel__title">Menu</span>
|
|
102
|
+
<button class="rf-mobile-panel__close" onclick={closeMenu} aria-label="Close menu">
|
|
103
|
+
<svg width="20" height="20" viewBox="0 0 20 20" stroke="currentColor" stroke-width="2" fill="none">
|
|
104
|
+
<line x1="4" y1="4" x2="16" y2="16"/><line x1="16" y1="4" x2="4" y2="16"/>
|
|
105
|
+
</svg>
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
<nav class="rf-mobile-panel__nav">
|
|
109
|
+
{#if regions.header}
|
|
110
|
+
<Renderer node={regions.header.content} />
|
|
111
|
+
{/if}
|
|
112
|
+
</nav>
|
|
113
|
+
</div>
|
|
114
|
+
{/if}
|
|
115
|
+
|
|
116
|
+
<!-- Mobile docs toolbar with hamburger + breadcrumbs -->
|
|
117
|
+
{#if hasNav}
|
|
118
|
+
<div class="rf-docs-toolbar">
|
|
119
|
+
<button class="rf-docs-toolbar__hamburger" onclick={() => { menuOpen = false; navOpen = !navOpen; }} aria-label="Toggle navigation">
|
|
120
|
+
<svg width="20" height="20" viewBox="0 0 20 20" stroke="currentColor" stroke-width="2" fill="none">
|
|
121
|
+
<line x1="3" y1="5" x2="17" y2="5"/><line x1="3" y1="10" x2="17" y2="10"/><line x1="3" y1="15" x2="17" y2="15"/>
|
|
122
|
+
</svg>
|
|
123
|
+
</button>
|
|
124
|
+
<div class="rf-docs-toolbar__breadcrumb">
|
|
125
|
+
{#if breadcrumbCategory}
|
|
126
|
+
<span class="rf-docs-breadcrumb-category">{breadcrumbCategory}</span>
|
|
127
|
+
<span class="rf-docs-breadcrumb-sep">›</span>
|
|
128
|
+
{/if}
|
|
129
|
+
<span class="rf-docs-breadcrumb-page">{title}</span>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
{/if}
|
|
133
|
+
|
|
134
|
+
<!-- Mobile nav panel -->
|
|
135
|
+
{#if navOpen}
|
|
136
|
+
<div class="rf-mobile-panel rf-mobile-panel--nav" role="dialog" aria-label="Page navigation">
|
|
137
|
+
<div class="rf-mobile-panel__body">
|
|
138
|
+
{#if regions.nav}
|
|
139
|
+
<Renderer node={regions.nav.content} />
|
|
140
|
+
{/if}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
{/if}
|
|
144
|
+
|
|
145
|
+
{#if regions.nav}
|
|
146
|
+
<aside class="rf-docs-sidebar">
|
|
147
|
+
<Renderer node={regions.nav.content} />
|
|
148
|
+
</aside>
|
|
149
|
+
{/if}
|
|
150
|
+
|
|
151
|
+
<main class="rf-docs-content" class:rf-docs-content--has-nav={hasNav}>
|
|
152
|
+
<div class="rf-docs-content__inner">
|
|
153
|
+
<Renderer node={renderable} />
|
|
154
|
+
</div>
|
|
155
|
+
</main>
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { SerializedTag } from '@refrakt-md/svelte';
|
|
3
|
-
import type { Snippet } from 'svelte';
|
|
4
|
-
|
|
5
|
-
let { tag, children }: { tag: SerializedTag; children: Snippet } = $props();
|
|
6
|
-
|
|
7
|
-
const isGroup = $derived(tag.attributes.typeof === 'Accordion');
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
{#if isGroup}
|
|
11
|
-
<div class="rf-accordion">
|
|
12
|
-
{@render children()}
|
|
13
|
-
</div>
|
|
14
|
-
{:else}
|
|
15
|
-
{@const name = tag.children
|
|
16
|
-
.find((c) => c?.name === 'span' && c?.attributes?.property === 'name')
|
|
17
|
-
?.children?.[0] ?? ''}
|
|
18
|
-
<details class="rf-accordion-item">
|
|
19
|
-
<summary class="rf-accordion-item__header">
|
|
20
|
-
<span class="rf-accordion-item__title">{name}</span>
|
|
21
|
-
</summary>
|
|
22
|
-
<div class="rf-accordion-item__body">
|
|
23
|
-
{@render children()}
|
|
24
|
-
</div>
|
|
25
|
-
</details>
|
|
26
|
-
{/if}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import type { SerializedTag, RendererNode } from '@refrakt-md/svelte';
|
|
4
|
-
import { Renderer } from '@refrakt-md/svelte';
|
|
5
|
-
|
|
6
|
-
let { tag, children }: { tag: SerializedTag; children: Snippet } = $props();
|
|
7
|
-
|
|
8
|
-
function isTag(c: RendererNode): c is SerializedTag {
|
|
9
|
-
return typeof c === 'object' && c !== null && 'name' in c;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const isGroup = tag.attributes.typeof === 'Bento';
|
|
13
|
-
|
|
14
|
-
// For Bento container
|
|
15
|
-
const metas = isGroup ? tag.children.filter((c: any) => c?.name === 'meta') : [];
|
|
16
|
-
const gap = isGroup ? metas[0]?.attributes?.content || '1rem' : '1rem';
|
|
17
|
-
const columns = isGroup ? parseInt(metas[1]?.attributes?.content || '4', 10) : 4;
|
|
18
|
-
|
|
19
|
-
// Find the grid container in tag.children
|
|
20
|
-
const gridEl = isGroup
|
|
21
|
-
? tag.children.find((c): c is SerializedTag => isTag(c) && c.attributes?.['data-name'] === 'grid')
|
|
22
|
-
: undefined;
|
|
23
|
-
|
|
24
|
-
// For BentoCell
|
|
25
|
-
const cellName = !isGroup
|
|
26
|
-
? tag.children.find((c: any) => c?.name === 'span' && c?.attributes?.property === 'name')?.children?.[0] || ''
|
|
27
|
-
: '';
|
|
28
|
-
const cellSize = !isGroup
|
|
29
|
-
? tag.attributes['data-size'] || 'small'
|
|
30
|
-
: 'small';
|
|
31
|
-
</script>
|
|
32
|
-
|
|
33
|
-
{#if isGroup}
|
|
34
|
-
<section class="rf-bento">
|
|
35
|
-
<div class="rf-bento__grid" style:grid-template-columns="repeat({columns}, 1fr)" style:gap={gap}>
|
|
36
|
-
{#if gridEl}
|
|
37
|
-
<Renderer node={gridEl.children} />
|
|
38
|
-
{/if}
|
|
39
|
-
</div>
|
|
40
|
-
</section>
|
|
41
|
-
{:else}
|
|
42
|
-
<div class="rf-bento-cell rf-bento-cell--{cellSize}">
|
|
43
|
-
{#if cellName}
|
|
44
|
-
<h3 class="rf-bento-cell__title">{cellName}</h3>
|
|
45
|
-
{/if}
|
|
46
|
-
<div class="rf-bento-cell__body">
|
|
47
|
-
{@render children()}
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
{/if}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
|
-
import type { SerializedTag } from '@refrakt-md/svelte';
|
|
4
|
-
|
|
5
|
-
let { tag, children }: { tag: SerializedTag; children: Snippet } = $props();
|
|
6
|
-
|
|
7
|
-
const chartType = tag.children.find((c: any) => c?.name === 'meta' && c?.attributes?.property === 'type')?.attributes?.content || 'bar';
|
|
8
|
-
const title = tag.children.find((c: any) => c?.name === 'meta' && c?.attributes?.property === 'title')?.attributes?.content || '';
|
|
9
|
-
const stacked = tag.children.find((c: any) => c?.name === 'meta' && c?.attributes?.property === 'stacked')?.attributes?.content === 'true';
|
|
10
|
-
|
|
11
|
-
const dataJson = tag.children.find((c: any) => c?.name === 'meta' && c?.attributes?.['data-name'] === 'data')?.attributes?.content || '{}';
|
|
12
|
-
let chartData: { headers: string[]; rows: string[][] } = { headers: [], rows: [] };
|
|
13
|
-
try {
|
|
14
|
-
chartData = JSON.parse(dataJson);
|
|
15
|
-
} catch {}
|
|
16
|
-
|
|
17
|
-
const colors = [
|
|
18
|
-
'var(--rf-color-info)',
|
|
19
|
-
'var(--rf-color-success)',
|
|
20
|
-
'var(--rf-color-warning)',
|
|
21
|
-
'var(--rf-color-danger)',
|
|
22
|
-
'#7c3aed',
|
|
23
|
-
'#0891b2',
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
const svgWidth = 600;
|
|
27
|
-
const svgHeight = 300;
|
|
28
|
-
const padding = { top: 30, right: 20, bottom: 40, left: 50 };
|
|
29
|
-
const chartWidth = svgWidth - padding.left - padding.right;
|
|
30
|
-
const chartHeight = svgHeight - padding.top - padding.bottom;
|
|
31
|
-
|
|
32
|
-
const labels = chartData.rows.map(r => r[0] || '');
|
|
33
|
-
const series = chartData.headers.slice(1);
|
|
34
|
-
const values = chartData.rows.map(r => r.slice(1).map(v => parseFloat(v) || 0));
|
|
35
|
-
|
|
36
|
-
const allValues = values.flat();
|
|
37
|
-
const maxValue = Math.max(...allValues, 1);
|
|
38
|
-
|
|
39
|
-
const barGroupWidth = chartWidth / Math.max(labels.length, 1);
|
|
40
|
-
const barWidth = barGroupWidth / Math.max(series.length + 1, 2);
|
|
41
|
-
</script>
|
|
42
|
-
|
|
43
|
-
<figure class="rf-chart" typeof="Chart">
|
|
44
|
-
{#if title}
|
|
45
|
-
<figcaption class="rf-chart__title">{title}</figcaption>
|
|
46
|
-
{/if}
|
|
47
|
-
<div class="rf-chart__container">
|
|
48
|
-
<svg viewBox="0 0 {svgWidth} {svgHeight}" class="rf-chart__svg">
|
|
49
|
-
<line
|
|
50
|
-
x1={padding.left} y1={padding.top}
|
|
51
|
-
x2={padding.left} y2={svgHeight - padding.bottom}
|
|
52
|
-
stroke="var(--rf-color-border)" stroke-width="1"
|
|
53
|
-
/>
|
|
54
|
-
<line
|
|
55
|
-
x1={padding.left} y1={svgHeight - padding.bottom}
|
|
56
|
-
x2={svgWidth - padding.right} y2={svgHeight - padding.bottom}
|
|
57
|
-
stroke="var(--rf-color-border)" stroke-width="1"
|
|
58
|
-
/>
|
|
59
|
-
|
|
60
|
-
{#if chartType === 'bar'}
|
|
61
|
-
{#each labels as label, i}
|
|
62
|
-
{#each series as _, si}
|
|
63
|
-
<rect
|
|
64
|
-
x={padding.left + i * barGroupWidth + si * barWidth + barWidth * 0.25}
|
|
65
|
-
y={padding.top + chartHeight - (values[i][si] / maxValue) * chartHeight}
|
|
66
|
-
width={barWidth * 0.75}
|
|
67
|
-
height={(values[i][si] / maxValue) * chartHeight}
|
|
68
|
-
style="fill: {colors[si % colors.length]}"
|
|
69
|
-
rx="2"
|
|
70
|
-
/>
|
|
71
|
-
{/each}
|
|
72
|
-
<text
|
|
73
|
-
x={padding.left + i * barGroupWidth + barGroupWidth / 2}
|
|
74
|
-
y={svgHeight - padding.bottom + 20}
|
|
75
|
-
text-anchor="middle"
|
|
76
|
-
font-size="12"
|
|
77
|
-
fill="var(--rf-color-muted)"
|
|
78
|
-
>{label}</text>
|
|
79
|
-
{/each}
|
|
80
|
-
{:else if chartType === 'line'}
|
|
81
|
-
{#each series as _, si}
|
|
82
|
-
<polyline
|
|
83
|
-
points={labels.map((_, i) =>
|
|
84
|
-
`${padding.left + i * barGroupWidth + barGroupWidth / 2},${padding.top + chartHeight - (values[i][si] / maxValue) * chartHeight}`
|
|
85
|
-
).join(' ')}
|
|
86
|
-
fill="none"
|
|
87
|
-
style="stroke: {colors[si % colors.length]}"
|
|
88
|
-
stroke-width="2"
|
|
89
|
-
/>
|
|
90
|
-
{#each labels as _, i}
|
|
91
|
-
<circle
|
|
92
|
-
cx={padding.left + i * barGroupWidth + barGroupWidth / 2}
|
|
93
|
-
cy={padding.top + chartHeight - (values[i][si] / maxValue) * chartHeight}
|
|
94
|
-
r="4"
|
|
95
|
-
style="fill: {colors[si % colors.length]}"
|
|
96
|
-
/>
|
|
97
|
-
{/each}
|
|
98
|
-
{/each}
|
|
99
|
-
{#each labels as label, i}
|
|
100
|
-
<text
|
|
101
|
-
x={padding.left + i * barGroupWidth + barGroupWidth / 2}
|
|
102
|
-
y={svgHeight - padding.bottom + 20}
|
|
103
|
-
text-anchor="middle"
|
|
104
|
-
font-size="12"
|
|
105
|
-
fill="var(--rf-color-muted)"
|
|
106
|
-
>{label}</text>
|
|
107
|
-
{/each}
|
|
108
|
-
{/if}
|
|
109
|
-
</svg>
|
|
110
|
-
</div>
|
|
111
|
-
{#if series.length > 1}
|
|
112
|
-
<div class="rf-chart__legend">
|
|
113
|
-
{#each series as name, i}
|
|
114
|
-
<span class="rf-chart__legend-item">
|
|
115
|
-
<span class="rf-chart__legend-color" style="background: {colors[i % colors.length]};"></span>
|
|
116
|
-
{name}
|
|
117
|
-
</span>
|
|
118
|
-
{/each}
|
|
119
|
-
</div>
|
|
120
|
-
{/if}
|
|
121
|
-
</figure>
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { SerializedTag, RendererNode } from '@refrakt-md/svelte';
|
|
3
|
-
import { Renderer } from '@refrakt-md/svelte';
|
|
4
|
-
import type { Snippet } from 'svelte';
|
|
5
|
-
|
|
6
|
-
let { tag, children }: { tag: SerializedTag; children: Snippet } = $props();
|
|
7
|
-
|
|
8
|
-
function isTag(n: RendererNode): n is SerializedTag {
|
|
9
|
-
return n !== null && typeof n === 'object' && !Array.isArray(n) && (n as any).$$mdtype === 'Tag';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function getTextContent(node: RendererNode): string {
|
|
13
|
-
if (typeof node === 'string') return node;
|
|
14
|
-
if (typeof node === 'number') return String(node);
|
|
15
|
-
if (isTag(node)) return node.children.map(getTextContent).join('');
|
|
16
|
-
if (Array.isArray(node)) return node.map(getTextContent).join('');
|
|
17
|
-
return '';
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const title = $derived.by(() => {
|
|
21
|
-
for (const child of tag.children) {
|
|
22
|
-
if (isTag(child) && child.attributes?.property === 'title') {
|
|
23
|
-
return getTextContent(child);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return '';
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const { tabs, panels } = $derived.by(() => {
|
|
30
|
-
const tabs: { name: string }[] = [];
|
|
31
|
-
const panels: { children: RendererNode[] }[] = [];
|
|
32
|
-
for (const child of tag.children) {
|
|
33
|
-
if (!isTag(child) || child.name !== 'ul') continue;
|
|
34
|
-
for (const item of child.children) {
|
|
35
|
-
if (!isTag(item)) continue;
|
|
36
|
-
if (item.attributes?.typeof === 'Tab') {
|
|
37
|
-
const nameNode = item.children.find(
|
|
38
|
-
(c): c is SerializedTag => isTag(c) && c.attributes?.property === 'name'
|
|
39
|
-
);
|
|
40
|
-
tabs.push({ name: (nameNode ? getTextContent(nameNode) : getTextContent(item)).trim() });
|
|
41
|
-
} else if (item.attributes?.typeof === 'TabPanel') {
|
|
42
|
-
panels.push({ children: item.children });
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return { tabs, panels };
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
let activeIndex = $state(0);
|
|
50
|
-
</script>
|
|
51
|
-
|
|
52
|
-
{#if tabs.length > 0}
|
|
53
|
-
<div class="rf-codegroup">
|
|
54
|
-
<div class="rf-codegroup__topbar">
|
|
55
|
-
<span class="rf-codegroup__dot"></span>
|
|
56
|
-
<span class="rf-codegroup__dot"></span>
|
|
57
|
-
<span class="rf-codegroup__dot"></span>
|
|
58
|
-
{#if title}
|
|
59
|
-
<span class="rf-codegroup__title">{title}</span>
|
|
60
|
-
{/if}
|
|
61
|
-
</div>
|
|
62
|
-
{#if tabs.length > 1}
|
|
63
|
-
<div class="rf-codegroup__tabs" role="tablist">
|
|
64
|
-
{#each tabs as tab, i}
|
|
65
|
-
<button
|
|
66
|
-
class="rf-codegroup__tab {i === activeIndex ? 'rf-codegroup__tab--active' : ''}"
|
|
67
|
-
role="tab"
|
|
68
|
-
aria-selected={i === activeIndex}
|
|
69
|
-
onclick={() => activeIndex = i}
|
|
70
|
-
>
|
|
71
|
-
{tab.name}
|
|
72
|
-
</button>
|
|
73
|
-
{/each}
|
|
74
|
-
</div>
|
|
75
|
-
{/if}
|
|
76
|
-
<div class="rf-codegroup__content">
|
|
77
|
-
{#each panels as panel, i}
|
|
78
|
-
{#if i === activeIndex}
|
|
79
|
-
<div class="rf-codegroup__panel" role="tabpanel">
|
|
80
|
-
<Renderer node={panel.children} />
|
|
81
|
-
</div>
|
|
82
|
-
{/if}
|
|
83
|
-
{/each}
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
{:else}
|
|
87
|
-
{@render children()}
|
|
88
|
-
{/if}
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { SerializedTag, RendererNode } from '@refrakt-md/svelte';
|
|
3
|
-
import { Renderer } from '@refrakt-md/svelte';
|
|
4
|
-
import type { Snippet } from 'svelte';
|
|
5
|
-
|
|
6
|
-
let { tag, children }: { tag: SerializedTag; children: Snippet } = $props();
|
|
7
|
-
|
|
8
|
-
const typeName = tag.attributes.typeof;
|
|
9
|
-
|
|
10
|
-
function isTag(n: RendererNode): n is SerializedTag {
|
|
11
|
-
return n !== null && typeof n === 'object' && !Array.isArray(n) && (n as any).$$mdtype === 'Tag';
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function meta(node: SerializedTag, prop: string): string {
|
|
15
|
-
const child = node.children.find(
|
|
16
|
-
(c): c is SerializedTag => isTag(c) && c.name === 'meta' && c.attributes?.property === prop
|
|
17
|
-
);
|
|
18
|
-
return child?.attributes?.content ?? '';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function propText(node: SerializedTag, prop: string): string {
|
|
22
|
-
const child = node.children.find(
|
|
23
|
-
(c): c is SerializedTag => isTag(c) && c.attributes?.property === prop
|
|
24
|
-
);
|
|
25
|
-
if (!child) return '';
|
|
26
|
-
return child.children.filter((c): c is string => typeof c === 'string').join('');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function findByType(node: SerializedTag, type: string): SerializedTag[] {
|
|
30
|
-
const results: SerializedTag[] = [];
|
|
31
|
-
function walk(children: RendererNode[]) {
|
|
32
|
-
for (const c of children) {
|
|
33
|
-
if (isTag(c)) {
|
|
34
|
-
if (c.attributes?.typeof === type) {
|
|
35
|
-
results.push(c);
|
|
36
|
-
} else {
|
|
37
|
-
walk(c.children);
|
|
38
|
-
}
|
|
39
|
-
} else if (Array.isArray(c)) {
|
|
40
|
-
walk(c);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
walk(node.children);
|
|
45
|
-
return results;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function findRef(node: SerializedTag, name: string): SerializedTag | undefined {
|
|
49
|
-
for (const c of node.children) {
|
|
50
|
-
if (isTag(c) && c.attributes?.['data-name'] === name) return c;
|
|
51
|
-
}
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const isComparison = typeName === 'Comparison';
|
|
56
|
-
const layout = isComparison ? (meta(tag, 'layout') || 'table') : '';
|
|
57
|
-
const verdict = isComparison ? meta(tag, 'verdict') : '';
|
|
58
|
-
const highlightedName = isComparison ? meta(tag, 'highlighted') : '';
|
|
59
|
-
const labelsPosition = isComparison ? (meta(tag, 'labels') || 'left') : '';
|
|
60
|
-
const rowLabelsJson = isComparison ? meta(tag, 'rowLabels') : '[]';
|
|
61
|
-
const rowLabels: string[] = isComparison ? (() => { try { return JSON.parse(rowLabelsJson); } catch { return []; } })() : [];
|
|
62
|
-
const columns = isComparison ? findByType(tag, 'ComparisonColumn') : [];
|
|
63
|
-
|
|
64
|
-
const titleTag = isComparison ? tag.children.find(
|
|
65
|
-
(c): c is SerializedTag => isTag(c) && /^h[1-6]$/.test(c.name)
|
|
66
|
-
) : undefined;
|
|
67
|
-
const titleText = titleTag ? titleTag.children.filter((c): c is string => typeof c === 'string').join('') : '';
|
|
68
|
-
|
|
69
|
-
interface ColumnData {
|
|
70
|
-
name: string;
|
|
71
|
-
highlighted: boolean;
|
|
72
|
-
rows: SerializedTag[];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const columnData: ColumnData[] = isComparison ? columns.map(col => ({
|
|
76
|
-
name: propText(col, 'name'),
|
|
77
|
-
highlighted: meta(col, 'highlighted') === 'true',
|
|
78
|
-
rows: findByType(col, 'ComparisonRow'),
|
|
79
|
-
})) : [];
|
|
80
|
-
|
|
81
|
-
const isColumn = typeName === 'ComparisonColumn';
|
|
82
|
-
const colName = isColumn ? propText(tag, 'name') : '';
|
|
83
|
-
const colHighlighted = isColumn ? meta(tag, 'highlighted') === 'true' : false;
|
|
84
|
-
|
|
85
|
-
const isRow = typeName === 'ComparisonRow';
|
|
86
|
-
const rowLabel = isRow ? propText(tag, 'label') : '';
|
|
87
|
-
const rowType = isRow ? meta(tag, 'rowType') : '';
|
|
88
|
-
const rowBody = isRow ? findRef(tag, 'body') : undefined;
|
|
89
|
-
</script>
|
|
90
|
-
|
|
91
|
-
{#if isComparison}
|
|
92
|
-
<section class="rf-comparison rf-comparison--{layout}">
|
|
93
|
-
{#if titleText}
|
|
94
|
-
<h2 class="rf-comparison__title">{titleText}</h2>
|
|
95
|
-
{/if}
|
|
96
|
-
|
|
97
|
-
{#if layout === 'cards'}
|
|
98
|
-
<div class="rf-comparison__cards">
|
|
99
|
-
{#each columnData as col}
|
|
100
|
-
<div class="rf-comparison-card {col.highlighted ? 'rf-comparison-card--highlighted' : ''}">
|
|
101
|
-
{#if col.highlighted}
|
|
102
|
-
<div class="rf-comparison-card__badge">Recommended</div>
|
|
103
|
-
{/if}
|
|
104
|
-
<h3 class="rf-comparison-card__name">{col.name}</h3>
|
|
105
|
-
<ul class="rf-comparison-card__rows">
|
|
106
|
-
{#each col.rows as row}
|
|
107
|
-
{@const rType = meta(row, 'rowType')}
|
|
108
|
-
{@const rLabel = propText(row, 'label')}
|
|
109
|
-
{@const rBody = findRef(row, 'body')}
|
|
110
|
-
{#if rType !== 'empty'}
|
|
111
|
-
<li class="rf-comparison-card__row {rType === 'negative' ? 'rf-comparison-card__row--negative' : ''} {rType === 'callout' ? 'rf-comparison-card__row--callout' : ''}">
|
|
112
|
-
{#if rType === 'check'}
|
|
113
|
-
<span class="rf-comparison__row-icon rf-comparison__row-icon--check" aria-label="Supported">✓</span>
|
|
114
|
-
{#if rLabel}<strong>{rLabel}</strong>{/if}
|
|
115
|
-
{#if rBody}<Renderer node={rBody.children} />{/if}
|
|
116
|
-
{:else if rType === 'cross'}
|
|
117
|
-
<span class="rf-comparison__row-icon rf-comparison__row-icon--cross" aria-label="Not supported">✗</span>
|
|
118
|
-
{#if rLabel}<strong>{rLabel}</strong>{/if}
|
|
119
|
-
{#if rBody}<Renderer node={rBody.children} />{/if}
|
|
120
|
-
{:else if rType === 'negative'}
|
|
121
|
-
{#if rLabel}<strong>{rLabel}</strong>{/if}
|
|
122
|
-
{#if rBody}<span class="rf-comparison__negative"><Renderer node={rBody.children} /></span>{/if}
|
|
123
|
-
{:else if rType === 'callout'}
|
|
124
|
-
<div class="rf-comparison__callout-badge">
|
|
125
|
-
{#if rBody}<Renderer node={rBody.children} />{/if}
|
|
126
|
-
</div>
|
|
127
|
-
{:else}
|
|
128
|
-
{#if rLabel}<strong>{rLabel}</strong>{/if}
|
|
129
|
-
{#if rBody}<Renderer node={rBody.children} />{/if}
|
|
130
|
-
{/if}
|
|
131
|
-
</li>
|
|
132
|
-
{/if}
|
|
133
|
-
{/each}
|
|
134
|
-
</ul>
|
|
135
|
-
</div>
|
|
136
|
-
{/each}
|
|
137
|
-
</div>
|
|
138
|
-
{:else}
|
|
139
|
-
<div class="rf-comparison__table-wrapper">
|
|
140
|
-
<table class="rf-comparison__table">
|
|
141
|
-
<thead>
|
|
142
|
-
<tr>
|
|
143
|
-
{#if labelsPosition !== 'hidden'}
|
|
144
|
-
<th class="rf-comparison__label-col"></th>
|
|
145
|
-
{/if}
|
|
146
|
-
{#each columnData as col}
|
|
147
|
-
<th class="{col.highlighted ? 'rf-comparison__col-header--highlighted' : ''}">
|
|
148
|
-
{col.name}
|
|
149
|
-
{#if col.highlighted}
|
|
150
|
-
<span class="rf-comparison__recommended-badge">Recommended</span>
|
|
151
|
-
{/if}
|
|
152
|
-
</th>
|
|
153
|
-
{/each}
|
|
154
|
-
</tr>
|
|
155
|
-
</thead>
|
|
156
|
-
<tbody>
|
|
157
|
-
{#each rowLabels as label, i}
|
|
158
|
-
<tr>
|
|
159
|
-
{#if labelsPosition !== 'hidden'}
|
|
160
|
-
<th class="rf-comparison__row-label" scope="row">{label}</th>
|
|
161
|
-
{/if}
|
|
162
|
-
{#each columnData as col}
|
|
163
|
-
{@const row = col.rows[i]}
|
|
164
|
-
{@const rType = row ? meta(row, 'rowType') : 'empty'}
|
|
165
|
-
{@const rBody = row ? findRef(row, 'body') : undefined}
|
|
166
|
-
<td class="rf-comparison__cell {col.highlighted ? 'rf-comparison__cell--highlighted' : ''} {rType === 'empty' ? 'rf-comparison__cell--empty' : ''}">
|
|
167
|
-
{#if rType === 'check'}
|
|
168
|
-
<span class="rf-comparison__row-icon rf-comparison__row-icon--check" aria-label="Supported">✓</span>
|
|
169
|
-
{:else if rType === 'cross'}
|
|
170
|
-
<span class="rf-comparison__row-icon rf-comparison__row-icon--cross" aria-label="Not supported">✗</span>
|
|
171
|
-
{:else if rType === 'negative'}
|
|
172
|
-
{#if rBody}
|
|
173
|
-
<span class="rf-comparison__negative"><Renderer node={rBody.children} /></span>
|
|
174
|
-
{/if}
|
|
175
|
-
{:else if rType === 'empty'}
|
|
176
|
-
<span class="rf-comparison__cell--empty" aria-label="Not applicable">—</span>
|
|
177
|
-
{:else if rType === 'callout'}
|
|
178
|
-
{#if rBody}
|
|
179
|
-
<span class="rf-comparison__callout-badge"><Renderer node={rBody.children} /></span>
|
|
180
|
-
{/if}
|
|
181
|
-
{:else}
|
|
182
|
-
{#if rBody}
|
|
183
|
-
<Renderer node={rBody.children} />
|
|
184
|
-
{/if}
|
|
185
|
-
{/if}
|
|
186
|
-
</td>
|
|
187
|
-
{/each}
|
|
188
|
-
</tr>
|
|
189
|
-
{/each}
|
|
190
|
-
</tbody>
|
|
191
|
-
</table>
|
|
192
|
-
</div>
|
|
193
|
-
{/if}
|
|
194
|
-
|
|
195
|
-
{#if verdict}
|
|
196
|
-
<p class="rf-comparison__verdict">{verdict}</p>
|
|
197
|
-
{/if}
|
|
198
|
-
</section>
|
|
199
|
-
{:else if isColumn}
|
|
200
|
-
<div class="rf-comparison-column {colHighlighted ? 'rf-comparison-column--highlighted' : ''}">
|
|
201
|
-
<h3>{colName}</h3>
|
|
202
|
-
{@render children()}
|
|
203
|
-
</div>
|
|
204
|
-
{:else if isRow}
|
|
205
|
-
<div class="rf-comparison-row rf-comparison-row--{rowType}">
|
|
206
|
-
{#if rowLabel}<strong>{rowLabel}</strong>{/if}
|
|
207
|
-
{@render children()}
|
|
208
|
-
</div>
|
|
209
|
-
{/if}
|