radiant-docs 0.1.34 → 0.1.38

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 (56) hide show
  1. package/package.json +1 -1
  2. package/template/astro.config.mjs +27 -0
  3. package/template/package-lock.json +1027 -513
  4. package/template/package.json +3 -2
  5. package/template/scripts/generate-proxy-allowed-origins.mjs +217 -0
  6. package/template/scripts/generate-robots-txt.mjs +19 -0
  7. package/template/scripts/stamp-image-versions.mjs +63 -11
  8. package/template/src/components/Footer.astro +1 -1
  9. package/template/src/components/Header.astro +9 -9
  10. package/template/src/components/LogoLink.astro +2 -1
  11. package/template/src/components/OpenApiPage.astro +18 -18
  12. package/template/src/components/Search.astro +18 -18
  13. package/template/src/components/Sidebar.astro +4 -2
  14. package/template/src/components/SidebarDropdown.astro +82 -79
  15. package/template/src/components/SidebarGroup.astro +3 -0
  16. package/template/src/components/SidebarMenu.astro +14 -1
  17. package/template/src/components/SidebarSegmented.astro +5 -5
  18. package/template/src/components/SidebarSubgroup.astro +35 -12
  19. package/template/src/components/TableOfContents.astro +24 -15
  20. package/template/src/components/ThemeSwitcher.astro +15 -8
  21. package/template/src/components/chat/AskAiWidget.tsx +10 -5
  22. package/template/src/components/endpoint/PlaygroundBar.astro +3 -3
  23. package/template/src/components/endpoint/PlaygroundButton.astro +3 -3
  24. package/template/src/components/endpoint/PlaygroundField.astro +53 -53
  25. package/template/src/components/endpoint/PlaygroundForm.astro +51 -37
  26. package/template/src/components/endpoint/RequestSnippets.astro +54 -21
  27. package/template/src/components/endpoint/ResponseDisplay.astro +24 -24
  28. package/template/src/components/endpoint/ResponseFieldTree.astro +12 -12
  29. package/template/src/components/endpoint/ResponseFields.astro +19 -19
  30. package/template/src/components/endpoint/ResponseSnippets.astro +66 -29
  31. package/template/src/components/sidebar/SidebarEndpointLink.astro +18 -15
  32. package/template/src/components/sidebar/SidebarOpenApiPageLink.astro +56 -0
  33. package/template/src/components/ui/CodeTabEdge.astro +6 -4
  34. package/template/src/components/ui/Field.astro +7 -7
  35. package/template/src/components/ui/Icon.astro +2 -1
  36. package/template/src/components/ui/demo/Demo.astro +1 -1
  37. package/template/src/components/user/Accordion.astro +3 -3
  38. package/template/src/components/user/Callout.astro +8 -8
  39. package/template/src/components/user/CodeBlock.astro +57 -22
  40. package/template/src/components/user/CodeGroup.astro +14 -10
  41. package/template/src/components/user/ComponentPreviewBlock.astro +38 -12
  42. package/template/src/components/user/Image.astro +6 -2
  43. package/template/src/components/user/Step.astro +4 -4
  44. package/template/src/components/user/Tab.astro +1 -1
  45. package/template/src/components/user/Tabs.astro +15 -20
  46. package/template/src/layouts/Layout.astro +9 -4
  47. package/template/src/lib/code/code-block.ts +150 -15
  48. package/template/src/lib/mdx/remark-resolve-internal-links.ts +639 -0
  49. package/template/src/lib/pagefind.ts +2 -1
  50. package/template/src/lib/routes.ts +134 -58
  51. package/template/src/lib/static-asset-url.ts +62 -0
  52. package/template/src/lib/utils.ts +48 -0
  53. package/template/src/lib/validation.ts +115 -27
  54. package/template/src/pages/404.astro +44 -0
  55. package/template/src/styles/global.css +28 -19
  56. package/template/scripts/rewrite-static-asset-host.mjs +0 -408
@@ -12,7 +12,7 @@ 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/80 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
+ 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"
16
16
  >
17
17
  <Icon name="lucide:search" class="size-5 md:size-4" />
18
18
  <span class="hidden md:inline">Search documentation</span>
@@ -49,11 +49,11 @@ import { Icon } from "astro-icon/components";
49
49
  <!-- Search Input -->
