@sampleapp.ai/sdk 1.0.34 → 1.0.36
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/components/sandbox/Sandbox.js +15 -8
- package/dist/components/sandbox/guardian/ask-ai-view.js +4 -4
- package/dist/components/sandbox/guardian/code-focus-section.js +166 -28
- package/dist/components/sandbox/guardian/default-guide-view.js +1 -1
- package/dist/components/sandbox/guardian/demo/left-view.js +2 -2
- package/dist/components/sandbox/guardian/guardian-playground.js +2 -2
- package/dist/components/sandbox/guardian/guardian-style-wrapper.js +21 -2
- package/dist/components/sandbox/guardian/ide/browser.js +30 -30
- package/dist/components/sandbox/guardian/right-view/preview-control-bar.js +3 -3
- package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +1 -1
- package/dist/components/sandbox/guardian/right-view/simplified-editor.js +7 -7
- package/dist/components/sandbox/guardian/types/ide-types.js +0 -1
- package/dist/components/sandbox/guardian/ui/download-and-open-buttons.js +1 -1
- package/dist/components/sandbox/guardian/ui/markdown/code-group/code-block.js +48 -11
- package/dist/components/sandbox/guardian/ui/markdown.js +1 -1
- package/dist/components/sandbox/sandbox-home/SandboxCard.js +4 -4
- package/dist/components/sandbox/sandbox-home/SandboxHome.js +9 -9
- package/dist/components/sandbox/sandbox-home/SearchBar.js +2 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.es.js +10128 -19154
- package/dist/index.standalone.umd.js +11 -43
- package/dist/index.umd.js +99 -127
- package/dist/lib/generated-css.js +1 -1
- package/dist/lib/ssr-safe-decode-entity.js +16 -0
- package/dist/lib/ssr-safe-decode-named-character-reference.js +257 -0
- package/dist/sdk.css +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +3 -3
|
@@ -24,9 +24,16 @@ import { Skeleton } from "../ui/skeleton";
|
|
|
24
24
|
* apiKey={process.env.NEXT_PUBLIC_SAMPLEAPP_API_KEY!}
|
|
25
25
|
* sandboxId="launchdarkly-feature-flags"
|
|
26
26
|
* />
|
|
27
|
+
*
|
|
28
|
+
* // Light mode
|
|
29
|
+
* <Sandbox
|
|
30
|
+
* apiKey={process.env.NEXT_PUBLIC_SAMPLEAPP_API_KEY!}
|
|
31
|
+
* sandboxId="launchdarkly-feature-flags"
|
|
32
|
+
* theme="light"
|
|
33
|
+
* />
|
|
27
34
|
* ```
|
|
28
35
|
*/
|
|
29
|
-
export default function Sandbox({ apiKey, sandboxId, env, themeColor, }) {
|
|
36
|
+
export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "dark", }) {
|
|
30
37
|
var _a;
|
|
31
38
|
const [config, setConfig] = useState(null);
|
|
32
39
|
const [loading, setLoading] = useState(true);
|
|
@@ -61,11 +68,11 @@ export default function Sandbox({ apiKey, sandboxId, env, themeColor, }) {
|
|
|
61
68
|
};
|
|
62
69
|
}, [apiKey, sandboxId, themeColor]);
|
|
63
70
|
if (loading) {
|
|
64
|
-
return (React.createElement("div", { className: "h-screen w-screen bg-black" },
|
|
65
|
-
React.createElement(Skeleton, { className: "w-full h-full bg-zinc-900" })));
|
|
71
|
+
return (React.createElement("div", { className: "h-screen w-screen bg-white dark:bg-black" },
|
|
72
|
+
React.createElement(Skeleton, { className: "w-full h-full bg-zinc-100 dark:bg-zinc-900" })));
|
|
66
73
|
}
|
|
67
74
|
if (error) {
|
|
68
|
-
return (React.createElement("div", { className: "flex items-center justify-center h-screen bg-black" },
|
|
75
|
+
return (React.createElement("div", { className: "flex items-center justify-center h-screen bg-white dark:bg-black" },
|
|
69
76
|
React.createElement("div", { className: "text-red-500" },
|
|
70
77
|
"Error: ",
|
|
71
78
|
error)));
|
|
@@ -76,19 +83,19 @@ export default function Sandbox({ apiKey, sandboxId, env, themeColor, }) {
|
|
|
76
83
|
// Build the nested config from use cases
|
|
77
84
|
const nestedConfig = buildGuardianConfig(config.useCases, {
|
|
78
85
|
playgroundUid: sandboxId,
|
|
79
|
-
playgroundLogo: config.playgroundLogo || (React.createElement("div", { className: "text-white font-bold text-xl" }, config.name)),
|
|
86
|
+
playgroundLogo: config.playgroundLogo || (React.createElement("div", { className: "text-zinc-900 dark:text-white font-bold text-xl" }, config.name)),
|
|
80
87
|
});
|
|
81
88
|
// Get the first use case and framework as defaults
|
|
82
89
|
const firstUseCase = config.useCases[0];
|
|
83
90
|
const firstFramework = (_a = firstUseCase === null || firstUseCase === void 0 ? void 0 : firstUseCase.frameworks[0]) === null || _a === void 0 ? void 0 : _a.key;
|
|
84
91
|
if (!firstUseCase || !firstFramework) {
|
|
85
|
-
return (React.createElement("div", { className: "flex items-center justify-center h-screen bg-black" },
|
|
92
|
+
return (React.createElement("div", { className: "flex items-center justify-center h-screen bg-white dark:bg-black" },
|
|
86
93
|
React.createElement("div", { className: "text-red-500" }, "No use cases or frameworks found")));
|
|
87
94
|
}
|
|
88
95
|
// TODO: Pass env variables to the container runtime
|
|
89
96
|
// This will be implemented when integrating with the container technology
|
|
90
97
|
return (React.createElement(GuardianProvider, null,
|
|
91
98
|
React.createElement(VmProvider, null,
|
|
92
|
-
React.createElement("div", { className: "h-screen w-screen bg-black" },
|
|
93
|
-
React.createElement(GuardianPlayground, { nestedConfig: nestedConfig, useCase: firstUseCase.id, framework: firstFramework, isFrame: false, apiKey: apiKey, env: env, chatUid: config.chatUid })))));
|
|
99
|
+
React.createElement("div", { className: "h-screen w-screen bg-white dark:bg-black" },
|
|
100
|
+
React.createElement(GuardianPlayground, { nestedConfig: nestedConfig, useCase: firstUseCase.id, framework: firstFramework, isFrame: false, apiKey: apiKey, env: env, chatUid: config.chatUid, theme: theme })))));
|
|
94
101
|
}
|
|
@@ -181,12 +181,12 @@ export default function AskAiView({ codeZipFile, playgroundUid, themeColor, }) {
|
|
|
181
181
|
React.createElement("span", { className: "text-muted-foreground text-sm" }, "Thinking...")))))) : (React.createElement("div", { className: "flex h-full items-center justify-center" },
|
|
182
182
|
React.createElement("div", { className: "flex flex-col items-start space-y-4 px-4 max-w-4xl" },
|
|
183
183
|
React.createElement("div", { className: "text-start" },
|
|
184
|
-
React.createElement("h2", { className: "mb-2 text-2xl font-semibold tracking-tight text-gray-200" }, "Hi! Ask me anything"),
|
|
184
|
+
React.createElement("h2", { className: "mb-2 text-2xl font-semibold tracking-tight text-zinc-800 dark:text-gray-200" }, "Hi! Ask me anything"),
|
|
185
185
|
React.createElement("p", { className: "text-muted-foreground text-base" }, "Try: \u201CHow does this sample app work?\u201D"))))),
|
|
186
186
|
React.createElement("div", { ref: messagesEndRef })),
|
|
187
|
-
React.createElement("div", { className: "shrink-0 border-t border-gray-800/50 bg-[#0a0b0f]/95 backdrop-blur-sm" },
|
|
187
|
+
React.createElement("div", { className: "shrink-0 border-t border-zinc-200 dark:border-gray-800/50 bg-zinc-50/95 dark:bg-[#0a0b0f]/95 backdrop-blur-sm" },
|
|
188
188
|
React.createElement("div", { className: "max-w-4xl mx-auto px-4 py-4" },
|
|
189
|
-
React.createElement("div", { className: "flex items-end gap-3 bg-[#1a1b23] rounded-2xl border border-gray-800/50 px-4 py-3 transition-all", style: {
|
|
189
|
+
React.createElement("div", { className: "flex items-end gap-3 bg-white dark:bg-[#1a1b23] rounded-2xl border border-zinc-200 dark:border-gray-800/50 px-4 py-3 transition-all", style: {
|
|
190
190
|
boxShadow: `0 0 0 1px transparent`,
|
|
191
191
|
} },
|
|
192
192
|
React.createElement("div", { className: "flex-1 min-w-0" },
|
|
@@ -195,7 +195,7 @@ export default function AskAiView({ codeZipFile, playgroundUid, themeColor, }) {
|
|
|
195
195
|
e.preventDefault();
|
|
196
196
|
handleSend();
|
|
197
197
|
}
|
|
198
|
-
}, placeholder: "Type your message...", disabled: isLoading, rows: 1, className: "w-full bg-transparent border-0 resize-none text-base text-gray-200 placeholder:text-gray-500 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed max-h-40 overflow-y-auto leading-relaxed", style: {
|
|
198
|
+
}, placeholder: "Type your message...", disabled: isLoading, rows: 1, className: "w-full bg-transparent border-0 resize-none text-base text-zinc-800 dark:text-gray-200 placeholder:text-zinc-400 dark:placeholder:text-gray-500 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed max-h-40 overflow-y-auto leading-relaxed", style: {
|
|
199
199
|
height: "auto",
|
|
200
200
|
minHeight: "32px",
|
|
201
201
|
}, onInput: (e) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import React, { useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
3
3
|
import { useGuardianContext } from "./context/guardian-context";
|
|
4
4
|
// Helper function to find a node in the file tree by path
|
|
5
5
|
function getTargetNode(fileTree, targetPath) {
|
|
@@ -13,15 +13,159 @@ function getTargetNode(fileTree, targetPath) {
|
|
|
13
13
|
}
|
|
14
14
|
return current;
|
|
15
15
|
}
|
|
16
|
+
class CodeFocusSectionCoordinator {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.sections = new Map();
|
|
19
|
+
this.lastActiveSectionId = null;
|
|
20
|
+
this.scrollContainers = new Set();
|
|
21
|
+
this.scrollHandler = null;
|
|
22
|
+
if (typeof window !== "undefined") {
|
|
23
|
+
this.scrollHandler = this.handleScroll.bind(this);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
handleScroll() {
|
|
27
|
+
this.updateActiveSection();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Calculate how "centered" an element is relative to its scroll container.
|
|
31
|
+
* Returns a score from 0 to 1, where 1 means the element's top is at the ideal reading position.
|
|
32
|
+
*/
|
|
33
|
+
calculateScore(element) {
|
|
34
|
+
// Find the scroll container by walking up the DOM
|
|
35
|
+
const scrollContainer = this.findScrollContainer(element);
|
|
36
|
+
if (!scrollContainer)
|
|
37
|
+
return 0;
|
|
38
|
+
const containerRect = scrollContainer.getBoundingClientRect();
|
|
39
|
+
const elementRect = element.getBoundingClientRect();
|
|
40
|
+
// Calculate position relative to the scroll container
|
|
41
|
+
const relativeTop = elementRect.top - containerRect.top;
|
|
42
|
+
const relativeBottom = elementRect.bottom - containerRect.top;
|
|
43
|
+
const containerHeight = containerRect.height;
|
|
44
|
+
// Check if element is visible in the container
|
|
45
|
+
if (relativeBottom < 0 || relativeTop > containerHeight) {
|
|
46
|
+
return 0; // Not visible
|
|
47
|
+
}
|
|
48
|
+
// Target point: 20-30% from top of container for natural reading flow
|
|
49
|
+
// This is where the user's eyes naturally focus when reading
|
|
50
|
+
const targetPoint = containerHeight * 0.25;
|
|
51
|
+
// Calculate how close the element's top is to the target point
|
|
52
|
+
const distanceFromTarget = Math.abs(relativeTop - targetPoint);
|
|
53
|
+
const maxDistance = containerHeight;
|
|
54
|
+
// Base score from distance (closer to target = higher score)
|
|
55
|
+
const distanceScore = Math.max(0, 1 - distanceFromTarget / maxDistance);
|
|
56
|
+
// Bonus for elements that are in the "reading zone" (top 50% of container)
|
|
57
|
+
const inReadingZone = relativeTop >= 0 && relativeTop < containerHeight * 0.5;
|
|
58
|
+
const readingZoneBonus = inReadingZone ? 0.3 : 0;
|
|
59
|
+
// Visibility factor - how much of the element is visible
|
|
60
|
+
const visibleTop = Math.max(0, relativeTop);
|
|
61
|
+
const visibleBottom = Math.min(containerHeight, relativeBottom);
|
|
62
|
+
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
63
|
+
const elementHeight = elementRect.height;
|
|
64
|
+
const visibilityRatio = elementHeight > 0 ? visibleHeight / elementHeight : 0;
|
|
65
|
+
const visibilityFactor = visibilityRatio * 0.2;
|
|
66
|
+
return distanceScore * 0.5 + readingZoneBonus + visibilityFactor;
|
|
67
|
+
}
|
|
68
|
+
findScrollContainer(element) {
|
|
69
|
+
let current = element.parentElement;
|
|
70
|
+
while (current) {
|
|
71
|
+
const style = window.getComputedStyle(current);
|
|
72
|
+
const overflowY = style.overflowY;
|
|
73
|
+
if ((overflowY === "auto" || overflowY === "scroll") &&
|
|
74
|
+
current.scrollHeight > current.clientHeight) {
|
|
75
|
+
return current;
|
|
76
|
+
}
|
|
77
|
+
current = current.parentElement;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
updateActiveSection() {
|
|
82
|
+
if (this.sections.size === 0)
|
|
83
|
+
return;
|
|
84
|
+
// Find the section with the highest score
|
|
85
|
+
let bestSection = null;
|
|
86
|
+
let bestScore = -1;
|
|
87
|
+
let bestId = null;
|
|
88
|
+
for (const [id, entry] of this.sections) {
|
|
89
|
+
const score = this.calculateScore(entry.element);
|
|
90
|
+
if (score > bestScore) {
|
|
91
|
+
bestScore = score;
|
|
92
|
+
bestSection = entry;
|
|
93
|
+
bestId = id;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Only trigger if we have a valid section with a reasonable score
|
|
97
|
+
// and it's different from the last one
|
|
98
|
+
if (bestSection && bestId && bestScore > 0.1 && bestId !== this.lastActiveSectionId) {
|
|
99
|
+
this.lastActiveSectionId = bestId;
|
|
100
|
+
bestSection.focusCallback();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
register(id, element, focusCallback) {
|
|
104
|
+
const entry = { element, focusCallback, id };
|
|
105
|
+
this.sections.set(id, entry);
|
|
106
|
+
element.setAttribute("data-code-focus-id", id);
|
|
107
|
+
// Find and attach to the scroll container
|
|
108
|
+
const scrollContainer = this.findScrollContainer(element);
|
|
109
|
+
if (scrollContainer && this.scrollHandler) {
|
|
110
|
+
if (!this.scrollContainers.has(scrollContainer)) {
|
|
111
|
+
scrollContainer.addEventListener("scroll", this.scrollHandler, { passive: true });
|
|
112
|
+
this.scrollContainers.add(scrollContainer);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Trigger initial update
|
|
116
|
+
this.updateActiveSection();
|
|
117
|
+
// Return cleanup function
|
|
118
|
+
return () => {
|
|
119
|
+
this.sections.delete(id);
|
|
120
|
+
if (this.lastActiveSectionId === id) {
|
|
121
|
+
this.lastActiveSectionId = null;
|
|
122
|
+
}
|
|
123
|
+
// Clean up scroll listeners if no more sections
|
|
124
|
+
if (this.sections.size === 0) {
|
|
125
|
+
for (const container of this.scrollContainers) {
|
|
126
|
+
if (this.scrollHandler) {
|
|
127
|
+
container.removeEventListener("scroll", this.scrollHandler);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
this.scrollContainers.clear();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// Force focus a specific section (for click handling)
|
|
135
|
+
forceFocus(id) {
|
|
136
|
+
const section = this.sections.get(id);
|
|
137
|
+
if (section) {
|
|
138
|
+
this.lastActiveSectionId = id;
|
|
139
|
+
section.focusCallback();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Singleton instance - created lazily to avoid SSR issues
|
|
144
|
+
let coordinatorInstance = null;
|
|
145
|
+
function getCoordinator() {
|
|
146
|
+
if (!coordinatorInstance && typeof window !== "undefined") {
|
|
147
|
+
coordinatorInstance = new CodeFocusSectionCoordinator();
|
|
148
|
+
}
|
|
149
|
+
return coordinatorInstance;
|
|
150
|
+
}
|
|
151
|
+
// Counter for generating unique IDs
|
|
152
|
+
let sectionIdCounter = 0;
|
|
16
153
|
/**
|
|
17
154
|
* Component that focuses on a specific file and line range in the code editor
|
|
18
155
|
* when it comes into view while scrolling, or when the user clicks "View Code".
|
|
156
|
+
*
|
|
157
|
+
* Uses a global coordinator pattern (similar to Stripe docs) to ensure:
|
|
158
|
+
* - Only ONE section is active at a time
|
|
159
|
+
* - The section closest to the viewport center is selected
|
|
160
|
+
* - Smooth transitions without flickering during fast scrolling
|
|
161
|
+
* - Natural reading flow with slight bias towards top of viewport
|
|
19
162
|
*/
|
|
20
|
-
export default function CodeFocusSection({ filePath, lineRange, title, description, children,
|
|
163
|
+
export default function CodeFocusSection({ filePath, lineRange, title, description, children, themeColor, }) {
|
|
21
164
|
const sectionRef = useRef(null);
|
|
165
|
+
const sectionIdRef = useRef(`code-focus-${++sectionIdCounter}`);
|
|
22
166
|
const { generatedCode, fileTree, setActiveFilePath, setActiveFileName, setActiveLineNumber, setActiveLineRange, updateCode, setLanguage, filesEdited, activeFilePath, activeLineRange, } = useGuardianContext();
|
|
23
|
-
//
|
|
24
|
-
const focusCode = () => {
|
|
167
|
+
// Memoize the focus code function to prevent unnecessary recreations
|
|
168
|
+
const focusCode = useCallback(() => {
|
|
25
169
|
// Get the file code from generatedCode or fileTree
|
|
26
170
|
// Always search by file_path property first, as that's the source of truth
|
|
27
171
|
let code = "";
|
|
@@ -94,41 +238,35 @@ export default function CodeFocusSection({ filePath, lineRange, title, descripti
|
|
|
94
238
|
setActiveLineNumber(undefined);
|
|
95
239
|
}
|
|
96
240
|
}
|
|
97
|
-
};
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
const element = sectionRef.current;
|
|
100
|
-
if (!element)
|
|
101
|
-
return;
|
|
102
|
-
const observer = new IntersectionObserver((entries) => {
|
|
103
|
-
entries.forEach((entry) => {
|
|
104
|
-
// Trigger when the element enters the middle of the viewport
|
|
105
|
-
if (entry.isIntersecting) {
|
|
106
|
-
focusCode();
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}, {
|
|
110
|
-
threshold,
|
|
111
|
-
rootMargin: "-40% 0px -40% 0px", // Trigger when element enters the middle 20% of viewport
|
|
112
|
-
});
|
|
113
|
-
observer.observe(element);
|
|
114
|
-
return () => {
|
|
115
|
-
observer.disconnect();
|
|
116
|
-
};
|
|
117
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
118
241
|
}, [
|
|
119
242
|
filePath,
|
|
120
243
|
lineRange,
|
|
121
244
|
generatedCode,
|
|
122
245
|
fileTree,
|
|
246
|
+
filesEdited,
|
|
123
247
|
setActiveFilePath,
|
|
124
248
|
setActiveFileName,
|
|
125
249
|
setActiveLineNumber,
|
|
126
250
|
setActiveLineRange,
|
|
127
251
|
updateCode,
|
|
128
252
|
setLanguage,
|
|
129
|
-
filesEdited,
|
|
130
|
-
threshold,
|
|
131
253
|
]);
|
|
254
|
+
// Register with the global coordinator
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
const element = sectionRef.current;
|
|
257
|
+
if (!element || typeof window === "undefined")
|
|
258
|
+
return;
|
|
259
|
+
const coordinator = getCoordinator();
|
|
260
|
+
const cleanup = coordinator.register(sectionIdRef.current, element, focusCode);
|
|
261
|
+
return cleanup;
|
|
262
|
+
}, [focusCode]);
|
|
263
|
+
// Handle click to force focus
|
|
264
|
+
const handleClick = useCallback(() => {
|
|
265
|
+
if (typeof window !== "undefined") {
|
|
266
|
+
const coordinator = getCoordinator();
|
|
267
|
+
coordinator.forceFocus(sectionIdRef.current);
|
|
268
|
+
}
|
|
269
|
+
}, []);
|
|
132
270
|
// Get the resolved file path for comparison
|
|
133
271
|
// This ensures we compare against the actual file_path from the code structure
|
|
134
272
|
const resolvedFilePathForComparison = useMemo(() => {
|
|
@@ -154,7 +292,7 @@ export default function CodeFocusSection({ filePath, lineRange, title, descripti
|
|
|
154
292
|
}
|
|
155
293
|
return filePath;
|
|
156
294
|
}, [filePath, generatedCode, fileTree]);
|
|
157
|
-
return (React.createElement("div", { ref: sectionRef, className: `relative transition-colors py-6 cursor-pointer`, onClick:
|
|
295
|
+
return (React.createElement("div", { ref: sectionRef, className: `relative transition-colors py-6 cursor-pointer`, onClick: handleClick },
|
|
158
296
|
React.createElement("div", { className: "pointer-events-none absolute inset-x-[-2rem] top-0 border-t border-border" }),
|
|
159
297
|
React.createElement("div", { className: "pointer-events-none absolute inset-x-[-2rem] bottom-0 border-b border-border" }),
|
|
160
298
|
(activeFilePath === resolvedFilePathForComparison ||
|
|
@@ -4,7 +4,7 @@ import { Markdown } from "./ui/markdown";
|
|
|
4
4
|
export default function DefaultGuideView({ themeColor, }) {
|
|
5
5
|
return (React.createElement("div", { className: "h-full flex flex-col" },
|
|
6
6
|
React.createElement("div", { className: "flex-1 px-8 py-6" },
|
|
7
|
-
React.createElement(Markdown, { className: "text-gray-100", themeColor: themeColor }, `# Guardian Guide
|
|
7
|
+
React.createElement(Markdown, { className: "text-zinc-900 dark:text-gray-100", themeColor: themeColor }, `# Guardian Guide
|
|
8
8
|
|
|
9
9
|
Learn how to use Guardian to build and deploy your application with confidence.
|
|
10
10
|
|
|
@@ -20,7 +20,7 @@ export default function ConsoleWithGuide({ CustomConsole, GuideView, playgroundU
|
|
|
20
20
|
if (typeof CustomConsole === "string") {
|
|
21
21
|
return (React.createElement("div", { className: "h-full flex flex-col" },
|
|
22
22
|
React.createElement("div", { className: "flex-1 px-8 py-6" },
|
|
23
|
-
React.createElement(Markdown, { className: "text-gray-100", themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }, CustomConsole))));
|
|
23
|
+
React.createElement(Markdown, { className: "text-zinc-900 dark:text-gray-100", themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }, CustomConsole))));
|
|
24
24
|
}
|
|
25
25
|
const CustomConsoleComponent = CustomConsole;
|
|
26
26
|
return (React.createElement(CustomConsoleComponent, { onReloadPreview: onReloadPreview !== null && onReloadPreview !== void 0 ? onReloadPreview : (() => { }), onStageChange: onStageChange !== null && onStageChange !== void 0 ? onStageChange : (() => { }), themeColor: themeColor }));
|
|
@@ -64,7 +64,7 @@ export default function ConsoleWithGuide({ CustomConsole, GuideView, playgroundU
|
|
|
64
64
|
React.createElement("div", { className: "flex-1 min-h-0 overflow-y-auto" }, isConsoleView && renderCustomConsole()),
|
|
65
65
|
React.createElement("div", { className: "relative" },
|
|
66
66
|
React.createElement("div", { className: "flex flex-row w-full items-center text-xs justify-center opacity-30 mt-1 pb-2 bg-background" },
|
|
67
|
-
React.createElement("span", { className: "text-gray-300" }, "Powered by"),
|
|
67
|
+
React.createElement("span", { className: "text-zinc-600 dark:text-gray-300" }, "Powered by"),
|
|
68
68
|
React.createElement("span", { className: "hover:bg-accent rounded-sm p-1 mx-1" },
|
|
69
69
|
React.createElement(Logo, { width: 100, href: "https://sampleapp.ai" })))))));
|
|
70
70
|
}
|
|
@@ -9,7 +9,7 @@ import { GuardianStyleWrapper } from "./guardian-style-wrapper";
|
|
|
9
9
|
*
|
|
10
10
|
* When isFrame=true, reads framework from URL params client-side.
|
|
11
11
|
*/
|
|
12
|
-
export default function GuardianPlayground({ nestedConfig, useCase, framework: serverFramework, isFrame = false, apiKey, env, chatUid, }) {
|
|
12
|
+
export default function GuardianPlayground({ nestedConfig, useCase, framework: serverFramework, isFrame = false, apiKey, env, chatUid, theme = "dark", }) {
|
|
13
13
|
var _a, _b;
|
|
14
14
|
// When in frame mode, allow URL params to override framework
|
|
15
15
|
const frameParams = useFrameParams();
|
|
@@ -40,6 +40,6 @@ export default function GuardianPlayground({ nestedConfig, useCase, framework: s
|
|
|
40
40
|
firstFrameworkByUseCase[useCaseId] = firstFrameworkKey;
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
|
-
return (React.createElement(GuardianStyleWrapper, { themeColor: frameworkConfig.themeColor },
|
|
43
|
+
return (React.createElement(GuardianStyleWrapper, { themeColor: frameworkConfig.themeColor, theme: theme },
|
|
44
44
|
React.createElement(GuardianComponent, { demoOptions: frameworkConfig.demoOptions, frameworkOptions: frameworkConfig.frameworkOptions, firstFrameworkByUseCase: firstFrameworkByUseCase, currentFramework: frameworkConfig.currentFramework, CustomConsole: frameworkConfig.CustomConsole, GuideView: frameworkConfig.GuideView, playgroundUid: frameworkConfig.playgroundUid, browserUrl: (_a = frameParams.iframeUrl) !== null && _a !== void 0 ? _a : frameworkConfig.browserUrl, useVm: frameworkConfig.useVm, playgroundLogo: frameworkConfig.playgroundLogo, sandboxUid: frameworkConfig.sandboxUid, name: frameworkConfig.name, description: frameworkConfig.description, codeZipFile: frameworkConfig.codeZipFile, completeCodeZipFile: frameworkConfig.completeCodeZipFile, consoleUrlConfigs: frameworkConfig.consoleUrlConfigs, variant: frameworkConfig.variant, themeColor: frameworkConfig.themeColor, hasPreview: (_b = frameworkConfig.hasPreview) !== null && _b !== void 0 ? _b : true, currentUseCase: useCase, isFrame: isFrame, apiKey: apiKey, env: env, chatUid: chatUid, gitUrl: frameworkConfig.gitUrl })));
|
|
45
45
|
}
|
|
@@ -5,9 +5,28 @@ import { TAILWIND_CSS } from "../../../lib/generated-css";
|
|
|
5
5
|
* Style wrapper for Guardian components that injects Tailwind CSS
|
|
6
6
|
* into the document without using Shadow DOM (preserves React context)
|
|
7
7
|
*/
|
|
8
|
-
export const GuardianStyleWrapper = ({ children, themeColor = "#3b82f6", }) => {
|
|
8
|
+
export const GuardianStyleWrapper = ({ children, themeColor = "#3b82f6", theme = "dark", }) => {
|
|
9
9
|
const [stylesInjected, setStylesInjected] = useState(false);
|
|
10
|
+
const [resolvedTheme, setResolvedTheme] = useState(theme === "system" ? "dark" : theme);
|
|
10
11
|
const rootRef = useRef(null);
|
|
12
|
+
// Handle system theme preference
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (theme === "system") {
|
|
15
|
+
// Check if we're in browser environment
|
|
16
|
+
if (typeof window === "undefined")
|
|
17
|
+
return;
|
|
18
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
19
|
+
setResolvedTheme(mediaQuery.matches ? "dark" : "light");
|
|
20
|
+
const handler = (e) => {
|
|
21
|
+
setResolvedTheme(e.matches ? "dark" : "light");
|
|
22
|
+
};
|
|
23
|
+
mediaQuery.addEventListener("change", handler);
|
|
24
|
+
return () => mediaQuery.removeEventListener("change", handler);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
setResolvedTheme(theme);
|
|
28
|
+
}
|
|
29
|
+
}, [theme]);
|
|
11
30
|
useEffect(() => {
|
|
12
31
|
// Only run on client side
|
|
13
32
|
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
@@ -39,7 +58,7 @@ export const GuardianStyleWrapper = ({ children, themeColor = "#3b82f6", }) => {
|
|
|
39
58
|
rootRef.current.style.setProperty("--colors-secondary", secondaryColor);
|
|
40
59
|
}
|
|
41
60
|
}, [themeColor]);
|
|
42
|
-
return (React.createElement("div", { ref: rootRef, className:
|
|
61
|
+
return (React.createElement("div", { ref: rootRef, className: `guardian-sdk-root bg-background text-foreground ${resolvedTheme === "dark" ? "dark" : ""}`, style: {
|
|
43
62
|
width: "100%",
|
|
44
63
|
height: "100%",
|
|
45
64
|
fontFamily: 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
|
|
@@ -494,57 +494,57 @@ export default function Browser({ previewUrl, setPreviewUrl, containerEndpoint,
|
|
|
494
494
|
} }));
|
|
495
495
|
};
|
|
496
496
|
return (React.createElement("div", { className: "flex h-full w-full flex-col" },
|
|
497
|
-
!useVm && !isGuardian && (React.createElement("div", { className: "bg-
|
|
497
|
+
!useVm && !isGuardian && (React.createElement("div", { className: "bg-black border-b border-zinc-800 flex-shrink-0" },
|
|
498
498
|
React.createElement("div", { className: "flex items-center gap-1.5 px-2 py-1.5 h-[2.38rem]" },
|
|
499
499
|
React.createElement("div", { className: "flex gap-0.5" },
|
|
500
|
-
React.createElement("button", { onClick: handleBack, disabled: historyIndex === 0, className: "p-1 hover:bg-zinc-
|
|
501
|
-
React.createElement(ChevronLeft, { size: 14, className: "text-zinc-
|
|
502
|
-
React.createElement("button", { onClick: handleForward, disabled: historyIndex === historyLength - 1, className: "p-1 hover:bg-zinc-
|
|
503
|
-
React.createElement(ChevronRight, { size: 14, className: "text-zinc-
|
|
504
|
-
React.createElement("button", { onClick: handleRefresh, className: "p-1 hover:bg-zinc-
|
|
505
|
-
React.createElement(RefreshCw, { size: 14, className: `text-zinc-
|
|
506
|
-
React.createElement("div", { className: "flex-1 flex items-center gap-1.5 px-2 py-1 bg-zinc-
|
|
507
|
-
error ? (React.createElement(AlertTriangle, { size: 12, className: "text-red-500 flex-shrink-0" })) : (React.createElement(Lock, { size: 12, className: "text-zinc-
|
|
500
|
+
React.createElement("button", { onClick: handleBack, disabled: historyIndex === 0, className: "p-1 hover:bg-zinc-900 rounded disabled:opacity-30 disabled:hover:bg-transparent transition-colors", title: "Back" },
|
|
501
|
+
React.createElement(ChevronLeft, { size: 14, className: "text-zinc-400" })),
|
|
502
|
+
React.createElement("button", { onClick: handleForward, disabled: historyIndex === historyLength - 1, className: "p-1 hover:bg-zinc-900 rounded disabled:opacity-30 disabled:hover:bg-transparent transition-colors", title: "Forward" },
|
|
503
|
+
React.createElement(ChevronRight, { size: 14, className: "text-zinc-400" })),
|
|
504
|
+
React.createElement("button", { onClick: handleRefresh, className: "p-1 hover:bg-zinc-900 rounded transition-colors", title: "Refresh" },
|
|
505
|
+
React.createElement(RefreshCw, { size: 14, className: `text-zinc-400 ${isLoading ? "animate-spin" : ""}` }))),
|
|
506
|
+
React.createElement("div", { className: "flex-1 flex items-center gap-1.5 px-2 py-1 bg-zinc-950 rounded text-zinc-400 focus-within:bg-zinc-900 transition-colors" },
|
|
507
|
+
error ? (React.createElement(AlertTriangle, { size: 12, className: "text-red-500 flex-shrink-0" })) : (React.createElement(Lock, { size: 12, className: "text-zinc-600 flex-shrink-0" })),
|
|
508
508
|
React.createElement("input", { type: "text",
|
|
509
509
|
// value={urlInput}
|
|
510
|
-
value: "/", onChange: (e) => setUrlInput(e.target.value), onKeyDown: handleUrlChange, className: "flex-1 text-xs border-none outline-none bg-transparent text-zinc-
|
|
511
|
-
isGuardian && (React.createElement("button", { onClick: () => setIsBrowserMaximized(!isBrowserMaximized), className: "p-1 hover:bg-zinc-
|
|
510
|
+
value: "/", onChange: (e) => setUrlInput(e.target.value), onKeyDown: handleUrlChange, className: "flex-1 text-xs border-none outline-none bg-transparent text-zinc-300 placeholder:text-zinc-600", placeholder: "Enter URL" }),
|
|
511
|
+
isGuardian && (React.createElement("button", { onClick: () => setIsBrowserMaximized(!isBrowserMaximized), className: "p-1 hover:bg-zinc-900 rounded transition-colors", title: isBrowserMaximized
|
|
512
512
|
? "Exit fullscreen"
|
|
513
|
-
: "Fullscreen preview" }, isBrowserMaximized ? (React.createElement(Minimize2, { size: 14, className: "text-zinc-
|
|
513
|
+
: "Fullscreen preview" }, isBrowserMaximized ? (React.createElement(Minimize2, { size: 14, className: "text-zinc-400" })) : (React.createElement(Maximize2, { size: 14, className: "text-zinc-400" })))),
|
|
514
514
|
React.createElement("button", { onClick: () => {
|
|
515
515
|
window.open(previewUrl, "_blank", "noopener,noreferrer");
|
|
516
|
-
}, className: "ml-1 p-1 hover:bg-zinc-
|
|
517
|
-
React.createElement(ExternalLink, { size: 14, className: "text-zinc-
|
|
516
|
+
}, className: "ml-1 p-1 hover:bg-zinc-900 rounded transition-colors", title: "Open in new tab", disabled: !urlInput || !!error, tabIndex: -1, type: "button" },
|
|
517
|
+
React.createElement(ExternalLink, { size: 14, className: "text-zinc-400" })),
|
|
518
518
|
React.createElement(DropdownMenu, null,
|
|
519
519
|
React.createElement(DropdownMenuTrigger, { asChild: true },
|
|
520
|
-
React.createElement("button", { className: "ml-1 p-1 hover:bg-zinc-
|
|
521
|
-
React.createElement(MoreVertical, { size: 14, className: "text-zinc-
|
|
522
|
-
React.createElement(DropdownMenuContent, { align: "end", className: "bg-
|
|
520
|
+
React.createElement("button", { className: "ml-1 p-1 hover:bg-zinc-900 rounded transition-colors", title: "More options", type: "button" },
|
|
521
|
+
React.createElement(MoreVertical, { size: 14, className: "text-zinc-400" }))),
|
|
522
|
+
React.createElement(DropdownMenuContent, { align: "end", className: "bg-zinc-900 border border-zinc-800" },
|
|
523
523
|
React.createElement(DropdownMenuItem, { onClick: () => {
|
|
524
524
|
// Restart functionality - can be implemented if needed
|
|
525
|
-
}, className: "cursor-pointer text-zinc-
|
|
526
|
-
error && (React.createElement("div", { className: "p-4 bg-red-
|
|
525
|
+
}, className: "cursor-pointer text-zinc-300 hover:bg-zinc-800" }, "Restart App"))))))),
|
|
526
|
+
error && (React.createElement("div", { className: "p-4 bg-red-950 border-b border-red-800 text-red-200 text-sm" },
|
|
527
527
|
React.createElement("div", { className: "flex items-center gap-2" },
|
|
528
528
|
React.createElement(AlertTriangle, { size: 16 }),
|
|
529
529
|
React.createElement("span", null, error)))),
|
|
530
|
-
React.createElement("div", { ref: containerRef, className: "flex-1 min-h-0 relative" }, sandboxError && sandboxError.includes("env is required") ? (React.createElement("div", { className: "h-full w-full flex items-center justify-center bg-zinc-
|
|
530
|
+
React.createElement("div", { ref: containerRef, className: "flex-1 min-h-0 relative" }, sandboxError && sandboxError.includes("env is required") ? (React.createElement("div", { className: "h-full w-full flex items-center justify-center bg-zinc-900" },
|
|
531
531
|
React.createElement("div", { className: "max-w-md mx-auto p-8 text-center" },
|
|
532
532
|
React.createElement("div", { className: "mb-4 flex justify-center" },
|
|
533
|
-
React.createElement(AlertTriangle, { size: 48, className: "text-amber-
|
|
534
|
-
React.createElement("h2", { className: "text-xl font-semibold text-zinc-
|
|
535
|
-
React.createElement("p", { className: "text-sm text-zinc-
|
|
533
|
+
React.createElement(AlertTriangle, { size: 48, className: "text-amber-400" })),
|
|
534
|
+
React.createElement("h2", { className: "text-xl font-semibold text-zinc-100 mb-2" }, "Environment Variables Required"),
|
|
535
|
+
React.createElement("p", { className: "text-sm text-zinc-400 mb-4" },
|
|
536
536
|
"This sandbox doesn't have a published URL and needs to be started dynamically. Please provide the",
|
|
537
537
|
" ",
|
|
538
|
-
React.createElement("code", { className: "px-1.5 py-0.5 bg-zinc-
|
|
538
|
+
React.createElement("code", { className: "px-1.5 py-0.5 bg-zinc-800 rounded text-xs font-mono" }, "env"),
|
|
539
539
|
" ",
|
|
540
540
|
"prop to the",
|
|
541
541
|
" ",
|
|
542
|
-
React.createElement("code", { className: "px-1.5 py-0.5 bg-zinc-
|
|
542
|
+
React.createElement("code", { className: "px-1.5 py-0.5 bg-zinc-800 rounded text-xs font-mono" }, "<Sandbox>"),
|
|
543
543
|
" ",
|
|
544
544
|
"component."),
|
|
545
|
-
React.createElement("div", { className: "mt-6 p-4 bg-zinc-
|
|
546
|
-
React.createElement("p", { className: "text-xs font-mono text-zinc-
|
|
547
|
-
React.createElement("pre", { className: "text-xs text-zinc-
|
|
545
|
+
React.createElement("div", { className: "mt-6 p-4 bg-zinc-800 rounded-lg text-left" },
|
|
546
|
+
React.createElement("p", { className: "text-xs font-mono text-zinc-300 mb-2" }, "Example:"),
|
|
547
|
+
React.createElement("pre", { className: "text-xs text-zinc-400 overflow-x-auto" }, `<Sandbox
|
|
548
548
|
apiKey="your-api-key"
|
|
549
549
|
sandboxId="sandbox-id"
|
|
550
550
|
env={{
|
|
@@ -553,8 +553,8 @@ export default function Browser({ previewUrl, setPreviewUrl, containerEndpoint,
|
|
|
553
553
|
}}
|
|
554
554
|
/>`))))) : (React.createElement(React.Fragment, null,
|
|
555
555
|
renderIframe(),
|
|
556
|
-
isLoading && (React.createElement("div", { className: "absolute inset-0 bg-zinc-
|
|
557
|
-
React.createElement("div", { className: "text-sm text-zinc-
|
|
556
|
+
isLoading && (React.createElement("div", { className: "absolute inset-0 bg-zinc-800 bg-opacity-50 flex items-center justify-center z-10" },
|
|
557
|
+
React.createElement("div", { className: "text-sm text-zinc-400 bg-black p-3 rounded-lg shadow-md" },
|
|
558
558
|
"Loading ",
|
|
559
559
|
urlInput,
|
|
560
560
|
"..."))),
|
|
@@ -16,10 +16,10 @@ export default function PreviewControlBar({ isMinimized, onToggle, onRefresh, th
|
|
|
16
16
|
React.createElement(ChevronDown, { className: "w-4 h-4" })),
|
|
17
17
|
React.createElement("span", { className: "text-sm font-medium", style: { color: effectiveThemeColor } }, "Preview")),
|
|
18
18
|
React.createElement("div", { className: "flex items-center gap-2" },
|
|
19
|
-
externalUrl && (React.createElement("a", { href: externalUrl, target: "_blank", rel: "noopener noreferrer", title: "Open in new tab", style: { color: effectiveThemeColor }, className: "p-1.5 rounded transition-colors hover:bg-zinc-800
|
|
19
|
+
externalUrl && (React.createElement("a", { href: externalUrl, target: "_blank", rel: "noopener noreferrer", title: "Open in new tab", style: { color: effectiveThemeColor }, className: "p-1.5 rounded transition-colors hover:bg-zinc-800" },
|
|
20
20
|
React.createElement(ExternalLink, { className: "w-4 h-4" }),
|
|
21
21
|
React.createElement("span", { className: "sr-only" }, "Open external link"))),
|
|
22
|
-
React.createElement("button", { onClick: onRefresh, className: "p-1.5 rounded transition-colors hover:bg-zinc-800
|
|
22
|
+
React.createElement("button", { onClick: onRefresh, className: "p-1.5 rounded transition-colors hover:bg-zinc-800", title: "Refresh preview", style: { color: effectiveThemeColor } },
|
|
23
23
|
React.createElement(RefreshCw, { className: "w-4 h-4" })),
|
|
24
|
-
React.createElement("button", { onClick: () => setIsBrowserMaximized(!isBrowserMaximized), className: "p-1.5 rounded transition-colors hover:bg-zinc-800
|
|
24
|
+
React.createElement("button", { onClick: () => setIsBrowserMaximized(!isBrowserMaximized), className: "p-1.5 rounded transition-colors hover:bg-zinc-800", title: isBrowserMaximized ? "Exit fullscreen" : "Fullscreen preview", style: { color: effectiveThemeColor } }, isBrowserMaximized ? (React.createElement(Minimize2, { className: "w-4 h-4" })) : (React.createElement(Maximize2, { className: "w-4 h-4" }))))));
|
|
25
25
|
}
|
|
@@ -217,7 +217,7 @@ export default function RightView({ reloadCounter, overlayStage, browserUrl, use
|
|
|
217
217
|
React.createElement("div", { className: "h-full w-full flex flex-col" },
|
|
218
218
|
React.createElement("div", { className: "flex-1 min-h-0 flex flex-col" },
|
|
219
219
|
React.createElement(React.Fragment, null,
|
|
220
|
-
React.createElement("div", { className: "flex items-center justify-between border-b border-zinc-800
|
|
220
|
+
React.createElement("div", { className: "flex items-center justify-between border-b border-zinc-800 bg-zinc-900" },
|
|
221
221
|
React.createElement(PillFileSelector, { themeColor: effectiveThemeColor }),
|
|
222
222
|
React.createElement("div", { className: "px-2 flex-shrink-0" },
|
|
223
223
|
React.createElement("button", { type: "button", className: "inline-flex items-center gap-1 text-[10px] font-medium transition-colors hover:opacity-80", style: { color: effectiveThemeColor }, onClick: handleDownloadAsZip },
|
|
@@ -207,27 +207,27 @@ export default function SimplifiedEditor({ themeColor }) {
|
|
|
207
207
|
colorDecorators: true,
|
|
208
208
|
})), { domReadOnly: isGenerating, readOnly: isGenerating }) })),
|
|
209
209
|
React.createElement("style", null, `
|
|
210
|
-
|
|
210
|
+
.search-highlight-line {
|
|
211
211
|
background-color: ${highlightBackground} !important;
|
|
212
212
|
}
|
|
213
|
-
|
|
213
|
+
.search-highlight-glyph {
|
|
214
214
|
background-color: ${glyphBackground};
|
|
215
215
|
}
|
|
216
|
-
|
|
216
|
+
.muted-code-text {
|
|
217
217
|
opacity: 0.5;
|
|
218
218
|
filter: grayscale(0.4);
|
|
219
219
|
}
|
|
220
220
|
/* Remove blue focus outline from Monaco Editor */
|
|
221
|
-
|
|
221
|
+
.monaco-editor .inputarea.ime-input {
|
|
222
222
|
outline: none !important;
|
|
223
223
|
}
|
|
224
|
-
|
|
224
|
+
.monaco-editor {
|
|
225
225
|
outline: none !important;
|
|
226
226
|
}
|
|
227
|
-
|
|
227
|
+
.monaco-editor .monaco-editor-background {
|
|
228
228
|
outline: none !important;
|
|
229
229
|
}
|
|
230
|
-
|
|
230
|
+
.monaco-editor .view-overlays {
|
|
231
231
|
outline: none !important;
|
|
232
232
|
}
|
|
233
233
|
`)));
|
|
@@ -59,7 +59,7 @@ export const DownloadAndOpenButtons = ({ downloadUrl, themeColor, gitUrl, browse
|
|
|
59
59
|
React.createElement("button", { type: "button", className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md !text-white transition-all hover:opacity-90 active:scale-[0.98]", style: { backgroundColor: themeColor }, onClick: handleDownload },
|
|
60
60
|
React.createElement(DownloadIcon, { className: "w-3.5 h-3.5 !text-white" }),
|
|
61
61
|
"Download example"),
|
|
62
|
-
React.createElement("button", { type: "button", className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border transition-all hover:opacity-90 active:scale-[0.98] bg-zinc-
|
|
62
|
+
React.createElement("button", { type: "button", className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border transition-all hover:opacity-90 active:scale-[0.98] bg-zinc-100 dark:bg-zinc-800 border-zinc-300 dark:border-zinc-700 text-zinc-900 dark:text-white", onClick: handleOpenInVSCode },
|
|
63
63
|
React.createElement(VSCodeLogo, { className: "w-3.5 h-3.5" }),
|
|
64
64
|
"Integrate in VS Code")),
|
|
65
65
|
React.createElement(Dialog, { open: isModalOpen, onOpenChange: setIsModalOpen },
|