radiant-docs 0.1.7 → 0.1.8

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 (78) hide show
  1. package/dist/index.js +28 -5
  2. package/package.json +3 -3
  3. package/template/astro.config.mjs +76 -3
  4. package/template/package-lock.json +924 -737
  5. package/template/package.json +7 -5
  6. package/template/scripts/generate-og-images.mjs +335 -0
  7. package/template/scripts/generate-og-metadata.mjs +173 -0
  8. package/template/scripts/rewrite-static-asset-host.mjs +408 -0
  9. package/template/scripts/stamp-image-versions.mjs +277 -0
  10. package/template/scripts/stamp-og-image-versions.mjs +199 -0
  11. package/template/scripts/stamp-pagefind-runtime-version.mjs +140 -0
  12. package/template/src/assets/fonts/geist-mono/cyrillic.woff2 +0 -0
  13. package/template/src/assets/fonts/geist-mono/latin-ext.woff2 +0 -0
  14. package/template/src/assets/fonts/geist-mono/latin.woff2 +0 -0
  15. package/template/src/assets/fonts/google-sans-flex/canadian-aboriginal.woff2 +0 -0
  16. package/template/src/assets/fonts/google-sans-flex/cherokee.woff2 +0 -0
  17. package/template/src/assets/fonts/google-sans-flex/latin-ext.woff2 +0 -0
  18. package/template/src/assets/fonts/google-sans-flex/latin.woff2 +0 -0
  19. package/template/src/assets/fonts/google-sans-flex/math.woff2 +0 -0
  20. package/template/src/assets/fonts/google-sans-flex/nushu.woff2 +0 -0
  21. package/template/src/assets/fonts/google-sans-flex/symbols.woff2 +0 -0
  22. package/template/src/assets/fonts/google-sans-flex/syriac.woff2 +0 -0
  23. package/template/src/assets/fonts/google-sans-flex/tifinagh.woff2 +0 -0
  24. package/template/src/assets/fonts/google-sans-flex/vietnamese.woff2 +0 -0
  25. package/template/src/components/Footer.astro +94 -0
  26. package/template/src/components/Header.astro +11 -66
  27. package/template/src/components/LogoLink.astro +103 -0
  28. package/template/src/components/MdxPage.astro +126 -11
  29. package/template/src/components/OpenApiPage.astro +1036 -69
  30. package/template/src/components/Search.astro +0 -2
  31. package/template/src/components/SidebarDropdown.astro +34 -14
  32. package/template/src/components/SidebarGroup.astro +3 -6
  33. package/template/src/components/SidebarLink.astro +22 -12
  34. package/template/src/components/SidebarMenu.astro +19 -16
  35. package/template/src/components/SidebarSegmented.astro +99 -0
  36. package/template/src/components/SidebarSubgroup.astro +12 -12
  37. package/template/src/components/ThemeSwitcher.astro +30 -7
  38. package/template/src/components/endpoint/PlaygroundBar.astro +32 -36
  39. package/template/src/components/endpoint/PlaygroundButton.astro +40 -4
  40. package/template/src/components/endpoint/PlaygroundField.astro +1068 -22
  41. package/template/src/components/endpoint/PlaygroundForm.astro +559 -61
  42. package/template/src/components/endpoint/RequestSnippets.astro +342 -193
  43. package/template/src/components/endpoint/ResponseDisplay.astro +161 -147
  44. package/template/src/components/endpoint/ResponseFieldTree.astro +134 -0
  45. package/template/src/components/endpoint/ResponseFields.astro +711 -68
  46. package/template/src/components/endpoint/ResponseSnippets.astro +299 -173
  47. package/template/src/components/sidebar/SidebarEndpointLink.astro +1 -1
  48. package/template/src/components/ui/CodeLanguageIcon.astro +19 -0
  49. package/template/src/components/ui/CodeTabEdge.astro +79 -0
  50. package/template/src/components/ui/Field.astro +103 -20
  51. package/template/src/components/ui/Icon.astro +32 -0
  52. package/template/src/components/ui/ListChevronsToggle.astro +31 -0
  53. package/template/src/components/ui/Tag.astro +1 -1
  54. package/template/src/components/user/{Accordian.astro → Accordion.astro} +6 -6
  55. package/template/src/components/user/Callout.astro +5 -9
  56. package/template/src/components/user/CodeBlock.astro +400 -0
  57. package/template/src/components/user/CodeGroup.astro +225 -0
  58. package/template/src/components/user/ComponentPreview.astro +1 -0
  59. package/template/src/components/user/ComponentPreviewBlock.astro +181 -0
  60. package/template/src/components/user/Image.astro +132 -0
  61. package/template/src/components/user/Steps.astro +1 -3
  62. package/template/src/components/user/Tabs.astro +2 -2
  63. package/template/src/content.config.ts +1 -0
  64. package/template/src/layouts/Layout.astro +109 -8
  65. package/template/src/lib/code/code-block.ts +546 -0
  66. package/template/src/lib/frontmatter-schema.ts +8 -7
  67. package/template/src/lib/mdx/remark-code-block-component.ts +342 -0
  68. package/template/src/lib/mdx/remark-demote-h1.ts +16 -0
  69. package/template/src/lib/pagefind.ts +19 -5
  70. package/template/src/lib/routes.ts +49 -31
  71. package/template/src/lib/utils.ts +20 -0
  72. package/template/src/lib/validation.ts +638 -200
  73. package/template/src/pages/[...slug].astro +18 -5
  74. package/template/src/styles/geist-mono.css +33 -0
  75. package/template/src/styles/global.css +89 -84
  76. package/template/src/styles/google-sans-flex.css +143 -0
  77. package/template/ec.config.mjs +0 -51
  78. /package/template/src/components/user/{AccordianGroup.astro → AccordionGroup.astro} +0 -0
