radiant-docs 0.1.41 → 0.1.42

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 (32) hide show
  1. package/package.json +1 -1
  2. package/template/astro.config.mjs +42 -40
  3. package/template/package-lock.json +7 -0
  4. package/template/package.json +1 -0
  5. package/template/src/components/Header.astro +150 -16
  6. package/template/src/components/MdxPage.astro +76 -22
  7. package/template/src/components/PagePagination.astro +44 -8
  8. package/template/src/components/Sidebar.astro +10 -1
  9. package/template/src/components/TableOfContents.astro +159 -53
  10. package/template/src/components/chat/AssistantDocsWidget.tsx +221 -8
  11. package/template/src/components/chat/AssistantEmbedPanel.tsx +1090 -104
  12. package/template/src/components/user/Accordion.astro +2 -2
  13. package/template/src/components/user/AccordionGroup.astro +1 -1
  14. package/template/src/components/user/Callout.astro +2 -2
  15. package/template/src/components/user/Card.astro +488 -0
  16. package/template/src/components/user/CardGradient.astro +964 -0
  17. package/template/src/components/user/CodeBlock.astro +1 -1
  18. package/template/src/components/user/CodeGroup.astro +1 -1
  19. package/template/src/components/user/Column.astro +25 -0
  20. package/template/src/components/user/Columns.astro +200 -0
  21. package/template/src/components/user/ComponentPreviewBlock.astro +1 -1
  22. package/template/src/components/user/Image.astro +1 -1
  23. package/template/src/components/user/Step.astro +1 -1
  24. package/template/src/components/user/Steps.astro +1 -1
  25. package/template/src/components/user/Tab.astro +1 -3
  26. package/template/src/components/user/Tabs.astro +2 -2
  27. package/template/src/layouts/Layout.astro +2 -4
  28. package/template/src/lib/assistant-chrome-defaults.ts +12 -0
  29. package/template/src/lib/assistant-embed-script.ts +209 -18
  30. package/template/src/lib/validation.ts +325 -75
  31. package/template/src/styles/global.css +81 -4
  32. package/template/src/components/chat/AskAiWidget.tsx +0 -2011
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "radiant-docs",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "description": "CLI tool for previewing Radiant documentation locally",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,50 +18,52 @@ import remarkGfm from "remark-gfm";
18
18
  import rehypeSlug from "rehype-slug";
19
19
  import rehypeAutolinkHeadings from "rehype-autolink-headings";
20
20
 
