diff --git a/base.css b/base.css
index 90a667a..2d09fb0 100644
--- a/base.css
+++ b/base.css
@@ -4524,3 +4524,178 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
.lt-code-block { white-space: pre-wrap; word-break: break-all; }
.lt-page-header { border-bottom: 2px solid #333; padding-bottom: 0.5rem; margin-bottom: 1rem; }
}
+
+/* ----------------------------------------------------------------
+ 76. NOTIFICATION DROPDOWN PANEL
+ ---------------------------------------------------------------- */
+.lt-notif-dropdown-wrap {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+}
+
+.lt-notif-panel {
+ position: absolute;
+ top: calc(100% + 6px);
+ right: 0;
+ width: 300px;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 0 100%);
+ z-index: 10020;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.5);
+ transform-origin: top right;
+ transform: scale(0.95);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.15s ease, transform 0.15s ease;
+}
+.lt-notif-panel[aria-hidden="false"] {
+ opacity: 1;
+ transform: scale(1);
+ pointer-events: auto;
+}
+
+.lt-notif-panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.6rem 0.75rem;
+ border-bottom: 1px solid var(--border-dim);
+ font-size: 0.72rem;
+ font-family: var(--font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--text-muted);
+}
+.lt-notif-panel-clear {
+ background: none;
+ border: none;
+ color: var(--accent-cyan);
+ font-size: 0.68rem;
+ font-family: var(--font-mono);
+ cursor: pointer;
+ padding: 0;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+}
+.lt-notif-panel-clear:hover { text-decoration: underline; }
+
+.lt-notif-panel-list { max-height: 280px; overflow-y: auto; }
+
+.lt-notif-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 0.6rem;
+ padding: 0.6rem 0.75rem;
+ border-bottom: 1px solid var(--border-dim);
+ cursor: pointer;
+ transition: background 0.1s;
+}
+.lt-notif-item:hover { background: var(--bg-tertiary); }
+.lt-notif-item--unread { background: rgba(0, 212, 255, 0.04); }
+
+.lt-notif-dot {
+ flex-shrink: 0;
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: var(--accent-cyan);
+ margin-top: 4px;
+ box-shadow: 0 0 6px var(--accent-cyan);
+}
+.lt-notif-dot--read {
+ background: var(--border-color);
+ box-shadow: none;
+}
+
+.lt-notif-item-body { flex: 1; min-width: 0; }
+.lt-notif-item-title {
+ font-size: 0.76rem;
+ color: var(--text-primary);
+ line-height: 1.4;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.lt-notif-item--unread .lt-notif-item-title { color: var(--text-primary); font-weight: 600; }
+.lt-notif-item-time {
+ font-size: 0.64rem;
+ color: var(--text-muted);
+ margin-top: 2px;
+ font-family: var(--font-mono);
+}
+
+.lt-notif-panel-footer {
+ padding: 0.5rem 0.75rem;
+ border-top: 1px solid var(--border-dim);
+}
+
+/* ----------------------------------------------------------------
+ 77. GENERIC DROPDOWN WIDGET
+ ---------------------------------------------------------------- */
+.lt-dropdown-wrap {
+ position: relative;
+ display: inline-block;
+}
+
+.lt-dropdown-panel {
+ position: absolute;
+ top: calc(100% + 4px);
+ left: 0;
+ min-width: 160px;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 0 100%);
+ z-index: 10020;
+ box-shadow: 0 8px 24px rgba(0,0,0,0.4);
+ transform-origin: top left;
+ transform: scale(0.95);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.15s ease, transform 0.15s ease;
+}
+.lt-dropdown-panel--right {
+ left: auto;
+ right: 0;
+ transform-origin: top right;
+}
+.lt-dropdown-panel[aria-hidden="false"] {
+ opacity: 1;
+ transform: scale(1);
+ pointer-events: auto;
+}
+
+.lt-dropdown-item {
+ display: block;
+ width: 100%;
+ padding: 0.5rem 0.85rem;
+ background: none;
+ border: none;
+ text-align: left;
+ font-size: 0.76rem;
+ font-family: var(--font-mono);
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: background 0.1s, color 0.1s;
+ white-space: nowrap;
+}
+.lt-dropdown-item:hover {
+ background: var(--bg-tertiary);
+ color: var(--text-primary);
+}
+.lt-dropdown-item--danger { color: var(--accent-red); }
+.lt-dropdown-item--danger:hover { background: rgba(255,45,85,0.1); color: var(--accent-red); }
+
+.lt-dropdown-divider {
+ height: 1px;
+ background: var(--border-dim);
+ margin: 0.25rem 0;
+}
+
+/* textarea utility */
+.lt-textarea {
+ min-height: 60px;
+ resize: vertical;
+ line-height: 1.5;
+}
diff --git a/base.html b/base.html
index be00f61..8051cec 100644
--- a/base.html
+++ b/base.html
@@ -117,10 +117,49 @@
-
-
-
-
+
+
-
+
+
ID#123456789
-
StatusOpen
-
PriorityP1 Critical
-
AssigneeJDjdoe
-
Created2026-03-10
+
Created2026-03-10 09:14 UTC
-
Description
-
Storage array link-down on compute-storage-01. Affects prod write path. Investigate RAID controller firmware.
-
Activity
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Add Comment
+
+
+
+
+
+
+
Activity
jdoe assigned ticket2h ago
@@ -161,8 +248,8 @@
@@ -293,14 +380,65 @@
@@ -1721,6 +1859,86 @@ Storage array link-down on `compute-storage-01`.
// Sidebar submenus (re-init for demo sidebar)
lt.sidebarSubmenus.init(document.querySelector('.lt-section-body'));
+ // ----- Notification bell dropdown -----
+ (function() {
+ const btn = document.getElementById('lt-notif-bell-btn');
+ const panel = document.getElementById('lt-notif-panel');
+ if (!btn || !panel) return;
+
+ function open() {
+ panel.setAttribute('aria-hidden', 'false');
+ btn.setAttribute('aria-expanded', 'true');
+ // Mark all as read visually
+ }
+ function close() {
+ panel.setAttribute('aria-hidden', 'true');
+ btn.setAttribute('aria-expanded', 'false');
+ }
+
+ btn.addEventListener('click', e => {
+ e.stopPropagation();
+ panel.getAttribute('aria-hidden') === 'false' ? close() : open();
+ });
+
+ // "Mark all read" button
+ const clearBtn = document.getElementById('lt-notif-clear-all');
+ if (clearBtn) clearBtn.addEventListener('click', () => {
+ panel.querySelectorAll('.lt-notif-item--unread').forEach(el => el.classList.remove('lt-notif-item--unread'));
+ panel.querySelectorAll('.lt-notif-dot').forEach(el => { el.classList.remove('lt-notif-dot'); el.classList.add('lt-notif-dot', 'lt-notif-dot--read'); });
+ lt.notif.clear('#lt-notif-bell');
+ lt.toast.info('All notifications marked as read');
+ });
+
+ // Individual item click
+ panel.querySelectorAll('.lt-notif-item').forEach(item => {
+ item.addEventListener('click', () => {
+ item.classList.remove('lt-notif-item--unread');
+ const dot = item.querySelector('.lt-notif-dot');
+ if (dot) { dot.classList.add('lt-notif-dot--read'); }
+ close();
+ });
+ });
+
+ // Close on outside click
+ document.addEventListener('click', e => {
+ if (!document.getElementById('lt-notif-bell').contains(e.target)) close();
+ });
+
+ // Esc close
+ document.addEventListener('keydown', e => { if (e.key === 'Escape') close(); });
+ }());
+
+ // ----- Generic dropdown toggle (Advanced filter + Bulk Actions) -----
+ document.querySelectorAll('.lt-dropdown-trigger').forEach(btn => {
+ const wrap = btn.closest('.lt-dropdown-wrap');
+ const panel = wrap && wrap.querySelector('.lt-dropdown-panel');
+ if (!panel) return;
+
+ btn.addEventListener('click', e => {
+ e.stopPropagation();
+ const isOpen = panel.getAttribute('aria-hidden') === 'false';
+ // Close all other dropdowns first
+ document.querySelectorAll('.lt-dropdown-panel[aria-hidden="false"]').forEach(p => {
+ p.setAttribute('aria-hidden', 'true');
+ const t = p.closest('.lt-dropdown-wrap').querySelector('.lt-dropdown-trigger');
+ if (t) t.setAttribute('aria-expanded', 'false');
+ });
+ if (!isOpen) {
+ panel.setAttribute('aria-hidden', 'false');
+ btn.setAttribute('aria-expanded', 'true');
+ }
+ });
+ });
+
+ // Close dropdowns on outside click / Esc
+ document.addEventListener('click', () => {
+ document.querySelectorAll('.lt-dropdown-panel[aria-hidden="false"]').forEach(p => {
+ p.setAttribute('aria-hidden', 'true');
+ const t = p.closest('.lt-dropdown-wrap').querySelector('.lt-dropdown-trigger');
+ if (t) t.setAttribute('aria-expanded', 'false');
+ });
+ });
+
// Tab bar switching
document.querySelectorAll('.lt-tab-bar').forEach(bar => {
bar.addEventListener('click', e => {
diff --git a/base.js b/base.js
index 77006fd..69342d7 100644
--- a/base.js
+++ b/base.js
@@ -105,9 +105,14 @@
closeEl.setAttribute('aria-label', 'Dismiss');
closeEl.addEventListener('click', () => _dismissToast(toast));
+ const progressEl = document.createElement('div');
+ progressEl.className = 'lt-toast-progress';
+ progressEl.style.animationDuration = duration + 'ms';
+
toast.appendChild(iconEl);
toast.appendChild(msgEl);
toast.appendChild(closeEl);
+ toast.appendChild(progressEl);
container.appendChild(toast);
const timer = setTimeout(() => _dismissToast(toast), duration);
@@ -2027,32 +2032,6 @@
},
};
- /* ================================================================
- MODULE 47 — TOAST ENHANCEMENTS
- Adds a drain progress bar to each toast.
- The original showToast already handles queuing (section 2).
- ================================================================ */
- // Patch showToast to inject a progress bar after each toast is created
- const _origShowToast = showToast;
- function showToast(message, type, duration) {
- duration = duration || 4000;
- const result = _origShowToast(message, type, duration);
- // Inject drain bar into the most recently added toast
- requestAnimationFrame(() => {
- const container = document.getElementById('lt-toast-container');
- if (!container) return;
- const toasts = container.querySelectorAll('.lt-toast');
- const last = toasts[toasts.length - 1];
- if (last && !last.querySelector('.lt-toast-progress')) {
- const bar = document.createElement('div');
- bar.className = 'lt-toast-progress';
- bar.style.animationDuration = duration + 'ms';
- last.appendChild(bar);
- }
- });
- return result;
- }
-
/* ================================================================
MODULE 48 — SIDEBAR SUBMENUS
Auto-inits .lt-sidebar-group elements.