radiant-docs 0.1.68 → 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>
@@ -59,7 +59,7 @@ const snippetStickyClass = hasTopbarNavigationTabs
59
59
  : "top-[92px] max-h-[calc(100vh-92px)]";
60
60
  ---
61
61
 
62
- <Layout pageTitle={title}>
62
+ <Layout pageTitle={title} hasMarkdownAlternate>
63
63
  <article class="mx-auto w-full max-w-7xl">
64
64
  <header
65
65
  class="mb-8 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between"
@@ -431,24 +431,40 @@ const menuItems: MenuItem[] = [
431
431
  }
432
432
  </style>
433
433
 
434
- <script is:inline data-astro-rerun>
435
- (() => {
436
- const script = document.currentScript;
437
- let root = script?.previousElementSibling;
438
- while (
439
- root &&
440
- (!(root instanceof HTMLElement) ||
441
- !root.hasAttribute("data-rd-page-actions"))
442
- ) {
443
- root = root.previousElementSibling;
444
- }
434
+ <script>
435
+ const pageActionsSelector = "[data-rd-page-actions]";
436
+ const pageActionsControlsKey = "__rdPageActionsControls";
437
+
438
+ function fallbackCopy(text) {
439
+ const textarea = document.createElement("textarea");
440
+ textarea.value = text;
441
+ textarea.setAttribute("readonly", "");
442
+ textarea.style.position = "fixed";
443
+ textarea.style.opacity = "0";
444
+ document.body.appendChild(textarea);
445
+ textarea.select();
446
+ const copied = document.execCommand("copy");
447
+ document.body.removeChild(textarea);
448
+ return copied;
449
+ }
445
450
 
446
- if (
447
- !(root instanceof HTMLElement) ||
448
- root.dataset.pageActionsBound === "true"
449
- ) {
450
- return;
451
+ async function copyToClipboard(text) {
452
+ try {
453
+ if (navigator.clipboard?.writeText) {
454
+ await navigator.clipboard.writeText(text);
455
+ return true;
456
+ }
457
+ } catch {
458
+ // Fallback below when clipboard API is unavailable or blocked.
451
459
  }
460
+
461
+ return fallbackCopy(text);
462
+ }
463
+
464
+ function initPageActions(root) {
465
+ if (!(root instanceof HTMLElement)) return;
466
+ if (root.dataset.pageActionsBound === "true") return;
467
+
452
468
  root.dataset.pageActionsBound = "true";
453
469
 
454
470
  const trigger = root.querySelector("[data-rd-page-actions-trigger]");
@@ -463,32 +479,6 @@ const menuItems: MenuItem[] = [
463
479
  let mcpMetadataPromise;
464
480
  const copyFeedbackTimeouts = new WeakMap();
465
481
 
466
- function fallbackCopy(text) {
467
- const textarea = document.createElement("textarea");
468
- textarea.value = text;
469
- textarea.setAttribute("readonly", "");
470
- textarea.style.position = "fixed";
471
- textarea.style.opacity = "0";
472
- document.body.appendChild(textarea);
473
- textarea.select();
474
- const copied = document.execCommand("copy");
475
- document.body.removeChild(textarea);
476
- return copied;
477
- }
478
-
479
- async function copyToClipboard(text) {
480
- try {
481
- if (navigator.clipboard?.writeText) {
482
- await navigator.clipboard.writeText(text);
483
- return true;
484
- }
485
- } catch {
486
- // Fallback below when clipboard API is unavailable or blocked.
487
- }
488
-
489
- return fallbackCopy(text);
490
- }
491
-
492
482
  function setExpanded(nextExpanded) {
493
483
  if (!(trigger instanceof HTMLElement)) return;
494
484
  trigger.setAttribute("aria-expanded", String(nextExpanded));
@@ -711,5 +701,21 @@ const menuItems: MenuItem[] = [
711
701
  closeMenu();
712
702
  });
713
703
  });
714
- })();
704
+ }
705
+
706
+ function initAllPageActions() {
707
+ document.querySelectorAll(pageActionsSelector).forEach((root) => {
708
+ initPageActions(root);
709
+ });
710
+ }
711
+
712
+ window[pageActionsControlsKey] = window[pageActionsControlsKey] || {};
713
+ window[pageActionsControlsKey].init = initAllPageActions;
714
+
715
+ if (!window[pageActionsControlsKey].installed) {
716
+ window[pageActionsControlsKey].installed = true;
717
+ document.addEventListener("astro:page-load", initAllPageActions);
718
+ }
719
+
720
+ initAllPageActions();
715
721
  </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,24 +1,59 @@
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"
19
27
  >
20
28
  <SidebarMenu navigation={config.navigation} />
21
29
  </nav>
30
+ <script is:inline>
31
+ (() => {
32
+ const script = document.currentScript;
33
+ const sidebar = script?.previousElementSibling;
34
+
35
+ if (
36
+ !(sidebar instanceof HTMLElement) ||
37
+ !sidebar.hasAttribute("data-rd-sidebar-scroll-container")
38
+ ) {
39
+ return;
40
+ }
41
+
42
+ if (document.readyState !== "loading") return;
43
+
44
+ try {
45
+ const value = Number(
46
+ sessionStorage.getItem("radiant-docs:sidebar-scroll-top"),
47
+ );
48
+ if (!Number.isFinite(value) || value <= 0) return;
49
+
50
+ sidebar.scrollTop = Math.max(0, Math.round(value));
51
+ sidebar.dataset.rdSidebarScrollRestored = "true";
52
+ } catch {
53
+ // Storage can be unavailable in private browsing or locked-down embeds.
54
+ }
55
+ })();
56
+ </script>
22
57
  <div
23
58
  class:list={[
24
59
  "bg-background z-10 px-3 pt-3 pb-[calc(env(safe-area-inset-bottom)+0.75rem)] border-t border-t-border-light flex gap-1.5 items-center",
@@ -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"