reallysimpledocs 1.0.0-beta.2 → 1.0.0-beta.4

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 (36) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +11 -11
  3. package/package.json +10 -2
  4. package/src/astro/index.js +94 -3
  5. package/src/components/index.js +0 -2
  6. package/src/css/custom.css +45 -205
  7. package/src/runtime/components/CommandDialog.astro +20 -3
  8. package/src/runtime/components/DefaultContentHeader.astro +19 -0
  9. package/src/runtime/components/DefaultSidebarFooter.astro +2 -0
  10. package/src/runtime/components/DocsLayout.astro +100 -81
  11. package/src/runtime/components/Sidebar.astro +4 -0
  12. package/src/runtime/components/mdx/Callout.astro +36 -26
  13. package/src/runtime/components/mdx/Card.astro +8 -7
  14. package/src/runtime/components/mdx/CardGrid.astro +1 -1
  15. package/src/runtime/components/mdx/Code.astro +33 -13
  16. package/src/runtime/components/mdx/CodeGroup.astro +38 -14
  17. package/src/runtime/components/mdx/Preview.astro +16 -6
  18. package/src/runtime/components/mdx/Steps.astro +5 -1
  19. package/src/runtime/components/mdx/Tabs.astro +1 -1
  20. package/src/runtime/lib/docs.js +107 -36
  21. package/src/runtime/lib/highlight.js +27 -0
  22. package/src/runtime/lib/macros.js +5 -1
  23. package/src/runtime/lib/markdown-export.js +200 -0
  24. package/src/runtime/lib/markdown-exporters/index.js +144 -0
  25. package/src/runtime/lib/mdx-source.js +19 -0
  26. package/src/runtime/lib/preview-source-plugin.js +33 -0
  27. package/src/runtime/lib/site.js +0 -1
  28. package/src/runtime/pages/doc.astro +4 -1
  29. package/src/runtime/pages/llms-full.txt.ts +2 -2
  30. package/src/runtime/pages/llms.txt.ts +2 -2
  31. package/src/runtime/pages/markdown.ts +2 -2
  32. package/src/runtime/pages/search-index.ts +2 -2
  33. package/src/runtime/scripts/copy-code.js +51 -6
  34. package/src/site/ContentHeader.astro +33 -0
  35. package/src/runtime/components/mdx/Badge.astro +0 -15
  36. package/src/runtime/components/mdx/LinkButton.astro +0 -14
@@ -0,0 +1,19 @@
1
+ ---
2
+ import Moon from "lucide-static/icons/moon.svg?raw";
3
+ import Sun from "lucide-static/icons/sun.svg?raw";
4
+ ---
5
+
6
+ <nav class="hidden items-center gap-2 sm:flex">
7
+ <button
8
+ type="button"
9
+ aria-label="Toggle dark mode"
10
+ data-tooltip="Toggle dark mode"
11
+ data-side="bottom"
12
+ data-align="end"
13
+ onclick="document.dispatchEvent(new CustomEvent('basecoat:theme'))"
14
+ class="btn-sm-icon-ghost"
15
+ >
16
+ <span class="hidden dark:block"><Fragment set:html={Sun} /></span>
17
+ <span class="block dark:hidden"><Fragment set:html={Moon} /></span>
18
+ </button>
19
+ </nav>
@@ -4,12 +4,11 @@ import ArrowLeft from "lucide-static/icons/arrow-left.svg?raw";
4
4
  import ArrowRight from "lucide-static/icons/arrow-right.svg?raw";
5
5
  import Check from "lucide-static/icons/check.svg?raw";
6
6
  import Copy from "lucide-static/icons/copy.svg?raw";
7
- import Menu from "lucide-static/icons/menu.svg?raw";
8
- import Moon from "lucide-static/icons/moon.svg?raw";
9
7
  import PanelLeft from "lucide-static/icons/panel-left.svg?raw";
10
8
  import Search from "lucide-static/icons/search.svg?raw";
11
- import Sun from "lucide-static/icons/sun.svg?raw";
12
- import X from "lucide-static/icons/x.svg?raw";
9
+ import ChevronRight from "lucide-static/icons/chevron-right.svg?raw";
10
+ import Ellipsis from "lucide-static/icons/ellipsis.svg?raw";
11
+ import ContentHeader from "virtual:reallysimpledocs/components/ContentHeader";
13
12
  import ClaudeLogo from "../../icons/logos/claude.svg?raw";
