blume 0.4.0 → 0.5.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 (67) hide show
  1. package/dist/cli/index.js +1137 -722
  2. package/dist/cli/index.js.map +28 -23
  3. package/dist/types/core/data.d.ts +2 -0
  4. package/dist/types/core/project.d.ts +12 -2
  5. package/dist/types/core/schema.d.ts +154 -15
  6. package/dist/types/core/types.d.ts +7 -0
  7. package/docs/advanced/api-reference.mdx +33 -23
  8. package/docs/advanced/bridge.mdx +74 -0
  9. package/docs/advanced/meta.ts +8 -1
  10. package/docs/advanced/migrate.mdx +119 -0
  11. package/docs/configuration/index.mdx +1 -1
  12. package/docs/content/components.mdx +55 -2
  13. package/docs/content/i18n.mdx +1 -1
  14. package/docs/content/syntax.mdx +2 -2
  15. package/docs/index.mdx +2 -2
  16. package/docs/reference/cli.mdx +29 -1
  17. package/docs/reference/frontmatter.mdx +5 -0
  18. package/package.json +11 -1
  19. package/src/astro/generate.ts +18 -8
  20. package/src/astro/templates.ts +28 -4
  21. package/src/cli/commands/build.ts +107 -63
  22. package/src/cli/commands/check.ts +20 -0
  23. package/src/cli/dev-lock.ts +13 -5
  24. package/src/cli/prepare.ts +3 -0
  25. package/src/components/BlumePage.astro +6 -0
  26. package/src/components/Icon.astro +13 -10
  27. package/src/components/content/ApiField.astro +75 -0
  28. package/src/components/content/ParamField.astro +39 -0
  29. package/src/components/content/RequestField.astro +23 -0
  30. package/src/components/content/ResponseField.astro +23 -0
  31. package/src/components/content/Step.astro +1 -1
  32. package/src/components/layout/Breadcrumbs.astro +7 -2
  33. package/src/components/layout/NavTree.astro +24 -8
  34. package/src/components/layout/RootLayout.astro +56 -34
  35. package/src/components/layout/Search.astro +1 -1
  36. package/src/components/openapi/ApiOverview.astro +84 -0
  37. package/src/components/openapi/MethodBadge.astro +28 -0
  38. package/src/components/openapi/Operation.astro +140 -0
  39. package/src/components/openapi/ParametersTable.astro +97 -0
  40. package/src/components/openapi/RequestBody.astro +58 -0
  41. package/src/components/openapi/RequestPanel.astro +169 -0
  42. package/src/components/openapi/Responses.astro +91 -0
  43. package/src/components/openapi/SchemaProperty.astro +118 -0
  44. package/src/components/openapi/SchemaTable.astro +86 -0
  45. package/src/components/openapi/helpers.ts +238 -0
  46. package/src/components/openapi/panel.ts +59 -0
  47. package/src/components/openapi/snippets.ts +201 -0
  48. package/src/core/builtin-tags.ts +5 -0
  49. package/src/core/data.ts +2 -0
  50. package/src/core/project-graph.ts +5 -1
  51. package/src/core/project.ts +25 -3
  52. package/src/core/schema.ts +47 -6
  53. package/src/core/sources/mintlify.ts +1 -1
  54. package/src/core/sources/resolve.ts +28 -6
  55. package/src/core/types.ts +7 -0
  56. package/src/migrate/mintlify/config.ts +153 -1
  57. package/src/migrate/mintlify/content.ts +8 -2
  58. package/src/migrate/mintlify/index.ts +58 -1
  59. package/src/openapi/model.ts +174 -0
  60. package/src/openapi/parse.ts +48 -0
  61. package/src/openapi/references.ts +164 -0
  62. package/src/openapi/render-mdx.ts +76 -0
  63. package/src/openapi/scalar.ts +15 -103
  64. package/src/openapi/source.ts +140 -0
  65. package/src/registry/eject.ts +15 -2
  66. package/src/theme/chrome-icons.ts +22 -0
  67. package/src/theme/icons.ts +151 -161
@@ -200,9 +200,19 @@ const searchLocale =
200
200
  localeSwitch && localeSwitch.length > 1 ? locale : undefined;
201
201
  // TOC entries: the configured heading range, or none when the TOC is disabled
202
202
  // (an empty list makes TableOfContents render nothing).