50
50
  <div
51
51
  class="flex items-center gap-3 px-4"
52
- :class="{ 'border-b border-neutral-200/80': results.length > 0 || (query.length > 0 && !loading) }"
52
+ :class="{ 'border-b border-neutral-200/80 dark:border-neutral-700/70': results.length > 0 || (query.length > 0 && !loading) }"
53
53
  >
54
54
  <Icon
55
55
  name="lucide:search"
56
- class="size-5 sm:size-[18px] text-neutral-400 shrink-0"
56
+ class="size-5 sm:size-[18px] text-neutral-400 dark:text-neutral-500 shrink-0"
57
57
  />
58
58
  <input
59
59
  x-ref="searchInput"
@@ -65,18 +65,18 @@ import { Icon } from "astro-icon/components";
65
65
  x-on:keydown.enter.prevent="goToSelected()"
66
66
  type="text"
67
67
  placeholder="Search documentation..."
68
- class="flex-1 py-4 text-base bg-transparent outline-none placeholder:text-neutral-400 sm:text-sm"
68
+ class="flex-1 py-4 text-base bg-transparent text-neutral-900 dark:text-neutral-100 outline-none placeholder:text-neutral-400 dark:placeholder:text-neutral-500 sm:text-sm"
69
69
  />
70
70
  <div x-show="loading" class="shrink-0 p-1">
71
71
  <Icon
72
72
  name="lucide:loader"
73
- class="w-4 h-4 text-neutral-400 animate-spin"
73
+ class="w-4 h-4 text-neutral-400 dark:text-neutral-500 animate-spin"
74
74
  />
75
75
  </div>
76
76
  <button
77
77
  x-show="query.length > 0 && !loading"
78
78
  x-on:click="clear()"
79
- class="shrink-0 p-1 text-neutral-400 hover:text-neutral-600 transition-colors"
79
+ class="shrink-0 p-1 text-neutral-400 dark:text-neutral-500 hover:text-neutral-600 dark:hover:text-neutral-300 transition-colors"
80
80
  >
81
81
  <Icon name="lucide:x" class="w-4 h-4" />
82
82
  </button>
@@ -87,14 +87,14 @@ import { Icon } from "astro-icon/components";
87
87
  <!-- No Results -->
88
88
  <div
89
89
  x-show="query.length > 0 && results.length === 0 && !loading"
90
- class="px-4 py-8 text-center text-neutral-500"
90
+ class="px-4 py-8 text-center text-neutral-500 dark:text-neutral-400"
91
91
  >
92
92
  <Icon
93
93
  name="lucide:search-slash"
94
- class="size-8 mx-auto mb-3 text-neutral-300"
94
+ class="size-8 mx-auto mb-3 text-neutral-300 dark:text-neutral-600"
95
95
  />
96
96
  <p class="text-sm">
97
- No results found for <strong class="text-neutral-600"
97
+ No results found for <strong class="text-neutral-600 dark:text-neutral-300"
98
98
  >"<span x-text="query"></span>"</strong
99
99
  >.
100
100
  </p>
@@ -103,7 +103,7 @@ import { Icon } from "astro-icon/components";
103
103
  <!-- Results List -->
104
104
  <ul
105
105
  x-show="results.length > 0"
106
- class="divide-y divide-neutral-200/80"
106
+ class="divide-y divide-neutral-200/80 dark:divide-neutral-700/70"
107
107
  >
108
108
  <template
109
109
  x-for="(result, index) in results"
@@ -115,30 +115,30 @@ import { Icon } from "astro-icon/components";
115
115
  x-on:click="close()"
116
116
  x-on:mouseenter="selectedIndex = index"
117
117
  class="flex flex-col gap-1 px-4 py-3 transition-colors"
118
- :class="{ 'bg-neutral-100/60': selectedIndex === index }"
118
+ :class="{ 'bg-neutral-100/60 dark:bg-neutral-700/30': selectedIndex === index }"
119
119
  >
120
120
  <div class="flex items-center gap-2">
121
121
  <!-- Page icon -->
122
122
  <Icon
123
123
  name="lucide:file"
124
124
  x-show="result.type === 'page'"