14
13
  import CursorLogo from "../../icons/logos/cursor.svg?raw";
15
14
  import MarkdownLogo from "../../icons/logos/markdown.svg?raw";
@@ -18,18 +17,36 @@ import BaseLayout from "./BaseLayout.astro";
18
17
  import CommandDialog from "./CommandDialog.astro";
19
18
  import DropdownMenu from "./DropdownMenu.astro";
20
19
  import Sidebar from "./Sidebar.astro";
21
- import { buildMenus, getNavigation } from "../lib/docs.js";
20
+ import { buildMenus, getBreadcrumbs, getNavigation } from "../lib/docs.js";
22
21
  import { addSvgClass } from "../lib/html.js";
22
+ import { renderDropdownMenu } from "../lib/macros.js";
23
23
  import { routeFilePath } from "../lib/paths.js";
24
24
  import { getSite } from "../lib/site.js";
25
25
 
26
26
  const { page, headings } = Astro.props;
27
27
  const site = getSite(config);
28
28
  const navigation = getNavigation(config, page);
29
+ const breadcrumbs = getBreadcrumbs(config, page);
29
30
  const { sidebar } = buildMenus(config, page.slug);
31
+ const hasToc = headings.length > 0;
30
32
  const searchIndexUrl = routeFilePath(config.routeBase, "search-index.json");
33
+ const chevronRightIcon = addSvgClass(ChevronRight, "size-3.5");
34
+ const ellipsisIcon = addSvgClass(Ellipsis, "size-4");
31
35
  const panelLeftIcon = addSvgClass(PanelLeft, "size-4");
32
36
  const searchIcon = addSvgClass(Search, "size-4");
37
+ const breadcrumbItems =
38
+ breadcrumbs.length < 2
39
+ ? []
40
+ : breadcrumbs.length > 3
41
+ ? [
42
+ breadcrumbs[0],
43
+ {
44
+ type: "dropdown",
45
+ items: breadcrumbs.slice(1, -1),
46
+ },
47
+ breadcrumbs.at(-1),
48
+ ]
49
+ : breadcrumbs;
33
50
  const siteUrl = Astro.site?.toString().replace(/\/$/, "") || site.url || "";
34
51
  const pageUrl = `${siteUrl}${page.path}`;
35
52
  const llmPrompt = `Read from ${pageUrl} so I can ask questions about it.`;