@@ -207,8 +207,6 @@ import { Icon } from "astro-icon/components";
207
207
  close() {
208
208
  this.isOpen = false;
209
209
  document.body.style.overflow = "";
210
- this.query = "";
211
- this.results = [];
212
210
  this.selectedIndex = -1;
213
211
  },
214
212
 
@@ -1,6 +1,6 @@
1
1
  ---
2
- import { Icon } from "astro-icon/components";
3
- import type { NavMenu } from "../lib/validation";
2
+ import Icon from "./ui/Icon.astro";
3
+ import { getConfig, type NavMenu } from "../lib/validation";
4
4
  import SidebarMenu from "./SidebarMenu.astro";
5
5
  import { slugify } from "../lib/utils";
6
6
  import { getAllRoutes } from "../lib/routes";
@@ -13,13 +13,30 @@ interface Props {
13
13
  const { menu, parentSlug = "" } = Astro.props;
14
14
 
15
15
  const pathname = Astro.url.pathname;
16
+ const config = await getConfig();
17
+ const routes = await getAllRoutes();
16
18
 
17
19
  let currentMenuIndex = menu.items.findIndex(
18
- (i) => slugify(i.label) === pathname.split("/")[1]
20
+ (i) => slugify(i.label) === pathname.split("/")[1],
19
21
  );
20
- currentMenuIndex = currentMenuIndex === -1 ? 0 : currentMenuIndex;
21
22
 
22
- const routes = await getAllRoutes();
23
+ if (pathname === "/" && config.home) {
24
+ const homeRoute = routes.find(
25
+ (route) => route.type === "mdx" && route.filePath === config.home,
26
+ );
27
+
28
+ if (homeRoute) {
29
+ const homeTopLevelSegment = homeRoute.slug.split("/")[0];
30
+ const homeMenuIndex = menu.items.findIndex(
31
+ (item) => slugify(item.label) === homeTopLevelSegment,
32
+ );
33
+ if (homeMenuIndex !== -1) {
34
+ currentMenuIndex = homeMenuIndex;
35
+ }
36
+ }
37
+ }
38
+
39
+ currentMenuIndex = currentMenuIndex === -1 ? 0 : currentMenuIndex;
23
40
 
24
41
  let firstHrefOfMenuItems: string[] = [];
25
42
 
@@ -33,7 +50,11 @@ for (const route of routes) {
33
50
 
34
51
  if (!seen.has(topLevel)) {
35
52
  seen.add(topLevel);
36
- firstHrefOfMenuItems.push(slug);
53
+ const href =
54
+ route.type === "mdx" && config.home && route.filePath === config.home
55
+ ? "/"
56
+ : `/${slug}`;
57
+ firstHrefOfMenuItems.push(href);
37
58
  }
38
59
  }
39
60
 
@@ -51,8 +72,7 @@ const currentPrefix = parentSlug
51
72
  <div
52
73
  class:list={[
53
74
  "mt-3 mx-2",
54
- menu.label &&
55
- "rounded-lg bg-neutral-100/80 px-0.5 pb-0.5 inset-shadow-xs inset-shadow-neutral-700/5 border border-neutral-200/60",
75
+ menu.label && "rounded-lg bg-neutral-100 p-[3px]",
56
76
  ]}
57
77
  >
58
78
  {
@@ -64,7 +84,7 @@ const currentPrefix = parentSlug
64
84
  }
65
85
  <div class="relative">
66
86
  <button
67
- class="flex items-center w-full text-sm bg-white border border-neutral-200 rounded-lg shadow-xs px-3 py-2 cursor-pointer"
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"
68
88
  x-on:click="open = true"
69
89
  aria-haspopup="menu"
70
90
  aria-expanded
@@ -72,8 +92,8 @@ const currentPrefix = parentSlug
72
92
  {
73
93
  menu.items[currentMenuIndex].icon && (
74
94
  <Icon
75
- class="mr-2 size-4"
76
- name={`lucide:${menu.items[currentMenuIndex].icon}`}
95
+ class="mr-2 size-4 opacity-80"
96
+ name={menu.items[currentMenuIndex].icon}
77
97
  />
78
98
  )
79
99
  }
@@ -109,12 +129,12 @@ const currentPrefix = parentSlug
109
129
  ? "before:bg-neutral-200/50 text-neutral-900"
110
130
  : "hover:before:bg-neutral-100/70",
111
131
  ]}
