@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,107 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
THEME_STORAGE_KEY,
|
|
4
|
+
applyTheme,
|
|
5
|
+
type Theme,
|
|
6
|
+
} from "../utils/theme-helpers";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ThemeToggle — React island for switching between dark and light mode.
|
|
10
|
+
*
|
|
11
|
+
* Renders as a button with sun/moon icons that crossfade on toggle.
|
|
12
|
+
* Reads initial state from the DOM (set by the FOUC-prevention inline script),
|
|
13
|
+
* toggles .dark class on <html>, and persists preference to localStorage.
|
|
14
|
+
*
|
|
15
|
+
* Uses inline SVGs — no icon library dependency to keep bundle minimal.
|
|
16
|
+
* Imports shared utilities from theme-helpers.ts for consistency.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export function ThemeToggle() {
|
|
20
|
+
const [isDark, setIsDark] = useState(() => {
|
|
21
|
+
// Read initial state from DOM — FOUC script already set .dark class
|
|
22
|
+
if (typeof document !== "undefined") {
|
|
23
|
+
return document.documentElement.classList.contains("dark");
|
|
24
|
+
}
|
|
25
|
+
return true; // SSR fallback: default dark
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Sync with external changes (e.g., other tabs via StorageEvent)
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const handleStorage = (e: StorageEvent) => {
|
|
31
|
+
if (e.key === THEME_STORAGE_KEY) {
|
|
32
|
+
const newTheme: Theme = e.newValue === "light" ? "light" : "dark";
|
|
33
|
+
setIsDark(newTheme === "dark");
|
|
34
|
+
applyTheme(newTheme);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
window.addEventListener("storage", handleStorage);
|
|
38
|
+
return () => window.removeEventListener("storage", handleStorage);
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
const toggleTheme = useCallback(() => {
|
|
42
|
+
// Read from DOM (source of truth) to avoid stale closure issues
|
|
43
|
+
const currentlyDark =
|
|
44
|
+
document.documentElement.classList.contains("dark");
|
|
45
|
+
const newIsDark = !currentlyDark;
|
|
46
|
+
setIsDark(newIsDark);
|
|
47
|
+
applyTheme(newIsDark ? "dark" : "light");
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
onClick={toggleTheme}
|
|
54
|
+
aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
|
|
55
|
+
className="inline-flex items-center justify-center rounded-md p-2 text-text-muted hover:text-(--color-text) hover:bg-hover-bg focus:outline-none focus-visible:ring-2 focus-visible:ring-primary transition-colors"
|
|
56
|
+
>
|
|
57
|
+
{/* Icon container — both icons rendered, crossfade via CSS transitions */}
|
|
58
|
+
<span className="relative inline-flex items-center justify-center w-5 h-5">
|
|
59
|
+
{/* Sun icon — visible in dark mode, click to switch to light */}
|
|
60
|
+
<svg
|
|
61
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
62
|
+
width="20"
|
|
63
|
+
height="20"
|
|
64
|
+
viewBox="0 0 24 24"
|
|
65
|
+
fill="none"
|
|
66
|
+
stroke="currentColor"
|
|
67
|
+
strokeWidth="2"
|
|
68
|
+
strokeLinecap="round"
|
|
69
|
+
strokeLinejoin="round"
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
className={`absolute inset-0 transition-transform duration-300 ${
|
|
72
|
+
isDark ? "rotate-0 scale-100" : "-rotate-90 scale-0"
|
|
73
|
+
}`}
|
|
74
|
+
>
|
|
75
|
+
<circle cx="12" cy="12" r="4" />
|
|
76
|
+
<path d="M12 2v2" />
|
|
77
|
+
<path d="M12 20v2" />
|
|
78
|
+
<path d="m4.93 4.93 1.41 1.41" />
|
|
79
|
+
<path d="m17.66 17.66 1.41 1.41" />
|
|
80
|
+
<path d="M2 12h2" />
|
|
81
|
+
<path d="M20 12h2" />
|
|
82
|
+
<path d="m6.34 17.66-1.41 1.41" />
|
|
83
|
+
<path d="m19.07 4.93-1.41 1.41" />
|
|
84
|
+
</svg>
|
|
85
|
+
|
|
86
|
+
{/* Moon icon — visible in light mode, click to switch to dark */}
|
|
87
|
+
<svg
|
|
88
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
89
|
+
width="20"
|
|
90
|
+
height="20"
|
|
91
|
+
viewBox="0 0 24 24"
|
|
92
|
+
fill="none"
|
|
93
|
+
stroke="currentColor"
|
|
94
|
+
strokeWidth="2"
|
|
95
|
+
strokeLinecap="round"
|
|
96
|
+
strokeLinejoin="round"
|
|
97
|
+
aria-hidden="true"
|
|
98
|
+
className={`absolute inset-0 transition-transform duration-300 ${
|
|
99
|
+
!isDark ? "rotate-0 scale-100" : "rotate-90 scale-0"
|
|
100
|
+
}`}
|
|
101
|
+
>
|
|
102
|
+
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
|
|
103
|
+
</svg>
|
|
104
|
+
</span>
|
|
105
|
+
</button>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ApiReferencePage.astro — Layout for API reference endpoint pages.
|
|
4
|
+
* Mirrors DocPage structure (Header, Sidebar, Footer, mobile menu) but
|
|
5
|
+
* replaces the Table of Contents with API endpoint navigation.
|
|
6
|
+
*/
|
|
7
|
+
import Header from "../components/Header.astro";
|
|
8
|
+
import Footer from "../components/Footer.astro";
|
|
9
|
+
import ApiNavigation from "../components/ApiNavigation.astro";
|
|
10
|
+
import ApiEndpoint from "../components/ApiEndpoint.astro";
|
|
11
|
+
import ApiParameters from "../components/ApiParameters.astro";
|
|
12
|
+
import ApiResponse from "../components/ApiResponse.astro";
|
|
13
|
+
import ApiAuth from "../components/ApiAuth.astro";
|
|
14
|
+
import { ThemeToggle } from "../islands/ThemeToggle";
|
|
15
|
+
import { SearchPalette } from "../islands/SearchPalette";
|
|
16
|
+
import "../styles/global.css";
|
|
17
|
+
import type {
|
|
18
|
+
NavigationTree,
|
|
19
|
+
ApiEndpoint as ApiEndpointType,
|
|
20
|
+
ApiSecurityRequirement,
|
|
21
|
+
SpecglassConfig,
|
|
22
|
+
} from "@specglass/core";
|
|
23
|
+
import { generateThemeCSS } from "../utils/theme-css";
|
|
24
|
+
|
|
25
|
+
export interface Props {
|
|
26
|
+
/** The current endpoint to display */
|
|
27
|
+
endpoint: ApiEndpointType;
|
|
28
|
+
/** Documentation navigation tree */
|
|
29
|
+
navigation: NavigationTree;
|
|
30
|
+
/** Site configuration */
|
|
31
|
+
config: SpecglassConfig;
|
|
32
|
+
/** All endpoints for the sidebar navigation */
|
|
33
|
+
allEndpoints: ApiEndpointType[];
|
|
34
|
+
/** Spec-level information */
|
|
35
|
+
specInfo: {
|
|
36
|
+
title: string;
|
|
37
|
+
version: string;
|
|
38
|
+
};
|
|
39
|
+
/** Global security schemes for the whole spec */
|
|
40
|
+
securitySchemes?: Record<string, ApiSecurityRequirement>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { endpoint, navigation, config, allEndpoints, specInfo, securitySchemes } = Astro.props;
|
|
44
|
+
const themeCSS = generateThemeCSS(config);
|
|
45
|
+
|
|
46
|
+
const pageTitle = `${endpoint.method.toUpperCase()} ${endpoint.path} — ${specInfo.title} API`;
|
|
47
|
+
const pageDescription =
|
|
48
|
+
endpoint.summary ||
|
|
49
|
+
endpoint.description ||
|
|
50
|
+
`API reference for ${endpoint.method.toUpperCase()} ${endpoint.path}`;
|
|
51
|
+
const currentEndpointId = `${endpoint.method}-${endpoint.path}`;
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
<html lang="en" class="dark">
|
|
55
|
+
<head>
|
|
56
|
+
<meta charset="utf-8" />
|
|
57
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
58
|
+
<title>{pageTitle}</title>
|
|
59
|
+
<meta name="description" content={pageDescription} />
|
|
60
|
+
{config.theme?.favicon && <link rel="icon" href={config.theme.favicon} />}
|
|
61
|
+
{config.theme?.socialImage && <meta property="og:image" content={config.theme.socialImage} />}
|
|
62
|
+
{themeCSS && <style set:html={themeCSS} />}
|
|
63
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
64
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
65
|
+
<link
|
|
66
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
67
|
+
rel="stylesheet"
|
|
68
|
+
/>
|
|
69
|
+
<!-- FOUC prevention: apply stored theme before first paint -->
|
|
70
|
+
<script is:inline>
|
|
71
|
+
(function () {
|
|
72
|
+
function applyStoredTheme() {
|
|
73
|
+
var stored = null;
|
|
74
|
+
try {
|
|
75
|
+
stored = localStorage.getItem("specglass-theme");
|
|
76
|
+
} catch (e) {}
|
|
77
|
+
var theme = stored === "dark" || stored === "light" ? stored : "dark";
|
|
78
|
+
if (theme === "dark") {
|
|
79
|
+
document.documentElement.classList.add("dark");
|
|
80
|
+
} else {
|
|
81
|
+
document.documentElement.classList.remove("dark");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
applyStoredTheme();
|
|
85
|
+
if (!window.__sgThemeInit) {
|
|
86
|
+
window.__sgThemeInit = true;
|
|
87
|
+
document.addEventListener("astro:after-swap", applyStoredTheme);
|
|
88
|
+
}
|
|
89
|
+
})();
|
|
90
|
+
</script>
|
|
91
|
+
</head>
|
|
92
|
+
<body class="min-h-screen bg-surface text-text">
|
|
93
|
+
<!-- Skip to content link for accessibility -->
|
|
94
|
+
<a
|
|
95
|
+
href="#main-content"
|
|
96
|
+
class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:bg-primary focus:text-white focus:px-4 focus:py-2 focus:rounded"
|
|
97
|
+
>
|
|
98
|
+
Skip to content
|
|
99
|
+
</a>
|
|
100
|
+
|
|
101
|
+
<Header>
|
|
102
|
+
<SearchPalette client:idle />
|
|
103
|
+
<ThemeToggle client:load />
|
|
104
|
+
</Header>
|
|
105
|
+
|
|
106
|
+
<div class="flex pt-(--height-header)">
|
|
107
|
+
<!-- API Endpoint Navigation (desktop) -->
|
|
108
|
+
<nav
|
|
109
|
+
id="sidebar"
|
|
110
|
+
aria-label="API endpoint navigation"
|
|
111
|
+
class="hidden md:block fixed top-(--height-header) left-0 bottom-0 w-(--width-sidebar) overflow-y-auto border-r border-border bg-sidebar-bg px-4 py-6"
|
|
112
|
+
data-pagefind-ignore
|
|
113
|
+
>
|
|
114
|
+
<!-- Spec title header -->
|
|
115
|
+
<div class="mb-4 px-3">
|
|
116
|
+
<h2 class="text-sm font-semibold text-text truncate">{specInfo.title}</h2>
|
|
117
|
+
<span class="text-xs text-text-muted">v{specInfo.version}</span>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<ApiNavigation
|
|
121
|
+
endpoints={allEndpoints}
|
|
122
|
+
currentEndpointId={currentEndpointId}
|
|
123
|
+
basePath="/api-reference"
|
|
124
|
+
/>
|
|
125
|
+
</nav>
|
|
126
|
+
|
|
127
|
+
<!-- Mobile sidebar overlay -->
|
|
128
|
+
<div id="sidebar-overlay" class="fixed inset-0 bg-black/50 z-30 hidden" aria-hidden="true">
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<!-- Mobile sidebar drawer -->
|
|
132
|
+
<nav
|
|
133
|
+
id="sidebar-mobile"
|
|
134
|
+
aria-label="API endpoint navigation"
|
|
135
|
+
class="fixed top-(--height-header) left-0 bottom-0 w-(--width-sidebar) overflow-y-auto border-r border-border bg-sidebar-bg px-4 py-6 z-40 -translate-x-full transition-transform duration-(--transition-normal) md:hidden"
|
|
136
|
+
>
|
|
137
|
+
<div class="mb-4 px-3">
|
|
138
|
+
<h2 class="text-sm font-semibold text-text truncate">{specInfo.title}</h2>
|
|
139
|
+
<span class="text-xs text-text-muted">v{specInfo.version}</span>
|
|
140
|
+
</div>
|
|
141
|
+
<ApiNavigation
|
|
142
|
+
endpoints={allEndpoints}
|
|
143
|
+
currentEndpointId={currentEndpointId}
|
|
144
|
+
basePath="/api-reference"
|
|
145
|
+
/>
|
|
146
|
+
</nav>
|
|
147
|
+
|
|
148
|
+
<!-- Main content area -->
|
|
149
|
+
<main id="main-content" class="flex-1 min-w-0 md:ml-(--width-sidebar)" data-pagefind-body>
|
|
150
|
+
<article class="max-w-(--width-content-max) mx-auto px-(--spacing-page) py-8">
|
|
151
|
+
<!-- Endpoint header -->
|
|
152
|
+
<ApiEndpoint endpoint={endpoint} />
|
|
153
|
+
|
|
154
|
+
<!-- Authentication -->
|
|
155
|
+
{
|
|
156
|
+
endpoint.security && endpoint.security.length > 0 && (
|
|
157
|
+
<ApiAuth security={endpoint.security} securitySchemes={securitySchemes} />
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
<!-- Parameters & Request Body -->
|
|
162
|
+
{
|
|
163
|
+
(endpoint.parameters.length > 0 || endpoint.requestBody) && (
|
|
164
|
+
<div>
|
|
165
|
+
<h2 class="text-lg font-semibold text-text mb-4 mt-0">Parameters</h2>
|
|
166
|
+
<ApiParameters
|
|
167
|
+
parameters={endpoint.parameters}
|
|
168
|
+
requestBody={endpoint.requestBody}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
<!-- Responses -->
|
|
175
|
+
{
|
|
176
|
+
endpoint.responses.length > 0 && (
|
|
177
|
+
<div>
|
|
178
|
+
<h2 class="text-lg font-semibold text-text mb-4">Responses</h2>
|
|
179
|
+
<ApiResponse responses={endpoint.responses} />
|
|
180
|
+
</div>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
</article>
|
|
184
|
+
</main>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<!-- Mobile menu toggle script -->
|
|
188
|
+
<script>
|
|
189
|
+
function initMobileMenu() {
|
|
190
|
+
const toggle = document.getElementById("mobile-menu-toggle");
|
|
191
|
+
const sidebar = document.getElementById("sidebar-mobile");
|
|
192
|
+
const overlay = document.getElementById("sidebar-overlay");
|
|
193
|
+
const mainContent = document.getElementById("main-content");
|
|
194
|
+
|
|
195
|
+
if (!toggle || !sidebar || !overlay) return;
|
|
196
|
+
|
|
197
|
+
function openMenu() {
|
|
198
|
+
sidebar!.classList.remove("-translate-x-full");
|
|
199
|
+
overlay!.classList.remove("hidden");
|
|
200
|
+
toggle!.setAttribute("aria-expanded", "true");
|
|
201
|
+
document.body.style.overflow = "hidden";
|
|
202
|
+
mainContent?.setAttribute("inert", "");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function closeMenu() {
|
|
206
|
+
sidebar!.classList.add("-translate-x-full");
|
|
207
|
+
overlay!.classList.add("hidden");
|
|
208
|
+
toggle!.setAttribute("aria-expanded", "false");
|
|
209
|
+
document.body.style.overflow = "";
|
|
210
|
+
mainContent?.removeAttribute("inert");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
toggle.addEventListener("click", () => {
|
|
214
|
+
const isOpen = toggle.getAttribute("aria-expanded") === "true";
|
|
215
|
+
if (isOpen) {
|
|
216
|
+
closeMenu();
|
|
217
|
+
} else {
|
|
218
|
+
openMenu();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
overlay.addEventListener("click", closeMenu);
|
|
223
|
+
|
|
224
|
+
document.addEventListener("keydown", (e) => {
|
|
225
|
+
if (e.key === "Escape") closeMenu();
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
initMobileMenu();
|
|
230
|
+
document.addEventListener("astro:after-swap", initMobileMenu);
|
|
231
|
+
</script>
|
|
232
|
+
<!-- Fenced code block copy button enhancer -->
|
|
233
|
+
<script>
|
|
234
|
+
import "../scripts/code-block-enhancer.ts";
|
|
235
|
+
</script>
|
|
236
|
+
|
|
237
|
+
<Footer />
|
|
238
|
+
</body>
|
|
239
|
+
</html>
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Header from "../components/Header.astro";
|
|
3
|
+
import Footer from "../components/Footer.astro";
|
|
4
|
+
import Sidebar from "../components/Sidebar.astro";
|
|
5
|
+
import TableOfContents from "../components/TableOfContents.astro";
|
|
6
|
+
import { ThemeToggle } from "../islands/ThemeToggle";
|
|
7
|
+
import { SearchPalette } from "../islands/SearchPalette";
|
|
8
|
+
import "../styles/global.css";
|
|
9
|
+
import type { NavigationTree } from "@specglass/core";
|
|
10
|
+
import { config } from "virtual:specglass/config";
|
|
11
|
+
import { generateThemeCSS } from "../utils/theme-css";
|
|
12
|
+
|
|
13
|
+
const themeCSS = generateThemeCSS(config);
|
|
14
|
+
|
|
15
|
+
export interface Props {
|
|
16
|
+
/** Page title from frontmatter */
|
|
17
|
+
title: string;
|
|
18
|
+
/** Page description from frontmatter */
|
|
19
|
+
description?: string;
|
|
20
|
+
/** Full frontmatter data */
|
|
21
|
+
frontmatter: Record<string, unknown>;
|
|
22
|
+
/** Navigation tree for sidebar */
|
|
23
|
+
navigation: NavigationTree;
|
|
24
|
+
/** Headings extracted from the page content for ToC */
|
|
25
|
+
headings: Array<{ depth: number; slug: string; text: string }>;
|
|
26
|
+
/** Current page slug for sidebar active state */
|
|
27
|
+
currentSlug: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { title, description, frontmatter, navigation, headings, currentSlug } = Astro.props;
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
<html lang="en" class="dark">
|
|
34
|
+
<head>
|
|
35
|
+
<meta charset="utf-8" />
|
|
36
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
37
|
+
<title>{title}</title>
|
|
38
|
+
{description && <meta name="description" content={description} />}
|
|
39
|
+
{config.theme?.favicon && <link rel="icon" href={config.theme.favicon} />}
|
|
40
|
+
{config.theme?.socialImage && <meta property="og:image" content={config.theme.socialImage} />}
|
|
41
|
+
{themeCSS && <style set:html={themeCSS} />}
|
|
42
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
43
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
44
|
+
<link
|
|
45
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
46
|
+
rel="stylesheet"
|
|
47
|
+
/>
|
|
48
|
+
<!-- FOUC prevention: apply stored theme before first paint -->
|
|
49
|
+
<script is:inline>
|
|
50
|
+
(function () {
|
|
51
|
+
function applyStoredTheme() {
|
|
52
|
+
var stored = null;
|
|
53
|
+
try {
|
|
54
|
+
stored = localStorage.getItem("specglass-theme");
|
|
55
|
+
} catch (e) {}
|
|
56
|
+
var theme = stored === "dark" || stored === "light" ? stored : "dark";
|
|
57
|
+
if (theme === "dark") {
|
|
58
|
+
document.documentElement.classList.add("dark");
|
|
59
|
+
} else {
|
|
60
|
+
document.documentElement.classList.remove("dark");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
applyStoredTheme();
|
|
64
|
+
// Re-apply after Astro ViewTransitions page swap (guard prevents duplicate listeners)
|
|
65
|
+
if (!window.__sgThemeInit) {
|
|
66
|
+
window.__sgThemeInit = true;
|
|
67
|
+
document.addEventListener("astro:after-swap", applyStoredTheme);
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
70
|
+
</script>
|
|
71
|
+
</head>
|
|
72
|
+
<body class="min-h-screen bg-surface text-text">
|
|
73
|
+
<!-- Skip to content link for accessibility -->
|
|
74
|
+
<a
|
|
75
|
+
href="#main-content"
|
|
76
|
+
class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:bg-primary focus:text-white focus:px-4 focus:py-2 focus:rounded"
|
|
77
|
+
>
|
|
78
|
+
Skip to content
|
|
79
|
+
</a>
|
|
80
|
+
|
|
81
|
+
<Header>
|
|
82
|
+
<SearchPalette client:idle />
|
|
83
|
+
<ThemeToggle client:load />
|
|
84
|
+
</Header>
|
|
85
|
+
|
|
86
|
+
<div class="flex pt-(--height-header)">
|
|
87
|
+
<!-- Sidebar navigation -->
|
|
88
|
+
<nav
|
|
89
|
+
id="sidebar"
|
|
90
|
+
aria-label="Documentation navigation"
|
|
91
|
+
class="hidden md:block fixed top-(--height-header) left-0 bottom-0 w-(--width-sidebar) overflow-y-auto border-r border-border bg-sidebar-bg px-4 py-6"
|
|
92
|
+
data-pagefind-ignore
|
|
93
|
+
>
|
|
94
|
+
<Sidebar items={navigation.items} currentSlug={currentSlug} />
|
|
95
|
+
</nav>
|
|
96
|
+
|
|
97
|
+
<!-- Mobile sidebar overlay -->
|
|
98
|
+
<div id="sidebar-overlay" class="fixed inset-0 bg-black/50 z-30 hidden" aria-hidden="true">
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<!-- Mobile sidebar drawer -->
|
|
102
|
+
<nav
|
|
103
|
+
id="sidebar-mobile"
|
|
104
|
+
aria-label="Documentation navigation"
|
|
105
|
+
class="fixed top-(--height-header) left-0 bottom-0 w-(--width-sidebar) overflow-y-auto border-r border-border bg-sidebar-bg px-4 py-6 z-40 -translate-x-full transition-transform duration-(--transition-normal) md:hidden"
|
|
106
|
+
>
|
|
107
|
+
<Sidebar items={navigation.items} currentSlug={currentSlug} />
|
|
108
|
+
</nav>
|
|
109
|
+
|
|
110
|
+
<!-- Main content area -->
|
|
111
|
+
<main
|
|
112
|
+
id="main-content"
|
|
113
|
+
class="flex-1 min-w-0 md:ml-(--width-sidebar) lg:mr-(--width-toc)"
|
|
114
|
+
data-pagefind-body
|
|
115
|
+
>
|
|
116
|
+
<article class="sg-content max-w-(--width-content-max) mx-auto px-(--spacing-page) py-8">
|
|
117
|
+
<h1 class="text-3xl font-bold tracking-tight mb-2">
|
|
118
|
+
{frontmatter.title}
|
|
119
|
+
</h1>
|
|
120
|
+
{
|
|
121
|
+
frontmatter.description && (
|
|
122
|
+
<p class="text-text-muted text-lg mb-8 mt-0">{frontmatter.description}</p>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
<slot />
|
|
126
|
+
</article>
|
|
127
|
+
</main>
|
|
128
|
+
|
|
129
|
+
<!-- Table of Contents -->
|
|
130
|
+
{
|
|
131
|
+
headings.length > 0 && (
|
|
132
|
+
<aside
|
|
133
|
+
aria-label="Table of contents"
|
|
134
|
+
class="hidden lg:block fixed top-(--height-header) right-0 bottom-0 w-(--width-toc) overflow-y-auto py-6 px-4"
|
|
135
|
+
data-pagefind-ignore
|
|
136
|
+
>
|
|
137
|
+
<TableOfContents headings={headings} />
|
|
138
|
+
</aside>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<!-- Mobile menu toggle script -->
|
|
144
|
+
<script>
|
|
145
|
+
function initMobileMenu() {
|
|
146
|
+
const toggle = document.getElementById("mobile-menu-toggle");
|
|
147
|
+
const sidebar = document.getElementById("sidebar-mobile");
|
|
148
|
+
const overlay = document.getElementById("sidebar-overlay");
|
|
149
|
+
const mainContent = document.getElementById("main-content");
|
|
150
|
+
|
|
151
|
+
if (!toggle || !sidebar || !overlay) return;
|
|
152
|
+
|
|
153
|
+
function openMenu() {
|
|
154
|
+
sidebar!.classList.remove("-translate-x-full");
|
|
155
|
+
overlay!.classList.remove("hidden");
|
|
156
|
+
toggle!.setAttribute("aria-expanded", "true");
|
|
157
|
+
document.body.style.overflow = "hidden";
|
|
158
|
+
// Focus trap: make background content inert
|
|
159
|
+
mainContent?.setAttribute("inert", "");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function closeMenu() {
|
|
163
|
+
sidebar!.classList.add("-translate-x-full");
|
|
164
|
+
overlay!.classList.add("hidden");
|
|
165
|
+
toggle!.setAttribute("aria-expanded", "false");
|
|
166
|
+
document.body.style.overflow = "";
|
|
167
|
+
// Remove focus trap
|
|
168
|
+
mainContent?.removeAttribute("inert");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
toggle.addEventListener("click", () => {
|
|
172
|
+
const isOpen = toggle.getAttribute("aria-expanded") === "true";
|
|
173
|
+
if (isOpen) {
|
|
174
|
+
closeMenu();
|
|
175
|
+
} else {
|
|
176
|
+
openMenu();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
overlay.addEventListener("click", closeMenu);
|
|
181
|
+
|
|
182
|
+
// Close on Escape key
|
|
183
|
+
document.addEventListener("keydown", (e) => {
|
|
184
|
+
if (e.key === "Escape") closeMenu();
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Run on initial load and on Astro page transitions
|
|
189
|
+
initMobileMenu();
|
|
190
|
+
document.addEventListener("astro:after-swap", initMobileMenu);
|
|
191
|
+
</script>
|
|
192
|
+
<!-- Fenced code block copy button enhancer -->
|
|
193
|
+
<script>
|
|
194
|
+
import "../scripts/code-block-enhancer.ts";
|
|
195
|
+
</script>
|
|
196
|
+
|
|
197
|
+
<Footer />
|
|
198
|
+
</body>
|
|
199
|
+
</html>
|