@opendocsdev/cli 0.2.6 → 0.2.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendocsdev/cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -59,173 +59,3 @@ const showTabs = labels.length > 1;
59
59
  <slot />
60
60
  </div>
61
61
  </code-group>
62
-
63
- <script>
64
- interface CodeGroupSyncEvent extends CustomEvent {
65
- detail: {
66
- syncKey: string;
67
- label: string;
68
- };
69
- }
70
-
71
- class CodeGroupElement extends HTMLElement {
72
- private syncKey: string = "";
73
- private labels: string[] = [];
74
- private panels: HTMLPreElement[] = [];
75
- private boundHandleSync: (e: Event) => void;
76
- private boundHandleStorage: (e: StorageEvent) => void;
77
-
78
- constructor() {
79
- super();
80
- this.boundHandleSync = this.handleSync.bind(this);
81
- this.boundHandleStorage = this.handleStorage.bind(this);
82
- }
83
-
84
- connectedCallback() {
85
- // Parse labels and sync key
86
- const labelsJson = this.dataset.labels;
87
- this.labels = labelsJson ? JSON.parse(labelsJson) : [];
88
- this.syncKey = this.dataset.syncKey || "";
89
-
90
- const panelsContainer = this.querySelector(".panels");
91
- if (!panelsContainer) return;
92
-
93
- // Find all pre elements (code blocks)
94
- this.panels = Array.from(panelsContainer.querySelectorAll(":scope > pre"));
95
- if (this.panels.length === 0) {
96
- this.panels = Array.from(panelsContainer.querySelectorAll("pre"));
97
- }
98
-
99
- if (this.panels.length === 0) return;
100
-
101
- // Initialize panels with data-active attribute
102
- this.initPanels();
103
-
104
- // If only one panel, just mark it active
105
- if (this.panels.length === 1) {
106
- this.panels[0].setAttribute("data-active", "true");
107
- return;
108
- }
109
-
110
- // Add click handlers to server-rendered buttons
111
- this.attachClickHandlers();
112
-
113
- // Restore saved selection from localStorage
114
- this.restoreFromStorage();
115
-
116
- // Listen for sync events from other CodeGroups on the same page
117
- window.addEventListener("codegroup-sync", this.boundHandleSync);
118
-
119
- // Listen for storage events (cross-tab sync)
120
- window.addEventListener("storage", this.boundHandleStorage);
121
- }
122
-
123
- disconnectedCallback() {
124
- window.removeEventListener("codegroup-sync", this.boundHandleSync);
125
- window.removeEventListener("storage", this.boundHandleStorage);
126
- }
127
-
128
- private initPanels() {
129
- this.panels.forEach((panel, index) => {
130
- panel.setAttribute("role", "tabpanel");
131
- panel.setAttribute("data-active", String(index === 0));
132
- });
133
- }
134
-
135
- private attachClickHandlers() {
136
- const buttons = this.querySelectorAll<HTMLButtonElement>(".tabs button");
137
- buttons.forEach((btn) => {
138
- btn.addEventListener("click", () => {
139
- const label = btn.dataset.label;
140
- if (label) {
141
- this.setActiveByLabel(label);
142
- this.broadcastChange(label);
143
- }
144
- });
145
- });
146
- }
147
-
148
- private restoreFromStorage() {
149
- if (!this.syncKey) return;
150
-
151
- try {
152
- const saved = localStorage.getItem(`codegroup:${this.syncKey}`);
153
- if (saved && this.labels.includes(saved)) {
154
- this.setActiveByLabel(saved);
155
- }
156
- } catch {
157
- // localStorage may be unavailable (private browsing, etc.)
158
- }
159
- }
160
-
161
- private handleSync(e: Event) {
162
- const event = e as CodeGroupSyncEvent;
163
- const { syncKey, label } = event.detail;
164
- if (syncKey === this.syncKey && this.labels.includes(label)) {
165
- this.setActiveByLabel(label);
166
- }
167
- }
168
-
169
- private handleStorage(e: StorageEvent) {
170
- if (!e.key?.startsWith("codegroup:")) return;
171
-
172
- const storedSyncKey = e.key.replace("codegroup:", "");
173
- if (storedSyncKey === this.syncKey && e.newValue) {
174
- this.setActiveByLabel(e.newValue);
175
- }
176
- }
177
-
178
- private broadcastChange(label: string) {
179
- if (!this.syncKey) return;
180
-
181
- // Save to localStorage
182
- try {
183
- localStorage.setItem(`codegroup:${this.syncKey}`, label);
184
- } catch {
185
- // localStorage may be unavailable
186
- }
187
-
188
- // Notify other CodeGroups on the same page
189
- window.dispatchEvent(
190
- new CustomEvent("codegroup-sync", {
191
- detail: { syncKey: this.syncKey, label },
192
- })
193
- );
194
- }
195
-
196
- private setActiveByLabel(label: string) {
197
- const index = this.labels.indexOf(label);
198
- if (index >= 0) {
199
- this.setActiveTab(index);
200
- }
201
- }
202
-
203
- private setActiveTab(index: number) {
204
- // Update buttons
205
- const buttons = this.querySelectorAll<HTMLButtonElement>(".tabs button");
206
- buttons.forEach((btn, i) => {
207
- const isActive = i === index;
208
- btn.className = this.getTabClass(isActive);
209
- btn.setAttribute("aria-selected", String(isActive));
210
- btn.tabIndex = isActive ? 0 : -1;
211
- });
212
-
213
- // Update panels
214
- this.panels.forEach((panel, i) => {
215
- panel.setAttribute("data-active", String(i === index));
216
- });
217
- }
218
-
219
- private getTabClass(isActive: boolean): string {
220
- const base =
221
- "px-2 sm:px-3 py-2 text-xs sm:text-sm font-medium transition-colors -mb-px whitespace-nowrap flex-shrink-0";
222
- const active =
223
- "text-[var(--color-foreground)] border-b-2 border-[var(--color-primary)]";
224
- const inactive =
225
- "text-[var(--color-muted)] hover:text-[var(--color-foreground)]";
226
- return `${base} ${isActive ? active : inactive}`;
227
- }
228
- }
229
-
230
- customElements.define("code-group", CodeGroupElement);
231
- </script>
@@ -59,77 +59,3 @@ const showTabs = tabs.length > 1;
59
59
  <slot />
