@dominikcz/greg 0.9.27
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 +397 -0
- package/bin/greg.js +241 -0
- package/bin/init.js +351 -0
- package/bin/templates/docs/getting-started.md +47 -0
- package/bin/templates/docs/index.md +11 -0
- package/bin/templates/greg.config.js +39 -0
- package/bin/templates/greg.config.ts +38 -0
- package/bin/templates/index.html +16 -0
- package/bin/templates/src/App.svelte +5 -0
- package/bin/templates/src/app.css +20 -0
- package/bin/templates/src/main.js +9 -0
- package/bin/templates/svelte.config.js +1 -0
- package/bin/templates/tsconfig.json +21 -0
- package/bin/templates/vite.config.js +23 -0
- package/docs/__partials/markdown/examples/basic.md +4 -0
- package/docs/__partials/markdown/examples/diff.md +10 -0
- package/docs/__partials/markdown/examples/focus.md +5 -0
- package/docs/__partials/markdown/examples/language-title.md +3 -0
- package/docs/__partials/markdown/examples/line-highlighting.md +5 -0
- package/docs/__partials/markdown/examples/line-numbers.md +5 -0
- package/docs/__partials/note.md +4 -0
- package/docs/guide/__shared-warning.md +4 -0
- package/docs/guide/asset-handling.md +88 -0
- package/docs/guide/deploying.md +162 -0
- package/docs/guide/getting-started.md +334 -0
- package/docs/guide/index.md +23 -0
- package/docs/guide/localization.md +290 -0
- package/docs/guide/markdown/code.md +95 -0
- package/docs/guide/markdown/components-and-mermaid.md +43 -0
- package/docs/guide/markdown/containers.md +110 -0
- package/docs/guide/markdown/header-anchors.md +34 -0
- package/docs/guide/markdown/includes.md +84 -0
- package/docs/guide/markdown/index.md +20 -0
- package/docs/guide/markdown/inline-attributes.md +21 -0
- package/docs/guide/markdown/links-and-toc.md +64 -0
- package/docs/guide/markdown/math.md +54 -0
- package/docs/guide/markdown/syntax-highlighting.md +75 -0
- package/docs/guide/routing.md +150 -0
- package/docs/guide/using-svelte.md +88 -0
- package/docs/guide/versioning.md +281 -0
- package/docs/incompatibilities.md +48 -0
- package/docs/index.md +43 -0
- package/docs/reference/badge.md +100 -0
- package/docs/reference/carbon-ads.md +46 -0
- package/docs/reference/code-group.md +126 -0
- package/docs/reference/home-page.md +232 -0
- package/docs/reference/index.md +18 -0
- package/docs/reference/markdowndocs.md +275 -0
- package/docs/reference/outline.md +79 -0
- package/docs/reference/search.md +263 -0
- package/docs/reference/steps.md +200 -0
- package/docs/reference/team-page.md +189 -0
- package/docs/reference/theme.md +150 -0
- package/fakeDocsGenerator/generate_docs.js +310 -0
- package/package.json +92 -0
- package/scripts/build-versions.js +609 -0
- package/scripts/generate-static.js +79 -0
- package/scripts/render-markdown.js +420 -0
- package/src/lib/MarkdownDocs/AiChat.svelte +936 -0
- package/src/lib/MarkdownDocs/BackToTop.svelte +68 -0
- package/src/lib/MarkdownDocs/Breadcrumb.svelte +68 -0
- package/src/lib/MarkdownDocs/DocsNavigation.svelte +149 -0
- package/src/lib/MarkdownDocs/DocsSiteHeader.svelte +758 -0
- package/src/lib/MarkdownDocs/DocsVersionSwitcher.svelte +103 -0
- package/src/lib/MarkdownDocs/MarkdownDocs.svelte +2115 -0
- package/src/lib/MarkdownDocs/MarkdownRenderer.svelte +487 -0
- package/src/lib/MarkdownDocs/Outline.svelte +238 -0
- package/src/lib/MarkdownDocs/PrevNext.svelte +115 -0
- package/src/lib/MarkdownDocs/SearchModal.svelte +1241 -0
- package/src/lib/MarkdownDocs/TreeView.svelte +32 -0
- package/src/lib/MarkdownDocs/TreeViewItem.svelte +219 -0
- package/src/lib/MarkdownDocs/VersionOutdatedNotice.svelte +72 -0
- package/src/lib/MarkdownDocs/__tests__/codeDirectives.test.js +54 -0
- package/src/lib/MarkdownDocs/__tests__/common.test.js +41 -0
- package/src/lib/MarkdownDocs/__tests__/docsExamplesLint.test.js +77 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/__partial-basic.md +3 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/snippet.js +9 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/includes/part.md +11 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/includes/wrapper.md +5 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.js +8 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.md +5 -0
- package/src/lib/MarkdownDocs/__tests__/helpers.js +67 -0
- package/src/lib/MarkdownDocs/__tests__/localeUtils.test.js +204 -0
- package/src/lib/MarkdownDocs/__tests__/markdown.test.js +704 -0
- package/src/lib/MarkdownDocs/__tests__/markdownRendererRuntime.test.js +65 -0
- package/src/lib/MarkdownDocs/__tests__/searchIndexBuilder.test.js +117 -0
- package/src/lib/MarkdownDocs/__tests__/sqliteStore.test.js +202 -0
- package/src/lib/MarkdownDocs/__tests__/useRouter.test.js +16 -0
- package/src/lib/MarkdownDocs/ai/adapters/customAdapter.js +14 -0
- package/src/lib/MarkdownDocs/ai/adapters/customAdapter.ts +43 -0
- package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.js +81 -0
- package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.ts +116 -0
- package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.js +92 -0
- package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.ts +137 -0
- package/src/lib/MarkdownDocs/ai/aiProvider.ts +31 -0
- package/src/lib/MarkdownDocs/ai/characters.js +52 -0
- package/src/lib/MarkdownDocs/ai/characters.ts +69 -0
- package/src/lib/MarkdownDocs/ai/chunkStore.ts +25 -0
- package/src/lib/MarkdownDocs/ai/chunker.js +85 -0
- package/src/lib/MarkdownDocs/ai/chunker.ts +135 -0
- package/src/lib/MarkdownDocs/ai/docLinker.js +26 -0
- package/src/lib/MarkdownDocs/ai/docLinker.ts +36 -0
- package/src/lib/MarkdownDocs/ai/promptBuilder.js +33 -0
- package/src/lib/MarkdownDocs/ai/promptBuilder.ts +53 -0
- package/src/lib/MarkdownDocs/ai/ragPipeline.js +54 -0
- package/src/lib/MarkdownDocs/ai/ragPipeline.ts +106 -0
- package/src/lib/MarkdownDocs/ai/stores/memoryStore.js +88 -0
- package/src/lib/MarkdownDocs/ai/stores/memoryStore.ts +112 -0
- package/src/lib/MarkdownDocs/ai/stores/sqliteStore.ts +372 -0
- package/src/lib/MarkdownDocs/ai/types.ts +71 -0
- package/src/lib/MarkdownDocs/aiServer.js +288 -0
- package/src/lib/MarkdownDocs/codeDirectives.js +191 -0
- package/src/lib/MarkdownDocs/codeFenceInfo.js +45 -0
- package/src/lib/MarkdownDocs/codeGroup.ts +46 -0
- package/src/lib/MarkdownDocs/common.ts +47 -0
- package/src/lib/MarkdownDocs/docsUtils.js +281 -0
- package/src/lib/MarkdownDocs/index.plugins.js +22 -0
- package/src/lib/MarkdownDocs/layouts/LayoutDoc.svelte +8 -0
- package/src/lib/MarkdownDocs/layouts/LayoutHome.svelte +58 -0
- package/src/lib/MarkdownDocs/layouts/LayoutPage.svelte +9 -0
- package/src/lib/MarkdownDocs/loadGregConfig.js +82 -0
- package/src/lib/MarkdownDocs/localeUtils.ts +682 -0
- package/src/lib/MarkdownDocs/markdownRendererRuntime.ts +314 -0
- package/src/lib/MarkdownDocs/mermaidThemes.js +319 -0
- package/src/lib/MarkdownDocs/navigationUtils.js +22 -0
- package/src/lib/MarkdownDocs/rehypeCodeGroup.js +326 -0
- package/src/lib/MarkdownDocs/rehypeCodeTitle.js +96 -0
- package/src/lib/MarkdownDocs/rehypeToc.js +170 -0
- package/src/lib/MarkdownDocs/remarkCodeMeta.js +22 -0
- package/src/lib/MarkdownDocs/remarkContainers.js +329 -0
- package/src/lib/MarkdownDocs/remarkCustomAnchors.js +42 -0
- package/src/lib/MarkdownDocs/remarkEscapeSvelte.js +33 -0
- package/src/lib/MarkdownDocs/remarkGlobalComponents.js +65 -0
- package/src/lib/MarkdownDocs/remarkImports.js +461 -0
- package/src/lib/MarkdownDocs/remarkImportsBrowser.js +349 -0
- package/src/lib/MarkdownDocs/remarkInlineAttrs.js +95 -0
- package/src/lib/MarkdownDocs/remarkMathToHtml.js +138 -0
- package/src/lib/MarkdownDocs/searchIndexBuilder.js +497 -0
- package/src/lib/MarkdownDocs/searchServer.js +263 -0
- package/src/lib/MarkdownDocs/treeViewTypes.ts +11 -0
- package/src/lib/MarkdownDocs/useRouter.svelte.ts +114 -0
- package/src/lib/MarkdownDocs/useSplitter.svelte.ts +33 -0
- package/src/lib/MarkdownDocs/versioningDefaults.js +20 -0
- package/src/lib/MarkdownDocs/vitePluginAiServer.js +204 -0
- package/src/lib/MarkdownDocs/vitePluginCopyDocs.js +153 -0
- package/src/lib/MarkdownDocs/vitePluginFrontmatter.js +109 -0
- package/src/lib/MarkdownDocs/vitePluginGregConfig.js +108 -0
- package/src/lib/MarkdownDocs/vitePluginSearchIndex.js +57 -0
- package/src/lib/MarkdownDocs/vitePluginSearchServer.js +190 -0
- package/src/lib/components/Badge.svelte +59 -0
- package/src/lib/components/Button.svelte +138 -0
- package/src/lib/components/CarbonAds.svelte +99 -0
- package/src/lib/components/CodeGroup.svelte +102 -0
- package/src/lib/components/Feature.svelte +209 -0
- package/src/lib/components/Features.svelte +123 -0
- package/src/lib/components/Hero.svelte +399 -0
- package/src/lib/components/Image.svelte +128 -0
- package/src/lib/components/Link.svelte +105 -0
- package/src/lib/components/SocialLink.svelte +84 -0
- package/src/lib/components/SocialLinks.svelte +33 -0
- package/src/lib/components/Steps.svelte +143 -0
- package/src/lib/components/TeamMember.svelte +273 -0
- package/src/lib/components/TeamMembers.svelte +81 -0
- package/src/lib/components/TeamPage.svelte +65 -0
- package/src/lib/components/TeamPageSection.svelte +108 -0
- package/src/lib/components/TeamPageTitle.svelte +89 -0
- package/src/lib/components/index.js +24 -0
- package/src/lib/portal/context.js +12 -0
- package/src/lib/portal/index.js +3 -0
- package/src/lib/portal/portal.svelte +14 -0
- package/src/lib/portal/slot.svelte +8 -0
- package/src/lib/scss/__code.scss +128 -0
- package/src/lib/scss/__containers.scss +99 -0
- package/src/lib/scss/__markdown.scss +447 -0
- package/src/lib/scss/__scrollbar.scss +60 -0
- package/src/lib/scss/__steps.scss +100 -0
- package/src/lib/scss/__theme.scss +238 -0
- package/src/lib/scss/__toc.scss +55 -0
- package/src/lib/scss/__utilities.scss +7 -0
- package/src/lib/scss/greg.scss +9 -0
- package/src/lib/spinner/spinner.svelte +42 -0
- package/svelte.config.js +146 -0
- package/types/index.d.ts +456 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
tag?: string;
|
|
6
|
+
size?: "medium" | "big";
|
|
7
|
+
theme?: "brand" | "alt" | "sponsor";
|
|
8
|
+
text?: string;
|
|
9
|
+
href?: string;
|
|
10
|
+
target?: string;
|
|
11
|
+
rel?: string;
|
|
12
|
+
onclick?: (e: MouseEvent) => void;
|
|
13
|
+
children?: Snippet;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
tag,
|
|
18
|
+
size = "medium",
|
|
19
|
+
theme = "brand",
|
|
20
|
+
text,
|
|
21
|
+
href,
|
|
22
|
+
target,
|
|
23
|
+
rel,
|
|
24
|
+
onclick,
|
|
25
|
+
children,
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
const EXTERNAL_RE = /^(?:[a-z][a-z\d+\-.]*:|\/\/)/i;
|
|
29
|
+
|
|
30
|
+
let resolvedTag = $derived(tag ?? (href ? "a" : "button"));
|
|
31
|
+
let isExternal = $derived(!!href && EXTERNAL_RE.test(href));
|
|
32
|
+
let resolvedTarget = $derived(
|
|
33
|
+
target ?? (isExternal ? "_blank" : undefined),
|
|
34
|
+
);
|
|
35
|
+
let resolvedRel = $derived(rel ?? (isExternal ? "noreferrer" : undefined));
|
|
36
|
+
let resolvedRole = $derived(
|
|
37
|
+
resolvedTag === "a" || resolvedTag === "button" ? undefined : "button",
|
|
38
|
+
);
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<svelte:element
|
|
42
|
+
this={resolvedTag}
|
|
43
|
+
class="Button {size} {theme}"
|
|
44
|
+
{href}
|
|
45
|
+
target={resolvedTarget}
|
|
46
|
+
rel={resolvedRel}
|
|
47
|
+
role={resolvedRole}
|
|
48
|
+
{onclick}
|
|
49
|
+
>
|
|
50
|
+
{#if children}
|
|
51
|
+
{@render children()}
|
|
52
|
+
{:else}
|
|
53
|
+
{text}
|
|
54
|
+
{/if}
|
|
55
|
+
</svelte:element>
|
|
56
|
+
|
|
57
|
+
<style>
|
|
58
|
+
.Button {
|
|
59
|
+
display: inline-block;
|
|
60
|
+
border: 1px solid transparent;
|
|
61
|
+
text-align: center;
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
white-space: nowrap;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
text-decoration: none;
|
|
66
|
+
transition:
|
|
67
|
+
color 0.25s,
|
|
68
|
+
border-color 0.25s,
|
|
69
|
+
background-color 0.25s;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.Button:active {
|
|
73
|
+
transition:
|
|
74
|
+
color 0.1s,
|
|
75
|
+
border-color 0.1s,
|
|
76
|
+
background-color 0.1s;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.Button.medium {
|
|
80
|
+
border-radius: 20px;
|
|
81
|
+
padding: 0 20px;
|
|
82
|
+
line-height: 38px;
|
|
83
|
+
font-size: 14px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.Button.big {
|
|
87
|
+
border-radius: 24px;
|
|
88
|
+
padding: 0 24px;
|
|
89
|
+
line-height: 46px;
|
|
90
|
+
font-size: 16px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* brand */
|
|
94
|
+
.Button.brand {
|
|
95
|
+
border-color: var(--greg-accent);
|
|
96
|
+
color: var(--greg-menu-active-color);
|
|
97
|
+
background-color: var(--greg-accent);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.Button.brand:hover {
|
|
101
|
+
border-color: color-mix(in srgb, var(--greg-accent) 80%, white);
|
|
102
|
+
background-color: color-mix(in srgb, var(--greg-accent) 80%, white);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.Button.brand:active {
|
|
106
|
+
border-color: color-mix(in srgb, var(--greg-accent) 90%, black);
|
|
107
|
+
background-color: color-mix(in srgb, var(--greg-accent) 90%, black);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* alt */
|
|
111
|
+
.Button.alt {
|
|
112
|
+
border-color: var(--greg-border-color);
|
|
113
|
+
color: var(--greg-color);
|
|
114
|
+
background-color: var(--greg-menu-background);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.Button.alt:hover {
|
|
118
|
+
border-color: var(--greg-accent);
|
|
119
|
+
color: var(--greg-accent);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.Button.alt:active {
|
|
123
|
+
background-color: var(--greg-accent-light);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* sponsor */
|
|
127
|
+
.Button.sponsor {
|
|
128
|
+
border-color: var(--greg-sponsor-color);
|
|
129
|
+
color: var(--greg-sponsor-color);
|
|
130
|
+
background-color: transparent;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.Button.sponsor:hover {
|
|
134
|
+
border-color: var(--greg-sponsor-color);
|
|
135
|
+
color: var(--greg-sponsor-color);
|
|
136
|
+
background-color: var(--greg-sponsor-bg);
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
code: string;
|
|
6
|
+
placement: string;
|
|
7
|
+
/** Current SPA route — when it changes, Carbon Ads is refreshed. */
|
|
8
|
+
active?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let { code, placement, active }: Props = $props();
|
|
12
|
+
|
|
13
|
+
let container: HTMLDivElement;
|
|
14
|
+
let initialized = false;
|
|
15
|
+
|
|
16
|
+
onMount(() => {
|
|
17
|
+
if (!initialized) {
|
|
18
|
+
initialized = true;
|
|
19
|
+
const s = document.createElement("script");
|
|
20
|
+
s.id = "_carbonads_js";
|
|
21
|
+
s.src = `//cdn.carbonads.com/carbon.js?serve=${code}&placement=${placement}`;
|
|
22
|
+
s.async = true;
|
|
23
|
+
container.appendChild(s);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
$effect(() => {
|
|
28
|
+
// Track `active` so the effect re-runs on page navigation.
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
30
|
+
active;
|
|
31
|
+
if (initialized) {
|
|
32
|
+
(window as any)._carbonads?.refresh();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<div class="CarbonAds" bind:this={container}></div>
|
|
38
|
+
|
|
39
|
+
<style>
|
|
40
|
+
.CarbonAds {
|
|
41
|
+
display: flex;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
align-items: center;
|
|
44
|
+
padding: 24px;
|
|
45
|
+
border-radius: 12px;
|
|
46
|
+
min-height: 256px;
|
|
47
|
+
text-align: center;
|
|
48
|
+
line-height: 18px;
|
|
49
|
+
font-size: 12px;
|
|
50
|
+
font-weight: 500;
|
|
51
|
+
background-color: var(
|
|
52
|
+
--greg-carbon-ads-bg-color,
|
|
53
|
+
var(--greg-menu-background)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.CarbonAds :global(img) {
|
|
58
|
+
margin: 0 auto;
|
|
59
|
+
border-radius: 6px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.CarbonAds :global(.carbon-text) {
|
|
63
|
+
display: block;
|
|
64
|
+
margin: 0 auto;
|
|
65
|
+
padding-top: 12px;
|
|
66
|
+
color: var(--greg-carbon-ads-text-color, var(--greg-color));
|
|
67
|
+
transition: color 0.25s;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.CarbonAds :global(.carbon-text:hover) {
|
|
71
|
+
color: var(--greg-carbon-ads-hover-text-color, var(--greg-accent));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.CarbonAds :global(.carbon-poweredby) {
|
|
75
|
+
display: block;
|
|
76
|
+
padding-top: 6px;
|
|
77
|
+
font-size: 11px;
|
|
78
|
+
font-weight: 500;
|
|
79
|
+
color: var(
|
|
80
|
+
--greg-carbon-ads-poweredby-color,
|
|
81
|
+
var(--greg-menu-section-color)
|
|
82
|
+
);
|
|
83
|
+
text-transform: uppercase;
|
|
84
|
+
transition: color 0.25s;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.CarbonAds :global(.carbon-poweredby:hover) {
|
|
88
|
+
color: var(--greg-carbon-ads-hover-poweredby-color, var(--greg-accent));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Carbon injects multiple wrappers — show only the first */
|
|
92
|
+
.CarbonAds :global(> div) {
|
|
93
|
+
display: none;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.CarbonAds :global(> div:first-of-type) {
|
|
97
|
+
display: block;
|
|
98
|
+
}
|
|
99
|
+
</style>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
type Props = {
|
|
3
|
+
tabs: string[];
|
|
4
|
+
blocks: string[];
|
|
5
|
+
initialActive?: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
let { tabs = [], blocks = [], initialActive = 0 }: Props = $props();
|
|
9
|
+
|
|
10
|
+
let activeIndex = $state(0);
|
|
11
|
+
|
|
12
|
+
// Keep active tab index in sync when parent-provided inputs change.
|
|
13
|
+
$effect(() => {
|
|
14
|
+
const maxIndex = Math.max(0, tabs.length - 1);
|
|
15
|
+
activeIndex = Math.max(0, Math.min(initialActive, maxIndex));
|
|
16
|
+
});
|
|
17
|
+
const uid = `rcg-hydrated-${Math.random().toString(36).slice(2, 10)}`;
|
|
18
|
+
|
|
19
|
+
function activate(index: number) {
|
|
20
|
+
if (index < 0 || index >= tabs.length) return;
|
|
21
|
+
activeIndex = index;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function handleKeydown(event: KeyboardEvent, index: number) {
|
|
25
|
+
const last = tabs.length - 1;
|
|
26
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
activate(index);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (event.key === "ArrowRight") {
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
const next = index >= last ? 0 : index + 1;
|
|
35
|
+
activate(next);
|
|
36
|
+
requestAnimationFrame(() => {
|
|
37
|
+
(document.getElementById(`${uid}-tab-${next}`) as HTMLButtonElement | null)?.focus();
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (event.key === "ArrowLeft") {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
const prev = index <= 0 ? last : index - 1;
|
|
45
|
+
activate(prev);
|
|
46
|
+
requestAnimationFrame(() => {
|
|
47
|
+
(document.getElementById(`${uid}-tab-${prev}`) as HTMLButtonElement | null)?.focus();
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (event.key === "Home") {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
activate(0);
|
|
55
|
+
requestAnimationFrame(() => {
|
|
56
|
+
(document.getElementById(`${uid}-tab-0`) as HTMLButtonElement | null)?.focus();
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (event.key === "End") {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
activate(last);
|
|
64
|
+
requestAnimationFrame(() => {
|
|
65
|
+
(document.getElementById(`${uid}-tab-${last}`) as HTMLButtonElement | null)?.focus();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<div class="rehype-code-group">
|
|
72
|
+
<div class="rcg-tab-container" role="tablist">
|
|
73
|
+
{#each tabs as label, index}
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
class="rcg-tab"
|
|
77
|
+
class:active={index === activeIndex}
|
|
78
|
+
role="tab"
|
|
79
|
+
aria-selected={index === activeIndex ? "true" : "false"}
|
|
80
|
+
aria-controls={`${uid}-block-${index}`}
|
|
81
|
+
id={`${uid}-tab-${index}`}
|
|
82
|
+
onclick={() => activate(index)}
|
|
83
|
+
onkeydown={(event) => handleKeydown(event, index)}
|
|
84
|
+
>
|
|
85
|
+
{label}
|
|
86
|
+
</button>
|
|
87
|
+
{/each}
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{#each blocks as block, index}
|
|
91
|
+
<div
|
|
92
|
+
class="rcg-block"
|
|
93
|
+
class:active={index === activeIndex}
|
|
94
|
+
role="tabpanel"
|
|
95
|
+
aria-labelledby={`${uid}-tab-${index}`}
|
|
96
|
+
id={`${uid}-block-${index}`}
|
|
97
|
+
hidden={index === activeIndex ? undefined : true}
|
|
98
|
+
>
|
|
99
|
+
{@html block}
|
|
100
|
+
</div>
|
|
101
|
+
{/each}
|
|
102
|
+
</div>
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Image from "./Image.svelte";
|
|
3
|
+
import Link from "./Link.svelte";
|
|
4
|
+
|
|
5
|
+
type FeatureIcon =
|
|
6
|
+
| string
|
|
7
|
+
| {
|
|
8
|
+
src: string;
|
|
9
|
+
alt?: string;
|
|
10
|
+
width?: number | string;
|
|
11
|
+
height?: number | string;
|
|
12
|
+
wrap?: boolean;
|
|
13
|
+
}
|
|
14
|
+
| { dark: string; light: string; alt?: string; wrap?: boolean };
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
icon?: FeatureIcon;
|
|
18
|
+
title: string;
|
|
19
|
+
details?: string | string[];
|
|
20
|
+
link?: string;
|
|
21
|
+
linkText?: string;
|
|
22
|
+
rel?: string;
|
|
23
|
+
target?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let { icon, title, details, link, linkText, rel, target }: Props = $props();
|
|
27
|
+
|
|
28
|
+
function isImageIcon(
|
|
29
|
+
i: FeatureIcon,
|
|
30
|
+
): i is {
|
|
31
|
+
src: string;
|
|
32
|
+
alt?: string;
|
|
33
|
+
width?: number | string;
|
|
34
|
+
height?: number | string;
|
|
35
|
+
wrap?: boolean;
|
|
36
|
+
} {
|
|
37
|
+
return typeof i === "object" && ("src" in i || "dark" in i);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function shouldWrap(i: FeatureIcon): boolean {
|
|
41
|
+
if (typeof i === "object" && "wrap" in i) return !!(i as any).wrap;
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Resolved per render — avoid TS cast inside templates
|
|
46
|
+
let iconImg = $derived(isImageIcon(icon!) ? (icon as any) : null);
|
|
47
|
+
let iconAlt = $derived(iconImg?.alt ?? "");
|
|
48
|
+
let iconWidth = $derived(iconImg?.width ?? 48);
|
|
49
|
+
let iconHeight = $derived(iconImg?.height ?? 48);
|
|
50
|
+
let iconWrapped = $derived(iconImg && shouldWrap(icon!));
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<Link
|
|
54
|
+
class="Feature"
|
|
55
|
+
href={link}
|
|
56
|
+
{rel}
|
|
57
|
+
{target}
|
|
58
|
+
noIcon
|
|
59
|
+
tag={link ? "a" : "div"}
|
|
60
|
+
>
|
|
61
|
+
<article class="box">
|
|
62
|
+
{#if icon}
|
|
63
|
+
{#if iconWrapped}
|
|
64
|
+
<div class="icon">
|
|
65
|
+
<Image
|
|
66
|
+
image={iconImg}
|
|
67
|
+
alt={iconAlt}
|
|
68
|
+
height={iconHeight}
|
|
69
|
+
width={iconWidth}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
{:else if iconImg}
|
|
73
|
+
<Image
|
|
74
|
+
image={iconImg}
|
|
75
|
+
alt={iconAlt}
|
|
76
|
+
height={iconHeight}
|
|
77
|
+
width={iconWidth}
|
|
78
|
+
/>
|
|
79
|
+
{:else if typeof icon === "string"}
|
|
80
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
81
|
+
<div class="icon">{@html icon}</div>
|
|
82
|
+
{/if}
|
|
83
|
+
{/if}
|
|
84
|
+
|
|
85
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
86
|
+
<h2 class="title">{@html title}</h2>
|
|
87
|
+
|
|
88
|
+
{#if Array.isArray(details)}
|
|
89
|
+
<ul class="details">
|
|
90
|
+
{#each details as item}
|
|
91
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
92
|
+
<li>{@html item}</li>
|
|
93
|
+
{/each}
|
|
94
|
+
</ul>
|
|
95
|
+
{:else if details}
|
|
96
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
97
|
+
<p class="details">{@html details}</p>
|
|
98
|
+
{/if}
|
|
99
|
+
|
|
100
|
+
{#if linkText}
|
|
101
|
+
<div class="link-text">
|
|
102
|
+
<p class="link-text-value">
|
|
103
|
+
{linkText}
|
|
104
|
+
<svg
|
|
105
|
+
class="link-text-icon"
|
|
106
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
107
|
+
viewBox="0 0 24 24"
|
|
108
|
+
aria-hidden="true"
|
|
109
|
+
>
|
|
110
|
+
<path
|
|
111
|
+
d="M19.9,12.4c0.1-0.2,0.1-0.5,0-0.8c-0.1-0.1-0.1-0.2-0.2-0.3l-7-7c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.3,5.3H5c-0.6,0-1,0.4-1,1s0.4,1,1,1h11.6l-5.3,5.3c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3l7-7C19.8,12.6,19.9,12.5,19.9,12.4z"
|
|
112
|
+
/>
|
|
113
|
+
</svg>
|
|
114
|
+
</p>
|
|
115
|
+
</div>
|
|
116
|
+
{/if}
|
|
117
|
+
</article>
|
|
118
|
+
</Link>
|
|
119
|
+
|
|
120
|
+
<style>
|
|
121
|
+
:global(.Feature) {
|
|
122
|
+
display: block;
|
|
123
|
+
border: 1px solid var(--greg-border-color);
|
|
124
|
+
border-radius: 12px;
|
|
125
|
+
height: 100%;
|
|
126
|
+
background-color: var(--greg-menu-background);
|
|
127
|
+
transition:
|
|
128
|
+
border-color 0.25s,
|
|
129
|
+
background-color 0.25s;
|
|
130
|
+
text-decoration: none;
|
|
131
|
+
color: inherit;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
:global(.Feature.link:hover) {
|
|
135
|
+
border-color: var(--greg-accent);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.box {
|
|
139
|
+
display: flex;
|
|
140
|
+
flex-direction: column;
|
|
141
|
+
padding: 24px;
|
|
142
|
+
height: 100%;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.icon {
|
|
146
|
+
display: flex;
|
|
147
|
+
justify-content: center;
|
|
148
|
+
align-items: center;
|
|
149
|
+
margin-bottom: 20px;
|
|
150
|
+
border-radius: 6px;
|
|
151
|
+
background-color: var(--greg-background);
|
|
152
|
+
width: 48px;
|
|
153
|
+
height: 48px;
|
|
154
|
+
font-size: 24px;
|
|
155
|
+
transition: background-color 0.25s;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.title {
|
|
159
|
+
line-height: 24px;
|
|
160
|
+
font-size: 16px;
|
|
161
|
+
font-weight: 600;
|
|
162
|
+
margin: 0 0 0;
|
|
163
|
+
border: none;
|
|
164
|
+
padding: 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.details {
|
|
168
|
+
flex-grow: 1;
|
|
169
|
+
padding-top: 8px;
|
|
170
|
+
line-height: 24px;
|
|
171
|
+
font-size: 14px;
|
|
172
|
+
font-weight: 500;
|
|
173
|
+
color: var(--greg-menu-section-color);
|
|
174
|
+
margin: 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
ul.details {
|
|
178
|
+
list-style-type: disc;
|
|
179
|
+
padding-left: 14px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.link-text {
|
|
183
|
+
padding-top: 8px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.link-text-value {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: 4px;
|
|
190
|
+
font-size: 14px;
|
|
191
|
+
font-weight: 500;
|
|
192
|
+
color: var(--greg-accent);
|
|
193
|
+
margin: 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.link-text-icon {
|
|
197
|
+
width: 14px;
|
|
198
|
+
height: 14px;
|
|
199
|
+
fill: currentColor;
|
|
200
|
+
flex-shrink: 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
:global(.box > .Image) {
|
|
204
|
+
margin-bottom: 20px;
|
|
205
|
+
width: 48px;
|
|
206
|
+
height: 48px;
|
|
207
|
+
object-fit: contain;
|
|
208
|
+
}
|
|
209
|
+
</style>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Feature from "./Feature.svelte";
|
|
3
|
+
|
|
4
|
+
type FeatureIcon =
|
|
5
|
+
| string
|
|
6
|
+
| {
|
|
7
|
+
src: string;
|
|
8
|
+
alt?: string;
|
|
9
|
+
width?: number | string;
|
|
10
|
+
height?: number | string;
|
|
11
|
+
wrap?: boolean;
|
|
12
|
+
}
|
|
13
|
+
| { dark: string; light: string; alt?: string };
|
|
14
|
+
|
|
15
|
+
type FeatureItem = {
|
|
16
|
+
icon?: FeatureIcon;
|
|
17
|
+
title: string;
|
|
18
|
+
details?: string | string[];
|
|
19
|
+
link?: string;
|
|
20
|
+
linkText?: string;
|
|
21
|
+
rel?: string;
|
|
22
|
+
target?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type Props = {
|
|
26
|
+
features: FeatureItem[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let { features }: Props = $props();
|
|
30
|
+
|
|
31
|
+
let grid = $derived.by(() => {
|
|
32
|
+
const n = features?.length ?? 0;
|
|
33
|
+
if (!n) return "";
|
|
34
|
+
if (n === 2) return "grid-2";
|
|
35
|
+
if (n === 3) return "grid-3";
|
|
36
|
+
if (n % 3 === 0) return "grid-6";
|
|
37
|
+
if (n > 3) return "grid-4";
|
|
38
|
+
return "";
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
{#if features?.length}
|
|
43
|
+
<div class="Features">
|
|
44
|
+
<div class="container">
|
|
45
|
+
<div class="items">
|
|
46
|
+
{#each features as feature (feature.title)}
|
|
47
|
+
<div class="item {grid}">
|
|
48
|
+
<Feature
|
|
49
|
+
icon={feature.icon}
|
|
50
|
+
title={feature.title}
|
|
51
|
+
details={feature.details}
|
|
52
|
+
link={feature.link}
|
|
53
|
+
linkText={feature.linkText}
|
|
54
|
+
rel={feature.rel}
|
|
55
|
+
target={feature.target}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
{/each}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
{/if}
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.Features {
|
|
66
|
+
position: relative;
|
|
67
|
+
padding: 0 24px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@media (min-width: 640px) {
|
|
71
|
+
.Features {
|
|
72
|
+
padding: 0 48px;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@media (min-width: 960px) {
|
|
77
|
+
.Features {
|
|
78
|
+
padding: 0 64px;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.container {
|
|
83
|
+
margin: 0 auto;
|
|
84
|
+
max-width: 1152px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.items {
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-wrap: wrap;
|
|
90
|
+
margin: -8px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.item {
|
|
94
|
+
padding: 8px;
|
|
95
|
+
width: 100%;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@media (min-width: 640px) {
|
|
99
|
+
.item.grid-2,
|
|
100
|
+
.item.grid-4,
|
|
101
|
+
.item.grid-6 {
|
|
102
|
+
width: calc(100% / 2);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@media (min-width: 768px) {
|
|
107
|
+
.item.grid-2,
|
|
108
|
+
.item.grid-4 {
|
|
109
|
+
width: calc(100% / 2);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.item.grid-3,
|
|
113
|
+
.item.grid-6 {
|
|
114
|
+
width: calc(100% / 3);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@media (min-width: 960px) {
|
|
119
|
+
.item.grid-4 {
|
|
120
|
+
width: calc(100% / 4);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
</style>
|