125
- class="w-4 h-4 text-neutral-400 shrink-0"
125
+ class="w-4 h-4 text-neutral-400 dark:text-neutral-500 shrink-0"
126
126
  />
127
127
  <!-- Heading icon -->
128
128
  <Icon
129
129
  name="lucide:hash"
130
130
  x-show="result.type === 'heading'"
131
- class="w-4 h-4 text-neutral-400 shrink-0"
131
+ class="w-4 h-4 text-neutral-400 dark:text-neutral-500 shrink-0"
132
132
  />
133
133
  <!-- Content/body text icon -->
134
134
  <Icon
135
135
  name="lucide:text"
136
136
  x-show="result.type === 'content'"
137
- class="w-4 h-4 text-neutral-400 shrink-0"
137
+ class="w-4 h-4 text-neutral-400 dark:text-neutral-500 shrink-0"
138
138
  />
139
139
  <!-- Parent title breadcrumb -->
140
140
  <span
141
- class="text-sm text-neutral-500 truncate"
141
+ class="text-sm text-neutral-500 dark:text-neutral-400 truncate"
142
142
  x-show="result.parentTitle"
143
143
  >
144
144
  <span x-text="result.parentTitle"></span>
@@ -147,17 +147,17 @@ import { Icon } from "astro-icon/components";
147
147
  <!-- Title: highlighted for page/heading, plain for content -->
148
148
  <span
149
149
  x-show="result.type === 'page' || result.type === 'heading'"
150
- class="font-medium text-neutral-900 truncate [&_mark]:bg-neutral-200 [&_mark]:text-neutral-900 [&_mark]:rounded [&_mark]:px-0.5"
150
+ class="font-medium text-neutral-900 dark:text-neutral-100 truncate [&_mark]:bg-neutral-200 [&_mark]:text-neutral-900 dark:[&_mark]:bg-neutral-700 dark:[&_mark]:text-neutral-100 [&_mark]:rounded [&_mark]:px-0.5"
151
151
  x-html="highlightMatch(result.title, query)"></span>
152
152
  <span
153
153
  x-show="result.type === 'content'"
154
- class="font-medium text-neutral-900 truncate"
154
+ class="font-medium text-neutral-900 dark:text-neutral-100 truncate"
155
155
  x-text="result.title"></span>
156
156
  </div>
157
157
  <!-- Excerpt: only for content type -->
158
158
  <p
159
159
  x-show="result.type === 'content'"
160
- class="text-sm text-neutral-600 line-clamp-2 pl-6 [&_mark]:bg-neutral-200 [&_mark]:text-neutral-900 [&_mark]:rounded [&_mark]:px-0.5"
160
+ class="text-sm text-neutral-600 dark:text-neutral-300 line-clamp-2 pl-6 [&_mark]:bg-neutral-200 [&_mark]:text-neutral-900 dark:[&_mark]:bg-neutral-700 dark:[&_mark]:text-neutral-100 [&_mark]:rounded [&_mark]:px-0.5"
161
161
  x-html="result.excerpt"
162
162
  >
163
163
  </p>
@@ -7,11 +7,13 @@ const config: DocsConfig = await getConfig();
7
7
  ---
8
8
 
9
9
  <aside class="flex flex-col h-full">
10
- <nav class="overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
10
+ <nav
11
+ class="overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
12
+ >
11
13
  <SidebarMenu navigation={config.navigation} />
12
14
  </nav>
13
15
  <div
14
- class="mt-auto bg-white z-10 p-3 border-t border-t-border-light flex gap-1.5 justify-end items-center"
16
+ class="mt-auto bg-background z-10 p-3 border-t border-t-border-light flex gap-1.5 justify-end items-center"
15
17
  >
16
18
  <span class="text-neutral-400 text-xs font-light">Theme</span>
17
19
  <ThemeSwitcher />
@@ -69,88 +69,91 @@ const currentPrefix = parentSlug
69
69
  <div x-data=`{
70
70
  open: false,
71
71
  }`>
