radiant-docs 0.1.69 → 0.1.70

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.
@@ -0,0 +1,410 @@
1
+ ---
2
+ import Icon from "./ui/Icon.astro";
3
+ import { getConfig, type ThemeColorByMode } from "../lib/validation";
4
+ import { resolveConfiguredHref } from "../lib/routes";
5
+ import {
6
+ getDocsBaseColorShade,
7
+ getThemeForegroundColor,
8
+ } from "../lib/theme-css";
9
+
10
+ const config = await getConfig();
11
+
12
+ function getConfiguredColorForMode(
13
+ value: string | ThemeColorByMode | undefined,
14
+ mode: "light" | "dark",
15
+ ): string | undefined {
16
+ if (typeof value === "string") return value;
17
+ return value?.[mode];
18
+ }
19
+
20
+ function getDefaultButtonColor(mode: "light" | "dark"): string {
21
+ return getDocsBaseColorShade(
22
+ config.theme,
23
+ mode,
24
+ mode === "light" ? "900" : "100",
25
+ );
26
+ }
27
+
28
+ function getButtonForegroundColor(
29
+ mode: "light" | "dark",
30
+ color: string,
31
+ ): string {
32
+ return getThemeForegroundColor(
33
+ color,
34
+ getDocsBaseColorShade(config.theme, mode, "900"),
35
+ );
36
+ }
37
+
38
+ function getNavbarPrimaryThemeColor(mode: "light" | "dark"): string {
39
+ return (
40
+ getConfiguredColorForMode(config.navbar?.primary?.color, mode) ??
41
+ getDefaultButtonColor(mode)
42
+ );
43
+ }
44
+
45
+ const navbarPrimaryThemeColors = config.navbar?.primary
46
+ ? {
47
+ light: getNavbarPrimaryThemeColor("light"),
48
+ dark: getNavbarPrimaryThemeColor("dark"),
49
+ }
50
+ : null;
51
+ const navbarPrimaryButtonStyle = navbarPrimaryThemeColors
52
+ ? [
53
+ `--mobile-navbar-primary-theme-light: ${navbarPrimaryThemeColors.light}`,
54
+ `--mobile-navbar-primary-theme-dark: ${navbarPrimaryThemeColors.dark}`,
55
+ `--mobile-navbar-primary-foreground-light: ${getButtonForegroundColor(
56
+ "light",
57
+ navbarPrimaryThemeColors.light,
58
+ )}`,
59
+ `--mobile-navbar-primary-foreground-dark: ${getButtonForegroundColor(
60
+ "dark",
61
+ navbarPrimaryThemeColors.dark,
62
+ )}`,
63
+ ].join("; ")
64
+ : "";
65
+
66
+ const navbarLinks = config.navbar?.links
67
+ ? await Promise.all(
68
+ config.navbar.links.map(async (link) => ({
69
+ ...link,
70
+ href: await resolveConfiguredHref(link.href, config),
71
+ })),
72
+ )
73
+ : [];
74
+ const navbarSecondaryHref = config.navbar?.secondary
75
+ ? await resolveConfiguredHref(config.navbar.secondary.href, config)
76
+ : "";
77
+ const navbarPrimaryHref = config.navbar?.primary
78
+ ? await resolveConfiguredHref(config.navbar.primary.href, config)
79
+ : "";
80
+ const mobileButtonCount =
81
+ Number(Boolean(config.navbar?.primary)) +
82
+ Number(Boolean(config.navbar?.secondary));
83
+ const hasMobileNavbarActions = mobileButtonCount > 0 || navbarLinks.length > 0;
84
+ ---
85
+
86
+ {
87
+ hasMobileNavbarActions && (
88
+ <div
89
+ class="relative shrink-0 border-b border-border-light px-4 py-3"
90
+ data-rd-mobile-navbar-actions
91
+ >
92
+ <nav aria-label="Site shortcuts">
93
+ <div
94
+ class="flex w-full min-w-0 items-center justify-end gap-3"
95
+ data-rd-mobile-navbar-row
96
+ >
97
+ {navbarLinks.length > 0 && (
98
+ <div
99
+ class:list={[
100
+ "flex shrink-0 items-center gap-1",
101
+ mobileButtonCount > 0 && "pr-2",
102
+ ]}
103
+ data-rd-mobile-navbar-links
104
+ >
105
+ {navbarLinks.map((link) => (
106
+ <a
107
+ class="flex items-center gap-1 px-1.5 py-[5px] text-[13px] font-medium text-neutral-600/85 whitespace-nowrap transition-colors hover:text-neutral-600 dark:font-normal dark:text-neutral-200/90 dark:hover:text-neutral-200"
108
+ href={link.href}
109
+ x-on:click="open = false"
110
+ >
111
+ {link.icon && (
112
+ <Icon
113
+ class="ml-[3px]"
114
+ name={link.icon}
115
+ width="14"
116
+ height="14"
117
+ />
118
+ )}
119
+ {link.text}
120
+ </a>
121
+ ))}
122
+ </div>
123
+ )}
124
+ {navbarLinks.length > 0 && (
125
+ <div class="shrink-0" data-rd-mobile-navbar-overflow hidden>
126
+ <button
127
+ type="button"
128
+ class="flex size-8 cursor-pointer items-center justify-center rounded-lg text-neutral-600 transition-colors hover:bg-neutral-100/70 hover:text-neutral-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-500 dark:text-neutral-300 dark:hover:bg-neutral-800/70 dark:hover:text-white dark:focus-visible:outline-neutral-400"
129
+ aria-label="Show more navbar links"
130
+ aria-haspopup="true"
131
+ aria-expanded="false"
132
+ aria-controls="mobile-navbar-overflow-menu"
133
+ data-rd-mobile-navbar-overflow-trigger
134
+ >
135
+ <Icon
136
+ name="lucide:ellipsis-vertical"
137
+ class="size-4"
138
+ aria-hidden="true"
139
+ />
140
+ </button>
141
+ </div>
142
+ )}
143
+ {mobileButtonCount > 0 && (
144
+ <div
145
+ class="flex shrink-0 items-center gap-3"
146
+ data-rd-mobile-navbar-buttons
147
+ >
148
+ {config.navbar?.secondary && (
149
+ <a
150
+ class="flex h-[33px] shrink-0 items-center gap-1.5 rounded-lg [corner-shape:superellipse(1.2)] border-[0.5px] border-neutral-900/10 bg-linear-to-br from-neutral-100/70 to-neutral-100/90 px-[11px] text-[13px] font-medium text-neutral-900/85 whitespace-nowrap transition-all hover:text-neutral-600 dark:border-white/5 dark:bg-none dark:bg-neutral-800/80 dark:text-neutral-200/95 dark:hover:text-neutral-200"
151
+ href={navbarSecondaryHref}
152
+ x-on:click="open = false"
153
+ >
154
+ {config.navbar.secondary.icon && (
155
+ <Icon
156
+ name={config.navbar.secondary.icon}
157
+ width="14"
158
+ height="14"
159
+ class="-ml-px"
160
+ />
161
+ )}
162
+ {config.navbar.secondary.text}
163
+ </a>
164
+ )}
165
+ {config.navbar?.primary && (
166
+ <a
167
+ class="mobile-navbar-primary-trigger flex h-8 shrink-0 items-center gap-2 rounded-lg [corner-shape:superellipse(1.2)] px-3 text-[13px] font-normal whitespace-nowrap transition-all duration-200 hover:opacity-95 dark:font-medium"
168
+ style={navbarPrimaryButtonStyle}
169
+ href={navbarPrimaryHref}
170
+ x-on:click="open = false"
171
+ >
172
+ {config.navbar.primary.icon && (
173
+ <Icon
174
+ name={config.navbar.primary.icon}
175
+ width="14"
176
+ height="14"
177
+ class="-ml-px"
178
+ />
179
+ )}
180
+ {config.navbar.primary.text}
181
+ </a>
182
+ )}
183
+ </div>
184
+ )}
185
+ </div>
186
+ </nav>
187
+ {navbarLinks.length > 0 && (
188
+ <nav
189
+ id="mobile-navbar-overflow-menu"
190
+ aria-label="More navbar links"
191
+ class="absolute right-4 top-full z-50 mt-2 w-56 max-w-[calc(100vw-2rem)] overflow-y-auto rounded-xl border-[0.5px] border-neutral-200 bg-white p-1 shadow-2xl shadow-neutral-900/12 sm:right-6 dark:border-neutral-800 dark:bg-neutral-800 dark:shadow-black/40"
192
+ data-rd-mobile-navbar-overflow-menu
193
+ hidden
194
+ >
195
+ {navbarLinks.map((link) => (
196
+ <a
197
+ class="flex min-h-9 items-center gap-2 rounded-lg px-2.5 py-2 text-[13px] font-medium text-neutral-700 transition-colors hover:bg-neutral-100/70 hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-neutral-500 dark:font-normal dark:text-neutral-200 dark:hover:bg-neutral-700/70 dark:hover:text-white dark:focus-visible:outline-neutral-400"
198
+ href={link.href}
199
+ x-on:click="open = false"
200
+ data-rd-mobile-navbar-overflow-link
201
+ >
202
+ {link.icon && (
203
+ <Icon
204
+ name={link.icon}
205
+ width="14"
206
+ height="14"
207
+ class="shrink-0"
208
+ />
209
+ )}
210
+ <span class="min-w-0 break-words">{link.text}</span>
211
+ </a>
212
+ ))}
213
+ </nav>
214
+ )}
215
+ </div>
216
+ )
217
+ }
218
+
219
+ <style>
220
+ .mobile-navbar-primary-trigger {
221
+ --mobile-navbar-primary-theme: var(--mobile-navbar-primary-theme-light);
222
+ --mobile-navbar-primary-foreground: var(
223
+ --mobile-navbar-primary-foreground-light
224
+ );
225
+ background: linear-gradient(
226
+ to bottom,
227
+ color-mix(in oklab, var(--mobile-navbar-primary-theme) 88%, white),
228
+ color-mix(in oklab, var(--mobile-navbar-primary-theme) 90%, black)
229
+ );
230
+ color: var(--mobile-navbar-primary-foreground);
231
+ }
232
+
233
+ :global(.dark) .mobile-navbar-primary-trigger {
234
+ --mobile-navbar-primary-theme: var(--mobile-navbar-primary-theme-dark);
235
+ --mobile-navbar-primary-foreground: var(
236
+ --mobile-navbar-primary-foreground-dark
237
+ );
238
+ }
239
+ </style>
240
+
241
+ <script is:inline>
242
+ (() => {
243
+ const cleanupKey = "__radiantDocsMobileNavbarFitCleanup";
244
+ const previousCleanup = window[cleanupKey];
245
+
246
+ if (typeof previousCleanup === "function") previousCleanup();
247
+
248
+ const root = document.querySelector("[data-rd-mobile-navbar-actions]");
249
+ const row = root?.querySelector("[data-rd-mobile-navbar-row]");
250
+ const links = root?.querySelector("[data-rd-mobile-navbar-links]");
251
+ const buttons = root?.querySelector("[data-rd-mobile-navbar-buttons]");
252
+ const overflow = root?.querySelector("[data-rd-mobile-navbar-overflow]");
253
+ const overflowTrigger = root?.querySelector(
254
+ "[data-rd-mobile-navbar-overflow-trigger]",
255
+ );
256
+ const overflowMenu = root?.querySelector(
257
+ "[data-rd-mobile-navbar-overflow-menu]",
258
+ );
259
+
260
+ if (
261
+ !(root instanceof HTMLElement) ||
262
+ !(row instanceof HTMLElement) ||
263
+ !(links instanceof HTMLElement) ||
264
+ !(overflow instanceof HTMLElement) ||
265
+ !(overflowTrigger instanceof HTMLButtonElement) ||
266
+ !(overflowMenu instanceof HTMLElement)
267
+ ) {
268
+ return;
269
+ }
270
+
271
+ const mobileQuery = window.matchMedia("(max-width: 63.999rem)");
272
+ let linksWidth = 0;
273
+ let frameId = 0;
274
+
275
+ const setOverflowMenuOpen = (nextOpen, focusFirstLink = false) => {
276
+ const canOpen =
277
+ nextOpen && mobileQuery.matches && !overflow.hasAttribute("hidden");
278
+
279
+ overflowMenu.hidden = !canOpen;
280
+ overflowTrigger.setAttribute("aria-expanded", String(canOpen));
281
+
282
+ if (!canOpen || !focusFirstLink) return;
283
+
284
+ const firstLink = overflowMenu.querySelector(
285
+ "[data-rd-mobile-navbar-overflow-link]",
286
+ );
287
+ if (firstLink instanceof HTMLElement) firstLink.focus();
288
+ };
289
+
290
+ const measureLinksWidth = () => {
291
+ const clone = links.cloneNode(true);
292
+ if (!(clone instanceof HTMLElement)) return linksWidth;
293
+
294
+ clone.removeAttribute("hidden");
295
+ clone.removeAttribute("data-rd-mobile-navbar-links");
296
+ clone.setAttribute("aria-hidden", "true");
297
+ clone.style.position = "fixed";
298
+ clone.style.inset = "0 auto auto -10000px";
299
+ clone.style.display = "flex";
300
+ clone.style.width = "max-content";
301
+ clone.style.visibility = "hidden";
302
+ clone.style.pointerEvents = "none";
303
+ document.body.append(clone);
304
+
305
+ const width = clone.getBoundingClientRect().width;
306
+ clone.remove();
307
+ return width;
308
+ };
309
+
310
+ const updateLinksVisibility = () => {
311
+ frameId = 0;
312
+
313
+ if (!mobileQuery.matches || row.clientWidth <= 0) {
314
+ if (!mobileQuery.matches) {
315
+ links.removeAttribute("hidden");
316
+ overflow.setAttribute("hidden", "");
317
+ setOverflowMenuOpen(false);
318
+ }
319
+ return;
320
+ }
321
+
322
+ const rowGap = Number.parseFloat(getComputedStyle(row).columnGap) || 0;
323
+ const buttonsWidth =
324
+ buttons instanceof HTMLElement
325
+ ? buttons.getBoundingClientRect().width
326
+ : 0;
327
+ const requiredWidth =
328
+ linksWidth +
329
+ buttonsWidth +
330
+ (buttons instanceof HTMLElement ? rowGap : 0);
331
+ const linksDoNotFit = requiredWidth > row.clientWidth;
332
+
333
+ links.toggleAttribute("hidden", linksDoNotFit);
334
+ overflow.toggleAttribute("hidden", !linksDoNotFit);
335
+ if (!linksDoNotFit) setOverflowMenuOpen(false);
336
+ };
337
+
338
+ const scheduleUpdate = () => {
339
+ if (frameId !== 0) return;
340
+ frameId = requestAnimationFrame(updateLinksVisibility);
341
+ };
342
+
343
+ const onOverflowTriggerClick = () => {
344
+ setOverflowMenuOpen(overflowMenu.hidden);
345
+ };
346
+
347
+ const onOverflowTriggerKeydown = (event) => {
348
+ if (event.key !== "ArrowDown") return;
349
+ event.preventDefault();
350
+ setOverflowMenuOpen(true, true);
351
+ };
352
+
353
+ const onDocumentPointerDown = (event) => {
354
+ if (overflowMenu.hidden || !(event.target instanceof Node)) return;
355
+ if (
356
+ overflowTrigger.contains(event.target) ||
357
+ overflowMenu.contains(event.target)
358
+ ) {
359
+ return;
360
+ }
361
+ setOverflowMenuOpen(false);
362
+ };
363
+
364
+ const onDocumentKeydown = (event) => {
365
+ if (event.key !== "Escape" || overflowMenu.hidden) return;
366
+ setOverflowMenuOpen(false);
367
+ overflowTrigger.focus();
368
+ };
369
+
370
+ const onOverflowMenuClick = (event) => {
371
+ if (
372
+ event.target instanceof Element &&
373
+ event.target.closest("[data-rd-mobile-navbar-overflow-link]")
374
+ ) {
375
+ setOverflowMenuOpen(false);
376
+ }
377
+ };
378
+
379
+ linksWidth = measureLinksWidth();
380
+ updateLinksVisibility();
381
+
382
+ const resizeObserver = new ResizeObserver(scheduleUpdate);
383
+ resizeObserver.observe(row);
384
+ if (buttons instanceof HTMLElement) resizeObserver.observe(buttons);
385
+ mobileQuery.addEventListener("change", scheduleUpdate);
386
+ overflowTrigger.addEventListener("click", onOverflowTriggerClick);
387
+ overflowTrigger.addEventListener("keydown", onOverflowTriggerKeydown);
388
+ overflowMenu.addEventListener("click", onOverflowMenuClick);
389
+ document.addEventListener("pointerdown", onDocumentPointerDown);
390
+ document.addEventListener("keydown", onDocumentKeydown);
391
+
392
+ document.fonts?.ready.then(() => {
393
+ if (!root.isConnected) return;
394
+ linksWidth = measureLinksWidth();
395
+ scheduleUpdate();
396
+ });
397
+
398
+ window[cleanupKey] = () => {
399
+ if (frameId !== 0) cancelAnimationFrame(frameId);
400
+ resizeObserver.disconnect();
401
+ mobileQuery.removeEventListener("change", scheduleUpdate);
402
+ overflowTrigger.removeEventListener("click", onOverflowTriggerClick);
403
+ overflowTrigger.removeEventListener("keydown", onOverflowTriggerKeydown);
404
+ overflowMenu.removeEventListener("click", onOverflowMenuClick);
405
+ document.removeEventListener("pointerdown", onDocumentPointerDown);
406
+ document.removeEventListener("keydown", onDocumentKeydown);
407
+ overflowMenu.hidden = true;
408
+ };
409
+ })();
410
+ </script>
@@ -23,7 +23,7 @@ function buildHref(route: Route): string {
23
23
 
24
24
  {
25
25
  (previousRoute || nextRoute) && (
26
- <nav class="w-full pt-16" aria-label="Page pagination">
26
+ <nav class="w-full" aria-label="Page pagination">
27
27
  <div class="flex gap-4 flex-col sm:flex-row">
28
28
  {previousRoute && (
29
29
  <a
@@ -12,9 +12,15 @@ import { Icon } from "astro-icon/components";
12
12
  <!-- Search Trigger Button -->
13
13
  <button
14
14
  x-on:click="open()"
15
- class="md:bg-white/90 dark:md:bg-neutral-800 flex items-center gap-2 h-[33px] md:min-w-64 px-3 -mr-3 xs:mr-0 text-xs text-neutral-500/80 dark:text-neutral-400/70 hover:text-neutral-500 dark:hover:text-neutral-400 md:border border-border rounded-lg cursor-pointer md:shadow-xs md:hover:shadow-sm transition"
15
+ x-bind:data-search-open="isOpen ? 'true' : 'false'"
16
+ aria-label="Search documentation"
17
+ class="mobile-search-trigger flex size-10 cursor-pointer items-center justify-center rounded-lg text-xs text-neutral-900 transition-colors hover:bg-neutral-100/70 dark:text-white dark:hover:bg-neutral-800/70 md:h-[33px] md:w-auto md:min-w-60 md:justify-start md:gap-2 md:border md:border-border md:bg-white/90 md:px-3 md:text-neutral-500/80 md:shadow-xs md:transition md:hover:bg-white/90 md:hover:text-neutral-500 md:hover:shadow-sm dark:md:bg-neutral-800 dark:md:text-neutral-400/70 dark:md:hover:bg-neutral-800 dark:md:hover:text-neutral-400 lg:min-w-64"
16
18
  >
17
- <Icon name="lucide:search" class="size-5 md:size-4" />
19
+ <Icon
20
+ name="lucide:search"
21
+ is:inline
22
+ class="mobile-search-icon size-4"
23
+ />
18
24
  <span class="hidden md:inline">Search documentation</span>
19
25
  <kbd
20
26
  class="font-sans tracking-wider hidden md:inline-flex ml-auto items-center gap-0.5 px-1.5 py-px text-[10px] text-neutral-500 dark:text-neutral-400 font-medium bg-white dark:bg-neutral-700 border border-border/80 rounded"
@@ -53,6 +59,7 @@ import { Icon } from "astro-icon/components";
53
59
  >
54
60
  <Icon
55
61
  name="lucide:search"
62
+ is:inline
56
63
  class="size-5 sm:size-[18px] text-neutral-400 dark:text-neutral-500 shrink-0"
57
64
  />
58
65
  <input
@@ -94,7 +101,8 @@ import { Icon } from "astro-icon/components";
94
101
  class="size-8 mx-auto mb-3 text-neutral-300 dark:text-neutral-600"
95
102
  />
96
103
  <p class="text-sm">
97
- No results found for <strong class="text-neutral-600 dark:text-neutral-300"
104
+ No results found for <strong
105
+ class="text-neutral-600 dark:text-neutral-300"
98
106
  >"<span x-text="query"></span>"</strong
99
107
  >.
100
108
  </p>
@@ -172,6 +180,19 @@ import { Icon } from "astro-icon/components";
172
180
  </template>
173
181
  </div>
174
182
 
183
+ <style>
184
+ @media (max-width: 47.999rem) {
185
+ .mobile-search-trigger[data-search-open="true"] {
186
+ background-color: transparent !important;
187
+ transition: none;
188
+ }
189
+
190
+ .mobile-search-icon :global(g) {
191
+ stroke-width: 2.75;
192
+ }
193
+ }
194
+ </style>
195
+
175
196
  <script>
176
197
  import {
177
198
  search as pagefindSearch,
@@ -1,18 +1,26 @@
1
1
  ---
2
2
  import { getConfig, type DocsConfig } from "../lib/validation";
3
+ import MobileNavbarActions from "./MobileNavbarActions.astro";
3
4
  import ThemeSwitcher from "./ThemeSwitcher.astro";
4
5
  import SidebarMenu from "./SidebarMenu.astro";
5
6
 
6
7
  interface Props {
7
8
  askAiEnabled?: boolean;
9
+ showMobileNavbarActions?: boolean;
8
10
  }
9
11
 
10
- const { askAiEnabled = false } = Astro.props as Props;
12
+ const { askAiEnabled = false, showMobileNavbarActions = false } =
13
+ Astro.props as Props;
11
14
 
12
15
  const config: DocsConfig = await getConfig();
13
16
  ---
14
17
 
15
18
  <aside class="flex flex-col h-full">
19
+ {
20
+ showMobileNavbarActions ? (
21
+ <MobileNavbarActions />
22
+ ) : null
23
+ }
16
24
  <nav
17
25
  data-rd-sidebar-scroll-container
18
26
  class="min-h-0 flex-1 overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
@@ -64,7 +64,9 @@ const selectedTabItem = tabItems[currentTabIndex];
64
64
  x-data={`{ selectedTabIndex: ${currentTabIndex} }`}
65
65
  x-init={`$watch('open', (value) => { if (value) selectedTabIndex = ${currentTabIndex} })`}
66
66
  >
67
- <div class="lg:hidden h-11 border-b border-border-light bg-background">
67
+ <div
68
+ class="sticky top-0 z-20 h-11 border-b border-border-light bg-background lg:hidden"
69
+ >
68
70
  <nav
69
71
  class="h-full overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
70
72
  aria-label="Documentation sections"
@@ -6,7 +6,7 @@ import AssistantEmbedPanel, {
6
6
  AssistantPanelIcon,
7
7
  type AssistantPanelSize,
8
8
  } from "./AssistantEmbedPanel";
9
- import type { AssistantShikiRuntimeConfig } from "../../lib/assistant-shiki-client";
9
+ import type { AssistantShikiRuntimeConfig } from "@radiant-docs/assistant-shiki";
10
10
  import {
11
11
  DEFAULT_ASSISTANT_CHROME_CONFIG,
12
12
  type AssistantChromeConfig,
@@ -6,13 +6,14 @@ import remarkParse from "remark-parse";
6
6
  import remarkGfm from "remark-gfm";
7
7
  import remarkRehype from "remark-rehype";
8
8
  import rehypeStringify from "rehype-stringify";
9
- import { getDocsBasePath, withBasePath } from "../../lib/base-path";
10
9
  import {
11
10
  highlightAssistantCodeToHtml,
12
11
  normalizeAssistantCodeLanguage,
12
+ resolveAssistantLanguageLabel,
13
13
  warmAssistantShikiRuntime,
14
14
  type AssistantShikiRuntimeConfig,
15
- } from "../../lib/assistant-shiki-client";
15
+ } from "@radiant-docs/assistant-shiki";
16
+ import { getDocsBasePath, withBasePath } from "../../lib/base-path";
16
17
 
17
18
  type AssistantLinkTarget = "current" | "blank";
18
19
  export type AssistantPanelSize = "default" | "expanded";
@@ -129,30 +130,6 @@ const DEFAULT_ASSISTANT_MOBILE_BREAKPOINT = "640px";
129
130
  const DEFAULT_ASSISTANT_PANEL_FULLSCREEN_HEIGHT_BREAKPOINT = "560px";
130
131
  const markdownHtmlCache = new Map<string, string>();
131
132
  const markdownHtmlPromiseCache = new Map<string, Promise<string>>();
132
- const ASSISTANT_LANGUAGE_LABEL: Record<string, string> = {
133
- bash: "Bash",
134
- css: "CSS",
135
- diff: "Diff",
136
- go: "Go",
137
- html: "HTML",
138
- java: "Java",
139
- javascript: "JavaScript",
140
- jsx: "JSX",
141
- json: "JSON",
142
- markdown: "Markdown",
143
- mdx: "MDX",
144
- php: "PHP",
145
- plaintext: "Text",
146
- python: "Python",
147
- ruby: "Ruby",
148
- rust: "Rust",
149
- shell: "Shell",
150
- sql: "SQL",
151
- tsx: "TSX",
152
- typescript: "TypeScript",
153
- yaml: "YAML",
154
- };
155
-
156
133
  const EXTERNAL_PROTOCOL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
157
134
 
158
135
  function buildMarkdownCacheKey(
@@ -633,20 +610,6 @@ function escapeHtml(value: string): string {
633
610
  .replaceAll("'", "&#39;");
634
611
  }
635
612
 
636
- function resolveAssistantLanguageLabel(language: string): string {
637
- const normalized = language.trim().toLowerCase();
638
- if (!normalized) {
639
- return "";
640
- }
641
-
642
- return (
643
- ASSISTANT_LANGUAGE_LABEL[normalized] ??
644
- normalized
645
- .replace(/[-_]+/g, " ")
646
- .replace(/\b\w/g, (char) => char.toUpperCase())
647
- );
648
- }
649
-
650
613
  function ensureCodeBlockCopyButton(preElement: HTMLPreElement): void {
651
614
  const existingButton = Array.from(preElement.children).find(
652
615
  (child) =>
@@ -1025,20 +988,33 @@ function AssistantSourcesBlock({
1025
988
  }
1026
989
 
1027
990
  return (
1028
- <div
1029
- className="ask-ai-sources ask-ai-markdown prose-rules mt-3 max-w-full min-w-0 wrap-break-word text-[15px]!"
1030
- onClick={onClick}
1031
- >
1032
- <p>Sources:</p>
1033
- <ul>
991
+ <div className="ask-ai-sources mt-4 max-w-full min-w-0" onClick={onClick}>
992
+ <div className="text-xs font-semibold text-neutral-700 dark:text-neutral-200">
993
+ Sources
994
+ </div>
995
+ <ul className="mt-2 flex flex-wrap gap-2">
1034
996
  {visibleSources.map((source) => (
1035
- <li key={`${source.href}\0${source.label}`}>
997
+ <li
998
+ key={`${source.href}\0${source.label}`}
999
+ className="min-w-0 max-w-full"
1000
+ >
1036
1001
  <a
1037
1002
  href={withBasePath(source.href)}
1038
1003
  target={linkTarget === "blank" ? "_blank" : undefined}
1039
1004
  rel={linkTarget === "blank" ? "noopener noreferrer" : undefined}
1005
+ className="inline-flex max-w-full items-center gap-1.5 rounded-md border border-neutral-200 bg-white px-2 py-1 text-xs font-medium text-neutral-700 transition-colors hover:bg-neutral-50 hover:text-neutral-950 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-200 dark:hover:bg-neutral-800 dark:hover:text-white"
1040
1006
  >
1041
- {source.label}
1007
+ <Icon
1008
+ icon="lucide:file-text"
1009
+ className="size-3.5 shrink-0"
1010
+ aria-hidden="true"
1011
+ />
1012
+ <span className="truncate">{source.label}</span>
1013
+ <Icon
1014
+ icon="lucide:external-link"
1015
+ className="size-3 shrink-0 text-neutral-500 dark:text-neutral-400"
1016
+ aria-hidden="true"
1017
+ />
1042
1018
  </a>
1043
1019
  </li>
1044
1020
  ))}
@@ -2846,6 +2822,18 @@ export default function AssistantEmbedPanel({
2846
2822
  -ms-overflow-style: none;
2847
2823
  }
2848
2824
 
2825
+ .ask-ai-markdown [data-rd-code-line] {
2826
+ display: flex;
2827
+ width: max-content;
2828
+ min-width: 100%;
2829
+ }
2830
+
2831
+ .ask-ai-markdown [data-rd-code-line-content] {
2832
+ flex: 1;
2833
+ padding-inline: 0.75rem;
2834
+ white-space: pre;
2835
+ }
2836
+
2849
2837
  .dark .ask-ai-markdown pre > code {
2850
2838
  scrollbar-width: none;
2851
2839
  }
@@ -1074,7 +1074,7 @@ const headerValuePrefixes = Object.fromEntries(
1074
1074
  import {
1075
1075
  highlightAssistantCodeToHtml,
1076
1076
  type AssistantShikiRuntimeConfig,
1077
- } from "../../lib/assistant-shiki-client";
1077
+ } from "@radiant-docs/assistant-shiki";
1078
1078
 
1079
1079
  type PlaygroundShikiConfig = AssistantShikiRuntimeConfig | null | undefined;
1080
1080