radiant-docs 0.1.59 → 0.1.61

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 (40) hide show
  1. package/package.json +1 -1
  2. package/template/astro.config.mjs +24 -2
  3. package/template/package-lock.json +121 -508
  4. package/template/package.json +3 -2
  5. package/template/scripts/generate-og-images.mjs +338 -6
  6. package/template/scripts/generate-og-metadata.mjs +29 -0
  7. package/template/src/components/Footer.astro +1 -1
  8. package/template/src/components/Header.astro +3 -10
  9. package/template/src/components/OpenApiPage.astro +181 -843
  10. package/template/src/components/SidebarGroup.astro +1 -1
  11. package/template/src/components/ThemeSwitcher.astro +5 -15
  12. package/template/src/components/chat/AssistantEmbedPanel.tsx +18 -7
  13. package/template/src/components/endpoint/PlaygroundBar.astro +54 -9
  14. package/template/src/components/endpoint/PlaygroundField.astro +1 -1
  15. package/template/src/components/endpoint/PlaygroundForm.astro +9 -5
  16. package/template/src/components/endpoint/RequestSnippets.astro +6 -1
  17. package/template/src/components/endpoint/ResponseFieldTree.astro +17 -13
  18. package/template/src/components/endpoint/ResponseFields.astro +4 -6
  19. package/template/src/components/endpoint/ResponseSnippets.astro +6 -1
  20. package/template/src/components/sidebar/SidebarEndpointLink.astro +9 -12
  21. package/template/src/components/sidebar/SidebarOpenApi.astro +3 -9
  22. package/template/src/components/ui/Field.astro +18 -15
  23. package/template/src/components/ui/Tag.astro +16 -2
  24. package/template/src/layouts/Layout.astro +6 -12
  25. package/template/src/lib/ai-artifacts.ts +792 -0
  26. package/template/src/lib/mdx/remark-resolve-internal-links.ts +22 -8
  27. package/template/src/lib/oas.ts +5 -1
  28. package/template/src/lib/openapi/operation-doc.ts +1150 -0
  29. package/template/src/lib/page-description.ts +20 -0
  30. package/template/src/lib/routes.ts +73 -18
  31. package/template/src/lib/utils.ts +11 -0
  32. package/template/src/pages/[...slug]/index.md.ts +35 -0
  33. package/template/src/pages/[...spec].json.ts +33 -0
  34. package/template/src/pages/[...spec].yaml.ts +33 -0
  35. package/template/src/pages/[...spec].yml.ts +33 -0
  36. package/template/src/pages/index.md.ts +17 -0
  37. package/template/src/pages/llms-full.txt.ts +11 -0
  38. package/template/src/pages/llms.txt.ts +11 -0
  39. package/template/src/styles/global.css +18 -15
  40. package/template/src/styles/vaul.css +0 -255
@@ -18,7 +18,7 @@ const groupSlug = slugify(item.group);
18
18
  const currentPrefix = parentSlug ? `${parentSlug}/${groupSlug}` : groupSlug;
19
19
  ---
20
20
 
21
- <li class="my-8 first:my-0 last:my-0">
21
+ <li class="my-8 first:mt-0 last:mb-0">
22
22
  <div class:list={["text-sm font-semibold mb-2 flex items-center gap-2 px-2"]}>
23
23
  {item.icon && <Icon name={item.icon} class="size-4 text-neutral-500" />}
24
24
  {item.group}
@@ -4,15 +4,15 @@ import { Icon } from "astro-icon/components";
4
4
 
5
5
  <div
6
6
  x-data="{