21
- const headingAnchorIcon = {
21
+ function createLucideIconNode({ className, path }) {
22
+ return {
23
+ type: "element",
24
+ tagName: "svg",
25
+ properties: {
26
+ xmlns: "http://www.w3.org/2000/svg",
27
+ width: "24",
28
+ height: "24",
29
+ viewBox: "0 0 24 24",
30
+ fill: "none",
31
+ stroke: "currentColor",
32
+ strokeWidth: "2",
33
+ strokeLinecap: "round",
34
+ strokeLinejoin: "round",
35
+ className,
36
+ ariaHidden: "true",
37
+ },
38
+ children: [
39
+ {
40
+ type: "element",
41
+ tagName: "path",
42
+ properties: {
43
+ d: path,
44
+ },
45
+ children: [],
46
+ },
47
+ ],
48
+ };
49
+ }
50
+
51
+ const headingAnchorContent = {
22
52
  type: "element",
23
- tagName: "svg",
53
+ tagName: "span",
24
54
  properties: {
25
- xmlns: "http://www.w3.org/2000/svg",
26
- width: "24",
27
- height: "24",
28
- viewBox: "0 0 24 24",
29
- fill: "none",
30
- stroke: "currentColor",
31
- strokeWidth: "2",
32
- strokeLinecap: "round",
33
- strokeLinejoin: "round",
34
- className: ["heading-anchor-icon"],
55
+ className: ["heading-anchor-icons"],
35
56
  ariaHidden: "true",
36
57
  },
37
58
  children: [
38
- {
39
- type: "element",
40
- tagName: "path",
41
- properties: {
42
- d: "M9 17H7A5 5 0 0 1 7 7h2",
43
- },
44
- children: [],
45
- },
46
- {
47
- type: "element",
48
- tagName: "path",
49
- properties: {
50
- d: "M15 7h2a5 5 0 1 1 0 10h-2",
51
- },
52
- children: [],
53
- },
54
- {
55
- type: "element",
56
- tagName: "line",
57
- properties: {
58
- x1: "8",
59
- x2: "16",
60
- y1: "12",
61
- y2: "12",
62
- },
63
- children: [],
64
- },
59
+ createLucideIconNode({
60
+ className: ["heading-anchor-icon", "heading-anchor-link-icon"],
61
+ path: "M9 17H7A5 5 0 0 1 7 7h2m6 0h2a5 5 0 1 1 0 10h-2m-7-5h8",
62
+ }),
63
+ createLucideIconNode({
64
+ className: ["heading-anchor-icon", "heading-anchor-check-icon"],
65
+ path: "M20 6L9 17l-5-5",
66
+ }),
65
67
  ],
66
68
  };
67
69
 
@@ -347,7 +349,7 @@ export default defineConfig({
347
349
  rehypeAutolinkHeadings,
348
350
  {
349
351
  behavior: "append",
350
- content: headingAnchorIcon,
352
+ content: headingAnchorContent,
351
353
  properties: {
352
354
  className: ["heading-anchor"],
353
355
  ariaLabel: "Copy section link",
@@ -21,6 +21,7 @@
21
21
  "@iconify-json/simple-icons": "^1.2.69",
22
22
  "@iconify/react": "^6.0.2",
23
23
  "@jongwooo/prism-theme-github": "^1.15.1",
24
+ "@paper-design/shaders": "^0.0.76",
24
25
  "@preact/preset-vite": "^2.10.3",
25
26
  "@readme/oas-to-snippet": "^29.3.0",
26
27
  "@resvg/resvg-js": "^2.6.2",
@@ -2826,6 +2827,12 @@
2826
2827
  "win32"
2827
2828
  ]
2828
2829
  },
2830
+ "node_modules/@paper-design/shaders": {
2831
+ "version": "0.0.76",
2832
+ "resolved": "https://registry.npmjs.org/@paper-design/shaders/-/shaders-0.0.76.tgz",
2833
+ "integrity": "sha512-AcNDY4J66YQHUfQYFInkCP7M9VOje0od7wLpOR7LtCmc532opJy6ll+h1W9zBovz8tt9U7OADUmJ/qKEXyOX/A==",
2834
+ "license": "SEE LICENSE IN https://github.com/paper-design/shaders/blob/main/LICENSE"
2835
+ },
2829
2836
  "node_modules/@preact/preset-vite": {
2830
2837
  "version": "2.10.3",
2831
2838
  "resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.10.3.tgz",
@@ -25,6 +25,7 @@
25
25
  "@iconify-json/simple-icons": "^1.2.69",
26
26
  "@iconify/react": "^6.0.2",
27
27
  "@jongwooo/prism-theme-github": "^1.15.1",
28
+ "@paper-design/shaders": "^0.0.76",
28
29
  "@preact/preset-vite": "^2.10.3",
29
30
  "@readme/oas-to-snippet": "^29.3.0",
30
31
  "@resvg/resvg-js": "^2.6.2",
@@ -1,10 +1,14 @@
1
1
  ---
2
2
  import Icon from "./ui/Icon.astro";
3
- import { getConfig } from "../lib/validation";
3
+ import { getConfig, type ThemeColorByMode } from "../lib/validation";
4
4
  import Search from "./Search.astro";
5
5
  import LogoLink from "./LogoLink.astro";
6
- import sparkleIcon from "../assets/icons/sparkle.svg?url";
7
6
  import { withBasePath } from "../lib/base-path";
7
+ import { getAssistantPanelRuntimeConfig } from "../lib/assistant-panel-config";
8
+ import {
9
+ getDocsBaseColorShade,
10
+ getThemeForegroundColor,
11
+ } from "../lib/theme-css";
8
12
 
9
13
  interface Props {
10
14
  showAskAiTrigger?: boolean;
@@ -12,6 +16,98 @@ interface Props {
12
16
 
13
17
  const { showAskAiTrigger = false } = Astro.props as Props;
14
18
  const config = await getConfig();
19
+ const assistantNavbarButton = config.assistant?.navbarButton;
20
+ const showAssistantNavbarButton =
21
+ showAskAiTrigger && assistantNavbarButton?.enabled !== false;
22
+ const assistantNavbarButtonText = assistantNavbarButton?.text ?? "Ask";
23
+ const assistantRuntimeConfig = showAssistantNavbarButton
24
+ ? getAssistantPanelRuntimeConfig(config)
25
+ : null;
26
+ const assistantHeaderIconSrc =
27
+ assistantRuntimeConfig?.launcherIconImageSrc ?? "";
28
+
29
+ function getConfiguredColorForMode(
30
+ value: string | ThemeColorByMode | undefined,
31
+ mode: "light" | "dark",
32
+ ): string | undefined {
33
+ if (typeof value === "string") return value;
34
+ return value?.[mode];
35
+ }
36
+
37
+ function getDefaultButtonColor(mode: "light" | "dark"): string {
38
+ return getDocsBaseColorShade(
39
+ config.theme,
40
+ mode,
41
+ mode === "light" ? "900" : "100",
42
+ );
43
+ }
44
+
45
+ function getButtonForegroundColor(
46
+ mode: "light" | "dark",
47
+ color: string,
48
+ ): string {
49
+ return getThemeForegroundColor(
50
+ color,
51
+ getDocsBaseColorShade(config.theme, mode, "900"),
52
+ );
53
+ }
54
+
55
+ function getAssistantNavbarButtonColor(mode: "light" | "dark"): string {
56
+ return (
57
+ getConfiguredColorForMode(config.assistant?.navbarButton?.color, mode) ??
58
+ getConfiguredColorForMode(config.assistant?.button?.color, mode) ??
59
+ getDefaultButtonColor(mode)
60
+ );
61
+ }
62
+
63
+ function getNavbarPrimaryThemeColor(mode: "light" | "dark"): string {
64
+ return (
65
+ getConfiguredColorForMode(config.navbar?.primary?.color, mode) ??
66
+ getDefaultButtonColor(mode)
67
+ );
68
+ }
69
+
70
+ const assistantHeaderThemeColors = showAssistantNavbarButton
71
+ ? {
72
+ light: getAssistantNavbarButtonColor("light"),
73
+ dark: getAssistantNavbarButtonColor("dark"),
74
+ }
75
+ : null;
76
+ const assistantHeaderButtonStyle = assistantHeaderThemeColors
77
+ ? [
78
+ `--assistant-header-theme-light: ${assistantHeaderThemeColors.light}`,
79
+ `--assistant-header-theme-dark: ${assistantHeaderThemeColors.dark}`,
80
+ `--assistant-header-foreground-light: ${
81
+ config.assistant?.icon?.color ??
82
+ getButtonForegroundColor("light", assistantHeaderThemeColors.light)
83
+ }`,
84
+ `--assistant-header-foreground-dark: ${
85
+ config.assistant?.icon?.color ??
86
+ getButtonForegroundColor("dark", assistantHeaderThemeColors.dark)
87
+ }`,
88
+ ].join("; ")
89
+ : "";
90
+
91
+ const navbarPrimaryThemeColors = config.navbar?.primary
92
+ ? {
93
+ light: getNavbarPrimaryThemeColor("light"),
94
+ dark: getNavbarPrimaryThemeColor("dark"),
95
+ }
96
+ : null;
97
+ const navbarPrimaryButtonStyle = navbarPrimaryThemeColors
98
+ ? [
99
+ `--navbar-primary-theme-light: ${navbarPrimaryThemeColors.light}`,
100
+ `--navbar-primary-theme-dark: ${navbarPrimaryThemeColors.dark}`,
101
+ `--navbar-primary-foreground-light: ${getButtonForegroundColor(
102
+ "light",
103
+ navbarPrimaryThemeColors.light,
104
+ )}`,
105
+ `--navbar-primary-foreground-dark: ${getButtonForegroundColor(
106
+ "dark",
107
+ navbarPrimaryThemeColors.dark,
108
+ )}`,
109
+ ].join("; ")
110
+ : "";
15
111
  ---
16
112
 
17
113
  <header
@@ -56,20 +152,23 @@ const config = await getConfig();
56
152
  <div class="flex items-center gap-2 mx-0 md:mx-auto lg:mx-0">
57
153
  <Search />
58
154
  {
59
- showAskAiTrigger ? (
155
+ showAssistantNavbarButton ? (
60
156
  <button
61
157
  type="button"
62
- class="hidden md:inline-flex items-center gap-2 h-8 rounded-lg [corner-shape:superellipse(1.2)] bg-linear-to-b from-[var(--color-theme-top)] to-[var(--color-theme-bottom)] px-3 text-[13px] font-[350] text-[var(--color-theme-foreground)] shadow-sm hover:opacity-95 cursor-pointer"
63
- onclick="window.__assistantOpenRequested = true; window.dispatchEvent(new CustomEvent('ask-ai:open'));"
158
+ aria-label="Open assistant"
159
+ class="assistant-header-trigger hidden md:inline-flex items-center gap-2 h-8 rounded-lg [corner-shape:superellipse(1.2)] px-3 text-[13px] font-[350] dark:font-[450] transition-opacity duration-200 hover:opacity-95 cursor-pointer"
160
+ style={assistantHeaderButtonStyle}
161
+ onclick="window.__assistantToggleRequested = true; window.dispatchEvent(new CustomEvent('ask-ai:toggle'));"
64
162
  >
65
- <img
66
- src={sparkleIcon}
67
- alt=""
68
- aria-hidden="true"
69
- class="size-3 -ml-px"
70
- style="filter: var(--color-theme-icon-filter);"
71
- />
72
- Ask AI
163
+ {assistantHeaderIconSrc ? (
164
+ <img
165
+ src={assistantHeaderIconSrc}
166
+ alt=""
167
+ aria-hidden="true"
168
+ class="size-4 -ml-px object-contain"
169
+ />
170
+ ) : null}
171
+ {assistantNavbarButtonText}
73
172
  </button>
74
173
  ) : null
75
174
  }
@@ -90,7 +189,7 @@ const config = await getConfig();
90
189
  <a
91
190
  class:list={[
92
191
  "items-center gap-1 text-[13px] font-[450] dark:font-normal text-neutral-600/85 hover:text-neutral-600 dark:text-neutral-200/90 dark:hover:text-neutral-200 duration-200 px-1.5 py-[5px] whitespace-nowrap",
93
- showAskAiTrigger && a.length === 3 && i === 2
192
+ showAssistantNavbarButton && a.length === 3 && i === 2
94
193
  ? "hidden 2xl:flex"
95
194
  : "flex",
96
195
  ]}
@@ -112,7 +211,7 @@ const config = await getConfig();
112
211
  {config.navbar.secondary && (
113
212
  <a
114
213
  class:list={[
115
- "h-[33px] items-center gap-1.5 px-[11px] text-[13px] bg-white/90 dark:bg-neutral-800/80 text-neutral-600/85 hover:text-neutral-600 dark:text-neutral-200/95 dark:hover:text-neutral-200 rounded-lg [corner-shape:superellipse(1.2)] border border-neutral-200 dark:border-neutral-700/40 shadow-xs transition-all whitespace-nowrap",
214
+ "h-[33px] items-center gap-1.5 px-[11px] text-[13px] bg-linear-to-br from-neutral-100/70 to-neutral-100/90 dark:bg-none dark:bg-neutral-800/80 text-neutral-900/85 hover:text-neutral-600 dark:text-neutral-200/95 dark:hover:text-neutral-200 rounded-lg [corner-shape:superellipse(1.2)] border-[0.5px] border-neutral-900/10 dark:border-white/5 transition-all whitespace-nowrap",
116
215
  config.navbar.primary ? "hidden lg:flex" : "flex",
117
216
  ]}
118
217
  href={withBasePath(config.navbar.secondary.href)}
@@ -131,8 +230,9 @@ const config = await getConfig();
131
230
  {config.navbar.primary && (
132
231
  <a
133
232
  class:list={[
134
- "font-[350] h-8 flex items-center gap-2 px-3 text-[13px] rounded-lg [corner-shape:superellipse(1.2)] bg-linear-to-b from-[var(--color-theme-top)] to-[var(--color-theme-bottom)] text-[var(--color-theme-foreground)] duration-200 shadow-sm transition-all whitespace-nowrap hover:opacity-95",
233
+ "navbar-primary-trigger font-[350] dark:font-[450] h-8 flex items-center gap-2 px-3 text-[13px] rounded-lg [corner-shape:superellipse(1.2)] duration-200 transition-all whitespace-nowrap hover:opacity-95",
135
234
  ]}
235
+ style={navbarPrimaryButtonStyle}
136
236
  href={withBasePath(config.navbar.primary.href)}
137
237
  >
138
238
  {config.navbar.primary.icon && (
@@ -153,3 +253,37 @@ const config = await getConfig();
153
253
  </div>
154
254
  </div>
155
255
  </header>
256
+
257
+ <style>
258
+ .assistant-header-trigger {
259
+ --assistant-header-theme: var(--assistant-header-theme-light);
260
+ --assistant-header-foreground: var(--assistant-header-foreground-light);
261
+ background: linear-gradient(
262
+ to bottom,
263
+ color-mix(in oklab, var(--assistant-header-theme) 88%, white),
264
+ color-mix(in oklab, var(--assistant-header-theme) 90%, black)
265
+ );
266
+ color: var(--assistant-header-foreground);
267
+ }
268
+
269
+ :global(.dark) .assistant-header-trigger {
270
+ --assistant-header-theme: var(--assistant-header-theme-dark);
271
+ --assistant-header-foreground: var(--assistant-header-foreground-dark);
272
+ }
273
+
274
+ .navbar-primary-trigger {
275
+ --navbar-primary-theme: var(--navbar-primary-theme-light);
276
+ --navbar-primary-foreground: var(--navbar-primary-foreground-light);
277
+ background: linear-gradient(
278
+ to bottom,
279
+ color-mix(in oklab, var(--navbar-primary-theme) 88%, white),
280
+ color-mix(in oklab, var(--navbar-primary-theme) 90%, black)
281
+ );
282
+ color: var(--navbar-primary-foreground);
283
+ }
284
+
285
+ :global(.dark) .navbar-primary-trigger {
286
+ --navbar-primary-theme: var(--navbar-primary-theme-dark);
287
+ --navbar-primary-foreground: var(--navbar-primary-foreground-dark);
288
+ }
289
+ </style>
@@ -8,6 +8,9 @@ import Steps from "./user/Steps.astro";
8
8
  import Step from "./user/Step.astro";
9
9
  import Accordion from "./user/Accordion.astro";
10
10
  import AccordionGroup from "./user/AccordionGroup.astro";
11
+ import Card from "./user/Card.astro";
12
+ import Column from "./user/Column.astro";
13
+ import Columns from "./user/Columns.astro";
11
14
  import TableOfContents from "./TableOfContents.astro";
12
15
  import Image from "./user/Image.astro";
13
16
  import CodeBlock from "./user/CodeBlock.astro";
@@ -30,6 +33,9 @@ const { entry, route, previousRoute, nextRoute, homePath } = Astro.props;
30
33
  const components = {
31
34
  Accordion,
32
35
  AccordionGroup,
36
+ Card,
37
+ Column,
38
+ Columns,
33
39
  Callout,
34
40
  Tabs,
35
41
  Tab,
@@ -54,26 +60,24 @@ const tocHeadings = headings.filter(({ depth }) => depth === 2 || depth === 3);
54
60
  ---
55
61
 
56
62
  <Layout pageTitle={title} pageDescription={description}>
57
- <article>
58
- <header class="mb-6">
59
- <h1 class="text-4xl font-semibold tracking-tight">{title}</h1>
60
- </header>
61
- <div class="flex w-full min-w-0 justify-between gap-x-12">
62
- <div class="min-w-0 flex-1">
63
- <div class="prose-rules">
64
- <Content components={components} />
65
- </div>
66
- <PagePagination
67
- previousRoute={previousRoute}
68
- nextRoute={nextRoute}
69
- homePath={homePath}
70
- />
71
- </div>
72
- <aside class="hidden xl:block w-56 shrink-0">
73
- <TableOfContents headings={tocHeadings} />
74
- </aside>
63
+ <div class="flex w-full min-w-0 justify-between gap-x-10">
64
+ <div class="mx-auto max-w-3xl w-full">
65
+ <header class="mb-6">
66
+ <h1 class="text-4xl font-semibold tracking-tight">{title}</h1>
67
+ </header>
68
+ <article class="prose-rules">
69
+ <Content components={components} />
70
+ </article>
71
+ <PagePagination
72
+ previousRoute={previousRoute}
73
+ nextRoute={nextRoute}
74
+ homePath={homePath}
75
+ />
75
76
  </div>
76
- </article>
77
+ <aside class="hidden xl:block w-48 shrink-0">
78
+ <TableOfContents headings={tocHeadings} />
79
+ </aside>
80
+ </div>
77
81
  <script is:inline>
78
82
  function fallbackCopy(text) {
79
83
  const textarea = document.createElement("textarea");
@@ -101,7 +105,52 @@ const tocHeadings = headings.filter(({ depth }) => depth === 2 || depth === 3);
101
105
  return fallbackCopy(text);
102
106
  }
103
107
 
104
- async function copyHeadingUrlFromHash(hash) {
108
+ var headingCopyStateTimers =
109
+ window.__headingCopyStateTimers instanceof WeakMap
110
+ ? window.__headingCopyStateTimers
111
+ : new WeakMap();
112
+ window.__headingCopyStateTimers = headingCopyStateTimers;
113
+
114
+ function showHeadingCopiedState(anchor) {
115
+ if (!(anchor instanceof HTMLElement)) return;
116
+
117
+ const defaultLabel =
118
+ anchor.dataset.defaultAriaLabel ||
119
+ anchor.getAttribute("aria-label") ||
120
+ "Copy section link";
121
+ const defaultTitle =
122
+ anchor.dataset.defaultTitle ||
123
+ anchor.getAttribute("title") ||
124
+ defaultLabel;
125
+ anchor.dataset.defaultAriaLabel = defaultLabel;
126
+ anchor.dataset.defaultTitle = defaultTitle;
127
+
128
+ const existingTimeout = headingCopyStateTimers.get(anchor);
129
+ if (existingTimeout) window.clearTimeout(existingTimeout);
130
+
131
+ anchor.dataset.copyState = "copied";
132
+ anchor.setAttribute("aria-label", "Copied section link");
133
+ anchor.setAttribute("title", "Copied");
134
+
135
+ const timeout = window.setTimeout(() => {
136
+ if (anchor.dataset.copyState === "copied") {
137
+ delete anchor.dataset.copyState;
138
+ anchor.setAttribute(
139
+ "aria-label",
140
+ anchor.dataset.defaultAriaLabel || "Copy section link",
141
+ );
142
+ anchor.setAttribute(
143
+ "title",
144
+ anchor.dataset.defaultTitle || "Copy section link",
145
+ );
146
+ }
147
+ headingCopyStateTimers.delete(anchor);
148
+ }, 1400);
149
+
150
+ headingCopyStateTimers.set(anchor, timeout);
151
+ }
152
+
153
+ async function copyHeadingUrlFromHash(hash, stateTarget) {
105
154
  const url = `${window.location.origin}${window.location.pathname}${hash}`;
106
155
  const copied = await copyToClipboard(url);
107
156
  if (!copied) return false;
@@ -112,6 +161,8 @@ const tocHeadings = headings.filter(({ depth }) => depth === 2 || depth === 3);
112
161
  window.location.hash = hash;
113
162
  }
114
163
 
164
+ showHeadingCopiedState(stateTarget);
165
+
115
166
  return true;
116
167
  }
117
168
 
@@ -134,7 +185,7 @@ const tocHeadings = headings.filter(({ depth }) => depth === 2 || depth === 3);
134
185
  return;
135
186
  }
136
187
 
137
- const copied = await copyHeadingUrlFromHash(hash);
188
+ const copied = await copyHeadingUrlFromHash(hash, anchor);
138
189
  if (!copied) {
139
190
  if (isPointerClick) anchor.blur();
140
191
  return;
@@ -160,7 +211,10 @@ const tocHeadings = headings.filter(({ depth }) => depth === 2 || depth === 3);
160
211
  const id = heading.getAttribute("id");
161
212
  if (!id) return;
162
213
 
163
- await copyHeadingUrlFromHash(`#${id}`);
214
+ await copyHeadingUrlFromHash(
215
+ `#${id}`,
216
+ heading.querySelector("a.heading-anchor"),
217
+ );
164
218
  });
165
219
  });
166
220
  }
@@ -23,17 +23,35 @@ function buildHref(route: Route): string {
23
23
 
24
24
  {
25
25
  (previousRoute || nextRoute) && (
26
- <nav class="w-full max-w-2xl pt-16 dark:border-neutral-800" aria-label="Page pagination">
27
- <div class="grid gap-3 sm:grid-cols-2">
26
+ <nav class="w-full pt-16" aria-label="Page pagination">
27
+ <div class="flex gap-4 xs:grid-cols-2">
28
28
  {previousRoute && (
29
29
  <a
30
30
  href={buildHref(previousRoute)}
31
- class="group flex flex-col rounded-lg border border-neutral-200/90 bg-white/70 p-4 transition-colors hover:border-neutral-300 dark:border-neutral-800 dark:bg-neutral-900/40 dark:hover:border-neutral-700 shadow-xs"
31
+ class="group flex-1 flex min-w-0 flex-col py-4 px-5 relative z-10 before:-z-10 before:absolute before:inset-0 hover:before:-inset-1 before:rounded-xl before:bg-neutral-50 hover:before:bg-neutral-100/80 dark:before:bg-(--rd-code-surface)/70 dark:hover:before:bg-(--rd-code-surface) before:duration-200 before:ease-in-out"
32
32
  >
33
- <span class="block text-[10px] font-medium uppercase tracking-wide text-neutral-400 dark:text-neutral-400">
33
+ <span class="inline-flex items-center text-sm tracking-wide text-neutral-500/90 dark:text-neutral-300/70">
34
+ <svg
35
+ xmlns="http://www.w3.org/2000/svg"
36
+ width="24"
37
+ height="24"
38
+ viewBox="0 0 24 24"
39
+ fill="none"
40
+ stroke="currentColor"
41
+ stroke-width="2.2"
42
+ stroke-linecap="round"
43
+ stroke-linejoin="round"
44
+ class="size-3.5 group-hover:-translate-x-[5px] duration-200 ease-in-out"
45
+ >
46
+ <path d="m12 19-7-7 7-7" />
47
+ <path
48
+ d="M19 12H5"
49
+ class="scale-x-0 group-hover:scale-x-100 origin-[30%] duration-200 ease-in-out"
50
+ />
51
+ </svg>
34
52
  Previous
35
53
  </span>
36
- <div class="font-medium text-neutral-900 dark:text-neutral-100">
54
+ <div class="min-w-0 max-w-full wrap-break-word font-medium text-neutral-900 dark:text-neutral-100">
37
55
  {previousRoute.title}
38
56
  </div>
39
57
  </a>
@@ -43,14 +61,32 @@ function buildHref(route: Route): string {
43
61
  <a
44
62
  href={buildHref(nextRoute)}
45
63
  class:list={[
46
- "group flex flex-col rounded-lg border border-neutral-200/90 bg-white/70 p-4 transition-colors hover:border-neutral-300/80 dark:border-neutral-800 dark:bg-neutral-900/40 dark:hover:border-neutral-700 shadow-xs",
64
+ "group flex-1 flex min-w-0 flex-col py-4 px-5 relative z-10 before:-z-10 before:absolute before:inset-0 hover:before:-inset-1 before:rounded-xl before:bg-neutral-50 hover:before:bg-neutral-100/80 dark:before:bg-(--rd-code-surface)/70 dark:hover:before:bg-(--rd-code-surface) before:duration-200 before:ease-in-out",
47
65
  !previousRoute && "sm:col-start-2",
48
66
  ]}
49
67
  >
50
- <span class="block text-right text-[10px] font-medium uppercase tracking-wide text-neutral-400 dark:text-neutral-400">
68
+ <span class="ml-auto inline-flex items-center text-sm tracking-wide text-neutral-500/90 dark:text-neutral-300/70">
51
69
  Next
70
+ <svg
71
+ xmlns="http://www.w3.org/2000/svg"
72
+ width="24"
73
+ height="24"
74
+ viewBox="0 0 24 24"
75
+ fill="none"
76
+ stroke="currentColor"
77
+ stroke-width="2.2"
78
+ stroke-linecap="round"
79
+ stroke-linejoin="round"
80
+ class="size-3.5 group-hover:translate-x-[5px] duration-200 ease-in-out"
81
+ >
82
+ <path
83
+ d="M5 12h14"
84
+ class="scale-x-0 group-hover:scale-x-100 origin-[70%] duration-200 ease-in-out"
85
+ />
86
+ <path d="m12 5 7 7-7 7" />
87
+ </svg>
52
88
  </span>
53
- <div class="text-right font-medium text-neutral-900 dark:text-neutral-100">
89
+ <div class="min-w-0 max-w-full wrap-break-word text-right font-medium text-neutral-900 dark:text-neutral-100">
54
90
  {nextRoute.title}
55
91
  </div>
56
92
  </a>
@@ -3,6 +3,12 @@ import { getConfig, type DocsConfig } from "../lib/validation";
3
3
  import ThemeSwitcher from "./ThemeSwitcher.astro";
4
4
  import SidebarMenu from "./SidebarMenu.astro";
5
5
 
6
+ interface Props {
7
+ askAiEnabled?: boolean;
8
+ }
9
+
10
+ const { askAiEnabled = false } = Astro.props as Props;
11
+
6
12
  const config: DocsConfig = await getConfig();
7
13
  ---
8
14
 
@@ -13,7 +19,10 @@ const config: DocsConfig = await getConfig();
13
19
  <SidebarMenu navigation={config.navigation} />
14
20
  </nav>
15
21
  <div
16
- class="mt-auto bg-background z-10 p-3 border-t border-t-border-light flex gap-1.5 justify-end items-center"
22
+ class:list={[
23
+ "mt-auto bg-background z-10 p-3 border-t border-t-border-light flex gap-1.5 items-center",
24
+ askAiEnabled ? "justify-start" : "justify-end",
25
+ ]}
17
26
  >
18
27
  <span class="text-neutral-400 text-xs font-light">Theme</span>
19
28
  <ThemeSwitcher />