203
- const tocHeadings = toc.enabled
204
- ? headings.filter((h) => h.depth >= toc.minLevel && h.depth <= toc.maxLevel)
205
- : [];
203
+ // API operation pages own a two-column body (docs + request panel) and their own
204
+ // right rail, so they drop the table of contents and widen the content column.
205
+ const isApiOperation = pageType === "openapi-operation";
206
+ const tocHeadings =
207
+ toc.enabled && !isApiOperation
208
+ ? headings.filter((h) => h.depth >= toc.minLevel && h.depth <= toc.maxLevel)
209
+ : [];
210
+ const gridClass = isApiOperation
211
+ ? "lg:grid-cols-[17.5rem_minmax(0,1fr)]"
212
+ : "lg:grid-cols-[17.5rem_minmax(0,1fr)] xl:grid-cols-[17.5rem_minmax(0,1fr)_17.5rem]";
213
+ const articleClass = isApiOperation
214
+ ? "prose max-w-none"
215
+ : "prose mx-auto max-w-[42rem]";
206
216
  const pageTitle = page.title ? `${page.title} - ${site.title}` : site.title;
207
217
  const description = page.description ?? site.description;
208
218
 
@@ -331,7 +341,7 @@ const bannerScript = banner?.dismissible
331
341
  >
332
342
  <slot name="ask" slot="ask" />
333
343
  </HeaderSlot>
334
- <div class="mx-auto grid grid-cols-1 items-start lg:grid-cols-[17.5rem_minmax(0,1fr)] xl:grid-cols-[17.5rem_minmax(0,1fr)_17.5rem]">
344
+ <div class:list={["mx-auto grid grid-cols-1 items-start", gridClass]}>
335
345
  <aside
336
346
  aria-label="Primary"
337
347
  class="fixed top-16 start-0 z-[35] h-[calc(100dvh-4rem)] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:sticky lg:z-auto lg:w-auto lg:max-w-none lg:translate-x-0! lg:border-e-0 lg:bg-transparent lg:px-4"
@@ -354,47 +364,59 @@ const bannerScript = banner?.dismissible
354
364
  </nav>
355
365
  </aside>
356
366
  <main class="min-w-0 px-6 pt-6 pb-10 lg:px-8 xl:px-10" id="blume-content">
357
- <BreadcrumbsSlot crumbs={crumbs} />
367
+ <BreadcrumbsSlot crumbs={crumbs} wide={isApiOperation} />
358
368
  <TableOfContentsSlot
359
369
  headings={tocHeadings}
360
370
  title={strings.toc.title}
361
371
  variant="mobile"
362
372
  />
363
373
  <PageHeaderSlot headings={tocHeadings} page={page} route={page.route} />
364
- <article class="prose mx-auto max-w-[42rem]" dir={contentDir}>
374
+ <article class:list={[articleClass]} dir={contentDir}>
365
375
  <slot />
366
376
  </article>