@@ -84,74 +101,74 @@ const actionItems = [
84
101
  <Fragment set:html={panelLeftIcon} />
85
102
  </button>
86
103
 
104
+ {
105
+ breadcrumbItems.length > 0 && (
106
+ <nav class="hidden min-w-0 overflow-visible md:block" aria-label="Breadcrumb">
107
+ <ol class="flex min-w-0 items-center gap-1.5 overflow-visible text-sm text-muted-foreground">
108
+ {breadcrumbItems.map((crumb, index) => {
109
+ const current = index === breadcrumbItems.length - 1;
110
+ return (
111
+ <>
112
+ {index > 0 && (
113
+ <li class="shrink-0" aria-hidden="true">
114
+ <Fragment set:html={chevronRightIcon} />
115
+ </li>
116
+ )}
117
+ <li class="inline-flex min-w-0 items-center gap-1.5 overflow-visible">
118
+ {crumb.type === "dropdown" ? (
119
+ <Fragment
120
+ set:html={renderDropdownMenu({
121
+ id: `breadcrumb-${page.slug.replace(/[^a-z0-9_-]/gi, "-")}`,
122
+ trigger: ellipsisIcon,
123
+ items: crumb.items.map((item) => ({
124
+ label: item.label,
125
+ url: item.url,
126
+ })),
127
+ triggerAttrs: {
128
+ class: "flex size-4 items-center justify-center hover:text-foreground cursor-pointer",
129
+ "aria-label": "Show breadcrumb pages",
130
+ },
131
+ popoverAttrs: { "data-align": "start" },
132
+ })}
133
+ />
134
+ ) : current || !crumb.url ? (
135
+ <span class="truncate font-medium text-foreground">{crumb.label}</span>
136
+ ) : (
137
+ <a href={crumb.url} class="truncate transition-colors hover:text-foreground">
138
+ {crumb.label}
139
+ </a>
140
+ )}
141
+ </li>
142
+ </>
143
+ );
144
+ })}
145
+ </ol>
146
+ </nav>
147
+ )
148
+ }
149
+
87
150
  <button
88
151
  type="button"
89
- class="relative flex h-8 w-full min-w-0 max-w-72 cursor-text items-center gap-x-2 rounded-md border border-input bg-background px-3 py-1 text-base text-muted-foreground outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 dark:bg-input/30 sm:ml-auto md:text-sm"
152
+ class="btn-sm-icon-ghost ml-auto sm:hidden"
153
+ aria-label="Search"
154
+ data-tooltip="Search"
155
+ data-side="bottom"
90
156
  onclick="window.rsdOpenCommandSearch ? window.rsdOpenCommandSearch() : document.getElementById('command-search')?.showModal()"
91
157
  >
92
158
  <Fragment set:html={searchIcon} />
93
- Search the docs...
94
- <kbd class="kbd ml-auto -mr-1.5">⌘K</kbd>
95
159
  </button>
96
160
 
97
- <nav class="hidden items-center gap-2 sm:flex">
98
- {
99
- (site.links || []).map((link) => (
100
- <a
101
- class={link.attrs?.class || "btn-sm-outline"}
102
- href={link.url}
103
- aria-label={link.label}
104
- data-tooltip={link.label}
105
- data-side="bottom"
106
- target={link.attrs?.target}
107
- rel={link.attrs?.rel}
108
- >
109
- {link.icon && <Fragment set:html={link.icon} />}
110
- {!link.iconOnly && link.label}
111
- </a>
112
- ))
113
- }
114
- <button
115
- type="button"
116
- aria-label="Toggle dark mode"
117
- data-tooltip="Toggle dark mode"
118
- data-side="bottom"
119
- data-align="end"
120
- onclick="document.dispatchEvent(new CustomEvent('basecoat:theme'))"
121
- class="btn-sm-icon-outline"
122
- >
123
- <span class="hidden dark:block"><Fragment set:html={Sun} /></span>
124
- <span class="block dark:hidden"><Fragment set:html={Moon} /></span>
125
- </button>
126
- </nav>
161
+ <button
162
+ type="button"
163
+ class="relative hidden h-8 w-full min-w-0 max-w-72 cursor-text items-center gap-x-2 rounded-md border border-input bg-background px-3 py-1 text-base text-muted-foreground outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 dark:bg-input/30 sm:ml-auto sm:flex md:text-sm"
164
+ onclick="window.rsdOpenCommandSearch ? window.rsdOpenCommandSearch() : document.getElementById('command-search')?.showModal()"
165
+ >
166
+ <Fragment set:html={searchIcon} />
167
+ Search...
168
+ <kbd class="kbd ml-auto -mr-1.5">⌘K</kbd>
169
+ </button>
127
170
 
128
- <div class="sm:hidden">
129
- <button
130
- type="button"
131
- class="btn-sm-icon-ghost peer group"
132
- aria-label="Toggle menu"
133
- aria-expanded="false"
134
- aria-controls="mobile-menu"
135
- onclick="this.setAttribute('aria-expanded', this.getAttribute('aria-expanded') === 'true' ? 'false' : 'true')"
136
- >
137
- <span class="group-aria-expanded:hidden"><Fragment set:html={Menu} /></span>
138
- <span class="hidden group-aria-expanded:block"><Fragment set:html={X} /></span>
139
- </button>
140
- <nav
141
- id="mobile-menu"
142
- class="fixed inset-0 top-14 z-50 flex invisible -translate-y-16 scale-95 flex-col gap-2 bg-background p-4 opacity-0 transition-all pointer-events-none peer-aria-expanded:visible peer-aria-expanded:translate-y-0 peer-aria-expanded:scale-100 peer-aria-expanded:opacity-100 peer-aria-expanded:pointer-events-auto"
143
- data-mobile-menu
144
- >
145
- {
146
- (site.links || []).map((link) => (
147
- <a class="btn-lg-ghost justify-between" href={link.url} target={link.attrs?.target} rel={link.attrs?.rel}>
148
- <span>{link.label}</span>
149
- {link.icon && <Fragment set:html={link.icon} />}
150
- </a>
151
- ))
152
- }
153
- </nav>
154
- </div>
171
+ <ContentHeader site={site} page={page} navigation={navigation} config={config} />
155
172
  </div>
156
173
  </header>
157
174
 
@@ -221,22 +238,24 @@ const actionItems = [
221
238
  </footer>
222
239
  </div>
223
240
  </article>
224
- <aside class="hidden w-56 xl:block">
225
- <div class="docs-toc sticky top-20 space-y-2 text-sm [&_a]:text-muted-foreground [&_a]:hover:text-foreground [&_a[data-active='true']]:text-foreground [&_ol]:space-y-2 [&_ol_ol]:mt-2 [&_ol_ol]:pl-3" data-docs-toc>
226
- <h2 class="flex items-center gap-1 font-medium">
227
- On this page
228
- </h2>
229
- <ol>
230
- {
231
- headings.map((heading) => (
232
- <li class={heading.depth === 3 ? "pl-3" : ""}>
233
- <a href={`#${heading.id}`} data-toc-link>{heading.text}</a>
234
- </li>
235
- ))
236
- }
237
- </ol>
238
- </div>
239
- </aside>
241
+ {
242
+ hasToc && (
243
+ <aside class="hidden w-56 xl:block">
244
+ <div class="docs-toc sticky top-20 space-y-2 text-sm [&_a]:text-muted-foreground [&_a]:hover:text-foreground [&_a[data-active='true']]:text-foreground [&_ol]:space-y-2 [&_ol_ol]:mt-2 [&_ol_ol]:pl-3" data-docs-toc>
245
+ <h2 class="flex items-center gap-1 font-medium text-muted-foreground">On this page</h2>
246
+ <ol>
247
+ {headings.map((heading) => (
248
+ <li class={heading.depth === 3 ? "pl-3" : ""}>
249
+ <a href={`#${heading.id}`} data-toc-link>
250
+ {heading.text}
251
+ </a>
252
+ </li>
253
+ ))}
254
+ </ol>
255
+ </div>
256
+ </aside>
257
+ )
258
+ }
240
259
  </div>
241
260
  </main>
242
261
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import config from "virtual:reallysimpledocs/config";
3
3
  import SidebarHeader from "virtual:reallysimpledocs/components/SidebarHeader";
4
+ import SidebarFooter from "virtual:reallysimpledocs/components/SidebarFooter";
4
5
  import { renderSidebarContent } from "../lib/macros.js";
5
6
  import { getSite } from "../lib/site.js";
6
7
 
@@ -16,5 +17,8 @@ const site = getSite(config);
16
17
  <section class="scrollbar">
17
18
  <Fragment set:html={renderSidebarContent(menu, "sidebar-content")} />
18
19
  </section>
20
+ <footer>
21
+ <SidebarFooter site={site} />
22
+ </footer>
19
23
  </nav>
20
24
  </aside>
@@ -1,29 +1,23 @@
1
1
  ---
2
- import CircleAlert from "lucide-static/icons/circle-alert.svg?raw";
3
- import Info from "lucide-static/icons/info.svg?raw";
4
- import Lightbulb from "lucide-static/icons/lightbulb.svg?raw";
5
- import TriangleAlert from "lucide-static/icons/triangle-alert.svg?raw";
6
2
  import * as lucideIcons from "lucide-static";
7
3
  import { addSvgClass } from "../../lib/html.js";
8
4
 
9
- const { type, variant, title, icon: showIcon = true, class: className = "" } = Astro.props;
10
- const normalizedType = String(variant || type || "note").toLowerCase();
11
- const calloutType = normalizedType === "note" ? "info" : normalizedType;
12
- const destructive = calloutType === "danger";
13
- const labels = {
14
- note: "Note",
15
- info: "Info",
16
- tip: "Tip",
17
- warning: "Warning",
18
- danger: "Danger",
19
- };
20
- const icons = {
21
- note: Info,
22
- info: Info,
23
- tip: Lightbulb,
24
- warning: TriangleAlert,
25
- danger: CircleAlert,
26
- };
5
+ const { type, title = "Note", icon: iconName, action, class: className = "" } = Astro.props;
6
+ const normalizedType = String(type || "").toLowerCase();
7
+ const calloutType = normalizedType === "warning" || normalizedType === "destructive" ? normalizedType : "";
8
+ const destructive = calloutType === "destructive";
9
+ const actionLabel = action?.label;
10
+ const actionClass = action?.class || "btn-sm-secondary";
11
+ const actionAttrs = action
12
+ ? {
13
+ ...action.attrs,
14
+ href: action.href,
15
+ target: action.target,
16
+ rel: action.rel,
17
+ onclick: action.onclick,
18
+ class: actionClass,
19
+ }
20
+ : {};
27
21
 
28
22
  function resolveNamedIcon(name) {
29
23
  if (!name || typeof name !== "string") return "";
@@ -39,16 +33,32 @@ function resolveNamedIcon(name) {
39
33
  return lucideIcons[pascalName] || "";
40
34
  }
41
35
 
42
- const iconSource =
43
- showIcon === false ? "" : typeof showIcon === "string" ? resolveNamedIcon(showIcon) : icons[normalizedType] || Info;
36
+ const iconSource = typeof iconName === "string" ? resolveNamedIcon(iconName) : "";
44
37
  const icon = addSvgClass(iconSource, "size-4");
45
- const heading = title || labels[normalizedType] || labels.note;
38
+ const heading = title;
39
+ const variantClass =
40
+ calloutType === "warning"
41
+ ? "[&>svg]:text-amber-700 [&>h3]:text-amber-700 [&>section]:text-amber-700 dark:[&>svg]:text-amber-300 dark:[&>h3]:text-amber-300 dark:[&>section]:text-amber-300"
42
+ : destructive
43
+ ? "[&>svg]:text-destructive [&>h3]:text-destructive [&>section]:text-destructive"
44
+ : "";
46
45
  ---
47
46
 
48
- <div class:list={[destructive ? "alert-destructive" : "alert", className]} data-variant={calloutType}>
47
+ <div class:list={[destructive ? "alert-destructive" : "alert", "my-6", variantClass, className]}>
49
48
  {icon && <Fragment set:html={icon} />}
50
49
  <h3>{heading}</h3>
51
50
  <section>
52
51
  <slot />
53
52
  </section>
53
+ {
54
+ actionLabel && (
55
+ <footer>
56
+ {action?.href ? (
57
+ <a {...actionAttrs}>{actionLabel}</a>
58
+ ) : (
59
+ <button type="button" {...actionAttrs}>{actionLabel}</button>
60
+ )}
61
+ </footer>
62
+ )
63
+ }
54
64
  </div>
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  const { title, description, href, class: className = "" } = Astro.props;
3
3
  const Tag = href ? "a" : "article";
4
+ const classes = [
5
+ "my-6 block w-full rounded-lg border bg-card p-3 text-card-foreground no-underline",
6
+ href && "transition-colors hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
7
+ className,
8
+ ];
4
9
  ---
5
10
 
6
- <Tag href={href} class:list={["card w-full no-underline", href && "docs-card-link", className]}>
7
- {(title || description) && (
8
- <header>
9
- {title && <h3>{title}</h3>}
10
- {description && <p>{description}</p>}
11
- </header>
12
- )}
11
+ <Tag href={href} class:list={classes}>
12
+ {title && <h3 class="font-medium">{title}</h3>}
13
+ {description && <p class="mt-1 text-sm text-muted-foreground">{description}</p>}
13
14
  </Tag>
@@ -2,6 +2,6 @@
2
2
  const { class: className = "" } = Astro.props;
3
3
  ---
4
4
 
5
- <div class:list={["grid w-full gap-4 sm:grid-cols-2", className]}>
5
+ <div class:list={["my-6 grid w-full gap-4 sm:grid-cols-2 [&>*]:my-0", className]}>
6
6
  <slot />
7
7
  </div>
@@ -1,19 +1,39 @@
1
1
  ---
2
- import { codeToHtml } from "shiki";
2
+ import { highlightCode } from "../../lib/highlight.js";
3
3
 
4
- const { code = "", lang = "text", title, class: className = "" } = Astro.props;
5
- const highlightLang = String(lang || "text").toLowerCase() === "mdx" ? "tsx" : String(lang || "text");
6
- const html = await codeToHtml(String(code || ""), {
7
- lang: highlightLang,
8
- themes: {
9
- light: "github-light",
10
- dark: "github-dark",
11
- },
12
- defaultColor: false,
13
- });
4
+ const { lang = "text", title, class: className = "" } = Astro.props;
5
+ const slotHtml = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
6
+ const rawCode = decodeSlotText(slotHtml);
7
+ const html = await highlightCode(rawCode, lang);
8
+ const classes = [
9
+ "relative my-6 w-full max-w-full overflow-hidden rounded-lg",
10
+ className,
11
+ ];
12
+
13
+ function decodeSlotText(value) {
14
+ return String(value || "")
15
+ .replace(/^<p>/, "")
16
+ .replace(/<\/p>$/, "")
17
+ .replace(/&lt;/g, "<")
18
+ .replace(/&gt;/g, ">")
19
+ .replace(/&amp;/g, "&")
20
+ .replace(/&quot;/g, '"')
21
+ .replace(/&#39;/g, "'")
22
+ .trim();
23
+ }
14
24
  ---
15
25
 
16
- <div class:list={["code-block", className]}>
17
- {title && <header><span>{title}</span></header>}
26
+ <div
27
+ data-code-block
28
+ data-code-title={title || undefined}
29
+ class:list={classes}
30
+ >
31
+ {
32
+ title && (
33
+ <header class="flex h-10 items-center gap-2 border-b border-border/50 bg-muted/50 px-3 text-sm font-medium">
34
+ <span class="mr-auto">{title}</span>
35
+ </header>
36
+ )
37
+ }
18
38
  <Fragment set:html={html} />
19
39
  </div>
@@ -1,21 +1,45 @@
1
1
  ---
2
- import Code from "./Code.astro";
3
-
4
- const { tabs = [], defaultTab, class: className = "" } = Astro.props;
2
+ const { defaultTab, class: className = "" } = Astro.props;
5
3
  const id = `code-group-${Math.random().toString(36).slice(2)}`;
6
- const normalizedTabs = tabs.map((tab, index) => ({
7
- ...tab,
8
- value: tab.value || tab.label || String(index),
9
- lang: tab.lang || "text",
10
- code: tab.code || "",
11
- }));
4
+ const slotHtml = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
5
+ const normalizedTabs = parseCodeTabs(slotHtml);
12
6
  const defaultValue = defaultTab || normalizedTabs[0]?.value;
7
+ const classes = [
8
+ "relative my-6 w-full max-w-full overflow-hidden rounded-lg [&>[role=tabpanel]]:border-t-0 [&_[data-code-block]]:my-0 [&_[data-code-block]]:min-w-0 [&_[data-code-block]]:rounded-none [&_[data-code-block]>header]:hidden",
9
+ className,
10
+ ];
11
+
12
+ function parseCodeTabs(html) {
13
+ const blocks = [];
14
+ const pattern =
15
+ /<div\b(?=[^>]*\bdata-code-block\b)(?=[^>]*\bdata-code-title="([^"]*)")[^>]*>([\s\S]*?)<\/div>/g;
16
+ let match;
17
+ while ((match = pattern.exec(html))) {
18
+ const label = decodeHtml(match[1]).trim();
19
+ if (!label) continue;
20
+ blocks.push({
21
+ label,
22
+ value: label,
23
+ html: match[0],
24
+ });
25
+ }
26
+ return blocks;
27
+ }
28
+
29
+ function decodeHtml(value) {
30
+ return String(value || "")
31
+ .replace(/&lt;/g, "<")
32
+ .replace(/&gt;/g, ">")
33
+ .replace(/&amp;/g, "&")
34
+ .replace(/&quot;/g, '"')
35
+ .replace(/&#39;/g, "'");
36
+ }
13
37
  ---
14
38
 
15
- <div class:list={["code-group", className]}>
16
- <header>
17
- <div class="tabs">
18
- <nav role="tablist" aria-orientation="horizontal" data-variant="line">
39
+ <div class:list={classes} data-code-group>
40
+ <header class="flex h-10 items-center gap-2 border-b border-border/50 bg-muted/50 px-3 text-sm font-medium">
41
+ <div class="tabs my-0 min-w-0 max-w-full gap-0">
42
+ <nav class="h-full justify-start rounded-none bg-transparent p-0" role="tablist" aria-orientation="horizontal" data-variant="line">
19
43
  {
20
44
  normalizedTabs.map((tab, index) => {
21
45
  const selected = tab.value === defaultValue;
@@ -48,7 +72,7 @@ const defaultValue = defaultTab || normalizedTabs[0]?.value;
48
72
  aria-selected={selected ? "true" : "false"}
49
73
  hidden={!selected}
50
74
  >
51
- <Code code={tab.code} lang={tab.lang} />
75
+ <Fragment set:html={tab.html} />
52
76
  </div>
53
77
  );
54
78
  })
@@ -1,14 +1,18 @@
1
1
  ---
2
- import Code from "./Code.astro";
2
+ import { highlightCode } from "../../lib/highlight.js";
3
3
 
4
4
  const { code = "", lang = "html", title = "Preview", class: className = "" } = Astro.props;
5
5
  const id = `preview-${Math.random().toString(36).slice(2)}`;
6
+ const html = await highlightCode(code, lang);
6
7
  ---
7
8
 
8
- <section class="preview">
9
- <header>
10
- <div class="tabs">
11
- <nav role="tablist" aria-orientation="horizontal" data-variant="line">
9
+ <section
10
+ class="relative my-6 max-w-full overflow-visible rounded-lg border [&>[role=tabpanel]]:border-t-0 [&>[data-code-panel]_[data-code-block]]:my-0 [&>[data-code-panel]_[data-code-block]]:min-w-0 [&>[data-code-panel]_[data-code-block]]:rounded-none"
11
+ data-preview
12
+ >
13
+ <header class="flex h-10 items-center gap-2 border-b border-border/50 bg-muted/50 px-3 text-sm font-medium">
14
+ <div class="tabs my-0 min-w-0 max-w-full gap-0">
15
+ <nav class="h-full justify-start rounded-none bg-transparent p-0" role="tablist" aria-orientation="horizontal" data-variant="line">
12
16
  <button
13
17
  type="button"
14
18
  role="tab"
@@ -39,6 +43,7 @@ const id = `preview-${Math.random().toString(36).slice(2)}`;
39
43
  aria-labelledby={`${id}-preview-tab`}
40
44
  tabindex="-1"
41
45
  aria-selected="true"
46
+ class="flex min-h-64 w-full items-center justify-center p-6"
42
47
  >
43
48
  <div class={className}>
44
49
  <slot />
@@ -53,6 +58,11 @@ const id = `preview-${Math.random().toString(36).slice(2)}`;
53
58
  aria-selected="false"
54
59
  hidden
55
60
  >
56
- <Code code={code} lang={lang} />
61
+ <div
62
+ class="relative my-6 w-full max-w-full overflow-hidden rounded-lg"
63
+ data-code-block
64
+ >
65
+ <Fragment set:html={html} />
66
+ </div>
57
67
  </section>
58
68
  </section>
@@ -1,7 +1,11 @@
1
1
  ---
2
2
  const { class: className = "" } = Astro.props;
3
+ const classes = [
4
+ "mb-12 mt-6 [counter-reset:step] md:ml-4 md:border-l md:pl-8 [&>section]:mb-10 [&>section]:[counter-increment:step] [&>section:last-child]:mb-0 [&>section>*+*]:mt-6 [&>section>*]:leading-relaxed [&>section>*:first-child]:mt-0 [&>section>*:last-child]:mb-0 [&>section>h3+*]:mt-4 [&>section>h3]:relative [&>section>h3]:scroll-m-28 [&>section>h3]:text-lg [&>section>h3]:font-medium [&>section>h3]:tracking-tight [&>section>h3]:before:mr-4 [&>section>h3]:before:inline-flex [&>section>h3]:before:size-7 [&>section>h3]:before:items-center [&>section>h3]:before:justify-center [&>section>h3]:before:rounded-full [&>section>h3]:before:bg-muted [&>section>h3]:before:text-sm [&>section>h3]:before:font-medium [&>section>h3]:before:text-muted-foreground [&>section>h3]:before:content-[counter(step)] md:[&>section>h3]:before:absolute md:[&>section>h3]:before:left-0 md:[&>section>h3]:before:top-0 md:[&>section>h3]:before:mr-0 md:[&>section>h3]:before:-translate-x-[2.875rem]",
5
+ className,
6
+ ];
3
7
  ---
4
8
 
5
- <div role="list" class:list={["steps", className]}>
9
+ <div role="list" class:list={classes}>
6
10
  <slot />
7
11
  </div>
@@ -9,7 +9,7 @@ const normalizedTabs = tabs.map((tab, index) => ({
9
9
  const defaultValue = defaultTab || normalizedTabs.find((tab) => !tab.disabled)?.value || normalizedTabs[0]?.value;
10
10
  ---
11
11
 
12
- <div class:list={["tabs", className]}>
12
+ <div class:list={["tabs my-6", className]}>
13
13
  <nav role="tablist" aria-orientation={orientation} data-variant={variant}>
14
14
  {
15
15
  normalizedTabs.map((tab, index) => {