lightview
Version:
A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation
565 lines (518 loc) • 30.1 kB
HTML
<!-- SEO-friendly SPA Shim -->
<script src="/lightview-router.js"></script>
<script>
if (globalThis.LightviewRouter) {
LightviewRouter.base('/index.html');
}
</script>
<!-- Load the page-specific stylesheet -->
<link rel="stylesheet" href="./index.css">
<!-- Gallery Structure -->
<div class="gallery-page">
<div class="gallery-layout">
<!-- Sidebar Overlay -->
<div id="sidebar-overlay" class="sidebar-overlay"></div>
<!-- Sidebar -->
<div id="gallery-sidebar" class="gallery-sidebar" style="visibility: hidden" src="./component-nav.html"></div>
<!-- Main Content -->
<div id="gallery-main" class="gallery-main">
<!-- Header Container -->
<div
style="position: sticky; top: 0; z-index: 30; background: var(--gallery-surface); border-bottom: 1px solid var(--gallery-border); backdrop-filter: blur(8px);">
<!-- Breadcrumbs Row -->
<div style="padding: 0.75rem 1.5rem 0;">
<script>
(() => {
const { Breadcrumbs } = Lightview.tags;
const breadcrumbs = Breadcrumbs({
id: 'page-breadcrumbs',
items: [
{ label: 'Components', href: '/docs/components' },
{ label: 'Button' }
]
});
document.currentScript.replaceWith(breadcrumbs.domEl);
})();
</script>
</div>
<!-- Title Row -->
<div class="gallery-header"
style="border-bottom: none; height: auto; padding-top: 0.5rem; padding-bottom: 0.75rem;">
<button id="toggle-btn" class="toggle-btn" aria-label="Toggle Sidebar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="toggle-icon"
style="stroke: currentColor; stroke-width: 2;">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
<path stroke-linecap="round" stroke-linejoin="round" d="M11 19l-7-7 7-7" />
</svg>
</button>
<h1 class="gallery-title">Button</h1>
</div>
</div>
<!-- Content -->
<div class="gallery-content">
<div class="section-content" style="max-width: 1000px;">
<p>
Buttons allow the user to take actions or make choices.
Available in multiple variants, colors, and sizes with reactive state support.
</p>
<!-- Interactive Examples -->
<!-- Interactive Examples -->
<!-- Basic Usage -->
<div class="card bg-base-200" style="margin-bottom: 2rem;">
<div class="card-body">
<h2 class="card-title">Basic Usage</h2>
<p class="text-sm" style="opacity: 0.7; margin-bottom: 1rem;">Standard buttons with default
styling.</p>
<!-- Tabs -->
<script>
globalThis.switchSyntaxTab = (tabId) => {
const tabs = ['tagged', 'vdom', 'object', 'html'];
tabs.forEach(t => {
const tabEl = document.getElementById(`tab-btn-${t}`);
const contentEl = document.getElementById(`syntax-${t}`);
if (t === tabId) {
tabEl.classList.add('syntax-tab-active');
contentEl.style.display = 'block';
} else {
tabEl.classList.remove('syntax-tab-active');
contentEl.style.display = 'none';
}
});
};
</script>
<div role="tablist" class="syntax-tabs" style="margin-bottom: 1rem;">
<button id="tab-btn-tagged" role="tab" class="syntax-tab syntax-tab-active"
onclick="switchSyntaxTab('tagged')">Tagged</button>
<button id="tab-btn-vdom" role="tab" class="syntax-tab"
onclick="switchSyntaxTab('vdom')">vDOM</button>
<button id="tab-btn-object" role="tab" class="syntax-tab"
onclick="switchSyntaxTab('object')">Object
DOM</button>
<button id="tab-btn-html" role="tab" class="syntax-tab"
onclick="switchSyntaxTab('html')">HTML</button>
</div>
<!-- Tagged Syntax -->
<div id="syntax-tagged">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js'],
styles: ['https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.min.css'],
type: 'module',
height: '100px',
autoRun: true
});
</script><code contenteditable="true">await import('/components/actions/button.js');
const { tags, $ } = Lightview;
const { div, Button } = tags;
const buttons = div({ style: 'display: flex; gap: 0.5rem;' },
Button({}, 'Default'),
Button({ color: 'primary' }, 'Primary'),
Button({ color: 'secondary' }, 'Secondary')
);
$('#example').content(buttons);</code></pre>
</div>
<!-- vDOM Syntax -->
<div id="syntax-vdom" style="display: none;">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js'],
styles: ['https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.min.css'],
type: 'module',
height: '100px'
});
</script><code contenteditable="true">await import('/components/actions/button.js');
const { $, tags } = Lightview;
const { div, Button } = tags;
const buttons = {
tag: div,
attributes: { style: 'display: flex; gap: 0.5rem;' },
children: [
{ tag: Button, children: ['Default'] },
{ tag: Button, attributes: { color: 'primary' }, children: ['Primary'] },
{ tag: Button, attributes: { color: 'secondary' }, children: ['Secondary'] }
]
};
$('#example').content(buttons);</code></pre>
</div>
<!-- Object DOM Syntax -->
<div id="syntax-object" style="display: none;">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js'],
styles: ['https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.min.css'],
type: 'module',
height: '100px'
});
</script><code contenteditable="true">// Object DOM syntax now requires lightview-x.js
await import('/components/actions/button.js');
const { $ } = Lightview;
const buttons = {
div: {
style: 'display: flex; gap: 0.5rem;',
children: [
{ Button: { children: ['Default'] } },
{ Button: { color: 'primary', children: ['Primary'] } },
{ Button: { color: 'secondary', children: ['Secondary'] } }
]
}
};
$('#example').content(buttons);</code></pre>
</div>
<!-- HTML Syntax -->
<div id="syntax-html" style="display: none;">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js'],
styles: ['https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.min.css'],
type: 'module',
language: 'html',
height: '100px'
});
</script><code contenteditable="true" class="language-html"><!-- Import the component (registers lv-button) -->
<script type="module" src="/components/actions/button.js"></script>
<div style="display: flex; gap: 0.5rem;">
<lv-button>Default</lv-button>
<lv-button color="primary">Primary</lv-button>
<lv-button color="secondary">Secondary</lv-button>
</div></code></pre>
</div>
</div>
</div>
<!-- Reactive State -->
<div class="card bg-base-200" style="margin-bottom: 2rem;">
<div class="card-body">
<h2 class="card-title">Reactive State</h2>
<p class="text-sm" style="opacity: 0.7; margin-bottom: 1rem;">Buttons with built-in loading
and state
handling.</p>
<!-- Tabs -->
<script>
globalThis.switchReactiveSyntaxTab = (tabId) => {
const tabs = ['tagged', 'vdom', 'object', 'html'];
tabs.forEach(t => {
const tabEl = document.getElementById(`reactive-tab-btn-${t}`);
const contentEl = document.getElementById(`reactive-syntax-${t}`);
if (t === tabId) {
tabEl.classList.add('syntax-tab-active');
contentEl.style.display = 'block';
} else {
tabEl.classList.remove('syntax-tab-active');
contentEl.style.display = 'none';
}
});
};
</script>
<div role="tablist" class="syntax-tabs" style="margin-bottom: 1rem;">
<button id="reactive-tab-btn-tagged" role="tab" class="syntax-tab syntax-tab-active"
onclick="switchReactiveSyntaxTab('tagged')">Tagged</button>
<button id="reactive-tab-btn-vdom" role="tab" class="syntax-tab"
onclick="switchReactiveSyntaxTab('vdom')">vDOM</button>
<button id="reactive-tab-btn-object" role="tab" class="syntax-tab"
onclick="switchReactiveSyntaxTab('object')">Object DOM</button>
<button id="reactive-tab-btn-html" role="tab" class="syntax-tab"
onclick="switchReactiveSyntaxTab('html')">HTML</button>
</div>
<!-- Tagged Syntax -->
<div id="reactive-syntax-tagged">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js'],
styles: ['https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.min.css'],
type: 'module',
height: '80px'
});
</script><code contenteditable="true">await import('/components/actions/button.js');
const { signal, tags, $ } = Lightview;
const { div, span, Button } = tags;
const isLoading = signal(false);
const button = div({ style: 'display: flex; gap: 1rem; align-items: center;' },
Button({
color: 'primary',
loading: () => isLoading.value,
onclick: async () => {
isLoading.value = true;
await new Promise(r => setTimeout(r, 2000));
isLoading.value = false;
}
}, 'Save Changes'),
span({ style: 'opacity: 0.7;' },
() => isLoading.value ? '⏳ Saving...' : 'Click to test'
)
);
$('#example').content(button);</code></pre>
</div>
<!-- vDOM Syntax -->
<div id="reactive-syntax-vdom" style="display: none;">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js'],
styles: ['https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.min.css'],
type: 'module',
minHeight: '80px'
});
</script><code contenteditable="true">await import('/components/actions/button.js');
const { signal, tags, $ } = Lightview;
const { div, span, Button } = tags;
const isLoading = signal(false);
const button = {
tag: 'div',
attributes: { style: 'display: flex; gap: 1rem; align-items: center;' },
children: [
{
tag: Button,
attributes: {
color: 'primary',
loading: () => isLoading.value,
onclick: async () => {
isLoading.value = true;
await new Promise(r => setTimeout(r, 2000));
isLoading.value = false;
}
},
children: ['Save Changes']
},
{
tag: 'span',
attributes: { style: 'opacity: 0.7;' },
children: [() => isLoading.value ? '⏳ Saving...' : 'Click to test']
}
]
};
$('#example').content(button);</code></pre>
</div>
<!-- Object DOM Syntax -->
<div id="reactive-syntax-object" style="display: none;">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js'],
styles: ['https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.min.css'],
type: 'module',
minHeight: 100
});
</script><code contenteditable="true">await import('/components/actions/button.js');
const { signal, tags, $ } = Lightview;
const { div, span, Button } = tags;
const isLoading = signal(false);
const button = {
div: {
style: 'display: flex; gap: 1rem; align-items: center;',
children: [
{
Button: {
color: 'primary',
loading: () => isLoading.value,
onclick: async () => {
isLoading.value = true;
await new Promise(r => setTimeout(r, 2000));
isLoading.value = false;
},
children: ['Save Changes']
}
},
{
span: {
style: 'opacity: 0.7;',
children: [() => isLoading.value ? '⏳ Saving...' : 'Click to test']
}
}
]
}
};
$('#example').content(button);</code></pre>
</div>
<!-- HTML Syntax -->
<div id="reactive-syntax-html" style="display: none;">
<pre><script>
examplify(document.currentScript.nextElementSibling, {
at: document.currentScript.parentElement,
scripts: ['/lightview.js', '/lightview-x.js'],
styles: ['https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.min.css'],
type: 'module',
language: 'html',
height: '100px'
});
</script><code contenteditable="true" class="language-html"><!-- Define a named signal globally -->
<script>
globalThis.signal = Lightview.signal;
signal(false, 'btnLoading');
</script>
<!-- Import Button -->
<script type="module" src="/components/actions/button.js"></script>
<div style="display: flex; gap: 1rem; align-items: center;">
<lv-button
color="primary"
loading="${signal.get('btnLoading').value}"
onclick="signal.get('btnLoading').value = true; setTimeout(() => signal.get('btnLoading').value = false, 2000)"
>
Save Changes
</lv-button>
<span style="opacity: 0.7;">
${signal.get('btnLoading').value ? '⏳ Saving...' : 'Click to test'}
</span>
</div></code></pre>
</div>
</div>
</div>
<!-- Props Table -->
<h2 class="text-xl font-bold" style="margin-bottom: 1rem;">Props</h2>
<div style="overflow-x: auto; margin-bottom: 2rem;">
<table class="table table-zebra">
<thead>
<tr>
<th>Prop</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>color</code></td>
<td>string</td>
<td>-</td>
<td>'primary' | 'secondary' | 'accent' | 'neutral' | 'info' | 'success' | 'warning'
| 'error' |
'ghost' | 'link'</td>
</tr>
<tr>
<td><code>size</code></td>
<td>string</td>
<td>'md'</td>
<td>'xs' | 'sm' | 'md' | 'lg'</td>
</tr>
<tr>
<td><code>variant</code></td>
<td>string</td>
<td>-</td>
<td>'outline' | 'soft' | 'dash' | 'wide' | 'block' | 'square' | 'circle'</td>
</tr>
<tr>
<td><code>disabled</code></td>
<td>boolean | function</td>
<td>false</td>
<td>Disable the button (supports reactive function)</td>
</tr>
<tr>
<td><code>loading</code></td>
<td>boolean | function</td>
<td>false</td>
<td>Show loading spinner (supports reactive function)</td>
</tr>
<tr>
<td><code>active</code></td>
<td>boolean</td>
<td>false</td>
<td>Force active/pressed state</td>
</tr>
<tr>
<td><code>glass</code></td>
<td>boolean</td>
<td>false</td>
<td>Glass morphism effect</td>
</tr>
<tr>
<td><code>noAnimation</code></td>
<td>boolean</td>
<td>false</td>
<td>Disable click animation</td>
</tr>
</tbody>
</table>
</div>
<!-- Custom Element Gallery -->
<h2 class="text-xl font-bold" style="margin-top: 2rem; margin-bottom: 1rem;">Button Gallery</h2>
<p class="text-sm" style="opacity: 0.7; margin-bottom: 1.5rem;">
Live examples using <code><lv-button></code> custom elements.
</p>
<script type="module" src="/components/actions/button.js"></script>
<!-- All Colors -->
<h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Colors</h3>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 2rem;">
<lv-button>Default</lv-button>
<lv-button color="neutral">Neutral</lv-button>
<lv-button color="primary">Primary</lv-button>
<lv-button color="secondary">Secondary</lv-button>
<lv-button color="accent">Accent</lv-button>
<lv-button color="info">Info</lv-button>
<lv-button color="success">Success</lv-button>
<lv-button color="warning">Warning</lv-button>
<lv-button color="error">Error</lv-button>
<lv-button color="ghost">Ghost</lv-button>
<lv-button color="link">Link</lv-button>
</div>
<!-- Variants -->
<h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Variants</h3>
<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem;">
<div>
<h4 class="text-sm font-medium" style="margin-bottom: 0.5rem; opacity: 0.8;">Outline</h4>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
<lv-button variant="outline">Default</lv-button>
<lv-button variant="outline" color="primary">Primary</lv-button>
<lv-button variant="outline" color="secondary">Secondary</lv-button>
<lv-button variant="outline" color="accent">Accent</lv-button>
</div>
</div>
<div>
<h4 class="text-sm font-medium" style="margin-bottom: 0.5rem; opacity: 0.8;">Soft</h4>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
<lv-button variant="soft" color="primary">Primary</lv-button>
<lv-button variant="soft" color="secondary">Secondary</lv-button>
<lv-button variant="soft" color="accent">Accent</lv-button>
</div>
</div>
<div>
<h4 class="text-sm font-medium" style="margin-bottom: 0.5rem; opacity: 0.8;">Ghost & Link
</h4>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
<lv-button color="ghost">Ghost</lv-button>
<lv-button color="link">Link</lv-button>
</div>
</div>
</div>
<!-- Sizes -->
<h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Sizes</h3>
<div style="display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 2rem;">
<lv-button size="xs" color="primary">Extra Small</lv-button>
<lv-button size="sm" color="primary">Small</lv-button>
<lv-button color="primary">Medium</lv-button>
<lv-button size="lg" color="primary">Large</lv-button>
</div>
<!-- States -->
<h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">States</h3>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 2rem;">
<lv-button color="primary">Normal</lv-button>
<lv-button color="primary" active="true">Active</lv-button>
<lv-button color="primary" disabled="true">Disabled</lv-button>
<lv-button color="primary" loading="true">Loading</lv-button>
</div>
<!-- Special Shapes -->
<h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Special Shapes</h3>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 2rem;">
<lv-button variant="wide" color="primary">Wide Button</lv-button>
<lv-button variant="square" color="primary">⊗</lv-button>
<lv-button variant="circle" color="secondary">✓</lv-button>
</div>
<!-- Mixed Combinations -->
<h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Mixed Combinations</h3>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 2rem;">
<lv-button size="xs" color="success">Tiny Success</lv-button>
<lv-button size="sm" variant="outline" color="info">Small Outline</lv-button>
<lv-button variant="soft" color="warning">Soft Warning</lv-button>
<lv-button size="lg" color="error">Large Error</lv-button>
<lv-button variant="outline" color="secondary" glass="true">Glass Effect</lv-button>
<lv-button size="sm" variant="soft" color="accent">Soft Accent</lv-button>
<lv-button color="ghost" size="xs">Tiny Ghost</lv-button>
<lv-button variant="wide" color="primary">Wide Primary</lv-button>
</div>
</div>
</div>
</div>
</div>
</div>