blob: a239142517758821e9085149799e62c8d8bc4f2f [file] [log] [blame]
Sean McCullough618bfb22025-06-25 20:52:30 +00001<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>Sketch Web Components Demo Runner</title>
Sean McCullough618bfb22025-06-25 20:52:30 +00007 <link rel="stylesheet" href="/dist/tailwind.css" />
8 <style>
9 :root {
10 --demo-primary: #0969da;
11 --demo-secondary: #656d76;
12 --demo-background: #f6f8fa;
13 --demo-border: #d1d9e0;
banksean1ee0bc62025-07-22 23:24:18 +000014 --demo-text-primary: #24292f;
15 --demo-hover-bg: #ffffff;
16 --demo-container-bg: #ffffff;
17 --demo-error-bg: #ffeaea;
18 --demo-error-border: #ffcccc;
19 --demo-error-text: #d73a49;
20 }
21
22 .dark {
23 --demo-primary: #4493f8;
24 --demo-secondary: #8b949e;
25 --demo-background: #21262d;
26 --demo-border: #30363d;
27 --demo-text-primary: #e6edf3;
28 --demo-hover-bg: #30363d;
29 --demo-container-bg: #0d1117;
30 --demo-error-bg: #3c1e1e;
31 --demo-error-border: #6a2c2c;
32 --demo-error-text: #f85149;
33 }
34
35 body {
36 background: var(--demo-container-bg);
37 color: var(--demo-text-primary);
38 transition:
39 background-color 0.2s,
40 color 0.2s;
41 margin: 0;
Sean McCullough618bfb22025-06-25 20:52:30 +000042 }
43
44 .demo-runner {
Sean McCullough4337aa72025-06-27 23:41:33 +000045 width: 100%;
Sean McCullough618bfb22025-06-25 20:52:30 +000046 display: flex;
47 height: 100vh;
48 font-family:
49 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
50 }
51
52 .demo-sidebar {
53 width: 280px;
54 background: var(--demo-background);
55 border-right: 1px solid var(--demo-border);
56 padding: 20px;
57 overflow-y: auto;
banksean1ee0bc62025-07-22 23:24:18 +000058 transition:
59 background-color 0.2s,
60 border-color 0.2s;
Sean McCullough618bfb22025-06-25 20:52:30 +000061 }
62
63 .demo-content {
64 flex: 1;
65 padding: 20px;
66 overflow-y: auto;
banksean1ee0bc62025-07-22 23:24:18 +000067 background: var(--demo-container-bg);
68 transition: background-color 0.2s;
Sean McCullough618bfb22025-06-25 20:52:30 +000069 }
70
71 .demo-nav {
72 list-style: none;
73 padding: 0;
74 margin: 0;
75 }
76
77 .demo-nav li {
78 margin-bottom: 4px;
79 }
80
81 .demo-nav button {
82 width: 100%;
83 text-align: left;
84 padding: 8px 12px;
85 background: transparent;
86 border: 1px solid transparent;
87 border-radius: 6px;
88 cursor: pointer;
89 font-size: 14px;
90 color: var(--demo-secondary);
91 transition: all 0.2s;
92 }
93
94 .demo-nav button:hover {
banksean1ee0bc62025-07-22 23:24:18 +000095 background: var(--demo-hover-bg);
Sean McCullough618bfb22025-06-25 20:52:30 +000096 border-color: var(--demo-border);
97 color: var(--demo-primary);
98 }
99
100 .demo-nav button.active {
101 background: var(--demo-primary);
102 color: white;
103 }
104
105 .demo-header {
106 margin-bottom: 20px;
107 padding-bottom: 15px;
108 border-bottom: 1px solid var(--demo-border);
109 }
110
111 .demo-title {
112 font-size: 24px;
113 font-weight: 600;
114 margin: 0 0 8px 0;
banksean1ee0bc62025-07-22 23:24:18 +0000115 color: var(--demo-text-primary);
Sean McCullough618bfb22025-06-25 20:52:30 +0000116 }
117
118 .demo-description {
119 color: var(--demo-secondary);
120 margin: 0;
121 font-size: 14px;
122 }
123
124 .demo-container {
banksean1ee0bc62025-07-22 23:24:18 +0000125 background: var(--demo-container-bg);
Sean McCullough618bfb22025-06-25 20:52:30 +0000126 border: 1px solid var(--demo-border);
127 border-radius: 8px;
128 min-height: 400px;
129 padding: 20px;
130 }
131
132 .demo-loading {
133 display: flex;
134 align-items: center;
135 justify-content: center;
136 height: 200px;
137 color: var(--demo-secondary);
138 }
139
140 .demo-welcome {
141 text-align: center;
142 padding: 60px 20px;
143 color: var(--demo-secondary);
144 }
145
146 .demo-welcome h2 {
147 margin-bottom: 10px;
banksean1ee0bc62025-07-22 23:24:18 +0000148 color: var(--demo-text-primary);
Sean McCullough618bfb22025-06-25 20:52:30 +0000149 }
150
151 .search-box {
152 width: 100%;
153 padding: 8px 12px;
154 margin-bottom: 16px;
155 border: 1px solid var(--demo-border);
156 border-radius: 6px;
157 font-size: 14px;
banksean1ee0bc62025-07-22 23:24:18 +0000158 background: var(--demo-container-bg);
159 color: var(--demo-text-primary);
160 transition:
161 background-color 0.2s,
162 border-color 0.2s,
163 color 0.2s;
164 }
165
166 .search-box::placeholder {
167 color: var(--demo-secondary);
Sean McCullough618bfb22025-06-25 20:52:30 +0000168 }
169
170 .search-box:focus {
171 outline: none;
172 border-color: var(--demo-primary);
173 }
174
175 .demo-error {
176 padding: 20px;
banksean1ee0bc62025-07-22 23:24:18 +0000177 background: var(--demo-error-bg);
178 border: 1px solid var(--demo-error-border);
Sean McCullough618bfb22025-06-25 20:52:30 +0000179 border-radius: 6px;
banksean1ee0bc62025-07-22 23:24:18 +0000180 color: var(--demo-error-text);
Sean McCullough618bfb22025-06-25 20:52:30 +0000181 }
182 </style>
183 </head>
184 <body>
185 <div class="demo-runner">
186 <nav class="demo-sidebar">
banksean1ee0bc62025-07-22 23:24:18 +0000187 <h1
188 style="
189 font-size: 18px;
190 margin: 0 0 20px 0;
191 color: var(--demo-text-primary);
192 "
193 >
Sean McCullough618bfb22025-06-25 20:52:30 +0000194 Component Demos
195 </h1>
196
banksean1ee0bc62025-07-22 23:24:18 +0000197 <div
198 style="
199 margin-bottom: 16px;
200 display: flex;
201 align-items: center;
202 justify-content: space-between;
203 "
204 >
205 <span style="font-size: 12px; color: var(--demo-secondary)"
206 >Theme:</span
207 >
208 <sketch-theme-toggle></sketch-theme-toggle>
209 </div>
210
Sean McCullough618bfb22025-06-25 20:52:30 +0000211 <input
212 type="text"
213 class="search-box"
214 placeholder="Search components..."
215 id="demo-search"
216 />
217
218 <ul class="demo-nav" id="demo-nav">
219 <!-- Component list will be populated dynamically -->
220 </ul>
221 </nav>
222
223 <main class="demo-content">
224 <div class="demo-header" id="demo-header" style="display: none">
225 <h1 class="demo-title" id="demo-title"></h1>
226 <p class="demo-description" id="demo-description"></p>
227 </div>
228
229 <div class="demo-container" id="demo-container">
230 <div class="demo-welcome">
231 <h2>Welcome to Sketch Component Demos</h2>
232 <p>Select a component from the sidebar to view its demo.</p>
233 </div>
234 </div>
235 </main>
236 </div>
237
238 <script type="module">
239 import { DemoRunner } from "./demo-framework/demo-runner.ts";
banksean1ee0bc62025-07-22 23:24:18 +0000240 import "../sketch-theme-toggle.ts";
241 import "../theme-service.ts";
Sean McCullough618bfb22025-06-25 20:52:30 +0000242
243 class DemoRunnerApp {
244 constructor() {
245 this.demoRunner = new DemoRunner({
246 container: document.getElementById("demo-container"),
247 onDemoChange: this.onDemoChange.bind(this),
248 });
249
250 this.searchBox = document.getElementById("demo-search");
251 this.navList = document.getElementById("demo-nav");
252 this.demoHeader = document.getElementById("demo-header");
253 this.demoTitle = document.getElementById("demo-title");
254 this.demoDescription = document.getElementById("demo-description");
255
256 this.currentComponent = null;
257 this.availableComponents = [];
258
banksean1ee0bc62025-07-22 23:24:18 +0000259 // Initialize theme service
260 this.initTheme();
261
Sean McCullough618bfb22025-06-25 20:52:30 +0000262 this.init();
263 }
264
banksean1ee0bc62025-07-22 23:24:18 +0000265 initTheme() {
266 // Import and initialize the theme service
267 import("../theme-service.ts").then(({ ThemeService }) => {
268 const themeService = ThemeService.getInstance();
269 themeService.initializeTheme();
270 });
271 }
272
Sean McCullough618bfb22025-06-25 20:52:30 +0000273 async init() {
274 try {
275 // Load available components
276 this.availableComponents =
277 await this.demoRunner.getAvailableComponents();
278 this.renderNavigation();
279
280 // Set up search
281 this.searchBox.addEventListener(
282 "input",
283 this.handleSearch.bind(this),
284 );
285
286 // Handle URL hash for direct linking
287 this.handleHashChange();
288 window.addEventListener(
289 "hashchange",
290 this.handleHashChange.bind(this),
291 );
292 } catch (error) {
293 console.error("Failed to initialize demo runner:", error);
294 this.showError("Failed to load demo components");
295 }
296 }
297
298 renderNavigation(filter = "") {
299 const filteredComponents = this.availableComponents.filter(
300 (component) =>
301 component.toLowerCase().includes(filter.toLowerCase()),
302 );
303
304 this.navList.innerHTML = "";
305
306 filteredComponents.forEach((component) => {
307 const li = document.createElement("li");
308 const button = document.createElement("button");
309 button.textContent = this.formatComponentName(component);
310 button.addEventListener("click", () =>
311 this.loadComponent(component),
312 );
313
314 if (component === this.currentComponent) {
315 button.classList.add("active");
316 }
317
318 li.appendChild(button);
319 this.navList.appendChild(li);
320 });
321 }
322
323 formatComponentName(component) {
324 return component
325 .replace(/^sketch-/, "")
326 .replace(/-/g, " ")
327 .replace(/\b\w/g, (l) => l.toUpperCase());
328 }
329
330 async loadComponent(componentName) {
331 if (this.currentComponent === componentName) {
332 return;
333 }
334
335 try {
336 this.showLoading();
337 await this.demoRunner.loadDemo(componentName);
338 this.currentComponent = componentName;
339
340 // Update URL hash
341 window.location.hash = componentName;
342
343 // Update navigation
344 this.renderNavigation(this.searchBox.value);
345 } catch (error) {
346 console.error(`Failed to load demo for ${componentName}:`, error);
347 this.showError(`Failed to load demo for ${componentName}`);
348 }
349 }
350
351 onDemoChange(componentName, demo) {
352 // Update header
353 this.demoTitle.textContent = demo.title;
354 this.demoDescription.textContent = demo.description || "";
355
356 if (demo.description) {
357 this.demoDescription.style.display = "block";
358 } else {
359 this.demoDescription.style.display = "none";
360 }
361
362 this.demoHeader.style.display = "block";
363 }
364
365 handleSearch(event) {
366 this.renderNavigation(event.target.value);
367 }
368
369 handleHashChange() {
370 const hash = window.location.hash.slice(1);
371 if (hash && this.availableComponents.includes(hash)) {
372 this.loadComponent(hash);
373 }
374 }
375
376 showLoading() {
377 document.getElementById("demo-container").innerHTML = `
378 <div class="demo-loading">
379 Loading demo...
380 </div>
381 `;
382 }
383
384 showError(message) {
385 document.getElementById("demo-container").innerHTML = `
386 <div class="demo-error">
387 <strong>Error:</strong> ${message}
388 </div>
389 `;
390 }
391 }
392
393 // Initialize the demo runner when DOM is ready
394 if (document.readyState === "loading") {
395 document.addEventListener(
396 "DOMContentLoaded",
397 () => new DemoRunnerApp(),
398 );
399 } else {
400 new DemoRunnerApp();
401 }
402 </script>
403 </body>
404</html>