@refrakt-md/lumina 0.4.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/contracts/structures.json +5 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +238 -0
- package/dist/config.js.map +1 -0
- package/dist/lib/engine.d.ts +13 -0
- package/dist/lib/engine.d.ts.map +1 -0
- package/dist/lib/engine.js +218 -0
- package/dist/lib/engine.js.map +1 -0
- package/dist/lib/helpers.d.ts +14 -0
- package/dist/lib/helpers.d.ts.map +1 -0
- package/dist/lib/helpers.js +26 -0
- package/dist/lib/helpers.js.map +1 -0
- package/dist/lib/types.d.ts +74 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/transform.d.ts +5 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/transform.js +7 -0
- package/dist/transform.js.map +1 -0
- package/index.css +57 -0
- package/manifest.json +13 -0
- package/package.json +62 -0
- package/styles/elements/blockquote.css +22 -0
- package/styles/elements/table.css +26 -0
- package/styles/global.css +138 -0
- package/styles/layouts/default.css +45 -0
- package/styles/layouts/docs.css +94 -0
- package/styles/runes/accordion.css +55 -0
- package/styles/runes/annotate.css +87 -0
- package/styles/runes/api.css +77 -0
- package/styles/runes/bento.css +48 -0
- package/styles/runes/breadcrumb.css +46 -0
- package/styles/runes/cast.css +56 -0
- package/styles/runes/changelog.css +53 -0
- package/styles/runes/chart.css +33 -0
- package/styles/runes/codegroup.css +72 -0
- package/styles/runes/compare.css +41 -0
- package/styles/runes/comparison.css +211 -0
- package/styles/runes/conversation.css +54 -0
- package/styles/runes/cta.css +87 -0
- package/styles/runes/datatable.css +112 -0
- package/styles/runes/details.css +44 -0
- package/styles/runes/diagram.css +27 -0
- package/styles/runes/diff.css +74 -0
- package/styles/runes/embed.css +32 -0
- package/styles/runes/event.css +57 -0
- package/styles/runes/feature.css +67 -0
- package/styles/runes/figure.css +24 -0
- package/styles/runes/form.css +184 -0
- package/styles/runes/grid.css +13 -0
- package/styles/runes/hero.css +102 -0
- package/styles/runes/hint.css +97 -0
- package/styles/runes/howto.css +37 -0
- package/styles/runes/nav.css +50 -0
- package/styles/runes/organization.css +26 -0
- package/styles/runes/page-section.css +20 -0
- package/styles/runes/pricing.css +109 -0
- package/styles/runes/recipe.css +47 -0
- package/styles/runes/reveal.css +55 -0
- package/styles/runes/sidenote.css +28 -0
- package/styles/runes/steps.css +59 -0
- package/styles/runes/storyboard.css +65 -0
- package/styles/runes/tabs.css +50 -0
- package/styles/runes/testimonial.css +59 -0
- package/styles/runes/timeline.css +67 -0
- package/styles/runes/toc.css +43 -0
- package/sveltekit/components/Accordion.svelte +26 -0
- package/sveltekit/components/Bento.svelte +50 -0
- package/sveltekit/components/Chart.svelte +121 -0
- package/sveltekit/components/CodeGroup.svelte +88 -0
- package/sveltekit/components/Comparison.svelte +209 -0
- package/sveltekit/components/DataTable.svelte +154 -0
- package/sveltekit/components/Details.svelte +23 -0
- package/sveltekit/components/Diagram.svelte +45 -0
- package/sveltekit/components/Embed.svelte +36 -0
- package/sveltekit/components/Form.svelte +194 -0
- package/sveltekit/components/Grid.svelte +42 -0
- package/sveltekit/components/Nav.svelte +62 -0
- package/sveltekit/components/Pricing.svelte +20 -0
- package/sveltekit/components/Reveal.svelte +62 -0
- package/sveltekit/components/Storyboard.svelte +41 -0
- package/sveltekit/components/Tabs.svelte +75 -0
- package/sveltekit/components/Testimonial.svelte +26 -0
- package/sveltekit/elements/Blockquote.svelte +37 -0
- package/sveltekit/elements/Pre.svelte +77 -0
- package/sveltekit/elements/Table.svelte +40 -0
- package/sveltekit/elements.ts +11 -0
- package/sveltekit/index.ts +32 -0
- package/sveltekit/layouts/BlogLayout.svelte +382 -0
- package/sveltekit/layouts/DefaultLayout.svelte +70 -0
- package/sveltekit/layouts/DocsLayout.svelte +133 -0
- package/sveltekit/manifest.json +51 -0
- package/sveltekit/registry.ts +59 -0
- package/sveltekit/tokens.css +71 -0
- package/tokens/base.css +78 -0
- package/tokens/dark.css +102 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/* Storyboard */
|
|
2
|
+
.rf-storyboard {
|
|
3
|
+
margin: 1.5rem 0;
|
|
4
|
+
}
|
|
5
|
+
.rf-storyboard__panels {
|
|
6
|
+
display: grid;
|
|
7
|
+
grid-template-columns: repeat(var(--sb-columns, 3), 1fr);
|
|
8
|
+
gap: 1rem;
|
|
9
|
+
}
|
|
10
|
+
/* Clean style */
|
|
11
|
+
.rf-storyboard--clean .rf-storyboard-panel {
|
|
12
|
+
border: 1px solid var(--rf-color-border);
|
|
13
|
+
border-radius: var(--rf-radius-md);
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
}
|
|
16
|
+
.rf-storyboard--clean .rf-storyboard-panel__body {
|
|
17
|
+
padding: 0.75rem;
|
|
18
|
+
}
|
|
19
|
+
/* Comic style */
|
|
20
|
+
.rf-storyboard--comic .rf-storyboard-panel {
|
|
21
|
+
border: 3px solid var(--rf-color-text);
|
|
22
|
+
border-radius: 0.25rem;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
transform: rotate(-0.5deg);
|
|
25
|
+
}
|
|
26
|
+
.rf-storyboard--comic .rf-storyboard-panel:nth-child(even) {
|
|
27
|
+
transform: rotate(0.5deg);
|
|
28
|
+
}
|
|
29
|
+
.rf-storyboard--comic .rf-storyboard-panel__body {
|
|
30
|
+
padding: 0.5rem;
|
|
31
|
+
}
|
|
32
|
+
.rf-storyboard--comic .rf-storyboard-panel__body p {
|
|
33
|
+
font-family: 'Comic Sans MS', 'Chalkboard SE', cursive;
|
|
34
|
+
font-size: 0.9375rem;
|
|
35
|
+
text-align: center;
|
|
36
|
+
margin: 0.5rem 0 0;
|
|
37
|
+
}
|
|
38
|
+
/* Polaroid style */
|
|
39
|
+
.rf-storyboard--polaroid .rf-storyboard-panel {
|
|
40
|
+
background: white;
|
|
41
|
+
padding: 0.75rem 0.75rem 2.5rem;
|
|
42
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
43
|
+
border-radius: 2px;
|
|
44
|
+
}
|
|
45
|
+
.rf-storyboard--polaroid .rf-storyboard-panel__body p {
|
|
46
|
+
font-size: 0.8125rem;
|
|
47
|
+
text-align: center;
|
|
48
|
+
color: var(--rf-color-muted);
|
|
49
|
+
margin: 0.5rem 0 0;
|
|
50
|
+
}
|
|
51
|
+
/* Shared image styles */
|
|
52
|
+
.rf-storyboard-panel img {
|
|
53
|
+
width: 100%;
|
|
54
|
+
height: auto;
|
|
55
|
+
display: block;
|
|
56
|
+
}
|
|
57
|
+
.rf-storyboard-panel__body span[property],
|
|
58
|
+
.rf-storyboard-panel__body meta { display: none; }
|
|
59
|
+
.rf-storyboard-panel__body p:last-child { margin-bottom: 0; }
|
|
60
|
+
@media (max-width: 768px) {
|
|
61
|
+
.rf-storyboard__panels { grid-template-columns: repeat(2, 1fr) !important; }
|
|
62
|
+
}
|
|
63
|
+
@media (max-width: 480px) {
|
|
64
|
+
.rf-storyboard__panels { grid-template-columns: 1fr !important; }
|
|
65
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* Tabs */
|
|
2
|
+
.rf-tabs {
|
|
3
|
+
border: 1px solid var(--rf-color-border);
|
|
4
|
+
border-radius: var(--rf-radius-md);
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
margin: 1.5rem 0;
|
|
7
|
+
background: var(--rf-color-bg);
|
|
8
|
+
}
|
|
9
|
+
.rf-tabs__bar {
|
|
10
|
+
display: flex;
|
|
11
|
+
background: var(--rf-color-surface);
|
|
12
|
+
border-bottom: 1px solid var(--rf-color-border);
|
|
13
|
+
overflow-x: auto;
|
|
14
|
+
overflow-y: hidden;
|
|
15
|
+
-webkit-overflow-scrolling: touch;
|
|
16
|
+
}
|
|
17
|
+
.rf-tabs__button {
|
|
18
|
+
flex: 0 0 auto;
|
|
19
|
+
padding: 0.625rem 1.25rem;
|
|
20
|
+
font-size: 0.85rem;
|
|
21
|
+
font-weight: 500;
|
|
22
|
+
color: var(--rf-color-muted);
|
|
23
|
+
background: none;
|
|
24
|
+
border: none;
|
|
25
|
+
border-bottom: 2px solid transparent;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
transition: color 200ms ease, border-color 200ms ease, background-color 200ms ease;
|
|
28
|
+
white-space: nowrap;
|
|
29
|
+
margin-bottom: -1px;
|
|
30
|
+
font-family: inherit;
|
|
31
|
+
}
|
|
32
|
+
.rf-tabs__button:hover {
|
|
33
|
+
color: var(--rf-color-text);
|
|
34
|
+
background: var(--rf-color-surface-hover);
|
|
35
|
+
}
|
|
36
|
+
.rf-tabs__button--active {
|
|
37
|
+
color: var(--rf-color-primary);
|
|
38
|
+
border-bottom-color: var(--rf-color-primary);
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
background: var(--rf-color-bg);
|
|
41
|
+
}
|
|
42
|
+
.rf-tabs__panel {
|
|
43
|
+
padding: 1.25rem 1.5rem;
|
|
44
|
+
}
|
|
45
|
+
.rf-tabs__panel pre {
|
|
46
|
+
margin: 0;
|
|
47
|
+
}
|
|
48
|
+
.rf-tabs__panel p:last-child {
|
|
49
|
+
margin-bottom: 0;
|
|
50
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* Testimonial */
|
|
2
|
+
.rf-testimonial {
|
|
3
|
+
border: 1px solid var(--rf-color-border);
|
|
4
|
+
border-radius: var(--rf-radius-lg);
|
|
5
|
+
padding: 1.75rem 2rem;
|
|
6
|
+
margin: 1.5rem 0;
|
|
7
|
+
background: var(--rf-color-surface);
|
|
8
|
+
}
|
|
9
|
+
.rf-testimonial__rating {
|
|
10
|
+
display: flex;
|
|
11
|
+
gap: 0.125rem;
|
|
12
|
+
margin-bottom: 0.75rem;
|
|
13
|
+
}
|
|
14
|
+
.rf-testimonial__star {
|
|
15
|
+
font-size: 1.1rem;
|
|
16
|
+
color: var(--rf-color-border);
|
|
17
|
+
line-height: 1;
|
|
18
|
+
}
|
|
19
|
+
.rf-testimonial__star--filled {
|
|
20
|
+
color: var(--rf-color-warning);
|
|
21
|
+
}
|
|
22
|
+
.rf-testimonial blockquote,
|
|
23
|
+
.rf-testimonial .rf-blockquote {
|
|
24
|
+
border: none;
|
|
25
|
+
padding: 0;
|
|
26
|
+
margin: 0 0 1rem;
|
|
27
|
+
font-size: 1.05rem;
|
|
28
|
+
font-style: italic;
|
|
29
|
+
line-height: 1.7;
|
|
30
|
+
color: var(--rf-color-text);
|
|
31
|
+
background: none;
|
|
32
|
+
border-radius: 0;
|
|
33
|
+
}
|
|
34
|
+
.rf-testimonial .rf-blockquote__quote-mark {
|
|
35
|
+
display: none;
|
|
36
|
+
}
|
|
37
|
+
.rf-testimonial blockquote p {
|
|
38
|
+
margin: 0;
|
|
39
|
+
}
|
|
40
|
+
.rf-testimonial span[property="authorName"] {
|
|
41
|
+
display: block;
|
|
42
|
+
font-weight: 700;
|
|
43
|
+
font-style: normal;
|
|
44
|
+
font-size: 0.925rem;
|
|
45
|
+
color: var(--rf-color-text);
|
|
46
|
+
}
|
|
47
|
+
.rf-testimonial span[property="authorRole"] {
|
|
48
|
+
display: block;
|
|
49
|
+
margin-top: 0.125rem;
|
|
50
|
+
font-size: 0.85rem;
|
|
51
|
+
color: var(--rf-color-muted);
|
|
52
|
+
}
|
|
53
|
+
.rf-testimonial img {
|
|
54
|
+
width: 48px;
|
|
55
|
+
height: 48px;
|
|
56
|
+
border-radius: 50%;
|
|
57
|
+
object-fit: cover;
|
|
58
|
+
margin-top: 0.75rem;
|
|
59
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* Timeline */
|
|
2
|
+
.rf-timeline {
|
|
3
|
+
margin: 2rem 0;
|
|
4
|
+
}
|
|
5
|
+
.rf-timeline ol {
|
|
6
|
+
list-style: none;
|
|
7
|
+
padding: 0;
|
|
8
|
+
margin: 0;
|
|
9
|
+
position: relative;
|
|
10
|
+
}
|
|
11
|
+
.rf-timeline--vertical ol {
|
|
12
|
+
padding-left: 2rem;
|
|
13
|
+
border-left: 2px solid var(--rf-color-border);
|
|
14
|
+
}
|
|
15
|
+
.rf-timeline__entries {
|
|
16
|
+
display: contents;
|
|
17
|
+
}
|
|
18
|
+
.rf-timeline-entry {
|
|
19
|
+
position: relative;
|
|
20
|
+
padding: 0 0 2rem;
|
|
21
|
+
}
|
|
22
|
+
.rf-timeline-entry:last-child {
|
|
23
|
+
padding-bottom: 0;
|
|
24
|
+
}
|
|
25
|
+
.rf-timeline-entry::before {
|
|
26
|
+
content: '';
|
|
27
|
+
position: absolute;
|
|
28
|
+
left: -2.5625rem;
|
|
29
|
+
top: 0.35rem;
|
|
30
|
+
width: 12px;
|
|
31
|
+
height: 12px;
|
|
32
|
+
border-radius: 50%;
|
|
33
|
+
background: var(--rf-color-primary);
|
|
34
|
+
border: 2px solid var(--rf-color-bg);
|
|
35
|
+
box-shadow: 0 0 0 2px var(--rf-color-primary);
|
|
36
|
+
}
|
|
37
|
+
.rf-timeline-entry__body time {
|
|
38
|
+
display: block;
|
|
39
|
+
font-size: 0.8rem;
|
|
40
|
+
font-weight: 600;
|
|
41
|
+
color: var(--rf-color-primary);
|
|
42
|
+
letter-spacing: 0.02em;
|
|
43
|
+
margin-bottom: 0.25rem;
|
|
44
|
+
}
|
|
45
|
+
.rf-timeline-entry__body span {
|
|
46
|
+
display: block;
|
|
47
|
+
font-size: 1.05rem;
|
|
48
|
+
font-weight: 700;
|
|
49
|
+
color: var(--rf-color-text);
|
|
50
|
+
margin-bottom: 0.5rem;
|
|
51
|
+
}
|
|
52
|
+
.rf-timeline-entry__body {
|
|
53
|
+
font-size: 0.925rem;
|
|
54
|
+
line-height: 1.65;
|
|
55
|
+
color: var(--rf-color-muted);
|
|
56
|
+
}
|
|
57
|
+
.rf-timeline-entry__body p:last-child {
|
|
58
|
+
margin-bottom: 0;
|
|
59
|
+
}
|
|
60
|
+
.rf-timeline--horizontal ol {
|
|
61
|
+
display: flex;
|
|
62
|
+
gap: 2rem;
|
|
63
|
+
overflow-x: auto;
|
|
64
|
+
padding: 2rem 0 1rem;
|
|
65
|
+
border-left: none;
|
|
66
|
+
border-top: 2px solid var(--rf-color-border);
|
|
67
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* Table of Contents */
|
|
2
|
+
.rf-toc {
|
|
3
|
+
padding: 1.25rem 1.5rem;
|
|
4
|
+
border-left: 3px solid var(--rf-color-primary);
|
|
5
|
+
background: var(--rf-color-surface);
|
|
6
|
+
border-radius: 0 var(--rf-radius-md) var(--rf-radius-md) 0;
|
|
7
|
+
margin: 1.5rem 0;
|
|
8
|
+
}
|
|
9
|
+
.rf-toc__list {
|
|
10
|
+
font-weight: 650;
|
|
11
|
+
margin-bottom: 0.75rem;
|
|
12
|
+
font-size: 0.7rem;
|
|
13
|
+
text-transform: uppercase;
|
|
14
|
+
letter-spacing: 0.06em;
|
|
15
|
+
color: var(--rf-color-muted);
|
|
16
|
+
}
|
|
17
|
+
.rf-toc ul,
|
|
18
|
+
.rf-toc ol {
|
|
19
|
+
list-style: none;
|
|
20
|
+
padding-left: 0;
|
|
21
|
+
margin: 0;
|
|
22
|
+
}
|
|
23
|
+
.rf-toc li {
|
|
24
|
+
margin: 0.125rem 0;
|
|
25
|
+
}
|
|
26
|
+
.rf-toc li[data-level="3"] {
|
|
27
|
+
padding-left: 1rem;
|
|
28
|
+
}
|
|
29
|
+
.rf-toc li[data-level="4"] {
|
|
30
|
+
padding-left: 2rem;
|
|
31
|
+
}
|
|
32
|
+
.rf-toc a {
|
|
33
|
+
color: var(--rf-color-text);
|
|
34
|
+
text-decoration: none;
|
|
35
|
+
font-size: 0.85rem;
|
|
36
|
+
display: inline-block;
|
|
37
|
+
padding: 0.2rem 0;
|
|
38
|
+
transition: color 150ms ease;
|
|
39
|
+
}
|
|
40
|
+
.rf-toc a:hover {
|
|
41
|
+
color: var(--rf-color-primary);
|
|
42
|
+
text-decoration: none;
|
|
43
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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}
|
|
@@ -0,0 +1,50 @@
|
|
|
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}
|
|
@@ -0,0 +1,121 @@
|
|
|
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>
|
|
@@ -0,0 +1,88 @@
|
|
|
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}
|