@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.
Files changed (141) hide show
  1. package/dist/__tests__/code-tabs.test.d.ts +2 -0
  2. package/dist/__tests__/code-tabs.test.d.ts.map +1 -0
  3. package/dist/__tests__/code-tabs.test.js +219 -0
  4. package/dist/__tests__/code-tabs.test.js.map +1 -0
  5. package/dist/__tests__/copy-button.test.d.ts +2 -0
  6. package/dist/__tests__/copy-button.test.d.ts.map +1 -0
  7. package/dist/__tests__/copy-button.test.js +116 -0
  8. package/dist/__tests__/copy-button.test.js.map +1 -0
  9. package/dist/__tests__/search-palette.test.d.ts +2 -0
  10. package/dist/__tests__/search-palette.test.d.ts.map +1 -0
  11. package/dist/__tests__/search-palette.test.js +71 -0
  12. package/dist/__tests__/search-palette.test.js.map +1 -0
  13. package/dist/__tests__/shiki.test.d.ts +2 -0
  14. package/dist/__tests__/shiki.test.d.ts.map +1 -0
  15. package/dist/__tests__/shiki.test.js +37 -0
  16. package/dist/__tests__/shiki.test.js.map +1 -0
  17. package/dist/__tests__/theme-css.test.d.ts +2 -0
  18. package/dist/__tests__/theme-css.test.d.ts.map +1 -0
  19. package/dist/__tests__/theme-css.test.js +124 -0
  20. package/dist/__tests__/theme-css.test.js.map +1 -0
  21. package/dist/__tests__/theme-helpers.test.d.ts +2 -0
  22. package/dist/__tests__/theme-helpers.test.d.ts.map +1 -0
  23. package/dist/__tests__/theme-helpers.test.js +81 -0
  24. package/dist/__tests__/theme-helpers.test.js.map +1 -0
  25. package/dist/index.d.ts +63 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +13 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/islands/CodeTabs.d.ts +21 -0
  30. package/dist/islands/CodeTabs.d.ts.map +1 -0
  31. package/dist/islands/CodeTabs.js +125 -0
  32. package/dist/islands/CodeTabs.js.map +1 -0
  33. package/dist/islands/CopyButton.d.ts +16 -0
  34. package/dist/islands/CopyButton.d.ts.map +1 -0
  35. package/dist/islands/CopyButton.js +54 -0
  36. package/dist/islands/CopyButton.js.map +1 -0
  37. package/dist/islands/SearchPalette.d.ts +2 -0
  38. package/dist/islands/SearchPalette.d.ts.map +1 -0
  39. package/dist/islands/SearchPalette.js +109 -0
  40. package/dist/islands/SearchPalette.js.map +1 -0
  41. package/dist/islands/SearchResults.d.ts +2 -0
  42. package/dist/islands/SearchResults.d.ts.map +1 -0
  43. package/dist/islands/SearchResults.js +130 -0
  44. package/dist/islands/SearchResults.js.map +1 -0
  45. package/dist/islands/ThemeToggle.d.ts +12 -0
  46. package/dist/islands/ThemeToggle.d.ts.map +1 -0
  47. package/dist/islands/ThemeToggle.js +43 -0
  48. package/dist/islands/ThemeToggle.js.map +1 -0
  49. package/dist/layouts/DocPage.test.d.ts +2 -0
  50. package/dist/layouts/DocPage.test.d.ts.map +1 -0
  51. package/dist/layouts/DocPage.test.js +165 -0
  52. package/dist/layouts/DocPage.test.js.map +1 -0
  53. package/dist/lib/utils.d.ts +10 -0
  54. package/dist/lib/utils.d.ts.map +1 -0
  55. package/dist/lib/utils.js +13 -0
  56. package/dist/lib/utils.js.map +1 -0
  57. package/dist/scripts/code-block-enhancer.d.ts +16 -0
  58. package/dist/scripts/code-block-enhancer.d.ts.map +1 -0
  59. package/dist/scripts/code-block-enhancer.js +55 -0
  60. package/dist/scripts/code-block-enhancer.js.map +1 -0
  61. package/dist/ui/command.d.ts +87 -0
  62. package/dist/ui/command.d.ts.map +1 -0
  63. package/dist/ui/command.js +28 -0
  64. package/dist/ui/command.js.map +1 -0
  65. package/dist/ui/dialog.d.ts +20 -0
  66. package/dist/ui/dialog.d.ts.map +1 -0
  67. package/dist/ui/dialog.js +22 -0
  68. package/dist/ui/dialog.js.map +1 -0
  69. package/dist/utils/parse-highlight-range.d.ts +12 -0
  70. package/dist/utils/parse-highlight-range.d.ts.map +1 -0
  71. package/dist/utils/parse-highlight-range.js +40 -0
  72. package/dist/utils/parse-highlight-range.js.map +1 -0
  73. package/dist/utils/parse-highlight-range.test.d.ts +2 -0
  74. package/dist/utils/parse-highlight-range.test.d.ts.map +1 -0
  75. package/dist/utils/parse-highlight-range.test.js +32 -0
  76. package/dist/utils/parse-highlight-range.test.js.map +1 -0
  77. package/dist/utils/schema-renderer.d.ts +38 -0
  78. package/dist/utils/schema-renderer.d.ts.map +1 -0
  79. package/dist/utils/schema-renderer.js +115 -0
  80. package/dist/utils/schema-renderer.js.map +1 -0
  81. package/dist/utils/schema-renderer.test.d.ts +2 -0
  82. package/dist/utils/schema-renderer.test.d.ts.map +1 -0
  83. package/dist/utils/schema-renderer.test.js +219 -0
  84. package/dist/utils/schema-renderer.test.js.map +1 -0
  85. package/dist/utils/shiki.d.ts +20 -0
  86. package/dist/utils/shiki.d.ts.map +1 -0
  87. package/dist/utils/shiki.js +84 -0
  88. package/dist/utils/shiki.js.map +1 -0
  89. package/dist/utils/sidebar-helpers.d.ts +10 -0
  90. package/dist/utils/sidebar-helpers.d.ts.map +1 -0
  91. package/dist/utils/sidebar-helpers.js +14 -0
  92. package/dist/utils/sidebar-helpers.js.map +1 -0
  93. package/dist/utils/theme-css.d.ts +21 -0
  94. package/dist/utils/theme-css.d.ts.map +1 -0
  95. package/dist/utils/theme-css.js +77 -0
  96. package/dist/utils/theme-css.js.map +1 -0
  97. package/dist/utils/theme-helpers.d.ts +28 -0
  98. package/dist/utils/theme-helpers.d.ts.map +1 -0
  99. package/dist/utils/theme-helpers.js +55 -0
  100. package/dist/utils/theme-helpers.js.map +1 -0
  101. package/dist/utils/toc-helpers.d.ts +12 -0
  102. package/dist/utils/toc-helpers.d.ts.map +1 -0
  103. package/dist/utils/toc-helpers.js +9 -0
  104. package/dist/utils/toc-helpers.js.map +1 -0
  105. package/package.json +68 -0
  106. package/src/components/ApiAuth.astro +116 -0
  107. package/src/components/ApiEndpoint.astro +75 -0
  108. package/src/components/ApiNavigation.astro +110 -0
  109. package/src/components/ApiParameters.astro +204 -0
  110. package/src/components/ApiResponse.astro +144 -0
  111. package/src/components/Callout.astro +54 -0
  112. package/src/components/Card.astro +46 -0
  113. package/src/components/CodeBlock.astro +142 -0
  114. package/src/components/CodeBlockGroup.astro +196 -0
  115. package/src/components/CodeTabs.astro +53 -0
  116. package/src/components/Footer.astro +41 -0
  117. package/src/components/Header.astro +80 -0
  118. package/src/components/Sidebar.astro +117 -0
  119. package/src/components/TabItem.astro +24 -0
  120. package/src/components/TableOfContents.astro +111 -0
  121. package/src/components/Tabs.astro +185 -0
  122. package/src/islands/CodeTabs.tsx +212 -0
  123. package/src/islands/CopyButton.tsx +101 -0
  124. package/src/islands/SearchPalette.tsx +307 -0
  125. package/src/islands/SearchResults.tsx +301 -0
  126. package/src/islands/ThemeToggle.tsx +107 -0
  127. package/src/layouts/ApiReferencePage.astro +239 -0
  128. package/src/layouts/DocPage.astro +199 -0
  129. package/src/layouts/DocPage.test.ts +183 -0
  130. package/src/layouts/LandingPage.astro +143 -0
  131. package/src/lib/utils.ts +13 -0
  132. package/src/styles/global.css +241 -0
  133. package/src/utils/parse-highlight-range.test.ts +40 -0
  134. package/src/utils/parse-highlight-range.ts +41 -0
  135. package/src/utils/schema-renderer.test.ts +269 -0
  136. package/src/utils/schema-renderer.ts +152 -0
  137. package/src/utils/shiki.ts +99 -0
  138. package/src/utils/sidebar-helpers.ts +24 -0
  139. package/src/utils/theme-css.ts +101 -0
  140. package/src/utils/theme-helpers.ts +59 -0
  141. 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>