@specglass/theme-default 0.0.2 → 0.0.3
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 +0 -1
- package/dist/__tests__/code-tabs.test.js +0 -1
- package/dist/__tests__/copy-button.test.d.ts +0 -1
- package/dist/__tests__/copy-button.test.js +0 -1
- package/dist/__tests__/search-palette.test.d.ts +0 -1
- package/dist/__tests__/search-palette.test.js +0 -1
- package/dist/__tests__/shiki.test.d.ts +0 -1
- package/dist/__tests__/shiki.test.js +0 -1
- package/dist/__tests__/theme-css.test.d.ts +0 -1
- package/dist/__tests__/theme-css.test.js +0 -1
- package/dist/__tests__/theme-helpers.test.d.ts +0 -1
- package/dist/__tests__/theme-helpers.test.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/islands/CodeTabs.d.ts +0 -1
- package/dist/islands/CodeTabs.js +0 -1
- package/dist/islands/CopyButton.d.ts +0 -1
- package/dist/islands/CopyButton.js +0 -1
- package/dist/islands/SearchPalette.d.ts +0 -1
- package/dist/islands/SearchPalette.js +0 -1
- package/dist/islands/SearchResults.d.ts +0 -1
- package/dist/islands/SearchResults.js +0 -1
- package/dist/islands/ThemeToggle.d.ts +0 -1
- package/dist/islands/ThemeToggle.js +0 -1
- package/dist/layouts/DocPage.test.d.ts +0 -1
- package/dist/layouts/DocPage.test.js +0 -1
- package/dist/lib/utils.d.ts +0 -1
- package/dist/lib/utils.js +0 -1
- package/dist/scripts/code-block-enhancer.d.ts +0 -1
- package/dist/scripts/code-block-enhancer.js +0 -1
- package/dist/ui/command.d.ts +0 -1
- package/dist/ui/command.js +0 -1
- package/dist/ui/dialog.d.ts +0 -1
- package/dist/ui/dialog.js +0 -1
- package/dist/utils/parse-highlight-range.d.ts +0 -1
- package/dist/utils/parse-highlight-range.js +0 -1
- package/dist/utils/parse-highlight-range.test.d.ts +0 -1
- package/dist/utils/parse-highlight-range.test.js +0 -1
- package/dist/utils/schema-renderer.d.ts +0 -1
- package/dist/utils/schema-renderer.js +0 -1
- package/dist/utils/schema-renderer.test.d.ts +0 -1
- package/dist/utils/schema-renderer.test.js +0 -1
- package/dist/utils/shiki.d.ts +0 -1
- package/dist/utils/shiki.js +0 -1
- package/dist/utils/sidebar-helpers.d.ts +0 -1
- package/dist/utils/sidebar-helpers.js +0 -1
- package/dist/utils/theme-css.d.ts +0 -1
- package/dist/utils/theme-css.js +0 -1
- package/dist/utils/theme-helpers.d.ts +0 -1
- package/dist/utils/theme-helpers.js +0 -1
- package/dist/utils/toc-helpers.d.ts +0 -1
- package/dist/utils/toc-helpers.js +0 -1
- package/package.json +7 -3
- package/src/components/ApiExampleRequest.astro +179 -0
- package/src/components/ApiExampleResponse.astro +134 -0
- package/src/layouts/ApiReferencePage.astro +8 -0
- package/src/scripts/code-block-enhancer.ts +59 -0
- package/src/ui/command.tsx +183 -0
- package/src/ui/dialog.tsx +133 -0
- package/dist/__tests__/code-tabs.test.d.ts.map +0 -1
- package/dist/__tests__/code-tabs.test.js.map +0 -1
- package/dist/__tests__/copy-button.test.d.ts.map +0 -1
- package/dist/__tests__/copy-button.test.js.map +0 -1
- package/dist/__tests__/search-palette.test.d.ts.map +0 -1
- package/dist/__tests__/search-palette.test.js.map +0 -1
- package/dist/__tests__/shiki.test.d.ts.map +0 -1
- package/dist/__tests__/shiki.test.js.map +0 -1
- package/dist/__tests__/theme-css.test.d.ts.map +0 -1
- package/dist/__tests__/theme-css.test.js.map +0 -1
- package/dist/__tests__/theme-helpers.test.d.ts.map +0 -1
- package/dist/__tests__/theme-helpers.test.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/islands/CodeTabs.d.ts.map +0 -1
- package/dist/islands/CodeTabs.js.map +0 -1
- package/dist/islands/CopyButton.d.ts.map +0 -1
- package/dist/islands/CopyButton.js.map +0 -1
- package/dist/islands/SearchPalette.d.ts.map +0 -1
- package/dist/islands/SearchPalette.js.map +0 -1
- package/dist/islands/SearchResults.d.ts.map +0 -1
- package/dist/islands/SearchResults.js.map +0 -1
- package/dist/islands/ThemeToggle.d.ts.map +0 -1
- package/dist/islands/ThemeToggle.js.map +0 -1
- package/dist/layouts/DocPage.test.d.ts.map +0 -1
- package/dist/layouts/DocPage.test.js.map +0 -1
- package/dist/lib/utils.d.ts.map +0 -1
- package/dist/lib/utils.js.map +0 -1
- package/dist/scripts/code-block-enhancer.d.ts.map +0 -1
- package/dist/scripts/code-block-enhancer.js.map +0 -1
- package/dist/ui/command.d.ts.map +0 -1
- package/dist/ui/command.js.map +0 -1
- package/dist/ui/dialog.d.ts.map +0 -1
- package/dist/ui/dialog.js.map +0 -1
- package/dist/utils/parse-highlight-range.d.ts.map +0 -1
- package/dist/utils/parse-highlight-range.js.map +0 -1
- package/dist/utils/parse-highlight-range.test.d.ts.map +0 -1
- package/dist/utils/parse-highlight-range.test.js.map +0 -1
- package/dist/utils/schema-renderer.d.ts.map +0 -1
- package/dist/utils/schema-renderer.js.map +0 -1
- package/dist/utils/schema-renderer.test.d.ts.map +0 -1
- package/dist/utils/schema-renderer.test.js.map +0 -1
- package/dist/utils/shiki.d.ts.map +0 -1
- package/dist/utils/shiki.js.map +0 -1
- package/dist/utils/sidebar-helpers.d.ts.map +0 -1
- package/dist/utils/sidebar-helpers.js.map +0 -1
- package/dist/utils/theme-css.d.ts.map +0 -1
- package/dist/utils/theme-css.js.map +0 -1
- package/dist/utils/theme-helpers.d.ts.map +0 -1
- package/dist/utils/theme-helpers.js.map +0 -1
- package/dist/utils/toc-helpers.d.ts.map +0 -1
- package/dist/utils/toc-helpers.js.map +0 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -10,4 +10,3 @@ export { getStoredTheme, getEffectiveTheme, applyTheme, THEME_STORAGE_KEY, DEFAU
|
|
|
10
10
|
export { ThemeToggle } from "./islands/ThemeToggle.js";
|
|
11
11
|
export { CopyButton } from "./islands/CopyButton.js";
|
|
12
12
|
export { CodeTabs } from "./islands/CodeTabs.js";
|
|
13
|
-
//# sourceMappingURL=index.js.map
|
package/dist/islands/CodeTabs.js
CHANGED
|
@@ -122,4 +122,3 @@ function CodeTabs({ tabs, syncKey = "sdk-language", ariaLabel = "SDK language ta
|
|
|
122
122
|
return (_jsxs("div", { className: "my-6 border border-border rounded-lg overflow-hidden", children: [_jsx("div", { role: "tablist", "aria-label": ariaLabel, className: "flex border-b border-border bg-surface-raised", onKeyDown: handleKeyDown, children: tabs.map((tab, i) => (_jsx("button", { id: `${groupId}-tab-${i}`, role: "tab", "aria-selected": i === activeIndex, "aria-controls": `${groupId}-panel-${i}`, tabIndex: i === activeIndex ? 0 : -1, className: cn("px-4 py-2 border-b-2 border-transparent text-[0.8125rem] font-medium font-mono", "text-text-muted cursor-pointer whitespace-nowrap bg-transparent", "transition-colors duration-150 hover:text-text", "focus-visible:outline-2 focus-visible:outline-primary focus-visible:-outline-offset-2", i === activeIndex && "text-text border-b-primary"), onClick: () => setTab(i), children: tab.label }, tab.language))) }), tabs.map((tab, i) => (_jsx("div", { id: `${groupId}-panel-${i}`, role: "tabpanel", "aria-labelledby": `${groupId}-tab-${i}`, tabIndex: 0, className: "code-tabs-panel", style: { display: i === activeIndex ? undefined : "none" }, dangerouslySetInnerHTML: { __html: tab.content } }, tab.language)))] }));
|
|
123
123
|
}
|
|
124
124
|
export { CodeTabs };
|
|
125
|
-
//# sourceMappingURL=CodeTabs.js.map
|
|
@@ -51,4 +51,3 @@ export function CopyButton({ code }) {
|
|
|
51
51
|
/* Copy icon */
|
|
52
52
|
_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), _jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] })) }));
|
|
53
53
|
}
|
|
54
|
-
//# sourceMappingURL=CopyButton.js.map
|
|
@@ -106,4 +106,3 @@ export function SearchPalette() {
|
|
|
106
106
|
__html: result.sub_results?.[0]?.excerpt ?? result.excerpt ?? "",
|
|
107
107
|
} })] }, result.id)))] }), (results.length > 0 || query.trim().length > 0) && !isUnavailable && (_jsx("div", { className: "flex items-center justify-between border-t border-border px-3 py-2 text-xs text-text-muted", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("kbd", { className: "rounded border border-border bg-surface-raised px-1 py-0.5 font-mono text-[10px]", children: "\u2191\u2193" }), "navigate"] }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("kbd", { className: "rounded border border-border bg-surface-raised px-1 py-0.5 font-mono text-[10px]", children: "\u21B5" }), "open"] }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("kbd", { className: "rounded border border-border bg-surface-raised px-1 py-0.5 font-mono text-[10px]", children: "esc" }), "close"] })] }) }))] })] }));
|
|
108
108
|
}
|
|
109
|
-
//# sourceMappingURL=SearchPalette.js.map
|
|
@@ -40,4 +40,3 @@ export function ThemeToggle() {
|
|
|
40
40
|
}, []);
|
|
41
41
|
return (_jsx("button", { type: "button", onClick: toggleTheme, "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", 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", children: _jsxs("span", { className: "relative inline-flex items-center justify-center w-5 h-5", children: [_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: `absolute inset-0 transition-transform duration-300 ${isDark ? "rotate-0 scale-100" : "-rotate-90 scale-0"}`, children: [_jsx("circle", { cx: "12", cy: "12", r: "4" }), _jsx("path", { d: "M12 2v2" }), _jsx("path", { d: "M12 20v2" }), _jsx("path", { d: "m4.93 4.93 1.41 1.41" }), _jsx("path", { d: "m17.66 17.66 1.41 1.41" }), _jsx("path", { d: "M2 12h2" }), _jsx("path", { d: "M20 12h2" }), _jsx("path", { d: "m6.34 17.66-1.41 1.41" }), _jsx("path", { d: "m19.07 4.93-1.41 1.41" })] }), _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: `absolute inset-0 transition-transform duration-300 ${!isDark ? "rotate-0 scale-100" : "rotate-90 scale-0"}`, children: _jsx("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }) })] }) }));
|
|
42
42
|
}
|
|
43
|
-
//# sourceMappingURL=ThemeToggle.js.map
|
package/dist/lib/utils.d.ts
CHANGED
package/dist/lib/utils.js
CHANGED
package/dist/ui/command.d.ts
CHANGED
package/dist/ui/command.js
CHANGED
|
@@ -25,4 +25,3 @@ const CommandShortcut = ({ className, ...props }) => {
|
|
|
25
25
|
};
|
|
26
26
|
CommandShortcut.displayName = "CommandShortcut";
|
|
27
27
|
export { Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandShortcut, CommandSeparator, };
|
|
28
|
-
//# sourceMappingURL=command.js.map
|
package/dist/ui/dialog.d.ts
CHANGED
|
@@ -17,4 +17,3 @@ declare const DialogFooter: {
|
|
|
17
17
|
declare const DialogTitle: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
|
|
18
18
|
declare const DialogDescription: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
|
|
19
19
|
export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, };
|
|
20
|
-
//# sourceMappingURL=dialog.d.ts.map
|
package/dist/ui/dialog.js
CHANGED
|
@@ -19,4 +19,3 @@ DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
|
19
19
|
const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Description, { ref: ref, className: cn("text-sm text-text-muted", className), ...props })));
|
|
20
20
|
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
21
21
|
export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, };
|
|
22
|
-
//# sourceMappingURL=dialog.js.map
|
|
@@ -35,4 +35,3 @@ export declare function renderTypeString(schema: ApiSchema | undefined): string;
|
|
|
35
35
|
* suitable for rendering in a table. Only expands 1 level deep for MVP.
|
|
36
36
|
*/
|
|
37
37
|
export declare function flattenSchemaProperties(schema: ApiSchema | undefined, requiredFields?: string[], maxDepth?: number): SchemaProperty[];
|
|
38
|
-
//# sourceMappingURL=schema-renderer.d.ts.map
|
package/dist/utils/shiki.d.ts
CHANGED
package/dist/utils/shiki.js
CHANGED
|
@@ -7,4 +7,3 @@ import type { NavItem } from "@specglass/core";
|
|
|
7
7
|
export declare function filterVisibleItems(items: NavItem[]): NavItem[];
|
|
8
8
|
/** Check if this item or any descendant is the current page. */
|
|
9
9
|
export declare function isActiveOrAncestor(item: NavItem, currentSlug: string): boolean;
|
|
10
|
-
//# sourceMappingURL=sidebar-helpers.d.ts.map
|
package/dist/utils/theme-css.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@specglass/theme-default",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Default theme for Specglass — layouts, components, React islands, and styles",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"./layouts/*": "./src/layouts/*",
|
|
24
24
|
"./lib/*": "./src/lib/*",
|
|
25
25
|
"./styles/*": "./src/styles/*",
|
|
26
|
-
"./utils/*": "./src/utils/*"
|
|
26
|
+
"./utils/*": "./src/utils/*",
|
|
27
|
+
"./ui/*": "./src/ui/*",
|
|
28
|
+
"./scripts/*": "./src/scripts/*"
|
|
27
29
|
},
|
|
28
30
|
"files": [
|
|
29
31
|
"dist",
|
|
@@ -32,7 +34,9 @@
|
|
|
32
34
|
"src/layouts",
|
|
33
35
|
"src/lib",
|
|
34
36
|
"src/styles",
|
|
35
|
-
"src/utils"
|
|
37
|
+
"src/utils",
|
|
38
|
+
"src/ui",
|
|
39
|
+
"src/scripts"
|
|
36
40
|
],
|
|
37
41
|
"scripts": {
|
|
38
42
|
"build": "tsc",
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ApiExampleRequest.astro — Renders multi-language request code examples.
|
|
4
|
+
*
|
|
5
|
+
* Generates cURL, Python, and Node.js request examples from endpoint data.
|
|
6
|
+
* Uses a tab interface with localStorage persistence for language preference.
|
|
7
|
+
* Each tab includes a copy button for the code snippet.
|
|
8
|
+
*/
|
|
9
|
+
import type { ApiEndpoint } from "@specglass/core";
|
|
10
|
+
import { generateCurlExample, generatePythonExample, generateNodeExample } from "@specglass/core";
|
|
11
|
+
import { CopyButton } from "../islands/CopyButton";
|
|
12
|
+
|
|
13
|
+
export interface Props {
|
|
14
|
+
endpoint: ApiEndpoint;
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { endpoint, baseUrl = "https://api.example.com" } = Astro.props;
|
|
19
|
+
|
|
20
|
+
const options = { baseUrl };
|
|
21
|
+
|
|
22
|
+
const languages = [
|
|
23
|
+
{ id: "curl", label: "cURL", code: generateCurlExample(endpoint, options), lang: "bash" },
|
|
24
|
+
{ id: "python", label: "Python", code: generatePythonExample(endpoint, options), lang: "python" },
|
|
25
|
+
{
|
|
26
|
+
id: "node",
|
|
27
|
+
label: "Node.js",
|
|
28
|
+
code: generateNodeExample(endpoint, options),
|
|
29
|
+
lang: "javascript",
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const tabGroupId = `api-req-${endpoint.method}-${endpoint.path.replace(/[{}/]/g, "-")}`;
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
<div class="mt-6">
|
|
37
|
+
<h2 class="text-lg font-semibold text-text mb-4">Request Example</h2>
|
|
38
|
+
|
|
39
|
+
<div
|
|
40
|
+
class="api-code-tabs rounded-lg border border-border overflow-hidden"
|
|
41
|
+
data-tab-group={tabGroupId}
|
|
42
|
+
data-sync-key="api-lang"
|
|
43
|
+
>
|
|
44
|
+
{/* Tab buttons */}
|
|
45
|
+
<div
|
|
46
|
+
class="flex border-b border-border bg-surface-raised"
|
|
47
|
+
role="tablist"
|
|
48
|
+
aria-label="Request language"
|
|
49
|
+
>
|
|
50
|
+
{
|
|
51
|
+
languages.map((lang, i) => (
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
role="tab"
|
|
55
|
+
id={`${tabGroupId}-tab-${lang.id}`}
|
|
56
|
+
aria-controls={`${tabGroupId}-panel-${lang.id}`}
|
|
57
|
+
aria-selected={i === 0 ? "true" : "false"}
|
|
58
|
+
tabindex={i === 0 ? 0 : -1}
|
|
59
|
+
data-lang={lang.id}
|
|
60
|
+
class:list={[
|
|
61
|
+
"px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
|
|
62
|
+
i === 0
|
|
63
|
+
? "text-text border-b-2 border-primary bg-surface"
|
|
64
|
+
: "text-text-muted hover:text-text",
|
|
65
|
+
]}
|
|
66
|
+
>
|
|
67
|
+
{lang.label}
|
|
68
|
+
</button>
|
|
69
|
+
))
|
|
70
|
+
}
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{/* Tab panels */}
|
|
74
|
+
{
|
|
75
|
+
languages.map((lang, i) => (
|
|
76
|
+
<div
|
|
77
|
+
role="tabpanel"
|
|
78
|
+
id={`${tabGroupId}-panel-${lang.id}`}
|
|
79
|
+
aria-labelledby={`${tabGroupId}-tab-${lang.id}`}
|
|
80
|
+
data-lang={lang.id}
|
|
81
|
+
class={i === 0 ? "" : "hidden"}
|
|
82
|
+
>
|
|
83
|
+
<div class="relative group">
|
|
84
|
+
<pre class="overflow-x-auto p-4 bg-[#0d1117] dark:bg-[#0d1117] text-gray-300 text-sm font-mono leading-relaxed m-0">
|
|
85
|
+
<code>{lang.code}</code>
|
|
86
|
+
</pre>
|
|
87
|
+
<div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
88
|
+
<CopyButton client:idle code={lang.code} />
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
))
|
|
93
|
+
}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<script>
|
|
98
|
+
function initApiCodeTabs() {
|
|
99
|
+
const SYNC_KEY = "api-lang";
|
|
100
|
+
const STORAGE_KEY = `specglass-preferred-${SYNC_KEY}`;
|
|
101
|
+
|
|
102
|
+
document.querySelectorAll<HTMLElement>(".api-code-tabs").forEach((container) => {
|
|
103
|
+
const tabs = container.querySelectorAll<HTMLButtonElement>('[role="tab"]');
|
|
104
|
+
const panels = container.querySelectorAll<HTMLElement>('[role="tabpanel"]');
|
|
105
|
+
|
|
106
|
+
// Restore saved preference
|
|
107
|
+
try {
|
|
108
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
109
|
+
if (saved) {
|
|
110
|
+
activateTab(saved);
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// localStorage unavailable
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function activateTab(langId: string) {
|
|
117
|
+
let found = false;
|
|
118
|
+
tabs.forEach((tab) => {
|
|
119
|
+
const isActive = tab.dataset.lang === langId;
|
|
120
|
+
tab.setAttribute("aria-selected", isActive ? "true" : "false");
|
|
121
|
+
tab.tabIndex = isActive ? 0 : -1;
|
|
122
|
+
if (isActive) {
|
|
123
|
+
tab.classList.add("text-text", "border-b-2", "border-primary", "bg-surface");
|
|
124
|
+
tab.classList.remove("text-text-muted");
|
|
125
|
+
found = true;
|
|
126
|
+
} else {
|
|
127
|
+
tab.classList.remove("text-text", "border-b-2", "border-primary", "bg-surface");
|
|
128
|
+
tab.classList.add("text-text-muted");
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
panels.forEach((panel) => {
|
|
132
|
+
panel.classList.toggle("hidden", panel.dataset.lang !== langId);
|
|
133
|
+
});
|
|
134
|
+
return found;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
tabs.forEach((tab) => {
|
|
138
|
+
tab.addEventListener("click", () => {
|
|
139
|
+
const lang = tab.dataset.lang;
|
|
140
|
+
if (!lang) return;
|
|
141
|
+
activateTab(lang);
|
|
142
|
+
// Persist and sync
|
|
143
|
+
try {
|
|
144
|
+
localStorage.setItem(STORAGE_KEY, lang);
|
|
145
|
+
} catch {}
|
|
146
|
+
// Broadcast sync event for other tab groups on the page
|
|
147
|
+
document.dispatchEvent(
|
|
148
|
+
new CustomEvent("specglass-lang-sync", { detail: { syncKey: SYNC_KEY, lang } }),
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Keyboard navigation
|
|
153
|
+
tab.addEventListener("keydown", (e) => {
|
|
154
|
+
const tabList = Array.from(tabs);
|
|
155
|
+
const index = tabList.indexOf(tab);
|
|
156
|
+
let nextIndex = index;
|
|
157
|
+
if (e.key === "ArrowRight") nextIndex = (index + 1) % tabList.length;
|
|
158
|
+
else if (e.key === "ArrowLeft") nextIndex = (index - 1 + tabList.length) % tabList.length;
|
|
159
|
+
else if (e.key === "Home") nextIndex = 0;
|
|
160
|
+
else if (e.key === "End") nextIndex = tabList.length - 1;
|
|
161
|
+
else return;
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
tabList[nextIndex].focus();
|
|
164
|
+
tabList[nextIndex].click();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Listen for sync events from other tab groups
|
|
169
|
+
document.addEventListener("specglass-lang-sync", ((e: CustomEvent) => {
|
|
170
|
+
if (e.detail?.syncKey === SYNC_KEY) {
|
|
171
|
+
activateTab(e.detail.lang);
|
|
172
|
+
}
|
|
173
|
+
}) as EventListener);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
initApiCodeTabs();
|
|
178
|
+
document.addEventListener("astro:after-swap", initApiCodeTabs);
|
|
179
|
+
</script>
|