v1.1: Complete remaining 8 feature modules + 7 CSS component sections
JS modules added:
- lt.sidebarSubmenus — nested nav groups with expand/collapse, auto-opens active
- lt.infiniteScroll — IntersectionObserver-based (scroll fallback), loading indicator
- lt.wizard — multi-step form with indicators, validation hook, getData()
- lt.sortable — HTML5 drag-to-reorder lists with placeholder ghost + bus event
- lt.timer — countdown (urgent threshold + onExpire) + stopwatch (pause/reset)
- lt.lightbox — full-screen image viewer, prev/next, ESC, caption, loop
- lt.auth — JWT token management: setToken, refresh (auto + manual),
401 retry, onExpire hook, patches lt.api with Bearer header
- lt.markdown — micro-renderer (no deps); auto-delegates to window.marked /
markdownit if present; renders headings/bold/italic/code/
links/lists/blockquotes/tables/HR
CSS sections added (69–75):
- Infinite scroll sentinel + loading indicator
- Wizard step indicators (connectors, active/complete/error states, nav footer)
- Sortable item dragging + placeholder ghost
- Countdown/timer display + urgency blink animation
- Image lightbox overlay (close/prev/next controls, caption, counter)
- Sidebar submenu groups (chevron, expand/collapse, active sub-link)
- Markdown output styling (.lt-markdown — all block elements themed)
HTML demos for all 8 new components added and wired
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1256,6 +1256,191 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Wizard / Multi-Step Form -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// WIZARD / MULTI-STEP FORM</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div id="demo-wizard">
|
||||
<!-- Step indicators -->
|
||||
<div class="lt-wizard-steps">
|
||||
<div class="lt-wizard-step is-active" data-wizard-indicator>
|
||||
<div class="lt-wizard-num">1</div>
|
||||
<div class="lt-wizard-label">Details</div>
|
||||
</div>
|
||||
<div class="lt-wizard-step" data-wizard-indicator>
|
||||
<div class="lt-wizard-num">2</div>
|
||||
<div class="lt-wizard-label">Assign</div>
|
||||
</div>
|
||||
<div class="lt-wizard-step" data-wizard-indicator>
|
||||
<div class="lt-wizard-num">3</div>
|
||||
<div class="lt-wizard-label">Review</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Counter -->
|
||||
<p class="lt-wizard-counter">Step <strong data-wizard-current>1</strong> of <strong data-wizard-total>3</strong></p>
|
||||
<!-- Steps -->
|
||||
<div data-wizard-step="1" class="is-active" aria-hidden="false">
|
||||
<div class="lt-grid lt-grid-2" style="gap:1rem;margin-top:1rem">
|
||||
<div class="lt-form-group"><label class="lt-label">Ticket Title</label><input type="text" name="title" class="lt-input lt-w-full" placeholder="Brief description…"></div>
|
||||
<div class="lt-form-group"><label class="lt-label">Priority</label><select name="priority" class="lt-select lt-w-full"><option value="p1">P1 Critical</option><option value="p2">P2 High</option><option value="p3" selected>P3 Medium</option><option value="p4">P4 Low</option></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-wizard-step="2" aria-hidden="true">
|
||||
<div class="lt-form-group" style="margin-top:1rem"><label class="lt-label">Assign To</label><input type="text" name="assignee" class="lt-input lt-w-full" placeholder="Username…"></div>
|
||||
<div class="lt-form-group"><label class="lt-label">Due Date</label><input type="date" name="due" class="lt-input"></div>
|
||||
</div>
|
||||
<div data-wizard-step="3" aria-hidden="true">
|
||||
<div class="lt-empty-state lt-empty-state--sm"><div class="lt-empty-state-icon">✅</div><div class="lt-empty-state-title">Review & Submit</div><div class="lt-empty-state-body">Check the details above and click Submit when ready.</div></div>
|
||||
</div>
|
||||
<!-- Nav -->
|
||||
<div class="lt-wizard-nav">
|
||||
<button class="lt-btn lt-btn-sm" data-wizard-prev disabled>← Back</button>
|
||||
<button class="lt-btn lt-btn-primary lt-btn-sm" data-wizard-next>Next →</button>
|
||||
<button class="lt-btn lt-btn-primary lt-btn-sm" data-wizard-done style="display:none">Submit ✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sortable List -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// DRAG-TO-REORDER (SORTABLE)</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-grid lt-grid-2" style="gap:1.5rem">
|
||||
<div>
|
||||
<p style="font-size:0.72rem;color:var(--text-muted);margin-bottom:0.75rem">Drag items to reorder. Uses native HTML5 drag API with pointer-events fallback.</p>
|
||||
<ul id="demo-sortable" style="list-style:none;padding:0;display:flex;flex-direction:column;gap:4px">
|
||||
<li data-id="p1" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab">⠿</span> P1 — Storage link-down</li>
|
||||
<li data-id="p2" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab">⠿</span> P2 — Switch port flapping</li>
|
||||
<li data-id="p3" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab">⠿</span> P3 — SFP+ replacement</li>
|
||||
<li data-id="p4" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab">⠿</span> P4 — SSL cert renewal</li>
|
||||
</ul>
|
||||
<p id="demo-sort-order" style="font-size:0.68rem;color:var(--text-muted);margin-top:0.5rem">Order: p1, p2, p3, p4</p>
|
||||
</div>
|
||||
<div>
|
||||
<p style="font-size:0.72rem;color:var(--text-muted);margin-bottom:0.75rem">API:</p>
|
||||
<pre class="lt-code-block" style="font-size:0.7rem">const s = lt.sortable.init(listEl, {
|
||||
onSort: (items) => console.log(s.getOrder())
|
||||
});</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Countdown / Timer -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// COUNTDOWN & TIMER</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-grid lt-grid-3" style="gap:1rem">
|
||||
<div class="lt-stat-card">
|
||||
<div class="lt-stat-label">SLA Countdown</div>
|
||||
<div class="lt-countdown lt-num" id="demo-countdown">--:--:--</div>
|
||||
<div style="font-size:0.68rem;color:var(--text-muted);margin-top:0.25rem">Goes urgent (red) at <5 min</div>
|
||||
</div>
|
||||
<div class="lt-stat-card">
|
||||
<div class="lt-stat-label">Stopwatch</div>
|
||||
<div class="lt-countdown lt-num" id="demo-stopwatch">00:00:00</div>
|
||||
<div class="lt-flex lt-gap-xs" style="margin-top:0.5rem">
|
||||
<button class="lt-btn lt-btn-sm" id="sw-pause">Pause</button>
|
||||
<button class="lt-btn lt-btn-sm" id="sw-reset">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-stat-card">
|
||||
<div class="lt-stat-label">API</div>
|
||||
<pre class="lt-code-block" style="font-size:0.62rem;margin:0">lt.timer.countdown(el, date, {
|
||||
urgent: 300,
|
||||
onExpire: () => {}
|
||||
});
|
||||
lt.timer.stopwatch(el);</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Lightbox -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// IMAGE LIGHTBOX</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Click any image to open full-screen viewer. Keyboard: ←/→ navigate, Esc closes.</p>
|
||||
<div class="lt-flex lt-gap-md lt-wrap">
|
||||
<img class="lt-lightbox-demo" src="https://picsum.photos/seed/lt1/320/180" alt="Server rack overview" style="width:160px;height:90px;object-fit:cover;border:1px solid var(--border-dim)" loading="lazy">
|
||||
<img class="lt-lightbox-demo" src="https://picsum.photos/seed/lt2/320/180" alt="Network switch closeup" style="width:160px;height:90px;object-fit:cover;border:1px solid var(--border-dim)" loading="lazy">
|
||||
<img class="lt-lightbox-demo" src="https://picsum.photos/seed/lt3/320/180" alt="Datacenter floor view" style="width:160px;height:90px;object-fit:cover;border:1px solid var(--border-dim)" loading="lazy">
|
||||
</div>
|
||||
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.5rem"><code>lt.lightbox.init('.lt-lightbox-demo', { caption: 'alt', loop: true })</code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar Submenus -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// SIDEBAR SUBMENUS</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div style="width:220px;background:var(--bg-secondary);border:1px solid var(--border-dim);padding:0.5rem">
|
||||
<a href="#" class="lt-sidebar-link active" style="display:flex;align-items:center;gap:0.5rem;padding:0.4rem 0.75rem;font-size:0.78rem;color:var(--accent-orange);text-decoration:none">⊞ Dashboard</a>
|
||||
<a href="#" class="lt-sidebar-link" style="display:flex;align-items:center;gap:0.5rem;padding:0.4rem 0.75rem;font-size:0.78rem;color:var(--text-secondary);text-decoration:none">🎫 Tickets</a>
|
||||
<div class="lt-sidebar-group is-open">
|
||||
<div class="lt-sidebar-group-label">Admin <span class="chevron">▶</span></div>
|
||||
<div class="lt-sidebar-submenu">
|
||||
<a href="#" class="lt-sidebar-sub-link active" aria-current="page">⚙ Templates</a>
|
||||
<a href="#" class="lt-sidebar-sub-link">🔀 Workflow</a>
|
||||
<a href="#" class="lt-sidebar-sub-link">📋 Audit Log</a>
|
||||
<a href="#" class="lt-sidebar-sub-link">🔑 API Keys</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-sidebar-group">
|
||||
<div class="lt-sidebar-group-label">Reports <span class="chevron">▶</span></div>
|
||||
<div class="lt-sidebar-submenu">
|
||||
<a href="#" class="lt-sidebar-sub-link">📊 SLA Report</a>
|
||||
<a href="#" class="lt-sidebar-sub-link">📈 Volume Trends</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Markdown Renderer -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// MARKDOWN RENDERER</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-grid lt-grid-2" style="gap:1.5rem">
|
||||
<div>
|
||||
<div class="lt-section-label" style="margin-bottom:0.5rem">Source</div>
|
||||
<pre class="lt-code-block" style="font-size:0.7rem"># Incident Report
|
||||
**Severity**: P1 Critical
|
||||
|
||||
## Summary
|
||||
Storage array link-down on `compute-storage-01`.
|
||||
|
||||
- Affected: 3 production services
|
||||
- Duration: 2h 33m
|
||||
- Root cause: NIC hardware failure
|
||||
|
||||
> Resolved. Monitoring continues.
|
||||
|
||||
[View Ticket](#001)</pre>
|
||||
</div>
|
||||
<div>
|
||||
<div class="lt-section-label" style="margin-bottom:0.5rem">Rendered</div>
|
||||
<div id="demo-markdown" class="lt-markdown" style="background:var(--bg-card);border:1px solid var(--border-dim);padding:1rem" data-markdown="# Incident Report **Severity**: P1 Critical ## Summary Storage array link-down on `compute-storage-01`. - Affected: 3 production services - Duration: 2h 33m - Root cause: NIC hardware failure > Resolved. Monitoring continues. [View Ticket](#001)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.75rem">Built-in micro-renderer (no deps). Drops in <code>window.marked</code> or <code>window.markdownit</code> automatically if present.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main><!-- /.lt-main -->
|
||||
|
||||
|
||||
@@ -1490,6 +1675,48 @@
|
||||
// Demo notification badge initial count
|
||||
lt.notif.set('#lt-notif-bell', 3);
|
||||
|
||||
// Wizard demo
|
||||
lt.wizard.init(document.getElementById('demo-wizard'), {
|
||||
onComplete: data => lt.toast.success('Ticket submitted: ' + (data.title || 'untitled')),
|
||||
validate: (step, data) => {
|
||||
if (step === 1 && !data.title?.trim()) { lt.toast.error('Title is required'); return false; }
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
// Sortable demo
|
||||
const sortableList = lt.sortable.init(document.getElementById('demo-sortable'), {
|
||||
onSort: (items) => {
|
||||
document.getElementById('demo-sort-order').textContent = 'Order: ' + items.map(el => el.dataset.id).join(', ');
|
||||
},
|
||||
});
|
||||
|
||||
// Countdown demo — SLA expires 2 hours from now
|
||||
const slaTarget = new Date(Date.now() + 2 * 60 * 60 * 1000);
|
||||
lt.timer.countdown(document.getElementById('demo-countdown'), slaTarget, {
|
||||
urgent: 300,
|
||||
urgentClass: 'lt-text-red lt-countdown-urgent',
|
||||
onExpire: () => lt.toast.error('SLA BREACHED'),
|
||||
});
|
||||
|
||||
// Stopwatch demo
|
||||
const sw = lt.timer.stopwatch(document.getElementById('demo-stopwatch'));
|
||||
let swRunning = true;
|
||||
document.getElementById('sw-pause').addEventListener('click', function() {
|
||||
if (swRunning) { sw.pause(); this.textContent = 'Resume'; } else { sw.resume(); this.textContent = 'Pause'; }
|
||||
swRunning = !swRunning;
|
||||
});
|
||||
document.getElementById('sw-reset').addEventListener('click', () => { sw.reset(); swRunning = true; document.getElementById('sw-pause').textContent = 'Pause'; });
|
||||
|
||||
// Lightbox demo
|
||||
lt.lightbox.init('.lt-lightbox-demo');
|
||||
|
||||
// Markdown demo
|
||||
lt.markdown.init('#demo-markdown');
|
||||
|
||||
// Sidebar submenus (re-init for demo sidebar)
|
||||
lt.sidebarSubmenus.init(document.querySelector('.lt-section-body'));
|
||||
|
||||
// Tab bar switching
|
||||
document.querySelectorAll('.lt-tab-bar').forEach(bar => {
|
||||
bar.addEventListener('click', e => {
|
||||
|
||||
Reference in New Issue
Block a user