radiant-docs 0.1.0

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 (61) hide show
  1. package/dist/index.js +312 -0
  2. package/package.json +38 -0
  3. package/template/.vscode/extensions.json +4 -0
  4. package/template/.vscode/launch.json +11 -0
  5. package/template/astro.config.mjs +216 -0
  6. package/template/ec.config.mjs +51 -0
  7. package/template/package-lock.json +12546 -0
  8. package/template/package.json +51 -0
  9. package/template/public/favicon.svg +9 -0
  10. package/template/src/assets/icons/check.svg +33 -0
  11. package/template/src/assets/icons/danger.svg +37 -0
  12. package/template/src/assets/icons/info.svg +36 -0
  13. package/template/src/assets/icons/lightbulb.svg +74 -0
  14. package/template/src/assets/icons/warning.svg +37 -0
  15. package/template/src/components/Header.astro +176 -0
  16. package/template/src/components/MdxPage.astro +49 -0
  17. package/template/src/components/OpenApiPage.astro +270 -0
  18. package/template/src/components/Search.astro +362 -0
  19. package/template/src/components/Sidebar.astro +19 -0
  20. package/template/src/components/SidebarDropdown.astro +149 -0
  21. package/template/src/components/SidebarGroup.astro +51 -0
  22. package/template/src/components/SidebarLink.astro +56 -0
  23. package/template/src/components/SidebarMenu.astro +46 -0
  24. package/template/src/components/SidebarSubgroup.astro +136 -0
  25. package/template/src/components/TableOfContents.astro +480 -0
  26. package/template/src/components/ThemeSwitcher.astro +84 -0
  27. package/template/src/components/endpoint/PlaygroundBar.astro +68 -0
  28. package/template/src/components/endpoint/PlaygroundButton.astro +44 -0
  29. package/template/src/components/endpoint/PlaygroundField.astro +54 -0
  30. package/template/src/components/endpoint/PlaygroundForm.astro +203 -0
  31. package/template/src/components/endpoint/RequestSnippets.astro +308 -0
  32. package/template/src/components/endpoint/ResponseDisplay.astro +177 -0
  33. package/template/src/components/endpoint/ResponseFields.astro +224 -0
  34. package/template/src/components/endpoint/ResponseSnippets.astro +247 -0
  35. package/template/src/components/sidebar/SidebarEndpointLink.astro +51 -0
  36. package/template/src/components/sidebar/SidebarOpenApi.astro +207 -0
  37. package/template/src/components/ui/Field.astro +69 -0
  38. package/template/src/components/ui/Tag.astro +5 -0
  39. package/template/src/components/ui/demo/CodeDemo.astro +15 -0
  40. package/template/src/components/ui/demo/Demo.astro +3 -0
  41. package/template/src/components/ui/demo/UiDisplay.astro +13 -0
  42. package/template/src/components/user/Accordian.astro +69 -0
  43. package/template/src/components/user/AccordianGroup.astro +13 -0
  44. package/template/src/components/user/Callout.astro +101 -0
  45. package/template/src/components/user/Step.astro +51 -0
  46. package/template/src/components/user/Steps.astro +9 -0
  47. package/template/src/components/user/Tab.astro +25 -0
  48. package/template/src/components/user/Tabs.astro +122 -0
  49. package/template/src/content.config.ts +11 -0
  50. package/template/src/entrypoint.ts +9 -0
  51. package/template/src/layouts/Layout.astro +92 -0
  52. package/template/src/lib/component-error.ts +163 -0
  53. package/template/src/lib/frontmatter-schema.ts +9 -0
  54. package/template/src/lib/oas.ts +24 -0
  55. package/template/src/lib/pagefind.ts +88 -0
  56. package/template/src/lib/routes.ts +316 -0
  57. package/template/src/lib/utils.ts +59 -0
  58. package/template/src/lib/validation.ts +1097 -0
  59. package/template/src/pages/[...slug].astro +77 -0
  60. package/template/src/styles/global.css +209 -0
  61. package/template/tsconfig.json +5 -0
