v1.1: Add 10 new feature modules + 18 CSS component sections
JS modules added: - lt.theme — dark/light toggle, OS preference sync, localStorage persist - lt.notif — notification badge (set/inc/clear) on any element - lt.rightDrawer — right-side detail panel with focus trap + return focus - lt.contextMenu — right-click custom menu, keyboard nav, danger variant - lt.offline — navigator.onLine banner + event hooks - lt.ws — WebSocket manager with exponential backoff reconnect - lt.combobox — multi-select with search, tag chips, keyboard nav - lt.typeahead — async/sync autocomplete with match highlighting - lt.cookie — get/set/del with SameSite/Secure helpers - lt.splitPane — pointer-events resizable split pane (horizontal/vertical) - Toast queue: max-stack, progress bar drain animation, auto-drain CSS sections added (51–68): - Light theme (html[data-theme="light"]) with full variable overrides - Theme toggle button (.lt-theme-btn) - Skeleton loader variants (card, row, text, title, avatar, btn, badge) - Empty state component (.lt-empty-state, --sm variant) - Nav notification badge (.lt-notif-wrap / .lt-notif-badge) - Right-side drawer (.lt-drawer-right + overlay) - Sticky table header (.lt-table-sticky-wrap) - Multi-select combobox (.lt-combobox, tags, dropdown) - Context menu (.lt-context-menu, divider, label, danger) - Offline banner (.lt-offline-banner) - Timeline / activity feed (.lt-timeline, color variants) - Avatar + avatar group + status ring (.lt-avatar) - Split pane (.lt-split, .lt-split-divider with pointer drag) - Chart container (.lt-chart-wrap, legend, axis, loading state) - Toast queue stack + progress drain bar - Autocomplete / typeahead (.lt-typeahead-dropdown, match highlight) - WebSocket status indicator (.lt-ws-status, data-state variants) - Print enhancements (extended @media print rules) HTML demo sections for all new components added to base.html Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,16 @@
|
||||
</div>
|
||||
|
||||
<div class="lt-header-right">
|
||||
<!-- WebSocket status indicator -->
|
||||
<div class="lt-ws-status" id="lt-ws-indicator" data-state="disconnected" aria-label="WebSocket status">
|
||||
<span class="lt-dot"></span><span>Offline</span>
|
||||
</div>
|
||||
<!-- Theme toggle -->
|
||||
<button class="lt-theme-btn" id="lt-theme-btn" aria-label="Switch to light mode" title="Switch to light mode">☀</button>
|
||||
<!-- Notifications with badge -->
|
||||
<span class="lt-notif-wrap" id="lt-notif-bell" aria-label="Notifications">
|
||||
<button class="lt-btn lt-btn-sm" style="padding:0 0.5rem;min-height:32px;" aria-label="Open notifications">🔔</button>
|
||||
</span>
|
||||
<!-- Current user -->
|
||||
<span class="lt-header-user">operator</span>
|
||||
<!-- Admin badge (only show for admins) -->
|
||||
@@ -118,6 +128,45 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Right-side detail drawer -->
|
||||
<div id="lt-detail-drawer" class="lt-drawer-right" aria-hidden="true" role="dialog" aria-label="Detail panel" data-overlay="lt-detail-overlay">
|
||||
<div class="lt-drawer-right-header">
|
||||
<span class="lt-drawer-right-title">// TICKET DETAIL</span>
|
||||
<button class="lt-drawer-right-close" data-drawer-close aria-label="Close detail panel">✕</button>
|
||||
</div>
|
||||
<div class="lt-drawer-right-body">
|
||||
<div class="lt-kv-grid">
|
||||
<div class="lt-kv-row"><span class="lt-kv-label">ID</span><span class="lt-kv-value lt-text-cyan">#123456789</span></div>
|
||||
<div class="lt-kv-row"><span class="lt-kv-label">Status</span><span class="lt-kv-value"><span class="lt-badge lt-badge-open">Open</span></span></div>
|
||||
<div class="lt-kv-row"><span class="lt-kv-label">Priority</span><span class="lt-kv-value"><span class="lt-badge lt-badge-p1">P1 Critical</span></span></div>
|
||||
<div class="lt-kv-row"><span class="lt-kv-label">Assignee</span><span class="lt-kv-value" style="display:flex;align-items:center;gap:0.5rem"><span class="lt-avatar lt-avatar--sm lt-avatar--orange">JD</span>jdoe</span></div>
|
||||
<div class="lt-kv-row"><span class="lt-kv-label">Created</span><span class="lt-kv-value">2026-03-10</span></div>
|
||||
</div>
|
||||
<div class="lt-divider-label" style="margin:1rem 0">Description</div>
|
||||
<p style="font-size:0.78rem;color:var(--text-secondary);line-height:1.6">Storage array link-down on compute-storage-01. Affects prod write path. Investigate RAID controller firmware.</p>
|
||||
<div class="lt-divider-label" style="margin:1rem 0">Activity</div>
|
||||
<div class="lt-timeline">
|
||||
<div class="lt-timeline-item lt-timeline-item--orange">
|
||||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> assigned ticket<span class="lt-timeline-time">2h ago</span></div>
|
||||
<div class="lt-timeline-body">Assigned to self, escalated to P1.</div>
|
||||
</div>
|
||||
<div class="lt-timeline-item">
|
||||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">sysbot</span> auto-created<span class="lt-timeline-time">3h ago</span></div>
|
||||
<div class="lt-timeline-body">Alert triggered: <code>node_network_up = 0</code></div>
|
||||
</div>
|
||||
<div class="lt-timeline-item lt-timeline-item--dim">
|
||||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">monitor</span> detected<span class="lt-timeline-time">3h ago</span></div>
|
||||
<div class="lt-timeline-body">NIC link-down detected on large1:enp35s0.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-drawer-right-footer">
|
||||
<button class="lt-btn lt-btn-sm" onclick="lt.rightDrawer.close('lt-detail-drawer')">Close</button>
|
||||
<button class="lt-btn lt-btn-primary lt-btn-sm">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="lt-detail-overlay" class="lt-drawer-right-overlay"></div>
|
||||
|
||||
<!-- ===========================================================
|
||||
MAIN CONTENT AREA
|
||||
=========================================================== -->
|
||||
@@ -933,6 +982,280 @@
|
||||
</div><!-- /.lt-frame-inner (v1.2 additions) -->
|
||||
</div><!-- /.lt-frame (v1.2 additions) -->
|
||||
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════
|
||||
v1.1 COMPONENT SHOWCASE
|
||||
═══════════════════════════════════════════════════════════ -->
|
||||
|
||||
<!-- Theme Toggle -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// THEME TOGGLE</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Dark/light mode with OS preference detection and localStorage persistence.</p>
|
||||
<div class="lt-flex lt-gap-sm lt-align-center lt-wrap">
|
||||
<button class="lt-btn lt-btn-primary" onclick="lt.theme.toggle()">Toggle Theme</button>
|
||||
<button class="lt-btn" onclick="lt.theme.set('dark')">Force Dark</button>
|
||||
<button class="lt-btn" onclick="lt.theme.set('light')">Force Light</button>
|
||||
<code style="font-size:0.72rem;color:var(--accent-cyan)">lt.theme.toggle() | .set('light') | .get()</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Skeleton Loaders -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// SKELETON LOADERS</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-grid lt-grid-3" style="gap:1rem;align-items:start">
|
||||
<div class="lt-skeleton-card">
|
||||
<div class="lt-skeleton-card-header">
|
||||
<span class="lt-skeleton lt-skeleton-avatar"></span>
|
||||
<div style="flex:1"><span class="lt-skeleton lt-skeleton-title"></span><span class="lt-skeleton lt-skeleton-line-sm"></span></div>
|
||||
</div>
|
||||
<span class="lt-skeleton lt-skeleton-text"></span>
|
||||
<span class="lt-skeleton lt-skeleton-line-lg"></span>
|
||||
<span class="lt-skeleton lt-skeleton-text" style="width:70%"></span>
|
||||
<div class="lt-flex lt-gap-sm" style="margin-top:0.25rem">
|
||||
<span class="lt-skeleton lt-skeleton-btn"></span>
|
||||
<span class="lt-skeleton lt-skeleton-badge"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="grid-column:span 2">
|
||||
<div class="lt-skeleton-row"><span class="lt-skeleton" style="height:1rem"></span><span class="lt-skeleton lt-skeleton-text"></span><span class="lt-skeleton" style="height:0.8rem;width:80%"></span><span class="lt-skeleton lt-skeleton-badge"></span><span class="lt-skeleton" style="height:0.8rem;width:60%"></span><span class="lt-skeleton lt-skeleton-btn"></span></div>
|
||||
<div class="lt-skeleton-row" style="opacity:0.65"><span class="lt-skeleton" style="height:1rem"></span><span class="lt-skeleton lt-skeleton-text"></span><span class="lt-skeleton" style="height:0.8rem;width:65%"></span><span class="lt-skeleton lt-skeleton-badge"></span><span class="lt-skeleton" style="height:0.8rem;width:50%"></span><span class="lt-skeleton lt-skeleton-btn"></span></div>
|
||||
<div class="lt-skeleton-row" style="opacity:0.35"><span class="lt-skeleton" style="height:1rem"></span><span class="lt-skeleton lt-skeleton-text"></span><span class="lt-skeleton" style="height:0.8rem;width:55%"></span><span class="lt-skeleton lt-skeleton-badge"></span><span class="lt-skeleton" style="height:0.8rem;width:40%"></span><span class="lt-skeleton lt-skeleton-btn"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty States -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// EMPTY STATES</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-grid lt-grid-3" style="gap:1rem">
|
||||
<div class="lt-card"><div class="lt-empty-state"><div class="lt-empty-state-icon">📭</div><div class="lt-empty-state-title">No Tickets Found</div><div class="lt-empty-state-body">No tickets match your current filters.</div><button class="lt-btn lt-btn-sm lt-btn-primary">Clear Filters</button></div></div>
|
||||
<div class="lt-card"><div class="lt-empty-state"><div class="lt-empty-state-icon">🔌</div><div class="lt-empty-state-title">No Workers Online</div><div class="lt-empty-state-body">All workers are offline or unreachable.</div><button class="lt-btn lt-btn-sm" onclick="lt.toast.warning('Checking workers…')">Retry</button></div></div>
|
||||
<div class="lt-card"><div class="lt-empty-state lt-empty-state--sm"><div class="lt-empty-state-icon">🗂</div><div class="lt-empty-state-title">No Results</div><div class="lt-empty-state-body">Try a different search term.</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Avatars & Notification Badges -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// AVATARS & NOTIFICATION BADGES</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-flex lt-gap-lg lt-wrap lt-align-center" style="margin-bottom:1rem">
|
||||
<span class="lt-avatar lt-avatar--xs lt-avatar--orange">AB</span>
|
||||
<span class="lt-avatar lt-avatar--sm lt-avatar--green">CD</span>
|
||||
<span class="lt-avatar lt-avatar--purple">EF</span>
|
||||
<span class="lt-avatar lt-avatar--lg lt-avatar--red">GH</span>
|
||||
<div class="lt-avatar-wrap"><span class="lt-avatar lt-avatar--orange">JD</span><span class="lt-avatar-status lt-avatar-status--online"></span></div>
|
||||
<div class="lt-avatar-wrap"><span class="lt-avatar">SK</span><span class="lt-avatar-status lt-avatar-status--away"></span></div>
|
||||
<div class="lt-avatar-wrap"><span class="lt-avatar lt-avatar--red">MR</span><span class="lt-avatar-status lt-avatar-status--busy"></span></div>
|
||||
<div class="lt-avatar-group">
|
||||
<span class="lt-avatar lt-avatar--sm lt-avatar--orange">AA</span>
|
||||
<span class="lt-avatar lt-avatar--sm lt-avatar--green">BB</span>
|
||||
<span class="lt-avatar lt-avatar--sm lt-avatar--purple">CC</span>
|
||||
<span class="lt-avatar lt-avatar--sm" style="background:var(--bg-tertiary);color:var(--text-muted);font-size:0.6rem">+4</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-flex lt-gap-md lt-align-center lt-wrap">
|
||||
<span class="lt-notif-wrap" id="demo-notif-btn"><button class="lt-btn lt-btn-sm">🔔 Alerts</button></span>
|
||||
<button class="lt-btn lt-btn-sm" onclick="lt.notif.inc('#demo-notif-btn')">+1 Badge</button>
|
||||
<button class="lt-btn lt-btn-sm" onclick="lt.notif.clear('#demo-notif-btn')">Clear</button>
|
||||
<button class="lt-btn lt-btn-sm" onclick="lt.notif.inc('#lt-notif-bell')">+1 Header Bell</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timeline -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// TIMELINE / ACTIVITY FEED</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-grid lt-grid-2" style="gap:1.5rem">
|
||||
<div class="lt-timeline">
|
||||
<div class="lt-timeline-item lt-timeline-item--red">
|
||||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">alertmanager</span> fired<span class="lt-timeline-time lt-num">14:22:05</span></div>
|
||||
<div class="lt-timeline-body">CRITICAL: Storage array link-down on <code>compute-storage-01</code>. Ticket auto-created.</div>
|
||||
</div>
|
||||
<div class="lt-timeline-item lt-timeline-item--orange">
|
||||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> assigned<span class="lt-timeline-time lt-num">14:24:11</span></div>
|
||||
<div class="lt-timeline-body">Escalated to P1 Critical. Paged on-call team.</div>
|
||||
</div>
|
||||
<div class="lt-timeline-item">
|
||||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> commented<span class="lt-timeline-time lt-num">14:31:44</span></div>
|
||||
<div class="lt-timeline-body">Confirmed NIC failure. Ordered replacement hardware. ETA 2h.</div>
|
||||
</div>
|
||||
<div class="lt-timeline-item lt-timeline-item--green">
|
||||
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> resolved<span class="lt-timeline-time lt-num">16:55:00</span></div>
|
||||
<div class="lt-timeline-body">Hardware replaced. Link restored. Monitoring for 30 min.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:0.75rem">
|
||||
<div class="lt-stat-card"><div class="lt-stat-label">Resolution Time</div><div class="lt-stat-value lt-text-green lt-num">2h 33m</div></div>
|
||||
<div class="lt-stat-card"><div class="lt-stat-label">Events</div><div class="lt-stat-value lt-num">4</div></div>
|
||||
<div class="lt-stat-card"><div class="lt-stat-label">SLA Status</div><div class="lt-stat-value lt-text-orange">Within SLA</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Drawer -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// RIGHT-SIDE DRAWER</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Detail/inspect panel from the right. Focus trap, ESC close, overlay backdrop, return-focus.</p>
|
||||
<div class="lt-flex lt-gap-sm lt-wrap">
|
||||
<button class="lt-btn lt-btn-primary" data-drawer-open="lt-detail-drawer">Open Detail Panel</button>
|
||||
<code style="font-size:0.72rem;color:var(--accent-cyan)">lt.rightDrawer.open('id') | .close() | .toggle()</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Context Menu -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// CONTEXT MENU</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Right-click any element with <code>data-context-menu</code> or trigger programmatically.</p>
|
||||
<div class="lt-flex lt-gap-sm lt-wrap lt-align-center">
|
||||
<div data-context-menu="demo-ctx" class="lt-card" style="padding:0.75rem 1.25rem;cursor:context-menu;border-style:dashed;display:inline-block">
|
||||
Right-click this card ›
|
||||
</div>
|
||||
<button class="lt-btn lt-btn-sm" id="demo-ctx-btn">Show Context Menu</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Combobox + Typeahead -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// COMBOBOX & TYPEAHEAD</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-grid lt-grid-2" style="gap:1.5rem">
|
||||
<div>
|
||||
<label class="lt-label">Assign Workers (multi-select)</label>
|
||||
<div class="lt-combobox" id="demo-combobox">
|
||||
<div class="lt-combobox-input-wrap">
|
||||
<input type="text" class="lt-combobox-input" id="demo-combobox-input" placeholder="Search workers…" autocomplete="off">
|
||||
</div>
|
||||
<div class="lt-combobox-dropdown"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="lt-label">Search Tickets (typeahead)</label>
|
||||
<div class="lt-typeahead" id="demo-typeahead">
|
||||
<input type="text" class="lt-input lt-w-full" id="demo-typeahead-input" placeholder="Type to search…" autocomplete="off">
|
||||
<div class="lt-typeahead-dropdown"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sticky Table -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// STICKY TABLE HEADERS</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-table-sticky-wrap">
|
||||
<table class="lt-table lt-table-responsive">
|
||||
<thead><tr><th>ID</th><th>Priority</th><th>Title</th><th>Status</th><th>Assignee</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#001</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p1">P1</span></td><td data-label="Title">Link-down on compute-storage-01</td><td data-label="Status"><span class="lt-badge lt-badge-open">Open</span></td><td data-label="Assignee">jdoe</td></tr>
|
||||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#002</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p2">P2</span></td><td data-label="Title">Switch port flapping USW-Pro-24</td><td data-label="Status"><span class="lt-badge lt-badge-in-progress">In Progress</span></td><td data-label="Assignee">smith</td></tr>
|
||||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#003</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p3">P3</span></td><td data-label="Title">Scheduled SFP+ replacement</td><td data-label="Status"><span class="lt-badge lt-badge-pending">Pending</span></td><td data-label="Assignee">ops-bot</td></tr>
|
||||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#004</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p4">P4</span></td><td data-label="Title">SSL cert renewal wiki</td><td data-label="Status"><span class="lt-badge lt-badge-open">Open</span></td><td data-label="Assignee">admin</td></tr>
|
||||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#005</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p1">P1</span></td><td data-label="Title">RAID controller firmware</td><td data-label="Status"><span class="lt-badge lt-badge-open">Open</span></td><td data-label="Assignee">jdoe</td></tr>
|
||||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#006</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p2">P2</span></td><td data-label="Title">Backup job failure nas-01</td><td data-label="Status"><span class="lt-badge lt-badge-closed">Closed</span></td><td data-label="Assignee">backup-bot</td></tr>
|
||||
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#007</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p3">P3</span></td><td data-label="Title">Prometheus alert rule tuning</td><td data-label="Status"><span class="lt-badge lt-badge-in-progress">In Progress</span></td><td data-label="Assignee">ops-team</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chart Containers -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// CHART CONTAINERS</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-grid lt-grid-2" style="gap:1rem">
|
||||
<div class="lt-chart-wrap">
|
||||
<div class="lt-chart-header">
|
||||
<span class="lt-chart-title">Ticket Volume (7d)</span>
|
||||
<div class="lt-chart-legend">
|
||||
<span class="lt-chart-legend-item"><span class="lt-chart-legend-dot" style="background:var(--accent-cyan)"></span>Open</span>
|
||||
<span class="lt-chart-legend-item"><span class="lt-chart-legend-dot" style="background:var(--accent-green)"></span>Closed</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-chart-body" style="min-height:120px;display:flex;align-items:center;justify-content:center;color:var(--text-muted);font-size:0.72rem;letter-spacing:0.1em">[ Plug in Chart.js / D3 here ]</div>
|
||||
<div class="lt-chart-axis"><span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span><span>Sat</span><span>Sun</span></div>
|
||||
</div>
|
||||
<div class="lt-chart-wrap is-loading">
|
||||
<div class="lt-chart-header"><span class="lt-chart-title">Worker Uptime</span></div>
|
||||
<div class="lt-chart-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Split Pane -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// SPLIT PANE</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-split" id="demo-split" style="height:200px;border:1px solid var(--border-dim)">
|
||||
<div class="lt-split-pane" style="padding:1rem;overflow:auto">
|
||||
<div class="lt-section-label" style="margin-bottom:0.5rem">Panel A</div>
|
||||
<p style="font-size:0.78rem;color:var(--text-secondary)">Drag the divider to resize. Stacks vertically on mobile.</p>
|
||||
</div>
|
||||
<div class="lt-split-divider" title="Drag to resize"></div>
|
||||
<div class="lt-split-pane" style="padding:1rem;overflow:auto">
|
||||
<div class="lt-section-label" style="margin-bottom:0.5rem">Panel B</div>
|
||||
<p style="font-size:0.78rem;color:var(--text-secondary)">Both panels maintain independent scrolling.</p>
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.5rem"><code>lt.splitPane.init(el, { initial: 0.4, minA: 120 })</code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WebSocket & Offline -->
|
||||
<div class="lt-section">
|
||||
<div class="lt-section-header">
|
||||
<span class="lt-section-title">// WEBSOCKET & OFFLINE DETECTION</span>
|
||||
</div>
|
||||
<div class="lt-section-body">
|
||||
<div class="lt-flex lt-gap-md lt-wrap lt-align-center" style="margin-bottom:1rem">
|
||||
<div class="lt-ws-status" data-state="connected"><span class="lt-dot"></span><span>Connected</span></div>
|
||||
<div class="lt-ws-status" data-state="connecting"><span class="lt-dot"></span><span>Connecting…</span></div>
|
||||
<div class="lt-ws-status" data-state="disconnected"><span class="lt-dot"></span><span>Disconnected</span></div>
|
||||
</div>
|
||||
<div class="lt-flex lt-gap-sm lt-wrap">
|
||||
<button class="lt-btn lt-btn-sm" onclick="lt.offline.isOnline() ? lt.toast.success('Online ✓') : lt.toast.error('Offline ✗')">Check Online Status</button>
|
||||
<button class="lt-btn lt-btn-sm" onclick="lt.toast.info('lt.ws.connect(url, { reconnect:true, onMessage: fn })')">WS API Hint</button>
|
||||
</div>
|
||||
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.75rem">Offline banner + body class auto-applied on <code>navigator.onLine</code> change. WS manager has exponential backoff, event emitter, status indicator binding.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main><!-- /.lt-main -->
|
||||
|
||||
|
||||
@@ -1111,6 +1434,62 @@
|
||||
requestAnimationFrame(() => { bar.style.width = bar.dataset.width; });
|
||||
});
|
||||
|
||||
// Theme toggle button
|
||||
document.getElementById('lt-theme-btn').addEventListener('click', () => lt.theme.toggle());
|
||||
|
||||
// Context menu: register demo menu items
|
||||
lt.contextMenu.register('demo-ctx', [
|
||||
{ icon: '📋', label: 'Copy ID', kbd: 'C', action: () => lt.toast.info('ID copied') },
|
||||
{ icon: '👁', label: 'View Details', action: () => lt.rightDrawer.open('lt-detail-drawer') },
|
||||
{ icon: '✏️', label: 'Edit Ticket', kbd: 'E', action: () => lt.toast.info('Edit…') },
|
||||
{ divider: true },
|
||||
{ icon: '🗑', label: 'Delete', danger: true, action: () => lt.toast.error('Deleted') },
|
||||
]);
|
||||
// Programmatic context menu button
|
||||
const demoCtxBtn = document.getElementById('demo-ctx-btn');
|
||||
if (demoCtxBtn) demoCtxBtn.addEventListener('click', e => {
|
||||
const r = demoCtxBtn.getBoundingClientRect();
|
||||
lt.contextMenu.show(r.left, r.bottom + 4, [
|
||||
{ icon: '📋', label: 'Copy ID', action: () => lt.toast.info('Copied') },
|
||||
{ icon: '✏️', label: 'Edit', action: () => lt.toast.info('Edit') },
|
||||
{ divider: true },
|
||||
{ icon: '🗑', label: 'Delete', danger: true, action: () => lt.toast.error('Deleted') },
|
||||
]);
|
||||
});
|
||||
|
||||
// Combobox demo
|
||||
lt.combobox.init(
|
||||
document.getElementById('demo-combobox-input'),
|
||||
[
|
||||
{ value: 'worker-01', label: 'worker-01', icon: '🖥' },
|
||||
{ value: 'worker-02', label: 'worker-02', icon: '🖥' },
|
||||
{ value: 'worker-03', label: 'worker-03', icon: '🖥' },
|
||||
{ value: 'gpu-01', label: 'gpu-01', icon: '⚡' },
|
||||
{ value: 'gpu-02', label: 'gpu-02', icon: '⚡' },
|
||||
{ value: 'storage-01',label: 'storage-01',icon: '💾' },
|
||||
],
|
||||
{ onChange: vals => console.log('[combobox]', vals) }
|
||||
);
|
||||
|
||||
// Typeahead demo
|
||||
lt.typeahead.init(
|
||||
document.getElementById('demo-typeahead-input'),
|
||||
[
|
||||
{ value: '001', label: 'Link-down on compute-storage-01', icon: '🔴', meta: 'P1' },
|
||||
{ value: '002', label: 'Switch port flapping USW-Pro-24', icon: '🟠', meta: 'P2' },
|
||||
{ value: '003', label: 'Scheduled SFP+ replacement large1',icon: '🔵', meta: 'P3' },
|
||||
{ value: '004', label: 'SSL cert renewal wiki.lotusguild.org', icon: '🟢', meta: 'P4' },
|
||||
{ value: '005', label: 'RAID controller firmware update', icon: '🔴', meta: 'P1' },
|
||||
],
|
||||
{ onSelect: item => lt.toast.info(`Selected: ${item.label}`) }
|
||||
);
|
||||
|
||||
// Split pane demo
|
||||
lt.splitPane.init(document.getElementById('demo-split'), { initial: 0.4, minA: 100, minB: 100 });
|
||||
|
||||
// Demo notification badge initial count
|
||||
lt.notif.set('#lt-notif-bell', 3);
|
||||
|
||||
// Tab bar switching
|
||||
document.querySelectorAll('.lt-tab-bar').forEach(bar => {
|
||||
bar.addEventListener('click', e => {
|
||||
|
||||
Reference in New Issue
Block a user