blob: a239142517758821e9085149799e62c8d8bc4f2f [file] [log] [blame]
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sketch Web Components Demo Runner</title>
<link rel="stylesheet" href="/dist/tailwind.css" />
<style>
:root {
--demo-primary: #0969da;
--demo-secondary: #656d76;
--demo-background: #f6f8fa;
--demo-border: #d1d9e0;
--demo-text-primary: #24292f;
--demo-hover-bg: #ffffff;
--demo-container-bg: #ffffff;
--demo-error-bg: #ffeaea;
--demo-error-border: #ffcccc;
--demo-error-text: #d73a49;
}
.dark {
--demo-primary: #4493f8;
--demo-secondary: #8b949e;
--demo-background: #21262d;
--demo-border: #30363d;
--demo-text-primary: #e6edf3;
--demo-hover-bg: #30363d;
--demo-container-bg: #0d1117;
--demo-error-bg: #3c1e1e;
--demo-error-border: #6a2c2c;
--demo-error-text: #f85149;
}
body {
background: var(--demo-container-bg);
color: var(--demo-text-primary);
transition:
background-color 0.2s,
color 0.2s;
margin: 0;
}
.demo-runner {
width: 100%;
display: flex;
height: 100vh;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.demo-sidebar {
width: 280px;
background: var(--demo-background);
border-right: 1px solid var(--demo-border);
padding: 20px;
overflow-y: auto;
transition:
background-color 0.2s,
border-color 0.2s;
}
.demo-content {
flex: 1;
padding: 20px;
overflow-y: auto;
background: var(--demo-container-bg);
transition: background-color 0.2s;
}
.demo-nav {
list-style: none;
padding: 0;
margin: 0;
}
.demo-nav li {
margin-bottom: 4px;
}
.demo-nav button {
width: 100%;
text-align: left;
padding: 8px 12px;
background: transparent;
border: 1px solid transparent;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
color: var(--demo-secondary);
transition: all 0.2s;
}
.demo-nav button:hover {
background: var(--demo-hover-bg);
border-color: var(--demo-border);
color: var(--demo-primary);
}
.demo-nav button.active {
background: var(--demo-primary);
color: white;
}
.demo-header {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid var(--demo-border);
}
.demo-title {
font-size: 24px;
font-weight: 600;
margin: 0 0 8px 0;
color: var(--demo-text-primary);
}
.demo-description {
color: var(--demo-secondary);
margin: 0;
font-size: 14px;
}
.demo-container {
background: var(--demo-container-bg);
border: 1px solid var(--demo-border);
border-radius: 8px;
min-height: 400px;
padding: 20px;
}
.demo-loading {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
color: var(--demo-secondary);
}
.demo-welcome {
text-align: center;
padding: 60px 20px;
color: var(--demo-secondary);
}
.demo-welcome h2 {
margin-bottom: 10px;
color: var(--demo-text-primary);
}
.search-box {
width: 100%;
padding: 8px 12px;
margin-bottom: 16px;
border: 1px solid var(--demo-border);
border-radius: 6px;
font-size: 14px;
background: var(--demo-container-bg);
color: var(--demo-text-primary);
transition:
background-color 0.2s,
border-color 0.2s,
color 0.2s;
}
.search-box::placeholder {
color: var(--demo-secondary);
}
.search-box:focus {
outline: none;
border-color: var(--demo-primary);
}
.demo-error {
padding: 20px;
background: var(--demo-error-bg);
border: 1px solid var(--demo-error-border);
border-radius: 6px;
color: var(--demo-error-text);
}
</style>
</head>
<body>
<div class="demo-runner">
<nav class="demo-sidebar">
<h1
style="
font-size: 18px;
margin: 0 0 20px 0;
color: var(--demo-text-primary);
"
>
Component Demos
</h1>
<div
style="
margin-bottom: 16px;
display: flex;
align-items: center;
justify-content: space-between;
"
>
<span style="font-size: 12px; color: var(--demo-secondary)"
>Theme:</span
>
<sketch-theme-toggle></sketch-theme-toggle>
</div>
<input
type="text"
class="search-box"
placeholder="Search components..."
id="demo-search"
/>
<ul class="demo-nav" id="demo-nav">
<!-- Component list will be populated dynamically -->
</ul>
</nav>
<main class="demo-content">
<div class="demo-header" id="demo-header" style="display: none">
<h1 class="demo-title" id="demo-title"></h1>
<p class="demo-description" id="demo-description"></p>
</div>
<div class="demo-container" id="demo-container">
<div class="demo-welcome">
<h2>Welcome to Sketch Component Demos</h2>
<p>Select a component from the sidebar to view its demo.</p>
</div>
</div>
</main>
</div>
<script type="module">
import { DemoRunner } from "./demo-framework/demo-runner.ts";
import "../sketch-theme-toggle.ts";
import "../theme-service.ts";
class DemoRunnerApp {
constructor() {
this.demoRunner = new DemoRunner({
container: document.getElementById("demo-container"),
onDemoChange: this.onDemoChange.bind(this),
});
this.searchBox = document.getElementById("demo-search");
this.navList = document.getElementById("demo-nav");
this.demoHeader = document.getElementById("demo-header");
this.demoTitle = document.getElementById("demo-title");
this.demoDescription = document.getElementById("demo-description");
this.currentComponent = null;
this.availableComponents = [];
// Initialize theme service
this.initTheme();
this.init();
}
initTheme() {
// Import and initialize the theme service
import("../theme-service.ts").then(({ ThemeService }) => {
const themeService = ThemeService.getInstance();
themeService.initializeTheme();
});
}
async init() {
try {
// Load available components
this.availableComponents =
await this.demoRunner.getAvailableComponents();
this.renderNavigation();
// Set up search
this.searchBox.addEventListener(
"input",
this.handleSearch.bind(this),
);
// Handle URL hash for direct linking
this.handleHashChange();
window.addEventListener(
"hashchange",
this.handleHashChange.bind(this),
);
} catch (error) {
console.error("Failed to initialize demo runner:", error);
this.showError("Failed to load demo components");
}
}
renderNavigation(filter = "") {
const filteredComponents = this.availableComponents.filter(
(component) =>
component.toLowerCase().includes(filter.toLowerCase()),
);
this.navList.innerHTML = "";
filteredComponents.forEach((component) => {
const li = document.createElement("li");
const button = document.createElement("button");
button.textContent = this.formatComponentName(component);
button.addEventListener("click", () =>
this.loadComponent(component),
);
if (component === this.currentComponent) {
button.classList.add("active");
}
li.appendChild(button);
this.navList.appendChild(li);
});
}
formatComponentName(component) {
return component
.replace(/^sketch-/, "")
.replace(/-/g, " ")
.replace(/\b\w/g, (l) => l.toUpperCase());
}
async loadComponent(componentName) {
if (this.currentComponent === componentName) {
return;
}
try {
this.showLoading();
await this.demoRunner.loadDemo(componentName);
this.currentComponent = componentName;
// Update URL hash
window.location.hash = componentName;
// Update navigation
this.renderNavigation(this.searchBox.value);
} catch (error) {
console.error(`Failed to load demo for ${componentName}:`, error);
this.showError(`Failed to load demo for ${componentName}`);
}
}
onDemoChange(componentName, demo) {
// Update header
this.demoTitle.textContent = demo.title;
this.demoDescription.textContent = demo.description || "";
if (demo.description) {
this.demoDescription.style.display = "block";
} else {
this.demoDescription.style.display = "none";
}
this.demoHeader.style.display = "block";
}
handleSearch(event) {
this.renderNavigation(event.target.value);
}
handleHashChange() {
const hash = window.location.hash.slice(1);
if (hash && this.availableComponents.includes(hash)) {
this.loadComponent(hash);
}
}
showLoading() {
document.getElementById("demo-container").innerHTML = `
<div class="demo-loading">
Loading demo...
</div>
`;
}
showError(message) {
document.getElementById("demo-container").innerHTML = `
<div class="demo-error">
<strong>Error:</strong> ${message}
</div>
`;
}
}
// Initialize the demo runner when DOM is ready
if (document.readyState === "loading") {
document.addEventListener(
"DOMContentLoaded",
() => new DemoRunnerApp(),
);
} else {
new DemoRunnerApp();
}
</script>
</body>
</html>