@@ -0,0 +1,54 @@
1
+ ---
2
+ import { Icon } from "astro-icon/components";
3
+ import type { Field as FieldType } from "../OpenApiPage.astro";
4
+ import Field from "../ui/Field.astro";
5
+
6
+ interface Props {
7
+ field: FieldType;
8
+ requestPart: string;
9
+ }
10
+
11
+ const { field, requestPart } = Astro.props;
12
+ ---
13
+
14
+ <div
15
+ class="flex justify-between flex-col sm:flex-row lg:flex-col xl:flex-row gap-4"
16
+ >
17
+ <div class="w-full mt-1.5">
18
+ <Field
19
+ name={field.name}
20
+ type={field.type}
21
+ description={field.description}
22
+ required={field.required}
23
+ />
24
+ </div>
25
+ <div class="w-full">
26
+ {
27
+ field.enum && field.enum.length > 0 ? (
28
+ <div class="relative">
29
+ <select
30
+ name={`${requestPart}_${field.name}`}
31
+ x-model={`inputs['${requestPart}']['${field.name}']`}
32
+ class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow placeholder:text-neutral-400 transition-all duration-200 bg-white appearance-none"
33
+ >
34
+ {field.enum.map((value) => (
35
+ <option value={String(value)}>{value}</option>
36
+ ))}
37
+ </select>
38
+ <Icon
39
+ class="absolute right-3 top-1/2 -translate-y-1/2 size-4 pointer-events-none text-neutral-400"
40
+ name="lucide:chevrons-up-down"
41
+ />
42
+ </div>
43
+ ) : (
44
+ <input
45
+ name={field.name}
46
+ placeholder={`Enter ${field.name}`}
47
+ type={field.type === "number" ? "number" : "text"}
48
+ x-model={`inputs['${requestPart}']['${field.name}']`}
49
+ class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow placeholder:text-neutral-400 transition-all duration-200"
50
+ />
51
+ )
52
+ }
53
+ </div>
54
+ </div>
@@ -0,0 +1,203 @@
1
+ ---
2
+ import { Icon } from "astro-icon/components";
3
+ import type { OpenApiRoute } from "../../lib/routes";
4
+ import { renderMarkdown } from "../../lib/utils";
5
+ import { headers, type RequestFields } from "../OpenApiPage.astro";
6
+ import Accordian from "../user/Accordian.astro";
7
+ import PlaygroundBar from "./PlaygroundBar.astro";
8
+ import Field from "../ui/Field.astro";
9
+ import ResponseDisplay from "./ResponseDisplay.astro";
10
+ import PlaygroundField from "./PlaygroundField.astro";
11
+
12
+ interface Props {
13
+ route: OpenApiRoute;
14
+ serverUrl?: string;
15
+ requestFields: RequestFields;
16
+ }
17
+
18
+ const { route, serverUrl, requestFields } = Astro.props;
19
+ ---
20
+
21
+ <div
22
+ x-data=`{
23
+ loading: false,
24
+ response: null,
25
+ inputs: {
26
+ header: {},
27
+ path: {},
28
+ query: {},
29
+ body: {}
30
+ },
31
+ async sendRequest() {
32
+ this.loading = true;
33
+ await new Promise(resolve => setTimeout(resolve, 2000))
34
+ try {
35
+ // 1. Construct URL with Path Parameters
36
+ let url = ${JSON.stringify(serverUrl + route.openApiPath)};
37
+ Object.entries(this.inputs.path).forEach(([key, val]) => {
38
+ url = url.replace('{' + key + '}', encodeURIComponent(val));
39
+ });
40
+
41
+ // 2. Add Query Parameters
42
+ const queryParams = new URLSearchParams(this.inputs.query).toString();
43
+ if (queryParams) url += '?' + queryParams;
44
+
45
+ // 3. Prepare Fetch Options
46
+ console.log("Hello", this.inputs)
47
+ const options = {
48
+ method: ${JSON.stringify(route.openApiMethod.toUpperCase())},
49
+ headers: {
50
+ 'Content-Type': 'application/json',
51
+ ...this.inputs.header
52
+ }
53
+ };
54
+
55
+ // 4. Add Body if needed
56
+ if (['POST', 'PUT', 'PATCH'].includes(options.method)) {
57
+ options.body = JSON.stringify(this.inputs.body);
58
+ }
59
+
60
+ // 5. Execute Request
61
+ const res = await fetch(url, options);
62
+
63
+ const status = res.status;
64
+ const statusTextMap = {
65
+ 400: 'Bad Request', 401: 'Unauthorized', 403: 'Forbidden',
66
+ 404: 'Not Found', 405: 'Method Not Allowed', 409: 'Conflict',
67
+ 422: 'Unprocessable Entity', 429: 'Too Many Requests',
68
+ 500: 'Internal Server Error', 502: 'Bad Gateway',
69
+ 503: 'Service Unavailable', 504: 'Gateway Timeout'
70
+ };
71
+ const statusText = res.statusText || statusTextMap[status] || 'Unknown';
72
+
73
+ const headers = {};
74
+ res.headers.forEach((value, key) => {
75
+ headers[key] = value;
76
+ });
77
+
78
+ let data;
79
+ let highlightedData;
80
+ const contentType = res.headers.get('content-type') || '';
81
+
82
+ try {
83
+ if (contentType.includes('application/json')) {
84
+ data = await res.json();
85
+ highlightedData = Prism.highlight(
86
+ JSON.stringify(data, null, 2),
87
+ Prism.languages.json,
88
+ 'json'
89
+ );
90
+ } else {
91
+ // For non-JSON responses, try to get as text
92
+ const text = await res.text();
93
+ console.log("TEXT", text)
94
+ highlightedData = Prism.highlight(
95
+ text || '(empty response)',
96
+ Prism.languages.plaintext,
97
+ 'plaintext'
98
+ );
99
+ }
100
+ } catch (parseError) {
101
+ // If parsing fails, still show the response with error info
102
+ highlightedData = Prism.highlight(
103
+ JSON.stringify({ error: 'Failed to parse response', message: parseError.message }, null, 2),
104
+ Prism.languages.json,
105
+ 'json'
106
+ );
107
+ }
108
+
109
+ this.response = {
110
+ status,
111
+ statusText,
112
+ headers,
113
+ highlightedData
114
+ };
115
+
116
+ } catch (err) {
117
+ console.log('error', err)
118
+ this.response = {
119
+ status: err.status || 0,
120
+ statusText: err.statusText || err.message || 'Network Error',
121
+ };
122
+ } finally {
123
+ this.loading = false;
124
+ }
125
+ }
126
+ }`
127
+ class="flex flex-col gap-4"
128
+ >
129
+ <div class="w-full">
130
+ <PlaygroundBar route={route} serverUrl={serverUrl}>
131
+ <button
132
+ @click="sendRequest()"
133
+ :disabled="loading"
134
+ class="m-px flex items-center gap-1.5 px-4 py-[5px] text-sm font-medium rounded-lg bg-neutral-900 text-white/95 hover:text-white shadow-[inset_0_1px_0_rgb(255,255,255,0.3),0_0_0_1px_var(--color-neutral-800)] duration-200 whitespace-nowrap cursor-pointer relative before:absolute before:inset-0 before:shadow-sm before:rounded-lg before:bg-transparent disabled:bg-neutral-300 disabled:shadow-none disabled:before:shadow-none disabled:duration-0"
135
+ >
136
+ <span
137
+ class="flex items-center gap-2"
138
+ x-bind:class="loading ? 'opacity-0':''"
139
+ >
140
+ Send <span class="hidden xs:inline">Request</span>
141
+ <Icon class="size-4" name="lucide:square-arrow-up-right" />
142
+ </span>
143
+ <Icon
144
+ x-show="loading"
145
+ class="size-4 absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 animate-spin **:stroke-3"
146
+ name="lucide:loader"
147
+ />
148
+ </button>
149
+ </PlaygroundBar>
150
+ </div>
151
+
152
+ <div class="flex flex-col lg:flex-row-reverse gap-4">
153
+ <div class="lg:max-w-xl w-full">
154
+ <ResponseDisplay />
155
+ </div>
156
+ <div class="flex-3 space-y-4">
157
+ {
158
+ Object.keys(requestFields).map(
159
+ (key) =>
160
+ requestFields[key as keyof RequestFields].length > 0 && (
161
+ <div class="border border-neutral-200 shadow-xs rounded-xl p-4">
162
+ <Accordian title={headers[key]} defaultOpen titleSize="xl">
163
+ {requestFields[key as keyof RequestFields].map((field) => (
164
+ <div class="border-b border-b-neutral-100 last:border-none pb-4 mb-4 last:pb-0 last:mb-0 first:pt-2">
165
+ <PlaygroundField field={field} requestPart={key} />
166
+ </div>
167
+ ))}
168
+ </Accordian>
169
+ </div>
170
+ )
171
+ )
172
+ }
173
+ </div>
174
+ </div>
175
+ </div>
176
+
177
+ <script>
178
+ import Prism from "prismjs";
179
+ import "prismjs/components/prism-json";
180
+ // Use a dark theme that matches the #0d1117 background
181
+ import "prismjs/themes/prism-tomorrow.css";
182
+ // Make Prism available to Alpine
183
+ window.Prism = Prism;
184
+ </script>
185
+
186
+ <style>
187
+ @reference "../../styles/global.css";
188
+
189
+ :global(pre code span.token.punctuation) {
190
+ @apply text-[#24292E];
191
+ }
192
+
193
+ :global(pre code span.token.operator) {
194
+ @apply text-[#24292E];
195
+ }
196
+
197
+ :global(pre code span.token.property) {
198
+ @apply text-[#C62C2C];
199
+ }
200
+ :global(pre code span.token.string) {
201
+ @apply text-[#1E7734];
202
+ }
203
+ </style>
@@ -0,0 +1,308 @@
1
+ ---
2
+ import { Code } from "astro-expressive-code/components";
3
+ import { Icon } from "astro-icon/components";
4
+ import { methodColors } from "../../lib/utils";
5
+ import { readFileSync } from "node:fs";
6
+ import { fileURLToPath } from "node:url";
7
+ import { resolve, dirname } from "node:path";
8
+ import oasToSnippet from "@readme/oas-to-snippet";
9
+ import type { Language } from "@readme/oas-to-snippet/languages";
10
+ import type { DataForHAR, HttpMethods, MediaTypeObject } from "oas/types";
11
+ import * as Sampler from "openapi-sampler";
12
+ import type { JSONSchema7 } from "json-schema";
13
+ import type Oas from "oas";
14
+
15
+ interface Props {
16
+ api: Oas;
17
+ method: string;
18
+ path: string;
19
+ }
20
+
21
+ const { method, path, api } = Astro.props;
22
+
23
+ const operation = api.operation(path, method as HttpMethods);
24
+
25
+ // Identify and build the Auth object
26
+ const auth: Record<string, string | number | { user?: string; pass?: string }> =
27
+ {};
28
+ const securityByType = operation.prepareSecurity();
29
+ Object.values(securityByType).forEach((schemes) => {
30
+ // schemes is an array of security scheme objects
31
+ (schemes as any[]).forEach((scheme) => {
32
+ const schemeName = scheme._key || scheme.name;
33
+
34
+ // Keep the original conditionals - they're correct!
35
+ if (scheme?.type === "http" && scheme?.scheme === "bearer") {
36
+ auth[schemeName] = "<BEARER_TOKEN>";
37
+ } else if (scheme?.type === "apiKey") {
38
+ auth[schemeName] = "<API_KEY>";
39
+ } else if (scheme?.type === "http" && scheme?.scheme === "basic") {
40
+ auth[schemeName] = { user: "<USERNAME>", pass: "<PASSWORD>" };
41
+ }
42
+ });
43
+ });
44
+
45
+ // 1. Initialize the values object
46
+ const values: DataForHAR = {
47
+ path: {},
48
+ query: {},
49
+ body: undefined,
50
+ };
51
+
52
+ // 2. Process Parameters (Path and Query)
53
+ const parameters = operation.getParameters();
54
+ parameters.forEach((param) => {
55
+ const name = param.name;
56
+
57
+ // Get example if it exists
58
+ const example =
59
+ param.example ||
60
+ (param.schema as any)?.example ||
61
+ (param.schema as any)?.default;
62
+
63
+ if (param.in === "path") {
64
+ // Per your request: Maintain curly braces for path
65
+ values.path![name] = `{${name}}`;
66
+ } else if (param.in === "query") {
67
+ // Precedence: Example -> Placeholder
68
+ values.query![name] = example ? String(example) : `<${name.toUpperCase()}>`;
69
+ }
70
+ });
71
+
72
+ // Process Request Body
73
+ if (operation.hasRequestBody()) {
74
+ const requestBody = operation.getRequestBody(
75
+ "application/json"
76
+ ) as MediaTypeObject;
77
+ const schema = requestBody?.schema;
78
+
79
+ if (schema) {
80
+ values.body = Sampler.sample(schema as JSONSchema7, { skipReadOnly: true });
81
+ }
82
+ }
83
+
84
+ let snippets: {
85
+ code: string | false;
86
+ highlightMode: string | false;
87
+ install: string | false;
88
+ }[] = [];
89
+
90
+ const languages = [
91
+ ["go", "native"],
92
+ ["shell", "curl"],
93
+ ["javascript", "fetch"],
94
+ ["python", "requests"],
95
+ ];
96
+
97
+ languages.forEach(([lang, target]) => {
98
+ try {
99
+ // Generate the snippet
100
+ // formData and auth can be empty objects for generic examples
101
+ const res = oasToSnippet(api, operation, values, auth, [
102
+ lang,
103
+ target,
104
+ ] as Language);
105
+
106
+ if (res && res.code) {
107
+ res.code = res.code
108
+ .replace(/%7B/g, "{")
109
+ .replace(/%7D/g, "}")
110
+ .replace(/%3C/g, "<")
111
+ .replace(/%3E/g, ">");
112
+ }
113
+
114
+ snippets.push(res);
115
+ } catch (err) {
116
+ const errorMessage = err instanceof Error ? err.message : String(err);
117
+ console.error(`Failed to generate ${lang} snippet:`, errorMessage);
118
+ }
119
+ });
120
+
121
+ // Resolve the path to node_modules
122
+ const __filename = fileURLToPath(import.meta.url);
123
+ const __dirname = dirname(__filename);
124
+ const iconsPath = resolve(
125
+ __dirname,
126
+ "../../../node_modules/@xt0rted/expressive-code-file-icons/dist/icons"
127
+ );
128
+
129
+ // Helper function to read SVG file
130
+ function getIconSvg(iconName: string): string {
131
+ try {
132
+ return readFileSync(resolve(iconsPath, `${iconName}.svg`), "utf-8");
133
+ } catch {
134
+ return "";
135
+ }
136
+ }
137
+
138
+ // Map language to display label
139
+ const displayLabelMap: Record<string, string> = {
140
+ shell: "cURL",
141
+ javascript: "JavaScript",
142
+ python: "Python",
143
+ go: "Go",
144
+ };
145
+
146
+ // Map language to icon filename
147
+ const iconFilenameMap: Record<string, string> = {
148
+ shell: "file_type_shell",
149
+ javascript: "file_type_js_official",
150
+ python: "file_type_python",
151
+ go: "file_type_go",
152
+ };
153
+
154
+ // Create icon map with SVG content
155
+ const iconMap: Record<string, string> = {};
156
+ for (const [lang, iconName] of Object.entries(iconFilenameMap)) {
157
+ iconMap[lang] = getIconSvg(iconName);
158
+ }
159
+
160
+ // Prepare snippets with display labels
161
+ const snippetsWithIcons = snippets.map((snippet) => {
162
+ if (!snippet.highlightMode) {
163
+ return { ...snippet, iconSvg: null, displayLabel: snippet.highlightMode };
164
+ }
165
+ const iconSvg = iconMap[snippet.highlightMode.toLowerCase()] || null;
166
+ const displayLabel =
167
+ displayLabelMap[snippet.highlightMode.toLowerCase()] ||
168
+ snippet.highlightMode;
169
+ return { ...snippet, iconSvg, displayLabel };
170
+ });
171
+ ---
172
+
173
+ <div
174
+ x-data={`{
175
+ snippets: ${JSON.stringify(snippetsWithIcons)},
176
+ selected: 0,
177
+ select(index) {
178
+ this.selected = index;
179
+ this.$refs.button?.focus();
180
+ this.close();
181
+ },
182
+ close(focusAfter) {
183
+ if (!this.open) return;
184
+ this.open = false;
185
+ focusAfter?.focus();
186
+ }
187
+ }`}
188
+ class="request-code-snippets bg-neutral-100 rounded-[14px] border border-neutral-200 p-[3px] inset-shadow-xs"
189
+ >
190
+ <div class="flex justify-between items-center">
191
+ <div class="font-medium ml-2 text-neutral-700 text-sm truncate">
192
+ <span
193
+ class:list={[
194
+ "text-xs uppercase font-semibold px-1.5 border py-px rounded-md mr-1.5",
195
+ methodColors[method],
196
+ ]}>{method}</span
197
+ >{path}
198
+ </div>
199
+ <div
200
+ x-data="{
201
+ open: false,
202
+ toggle() {
203
+ if (this.open) {
204
+ return this.close()
205
+ }
206
+
207
+ this.$refs.button.focus()
208
+
209
+ this.open = true
210
+ },
211
+ close(focusAfter) {
212
+ if (! this.open) return
213
+
214
+ this.open = false
215
+
216
+ focusAfter && focusAfter.focus()
217
+ }
218
+ }"
219
+ x-on:keydown.escape.prevent.stop="close($refs.button)"
220
+ x-on:focusin.window="! $refs.panel.contains($event.target) && close()"
221
+ x-id="['dropdown-button']"
222
+ class="shrink-0 relative max-w-36 w-full"
223
+ >
224
+ <button
225
+ x-ref="button"
226
+ x-on:click="toggle()"
227
+ :aria-expanded="open"
228
+ :aria-controls="$id('dropdown-button')"
229
+ type="button"
230
+ class="flex items-center px-3 pt-2 pb-1.5 relative border-x border-t border-neutral-200/70 rounded-t-xl w-full text-sm font-medium bg-white shadow-[-1px_0px_2px_0px_rgba(0,0,0,0.01)]. shadow-sm cursor-pointer after:absolute after:-bottom-[1.5px] after:inset-x-0 after:z-10 after:h-[3px] after:bg-white"
231
+ >
232
+ <span class="flex items-center gap-2">
233
+ <span
234
+ x-show="snippets[selected].iconSvg"
235
+ x-html="snippets[selected].iconSvg"
236
+ class="size-4 [&>svg]:w-full [&>svg]:h-full rounded overflow-hidden"
237
+ set:html={snippetsWithIcons[0]?.iconSvg || ""}
238
+ />
239
+ <span
240
+ x-text="snippets[selected].displayLabel"
241
+ set:html={snippetsWithIcons[0]?.displayLabel || ""}
242
+ />
243
+ </span>
244
+ <Icon name="lucide:chevrons-up-down" class="ml-auto" />
245
+ </button>
246
+ <!-- Panel -->
247
+ <ul
248
+ x-ref="panel"
249
+ x-show="open"
250
+ x-transition.origin.top.left
251
+ x-on:click.outside="close($refs.button)"
252
+ :id="$id('dropdown-button')"
253
+ x-cloak
254
+ role="menu"
255
+ class="absolute top-full right-2 min-w-48 rounded-lg shadow-xl mt-2 z-10 origin-top-right bg-white py-0.5 outline-none border border-neutral-200"
256
+ >
257
+ <template x-for="(snippet, index) in snippets" :key="index">
258
+ <li role="menuitem">
259
+ <button
260
+ x-on:click="select(index)"
261
+ type="button"
262
+ class="w-full text-left px-3 py-2 text-sm text-neutral-700 rounded-md transition-colors flex items-center gap-2 relative before:absolute before:inset-x-1 before:inset-y-0.5 before:-z-10 before:rounded-md before:duration-150 cursor-pointer"
263
+ x-bind:class="index === selected ? 'before:bg-neutral-200/50 text-neutral-900' : 'hover:before:bg-neutral-100/70'"
264
+ >
265
+ <span
266
+ x-show="snippet.iconSvg"
267
+ x-html="snippet.iconSvg"
268
+ class="size-4 [&>svg]:w-full [&>svg]:h-full rounded overflow-hidden"
269
+ ></span>
270
+ <span x-text="snippet.displayLabel"></span>
271
+ </button>
272
+ </li>
273
+ </template>
274
+ </ul>
275
+ </div>
276
+ </div>
277
+ {
278
+ snippets.map((snippet, index) => (
279
+ <div
280
+ {...(index !== 0 ? { "x-cloak": true } : {})}
281
+ x-show={`selected === ${index}`}
282
+ data-snippet-index={index}
283
+ >
284
+ {snippet.code && snippet.highlightMode && (
285
+ <Code
286
+ code={snippet.code as string}
287
+ lang={snippet.highlightMode as string}
288
+ frame="code"
289
+ />
290
+ )}
291
+ </div>
292
+ ))
293
+ }
294
+ </div>
295
+
296
+ <style>
297
+ @reference "../../styles/global.css";
298
+ :global(.request-code-snippets .expressive-code .frame) {
299
+ @apply shadow-sm;
300
+ }
301
+ :global(.request-code-snippets .expressive-code pre) {
302
+ @apply rounded-xl! rounded-tr-none! border-neutral-200/70!;
303
+ }
304
+
305
+ :global(.request-code-snippets .expressive-code pre code) {
306
+ @apply max-h-64 overflow-y-auto;
307
+ }
308
+ </style>