112
- href={`/${firstHrefOfMenuItems[index]}`}
132
+ href={firstHrefOfMenuItems[index] ?? "/"}
113
133
  >
114
134
  {menu.items[index].icon && (
115
135
  <Icon
116
- class="mr-2 size-4"
117
- name={`lucide:${menu.items[index].icon}`}
136
+ class="mr-2 size-4 opacity-75"
137
+ name={menu.items[index].icon}
118
138
  />
119
139
  )}
120
140
  {label}
@@ -4,7 +4,7 @@ import SidebarLink from "./SidebarLink.astro";
4
4
  import SidebarSubgroup from "./SidebarSubgroup.astro";
5
5
  import { slugify } from "../lib/utils";
6
6
  import Tag from "./ui/Tag.astro";
7
- import { Icon } from "astro-icon/components";
7
+ import Icon from "./ui/Icon.astro";
8
8
 
9
9
  interface Props {
10
10
  item: NavGroup;
@@ -19,11 +19,7 @@ const currentPrefix = parentSlug ? `${parentSlug}/${groupSlug}` : groupSlug;
19
19
 
20
20
  <li>
21
21
  <div class:list={["text-sm font-semibold mb-2 flex items-center gap-2 px-2"]}>
22
- {
23
- item.icon && (
24
- <Icon name={`lucide:${item.icon}`} class="size-4 text-neutral-500" />
25
- )
26
- }
22
+ {item.icon && <Icon name={item.icon} class="size-4 text-neutral-500" />}
27
23
  {item.group}
28
24
  {item.tag && <Tag>{item.tag}</Tag>}
29
25
  </div>
@@ -39,6 +35,7 @@ const currentPrefix = parentSlug ? `${parentSlug}/${groupSlug}` : groupSlug;
39
35
  path={child.page}
40
36
  icon={child.icon}
41
37
  tag={child.tag}
38
+ title={child.title}
42
39
  groupSlug={currentPrefix}
43
40
  />
44
41
  ) : (
@@ -1,19 +1,28 @@
1
1
  ---
2
- import { deriveTitleFromEntryId, slugify } from "../lib/utils";
3
- import { Icon } from "astro-icon/components";
2
+ import { buildMdxPageHref, deriveTitleFromEntryId } from "../lib/utils";
3
+ import Icon from "./ui/Icon.astro";
4
4
  import Tag from "./ui/Tag.astro";
5
5
  import { getCollection } from "astro:content";
6
+ import { getConfig } from "../lib/validation";
6
7
 
7
8
  interface Props {
8
9
  path: string;
9
10
  groupSlug?: string;
10
- icon?: string;
11
+ icon?: string | null;
11
12
  tag?: string;
13
+ title?: string;
12
14
  }
13
15
 
14
- const { path: filePath, groupSlug = "", icon, tag } = Astro.props;
16
+ const {
17
+ path: filePath,
18
+ groupSlug = "",
19
+ icon,
20
+ tag,
21
+ title: configTitle,
22
+ } = Astro.props;
15
23
 
16
24
  const docs = await getCollection("docs");
25
+ const config = await getConfig();
17
26
 
18
27
  // Find the entry matching this path
19
28
  const entry = docs.find((doc: any) => {
@@ -25,13 +34,14 @@ if (!entry) {
25
34
  throw new Error("Entry not found");
26
35
  }
27
36
 
28
- // Generate a nice label from the filename (e.g. "writing-content/page" -> "Page")
29
- const filename = filePath ? filePath.split("/").pop() || filePath : "";
30
- const text = entry.data.title || deriveTitleFromEntryId(entry.id);
37
+ // Use title from docs.json config if provided, otherwise derive from entry id
38
+ const text = configTitle || deriveTitleFromEntryId(entry.id);
31
39
 
32
- const pageSlug = slugify(filename);
33
- // If groupSlug exists, join it. Otherwise just use pageSlug (though practically groupSlug should exist from hierarchy)
34
- const href = groupSlug ? `/${groupSlug}/${pageSlug}` : `/${pageSlug}`;
40
+ const href = buildMdxPageHref({
41
+ filePath,
42
+ groupSlug,
43
+ homePath: config.home,
44
+ });
35
45
 
36
46
  // Normalize paths for comparison (remove trailing slashes)
37
47
  const currentPath = Astro.url.pathname.replace(/\/$/, "");
@@ -42,14 +52,14 @@ const isActive = currentPath === targetPath;
42
52
  <a
43
53
  href={href}
44
54
  class:list={[
45
- "block px-2 py-[7px] text-sm relative z-0 font-[450] before:-z-10 before:absolute before:inset-x-0 before:inset-y-px before:rounded-md before:duration-150",
55
+ "block px-2 py-[7px] text-sm relative z-0 before:-z-10 before:absolute before:inset-x-0 before:inset-y-px before:rounded-md before:duration-150",
46
56
  isActive
47
57
  ? "before:bg-neutral-200/50 dark:before:bg-neutral-800 text-neutral-900 dark:text-neutral-200"
48
58
  : "text-neutral-600 dark:text-neutral-400 hover:before:bg-neutral-100/70 dark:hover:before:bg-neutral-800/50 hover:text-neutral-900 dark:hover:text-neutral-300",
49
59
  ]}
50
60
  >
51
61
  <div class="flex items-center gap-2">
52
- {icon && <Icon name={`lucide:${icon}`} class="size-4 opacity-75" />}
62
+ {icon && <Icon name={icon} class="size-4 opacity-75" />}
53
63
  {text}
54
64
  {tag && <Tag>{tag}</Tag>}
55
65
  </div>
@@ -2,6 +2,7 @@
2
2
  import type { NavGroup, NavigationItem, NavPage } from "../lib/validation";
3
3
  import SidebarOpenApi from "./sidebar/SidebarOpenApi.astro";
4
4
  import SidebarDropdown from "./SidebarDropdown.astro";
5
+ import SidebarSegmented from "./SidebarSegmented.astro";
5
6
  import SidebarGroup from "./SidebarGroup.astro";
6
7
  import SidebarLink from "./SidebarLink.astro";
7
8
 
@@ -15,31 +16,33 @@ let { navigation, parentSlug = "" } = Astro.props;
15
16
 
16
17
  {
17
18
  navigation.pages ? (
18
- <ul class="px-2 pt-4 pb-3">
19
- {navigation.pages.map((item) => (
20
- <li>
21
- {typeof item === "string" ? (
22
- <SidebarLink path={item} />
23
- ) : (
19
+ <ul class="px-2 pt-4 pb-3 [&>:nth-child(n+2)]:mt-6 [&>:nth-child(n+2)]:pt-[25px] [&>:nth-child(n+2)]:relative [&>:nth-child(n+2)]:before:absolute [&>:nth-child(n+2)]:before:top-0 [&>:nth-child(n+2)]:before:inset-x-0 [&>:nth-child(n+2)]:before:h-px [&>:nth-child(n+2)]:before:bg-linear-[90deg,transparent,var(--color-neutral-200)_20%,var(--color-neutral-200)_80%,transparent] dark:[&>:nth-child(n+2)]:before:bg-linear-[90deg,transparent,var(--color-neutral-700)_20%,var(--color-neutral-700)_80%,transparent]">
20
+ {navigation.pages.map((item) =>
21
+ typeof item === "string" ? (
22
+ <li>
23
+ <SidebarLink path={item} groupSlug={parentSlug} />
24
+ </li>
25
+ ) : "group" in item ? (
26
+ <SidebarGroup item={item as NavGroup} parentSlug={parentSlug} />
27
+ ) : (
28
+ <li>
24
29
  <SidebarLink
25
30
  path={(item as NavPage).page}
26
31
  icon={(item as NavPage).icon}
27
32
  tag={(item as NavPage).tag}
33
+ title={(item as NavPage).title}
34
+ groupSlug={parentSlug}
28
35
  />
29
- )}
30
- </li>
31
- ))}
32
- </ul>
33
- ) : navigation.groups ? (
34
- <ul class="px-2 pt-4 pb-3 [&>:nth-child(n+2)]:mt-5 [&>:nth-child(n+2)]:pt-[25px] [&>:nth-child(n+2)]:relative [&>:nth-child(n+2)]:before:absolute [&>:nth-child(n+2)]:before:top-0 [&>:nth-child(n+2)]:before:inset-x-0 [&>:nth-child(n+2)]:before:h-px [&>:nth-child(n+2)]:before:bg-linear-[90deg,transparent,var(--color-neutral-200)_20%,var(--color-neutral-200)_80%,transparent] dark:[&>:nth-child(n+2)]:before:bg-linear-[90deg,transparent,var(--color-neutral-700)_20%,var(--color-neutral-700)_80%,transparent]">
35
- {navigation.groups.map((item) => (
36
- <SidebarGroup item={item as NavGroup} parentSlug={parentSlug} />
37
- ))}
36
+ </li>
37
+ ),
38
+ )}
38
39
  </ul>
39
40
  ) : navigation.menu ? (
40
41
  !navigation.menu.type || navigation.menu.type === "dropdown" ? (
41
42
  <SidebarDropdown menu={navigation.menu} parentSlug={parentSlug} />
42
- ) : navigation.menu.type === "collapsible" ? null : null
43
+ ) : navigation.menu.type === "segmented" ? (
44
+ <SidebarSegmented menu={navigation.menu} parentSlug={parentSlug} />
45
+ ) : null
43
46
  ) : navigation.openapi ? (
44
47
  <SidebarOpenApi openapi={navigation.openapi} parentSlug={parentSlug} />
45
48
  ) : null
@@ -0,0 +1,99 @@
1
+ ---
2
+ import Icon from "./ui/Icon.astro";
3
+ import { getConfig, type NavMenu } from "../lib/validation";
4
+ import SidebarMenu from "./SidebarMenu.astro";
5
+ import { slugify } from "../lib/utils";
6
+ import { getAllRoutes } from "../lib/routes";
7
+
8
+ interface Props {
9
+ menu: NavMenu;
10
+ parentSlug?: string;
11
+ }
12
+
13
+ const { menu, parentSlug = "" } = Astro.props;
14
+
15
+ const pathname = Astro.url.pathname;
16
+ const config = await getConfig();
17
+ const routes = await getAllRoutes();
18
+
19
+ let currentMenuIndex = menu.items.findIndex(
20
+ (i) => slugify(i.label) === pathname.split("/")[1],
21
+ );
22
+
23
+ if (pathname === "/" && config.home) {
24
+ const homeRoute = routes.find(
25
+ (route) => route.type === "mdx" && route.filePath === config.home,
26
+ );
27
+
28
+ if (homeRoute) {
29
+ const homeTopLevelSegment = homeRoute.slug.split("/")[0];
30
+ const homeMenuIndex = menu.items.findIndex(
31
+ (item) => slugify(item.label) === homeTopLevelSegment,
32
+ );
33
+ if (homeMenuIndex !== -1) {
34
+ currentMenuIndex = homeMenuIndex;
35
+ }
36
+ }
37
+ }
38
+
39
+ currentMenuIndex = currentMenuIndex === -1 ? 0 : currentMenuIndex;
40
+
41
+ let firstHrefOfMenuItems: string[] = [];
42
+
43
+ const seen = new Set();
44
+
45
+ for (const route of routes) {
46
+ const slug = route.slug;
47
+
48
+ const parts = slug.split("/");
49
+ const topLevel = parts[0];
50
+
51
+ if (!seen.has(topLevel)) {
52
+ seen.add(topLevel);
53
+ const href =
54
+ route.type === "mdx" && config.home && route.filePath === config.home
55
+ ? "/"
56
+ : `/${slug}`;
57
+ firstHrefOfMenuItems.push(href);
58
+ }
59
+ }
60
+
61
+ const selectedMenuItem = menu.items[currentMenuIndex];
62
+ const menuItemSlug = slugify(selectedMenuItem.label);
63
+ const currentPrefix = parentSlug
64
+ ? `${parentSlug}/${menuItemSlug}`
65
+ : menuItemSlug;
66
+ ---
67
+
68
+ <div class="mt-3 mx-2">
69
+ {
70
+ menu.label && (
71
+ <label class="font-semibold text-xs px-2 pb-1 block">{menu.label}</label>
72
+ )
73
+ }
74
+ <ul class="rounded-lg bg-neutral-100 p-[3px]">
75
+ {
76
+ menu.items.map((item, index) => (
77
+ <li>
78
+ <a
79
+ class="flex items-center px-3 py-1.5 cursor-pointer text-sm rounded-md border-x border-t"
80
+ class:list={[
81
+ index === currentMenuIndex
82
+ ? "bg-white border-neutral-200/70 shadow-sm shadow-neutral-200 text-neutral-900"
83
+ : "text-neutral-600 border-transparent",
84
+ ]}
85
+ href={firstHrefOfMenuItems[index] ?? "/"}
86
+ >
87
+ {item.icon && <Icon class="mr-2 size-4" name={item.icon} />}
88
+ {item.label}
89
+ </a>
90
+ </li>
91
+ ))
92
+ }
93
+ </ul>
94
+ </div>
95
+
96
+ <SidebarMenu
97
+ navigation={menu.items[currentMenuIndex].submenu}
98
+ parentSlug={currentPrefix}
99
+ />
@@ -1,9 +1,9 @@
1
1
  ---
2
- import type { NavGroup } from "../lib/validation";
2
+ import { getConfig, type NavGroup } from "../lib/validation";
3
3
  import SidebarLink from "./SidebarLink.astro";
4
- import { slugify } from "../lib/utils";
4
+ import { buildMdxPageHref, slugify } from "../lib/utils";
5
5
  import Tag from "./ui/Tag.astro";
6
- import { Icon } from "astro-icon/components";
6
+ import Icon from "./ui/Icon.astro";
7
7
 
8
8
  interface Props {
9
9
  item: NavGroup;
@@ -14,6 +14,7 @@ const { item, parentSlug } = Astro.props;
14
14
 
15
15
  const groupSlug = slugify(item.group);
16
16
  const currentPrefix = `${parentSlug}/${groupSlug}`;
17
+ const config = await getConfig();
17
18
 
18
19
  const groupId = `group-${currentPrefix}`;
19
20
  const listId = `list-${groupId}`;
@@ -24,9 +25,11 @@ const containsActivePage = item.pages.some((child) => {
24
25
  typeof child === "string" ? child : "pages" in child ? null : child.page;
25
26
 
26
27
  if (pagePath) {
27
- const filename = pagePath.split("/").pop() || pagePath;
28
- const pageSlug = slugify(filename);
29
- const href = `/${currentPrefix}/${pageSlug}`;
28
+ const href = buildMdxPageHref({
29
+ filePath: pagePath,
30
+ groupSlug: currentPrefix,
31
+ homePath: config.home,
32
+ });
30
33
 
31
34
  // Normalize paths for comparison (remove trailing slashes)
32
35
  const normalizedCurrent = Astro.url.pathname.replace(/\/$/, "");
@@ -52,15 +55,11 @@ const containsActivePage = item.pages.some((child) => {
52
55
  type="button"
53
56
  @click="expanded = !expanded"
54
57
  class:list={[
55
- "flex items-center justify-between gap-2 w-full px-2 py-[7px] text-sm font-[450] text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-neutral-300 cursor-pointer relative z-0 before:-z-10 before:absolute before:inset-x-0 before:inset-y-px before:rounded-md before:transition-colors hover:before:bg-neutral-100/70 dark:hover:before:bg-neutral-800/50",
58
+ "flex items-center justify-between gap-2 w-full px-2 py-[7px] text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-neutral-300 cursor-pointer relative z-0 before:-z-10 before:absolute before:inset-x-0 before:inset-y-px before:rounded-md before:transition-colors hover:before:bg-neutral-100/70 dark:hover:before:bg-neutral-800/50",
56
59
  ]}
57
60
  >
58
61
  <div class="flex items-center gap-2">
59
- {
60
- item.icon && (
61
- <Icon name={`lucide:${item.icon}`} class="size-4 opacity-75" />
62
- )
63
- }
62
+ {item.icon && <Icon name={item.icon} class="size-4 opacity-75" />}
64
63
  {item.group}
65
64
  {item.tag && <Tag>{item.tag}</Tag>}
66
65
  </div>
@@ -98,6 +97,7 @@ const containsActivePage = item.pages.some((child) => {
98
97
  path={child.page}
99
98
  icon={child.icon}
100
99
  tag={child.tag}
100
+ title={child.title}
101
101
  groupSlug={currentPrefix}
102
102
  />
103
103
  ) : null}
@@ -4,13 +4,30 @@ import { Icon } from "astro-icon/components";
4
4
 
5
5
  <div
6
6
  x-data="{
7
+ forcedTheme: (() => {
8
+ const mode = new URLSearchParams(window.location.search).get('mode');
9
+ return mode === 'light' || mode === 'dark' ? mode : null;
10
+ })(),
7
11
  theme: localStorage.getItem('theme') || 'system',
8
12
  init() {
13
+ if (this.forcedTheme) {
14
+ this.theme = this.forcedTheme;
15
+ }
16
+
17
+ this.updateDOM();
18
+
9
19
  // 1. Initial positioning
10
20
  this.$nextTick(() => this.updateMarker());
11
21
 
12
22
  // Watch for changes to the 'theme' state
13
23
  this.$watch('theme', val => {
24
+ if (this.forcedTheme) {
25
+ this.theme = this.forcedTheme;
26
+ this.updateDOM();
27
+ this.$nextTick(() => this.updateMarker());
28
+ return;
29
+ }
30
+
14
31
  localStorage.setItem('theme', val);
15
32
  this.updateDOM();
16
33
  this.$nextTick(() => this.updateMarker())
@@ -27,10 +44,12 @@ import { Icon } from "astro-icon/components";
27
44
  },
28
45
  updateDOM() {
29
46
  document.documentElement.classList.add('is-switching-theme')
30
- const isDark = this.theme === 'dark' ||
31
- (this.theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
47
+ const activeTheme = this.forcedTheme || this.theme;
48
+ const isDark = activeTheme === 'dark' ||
49
+ (activeTheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
32
50
 
33
51
  document.documentElement.classList.toggle('dark', isDark);
52
+ document.documentElement.dataset.theme = isDark ? 'dark' : 'light';
34
53
  setTimeout(() => {
35
54
  document.documentElement.classList.remove('is-switching-theme');
36
55
  }, 200);
@@ -38,7 +57,8 @@ import { Icon } from "astro-icon/components";
38
57
  markerStyle: { left: null, width: null },
39
58
  updateMarker() {
40
59
  // Use the theme name as the ref key
41
- const el = this.$refs[this.theme];
60
+ const markerTheme = this.forcedTheme || this.theme;
61
+ const el = this.$refs[markerTheme];
42
62
  if (el) {
43
63
  this.markerStyle = {
44
64
  left: el.offsetLeft + 'px',
@@ -47,10 +67,10 @@ import { Icon } from "astro-icon/components";
47
67
  }
48
68
  }
49
69
  }"
50
- class="relative flex gap-[3px] p-0.5 bg-neutral-100 dark:bg-neutral-800 rounded-full w-fit inset-shadow-sm text-neutral-500"
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"
51
71
  >
52
72
  <div
53
- class="anchor-pill absolute top-[3px] bottom-[3px] bg-white dark:bg-neutral-700 rounded-full shadow-sm ease-out z-0 flex items-center justify-center animate-[scaleIn_0.5s_ease-out]"
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]"
54
74
  style="left: 3px;"
55
75
  :style="markerStyle.width ? `left: ${markerStyle.left}; width: ${markerStyle.width}` : ''"
56
76
  >
@@ -59,6 +79,7 @@ import { Icon } from "astro-icon/components";
59
79
  x-ref="light"
60
80
  @click="theme = 'light'"
61
81
  :class="theme === 'light' ? 'text-neutral-800' : 'text-neutral-500'"
82
+ :disabled="Boolean(forcedTheme)"
62
83
  class="p-[5px] rounded-full text-sm font-medium transition-all cursor-pointer z-10"
63
84
  >
64
85
  <Icon name="lucide:sun-medium" class="size-[13px]" />
@@ -68,6 +89,7 @@ import { Icon } from "astro-icon/components";
68
89
  x-ref="dark"
69
90
  @click="theme = 'dark'"
70
91
  :class="theme === 'dark' ? 'text-neutral-300' : 'text-neutral-500'"
92
+ :disabled="Boolean(forcedTheme)"
71
93
  class="p-[5px] rounded-full text-sm font-medium transition-all cursor-pointer z-10"
72
94
  >
73
95
  <Icon name="lucide:moon" class="size-[13px]" />
@@ -77,8 +99,9 @@ import { Icon } from "astro-icon/components";
77
99
  x-ref="system"
78
100
  @click="theme = 'system'"
79
101
  :class="theme === 'system' ? 'text-neutral-800 dark:text-neutral-300' : 'text-neutral-500'"
80
- class="p-[5px] rounded-full text-sm font-medium transition-all cursor-pointer z-10"
102
+ :disabled="Boolean(forcedTheme)"
103
+ class="p-[6px] rounded-full text-sm font-medium transition-all cursor-pointer z-10"
81
104
  >
82
- <Icon name="lucide:monitor" class="size-[13px]" />
105
+ <Icon name="lucide:monitor" class="size-[12px]" />
83
106
  </button>
84
107
  </div>
@@ -12,23 +12,20 @@ const { route, serverUrl } = Astro.props;
12
12
  ---
13
13
 
14
14
  <div
15
- class="min-w-0 flex items-center gap-[3px] p-[2px] bg-neutral-100/80 inset-shadow-xs rounded-[14px] border border-neutral-200"
15
+ class="min-w-0 flex-1 flex items-center p-1 border bg-white rounded-xl shadow-xs overflow-hidden"
16
16
  >
17
- <div
18
- class="min-w-0 flex-1 flex items-center p-[3px] border border-neutral-200 bg-white rounded-xl shadow-xs overflow-hidden"
17
+ <span
18
+ class:list={[
19
+ "shrink-0 inline-block px-1.5 ml-1 text-sm font-semibold rounded-md uppercase border",
20
+ methodColors[route.openApiMethod.toLowerCase()] || methodColors.get,
21
+ ]}
19
22
  >
20
- <span
21
- class:list={[
22
- "shrink-0 inline-block px-1.5 ml-1 text-sm font-semibold rounded-md uppercase border",
23
- methodColors[route.openApiMethod.toLowerCase()] || methodColors.get,
24
- ]}
25
- >
26
- {route.openApiMethod}
27
- </span>
28
- <code
29
- class="group flex-1 mx-2 h-[30px] flex items-center text-[13px] text-neutral-600 min-w-0 relative cursor-pointer"
30
- @click="copyPath()"
31
- x-data=`{
23
+ {route.openApiMethod}
24
+ </span>
25
+ <code
26
+ class="group flex-1 mx-2 h-[30px] flex items-center text-[13px] text-neutral-600 min-w-0 relative cursor-pointer"
27
+ @click="copyPath()"
28
+ x-data=`{
32
29
  copied: false,
33
30
  path: '${serverUrl ? serverUrl + route.openApiPath : route.openApiPath}',
34
31
  async copyPath() {
@@ -43,26 +40,25 @@ const { route, serverUrl } = Astro.props;
43
40
  }
44
41
  }
45
42
  }`
43
+ >
44
+ <span class="truncate min-w-0 flex-1">
45
+ {route.openApiPath}
46
+ </span>
47
+ <div
48
+ class="absolute right-0 top-1/2 -translate-y-1/2 flex items-center gap-1 text-[12px] px-1.5 py-px bg-white border border-neutral-200 rounded-md duration-200 opacity-0 scale-75 group-hover:scale-100 group-hover:opacity-100 group-hover:duration-200 group-hover:ease-out group-hover:delay-75"
46
49
  >
47
- <span class="truncate min-w-0 flex-1">
48
- {route.openApiPath}
49
- </span>
50
- <div
51
- class="absolute right-0 top-1/2 -translate-y-1/2 flex items-center gap-1 text-[12px] px-1.5 py-px bg-white border border-neutral-200 rounded-md duration-200 opacity-0 scale-75 group-hover:scale-100 group-hover:opacity-100 group-hover:duration-200 group-hover:ease-out group-hover:delay-75"
52
- >
53
- <Icon
54
- name="lucide:copy"
55
- class="**:stroke-[2.4]"
56
- x-bind:class="copied ? 'scale-0 opacity-0 delay-0 duration-100':'duration-150 delay-75'"
57
- />
58
- <Icon
59
- name="lucide:check"
60
- class="absolute text-green-700 duration-150 **:stroke-3"
61
- x-bind:class="copied ? 'scale-100 opacity-100 delay-75':'scale-0 opacity-0'"
62
- />
63
- Copy
64
- </div>
65
- </code>
66
- <slot />
67
- </div>
50
+ <Icon
51
+ name="lucide:copy"
52
+ class="**:stroke-[2.4]"
53
+ x-bind:class="copied ? 'scale-0 opacity-0 delay-0 duration-100':'duration-150 delay-75'"
54
+ />
55
+ <Icon
56
+ name="lucide:check"
57
+ class="absolute text-green-700 duration-150 **:stroke-3"
58
+ x-bind:class="copied ? 'scale-100 opacity-100 delay-75':'scale-0 opacity-0'"
59
+ />
60
+ Copy
61
+ </div>
62
+ </code>
63
+ <slot />
68
64
  </div>