7
- forcedTheme: (() => {
7
+ initialTheme: (() => {
8
8
  const mode = new URLSearchParams(window.location.search).get('mode');
9
9
  return mode === 'light' || mode === 'dark' ? mode : null;
10
10
  })(),
11
11
  theme: localStorage.getItem('theme') || 'system',
12
12
  themeSwitchFrameId: null,
13
13
  init() {
14
- if (this.forcedTheme) {
15
- this.theme = this.forcedTheme;
14
+ if (this.initialTheme) {
15
+ this.theme = this.initialTheme;
16
16
  }
17
17
 
18
18
  this.updateDOM();
@@ -22,13 +22,6 @@ import { Icon } from "astro-icon/components";
22
22
 
23
23
  // Watch for changes to the 'theme' state
24
24
  this.$watch('theme', val => {
25
- if (this.forcedTheme) {
26
- this.theme = this.forcedTheme;
27
- this.updateDOM();
28
- this.$nextTick(() => this.updateMarker());
29
- return;
30
- }
31
-
32
25
  localStorage.setItem('theme', val);
33
26
  this.updateDOM();
34
27
  this.$nextTick(() => this.updateMarker())
@@ -46,7 +39,7 @@ import { Icon } from "astro-icon/components";
46
39
  updateDOM() {
47
40
  const root = document.documentElement;
48
41
  root.classList.add('is-switching-theme');
49
- const activeTheme = this.forcedTheme || this.theme;
42
+ const activeTheme = this.theme;
50
43
  const isDark = activeTheme === 'dark' ||
51
44
  (activeTheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
52
45
 
@@ -64,7 +57,7 @@ import { Icon } from "astro-icon/components";
64
57
  markerStyle: { left: null, width: null },
65
58
  updateMarker() {
66
59
  // Use the theme name as the ref key
67
- const markerTheme = this.forcedTheme || this.theme;
60
+ const markerTheme = this.theme;
68
61
  const el = this.$refs[markerTheme];
69
62
  if (el) {
70
63
  this.markerStyle = {
@@ -86,7 +79,6 @@ import { Icon } from "astro-icon/components";
86
79
  x-ref="light"
87
80
  @click="theme = 'light'"
88
81
  :class="theme === 'light' ? 'text-neutral-800' : 'text-neutral-500'"
89
- :disabled="Boolean(forcedTheme)"
90
82
  class="p-[5px] rounded-full text-sm font-medium transition-all cursor-pointer z-10"
91
83
  >
92
84
  <Icon name="lucide:sun-medium" class="size-[13px]" />
@@ -96,7 +88,6 @@ import { Icon } from "astro-icon/components";
96
88
  x-ref="dark"
97
89
  @click="theme = 'dark'"
98
90
  :class="theme === 'dark' ? 'text-neutral-300' : 'text-neutral-500'"
99
- :disabled="Boolean(forcedTheme)"
100
91
  class="p-[5px] rounded-full text-sm font-medium transition-all cursor-pointer z-10"
101
92
  >
102
93
  <Icon name="lucide:moon" class="size-[13px]" />
@@ -106,7 +97,6 @@ import { Icon } from "astro-icon/components";
106
97
  x-ref="system"
107
98
  @click="theme = 'system'"
108
99
  :class="theme === 'system' ? 'text-neutral-800 dark:text-neutral-300' : 'text-neutral-500'"
109
- :disabled="Boolean(forcedTheme)"
110
100
  class="p-[6px] rounded-full text-sm font-medium transition-all cursor-pointer z-10"
111
101
  >
112
102
  <Icon name="lucide:monitor" class="size-[12px]" />
@@ -1963,7 +1963,7 @@ export default function AssistantEmbedPanel({
1963
1963
  <AssistantPanelIcon
1964
1964
  color={launcherIconColor}
1965
1965
  imageSrc={launcherIconImageSrc}
1966
- className="assistant-embed-header-icon inline-flex size-9 shrink-0 items-center justify-center rounded-md dark:border-[0.5px] border-neutral-900/7 bg-radial from-neutral-700/5 dark:from-white/7 to-neutral-900/7 dark:to-white/5 to-60% text-neutral-900 dark:border-white/7 dark:text-neutral-50 [&_img]:block [&_img]:size-5.5 [&_img]:object-contain [&_svg]:static [&_svg]:block [&_svg]:size-5 [&_svg]:transform-none [&_svg]:opacity-100"
1966
+ className="assistant-embed-header-icon inline-flex size-9 shrink-0 items-center justify-center rounded-md dark:border-[0.5px] border-neutral-900/7 bg-neutral-700/7 dark:bg-white/6 text-neutral-900 dark:border-white/7 dark:text-neutral-50 [&_img]:block [&_img]:size-5.5 [&_img]:object-contain [&_svg]:static [&_svg]:block [&_svg]:size-5 [&_svg]:transform-none [&_svg]:opacity-100"
1967
1967
  />
1968
1968
  <div className="min-w-0 space-y-px">
1969
1969
  <p className="truncate text-sm font-medium leading-3.5 text-neutral-900 dark:text-neutral-50">
@@ -2198,14 +2198,25 @@ export default function AssistantEmbedPanel({
2198
2198
  </p>
2199
2199
  <button
2200
2200
  type="button"
2201
- className="mt-1 inline-flex items-center gap-1.5 rounded-md border border-neutral-900/8 bg-white px-2.5 py-1.5 text-[13px] font-medium text-neutral-700 shadow-xs transition hover:bg-neutral-50 dark:border-white/10 dark:bg-white/5 dark:text-neutral-200 dark:hover:bg-white/10 cursor-pointer"
2201
+ className="mt-1 inline-flex items-center gap-1.5 rounded-md border border-neutral-900/8 bg-white px-2.5 py-1.5 text-[13px] font-medium text-neutral-700 shadow-xs transition hover:bg-neutral-50/80 dark:border-white/10 dark:bg-white/5 dark:text-neutral-200 dark:hover:bg-white/10 cursor-pointer"
2202
2202
  onClick={handleUnavailableBack}
2203
2203
  >
2204
- <Icon
2205
- icon="lucide:arrow-left"
2206
- className="size-3.5"
2207
- aria-hidden="true"
2208
- />
2204
+ <svg
2205
+ xmlns="http://www.w3.org/2000/svg"
2206
+ width="14px"
2207
+ height="14px"
2208
+ viewBox="0 0 24 24"
2209
+ >
2210
+ <path d="M0 0h24v24H0z" fill="none" />
2211
+ <path
2212
+ fill="none"
2213
+ stroke="currentColor"
2214
+ stroke-linecap="round"
2215
+ stroke-linejoin="round"
2216
+ stroke-width="2"
2217
+ d="m12 19l-7-7l7-7m7 7H5"
2218
+ />
2219
+ </svg>
2209
2220
  Back
2210
2221
  </button>
2211
2222
  </div>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  import { Icon } from "astro-icon/components";
3
- import { methodColors } from "../../lib/utils";
3
+ import { methodTagColors } from "../../lib/utils";
4
4
  import type { OpenApiRoute } from "../../lib/routes";
5
5
 
6
6
  interface Props {
@@ -9,18 +9,26 @@ interface Props {
9
9
  }
10
10
 
11
11
  const { route, serverUrl } = Astro.props;
12
+ const methodColor =
13
+ methodTagColors[route.openApiMethod.toLowerCase()] ?? methodTagColors.get;
14
+
15
+ const pathParts = route.openApiPath
16
+ .split(/(\{[^}]+\})/g)
17
+ .filter(Boolean)
18
+ .map((text) => ({
19
+ text,
20
+ isParam: text.startsWith("{") && text.endsWith("}"),
21
+ }));
12
22
  ---
13
23
 
14
24
  <div
15
- class="min-w-0 flex-1 flex items-center p-1 border border-neutral-200 bg-white rounded-xl shadow-xs overflow-hidden dark:border-neutral-800 dark:bg-(--rd-code-surface)"
25
+ class="min-w-0 flex-1 flex items-center p-1 border-[0.5px] border-neutral-900/8 bg-white rounded-xl shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-6px_rgba(0,0,0,0.08)] overflow-hidden dark:border-white/6 dark:bg-(--rd-code-surface) dark:shadow-[0_-.5px_1px_rgba(255,255,255,0.15),0_5px_12px_-6px_rgba(0,0,0,0.2)]"
16
26
  >
17
27
  <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
- ]}
28
+ class="rd-endpoint-method-tag shrink-0 inline-block px-1.5 ml-1 text-sm font-semibold rounded-md uppercase border"
29
+ style={`--rd-tag-color-light:${methodColor};--rd-tag-color-dark:${methodColor};`}
22
30
  >
23
- {route.openApiMethod}
31
+ {route.openApiMethod.toUpperCase()}
24
32
  </span>
25
33
  <code
26
34
  class="group flex-1 mx-2 h-[30px] flex items-center text-[13px] text-neutral-600 dark:text-neutral-300 min-w-0 relative cursor-pointer"
@@ -41,8 +49,23 @@ const { route, serverUrl } = Astro.props;
41
49
  }
42
50
  }`
43
51
  >
44
- <span class="truncate min-w-0 flex-1">
45
- {route.openApiPath}
52
+ <span
53
+ class="flex min-w-0 flex-1 items-center overflow-hidden whitespace-nowrap"
54
+ >
55
+ {
56
+ pathParts.map((part) =>
57
+ part.isParam ? (
58
+ <span
59
+ class="rd-endpoint-path-param inline-block rounded-sm border p-px leading-none"
60
+ style={`--rd-tag-color-light:${methodColor};--rd-tag-color-dark:${methodColor};`}
61
+ >
62
+ {part.text}
63
+ </span>
64
+ ) : (
65
+ <span>{part.text}</span>
66
+ ),
67
+ )
68
+ }
46
69
  </span>
47
70
  <div
48
71
  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 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-300"
@@ -62,3 +85,25 @@ const { route, serverUrl } = Astro.props;
62
85
  </code>
63
86
  <slot />
64
87
  </div>
88
+
89
+ <style>
90
+ .rd-endpoint-method-tag,
91
+ .rd-endpoint-path-param {
92
+ --rd-tag-color: var(--rd-tag-color-light, var(--color-theme));
93
+ background-color: color-mix(in oklab, var(--rd-tag-color) 8%, transparent);
94
+ border-color: color-mix(in oklab, var(--rd-tag-color) 0%, transparent);
95
+ color: color-mix(in oklab, var(--rd-tag-color) 95%, transparent);
96
+ }
97
+
98
+ :global(.dark) .rd-endpoint-method-tag,
99
+ :global(.dark) .rd-endpoint-path-param,
100
+ :global([data-theme="dark"]) .rd-endpoint-method-tag,
101
+ :global([data-theme="dark"]) .rd-endpoint-path-param {
102
+ --rd-tag-color: var(
103
+ --rd-tag-color-dark,
104
+ var(--rd-tag-color-light, var(--color-theme))
105
+ );
106
+ background-color: color-mix(in oklab, var(--rd-tag-color) 12%, transparent);
107
+ color: color-mix(in oklab, var(--rd-tag-color) 85%, transparent);
108
+ }
109
+ </style>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  import { Icon } from "astro-icon/components";
3
- import type { Field as FieldType } from "../OpenApiPage.astro";
3
+ import type { OpenApiField as FieldType } from "../../lib/openapi/operation-doc";
4
4
  import Field from "../ui/Field.astro";
5
5
 
6
6
  interface Props {
@@ -5,15 +5,18 @@ import type { OpenApiRoute } from "../../lib/routes";
5
5
  import { getConfig } from "../../lib/validation";
6
6
  import { renderMarkdown } from "../../lib/utils";
7
7
  import {
8
- headers,
9
- type RequestFields,
10
- type RequestSectionVariantData,
11
- } from "../OpenApiPage.astro";
8
+ OPENAPI_REQUEST_SECTION_LABELS,
9
+ type OpenApiRequestFields,
10
+ type OpenApiRequestSectionVariantData,
11
+ } from "../../lib/openapi/operation-doc";
12
12
  import Accordion from "../user/Accordion.astro";
13
13
  import PlaygroundBar from "./PlaygroundBar.astro";
14
14
  import ResponseDisplay from "./ResponseDisplay.astro";
15
15
  import PlaygroundField from "./PlaygroundField.astro";
16
16
 
17
+ type RequestFields = OpenApiRequestFields;
18
+ type RequestSectionVariantData = OpenApiRequestSectionVariantData;
19
+
17
20
  interface Props {
18
21
  route: OpenApiRoute;
19
22
  serverUrl?: string;
@@ -33,6 +36,7 @@ const {
33
36
  bodyDescription = "",
34
37
  bodyDefaultKind,
35
38
  } = Astro.props;
39
+ const headers: Record<string, string> = OPENAPI_REQUEST_SECTION_LABELS;
36
40
  const config = await getConfig();
37
41
  const configuredProxyUrl =
38
42
  typeof import.meta.env.PUBLIC_PROXY_URL === "string"
@@ -583,7 +587,7 @@ const sectionVariantFieldNames = Object.fromEntries(
583
587
  }
584
588
 
585
589
  return (
586
- <div class="border border-neutral-200 bg-white shadow-xs rounded-xl p-4 pb-0 dark:border-neutral-800 dark:bg-(--rd-code-surface) [&_[role='region']]:border-b-0">
590
+ <div class="border-[0.5px] border-neutral-900/8 bg-white shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-6px_rgba(0,0,0,0.08)] rounded-xl p-4 pb-0 dark:border-white/6 dark:bg-(--rd-code-surface) dark:shadow-[0_-.5px_1px_rgba(255,255,255,0.15),0_5px_12px_-6px_rgba(0,0,0,0.2)] [&_[role='region']]:border-b-0">
587
591
  <Accordion title={headers[key]} defaultOpen titleSize="xl">
588
592
  {key === "body" && formattedBodyDescription && (
589
593
  <div
@@ -593,7 +593,12 @@ const hasMultipleRequests = requestSnippetItems.length > 1;
593
593
  </div>
594
594
  </div>
595
595
 
596
- <div class="relative min-h-0 min-w-0 flex-1 overflow-hidden rounded-b-xl rounded-tl-xl border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface)">
596
+ <div
597
+ class:list={[
598
+ "relative min-h-0 min-w-0 flex-1 overflow-hidden rounded-b-xl border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface)",
599
+ hasMultipleRequests && "rounded-tl-xl",
600
+ ]}
601
+ >
597
602
  <div
598
603
  x-ref="snippetPanels"
599
604
  class="relative h-full overflow-auto transition-[height] motion-reduce:transition-none [scrollbar-width:thin] [scrollbar-color:var(--color-neutral-300)_transparent] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-neutral-300/70 hover:[&::-webkit-scrollbar-thumb]:bg-neutral-300/90 dark:[scrollbar-color:var(--color-neutral-700)_transparent] dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700/70 dark:hover:[&::-webkit-scrollbar-thumb]:bg-neutral-700/90"
@@ -46,7 +46,9 @@ function countImmediateChildren(field: ResponseField): number {
46
46
  }
47
47
  ---
48
48
 
49
- <div class="flex flex-col gap-4 divide-y divide-neutral-100 dark:divide-neutral-800 *:pb-3 *:last:pb-0">
49
+ <div
50
+ class="flex flex-col gap-4 divide-y divide-neutral-100 dark:divide-neutral-800 *:pb-3 *:last:pb-0"
51
+ >
50
52
  {
51
53
  fields.map((field) => {
52
54
  const revealedFieldCount = countImmediateChildren(field);
@@ -69,10 +71,7 @@ function countImmediateChildren(field: ResponseField): number {
69
71
  exclusiveMaximum={field.exclusiveMaximum}
70
72
  />
71
73
  {hasExpandableContent && (
72
- <div
73
- class="mt-2 w-full overflow-hidden rounded-lg border border-neutral-200 bg-white transition-colors duration-200 dark:border-neutral-800 dark:bg-(--rd-code-surface)"
74
- x-bind:class="expanded ? 'border-neutral-300 dark:border-neutral-700' : 'border-neutral-200 dark:border-neutral-800'"
75
- >
74
+ <div class="mt-2.5 w-full overflow-hidden rounded-lg border-[0.5px] border-neutral-900/8 bg-white shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-6px_rgba(0,0,0,0.08)] transition-colors duration-200 dark:border-white/6 dark:bg-(--rd-code-surface) dark:shadow-[0_-.5px_1px_rgba(255,255,255,0.15),0_5px_12px_-6px_rgba(0,0,0,0.2)]">
76
75
  <button
77
76
  type="button"
78
77
  x-on:click="expanded = !expanded"
@@ -83,19 +82,21 @@ function countImmediateChildren(field: ResponseField): number {
83
82
  <ListChevronsToggle class="size-4 shrink-0 text-neutral-400 group-hover:text-neutral-600 transition duration-200 dark:text-neutral-500 dark:group-hover:text-neutral-300" />
84
83
  <span>
85
84
  {revealedFieldCount}{" "}
86
- {revealedFieldCount === 1
87
- ? "field"
88
- : "fields"}
85
+ {revealedFieldCount === 1 ? "field" : "fields"}
89
86
  </span>
90
87
  </span>
91
88
  </button>
92
89
  <div x-show="expanded" x-collapse x-cloak>
93
90
  <div class="border-t border-neutral-100 px-3 py-3 dark:border-neutral-800">
94
- {field.nested && field.nested.length > 0 && nestedFieldCount > 0 && (
95
- <Astro.self fields={field.nested} depth={depth + 1} />
96
- )}
91
+ {field.nested &&
92
+ field.nested.length > 0 &&
93
+ nestedFieldCount > 0 && (
94
+ <Astro.self fields={field.nested} depth={depth + 1} />
95
+ )}
97
96
  {field.variants && field.variants.length > 0 && (
98
- <div class:list={["space-y-2", nestedFieldCount > 0 && "mt-3"]}>
97
+ <div
98
+ class:list={["space-y-2", nestedFieldCount > 0 && "mt-3"]}
99
+ >
99
100
  <p class="text-xs text-neutral-500 dark:text-neutral-400">
100
101
  {field.variantType === "anyOf"
101
102
  ? "One or more variants may apply."
@@ -107,7 +108,10 @@ function countImmediateChildren(field: ResponseField): number {
107
108
  <div class="mb-2 text-xs font-medium text-neutral-600 dark:text-neutral-400">
108
109
  {variant.label}
109
110
  </div>
110
- <Astro.self fields={variant.fields} depth={depth + 1} />
111
+ <Astro.self
112
+ fields={variant.fields}
113
+ depth={depth + 1}
114
+ />
111
115
  </div>
112
116
  {field.variantType === "oneOf" &&
113
117
  index < field.variants.length - 1 && (
@@ -771,10 +771,9 @@ Object.entries(responses)
771
771
  {response.variants && response.variants.length > 0 && (
772
772
  <p class="mb-2 text-xs text-neutral-500 dark:text-neutral-400">Common fields</p>
773
773
  )}
774
- <div x-data="{ expanded: false }">
774
+ <div x-data="{ expanded: true }">
775
775
  <div
776
- class="w-full overflow-hidden rounded-xl border border-neutral-200 bg-white transition-colors duration-200 dark:border-neutral-800 dark:bg-(--rd-code-surface)"
777
- x-bind:class="expanded ? 'border-neutral-300 dark:border-neutral-700' : 'border-neutral-200 dark:border-neutral-800'"
776
+ class="w-full overflow-hidden rounded-xl border-[0.5px] border-neutral-900/8 bg-white shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-6px_rgba(0,0,0,0.08)] transition-colors duration-200 dark:border-white/6 dark:bg-(--rd-code-surface) dark:shadow-[0_-.5px_1px_rgba(255,255,255,0.15),0_5px_12px_-6px_rgba(0,0,0,0.2)]"
778
777
  >
779
778
  <button
780
779
  type="button"
@@ -814,10 +813,9 @@ Object.entries(responses)
814
813
  <div class="mb-2 text-xs font-medium text-neutral-600 dark:text-neutral-400">
815
814
  {variant.label}
816
815
  </div>
817
- <div x-data="{ expanded: false }">
816
+ <div x-data="{ expanded: true }">
818
817
  <div
819
- class="w-full overflow-hidden rounded-lg border border-neutral-200 bg-white transition-colors duration-200 dark:border-neutral-800 dark:bg-(--rd-code-surface)"
820
- x-bind:class="expanded ? 'border-neutral-300 dark:border-neutral-700' : 'border-neutral-200 dark:border-neutral-800'"
818
+ class="w-full overflow-hidden rounded-lg border-[0.5px] border-neutral-900/8 bg-white shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-6px_rgba(0,0,0,0.08)] transition-colors duration-200 dark:border-white/6 dark:bg-(--rd-code-surface) dark:shadow-[0_-.5px_1px_rgba(255,255,255,0.15),0_5px_12px_-6px_rgba(0,0,0,0.2)]"
821
819
  >
822
820
  <button
823
821
  type="button"
@@ -513,7 +513,12 @@ const hasMultipleResponses = responseSnippetItems.length > 1;
513
513
  </div>
514
514
  </div>
515
515
 
516
- <div class="relative min-h-0 min-w-0 flex-1 overflow-hidden rounded-b-xl rounded-tl-xl border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface)">
516
+ <div
517
+ class:list={[
518
+ "relative min-h-0 min-w-0 flex-1 overflow-hidden rounded-b-xl border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface)",
519
+ hasMultipleResponses && "rounded-tl-xl",
520
+ ]}
521
+ >
517
522
  <div
518
523
  x-ref="snippetPanels"
519
524
  class="relative h-full overflow-auto transition-[height] motion-reduce:transition-none [scrollbar-width:thin] [scrollbar-color:var(--color-neutral-300)_transparent] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-neutral-300/70 hover:[&::-webkit-scrollbar-thumb]:bg-neutral-300/90 dark:[scrollbar-color:var(--color-neutral-700)_transparent] dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700/70 dark:hover:[&::-webkit-scrollbar-thumb]:bg-neutral-700/90"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  import Tag from "../ui/Tag.astro";
3
- import { methodColors } from "../../lib/utils";
3
+ import { methodTagColors } from "../../lib/utils";
4
4
  import { getOpenApiEndpointRouteHref } from "../../lib/routes";
5
5
  import type { NavTag } from "../../lib/validation";
6
6
 
@@ -34,6 +34,10 @@ const href = await getOpenApiEndpointRouteHref({
34
34
 
35
35
  // Use summary for title, fallback to method + path
36
36
  const text = title || summary || `${normalizedMethod.toUpperCase()} ${pathStr}`;
37
+ const methodLabel = (
38
+ normalizedMethod !== "delete" ? normalizedMethod : "del"
39
+ ).toUpperCase();
40
+ const methodColor = methodTagColors[normalizedMethod] ?? methodTagColors.get;
37
41
 
38
42
  // Normalize paths for comparison (remove trailing slashes)
39
43
  const currentPath = Astro.url.pathname.replace(/\/$/, "");
@@ -44,22 +48,15 @@ const isActive = currentPath === targetPath;
44
48
  <a
45
49
  href={href}
46
50
  class:list={[
47
- "flex items-center 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",
51
+ "flex items-center gap-2 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",
48
52
  isActive
49
53
  ? "before:bg-neutral-200/50 dark:before:bg-neutral-800 text-neutral-900 dark:text-neutral-200"
50
54
  : "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",
51
55
  ]}
52
56
  >
53
- <span
54
- class:list={[
55
- "px-1 py-px mr-1.5 border rounded-md text-[10px] font-semibold uppercase",
56
- methodColors[normalizedMethod] ?? methodColors.get,
57
- ]}
58
- >
59
- {normalizedMethod !== "delete" ? normalizedMethod : "del"}
60
- </span>
61
- <div class="flex items-center gap-2 min-w-0">
62
- <span>{text}</span>
57
+ <div class="flex min-w-0 flex-1 items-center gap-2">
58
+ <span class="min-w-0 truncate">{text}</span>
63
59
  {tag && <Tag tag={tag} />}
64
60
  </div>
61
+ <Tag color={methodColor}>{methodLabel}</Tag>
65
62
  </a>
@@ -31,7 +31,7 @@ const openApiDoc = await loadOpenApiSpec(openApiPath);
31
31
 
32
32
  // Helper function to parse endpoint string
33
33
  function parseEndpointString(
34
- endpointStr: string
34
+ endpointStr: string,
35
35
  ): { method: string; path: string } | null {
36
36
  const trimmed = endpointStr.trim();
37
37
  const parts = trimmed.split(/\s+/);
@@ -57,7 +57,7 @@ function shouldIncludeEndpoint(
57
57
  method: string,
58
58
  pathStr: string,
59
59
  include?: string[],
60
- exclude?: string[]
60
+ exclude?: string[],
61
61
  ): boolean {
62
62
  const normalizedMethod = method.toUpperCase();
63
63
  const normalizedPath = pathStr.toLowerCase();
@@ -168,13 +168,7 @@ const sortedTagGroups: TagGroup[] = Array.from(tagGroups.entries())
168
168
  // If there are tagged groups, render them (including "Other" if it exists)
169
169
  hasTaggedGroups
170
170
  ? sortedTagGroups.map((group, index) => (
171
- <li
172
- class={
173
- index > 0
174
- ? "mt-5 pt-[25px] relative before:absolute before:top-0 before:inset-x-0 before:h-px before:bg-linear-[90deg,transparent,var(--color-neutral-200)_20%,var(--color-neutral-200)_80%,transparent] dark:before:bg-linear-[90deg,transparent,var(--color-neutral-700)_20%,var(--color-neutral-700)_80%,transparent]"
175
- : ""
176
- }
177
- >
171
+ <li class="my-8 first:mt-0 last:mb-0">
178
172
  <div class="text-sm font-semibold mb-2 flex items-center gap-2 px-2">
179
173
  {group.tag}
180
174
  </div>
@@ -1,5 +1,6 @@
1
1
  ---
2
- import { renderMarkdown } from "../../lib/utils";
2
+ import { methodTagColors, renderMarkdown } from "../../lib/utils";
3
+ import Tag from "./Tag.astro";
3
4
 
4
5
  interface Props {
5
6
  name: string;
@@ -73,6 +74,9 @@ if (hasMinLength && hasMaxLength) {
73
74
  } else if (hasMaxLength) {
74
75
  stringLengthLabel = `length <= ${maxLength}`;
75
76
  }
77
+
78
+ const codeChipClass =
79
+ "mx-px px-[5px] pr-1 bg-neutral-100/80 text-neutral-700/90 rounded-sm leading-none font-mono font-normal border border-neutral-900/4 after:hidden before:hidden dark:bg-neutral-800/90 dark:text-neutral-200/90 dark:border-white/3";
76
80
  ---
77
81
 
78
82
  <div class="space-y-3">
@@ -80,15 +84,14 @@ if (hasMinLength && hasMaxLength) {
80
84
  <span class="font-medium leading-4">
81
85
  {name}
82
86
  </span>
83
- <code
84
- class="text-[10px] font-medium text-neutral-500 border border-neutral-200/80 bg-neutral-50 px-1 rounded-sm dark:text-neutral-300 dark:border-neutral-700/70 dark:bg-neutral-900/70"
85
- >{type}</code
86
- >
87
+ <code class:list={["text-[10px] py-[1.5px]", codeChipClass]}>
88
+ {type}
89
+ </code>
87
90
  {
88
91
  required && (
89
- <div class="text-red-700/70 bg-red-50 border border-red-700/10 rounded-full px-1.5 text-[10px] font-mono font-medium leading-none py-0.5 pb-0.5 h-fit dark:text-red-300 dark:bg-red-950/40 dark:border-red-900/40">
92
+ <Tag color={methodTagColors.delete} class="text-[10px]!">
90
93
  required
91
- </div>
94
+ </Tag>
92
95
  )
93
96
  }
94
97
  {
@@ -114,14 +117,14 @@ if (hasMinLength && hasMaxLength) {
114
117
  <span class="font-medium text-xs">Options:</span>
115
118
  {enumValues.map((v, i, a) => {
116
119
  return (
117
- <div class="flex">
118
- <span class="text-[11px] font-medium text-neutral-600 border border-neutral-200 bg-neutral-50 px-1 py-px rounded-md dark:text-neutral-300 dark:border-neutral-700 dark:bg-neutral-900/70">
119
- <code>{v}</code>
120
- </span>
120
+ <div class="inline-flex items-center leading-none">
121
+ <code class:list={["text-[11px] py-[2px]", codeChipClass]}>
122
+ {v}
123
+ </code>
121
124
  {a.length - 2 === i ? (
122
- <span class="ml-1"> or </span>
125
+ <span class="ml-1 leading-none"> or </span>
123
126
  ) : (
124
- a.length - 1 !== i && <span>, </span>
127
+ a.length - 1 !== i && <span class="leading-none">, </span>
125
128
  )}
126
129
  </div>
127
130
  );
@@ -133,7 +136,7 @@ if (hasMinLength && hasMaxLength) {
133
136
  numericRangeLabel && (
134
137
  <div class="text-sm">
135
138
  <span class="font-medium text-xs">Range:</span>
136
- <code class="text-[11px] font-medium text-neutral-600 border border-neutral-200 bg-neutral-50 px-1 py-px rounded-md dark:text-neutral-300 dark:border-neutral-700 dark:bg-neutral-900/70">
139
+ <code class:list={["text-[11px]", codeChipClass]}>
137
140
  {numericRangeLabel}
138
141
  </code>
139
142
  </div>
@@ -143,7 +146,7 @@ if (hasMinLength && hasMaxLength) {
143
146
  stringLengthLabel && (
144
147
  <div class="text-sm">
145
148
  <span class="font-medium text-xs">Length:</span>
146
- <code class="text-[11px] font-medium text-neutral-600 border border-neutral-200 bg-neutral-50 px-1 py-px rounded-md dark:text-neutral-300 dark:border-neutral-700 dark:bg-neutral-900/70">
149
+ <code class:list={["text-[11px]", codeChipClass]}>
147
150
  {stringLengthLabel}
148
151
  </code>
149
152
  </div>
@@ -8,9 +8,10 @@ import {
8
8
  interface Props {
9
9
  tag?: NavTag;
10
10
  color?: string | ThemeColorByMode;
11
+ class?: string;
11
12
  }
12
13
 
13
- const { tag, color } = Astro.props;
14
+ const { tag, color, class: className } = Astro.props;
14
15
  const config = await getConfig();
15
16
 
16
17
  function getTagText(value: NavTag | undefined): string | undefined {
@@ -51,7 +52,10 @@ const colorStyle = resolvedColor
51
52
  ---
52
53
 
53
54
  <span
54
- class="rd-tag text-[9px] border px-[5px] py-[2px] rounded-full tracking-wide leading-none font-medium shrink-0"
55
+ class:list={[
56
+ "rd-tag text-[9px] border px-[5px] py-[2px] rounded-full tracking-wide leading-none font-medium shrink-0",
57
+ className,
58
+ ]}
55
59
  style={colorStyle}
56
60
  >
57
61
  {text ? text : <slot />}
@@ -64,4 +68,14 @@ const colorStyle = resolvedColor
64
68
  border-color: color-mix(in oklab, var(--rd-tag-color) 0%, transparent);
65
69
  color: color-mix(in oklab, var(--rd-tag-color) 95%, transparent);
66
70
  }
71
+
72
+ :global(.dark) .rd-tag,
73
+ :global([data-theme="dark"]) .rd-tag {
74
+ --rd-tag-color: var(
75
+ --rd-tag-color-dark,
76
+ var(--rd-tag-color-light, var(--color-theme))
77
+ );
78
+ background-color: color-mix(in oklab, var(--rd-tag-color) 12%, transparent);
79
+ color: color-mix(in oklab, var(--rd-tag-color) 85%, transparent);
80
+ }
67
81
  </style>
@@ -11,6 +11,7 @@ import { getFaviconConfig } from "../lib/favicon";
11
11
  import { getDocsFontCss, getDocsFontPreloads } from "../lib/font-css";
12
12
  import { resolveStaticAssetUrl } from "../lib/static-asset-url";
13
13
  import { getDocsThemeCss } from "../lib/theme-css";
14
+ import { resolvePageDescription } from "../lib/page-description";
14
15
 
15
16
  interface Props {
16
17
  pageTitle?: string;
@@ -39,14 +40,11 @@ const neutralPaletteCss = getDocsThemeCss(config.theme);
39
40
  const fontCss = getDocsFontCss(config.theme);
40
41
  const fontPreloads = getDocsFontPreloads(config.theme);
41
42
  const resolvedPageTitle = pageTitle?.trim();
42
- const resolvedPageDescription =
43
- typeof pageDescription === "string" && pageDescription.trim().length > 0
44
- ? pageDescription.trim()
45
- : undefined;
46
- const fallbackDescription = resolvedPageTitle
47
- ? `Learn about ${resolvedPageTitle} in the ${config.title} documentation.`
48
- : `${config.title} documentation.`;
49
- const resolvedMetaDescription = resolvedPageDescription ?? fallbackDescription;
43
+ const resolvedMetaDescription = resolvePageDescription({
44
+ pageTitle: resolvedPageTitle,
45
+ pageDescription,
46
+ docsTitle: config.title,
47
+ });
50
48
  const documentTitle = resolvedPageTitle
51
49
  ? `${resolvedPageTitle} | ${config.title}`
52
50
  : `${config.title} Docs`;
@@ -240,18 +238,15 @@ const askAiEnabled = isDev || orgTier >= 3;
240
238
  <!-- Edges -->
241
239
  <div
242
240
  class="fixed top-1 inset-x-1 h-16 -z-10 bg-background-dark"
243
- data-vaul-scale-chrome
244
241
  >
245
242
  <div class="bg-background w-full h-full rounded-t-2xl"></div>
246
243
  </div>
247
244
  <div
248
245
  class="fixed top-[63px] z-30 w-[5px] right-0 bottom-0 bg-background-dark border-l border-l-border"
249
- data-vaul-scale-chrome
250
246
  >
251
247
  </div>
252
248
  <div
253
249
  class="fixed top-[63px] z-30 w-[5px] left-0 bottom-0 bg-background-dark border-r border-r-border"
254
- data-vaul-scale-chrome
255
250
  >
256
251
  </div>
257
252
 
@@ -279,7 +274,6 @@ const askAiEnabled = isDev || orgTier >= 3;
279
274
  <!-- Main Content -->
280
275
  <div
281
276
  class="mx-1 mt-1 px-4 sm:px-6 lg:pl-[calc(288px+32px)] pt-16 lg:pr-8 bg-background"
282
- data-vaul-scale-chrome
283
277
  >
284
278
  <main class="mx-auto pt-16 pb-16 min-h-[calc(100vh-64px)]">
285
279
  <slot />