radiant-docs 0.1.7 → 0.1.9
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.
- package/dist/index.js +28 -5
- package/package.json +5 -4
- package/template/astro.config.mjs +76 -3
- package/template/package-lock.json +924 -737
- package/template/package.json +7 -5
- package/template/scripts/generate-og-images.mjs +335 -0
- package/template/scripts/generate-og-metadata.mjs +173 -0
- package/template/scripts/rewrite-static-asset-host.mjs +408 -0
- package/template/scripts/stamp-image-versions.mjs +277 -0
- package/template/scripts/stamp-og-image-versions.mjs +199 -0
- package/template/scripts/stamp-pagefind-runtime-version.mjs +140 -0
- package/template/src/assets/fonts/geist-mono/cyrillic.woff2 +0 -0
- package/template/src/assets/fonts/geist-mono/latin-ext.woff2 +0 -0
- package/template/src/assets/fonts/geist-mono/latin.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/canadian-aboriginal.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/cherokee.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/latin-ext.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/latin.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/math.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/nushu.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/symbols.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/syriac.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/tifinagh.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/vietnamese.woff2 +0 -0
- package/template/src/components/Footer.astro +94 -0
- package/template/src/components/Header.astro +11 -66
- package/template/src/components/LogoLink.astro +103 -0
- package/template/src/components/MdxPage.astro +126 -11
- package/template/src/components/OpenApiPage.astro +1036 -69
- package/template/src/components/Search.astro +0 -2
- package/template/src/components/SidebarDropdown.astro +34 -14
- package/template/src/components/SidebarGroup.astro +3 -6
- package/template/src/components/SidebarLink.astro +22 -12
- package/template/src/components/SidebarMenu.astro +19 -16
- package/template/src/components/SidebarSegmented.astro +99 -0
- package/template/src/components/SidebarSubgroup.astro +12 -12
- package/template/src/components/ThemeSwitcher.astro +30 -7
- package/template/src/components/endpoint/PlaygroundBar.astro +32 -36
- package/template/src/components/endpoint/PlaygroundButton.astro +40 -4
- package/template/src/components/endpoint/PlaygroundField.astro +1068 -22
- package/template/src/components/endpoint/PlaygroundForm.astro +559 -61
- package/template/src/components/endpoint/RequestSnippets.astro +342 -193
- package/template/src/components/endpoint/ResponseDisplay.astro +161 -147
- package/template/src/components/endpoint/ResponseFieldTree.astro +134 -0
- package/template/src/components/endpoint/ResponseFields.astro +711 -68
- package/template/src/components/endpoint/ResponseSnippets.astro +299 -173
- package/template/src/components/sidebar/SidebarEndpointLink.astro +1 -1
- package/template/src/components/ui/CodeLanguageIcon.astro +19 -0
- package/template/src/components/ui/CodeTabEdge.astro +79 -0
- package/template/src/components/ui/Field.astro +103 -20
- package/template/src/components/ui/Icon.astro +32 -0
- package/template/src/components/ui/ListChevronsToggle.astro +31 -0
- package/template/src/components/ui/Tag.astro +1 -1
- package/template/src/components/user/{Accordian.astro → Accordion.astro} +6 -6
- package/template/src/components/user/Callout.astro +5 -9
- package/template/src/components/user/CodeBlock.astro +400 -0
- package/template/src/components/user/CodeGroup.astro +225 -0
- package/template/src/components/user/ComponentPreview.astro +1 -0
- package/template/src/components/user/ComponentPreviewBlock.astro +181 -0
- package/template/src/components/user/Image.astro +132 -0
- package/template/src/components/user/Steps.astro +1 -3
- package/template/src/components/user/Tabs.astro +2 -2
- package/template/src/content.config.ts +1 -0
- package/template/src/layouts/Layout.astro +109 -8
- package/template/src/lib/code/code-block.ts +546 -0
- package/template/src/lib/frontmatter-schema.ts +8 -7
- package/template/src/lib/mdx/remark-code-block-component.ts +342 -0
- package/template/src/lib/mdx/remark-demote-h1.ts +16 -0
- package/template/src/lib/pagefind.ts +19 -5
- package/template/src/lib/routes.ts +49 -31
- package/template/src/lib/utils.ts +20 -0
- package/template/src/lib/validation.ts +638 -200
- package/template/src/pages/[...slug].astro +18 -5
- package/template/src/styles/geist-mono.css +33 -0
- package/template/src/styles/global.css +89 -84
- package/template/src/styles/google-sans-flex.css +143 -0
- package/template/ec.config.mjs +0 -51
- /package/template/src/components/user/{AccordianGroup.astro → AccordionGroup.astro} +0 -0
|
@@ -1,175 +1,189 @@
|
|
|
1
1
|
---
|
|
2
|
+
import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
2
3
|
import { Icon } from "astro-icon/components";
|
|
3
4
|
---
|
|
4
5
|
|
|
5
6
|
<div
|
|
6
7
|
x-data={`{
|
|
8
|
+
tabs: [
|
|
9
|
+
{ id: 'body', label: 'Body' },
|
|
10
|
+
{ id: 'header', label: 'Header' }
|
|
11
|
+
],
|
|
7
12
|
selected: 'body',
|
|
13
|
+
pillLeft: 0,
|
|
14
|
+
pillWidth: 0,
|
|
15
|
+
pillVisible: false,
|
|
16
|
+
tabSyncHandler: null,
|
|
17
|
+
playgroundOpenHandler: null,
|
|
18
|
+
init() {
|
|
19
|
+
const sync = () => this.syncPill();
|
|
20
|
+
this.tabSyncHandler = sync;
|
|
21
|
+
const onPlaygroundOpen = () => {
|
|
22
|
+
this.$nextTick(() => this.syncPill());
|
|
23
|
+
};
|
|
24
|
+
this.playgroundOpenHandler = onPlaygroundOpen;
|
|
25
|
+
this.$nextTick(() => {
|
|
26
|
+
this.syncPill();
|
|
27
|
+
this.$refs.tabList?.addEventListener('scroll', sync, {
|
|
28
|
+
passive: true
|
|
29
|
+
});
|
|
30
|
+
window.addEventListener('resize', sync);
|
|
31
|
+
window.addEventListener('rd:playground-opened', onPlaygroundOpen);
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
destroy() {
|
|
35
|
+
if (!this.tabSyncHandler) return;
|
|
36
|
+
this.$refs.tabList?.removeEventListener('scroll', this.tabSyncHandler);
|
|
37
|
+
window.removeEventListener('resize', this.tabSyncHandler);
|
|
38
|
+
if (this.playgroundOpenHandler) {
|
|
39
|
+
window.removeEventListener('rd:playground-opened', this.playgroundOpenHandler);
|
|
40
|
+
}
|
|
41
|
+
this.tabSyncHandler = null;
|
|
42
|
+
this.playgroundOpenHandler = null;
|
|
43
|
+
},
|
|
44
|
+
syncPill() {
|
|
45
|
+
const tabs = this.$refs.tabList;
|
|
46
|
+
if (!tabs) return;
|
|
47
|
+
const activeTab = tabs.querySelector(
|
|
48
|
+
'[data-rd-response-tab=\"' + this.selected + '\"]'
|
|
49
|
+
);
|
|
50
|
+
if (!activeTab) {
|
|
51
|
+
this.pillVisible = false;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const tabsRect = tabs.getBoundingClientRect();
|
|
55
|
+
const activeRect = activeTab.getBoundingClientRect();
|
|
56
|
+
this.pillLeft = activeRect.left - tabsRect.left + tabs.scrollLeft;
|
|
57
|
+
this.pillWidth = activeRect.width;
|
|
58
|
+
this.pillVisible = true;
|
|
59
|
+
},
|
|
8
60
|
select(value) {
|
|
9
61
|
this.selected = value;
|
|
10
|
-
this.$
|
|
11
|
-
|
|
62
|
+
this.$nextTick(() => {
|
|
63
|
+
this.syncPill();
|
|
64
|
+
window.dispatchEvent(new CustomEvent('rd:snippet-content-change'));
|
|
65
|
+
});
|
|
12
66
|
},
|
|
13
|
-
close(focusAfter) {
|
|
14
|
-
if (!this.open) return;
|
|
15
|
-
this.open = false;
|
|
16
|
-
focusAfter?.focus();
|
|
17
|
-
}
|
|
18
67
|
}`}
|
|
19
|
-
class="
|
|
68
|
+
class="h-full min-h-0 w-full min-w-0"
|
|
20
69
|
x-bind:class="loading && 'animate-loading'"
|
|
21
70
|
>
|
|
22
|
-
<div
|
|
71
|
+
<div
|
|
72
|
+
class="group/prose-code not-prose relative h-full min-h-0 w-full max-w-full min-w-0"
|
|
73
|
+
>
|
|
23
74
|
<div
|
|
24
|
-
class="flex-
|
|
75
|
+
class="flex h-full min-h-0 w-full max-w-full min-w-0 flex-col overflow-hidden rounded-xl border border-neutral-200 bg-white shadow-xs"
|
|
25
76
|
>
|
|
26
|
-
<
|
|
27
|
-
class
|
|
28
|
-
"shrink-0 text-xs uppercase font-semibold px-1.5 border py-px rounded-md mr-1.5",
|
|
29
|
-
]}
|
|
30
|
-
x-bind:class="{
|
|
31
|
-
'bg-green-50 text-green-700/70 border-green-700/10': Math.floor(response?.status / 100) === 2,
|
|
32
|
-
'bg-blue-50 text-blue-700/70 border-blue-700/10': Math.floor(response?.status / 100) === 3,
|
|
33
|
-
'bg-amber-50 text-amber-600/80 border-amber-700/10': Math.floor(response?.status / 100) === 4,
|
|
34
|
-
'bg-red-50 text-red-700/70 border-red-700/10': Math.floor(response?.status / 100) === 5
|
|
35
|
-
}"
|
|
36
|
-
x-text="response?.status"
|
|
37
|
-
x-show="response?.status"></span>
|
|
38
|
-
<span class="truncate" x-text="response?.statusText"></span>
|
|
39
|
-
</div>
|
|
40
|
-
<div
|
|
41
|
-
x-data="{
|
|
42
|
-
open: false,
|
|
43
|
-
toggle() {
|
|
44
|
-
if (this.open) {
|
|
45
|
-
return this.close()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
this.$refs.button.focus()
|
|
49
|
-
|
|
50
|
-
this.open = true
|
|
51
|
-
},
|
|
52
|
-
close(focusAfter) {
|
|
53
|
-
if (! this.open) return
|
|
54
|
-
|
|
55
|
-
this.open = false
|
|
56
|
-
|
|
57
|
-
focusAfter && focusAfter.focus()
|
|
58
|
-
}
|
|
59
|
-
}"
|
|
60
|
-
x-on:keydown.escape.prevent.stop="close($refs.button)"
|
|
61
|
-
x-on:focusin.window="! $refs.panel.contains($event.target) && close()"
|
|
62
|
-
x-id="['dropdown-button']"
|
|
63
|
-
class="shrink-0 relative max-w-24 w-full"
|
|
64
|
-
>
|
|
65
|
-
<button
|
|
66
|
-
x-ref="button"
|
|
67
|
-
x-on:click="toggle()"
|
|
68
|
-
:aria-expanded="open"
|
|
69
|
-
:aria-controls="$id('dropdown-button')"
|
|
70
|
-
type="button"
|
|
71
|
-
class="flex items-center justify-between px-3 pt-2 pb-1.5 relative border-x border-t border-neutral-200 rounded-t-xl w-full text-sm font-medium bg-white shadow-xs cursor-pointer after:absolute after:-bottom-[2px] after:inset-x-0 after:z-50 after:h-[3px] after:bg-white"
|
|
77
|
+
<div
|
|
78
|
+
class="flex items-center justify-between gap-2 border-b border-neutral-200 rounded-t-xl bg-neutral-50 inset-shadow-sm inset-shadow-neutral-100/80"
|
|
72
79
|
>
|
|
73
|
-
<
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
</button>
|
|
78
|
-
<!-- Panel -->
|
|
79
|
-
<ul
|
|
80
|
-
x-ref="panel"
|
|
81
|
-
x-show="open"
|
|
82
|
-
x-transition.origin.top.left
|
|
83
|
-
x-on:click.outside="close($refs.button)"
|
|
84
|
-
:id="$id('dropdown-button')"
|
|
85
|
-
x-cloak
|
|
86
|
-
role="menu"
|
|
87
|
-
class="absolute top-full right-2 min-w-28 rounded-lg shadow-xl mt-2 z-10 origin-top-right bg-white py-0.5 outline-none border border-neutral-200"
|
|
88
|
-
>
|
|
89
|
-
<li role="menuitem">
|
|
90
|
-
<button
|
|
91
|
-
x-on:click="select('header')"
|
|
92
|
-
type="button"
|
|
93
|
-
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"
|
|
94
|
-
x-bind:class="selected === 'header' ? 'before:bg-neutral-200/50 text-neutral-900' : 'hover:before:bg-neutral-100/70'"
|
|
95
|
-
>
|
|
96
|
-
Header
|
|
97
|
-
</button>
|
|
98
|
-
</li>
|
|
99
|
-
<li role="menuitem">
|
|
100
|
-
<button
|
|
101
|
-
x-on:click="select('body')"
|
|
102
|
-
type="button"
|
|
103
|
-
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"
|
|
104
|
-
x-bind:class="selected === 'body' ? 'before:bg-neutral-200/50 text-neutral-900' : 'hover:before:bg-neutral-100/70'"
|
|
80
|
+
<div class="min-w-0 flex-1 overflow-hidden rounded-t-xl">
|
|
81
|
+
<div
|
|
82
|
+
x-ref="tabList"
|
|
83
|
+
class="relative flex min-w-0 items-end gap-1 overflow-x-auto px-1 pr-8 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
105
84
|
>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
85
|
+
<div
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
class="pointer-events-none absolute top-1/2 z-0 h-[28px] -translate-y-1/2 rounded-lg border-[0.5px] border-neutral-200 bg-white shadow-xs transition-[left,width,opacity] duration-200 ease-out"
|
|
88
|
+
x-bind:class="pillVisible ? 'opacity-100' : 'opacity-0'"
|
|
89
|
+
x-bind:style="'left:' + pillLeft + 'px;width:' + pillWidth + 'px;'"
|
|
90
|
+
>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<template x-for="tab in tabs" :key="tab.id">
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
x-bind:data-rd-response-tab="tab.id"
|
|
97
|
+
x-on:click="select(tab.id)"
|
|
98
|
+
class="relative z-10 inline-flex h-9 items-center gap-2 border-0 bg-transparent px-3 py-1.5 text-xs font-medium transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer"
|
|
99
|
+
x-bind:class="selected === tab.id ? 'text-neutral-900' : 'text-neutral-600'"
|
|
100
|
+
>
|
|
101
|
+
<span class="whitespace-pre leading-none" x-text="tab.label"
|
|
102
|
+
></span>
|
|
103
|
+
</button>
|
|
104
|
+
</template>
|
|
126
105
|
</div>
|
|
127
|
-
<div class="text-lg text-center text-neutral-700">Send request</div>
|
|
128
|
-
<p class="text-sm text-neutral-500 text-center">
|
|
129
|
-
Send a request to see the response body.
|
|
130
|
-
</p>
|
|
131
|
-
</div>
|
|
132
|
-
<div x-show="response && response.highlightedData">
|
|
133
|
-
<pre
|
|
134
|
-
class="bg-transparent! m-0!"><code class="language-json text-[13px]! font-mono! text-neutral-700!" x-html="response?.highlightedData" /></pre>
|
|
135
106
|
</div>
|
|
136
|
-
|
|
137
|
-
<div x-show="selected === 'header'">
|
|
107
|
+
|
|
138
108
|
<div
|
|
139
|
-
|
|
140
|
-
class="text-sm px-4 py-4 xs:py-8 flex flex-col items-center justify-center"
|
|
109
|
+
class="relative h-9 w-fit max-w-full shrink-0 rounded-tr-xl bg-white"
|
|
141
110
|
>
|
|
142
|
-
<div class="
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
111
|
+
<div class="absolute inset-x-0 -bottom-px h-px bg-white"></div>
|
|
112
|
+
<CodeTabEdge
|
|
113
|
+
className="pointer-events-none absolute -top-px right-full z-10 h-[calc(100%+2px)] rotate-y-180"
|
|
114
|
+
/>
|
|
115
|
+
<div
|
|
116
|
+
class="relative z-20 inline-flex h-9 max-w-full items-center gap-1.5 w-14 pr-5 py-1.5 text-xs font-medium"
|
|
117
|
+
>
|
|
118
|
+
<span
|
|
119
|
+
class="size-[7px] shrink-0 rounded-full"
|
|
120
|
+
x-bind:class="{
|
|
121
|
+
'bg-green-700/70': Math.floor((response?.status || 0) / 100) === 2,
|
|
122
|
+
'bg-blue-700/70': Math.floor((response?.status || 0) / 100) === 3,
|
|
123
|
+
'bg-yellow-500/80': Math.floor((response?.status || 0) / 100) === 4,
|
|
124
|
+
'bg-red-700/70': Math.floor((response?.status || 0) / 100) === 5,
|
|
125
|
+
'bg-neutral-300': !response?.status || Math.floor((response?.status || 0) / 100) < 2
|
|
126
|
+
}"
|
|
127
|
+
></span>
|
|
128
|
+
<span
|
|
129
|
+
class="leading-none"
|
|
130
|
+
x-bind:class="response?.status ? 'text-neutral-700' : 'text-neutral-300'"
|
|
131
|
+
x-text="response?.status ?? '---'"></span>
|
|
147
132
|
</div>
|
|
148
|
-
<div class="text-lg text-center text-neutral-700">Send request</div>
|
|
149
|
-
<p class="text-sm text-neutral-500 text-center">
|
|
150
|
-
Send a request to see the response header.
|
|
151
|
-
</p>
|
|
152
133
|
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div class="relative min-h-0 min-w-0 flex-1 overflow-hidden rounded-b-xl">
|
|
153
137
|
<div
|
|
154
|
-
|
|
155
|
-
class=""
|
|
138
|
+
class="relative h-full overflow-auto [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"
|
|
156
139
|
>
|
|
157
|
-
<
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
140
|
+
<div
|
|
141
|
+
class="pointer-events-none sticky top-0 z-10 -mb-4 h-4 w-full bg-linear-to-b from-white to-transparent"
|
|
142
|
+
>
|
|
143
|
+
</div>
|
|
144
|
+
<div x-show="selected === 'body'" class="h-full">
|
|
145
|
+
<div
|
|
146
|
+
x-show="!response || !response.highlightedData"
|
|
147
|
+
class="text-sm px-4 py-4 xs:py-8 flex flex-col items-center justify-center h-full"
|
|
148
|
+
>
|
|
149
|
+
<div class="bg-neutral-50 p-2 rounded-xl mb-1">
|
|
150
|
+
<Icon
|
|
151
|
+
class="size-6 text-neutral-300"
|
|
152
|
+
name="lucide:square-arrow-up-right"
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="text-lg font-medium text-center">Send request</div>
|
|
156
|
+
<p class="text-sm text-neutral-500 text-center">
|
|
157
|
+
Send a request to see the response body.
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
<div x-show="response && response.highlightedData">
|
|
161
|
+
<pre
|
|
162
|
+
class="relative m-0 min-w-full bg-white p-0 text-[13px] leading-6"><code class="block min-w-full px-4 py-2.5 font-mono text-neutral-800" x-html="response?.highlightedData" /></pre>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<div x-show="selected === 'header'" x-cloak class="h-full">
|
|
167
|
+
<div
|
|
168
|
+
x-show="!response || !response.highlightedHeaders"
|
|
169
|
+
class="text-sm px-4 py-4 xs:py-8 flex flex-col items-center justify-center h-full"
|
|
170
|
+
>
|
|
171
|
+
<div class="bg-neutral-50 p-2 rounded-xl mb-1">
|
|
172
|
+
<Icon
|
|
173
|
+
class="size-6 text-neutral-300"
|
|
174
|
+
name="lucide:square-arrow-up-right"
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="text-lg font-medium text-center">Send request</div>
|
|
178
|
+
<p class="text-sm text-neutral-500 text-center">
|
|
179
|
+
Send a request to see the response header.
|
|
180
|
+
</p>
|
|
181
|
+
</div>
|
|
182
|
+
<div x-show="response && response.highlightedHeaders">
|
|
183
|
+
<pre
|
|
184
|
+
class="relative m-0 min-w-full bg-white p-0 text-[13px] leading-6"><code class="block min-w-full px-4 py-2.5 font-mono text-neutral-800" x-html="response?.highlightedHeaders" /></pre>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
173
187
|
</div>
|
|
174
188
|
</div>
|
|
175
189
|
</div>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Field from "../ui/Field.astro";
|
|
3
|
+
import ListChevronsToggle from "../ui/ListChevronsToggle.astro";
|
|
4
|
+
|
|
5
|
+
interface ResponseFieldVariant {
|
|
6
|
+
label: string;
|
|
7
|
+
fields: ResponseField[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ResponseField {
|
|
11
|
+
name: string;
|
|
12
|
+
required: boolean;
|
|
13
|
+
type: string;
|
|
14
|
+
description: string;
|
|
15
|
+
nested?: ResponseField[];
|
|
16
|
+
enum?: (string | number)[];
|
|
17
|
+
minLength?: number;
|
|
18
|
+
maxLength?: number;
|
|
19
|
+
minimum?: number;
|
|
20
|
+
maximum?: number;
|
|
21
|
+
exclusiveMinimum?: number;
|
|
22
|
+
exclusiveMaximum?: number;
|
|
23
|
+
variants?: ResponseFieldVariant[];
|
|
24
|
+
variantType?: "oneOf" | "anyOf";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
fields: ResponseField[];
|
|
29
|
+
depth?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { fields, depth = 0 } = Astro.props;
|
|
33
|
+
|
|
34
|
+
function getDisplayName(name: string, depth: number): string {
|
|
35
|
+
if (depth <= 0 || !name.includes(".")) return name;
|
|
36
|
+
const segments = name.split(".");
|
|
37
|
+
return segments[segments.length - 1] || name;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function countImmediateChildren(field: ResponseField): number {
|
|
41
|
+
const nestedCount = field.nested ? field.nested.length : 0;
|
|
42
|
+
const variantCount = field.variants
|
|
43
|
+
? field.variants.reduce((sum, variant) => sum + variant.fields.length, 0)
|
|
44
|
+
: 0;
|
|
45
|
+
return nestedCount + variantCount;
|
|
46
|
+
}
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
<div class="flex flex-col gap-4 divide-y divide-neutral-100 *:pb-3 *:last:pb-0">
|
|
50
|
+
{
|
|
51
|
+
fields.map((field) => {
|
|
52
|
+
const revealedFieldCount = countImmediateChildren(field);
|
|
53
|
+
const nestedFieldCount = field.nested ? field.nested.length : 0;
|
|
54
|
+
const hasExpandableContent = revealedFieldCount > 0;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div x-data="{ expanded: false }">
|
|
58
|
+
<Field
|
|
59
|
+
name={getDisplayName(field.name, depth)}
|
|
60
|
+
type={field.type}
|
|
61
|
+
required={field.required}
|
|
62
|
+
description={field.description}
|
|
63
|
+
enum={field.enum}
|
|
64
|
+
minLength={field.minLength}
|
|
65
|
+
maxLength={field.maxLength}
|
|
66
|
+
minimum={field.minimum}
|
|
67
|
+
maximum={field.maximum}
|
|
68
|
+
exclusiveMinimum={field.exclusiveMinimum}
|
|
69
|
+
exclusiveMaximum={field.exclusiveMaximum}
|
|
70
|
+
/>
|
|
71
|
+
{hasExpandableContent && (
|
|
72
|
+
<div
|
|
73
|
+
class="mt-2 w-full overflow-hidden rounded-lg border border-neutral-200 bg-white transition-colors duration-200"
|
|
74
|
+
x-bind:class="expanded ? 'border-neutral-300' : 'border-neutral-200'"
|
|
75
|
+
>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
x-on:click="expanded = !expanded"
|
|
79
|
+
x-bind:aria-expanded="expanded"
|
|
80
|
+
class="group flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-xs font-medium text-neutral-700 hover:bg-neutral-50 cursor-pointer transition-colors duration-200"
|
|
81
|
+
>
|
|
82
|
+
<span class="inline-flex items-center gap-2">
|
|
83
|
+
<ListChevronsToggle class="size-4 shrink-0 text-neutral-400 group-hover:text-neutral-600 transition duration-200" />
|
|
84
|
+
<span>
|
|
85
|
+
{revealedFieldCount}{" "}
|
|
86
|
+
{revealedFieldCount === 1
|
|
87
|
+
? "field"
|
|
88
|
+
: "fields"}
|
|
89
|
+
</span>
|
|
90
|
+
</span>
|
|
91
|
+
</button>
|
|
92
|
+
<div x-show="expanded" x-collapse x-cloak>
|
|
93
|
+
<div class="border-t border-neutral-100 px-3 py-3">
|
|
94
|
+
{field.nested && field.nested.length > 0 && nestedFieldCount > 0 && (
|
|
95
|
+
<Astro.self fields={field.nested} depth={depth + 1} />
|
|
96
|
+
)}
|
|
97
|
+
{field.variants && field.variants.length > 0 && (
|
|
98
|
+
<div class:list={["space-y-2", nestedFieldCount > 0 && "mt-3"]}>
|
|
99
|
+
<p class="text-xs text-neutral-500">
|
|
100
|
+
{field.variantType === "anyOf"
|
|
101
|
+
? "One or more variants may apply."
|
|
102
|
+
: "One of these variants applies."}
|
|
103
|
+
</p>
|
|
104
|
+
{field.variants.map((variant, index) => (
|
|
105
|
+
<>
|
|
106
|
+
<div class="rounded-lg border border-neutral-200 bg-white p-3">
|
|
107
|
+
<div class="mb-2 text-xs font-medium text-neutral-600">
|
|
108
|
+
{variant.label}
|
|
109
|
+
</div>
|
|
110
|
+
<Astro.self fields={variant.fields} depth={depth + 1} />
|
|
111
|
+
</div>
|
|
112
|
+
{field.variantType === "oneOf" &&
|
|
113
|
+
index < field.variants.length - 1 && (
|
|
114
|
+
<div class="flex items-center gap-2 py-0">
|
|
115
|
+
<div class="h-px flex-1 bg-neutral-200" />
|
|
116
|
+
<span class="px-1 text-[10px] uppercase tracking-wide text-neutral-500">
|
|
117
|
+
OR
|
|
118
|
+
</span>
|
|
119
|
+
<div class="h-px flex-1 bg-neutral-200" />
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
</>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
</div>
|