367
- <PageFooterSlot headings={tocHeadings} page={page} route={page.route} />
368
377
  {
369
- formattedLastModified && (
370
- <p class="mx-auto mt-10 max-w-[42rem] text-muted-foreground text-sm">
371
- {strings.page.lastUpdated} {formattedLastModified}
372
- </p>
378
+ !isApiOperation && (
379
+ <>
380
+ <PageFooterSlot
381
+ headings={tocHeadings}
382
+ page={page}
383
+ route={page.route}
384
+ />
385
+ {formattedLastModified && (
386
+ <p class="mx-auto mt-10 max-w-[42rem] text-muted-foreground text-sm">
387
+ {strings.page.lastUpdated} {formattedLastModified}
388
+ </p>
389
+ )}
390
+ {feedback && <FeedbackSlot strings={strings.feedback} />}
391
+ <PaginationSlot next={next} prev={prev} strings={strings.page} />
392
+ </>
373
393
  )
374
394
  }
375
- {feedback && <FeedbackSlot strings={strings.feedback} />}
376
- <PaginationSlot next={next} prev={prev} strings={strings.page} />
377
395
  </main>
378
- <aside
379
- aria-label={strings.toc.title}
380
- class="sticky top-16 hidden h-[calc(100dvh-4rem)] overflow-y-auto px-4 pt-6 pb-10 text-sm xl:block"
381
- >
382
- <TableOfContentsSlot
383
- headings={tocHeadings}
384
- title={strings.toc.title}
385
- variant="desktop"
386
- />
387
- <PageActions
388
- askEnabled={askEnabled}
389
- editUrl={editUrl}
390
- exportEpub={exportEpub}
391
- exportPdf={exportPdf}
392
- mcpName={mcp?.name}
393
- mcpUrl={mcpUrl}
394
- route={page.route}
395
- strings={strings.actions}
396
- />
397
- </aside>
396
+ {
397
+ !isApiOperation && (
398
+ <aside
399
+ aria-label={strings.toc.title}
400
+ class="sticky top-16 hidden h-[calc(100dvh-4rem)] overflow-y-auto px-4 pt-6 pb-10 text-sm xl:block"
401
+ >
402
+ <TableOfContentsSlot
403
+ headings={tocHeadings}
404
+ title={strings.toc.title}
405
+ variant="desktop"
406
+ />
407
+ <PageActions
408
+ askEnabled={askEnabled}
409
+ editUrl={editUrl}
410
+ exportEpub={exportEpub}
411
+ exportPdf={exportPdf}
412
+ mcpName={mcp?.name}
413
+ mcpUrl={mcpUrl}
414
+ route={page.route}
415
+ strings={strings.actions}
416
+ />
417
+ </aside>
418
+ )
419
+ }
398
420
  </div>
399
421
  <FooterSlot navigation={navigation} site={site} ui={strings} />
400
422
  {
@@ -414,7 +436,7 @@ const bannerScript = banner?.dismissible
414
436
  type="button"
415
437
  ></button>
416
438
  <script>
417
- import { icons } from "../../theme/icons.ts";
439
+ import { chromeIcons as icons } from "../../theme/chrome-icons.ts";
418
440
  // Registers the <blume-mermaid> custom element (emitted by ```mermaid
419
441
  // fences). Mermaid itself is lazy-loaded only on pages that use a diagram.
420
442
  import "../content/mermaid-element.ts";
@@ -138,7 +138,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
138
138
  />
139
139
 
140
140
  <script>
141
- import { icons } from "../../theme/icons.ts";
141
+ import { chromeIcons as icons } from "../../theme/chrome-icons.ts";
142
142
  import {
143
143
  escapeHtml,
144
144
  highlight,
@@ -0,0 +1,84 @@
1
+ ---
2
+ import specs from "blume:openapi";
3
+ import MethodBadge from "./MethodBadge.astro";
4
+
5
+ interface Props {
6
+ source: string;
7
+ }
8
+
9
+ const { source } = Astro.props;
10
+ const spec = specs[source];
11
+ const operations = Object.values(spec?.operations ?? {});
12
+ const servers =
13
+ ((spec?.document ?? {}) as { servers?: { url?: string }[] }).servers ?? [];
14
+
15
+ // Tags in declared order, then any operation tag not listed under `tags`.
16
+ const declaredSlugs = new Set(spec?.tags.map((tag) => tag.slug));
17
+ const extraSlugs = new Set<string>();
18
+ const extraTags: { slug: string; name: string; description: string }[] = [];
19
+ for (const operation of operations) {
20
+ if (!(declaredSlugs.has(operation.tagSlug) || extraSlugs.has(operation.tagSlug))) {
21
+ extraSlugs.add(operation.tagSlug);
22
+ extraTags.push({ description: "", name: operation.tag, slug: operation.tagSlug });
23
+ }
24
+ }
25
+ const sections = [...(spec?.tags ?? []), ...extraTags];
26
+ ---
27
+
28
+ {
29
+ spec && (
30
+ <div>
31
+ {spec.version && (
32
+ <div class="not-prose mb-4 text-muted-foreground text-sm">
33
+ Version {spec.version}
34
+ </div>
35
+ )}
36
+ {servers.length > 0 && (
37
+ <div class="not-prose mb-8 flex flex-wrap items-center gap-2">
38
+ <span class="text-muted-foreground text-xs">Base URL</span>
39
+ {servers.map((server) => (
40
+ <code class="rounded bg-muted px-2 py-0.5 text-foreground text-xs">
41
+ {server.url}
42
+ </code>
43
+ ))}
44
+ </div>
45
+ )}
46
+ {sections.map((tag) => {
47
+ const ops = operations.filter(
48
+ (operation) => operation.tagSlug === tag.slug
49
+ );
50
+ if (ops.length === 0) {
51
+ return null;
52
+ }
53
+ return (
54
+ <section class="mb-8">
55
+ <h2 class="mb-1 font-semibold text-foreground text-xl">
56
+ {tag.name}
57
+ </h2>
58
+ {tag.description && (
59
+ <div class="mb-3 text-muted-foreground text-sm" set:text={tag.description} />
60
+ )}
61
+ <ul class="not-prose flex list-none flex-col gap-2 p-0">
62
+ {ops.map((operation) => (
63
+ <li>
64
+ <a
65
+ class="flex items-center gap-3 rounded-blume border border-border p-3 text-inherit no-underline! transition-colors hover:border-accent hover:bg-muted hover:no-underline!"
66
+ href={operation.route}
67
+ >
68
+ <MethodBadge method={operation.method} />
69
+ <span class="font-medium text-foreground text-sm">
70
+ {operation.summary || operation.path}
71
+ </span>
72
+ <code class="ml-auto hidden text-muted-foreground text-xs sm:inline">
73
+ {operation.path}
74
+ </code>
75
+ </a>
76
+ </li>
77
+ ))}
78
+ </ul>
79
+ </section>
80
+ );
81
+ })}
82
+ </div>
83
+ )
84
+ }
@@ -0,0 +1,28 @@
1
+ ---
2
+ interface Props {
3
+ method: string;
4
+ class?: string;
5
+ }
6
+
7
+ const { method, class: className } = Astro.props;
8
+ const upper = method.toUpperCase();
9
+
10
+ const COLORS: Record<string, string> = {
11
+ DELETE: "bg-red-500/15 text-red-700 dark:text-red-300",
12
+ GET: "bg-green-500/15 text-green-700 dark:text-green-300",
13
+ HEAD: "bg-muted text-muted-foreground",
14
+ OPTIONS: "bg-muted text-muted-foreground",
15
+ PATCH: "bg-yellow-500/20 text-yellow-800 dark:text-yellow-300",
16
+ POST: "bg-blue-500/15 text-blue-700 dark:text-blue-300",
17
+ PUT: "bg-orange-500/15 text-orange-700 dark:text-orange-300",
18
+ };
19
+ const color = COLORS[upper] ?? "bg-muted text-muted-foreground";
20
+ ---
21
+
22
+ <span
23
+ class:list={[
24
+ "not-prose inline-flex items-center rounded-md px-2 py-0.5 font-mono font-semibold text-xs uppercase tracking-wide",
25
+ color,
26
+ className,
27
+ ]}>{upper}</span
28
+ >
@@ -0,0 +1,140 @@
1
+ ---
2
+ import specs from "blume:openapi";
3
+ import type { SchemaLike } from "./helpers.ts";
4
+ import { buildRequestSample, sampleLanguages } from "./snippets.ts";
5
+ import MethodBadge from "./MethodBadge.astro";
6
+ import ParametersTable from "./ParametersTable.astro";
7
+ import RequestBody from "./RequestBody.astro";
8
+ import RequestPanel from "./RequestPanel.astro";
9
+ import Responses from "./Responses.astro";
10
+
11
+ interface Props {
12
+ source: string;
13
+ id: string;
14
+ }
15
+
16
+ interface ParamLike {
17
+ $ref?: string;
18
+ name?: string;
19
+ in?: string;
20
+ description?: string;
21
+ required?: boolean;
22
+ deprecated?: boolean;
23
+ schema?: SchemaLike;
24
+ example?: unknown;
25
+ }
26
+
27
+ interface MediaTypeLike {
28
+ schema?: SchemaLike;
29
+ example?: unknown;
30
+ }
31
+
32
+ interface FullOperation {
33
+ summary?: string;
34
+ description?: string;
35
+ deprecated?: boolean;
36
+ parameters?: ParamLike[];
37
+ requestBody?: {
38
+ description?: string;
39
+ required?: boolean;
40
+ content?: Record<string, MediaTypeLike>;
41
+ };
42
+ responses?: Record<
43
+ string,
44
+ { description?: string; content?: Record<string, MediaTypeLike> }
45
+ >;
46
+ }
47
+
48
+ const { source, id } = Astro.props;
49
+ const spec = specs[source];
50
+ const ref = spec?.operations[id];
51
+
52
+ const doc = (spec?.document ?? {}) as {
53
+ paths?: Record<string, Record<string, unknown> & { parameters?: ParamLike[] }>;
54
+ components?: {
55
+ schemas?: Record<string, SchemaLike>;
56
+ parameters?: Record<string, ParamLike>;
57
+ };
58
+ servers?: { url?: string }[];
59
+ };
60
+ const pathItem = ref ? doc.paths?.[ref.path] : undefined;
61
+ const operation = (
62
+ pathItem && ref ? pathItem[ref.method] : undefined
63
+ ) as FullOperation | undefined;
64
+
65
+ const schemas = doc.components?.schemas ?? {};
66
+ const componentParams = doc.components?.parameters ?? {};
67
+ const PARAM_REF = /#\/components\/parameters\/(?<name>[^/]+)$/u;
68
+ const resolveParam = (param: ParamLike): ParamLike => {
69
+ if (typeof param.$ref === "string") {
70
+ const name = PARAM_REF.exec(param.$ref)?.groups?.name;
71
+ return (name && componentParams[name]) || param;
72
+ }
73
+ return param;
74
+ };
75
+ const params = [
76
+ ...(pathItem?.parameters ?? []),
77
+ ...(operation?.parameters ?? []),
78
+ ].map(resolveParam);
79
+
80
+ const sample =
81
+ ref && operation
82
+ ? buildRequestSample(
83
+ { parameters: params, requestBody: operation.requestBody },
84
+ ref.method,
85
+ ref.path,
86
+ doc.servers ?? [],
87
+ schemas
88
+ )
89
+ : null;
90
+ const languages = sampleLanguages(spec?.codeSamples ?? []);
91
+ ---
92
+
93
+ {
94
+ !(spec && ref && operation) ? (
95
+ <div class="text-muted-foreground">This API operation could not be found.</div>
96
+ ) : (
97
+ <div class="not-prose">
98
+ <div class="mb-6 flex flex-wrap items-center gap-3">
99
+ <MethodBadge method={ref.method} />
100
+ <code class="break-all font-mono text-foreground text-sm">
101
+ {ref.path}
102
+ </code>
103
+ {operation.deprecated && (
104
+ <span class="font-medium text-[0.625rem] text-orange-600 uppercase tracking-wide dark:text-orange-400">
105
+ deprecated
106
+ </span>
107
+ )}
108
+ </div>
109
+ <div class="grid grid-cols-1 items-start gap-x-10 gap-y-8 xl:grid-cols-[minmax(0,1fr)_minmax(0,28rem)]">
110
+ <div>
111
+ <ParametersTable parameters={params} schemas={schemas} />
112
+ {operation.requestBody && (
113
+ <RequestBody
114
+ expandAll={spec.expandSchemas}
115
+ requestBody={operation.requestBody}
116
+ schemas={schemas}
117
+ />
118
+ )}
119
+ {operation.responses && (
120
+ <Responses
121
+ expandAll={spec.expandSchemas}
122
+ responses={operation.responses}
123
+ schemas={schemas}
124
+ />
125
+ )}
126
+ </div>
127
+ {sample && (
128
+ <div class="xl:sticky xl:top-24 xl:self-start">
129
+ <RequestPanel
130
+ languages={languages}
131
+ responses={operation.responses ?? {}}
132
+ sample={sample}
133
+ schemas={schemas}
134
+ />
135
+ </div>
136
+ )}
137
+ </div>
138
+ </div>
139
+ )
140
+ }
@@ -0,0 +1,97 @@
1
+ ---
2
+ import {
3
+ constraints,
4
+ resolveSchema,
5
+ type SchemaLike,
6
+ typeLabel,
7
+ } from "./helpers.ts";
8
+
9
+ interface ParamLike {
10
+ name?: string;
11
+ in?: string;
12
+ description?: string;
13
+ required?: boolean;
14
+ deprecated?: boolean;
15
+ schema?: SchemaLike;
16
+ }
17
+
18
+ interface Props {
19
+ parameters: ParamLike[];
20
+ schemas: Record<string, SchemaLike>;
21
+ }
22
+
23
+ const { parameters, schemas } = Astro.props;
24
+
25
+ const SECTIONS: { in: string; title: string }[] = [
26
+ { in: "path", title: "Path parameters" },
27
+ { in: "query", title: "Query parameters" },
28
+ { in: "header", title: "Header parameters" },
29
+ { in: "cookie", title: "Cookie parameters" },
30
+ ];
31
+
32
+ const groups = SECTIONS.map((section) => ({
33
+ items: parameters.filter((param) => param.in === section.in),
34
+ title: section.title,
35
+ })).filter((group) => group.items.length > 0);
36
+ ---
37
+
38
+ {
39
+ groups.map((group) => (
40
+ <section class="mt-6">
41
+ <div
42
+ aria-level="3"
43
+ class="mb-2 font-semibold text-foreground text-sm"
44
+ role="heading"
45
+ >
46
+ {group.title}
47
+ </div>
48
+ <div class="not-prose rounded-blume border border-border px-4">
49
+ {group.items.map((param) => {
50
+ const resolved = resolveSchema(schemas, param.schema ?? {});
51
+ const type = param.schema ? typeLabel(param.schema, schemas) : "string";
52
+ const limits = constraints(resolved);
53
+ const enumValues = Array.isArray(resolved.enum) ? resolved.enum : null;
54
+ return (
55
+ <div class="border-border border-t py-3 first:border-t-0">
56
+ <div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
57
+ <code class="font-mono text-foreground text-sm">{param.name}</code>
58
+ <span class="text-muted-foreground text-xs">{type}</span>
59
+ {param.required && (
60
+ <span class="font-medium text-[0.625rem] text-red-600 uppercase tracking-wide dark:text-red-400">
61
+ required
62
+ </span>
63
+ )}
64
+ {param.deprecated && (
65
+ <span class="font-medium text-[0.625rem] text-muted-foreground uppercase tracking-wide line-through">
66
+ deprecated
67
+ </span>
68
+ )}
69
+ </div>
70
+ {param.description && (
71
+ <div
72
+ class="mt-1 text-muted-foreground text-sm"
73
+ set:text={param.description}
74
+ />
75
+ )}
76
+ {limits.length > 0 && (
77
+ <div class="mt-1 text-muted-foreground text-xs">
78
+ {limits.join(" · ")}
79
+ </div>
80
+ )}
81
+ {enumValues && (
82
+ <div class="mt-1 flex flex-wrap items-center gap-1 text-xs">
83
+ <span class="text-muted-foreground">Allowed:</span>
84
+ {enumValues.map((value) => (
85
+ <code class="rounded bg-muted px-1 py-0.5 text-foreground">
86
+ {String(value)}
87
+ </code>
88
+ ))}
89
+ </div>
90
+ )}
91
+ </div>
92
+ );
93
+ })}
94
+ </div>
95
+ </section>
96
+ ))
97
+ }
@@ -0,0 +1,58 @@
1
+ ---
2
+ import type { SchemaLike } from "./helpers.ts";
3
+ import SchemaTable from "./SchemaTable.astro";
4
+
5
+ interface MediaTypeLike {
6
+ schema?: SchemaLike;
7
+ }
8
+
9
+ interface RequestBodyLike {
10
+ description?: string;
11
+ required?: boolean;
12
+ content?: Record<string, MediaTypeLike>;
13
+ }
14
+
15
+ interface Props {
16
+ requestBody: RequestBodyLike;
17
+ schemas: Record<string, SchemaLike>;
18
+ expandAll?: boolean;
19
+ }
20
+
21
+ const { requestBody, schemas, expandAll = false } = Astro.props;
22
+
23
+ const entries = Object.entries(requestBody.content ?? {});
24
+ const chosen = entries.find(([type]) => type.includes("json")) ?? entries[0];
25
+ const contentType = chosen?.[0] ?? "application/json";
26
+ const schema = chosen?.[1]?.schema ?? {};
27
+ ---
28
+
29
+ <section class="mt-8">
30
+ <div class="mb-2 flex flex-wrap items-center gap-2">
31
+ <div
32
+ aria-level="2"
33
+ class="font-semibold text-foreground text-lg"
34
+ role="heading"
35
+ >
36
+ Request body
37
+ </div>
38
+ {
39
+ requestBody.required && (
40
+ <span class="font-medium text-[0.625rem] text-red-600 uppercase tracking-wide dark:text-red-400">
41
+ required
42
+ </span>
43
+ )
44
+ }
45
+ <code class="text-muted-foreground text-xs">{contentType}</code>
46
+ </div>
47
+ {
48
+ requestBody.description && (
49
+ <div
50
+ class="text-muted-foreground text-sm"
51
+ set:text={requestBody.description}
52
+ />
53
+ )
54
+ }
55
+ <div class="mt-3">
56
+ <SchemaTable expandAll={expandAll} schema={schema} schemas={schemas} />
57
+ </div>
58
+ </section>