Changeset 0.37.2 (#214)
Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
This commit is contained in:
359
src/dev.html
Normal file
359
src/dev.html
Normal file
@@ -0,0 +1,359 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Chat Switchboard — Dev Gallery</title>
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/sw-primitives.css">
|
||||
<link rel="stylesheet" href="css/sw-shell.css">
|
||||
<style>
|
||||
body { overflow: auto; padding: 0; }
|
||||
.dev-root { height: 100vh; display: flex; flex-direction: column; }
|
||||
.dev-nav { display: flex; gap: 0; border-bottom: 1px solid var(--border); background: var(--bg-surface); flex-shrink: 0; padding: 0 1rem; }
|
||||
.dev-nav button {
|
||||
padding: 0.6rem 1.2rem; background: none; border: none; border-bottom: 2px solid transparent;
|
||||
color: var(--text-2); font-family: var(--font); font-size: 0.85rem; font-weight: 500; cursor: pointer;
|
||||
}
|
||||
.dev-nav button:hover { color: var(--text); }
|
||||
.dev-nav button.active { color: var(--accent); border-bottom-color: var(--accent); }
|
||||
.dev-page { flex: 1; overflow: auto; }
|
||||
.gallery { max-width: 800px; margin: 0 auto; padding: 2rem; }
|
||||
.gallery h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
|
||||
.gallery .subtitle { color: var(--text-2); margin-bottom: 2rem; font-size: 0.9rem; }
|
||||
.section { margin-bottom: 2.5rem; }
|
||||
.section h2 {
|
||||
font-size: 0.85rem; font-weight: 600; text-transform: uppercase;
|
||||
letter-spacing: 0.5px; color: var(--text-3); margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem; border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.row { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; margin-bottom: 0.75rem; }
|
||||
.col { display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
.spacer { height: 0.5rem; }
|
||||
.shell-demo { height: 100%; }
|
||||
.shell-demo .sw-shell { height: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
(async () => {
|
||||
try {
|
||||
const { h, render } = await import('./js/sw/vendor/preact.module.js');
|
||||
const hooksModule = await import('./js/sw/vendor/hooks.module.js?v=2');
|
||||
const { default: htm } = await import('./js/sw/vendor/htm.module.js');
|
||||
const html = htm.bind(h);
|
||||
|
||||
const { useState, useEffect, useRef, useMemo, useCallback } = hooksModule;
|
||||
window.preact = { h, render };
|
||||
window.hooks = hooksModule;
|
||||
window.html = html;
|
||||
|
||||
const { Button } = await import('./js/sw/primitives/button.js');
|
||||
const { Spinner } = await import('./js/sw/primitives/spinner.js');
|
||||
const { Avatar } = await import('./js/sw/primitives/avatar.js');
|
||||
const { FormField } = await import('./js/sw/primitives/form-field.js');
|
||||
const { Tooltip } = await import('./js/sw/primitives/tooltip.js');
|
||||
const { Banner } = await import('./js/sw/primitives/banner.js');
|
||||
const { Dialog } = await import('./js/sw/primitives/dialog.js');
|
||||
const { toast, ToastContainer } = await import('./js/sw/primitives/toast.js');
|
||||
const { Menu } = await import('./js/sw/primitives/menu.js');
|
||||
const { Drawer } = await import('./js/sw/primitives/drawer.js');
|
||||
const { Tabs } = await import('./js/sw/primitives/tabs.js');
|
||||
const { Dropdown } = await import('./js/sw/primitives/dropdown.js');
|
||||
const { AppShell } = await import('./js/sw/shell/app-shell.js');
|
||||
|
||||
function PrimitivesPage() {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState('tab1');
|
||||
const [dropVal, setDropVal] = useState('opt2');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const menuAnchor = useRef(null);
|
||||
|
||||
return html`
|
||||
<div class="gallery">
|
||||
<h1>Primitives Gallery</h1>
|
||||
<p class="subtitle">Layer 0 — Preact + htm components (v0.37.2)</p>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="section">
|
||||
<h2>Button</h2>
|
||||
<div class="row">
|
||||
<${Button} variant="primary">Primary<//>
|
||||
<${Button} variant="secondary">Secondary<//>
|
||||
<${Button} variant="danger">Danger<//>
|
||||
<${Button} variant="ghost">Ghost<//>
|
||||
</div>
|
||||
<div class="row">
|
||||
<${Button} size="sm">Small<//>
|
||||
<${Button} size="md">Medium<//>
|
||||
<${Button} size="lg">Large<//>
|
||||
</div>
|
||||
<div class="row">
|
||||
<${Button} disabled>Disabled<//>
|
||||
<${Button} loading=${true}>Loading<//>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Spinner -->
|
||||
<div class="section">
|
||||
<h2>Spinner</h2>
|
||||
<div class="row">
|
||||
<${Spinner} size="sm" />
|
||||
<${Spinner} size="md" />
|
||||
<${Spinner} size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Avatar -->
|
||||
<div class="section">
|
||||
<h2>Avatar</h2>
|
||||
<div class="row">
|
||||
<${Avatar} name="Jane Doe" size="sm" />
|
||||
<${Avatar} name="Jane Doe" size="md" />
|
||||
<${Avatar} name="Jane Doe" size="lg" />
|
||||
<${Avatar} name="X" size="md" />
|
||||
<${Avatar} src="https://i.pravatar.cc/48" name="Photo" size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FormField -->
|
||||
<div class="section">
|
||||
<h2>FormField</h2>
|
||||
<div class="col" style="max-width:320px">
|
||||
<${FormField} label="Username" required>
|
||||
<input class="sw-input" placeholder="Enter username" />
|
||||
<//>
|
||||
<${FormField} label="Email" error="Invalid email address">
|
||||
<input class="sw-input" value="bad@" />
|
||||
<//>
|
||||
<${FormField} label="Bio" hint="Optional, max 200 chars">
|
||||
<textarea class="sw-input" rows="2" placeholder="Tell us about yourself" />
|
||||
<//>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tooltip -->
|
||||
<div class="section">
|
||||
<h2>Tooltip</h2>
|
||||
<div class="row">
|
||||
<${Tooltip} content="Top tooltip">
|
||||
<${Button} variant="secondary">Hover me (top)<//>
|
||||
<//>
|
||||
<${Tooltip} content="Bottom tooltip" position="bottom">
|
||||
<${Button} variant="secondary">Bottom<//>
|
||||
<//>
|
||||
<${Tooltip} content="Right tooltip" position="right">
|
||||
<${Button} variant="secondary">Right<//>
|
||||
<//>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Banner -->
|
||||
<div class="section">
|
||||
<h2>Banner</h2>
|
||||
<div class="col">
|
||||
<${Banner} text="Informational banner message" variant="info" />
|
||||
<${Banner} text="Warning: maintenance scheduled" variant="warn" dismissible />
|
||||
<${Banner} text="Error: service unavailable" variant="error" />
|
||||
<${Banner} text="Successfully deployed v0.37.2" variant="success" dismissible />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="section">
|
||||
<h2>Tabs</h2>
|
||||
<${Tabs}
|
||||
tabs=${[
|
||||
{ label: 'General', value: 'tab1' },
|
||||
{ label: 'Appearance', value: 'tab2' },
|
||||
{ label: 'Notifications', value: 'tab3' },
|
||||
{ label: 'Disabled', value: 'tab4', disabled: true },
|
||||
]}
|
||||
active=${activeTab}
|
||||
onChange=${setActiveTab}
|
||||
/>
|
||||
<div style="padding: 1rem 0; color: var(--text-2); font-size: 0.9rem">
|
||||
Active tab: ${activeTab}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dropdown -->
|
||||
<div class="section">
|
||||
<h2>Dropdown</h2>
|
||||
<div class="row">
|
||||
<div style="width:220px">
|
||||
<${Dropdown}
|
||||
options=${[
|
||||
{ label: 'Claude Sonnet', value: 'opt1' },
|
||||
{ label: 'Claude Opus', value: 'opt2' },
|
||||
{ label: 'Claude Haiku', value: 'opt3' },
|
||||
{ label: 'GPT-4o', value: 'opt4' },
|
||||
{ label: 'Disabled', value: 'opt5', disabled: true },
|
||||
]}
|
||||
value=${dropVal}
|
||||
onChange=${setDropVal}
|
||||
searchable
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menu -->
|
||||
<div class="section">
|
||||
<h2>Menu</h2>
|
||||
<div class="row">
|
||||
<${Button} ref=${menuAnchor} variant="secondary"
|
||||
onClick=${() => setMenuOpen(!menuOpen)}>
|
||||
Open Menu
|
||||
<//>
|
||||
<${Menu}
|
||||
open=${menuOpen}
|
||||
anchor=${menuAnchor.current}
|
||||
items=${[
|
||||
{ label: 'Settings', action: 'settings', icon: '\u2699' },
|
||||
{ label: 'Profile', action: 'profile', icon: '\ud83d\udc64' },
|
||||
{ divider: true },
|
||||
{ label: 'Submenu', children: [
|
||||
{ label: 'Option A', action: 'a' },
|
||||
{ label: 'Option B', action: 'b' },
|
||||
]},
|
||||
{ divider: true },
|
||||
{ label: 'Disabled', action: 'x', disabled: true },
|
||||
{ label: 'Sign Out', action: 'logout' },
|
||||
]}
|
||||
onSelect=${(action) => { toast.info('Selected: ' + action); }}
|
||||
onClose=${() => setMenuOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dialog -->
|
||||
<div class="section">
|
||||
<h2>Dialog</h2>
|
||||
<div class="row">
|
||||
<${Button} onClick=${() => setDialogOpen(true)}>Open Dialog<//>
|
||||
</div>
|
||||
<${Dialog}
|
||||
open=${dialogOpen}
|
||||
title="Example Dialog"
|
||||
onClose=${() => setDialogOpen(false)}
|
||||
actions=${[
|
||||
{ label: 'Cancel', variant: 'secondary', onClick: () => setDialogOpen(false) },
|
||||
{ label: 'Confirm', variant: 'primary', onClick: () => { setDialogOpen(false); toast.success('Confirmed!'); } },
|
||||
]}
|
||||
>
|
||||
<p>This is a modal dialog with focus trapping, Escape to close, and backdrop click to dismiss.</p>
|
||||
<//>
|
||||
</div>
|
||||
|
||||
<!-- Drawer -->
|
||||
<div class="section">
|
||||
<h2>Drawer</h2>
|
||||
<div class="row">
|
||||
<${Button} variant="secondary" onClick=${() => setDrawerOpen(true)}>Open Drawer<//>
|
||||
</div>
|
||||
<${Drawer}
|
||||
open=${drawerOpen}
|
||||
title="Settings"
|
||||
onClose=${() => setDrawerOpen(false)}
|
||||
>
|
||||
<p style="color: var(--text-2)">Slide-in panel for settings, admin sections, etc.</p>
|
||||
<div class="spacer" />
|
||||
<${FormField} label="Display Name">
|
||||
<input class="sw-input" value="Jane Doe" />
|
||||
<//>
|
||||
<//>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="section">
|
||||
<h2>Toast</h2>
|
||||
<div class="row">
|
||||
<${Button} variant="secondary" onClick=${() => toast.success('Operation successful')}>Success<//>
|
||||
<${Button} variant="secondary" onClick=${() => toast.error('Something went wrong')}>Error<//>
|
||||
<${Button} variant="secondary" onClick=${() => toast.warn('Check your settings')}>Warning<//>
|
||||
<${Button} variant="secondary" onClick=${() => toast.info('New message received')}>Info<//>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<${ToastContainer} />
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const VARIANTS = ['info', 'warn', 'error', 'success'];
|
||||
|
||||
function ShellControls({ bannerOn, setBannerOn, bannerVariant, setBannerVariant, bannerText, setBannerText,
|
||||
msgOn, setMsgOn, msgVariant, setMsgVariant, msgText, setMsgText,
|
||||
footerOn, setFooterOn }) {
|
||||
return html`
|
||||
<div style="display:flex; align-items:center; gap:0.75rem; padding:0.4rem 0; flex-wrap:wrap;">
|
||||
<${Button} variant=${bannerOn ? 'primary' : 'secondary'} size="sm"
|
||||
onClick=${() => setBannerOn(!bannerOn)}>Banner<//>
|
||||
${bannerOn && html`
|
||||
<${Dropdown} options=${VARIANTS.map(v => ({ label: v, value: v }))}
|
||||
value=${bannerVariant} onChange=${setBannerVariant} />
|
||||
<input class="sw-input" style="width:140px" placeholder="text..."
|
||||
value=${bannerText} onInput=${e => setBannerText(e.target.value)} />
|
||||
`}
|
||||
<${Button} variant=${msgOn ? 'primary' : 'secondary'} size="sm"
|
||||
onClick=${() => setMsgOn(!msgOn)}>Message<//>
|
||||
${msgOn && html`
|
||||
<${Dropdown} options=${VARIANTS.map(v => ({ label: v, value: v }))}
|
||||
value=${msgVariant} onChange=${setMsgVariant} />
|
||||
<input class="sw-input" style="width:140px" placeholder="text..."
|
||||
value=${msgText} onInput=${e => setMsgText(e.target.value)} />
|
||||
`}
|
||||
<${Button} variant=${footerOn ? 'primary' : 'secondary'} size="sm"
|
||||
onClick=${() => setFooterOn(!footerOn)}>Footer<//>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function DevRoot() {
|
||||
const [page, setPage] = useState('primitives');
|
||||
const [bannerOn, setBannerOn] = useState(false);
|
||||
const [msgOn, setMsgOn] = useState(false);
|
||||
const [footerOn, setFooterOn] = useState(false);
|
||||
const [bannerVariant, setBannerVariant] = useState('info');
|
||||
const [msgVariant, setMsgVariant] = useState('info');
|
||||
const [bannerText, setBannerText] = useState('');
|
||||
const [msgText, setMsgText] = useState('');
|
||||
|
||||
const banner = bannerOn ? { text: bannerText || 'Banner', variant: bannerVariant } : null;
|
||||
const msg = msgOn ? { text: msgText || 'Message', variant: msgVariant } : null;
|
||||
|
||||
return html`
|
||||
<${AppShell}
|
||||
banner=${banner}
|
||||
message=${msg}
|
||||
footer=${footerOn ? html`Chat Switchboard · Layer 1 Shell` : null}
|
||||
>
|
||||
<div class="dev-root">
|
||||
<nav class="dev-nav">
|
||||
<button class=${page === 'primitives' ? 'active' : ''} onClick=${() => setPage('primitives')}>Primitives</button>
|
||||
<button class=${page === 'shell' ? 'active' : ''} onClick=${() => setPage('shell')}>Shell</button>
|
||||
<div style="flex:1" />
|
||||
<${ShellControls} ...${{
|
||||
bannerOn, setBannerOn, bannerVariant, setBannerVariant, bannerText, setBannerText,
|
||||
msgOn, setMsgOn, msgVariant, setMsgVariant, msgText, setMsgText,
|
||||
footerOn, setFooterOn
|
||||
}} />
|
||||
</nav>
|
||||
<div class="dev-page">
|
||||
${page === 'primitives' && html`<${PrimitivesPage} />`}
|
||||
</div>
|
||||
</div>
|
||||
<//>
|
||||
`;
|
||||
}
|
||||
|
||||
render(html`<${DevRoot} />`, document.getElementById('app'));
|
||||
} catch(e) { console.error('DEV:', e); }
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user