blob: fe12f1849ac20c5d8c10620e8035ce5434479de6 [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;
14 }
15
16 .demo-runner {
Sean McCullough4337aa72025-06-27 23:41:33 +000017 width: 100%;
Sean McCullough618bfb22025-06-25 20:52:30 +000018 display: flex;
19 height: 100vh;
20 font-family:
21 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
22 }
23
24 .demo-sidebar {
25 width: 280px;
26 background: var(--demo-background);
27 border-right: 1px solid var(--demo-border);
28 padding: 20px;
29 overflow-y: auto;
30 }
31
32 .demo-content {
33 flex: 1;
34 padding: 20px;
35 overflow-y: auto;
36 }
37
38 .demo-nav {
39 list-style: none;
40 padding: 0;
41 margin: 0;
42 }
43
44 .demo-nav li {
45 margin-bottom: 4px;
46 }
47
48 .demo-nav button {
49 width: 100%;
50 text-align: left;
51 padding: 8px 12px;
52 background: transparent;
53 border: 1px solid transparent;
54 border-radius: 6px;
55 cursor: pointer;
56 font-size: 14px;
57 color: var(--demo-secondary);
58 transition: all 0.2s;
59 }
60
61 .demo-nav button:hover {
62 background: #ffffff;
63 border-color: var(--demo-border);
64 color: var(--demo-primary);
65 }
66
67 .demo-nav button.active {
68 background: var(--demo-primary);
69 color: white;
70 }
71
72 .demo-header {
73 margin-bottom: 20px;
74 padding-bottom: 15px;
75 border-bottom: 1px solid var(--demo-border);
76 }
77
78 .demo-title {
79 font-size: 24px;
80 font-weight: 600;
81 margin: 0 0 8px 0;
82 color: #24292f;
83 }
84
85 .demo-description {
86 color: var(--demo-secondary);
87 margin: 0;
88 font-size: 14px;
89 }
90
91 .demo-container {
92 background: white;
93 border: 1px solid var(--demo-border);
94 border-radius: 8px;
95 min-height: 400px;
96 padding: 20px;
97 }
98
99 .demo-loading {
100 display: flex;
101 align-items: center;
102 justify-content: center;
103 height: 200px;
104 color: var(--demo-secondary);
105 }
106
107 .demo-welcome {
108 text-align: center;
109 padding: 60px 20px;
110 color: var(--demo-secondary);
111 }
112
113 .demo-welcome h2 {
114 margin-bottom: 10px;
115 color: #24292f;
116 }
117
118 .search-box {
119 width: 100%;
120 padding: 8px 12px;
121 margin-bottom: 16px;
122 border: 1px solid var(--demo-border);
123 border-radius: 6px;
124 font-size: 14px;
125 }
126
127 .search-box:focus {
128 outline: none;
129 border-color: var(--demo-primary);
130 }
131
132 .demo-error {
133 padding: 20px;
134 background: #ffeaea;
135 border: 1px solid #ffcccc;
136 border-radius: 6px;
137 color: #d73a49;
138 }
139 </style>
140 </head>
141 <body>
142 <div class="demo-runner">
143 <nav class="demo-sidebar">
144 <h1 style="font-size: 18px; margin: 0 0 20px 0; color: #24292f">
145 Component Demos
146 </h1>
147
148 <input
149 type="text"
150 class="search-box"
151 placeholder="Search components..."
152 id="demo-search"
153 />
154
155 <ul class="demo-nav" id="demo-nav">
156 <!-- Component list will be populated dynamically -->
157 </ul>
158 </nav>
159
160 <main class="demo-content">
161 <div class="demo-header" id="demo-header" style="display: none">
162 <h1 class="demo-title" id="demo-title"></h1>
163 <p class="demo-description" id="demo-description"></p>
164 </div>
165
166 <div class="demo-container" id="demo-container">
167 <div class="demo-welcome">
168 <h2>Welcome to Sketch Component Demos</h2>
169 <p>Select a component from the sidebar to view its demo.</p>
170 </div>
171 </div>
172 </main>
173 </div>
174
175 <script type="module">
176 import { DemoRunner } from "./demo-framework/demo-runner.ts";
177
178 class DemoRunnerApp {
179 constructor() {
180 this.demoRunner = new DemoRunner({
181 container: document.getElementById("demo-container"),
182 onDemoChange: this.onDemoChange.bind(this),
183 });
184
185 this.searchBox = document.getElementById("demo-search");
186 this.navList = document.getElementById("demo-nav");
187 this.demoHeader = document.getElementById("demo-header");
188 this.demoTitle = document.getElementById("demo-title");
189 this.demoDescription = document.getElementById("demo-description");
190
191 this.currentComponent = null;
192 this.availableComponents = [];
193
194 this.init();
195 }
196
197 async init() {
198 try {
199 // Load available components
200 this.availableComponents =
201 await this.demoRunner.getAvailableComponents();
202 this.renderNavigation();
203
204 // Set up search
205 this.searchBox.addEventListener(
206 "input",
207 this.handleSearch.bind(this),
208 );
209
210 // Handle URL hash for direct linking
211 this.handleHashChange();
212 window.addEventListener(
213 "hashchange",
214 this.handleHashChange.bind(this),
215 );
216 } catch (error) {
217 console.error("Failed to initialize demo runner:", error);
218 this.showError("Failed to load demo components");
219 }
220 }
221
222 renderNavigation(filter = "") {
223 const filteredComponents = this.availableComponents.filter(
224 (component) =>
225 component.toLowerCase().includes(filter.toLowerCase()),
226 );
227
228 this.navList.innerHTML = "";
229
230 filteredComponents.forEach((component) => {
231 const li = document.createElement("li");
232 const button = document.createElement("button");
233 button.textContent = this.formatComponentName(component);
234 button.addEventListener("click", () =>
235 this.loadComponent(component),
236 );
237
238 if (component === this.currentComponent) {
239 button.classList.add("active");
240 }
241
242 li.appendChild(button);
243 this.navList.appendChild(li);
244 });
245 }
246
247 formatComponentName(component) {
248 return component
249 .replace(/^sketch-/, "")
250 .replace(/-/g, " ")
251 .replace(/\b\w/g, (l) => l.toUpperCase());
252 }
253
254 async loadComponent(componentName) {
255 if (this.currentComponent === componentName) {
256 return;
257 }
258
259 try {
260 this.showLoading();
261 await this.demoRunner.loadDemo(componentName);
262 this.currentComponent = componentName;
263
264 // Update URL hash
265 window.location.hash = componentName;
266
267 // Update navigation
268 this.renderNavigation(this.searchBox.value);
269 } catch (error) {
270 console.error(`Failed to load demo for ${componentName}:`, error);
271 this.showError(`Failed to load demo for ${componentName}`);
272 }
273 }
274
275 onDemoChange(componentName, demo) {
276 // Update header
277 this.demoTitle.textContent = demo.title;
278 this.demoDescription.textContent = demo.description || "";
279
280 if (demo.description) {
281 this.demoDescription.style.display = "block";
282 } else {
283 this.demoDescription.style.display = "none";
284 }
285
286 this.demoHeader.style.display = "block";
287 }
288
289 handleSearch(event) {
290 this.renderNavigation(event.target.value);
291 }
292
293 handleHashChange() {
294 const hash = window.location.hash.slice(1);
295 if (hash && this.availableComponents.includes(hash)) {
296 this.loadComponent(hash);
297 }
298 }
299
300 showLoading() {
301 document.getElementById("demo-container").innerHTML = `
302 <div class="demo-loading">
303 Loading demo...
304 </div>
305 `;
306 }
307
308 showError(message) {
309 document.getElementById("demo-container").innerHTML = `
310 <div class="demo-error">
311 <strong>Error:</strong> ${message}
312 </div>
313 `;
314 }
315 }
316
317 // Initialize the demo runner when DOM is ready
318 if (document.readyState === "loading") {
319 document.addEventListener(
320 "DOMContentLoaded",
321 () => new DemoRunnerApp(),
322 );
323 } else {
324 new DemoRunnerApp();
325 }
326 </script>
327 </body>
328</html>