72
- <div
73
- class:list={[
74
- "mt-3 mx-2 z-10 relative",
75
- menu.label && "rounded-lg bg-neutral-100 p-[3px]",
76
- ]}
77
- >
78
- {
79
- menu.label && (
80
- <label class="font-semibold text-xs px-2 py-1.5 block">
81
- {menu.label}
82
- </label>
83
- )
84
- }
85
- <div class="relative">
86
- <button
87
- class="flex items-center w-full text-sm text-neutral-700 bg-white border-t border-x border-neutral-200/70 rounded-lg shadow-sm shadow-neutral-200 px-3 py-2 cursor-pointer"
88
- x-on:click="open = true"
89
- aria-haspopup="menu"
90
- aria-expanded
91
- >
92
- {
93
- menu.items[currentMenuIndex].icon && (
94
- <Icon
95
- class="mr-2 size-4 opacity-80"
96
- name={menu.items[currentMenuIndex].icon}
97
- />
98
- )
99
- }
100
- <span class="font-medium">{menu.items[currentMenuIndex].label}</span>
101
- <Icon class="ml-auto" name="lucide:chevrons-up-down" />
102
- </button>
103
- <div class="fixed inset-0 z-50" x-show="open" x-on:click="open = false">
104
- </div>
105
- <ul
106
- class:list={[
107
- "z-50 absolute bg-white border border-neutral-200 rounded-lg inset-x-0 top-full py-[3px] shadow-xl overflow-hidden",
108
- menu.label ? "mt-1.5" : "mt-1",
109
- ]}
110
- x-init
111
- role="menu"
112
- x-show="open"
113
- x-transition.origin.top
114
- x-cloak
115
- >
116
- {
117
- menu.items.map(({ label }, index) => {
118
- return (
119
- <li
120
- x-data={`{
72
+ <div class="mt-3 mx-2 z-10 relative dark:bg-neutral-900 rounded-lg">
73
+ <div
74
+ class:list={[
75
+ "",
76
+ menu.label &&
77
+ "rounded-lg bg-neutral-100 dark:bg-neutral-800/60 p-[3px]",
78
+ ]}
79
+ >
80
+ {
81
+ menu.label && (
82
+ <label class="font-semibold text-xs px-2 py-1.5 block">
83
+ {menu.label}
84
+ </label>
85
+ )
86
+ }
87
+ <div class="relative">
88
+ <button
89
+ class="flex items-center w-full text-sm text-neutral-700 dark:text-neutral-200 bg-white dark:bg-neutral-700/30 border-t border-x border-neutral-200/70 dark:border-neutral-700/40 dark:border-b rounded-lg shadow-xs px-3 py-2 cursor-pointer"
90
+ x-on:click="open = true"
91
+ aria-haspopup="menu"
92
+ aria-expanded
93
+ >
94
+ {
95
+ menu.items[currentMenuIndex].icon && (
96
+ <Icon
97
+ class="mr-2 size-4 opacity-80"
98
+ name={menu.items[currentMenuIndex].icon}
99
+ />
100
+ )
101
+ }
102
+ <span class="font-medium">{menu.items[currentMenuIndex].label}</span>
103
+ <Icon class="ml-auto" name="lucide:chevrons-up-down" />
104
+ </button>
105
+ <div class="fixed inset-0 z-50" x-show="open" x-on:click="open = false">
106
+ </div>
107
+ <ul
108
+ class:list={[
109
+ "z-50 absolute bg-white dark:bg-neutral-800 border dark:border-neutral-700/40 rounded-lg inset-x-0 top-full py-[3px] shadow-xl overflow-hidden",
110
+ menu.label ? "mt-1.5" : "mt-1",
111
+ ]}
112
+ x-init
113
+ role="menu"
114
+ x-show="open"
115
+ x-transition.origin.top
116
+ x-cloak
117
+ >
118
+ {
119
+ menu.items.map(({ label }, index) => {
120
+ return (
121
+ <li
122
+ x-data={`{
121
123
  index: ${index}
122
124
  }`}
123
- role="menuitem"
124
- >
125
- <a
126
- class="flex items-center px-3 py-2 cursor-pointer text-sm text-neutral-700 relative z-0 before:-z-10 before:absolute before:inset-x-1 before:inset-y-px before:rounded-md before:duration-150"
127
- class:list={[
128
- index === currentMenuIndex
129
- ? "before:bg-neutral-200/50 text-neutral-900"
130
- : "hover:before:bg-neutral-100/70",
131
- ]}
132
- href={firstHrefOfMenuItems[index] ?? "/"}
125
+ role="menuitem"
133
126
  >
134
- {menu.items[index].icon && (
135
- <Icon
136
- class="mr-2 size-4 opacity-75"
137
- name={menu.items[index].icon}
138
- />
139
- )}
140
- {label}
141
- {index === currentMenuIndex && (
142
- <Icon
143
- class="ml-auto text-neutral-900 [&_path]:stroke-3"
144
- name="lucide:check"
145
- stroke-width={10}
146
- />
147
- )}
148
- </a>
149
- </li>
150
- );
151
- })
152
- }
153
- </ul>
127
+ <a
128
+ class="flex items-center px-3 py-2 cursor-pointer text-sm text-neutral-700 dark:text-neutral-300 relative z-0 before:-z-10 before:absolute before:inset-x-1 before:inset-y-px before:rounded-md before:duration-150"
129
+ class:list={[
130
+ index === currentMenuIndex
131
+ ? "before:bg-neutral-200/50 dark:before:bg-neutral-700/50 text-neutral-900 dark:text-white"
132
+ : "hover:before:bg-neutral-100/70 dark:hover:before:bg-neutral-700/30",
133
+ ]}
134
+ href={firstHrefOfMenuItems[index] ?? "/"}
135
+ >
136
+ {menu.items[index].icon && (
137
+ <Icon
138
+ class="mr-2 size-4 opacity-75"
139
+ name={menu.items[index].icon}
140
+ />
141
+ )}
142
+ {label}
143
+ {index === currentMenuIndex && (
144
+ <Icon
145
+ class="ml-auto text-primary [&_path]:stroke-3"
146
+ name="lucide:check"
147
+ stroke-width={10}
148
+ />
149
+ )}
150
+ </a>
151
+ </li>
152
+ );
153
+ })
154
+ }
155
+ </ul>
156
+ </div>
154
157
  </div>
155
158
  </div>
156
159
  <div
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import type { NavGroup } from "../lib/validation";
3
3
  import SidebarLink from "./SidebarLink.astro";
4
+ import SidebarOpenApiPageLink from "./sidebar/SidebarOpenApiPageLink.astro";
4
5
  import SidebarSubgroup from "./SidebarSubgroup.astro";
5
6
  import { slugify } from "../lib/utils";
6
7
  import Tag from "./ui/Tag.astro";
@@ -38,6 +39,8 @@ const currentPrefix = parentSlug ? `${parentSlug}/${groupSlug}` : groupSlug;
38
39
  title={child.title}
39
40
  groupSlug={currentPrefix}
40
41
  />
42
+ ) : "openapi" in child ? (
43
+ <SidebarOpenApiPageLink item={child} parentSlug={currentPrefix} />
41
44
  ) : (
42
45
  <SidebarSubgroup item={child} parentSlug={currentPrefix} />
43
46
  )}
@@ -1,6 +1,12 @@
1
1
  ---
2
- import type { NavGroup, NavigationItem, NavPage } from "../lib/validation";
2
+ import type {
3
+ NavGroup,
4
+ NavigationItem,
5
+ NavOpenApiPage,
6
+ NavPage,
7
+ } from "../lib/validation";
3
8
  import SidebarOpenApi from "./sidebar/SidebarOpenApi.astro";
9
+ import SidebarOpenApiPageLink from "./sidebar/SidebarOpenApiPageLink.astro";
4
10
  import SidebarDropdown from "./SidebarDropdown.astro";
5
11
  import SidebarSegmented from "./SidebarSegmented.astro";
6
12
  import SidebarGroup from "./SidebarGroup.astro";
@@ -24,6 +30,13 @@ let { navigation, parentSlug = "" } = Astro.props;
24
30
  </li>
25
31
  ) : "group" in item ? (
26
32
  <SidebarGroup item={item as NavGroup} parentSlug={parentSlug} />
33
+ ) : "openapi" in item ? (
34
+ <li>
35
+ <SidebarOpenApiPageLink
36
+ item={item as NavOpenApiPage}
37
+ parentSlug={parentSlug}
38
+ />
39
+ </li>
27
40
  ) : (
28
41
  <li>
29
42
  <SidebarLink
@@ -65,22 +65,22 @@ const currentPrefix = parentSlug
65
65
  : menuItemSlug;
66
66
  ---
67
67
 
68
- <div class="mt-3 mx-2">
68
+ <div class="mt-3 mx-2 z-10 relative dark:bg-neutral-900 rounded-lg">
69
69
  {
70
70
  menu.label && (
71
71
  <label class="font-semibold text-xs px-2 pb-1 block">{menu.label}</label>
72
72
  )
73
73
  }
74
- <ul class="rounded-lg bg-neutral-100 p-[3px]">
74
+ <ul class="rounded-lg bg-neutral-100 dark:bg-neutral-800/60 p-[3px]">
75
75
  {
76
76
  menu.items.map((item, index) => (
77
77
  <li>
78
78
  <a
79
- class="flex items-center px-3 py-1.5 cursor-pointer text-sm rounded-md border-x border-t"
79
+ class="flex items-center px-3 py-1.5 cursor-pointer text-sm rounded-md border-x border-t transition-colors duration-150"
80
80
  class:list={[
81
81
  index === currentMenuIndex
82
- ? "bg-white border-neutral-200/70 shadow-sm shadow-neutral-200 text-neutral-900"
83
- : "text-neutral-600 border-transparent",
82
+ ? "bg-white dark:bg-neutral-700/30 border-neutral-200/70 dark:border-neutral-700/40 dark:border-b shadow-sm shadow-neutral-200/80 dark:shadow-neutral-950/20 text-neutral-900 dark:text-white"
83
+ : "text-neutral-600 dark:text-neutral-300 border-transparent hover:text-primary dark:hover:text-primary",
84
84
  ]}
85
85
  href={firstHrefOfMenuItems[index] ?? "/"}
86
86
  >
@@ -1,7 +1,13 @@
1
1
  ---
2
2
  import { getConfig, type NavGroup } from "../lib/validation";
3
3
  import SidebarLink from "./SidebarLink.astro";
4
- import { buildMdxPageHref, slugify } from "../lib/utils";
4
+ import SidebarOpenApiPageLink from "./sidebar/SidebarOpenApiPageLink.astro";
5
+ import {
6
+ buildMdxPageHref,
7
+ buildOpenApiEndpointHref,
8
+ parseOpenApiEndpoint,
9
+ slugify,
10
+ } from "../lib/utils";
5
11
  import Tag from "./ui/Tag.astro";
6
12
  import Icon from "./ui/Icon.astro";
7
13
 
@@ -21,22 +27,37 @@ const listId = `list-${groupId}`;
21
27
 
22
28
  // Check if any child page is active (shallow check, no recursion needed)
23
29
  const containsActivePage = item.pages.some((child) => {
24
- const pagePath =
25
- typeof child === "string" ? child : "pages" in child ? null : child.page;
30
+ let href: string | null = null;
26
31
 
27
- if (pagePath) {
28
- const href = buildMdxPageHref({
29
- filePath: pagePath,
32
+ if (typeof child === "string") {
33
+ href = buildMdxPageHref({
34
+ filePath: child,
30
35
  groupSlug: currentPrefix,
31
36
  homePath: config.home,
32
37
  });
33
-
34
- // Normalize paths for comparison (remove trailing slashes)
35
- const normalizedCurrent = Astro.url.pathname.replace(/\/$/, "");
36
- const normalizedTarget = href.replace(/\/$/, "");
37
- return normalizedCurrent === normalizedTarget;
38
+ } else if ("page" in child) {
39
+ href = buildMdxPageHref({
40
+ filePath: child.page,
41
+ groupSlug: currentPrefix,
42
+ homePath: config.home,
43
+ });
44
+ } else if ("openapi" in child) {
45
+ const parsedEndpoint = parseOpenApiEndpoint(child.openapi.endpoint);
46
+ if (parsedEndpoint) {
47
+ href = buildOpenApiEndpointHref({
48
+ path: parsedEndpoint.path,
49
+ method: parsedEndpoint.method,
50
+ groupSlug: currentPrefix,
51
+ });
52
+ }
38
53
  }
39
- return false;
54
+
55
+ if (!href) return false;
56
+
57
+ // Normalize paths for comparison (remove trailing slashes)
58
+ const normalizedCurrent = Astro.url.pathname.replace(/\/$/, "");
59
+ const normalizedTarget = href.replace(/\/$/, "");
60
+ return normalizedCurrent === normalizedTarget;
40
61
  });
41
62
  ---
42
63
 
@@ -100,6 +121,8 @@ const containsActivePage = item.pages.some((child) => {
100
121
  title={child.title}
101
122
  groupSlug={currentPrefix}
102
123
  />
124
+ ) : "openapi" in child ? (
125
+ <SidebarOpenApiPageLink item={child} parentSlug={currentPrefix} />
103
126
  ) : null}
104
127
  </li>
105
128
  ))
@@ -33,15 +33,15 @@ for (const heading of headings) {
33
33
  aria-label="Table of Contents"
34
34
  data-slugs={JSON.stringify(headings.map((h) => h.slug))}
35
35
  >
36
- <div class="text-sm font-medium mb-3">On this page</div>
37
- <div class="flex text-neutral-600/90">
36
+ <div class="text-sm font-medium mb-3 text-neutral-900 dark:text-neutral-100">On this page</div>
37
+ <div class="flex text-neutral-600/90 dark:text-neutral-300/90">
38
38
  <svg viewBox="0 0 22 100" width="22px" height="100" class="shrink-0">
39
39
  <path
40
40
  id="toc-bg-path"
41
41
  d=""
42
42
  stroke-width="1.2"
43
43
  vector-effect="non-scaling-stroke"
44
- class="stroke-neutral-200"
44
+ class="stroke-neutral-200 dark:stroke-neutral-700/70"
45
45
  fill="none"
46
46
  stroke-linecap="round"
47
47
  stroke-linejoin="round"></path>
@@ -50,7 +50,7 @@ for (const heading of headings) {
50
50
  d=""
51
51
  stroke-width="1.4"
52
52
  vector-effect="non-scaling-stroke"
53
- class="stroke-blue-700/60 transition-[stroke-dasharray] duration-200"
53
+ class="stroke-neutral-700/70 dark:stroke-neutral-300/80 transition-[stroke-dasharray] duration-200"
54
54
  fill="none"
55
55
  stroke-linecap="round"
56
56
  stroke-linejoin="round"></path>
@@ -63,7 +63,7 @@ for (const heading of headings) {
63
63
  {heading.text && (
64
64
  <a
65
65
  href={`#${heading.slug}`}
66
- class="block transition duration-200 hover:text-neutral-900"
66
+ class="block transition duration-200 hover:text-neutral-900 dark:hover:text-neutral-100"
67
67
  data-slug={heading.slug}
68
68
  >
69
69
  {heading.text}
@@ -75,7 +75,7 @@ for (const heading of headings) {
75
75
  <li class:list={["", heading.text ? "mt-3" : "mt-0"]}>
76
76
  <a
77
77
  href={`#${subHeading.slug}`}
78
- class="block transition duration-200 hover:text-neutral-900"
78
+ class="block transition duration-200 hover:text-neutral-900 dark:hover:text-neutral-100"
79
79
  data-slug={subHeading.slug}
80
80
  >
81
81
  {subHeading.text}
@@ -239,10 +239,11 @@ for (const heading of headings) {
239
239
  );
240
240
  circle.setAttribute("cx", String(item.x));
241
241
  circle.setAttribute("cy", String(midY));
242
- circle.setAttribute("r", "2.5");
242
+ circle.setAttribute("r", "2.25");
243
243
  circle.setAttribute("fill", "currentColor");
244
244
  circle.classList.add(
245
245
  "fill-neutral-300",
246
+ "dark:fill-neutral-600",
246
247
  "transition-all",
247
248
  "duration-200"
248
249
  );
@@ -328,13 +329,21 @@ for (const heading of headings) {
328
329
  circles?.forEach((circle) => {
329
330
  const slug = (circle as SVGCircleElement).dataset.slug;
330
331
  if (slug && visibleSlugs.has(slug)) {
331
- circle.classList.remove("fill-neutral-300");
332
- circle.classList.add("fill-blue-700", "delay-100");
333
- circle.setAttribute("r", "3");
332
+ circle.classList.remove("fill-neutral-300", "dark:fill-neutral-600");
333
+ circle.classList.add(
334
+ "fill-neutral-700",
335
+ "dark:fill-neutral-200",
336
+ "delay-100"
337
+ );
338
+ circle.setAttribute("r", "2.75");
334
339
  } else {
335
- circle.classList.remove("fill-blue-700", "delay-100");
336
- circle.classList.add("fill-neutral-300");
337
- circle.setAttribute("r", "2.5");
340
+ circle.classList.remove(
341
+ "fill-neutral-700",
342
+ "dark:fill-neutral-200",
343
+ "delay-100"
344
+ );
345
+ circle.classList.add("fill-neutral-300", "dark:fill-neutral-600");
346
+ circle.setAttribute("r", "2.25");
338
347
  }
339
348
  });
340
349
 
@@ -343,9 +352,9 @@ for (const heading of headings) {
343
352
  tocLinks?.forEach((link) => {
344
353
  const slug = (link as HTMLAnchorElement).dataset.slug;
345
354
  if (slug && visibleSlugs.has(slug)) {
346
- link.classList.add("text-neutral-900");
355
+ link.classList.add("text-neutral-900", "dark:text-neutral-100");
347
356
  } else {
348
- link.classList.remove("text-neutral-900");
357
+ link.classList.remove("text-neutral-900", "dark:text-neutral-100");
349
358
  }
350
359
  });
351
360
  }
@@ -9,6 +9,7 @@ import { Icon } from "astro-icon/components";
9
9
  return mode === 'light' || mode === 'dark' ? mode : null;
10
10
  })(),
11
11
  theme: localStorage.getItem('theme') || 'system',
12
+ themeSwitchFrameId: null,
12
13
  init() {
13
14
  if (this.forcedTheme) {
14
15
  this.theme = this.forcedTheme;
@@ -43,16 +44,22 @@ import { Icon } from "astro-icon/components";
43
44
  });
44
45
  },
45
46
  updateDOM() {
46
- document.documentElement.classList.add('is-switching-theme')
47
+ const root = document.documentElement;
48
+ root.classList.add('is-switching-theme');
47
49
  const activeTheme = this.forcedTheme || this.theme;
48
50
  const isDark = activeTheme === 'dark' ||
49
51
  (activeTheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
50
52
 
51
- document.documentElement.classList.toggle('dark', isDark);
52
- document.documentElement.dataset.theme = isDark ? 'dark' : 'light';
53
- setTimeout(() => {
54
- document.documentElement.classList.remove('is-switching-theme');
55
- }, 200);
53
+ root.classList.toggle('dark', isDark);
54
+ root.dataset.theme = isDark ? 'dark' : 'light';
55
+
56
+ if (this.themeSwitchFrameId !== null) {
57
+ cancelAnimationFrame(this.themeSwitchFrameId);
58
+ }
59
+ this.themeSwitchFrameId = requestAnimationFrame(() => {
60
+ root.classList.remove('is-switching-theme');
61
+ this.themeSwitchFrameId = null;
62
+ });
56
63
  },
57
64
  markerStyle: { left: null, width: null },
58
65
  updateMarker() {
@@ -67,10 +74,10 @@ import { Icon } from "astro-icon/components";
67
74
  }
68
75
  }
69
76
  }"
70
- class="relative flex gap-[3px] p-px bg-neutral-100 dark:bg-neutral-800 rounded-full w-fit inset-shadow-sm inset-shadow-neutral-100/80 text-neutral-500"
77
+ class="relative flex gap-[3px] p-px bg-background-dark dark:bg-neutral-800/40 rounded-full w-fit text-neutral-500"
71
78
  >
72
79
  <div
73
- class="anchor-pill absolute top-px bottom-px bg-white dark:bg-neutral-700 rounded-full shadow-xs border-[0.5px] border-neutral-200/80 ease-out z-0 flex items-center justify-center animate-[scaleIn_0.5s_ease-out]"
80
+ class="anchor-pill absolute top-px dark:top-0.5 bottom-px dark:bottom-0.5 bg-white dark:bg-neutral-800 rounded-full shadow-xs border-[0.5px] border-neutral-200/80 dark:border-neutral-700/50 ease-out z-0 flex items-center justify-center animate-[scaleIn_0.5s_ease-out]"
74
81
  style="left: 3px;"
75
82
  :style="markerStyle.width ? `left: ${markerStyle.left}; width: ${markerStyle.width}` : ''"
76
83
  >