@specglass/theme-default 0.0.2
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/__tests__/code-tabs.test.d.ts +2 -0
- package/dist/__tests__/code-tabs.test.d.ts.map +1 -0
- package/dist/__tests__/code-tabs.test.js +219 -0
- package/dist/__tests__/code-tabs.test.js.map +1 -0
- package/dist/__tests__/copy-button.test.d.ts +2 -0
- package/dist/__tests__/copy-button.test.d.ts.map +1 -0
- package/dist/__tests__/copy-button.test.js +116 -0
- package/dist/__tests__/copy-button.test.js.map +1 -0
- package/dist/__tests__/search-palette.test.d.ts +2 -0
- package/dist/__tests__/search-palette.test.d.ts.map +1 -0
- package/dist/__tests__/search-palette.test.js +71 -0
- package/dist/__tests__/search-palette.test.js.map +1 -0
- package/dist/__tests__/shiki.test.d.ts +2 -0
- package/dist/__tests__/shiki.test.d.ts.map +1 -0
- package/dist/__tests__/shiki.test.js +37 -0
- package/dist/__tests__/shiki.test.js.map +1 -0
- package/dist/__tests__/theme-css.test.d.ts +2 -0
- package/dist/__tests__/theme-css.test.d.ts.map +1 -0
- package/dist/__tests__/theme-css.test.js +124 -0
- package/dist/__tests__/theme-css.test.js.map +1 -0
- package/dist/__tests__/theme-helpers.test.d.ts +2 -0
- package/dist/__tests__/theme-helpers.test.d.ts.map +1 -0
- package/dist/__tests__/theme-helpers.test.js +81 -0
- package/dist/__tests__/theme-helpers.test.js.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/islands/CodeTabs.d.ts +21 -0
- package/dist/islands/CodeTabs.d.ts.map +1 -0
- package/dist/islands/CodeTabs.js +125 -0
- package/dist/islands/CodeTabs.js.map +1 -0
- package/dist/islands/CopyButton.d.ts +16 -0
- package/dist/islands/CopyButton.d.ts.map +1 -0
- package/dist/islands/CopyButton.js +54 -0
- package/dist/islands/CopyButton.js.map +1 -0
- package/dist/islands/SearchPalette.d.ts +2 -0
- package/dist/islands/SearchPalette.d.ts.map +1 -0
- package/dist/islands/SearchPalette.js +109 -0
- package/dist/islands/SearchPalette.js.map +1 -0
- package/dist/islands/SearchResults.d.ts +2 -0
- package/dist/islands/SearchResults.d.ts.map +1 -0
- package/dist/islands/SearchResults.js +130 -0
- package/dist/islands/SearchResults.js.map +1 -0
- package/dist/islands/ThemeToggle.d.ts +12 -0
- package/dist/islands/ThemeToggle.d.ts.map +1 -0
- package/dist/islands/ThemeToggle.js +43 -0
- package/dist/islands/ThemeToggle.js.map +1 -0
- package/dist/layouts/DocPage.test.d.ts +2 -0
- package/dist/layouts/DocPage.test.d.ts.map +1 -0
- package/dist/layouts/DocPage.test.js +165 -0
- package/dist/layouts/DocPage.test.js.map +1 -0
- package/dist/lib/utils.d.ts +10 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +13 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/scripts/code-block-enhancer.d.ts +16 -0
- package/dist/scripts/code-block-enhancer.d.ts.map +1 -0
- package/dist/scripts/code-block-enhancer.js +55 -0
- package/dist/scripts/code-block-enhancer.js.map +1 -0
- package/dist/ui/command.d.ts +87 -0
- package/dist/ui/command.d.ts.map +1 -0
- package/dist/ui/command.js +28 -0
- package/dist/ui/command.js.map +1 -0
- package/dist/ui/dialog.d.ts +20 -0
- package/dist/ui/dialog.d.ts.map +1 -0
- package/dist/ui/dialog.js +22 -0
- package/dist/ui/dialog.js.map +1 -0
- package/dist/utils/parse-highlight-range.d.ts +12 -0
- package/dist/utils/parse-highlight-range.d.ts.map +1 -0
- package/dist/utils/parse-highlight-range.js +40 -0
- package/dist/utils/parse-highlight-range.js.map +1 -0
- package/dist/utils/parse-highlight-range.test.d.ts +2 -0
- package/dist/utils/parse-highlight-range.test.d.ts.map +1 -0
- package/dist/utils/parse-highlight-range.test.js +32 -0
- package/dist/utils/parse-highlight-range.test.js.map +1 -0
- package/dist/utils/schema-renderer.d.ts +38 -0
- package/dist/utils/schema-renderer.d.ts.map +1 -0
- package/dist/utils/schema-renderer.js +115 -0
- package/dist/utils/schema-renderer.js.map +1 -0
- package/dist/utils/schema-renderer.test.d.ts +2 -0
- package/dist/utils/schema-renderer.test.d.ts.map +1 -0
- package/dist/utils/schema-renderer.test.js +219 -0
- package/dist/utils/schema-renderer.test.js.map +1 -0
- package/dist/utils/shiki.d.ts +20 -0
- package/dist/utils/shiki.d.ts.map +1 -0
- package/dist/utils/shiki.js +84 -0
- package/dist/utils/shiki.js.map +1 -0
- package/dist/utils/sidebar-helpers.d.ts +10 -0
- package/dist/utils/sidebar-helpers.d.ts.map +1 -0
- package/dist/utils/sidebar-helpers.js +14 -0
- package/dist/utils/sidebar-helpers.js.map +1 -0
- package/dist/utils/theme-css.d.ts +21 -0
- package/dist/utils/theme-css.d.ts.map +1 -0
- package/dist/utils/theme-css.js +77 -0
- package/dist/utils/theme-css.js.map +1 -0
- package/dist/utils/theme-helpers.d.ts +28 -0
- package/dist/utils/theme-helpers.d.ts.map +1 -0
- package/dist/utils/theme-helpers.js +55 -0
- package/dist/utils/theme-helpers.js.map +1 -0
- package/dist/utils/toc-helpers.d.ts +12 -0
- package/dist/utils/toc-helpers.d.ts.map +1 -0
- package/dist/utils/toc-helpers.js +9 -0
- package/dist/utils/toc-helpers.js.map +1 -0
- package/package.json +68 -0
- package/src/components/ApiAuth.astro +116 -0
- package/src/components/ApiEndpoint.astro +75 -0
- package/src/components/ApiNavigation.astro +110 -0
- package/src/components/ApiParameters.astro +204 -0
- package/src/components/ApiResponse.astro +144 -0
- package/src/components/Callout.astro +54 -0
- package/src/components/Card.astro +46 -0
- package/src/components/CodeBlock.astro +142 -0
- package/src/components/CodeBlockGroup.astro +196 -0
- package/src/components/CodeTabs.astro +53 -0
- package/src/components/Footer.astro +41 -0
- package/src/components/Header.astro +80 -0
- package/src/components/Sidebar.astro +117 -0
- package/src/components/TabItem.astro +24 -0
- package/src/components/TableOfContents.astro +111 -0
- package/src/components/Tabs.astro +185 -0
- package/src/islands/CodeTabs.tsx +212 -0
- package/src/islands/CopyButton.tsx +101 -0
- package/src/islands/SearchPalette.tsx +307 -0
- package/src/islands/SearchResults.tsx +301 -0
- package/src/islands/ThemeToggle.tsx +107 -0
- package/src/layouts/ApiReferencePage.astro +239 -0
- package/src/layouts/DocPage.astro +199 -0
- package/src/layouts/DocPage.test.ts +183 -0
- package/src/layouts/LandingPage.astro +143 -0
- package/src/lib/utils.ts +13 -0
- package/src/styles/global.css +241 -0
- package/src/utils/parse-highlight-range.test.ts +40 -0
- package/src/utils/parse-highlight-range.ts +41 -0
- package/src/utils/schema-renderer.test.ts +269 -0
- package/src/utils/schema-renderer.ts +152 -0
- package/src/utils/shiki.ts +99 -0
- package/src/utils/sidebar-helpers.ts +24 -0
- package/src/utils/theme-css.ts +101 -0
- package/src/utils/theme-helpers.ts +59 -0
- package/src/utils/toc-helpers.ts +11 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ApiNavigation.astro — Sidebar navigation for API reference endpoints.
|
|
4
|
+
* Groups endpoints by tag and highlights the current endpoint.
|
|
5
|
+
*/
|
|
6
|
+
import type { ApiEndpoint } from "@specglass/core";
|
|
7
|
+
import { buildEndpointSlug, buildEndpointId } from "@specglass/core";
|
|
8
|
+
|
|
9
|
+
export interface Props {
|
|
10
|
+
endpoints: ApiEndpoint[];
|
|
11
|
+
currentEndpointId: string;
|
|
12
|
+
basePath: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { endpoints, currentEndpointId, basePath } = Astro.props;
|
|
16
|
+
|
|
17
|
+
// Group endpoints by tag
|
|
18
|
+
const grouped = new Map<string, ApiEndpoint[]>();
|
|
19
|
+
for (const ep of endpoints) {
|
|
20
|
+
const tag = ep.tags?.[0] ?? "default";
|
|
21
|
+
if (!grouped.has(tag)) grouped.set(tag, []);
|
|
22
|
+
grouped.get(tag)!.push(ep);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const methodBadgeColors: Record<string, string> = {
|
|
26
|
+
get: "bg-emerald-500/20 text-emerald-700 dark:text-emerald-400",
|
|
27
|
+
post: "bg-blue-500/20 text-blue-700 dark:text-blue-400",
|
|
28
|
+
put: "bg-amber-500/20 text-amber-700 dark:text-amber-400",
|
|
29
|
+
patch: "bg-yellow-500/20 text-yellow-700 dark:text-yellow-400",
|
|
30
|
+
delete: "bg-red-500/20 text-red-700 dark:text-red-400",
|
|
31
|
+
options: "bg-purple-500/20 text-purple-700 dark:text-purple-400",
|
|
32
|
+
head: "bg-gray-500/20 text-gray-700 dark:text-gray-400",
|
|
33
|
+
};
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
<nav class="api-nav space-y-4" aria-label="API endpoints">
|
|
37
|
+
{
|
|
38
|
+
Array.from(grouped.entries()).map(([tag, tagEndpoints]) => (
|
|
39
|
+
<div>
|
|
40
|
+
<button
|
|
41
|
+
class="flex w-full items-center gap-2 text-xs font-semibold text-text-muted uppercase tracking-wider px-3 py-2 hover:text-text transition-colors"
|
|
42
|
+
aria-expanded="true"
|
|
43
|
+
data-api-nav-group={tag}
|
|
44
|
+
>
|
|
45
|
+
<svg
|
|
46
|
+
class="h-3 w-3 shrink-0 transition-transform"
|
|
47
|
+
viewBox="0 0 12 12"
|
|
48
|
+
fill="currentColor"
|
|
49
|
+
aria-hidden="true"
|
|
50
|
+
>
|
|
51
|
+
<path d="M4.5 2l4 4-4 4" />
|
|
52
|
+
</svg>
|
|
53
|
+
{tag}
|
|
54
|
+
</button>
|
|
55
|
+
<ul class="ml-3 space-y-0.5" data-api-nav-items={tag}>
|
|
56
|
+
{tagEndpoints.map((ep) => {
|
|
57
|
+
const epId = buildEndpointId(ep);
|
|
58
|
+
const isActive = epId === currentEndpointId;
|
|
59
|
+
const slug = buildEndpointSlug(ep);
|
|
60
|
+
const badgeColor = methodBadgeColors[ep.method.toLowerCase()] ?? methodBadgeColors.get;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<li>
|
|
64
|
+
<a
|
|
65
|
+
href={`${basePath}/${slug}`}
|
|
66
|
+
class:list={[
|
|
67
|
+
"flex items-center gap-2 px-3 py-1.5 rounded-md text-xs transition-colors group",
|
|
68
|
+
isActive
|
|
69
|
+
? "bg-primary/10 text-primary font-medium"
|
|
70
|
+
: "text-text-muted hover:bg-surface-raised hover:text-text",
|
|
71
|
+
]}
|
|
72
|
+
aria-current={isActive ? "page" : undefined}
|
|
73
|
+
>
|
|
74
|
+
<span
|
|
75
|
+
class:list={[
|
|
76
|
+
"inline-flex w-12 justify-center shrink-0 px-1 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider",
|
|
77
|
+
badgeColor,
|
|
78
|
+
]}
|
|
79
|
+
>
|
|
80
|
+
{ep.method.toUpperCase()}
|
|
81
|
+
</span>
|
|
82
|
+
<span class="font-mono truncate">{ep.path}</span>
|
|
83
|
+
</a>
|
|
84
|
+
</li>
|
|
85
|
+
);
|
|
86
|
+
})}
|
|
87
|
+
</ul>
|
|
88
|
+
</div>
|
|
89
|
+
))
|
|
90
|
+
}
|
|
91
|
+
</nav>
|
|
92
|
+
|
|
93
|
+
<script>
|
|
94
|
+
// Collapsible tag groups
|
|
95
|
+
document.querySelectorAll<HTMLButtonElement>("[data-api-nav-group]").forEach((btn) => {
|
|
96
|
+
btn.addEventListener("click", () => {
|
|
97
|
+
const tag = btn.dataset.apiNavGroup!;
|
|
98
|
+
const items = document.querySelector(`[data-api-nav-items="${tag}"]`);
|
|
99
|
+
const arrow = btn.querySelector("svg");
|
|
100
|
+
if (items) {
|
|
101
|
+
const isHidden = items.classList.toggle("hidden");
|
|
102
|
+
btn.setAttribute("aria-expanded", String(!isHidden));
|
|
103
|
+
arrow?.classList.toggle("rotate-90", !isHidden);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
// Default to expanded
|
|
107
|
+
const arrow = btn.querySelector("svg");
|
|
108
|
+
arrow?.classList.add("rotate-90");
|
|
109
|
+
});
|
|
110
|
+
</script>
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ApiParameters.astro — Displays endpoint parameters and request body.
|
|
4
|
+
* Groups parameters by location (path, query, header, cookie) with type rendering.
|
|
5
|
+
*/
|
|
6
|
+
import type { ApiParameter, ApiRequestBody } from "@specglass/core";
|
|
7
|
+
import { renderTypeString, flattenSchemaProperties } from "../utils/schema-renderer";
|
|
8
|
+
|
|
9
|
+
export interface Props {
|
|
10
|
+
parameters: ApiParameter[];
|
|
11
|
+
requestBody?: ApiRequestBody;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { parameters, requestBody } = Astro.props;
|
|
15
|
+
|
|
16
|
+
// Group parameters by location
|
|
17
|
+
const paramGroups: { label: string; key: string; params: ApiParameter[] }[] = [
|
|
18
|
+
{ label: "Path Parameters", key: "path", params: [] },
|
|
19
|
+
{ label: "Query Parameters", key: "query", params: [] },
|
|
20
|
+
{ label: "Header Parameters", key: "header", params: [] },
|
|
21
|
+
{ label: "Cookie Parameters", key: "cookie", params: [] },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
for (const param of parameters) {
|
|
25
|
+
const group = paramGroups.find((g) => g.key === param.in);
|
|
26
|
+
if (group) {
|
|
27
|
+
group.params.push(param);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const nonEmptyGroups = paramGroups.filter((g) => g.params.length > 0);
|
|
32
|
+
const hasParameters = nonEmptyGroups.length > 0;
|
|
33
|
+
const hasRequestBody = !!requestBody;
|
|
34
|
+
|
|
35
|
+
// Process request body content types
|
|
36
|
+
const requestBodyEntries = requestBody?.content ? Object.entries(requestBody.content) : [];
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
(hasParameters || hasRequestBody) && (
|
|
41
|
+
<div class="mb-8">
|
|
42
|
+
{/* Parameter groups */}
|
|
43
|
+
{nonEmptyGroups.map((group) => (
|
|
44
|
+
<div class="mb-6">
|
|
45
|
+
<h3 class="text-sm font-semibold text-text-muted uppercase tracking-wider mb-3">
|
|
46
|
+
{group.label}
|
|
47
|
+
</h3>
|
|
48
|
+
<div class="border border-border rounded-lg overflow-hidden">
|
|
49
|
+
<table class="w-full text-sm">
|
|
50
|
+
<thead>
|
|
51
|
+
<tr class="bg-surface-raised border-b border-border">
|
|
52
|
+
<th class="text-left px-4 py-2.5 text-text-muted font-medium w-1/4">Name</th>
|
|
53
|
+
<th class="text-left px-4 py-2.5 text-text-muted font-medium w-1/5">Type</th>
|
|
54
|
+
<th class="text-left px-4 py-2.5 text-text-muted font-medium w-20">Required</th>
|
|
55
|
+
<th class="text-left px-4 py-2.5 text-text-muted font-medium">Description</th>
|
|
56
|
+
</tr>
|
|
57
|
+
</thead>
|
|
58
|
+
<tbody class="divide-y divide-border">
|
|
59
|
+
{group.params.map((param) => (
|
|
60
|
+
<tr class="hover:bg-surface-raised/50 transition-colors">
|
|
61
|
+
<td class="px-4 py-3">
|
|
62
|
+
<code class="text-xs font-mono text-text font-semibold">{param.name}</code>
|
|
63
|
+
</td>
|
|
64
|
+
<td class="px-4 py-3">
|
|
65
|
+
<code class="text-xs font-mono text-text-muted">
|
|
66
|
+
{renderTypeString(param.schema)}
|
|
67
|
+
</code>
|
|
68
|
+
{param.schema?.enum && param.schema.enum.length > 0 && (
|
|
69
|
+
<div class="mt-1 flex flex-wrap gap-1">
|
|
70
|
+
{param.schema.enum.map((val) => (
|
|
71
|
+
<span class="inline-block px-1.5 py-0.5 text-xs rounded bg-surface-raised text-text-muted font-mono">
|
|
72
|
+
{String(val)}
|
|
73
|
+
</span>
|
|
74
|
+
))}
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</td>
|
|
78
|
+
<td class="px-4 py-3">
|
|
79
|
+
{param.required ? (
|
|
80
|
+
<span class="inline-flex px-2 py-0.5 rounded text-xs font-medium bg-red-500/15 text-red-400 border border-red-500/30">
|
|
81
|
+
required
|
|
82
|
+
</span>
|
|
83
|
+
) : (
|
|
84
|
+
<span class="inline-flex px-2 py-0.5 rounded text-xs font-medium bg-surface-raised text-text-muted">
|
|
85
|
+
optional
|
|
86
|
+
</span>
|
|
87
|
+
)}
|
|
88
|
+
</td>
|
|
89
|
+
<td class="px-4 py-3 text-text-muted text-xs">
|
|
90
|
+
{param.description || "—"}
|
|
91
|
+
{param.example !== undefined && (
|
|
92
|
+
<div class="mt-1 text-text-muted/60">
|
|
93
|
+
Example:{" "}
|
|
94
|
+
<code class="text-xs font-mono">{JSON.stringify(param.example)}</code>
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
</td>
|
|
98
|
+
</tr>
|
|
99
|
+
))}
|
|
100
|
+
</tbody>
|
|
101
|
+
</table>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
|
|
106
|
+
{/* Request Body */}
|
|
107
|
+
{hasRequestBody && (
|
|
108
|
+
<div class="mb-6">
|
|
109
|
+
<h3 class="text-sm font-semibold text-text-muted uppercase tracking-wider mb-3">
|
|
110
|
+
Request Body
|
|
111
|
+
{requestBody!.required && (
|
|
112
|
+
<span class="ml-2 inline-flex px-2 py-0.5 rounded text-xs font-medium bg-red-500/15 text-red-400 border border-red-500/30">
|
|
113
|
+
required
|
|
114
|
+
</span>
|
|
115
|
+
)}
|
|
116
|
+
</h3>
|
|
117
|
+
{requestBody!.description && (
|
|
118
|
+
<p class="text-text-muted text-xs mb-3">{requestBody!.description}</p>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
{requestBodyEntries.map(([contentType, mediaType]) => {
|
|
122
|
+
const bodyProperties = flattenSchemaProperties(
|
|
123
|
+
mediaType.schema,
|
|
124
|
+
mediaType.schema?.required,
|
|
125
|
+
);
|
|
126
|
+
return (
|
|
127
|
+
<div class="mb-4">
|
|
128
|
+
<div class="flex items-center gap-2 mb-2">
|
|
129
|
+
<span class="text-xs font-mono text-text-muted px-2 py-0.5 rounded bg-surface-raised border border-border">
|
|
130
|
+
{contentType}
|
|
131
|
+
</span>
|
|
132
|
+
</div>
|
|
133
|
+
{bodyProperties.length > 0 ? (
|
|
134
|
+
<div class="border border-border rounded-lg overflow-hidden">
|
|
135
|
+
<table class="w-full text-sm">
|
|
136
|
+
<thead>
|
|
137
|
+
<tr class="bg-surface-raised border-b border-border">
|
|
138
|
+
<th class="text-left px-4 py-2.5 text-text-muted font-medium w-1/4">
|
|
139
|
+
Field
|
|
140
|
+
</th>
|
|
141
|
+
<th class="text-left px-4 py-2.5 text-text-muted font-medium w-1/5">
|
|
142
|
+
Type
|
|
143
|
+
</th>
|
|
144
|
+
<th class="text-left px-4 py-2.5 text-text-muted font-medium w-20">
|
|
145
|
+
Required
|
|
146
|
+
</th>
|
|
147
|
+
<th class="text-left px-4 py-2.5 text-text-muted font-medium">
|
|
148
|
+
Description
|
|
149
|
+
</th>
|
|
150
|
+
</tr>
|
|
151
|
+
</thead>
|
|
152
|
+
<tbody class="divide-y divide-border">
|
|
153
|
+
{bodyProperties.map((prop) => (
|
|
154
|
+
<tr class="hover:bg-surface-raised/50 transition-colors">
|
|
155
|
+
<td class:list={["px-4 py-3", prop.depth > 0 && "pl-8"]}>
|
|
156
|
+
<code class="text-xs font-mono text-text font-semibold">
|
|
157
|
+
{prop.depth > 0 && <span class="text-text-muted/40 mr-1">↳</span>}
|
|
158
|
+
{prop.name}
|
|
159
|
+
</code>
|
|
160
|
+
</td>
|
|
161
|
+
<td class="px-4 py-3">
|
|
162
|
+
<code class="text-xs font-mono text-text-muted">{prop.type}</code>
|
|
163
|
+
{prop.enumValues && prop.enumValues.length > 0 && (
|
|
164
|
+
<div class="mt-1 flex flex-wrap gap-1">
|
|
165
|
+
{prop.enumValues.map((val) => (
|
|
166
|
+
<span class="inline-block px-1.5 py-0.5 text-xs rounded bg-surface-raised text-text-muted font-mono">
|
|
167
|
+
{val}
|
|
168
|
+
</span>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
</td>
|
|
173
|
+
<td class="px-4 py-3">
|
|
174
|
+
{prop.required ? (
|
|
175
|
+
<span class="inline-flex px-2 py-0.5 rounded text-xs font-medium bg-red-500/15 text-red-400 border border-red-500/30">
|
|
176
|
+
required
|
|
177
|
+
</span>
|
|
178
|
+
) : (
|
|
179
|
+
<span class="inline-flex px-2 py-0.5 rounded text-xs font-medium bg-surface-raised text-text-muted">
|
|
180
|
+
optional
|
|
181
|
+
</span>
|
|
182
|
+
)}
|
|
183
|
+
</td>
|
|
184
|
+
<td class="px-4 py-3 text-text-muted text-xs">
|
|
185
|
+
{prop.description || "—"}
|
|
186
|
+
</td>
|
|
187
|
+
</tr>
|
|
188
|
+
))}
|
|
189
|
+
</tbody>
|
|
190
|
+
</table>
|
|
191
|
+
</div>
|
|
192
|
+
) : (
|
|
193
|
+
<div class="border border-border rounded-lg p-4 text-text-muted text-xs">
|
|
194
|
+
<code class="font-mono">{renderTypeString(mediaType.schema)}</code>
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
})}
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ApiResponse.astro — Displays endpoint response schemas.
|
|
4
|
+
* Shows status codes with color-coded badges, descriptions, and response body schemas.
|
|
5
|
+
*/
|
|
6
|
+
import type { ApiResponse } from "@specglass/core";
|
|
7
|
+
import { renderTypeString, flattenSchemaProperties } from "../utils/schema-renderer";
|
|
8
|
+
|
|
9
|
+
export interface Props {
|
|
10
|
+
responses: ApiResponse[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { responses } = Astro.props;
|
|
14
|
+
|
|
15
|
+
const statusColors: Record<string, string> = {
|
|
16
|
+
"2": "bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 border-emerald-500/30",
|
|
17
|
+
"3": "bg-blue-500/15 text-blue-700 dark:text-blue-400 border-blue-500/30",
|
|
18
|
+
"4": "bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-500/30",
|
|
19
|
+
"5": "bg-red-500/15 text-red-700 dark:text-red-400 border-red-500/30",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function getStatusColor(code: string): string {
|
|
23
|
+
const firstDigit = code[0];
|
|
24
|
+
return statusColors[firstDigit] ?? statusColors["2"];
|
|
25
|
+
}
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
responses.length > 0 && (
|
|
30
|
+
<div class="mb-8">
|
|
31
|
+
<h3 class="text-sm font-semibold text-text-muted uppercase tracking-wider mb-3">Responses</h3>
|
|
32
|
+
<div class="space-y-4">
|
|
33
|
+
{responses.map((response) => {
|
|
34
|
+
const contentEntries = response.content ? Object.entries(response.content) : [];
|
|
35
|
+
const headerEntries = response.headers ? Object.entries(response.headers) : [];
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div class="border border-border rounded-lg overflow-hidden">
|
|
39
|
+
{/* Status code header */}
|
|
40
|
+
<div class="flex items-center gap-3 px-4 py-3 bg-surface-raised border-b border-border">
|
|
41
|
+
<span
|
|
42
|
+
class:list={[
|
|
43
|
+
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-bold tracking-wider border",
|
|
44
|
+
getStatusColor(response.statusCode),
|
|
45
|
+
]}
|
|
46
|
+
>
|
|
47
|
+
{response.statusCode}
|
|
48
|
+
</span>
|
|
49
|
+
<span class="text-sm text-text-muted">{response.description}</span>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{/* Response body schemas */}
|
|
53
|
+
{contentEntries.map(([contentType, mediaType]) => {
|
|
54
|
+
const bodyProperties = flattenSchemaProperties(
|
|
55
|
+
mediaType.schema,
|
|
56
|
+
mediaType.schema?.required,
|
|
57
|
+
);
|
|
58
|
+
return (
|
|
59
|
+
<div class="px-4 py-3">
|
|
60
|
+
<div class="flex items-center gap-2 mb-2">
|
|
61
|
+
<span class="text-xs font-mono text-text-muted px-2 py-0.5 rounded bg-surface-raised border border-border">
|
|
62
|
+
{contentType}
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
|
+
{bodyProperties.length > 0 ? (
|
|
66
|
+
<table class="w-full text-sm">
|
|
67
|
+
<thead>
|
|
68
|
+
<tr class="border-b border-border/50">
|
|
69
|
+
<th class="text-left px-3 py-2 text-text-muted font-medium text-xs w-1/4">
|
|
70
|
+
Field
|
|
71
|
+
</th>
|
|
72
|
+
<th class="text-left px-3 py-2 text-text-muted font-medium text-xs w-1/5">
|
|
73
|
+
Type
|
|
74
|
+
</th>
|
|
75
|
+
<th class="text-left px-3 py-2 text-text-muted font-medium text-xs">
|
|
76
|
+
Description
|
|
77
|
+
</th>
|
|
78
|
+
</tr>
|
|
79
|
+
</thead>
|
|
80
|
+
<tbody class="divide-y divide-border/30">
|
|
81
|
+
{bodyProperties.map((prop) => (
|
|
82
|
+
<tr>
|
|
83
|
+
<td class:list={["px-3 py-2", prop.depth > 0 && "pl-6"]}>
|
|
84
|
+
<code class="text-xs font-mono text-text">
|
|
85
|
+
{prop.depth > 0 && <span class="text-text-muted/40 mr-1">↳</span>}
|
|
86
|
+
{prop.name}
|
|
87
|
+
</code>
|
|
88
|
+
</td>
|
|
89
|
+
<td class="px-3 py-2">
|
|
90
|
+
<code class="text-xs font-mono text-text-muted">{prop.type}</code>
|
|
91
|
+
</td>
|
|
92
|
+
<td class="px-3 py-2 text-text-muted text-xs">
|
|
93
|
+
{prop.description || "—"}
|
|
94
|
+
</td>
|
|
95
|
+
</tr>
|
|
96
|
+
))}
|
|
97
|
+
</tbody>
|
|
98
|
+
</table>
|
|
99
|
+
) : (
|
|
100
|
+
<div class="text-text-muted text-xs">
|
|
101
|
+
<code class="font-mono">{renderTypeString(mediaType.schema)}</code>
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
})}
|
|
107
|
+
|
|
108
|
+
{/* Response headers */}
|
|
109
|
+
{headerEntries.length > 0 && (
|
|
110
|
+
<div class="px-4 py-3 border-t border-border/50">
|
|
111
|
+
<h4 class="text-xs font-semibold text-text-muted mb-2">Response Headers</h4>
|
|
112
|
+
<table class="w-full text-sm">
|
|
113
|
+
<tbody class="divide-y divide-border/30">
|
|
114
|
+
{headerEntries.map(([headerName, headerParam]) => (
|
|
115
|
+
<tr>
|
|
116
|
+
<td class="px-3 py-2 w-1/3">
|
|
117
|
+
<code class="text-xs font-mono text-text">{headerName}</code>
|
|
118
|
+
</td>
|
|
119
|
+
<td class="px-3 py-2">
|
|
120
|
+
<code class="text-xs font-mono text-text-muted">
|
|
121
|
+
{renderTypeString(headerParam.schema)}
|
|
122
|
+
</code>
|
|
123
|
+
</td>
|
|
124
|
+
<td class="px-3 py-2 text-text-muted text-xs">
|
|
125
|
+
{headerParam.description || "—"}
|
|
126
|
+
</td>
|
|
127
|
+
</tr>
|
|
128
|
+
))}
|
|
129
|
+
</tbody>
|
|
130
|
+
</table>
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
{/* Empty response */}
|
|
135
|
+
{contentEntries.length === 0 && headerEntries.length === 0 && (
|
|
136
|
+
<div class="px-4 py-3 text-text-muted text-xs italic">No response body</div>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
})}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Callout component — renders styled callout boxes for documentation.
|
|
4
|
+
*
|
|
5
|
+
* Supports types: info, warning, danger, tip — each with distinct
|
|
6
|
+
* icon, background color, and left border color.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <Callout type="warning">This is a warning</Callout>
|
|
10
|
+
* <Callout>This is an info callout (default)</Callout>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface Props {
|
|
14
|
+
type?: "info" | "warning" | "danger" | "tip";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { type = "info" } = Astro.props;
|
|
18
|
+
|
|
19
|
+
const icons: Record<string, string> = {
|
|
20
|
+
info: "ℹ️",
|
|
21
|
+
warning: "⚠️",
|
|
22
|
+
danger: "🚨",
|
|
23
|
+
tip: "💡",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const icon = icons[type] ?? icons.info;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Tailwind classes per callout type.
|
|
30
|
+
* Each variant has: background, left-border, and text color.
|
|
31
|
+
* These are all light values — dark mode auto-remaps via design tokens.
|
|
32
|
+
*/
|
|
33
|
+
const typeClasses: Record<string, string> = {
|
|
34
|
+
info: "bg-blue-50 border-l-blue-500 text-blue-900 dark:bg-blue-950/40 dark:text-blue-200",
|
|
35
|
+
warning: "bg-amber-50 border-l-amber-500 text-amber-900 dark:bg-amber-950/40 dark:text-amber-200",
|
|
36
|
+
danger: "bg-red-50 border-l-red-500 text-red-900 dark:bg-red-950/40 dark:text-red-200",
|
|
37
|
+
tip: "bg-emerald-50 border-l-emerald-500 text-emerald-900 dark:bg-emerald-950/40 dark:text-emerald-200",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const classes = typeClasses[type] ?? typeClasses.info;
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
<aside
|
|
44
|
+
role="note"
|
|
45
|
+
class:list={[
|
|
46
|
+
"flex gap-3 px-5 py-4 my-6 border-l-4 rounded-md text-[0.95rem] leading-relaxed",
|
|
47
|
+
classes,
|
|
48
|
+
]}
|
|
49
|
+
>
|
|
50
|
+
<span class="shrink-0 text-xl leading-relaxed">{icon}</span>
|
|
51
|
+
<div class="min-w-0 flex-1 [&>p:first-child]:mt-0 [&>p:last-child]:mb-0">
|
|
52
|
+
<slot />
|
|
53
|
+
</div>
|
|
54
|
+
</aside>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Card component — renders a styled container with optional title, icon, and link.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* <Card title="Getting Started" icon="🚀" href="/docs/getting-started">
|
|
7
|
+
* Learn how to set up your project.
|
|
8
|
+
* </Card>
|
|
9
|
+
*
|
|
10
|
+
* <Card title="Configuration">
|
|
11
|
+
* Configure your docs site.
|
|
12
|
+
* </Card>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface Props {
|
|
16
|
+
title?: string;
|
|
17
|
+
icon?: string;
|
|
18
|
+
href?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { title, icon, href } = Astro.props;
|
|
22
|
+
const Tag = href ? "a" : "div";
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<Tag
|
|
26
|
+
class:list={[
|
|
27
|
+
"block p-5 my-4 border border-border rounded-lg bg-surface transition-[border-color,box-shadow] duration-150",
|
|
28
|
+
href &&
|
|
29
|
+
"no-underline text-inherit cursor-pointer hover:border-primary hover:shadow-sm hover:shadow-primary/10 focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-2",
|
|
30
|
+
]}
|
|
31
|
+
href={href}
|
|
32
|
+
>
|
|
33
|
+
{
|
|
34
|
+
(icon || title) && (
|
|
35
|
+
<div class="flex items-center gap-2 mb-2">
|
|
36
|
+
{icon && <span class="text-xl shrink-0">{icon}</span>}
|
|
37
|
+
{title && <h3 class="m-0 text-base font-semibold text-text">{title}</h3>}
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
<div
|
|
42
|
+
class="text-[0.9375rem] text-text-muted leading-relaxed [&>p:first-child]:mt-0 [&>p:last-child]:mb-0"
|
|
43
|
+
>
|
|
44
|
+
<slot />
|
|
45
|
+
</div>
|
|
46
|
+
</Tag>
|