60
60
  </div>
61
61
  </content-tabs>
62
-
63
- <script>
64
- class ContentTabsElement extends HTMLElement {
65
- private panels: HTMLElement[] = [];
66
-
67
- connectedCallback() {
68
- const panelsContainer = this.querySelector(".tabs-panels");
69
- if (!panelsContainer) return;
70
-
71
- this.panels = Array.from(
72
- panelsContainer.querySelectorAll(":scope > .tab-panel")
73
- );
74
-
75
- if (this.panels.length === 0) return;
76
-
77
- // Initialize panels with data-active attribute
78
- this.initPanels();
79
-
80
- // If only one panel, just mark it active
81
- if (this.panels.length === 1) {
82
- this.panels[0].setAttribute("data-active", "true");
83
- return;
84
- }
85
-
86
- // Add click handlers to server-rendered buttons
87
- this.attachClickHandlers();
88
- }
89
-
90
- private initPanels() {
91
- this.panels.forEach((panel, index) => {
92
- panel.setAttribute("role", "tabpanel");
93
- panel.setAttribute("data-active", String(index === 0));
94
- });
95
- }
96
-
97
- private attachClickHandlers() {
98
- const buttons = this.querySelectorAll<HTMLButtonElement>(
99
- ".tabs-nav button"
100
- );
101
- buttons.forEach((btn, index) => {
102
- btn.addEventListener("click", () => this.setActiveTab(index));
103
- });
104
- }
105
-
106
- private setActiveTab(index: number) {
107
- // Update buttons
108
- const buttons = this.querySelectorAll<HTMLButtonElement>(
109
- ".tabs-nav button"
110
- );
111
- buttons.forEach((btn, i) => {
112
- const isActive = i === index;
113
- btn.className = this.getTabClass(isActive);
114
- btn.setAttribute("aria-selected", String(isActive));
115
- btn.tabIndex = isActive ? 0 : -1;
116
- });
117
-
118
- // Update panels
119
- this.panels.forEach((panel, i) => {
120
- panel.setAttribute("data-active", String(i === index));
121
- });
122
- }
123
-
124
- private getTabClass(isActive: boolean): string {
125
- const base =
126
- "px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px";
127
- const active = "border-[var(--color-primary)] text-[var(--color-primary)]";
128
- const inactive =
129
- "border-transparent text-[var(--color-muted)] hover:text-[var(--color-foreground)] hover:border-[var(--color-border)]";
130
- return `${base} ${isActive ? active : inactive}`;
131
- }
132
- }
133
-
134
- customElements.define("content-tabs", ContentTabsElement);
135
- </script>
@@ -0,0 +1,68 @@
1
+ import React from "react";
2
+ import { cn } from "../../lib/utils";
3
+
4
+ interface CodeGroupProps {
5
+ "data-labels"?: string;
6
+ "data-sync-key"?: string;
7
+ className?: string;
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ /**
12
+ * React version of CodeGroup for use in snippets.
13
+ * Renders a <div> with the same class/structure as CodeGroup.astro.
14
+ * Uses <div> instead of <code-group> custom element because React 18 SSR
15
+ * doesn't convert className to class for custom elements.
16
+ * The global interactive-components.ts script handles interactivity via event delegation.
17
+ */
18
+ export function CodeGroup({
19
+ "data-labels": labelsJson,
20
+ "data-sync-key": syncKey,
21
+ className,
22
+ children,
23
+ }: CodeGroupProps) {
24
+ const labels: string[] = labelsJson ? JSON.parse(labelsJson) : [];
25
+ const showTabs = labels.length > 1;
26
+
27
+ return (
28
+ <div
29
+ className={cn(
30
+ "code-group not-prose my-4 rounded-xl border border-[var(--color-border)] overflow-hidden bg-[var(--color-surface-raised)] block",
31
+ className
32
+ )}
33
+ data-sync-key={syncKey}
34
+ data-labels={labelsJson}
35
+ >
36
+ {showTabs && (
37
+ <nav
38
+ className="tabs flex gap-1 px-3 sm:px-4 pt-3 pb-0 border-b border-[var(--color-border)] overflow-x-auto"
39
+ role="tablist"
40
+ aria-label="Code examples"
41
+ >
42
+ {labels.map((label, i) => (
43
+ <button
44
+ key={label}
45
+ type="button"
46
+ role="tab"
47
+ className={cn(
48
+ "px-2 sm:px-3 py-2 text-xs sm:text-sm font-medium transition-colors -mb-px whitespace-nowrap flex-shrink-0",
49
+ i === 0
50
+ ? "text-[var(--color-foreground)] border-b-2 border-[var(--color-primary)]"
51
+ : "text-[var(--color-muted)] hover:text-[var(--color-foreground)]"
52
+ )}
53
+ aria-selected={i === 0 ? true : false}
54
+ tabIndex={i === 0 ? 0 : -1}
55
+ data-index={i}
56
+ data-label={label}
57
+ >
58
+ {label}
59
+ </button>
60
+ ))}
61
+ </nav>
62
+ )}
63
+ <div className="panels">{children}</div>
64
+ </div>
65
+ );
66
+ }
67
+
68
+ export default CodeGroup;
@@ -0,0 +1,25 @@
1
+ import { cn } from "../../lib/utils";
2
+
3
+ interface TabProps {
4
+ label: string;
5
+ icon?: string;
6
+ className?: string;
7
+ children: React.ReactNode;
8
+ }
9
+
10
+ export function Tab({ label, icon, className, children }: TabProps) {
11
+ return (
12
+ <div
13
+ className={cn(
14
+ "tab-panel prose prose-sm dark:prose-invert max-w-none",
15
+ className
16
+ )}
17
+ data-label={label}
18
+ data-icon={icon}
19
+ >
20
+ {children}
21
+ </div>
22
+ );
23
+ }
24
+
25
+ export default Tab;
@@ -0,0 +1,71 @@
1
+ import React from "react";
2
+ import { cn } from "../../lib/utils";
3
+
4
+ interface TabInfo {
5
+ label: string;
6
+ icon?: string;
7
+ }
8
+
9
+ interface TabsProps {
10
+ "data-tabs"?: string;
11
+ className?: string;
12
+ children: React.ReactNode;
13
+ }
14
+
15
+ /**
16
+ * React version of Tabs for use in snippets.
17
+ * Renders a <div> with the same class/structure as Tabs.astro.
18
+ * Uses <div> instead of <content-tabs> custom element because React 18 SSR
19
+ * doesn't convert className to class for custom elements.
20
+ * The global interactive-components.ts script handles interactivity via event delegation.
21
+ */
22
+ export function Tabs({
23
+ "data-tabs": tabsJson,
24
+ className,
25
+ children,
26
+ }: TabsProps) {
27
+ const tabs: TabInfo[] = tabsJson ? JSON.parse(tabsJson) : [];
28
+ const showTabs = tabs.length > 1;
29
+
30
+ return (
31
+ <div
32
+ className={cn("tabs-container my-6 block", className)}
33
+ data-tabs={tabsJson}
34
+ >
35
+ {showTabs && (
36
+ <nav
37
+ className="tabs-nav flex border-b border-[var(--color-border)]"
38
+ role="tablist"
39
+ aria-label="Content tabs"
40
+ >
41
+ {tabs.map((tab, i) => (
42
+ <button
43
+ key={tab.label}
44
+ type="button"
45
+ role="tab"
46
+ className={cn(
47
+ "px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px",
48
+ i === 0
49
+ ? "border-[var(--color-primary)] text-[var(--color-primary)]"
50
+ : "border-transparent text-[var(--color-muted)] hover:text-[var(--color-foreground)] hover:border-[var(--color-border)]"
51
+ )}
52
+ aria-selected={i === 0 ? true : false}
53
+ tabIndex={i === 0 ? 0 : -1}
54
+ data-index={i}
55
+ >
56
+ {tab.icon && (
57
+ <span className="mr-2" aria-hidden="true">
58
+ {tab.icon}
59
+ </span>
60
+ )}
61
+ {tab.label}
62
+ </button>
63
+ ))}
64
+ </nav>
65
+ )}
66
+ <div className="tabs-panels pt-4">{children}</div>
67
+ </div>
68
+ );
69
+ }
70
+
71
+ export default Tabs;
@@ -11,4 +11,7 @@
11
11
  export { Callout } from "./Callout";
12
12
  export { Card } from "./Card";
13
13
  export { CardGroup } from "./CardGroup";
14
+ export { CodeGroup } from "./CodeGroup";
14
15
  export { Steps } from "./Steps";
16
+ export { Tab } from "./Tab";
17
+ export { Tabs } from "./Tabs";
@@ -281,6 +281,9 @@ const { previous: previousPage, next: nextPage } = getPageNavigation(navigation,
281
281
  <!-- Mobile sidebar toggle script -->
282
282
  <script src="../scripts/mobile-sidebar.ts"></script>
283
283
 
284
+ <!-- Interactive components (CodeGroup, Tabs) - global so both Astro and snippet-rendered instances work -->
285
+ <script src="../scripts/interactive-components.ts"></script>
286
+
284
287
  <!-- Search trigger for mobile header -->
285
288
  <script>
286
289
  function initSearchTriggers() {
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Global Web Component definitions for CodeGroup and Tabs.
3
+ * Also includes event-delegation handlers for React-rendered instances
4
+ * (React 18 SSR doesn't convert className→class for custom elements,
5
+ * so React versions render <div> instead).
6
+ *
7
+ * Loaded in the layout so both Astro and React-rendered instances work.
8
+ */
9
+
10
+ // ============================================
11
+ // Shared helpers
12
+ // ============================================
13
+
14
+ const CODE_TAB_BASE =
15
+ "px-2 sm:px-3 py-2 text-xs sm:text-sm font-medium transition-colors -mb-px whitespace-nowrap flex-shrink-0";
16
+ const CODE_TAB_ACTIVE =
17
+ "text-[var(--color-foreground)] border-b-2 border-[var(--color-primary)]";
18
+ const CODE_TAB_INACTIVE =
19
+ "text-[var(--color-muted)] hover:text-[var(--color-foreground)]";
20
+
21
+ const TABS_TAB_BASE =
22
+ "px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px";
23
+ const TABS_TAB_ACTIVE =
24
+ "border-[var(--color-primary)] text-[var(--color-primary)]";
25
+ const TABS_TAB_INACTIVE =
26
+ "border-transparent text-[var(--color-muted)] hover:text-[var(--color-foreground)] hover:border-[var(--color-border)]";
27
+
28
+ function codeTabClass(active: boolean) {
29
+ return `${CODE_TAB_BASE} ${active ? CODE_TAB_ACTIVE : CODE_TAB_INACTIVE}`;
30
+ }
31
+
32
+ function tabsTabClass(active: boolean) {
33
+ return `${TABS_TAB_BASE} ${active ? TABS_TAB_ACTIVE : TABS_TAB_INACTIVE}`;
34
+ }
35
+
36
+ function findPanels(container: Element): HTMLElement[] {
37
+ const pc = container.querySelector(".panels");
38
+ if (!pc) return [];
39
+ let panels = Array.from(pc.querySelectorAll<HTMLElement>(":scope > pre"));
40
+ if (panels.length === 0) {
41
+ panels = Array.from(pc.querySelectorAll<HTMLElement>("pre"));
42
+ }
43
+ return panels;
44
+ }
45
+
46
+ function setCodeGroupActive(
47
+ cg: Element,
48
+ panels: HTMLElement[],
49
+ index: number
50
+ ) {
51
+ cg.querySelectorAll<HTMLButtonElement>(".tabs button").forEach((btn, i) => {
52
+ const active = i === index;
53
+ btn.className = codeTabClass(active);
54
+ btn.setAttribute("aria-selected", String(active));
55
+ btn.tabIndex = active ? 0 : -1;
56
+ });
57
+ panels.forEach((p, i) =>
58
+ p.setAttribute("data-active", String(i === index))
59
+ );
60
+ }
61
+
62
+ function setTabsActive(ct: Element, panels: HTMLElement[], index: number) {
63
+ ct.querySelectorAll<HTMLButtonElement>(".tabs-nav button").forEach(
64
+ (btn, i) => {
65
+ const active = i === index;
66
+ btn.className = tabsTabClass(active);
67
+ btn.setAttribute("aria-selected", String(active));
68
+ btn.tabIndex = active ? 0 : -1;
69
+ }
70
+ );
71
+ panels.forEach((p, i) =>
72
+ p.setAttribute("data-active", String(i === index))
73
+ );
74
+ }
75
+
76
+ // ============================================
77
+ // CodeGroup Web Component (for Astro-rendered <code-group>)
78
+ // ============================================
79
+
80
+ interface CodeGroupSyncEvent extends CustomEvent {
81
+ detail: { syncKey: string; label: string };
82
+ }
83
+
84
+ if (!customElements.get("code-group")) {
85
+ class CodeGroupElement extends HTMLElement {
86
+ private syncKey = "";
87
+ private labels: string[] = [];
88
+ private panels: HTMLElement[] = [];
89
+ private boundSync: (e: Event) => void;
90
+ private boundStorage: (e: StorageEvent) => void;
91
+
92
+ constructor() {
93
+ super();
94
+ this.boundSync = this.handleSync.bind(this);
95
+ this.boundStorage = this.handleStorage.bind(this);
96
+ }
97
+
98
+ connectedCallback() {
99
+ this.labels = this.dataset.labels
100
+ ? JSON.parse(this.dataset.labels)
101
+ : [];
102
+ this.syncKey = this.dataset.syncKey || "";
103
+ this.panels = findPanels(this);
104
+ if (this.panels.length === 0) return;
105
+
106
+ this.panels.forEach((p, i) => {
107
+ p.setAttribute("role", "tabpanel");
108
+ p.setAttribute("data-active", String(i === 0));
109
+ });
110
+
111
+ if (this.panels.length === 1) return;
112
+
113
+ this.querySelectorAll<HTMLButtonElement>(".tabs button").forEach(
114
+ (btn) => {
115
+ btn.addEventListener("click", () => {
116
+ const label = btn.dataset.label;
117
+ if (label) {
118
+ this.activateLabel(label);
119
+ this.broadcast(label);
120
+ }
121
+ });
122
+ }
123
+ );
124
+
125
+ this.restore();
126
+ window.addEventListener("codegroup-sync", this.boundSync);
127
+ window.addEventListener("storage", this.boundStorage);
128
+ }
129
+
130
+ disconnectedCallback() {
131
+ window.removeEventListener("codegroup-sync", this.boundSync);
132
+ window.removeEventListener("storage", this.boundStorage);
133
+ }
134
+
135
+ private activateLabel(label: string) {
136
+ const i = this.labels.indexOf(label);
137
+ if (i >= 0) setCodeGroupActive(this, this.panels, i);
138
+ }
139
+
140
+ private broadcast(label: string) {
141
+ if (!this.syncKey) return;
142
+ try {
143
+ localStorage.setItem(`codegroup:${this.syncKey}`, label);
144
+ } catch {}
145
+ window.dispatchEvent(
146
+ new CustomEvent("codegroup-sync", {
147
+ detail: { syncKey: this.syncKey, label },
148
+ })
149
+ );
150
+ }
151
+
152
+ private restore() {
153
+ if (!this.syncKey) return;
154
+ try {
155
+ const saved = localStorage.getItem(`codegroup:${this.syncKey}`);
156
+ if (saved && this.labels.includes(saved)) this.activateLabel(saved);
157
+ } catch {}
158
+ }
159
+
160
+ private handleSync(e: Event) {
161
+ const { syncKey, label } = (e as CodeGroupSyncEvent).detail;
162
+ if (syncKey === this.syncKey && this.labels.includes(label))
163
+ this.activateLabel(label);
164
+ }
165
+
166
+ private handleStorage(e: StorageEvent) {
167
+ if (!e.key?.startsWith("codegroup:")) return;
168
+ const sk = e.key.replace("codegroup:", "");
169
+ if (sk === this.syncKey && e.newValue) this.activateLabel(e.newValue);
170
+ }
171
+ }
172
+
173
+ customElements.define("code-group", CodeGroupElement);
174
+ }
175
+
176
+ // ============================================
177
+ // Tabs Web Component (for Astro-rendered <content-tabs>)
178
+ // ============================================
179
+
180
+ if (!customElements.get("content-tabs")) {
181
+ class ContentTabsElement extends HTMLElement {
182
+ private panels: HTMLElement[] = [];
183
+
184
+ connectedCallback() {
185
+ const pc = this.querySelector(".tabs-panels");
186
+ if (!pc) return;
187
+ this.panels = Array.from(
188
+ pc.querySelectorAll<HTMLElement>(":scope > .tab-panel")
189
+ );
190
+ if (this.panels.length === 0) return;
191
+
192
+ this.panels.forEach((p, i) => {
193
+ p.setAttribute("role", "tabpanel");
194
+ p.setAttribute("data-active", String(i === 0));
195
+ });
196
+
197
+ if (this.panels.length === 1) return;
198
+
199
+ this.querySelectorAll<HTMLButtonElement>(".tabs-nav button").forEach(
200
+ (btn, index) => {
201
+ btn.addEventListener("click", () =>
202
+ setTabsActive(this, this.panels, index)
203
+ );
204
+ }
205
+ );
206
+ }
207
+ }
208
+
209
+ customElements.define("content-tabs", ContentTabsElement);
210
+ }
211
+
212
+ // ============================================
213
+ // Event delegation for React-rendered instances
214
+ // (div.code-group and div.tabs-container)
215
+ // ============================================
216
+
217
+ function initDivCodeGroups() {
218
+ document
219
+ .querySelectorAll<HTMLElement>(".code-group:not(code-group)")
220
+ .forEach((cg) => {
221
+ if (cg.dataset.cgInit) return;
222
+ cg.dataset.cgInit = "1";
223
+
224
+ const labels: string[] = cg.dataset.labels
225
+ ? JSON.parse(cg.dataset.labels)
226
+ : [];
227
+ const syncKey = cg.dataset.syncKey || "";
228
+ const panels = findPanels(cg);
229
+ if (panels.length === 0) return;
230
+
231
+ panels.forEach((p, i) => {
232
+ p.setAttribute("role", "tabpanel");
233
+ p.setAttribute("data-active", String(i === 0));
234
+ });
235
+ if (panels.length === 1) return;
236
+
237
+ cg.querySelectorAll<HTMLButtonElement>(".tabs button").forEach((btn) => {
238
+ btn.addEventListener("click", () => {
239
+ const label = btn.dataset.label;
240
+ if (!label) return;
241
+ const idx = labels.indexOf(label);
242
+ if (idx < 0) return;
243
+ setCodeGroupActive(cg, panels, idx);
244
+ if (syncKey) {
245
+ try {
246
+ localStorage.setItem(`codegroup:${syncKey}`, label);
247
+ } catch {}
248
+ window.dispatchEvent(
249
+ new CustomEvent("codegroup-sync", {
250
+ detail: { syncKey, label },
251
+ })
252
+ );
253
+ }
254
+ });
255
+ });
256
+
257
+ // Restore from localStorage
258
+ if (syncKey) {
259
+ try {
260
+ const saved = localStorage.getItem(`codegroup:${syncKey}`);
261
+ if (saved && labels.includes(saved)) {
262
+ const idx = labels.indexOf(saved);
263
+ if (idx >= 0) setCodeGroupActive(cg, panels, idx);
264
+ }
265
+ } catch {}
266
+
267
+ // Cross-instance sync
268
+ window.addEventListener("codegroup-sync", (e: Event) => {
269
+ const { syncKey: sk, label } = (e as CodeGroupSyncEvent).detail;
270
+ if (sk === syncKey && labels.includes(label)) {
271
+ const idx = labels.indexOf(label);
272
+ if (idx >= 0) setCodeGroupActive(cg, panels, idx);
273
+ }
274
+ });
275
+
276
+ window.addEventListener("storage", (e: StorageEvent) => {
277
+ if (e.key === `codegroup:${syncKey}` && e.newValue) {
278
+ const idx = labels.indexOf(e.newValue);
279
+ if (idx >= 0) setCodeGroupActive(cg, panels, idx);
280
+ }
281
+ });
282
+ }
283
+ });
284
+ }
285
+
286
+ function initDivTabs() {
287
+ document
288
+ .querySelectorAll<HTMLElement>(".tabs-container:not(content-tabs)")
289
+ .forEach((ct) => {
290
+ if (ct.dataset.tabsInit) return;
291
+ ct.dataset.tabsInit = "1";
292
+
293
+ const pc = ct.querySelector(".tabs-panels");
294
+ if (!pc) return;
295
+ const panels = Array.from(
296
+ pc.querySelectorAll<HTMLElement>(":scope > .tab-panel")
297
+ );
298
+ if (panels.length === 0) return;
299
+
300
+ panels.forEach((p, i) => {
301
+ p.setAttribute("role", "tabpanel");
302
+ p.setAttribute("data-active", String(i === 0));
303
+ });
304
+ if (panels.length === 1) return;
305
+
306
+ ct.querySelectorAll<HTMLButtonElement>(".tabs-nav button").forEach(
307
+ (btn, index) => {
308
+ btn.addEventListener("click", () =>
309
+ setTabsActive(ct, panels, index)
310
+ );
311
+ }
312
+ );
313
+ });
314
+ }
315
+
316
+ function initAll() {
317
+ initDivCodeGroups();
318
+ initDivTabs();
319
+ }
320
+
321
+ if (document.readyState === "loading") {
322
+ document.addEventListener("DOMContentLoaded", initAll);
323
+ } else {
324
+ initAll();
325
+ }
326
+
327
+ // Re-initialize after Astro view transitions
328
+ document.addEventListener("astro:after-swap", initAll);
@@ -415,17 +415,17 @@
415
415
  /* Handle both direct children and nested pre elements (MDX may wrap them) */
416
416
  .code-group .panels > pre:not(:first-child),
417
417
  .code-group .panels pre:not(:first-of-type) {
418
- @apply hidden; /* Hide non-first panels by default (prevents flash before JS) */
418
+ display: none; /* Hide non-first panels by default (prevents flash before JS) */
419
419
  }
420
420
 
421
421
  .code-group .panels > pre[data-active="true"],
422
422
  .code-group .panels pre[data-active="true"] {
423
- @apply block;
423
+ display: block;
424
424
  }
425
425
 
426
426
  .code-group .panels > pre[data-active="false"],
427
427
  .code-group .panels pre[data-active="false"] {
428
- @apply hidden;
428
+ display: none;
429
429
  }
430
430
 
431
431
  /* Tabs component styles - visibility for slotted tab panels */