From 5dec88fd15a14480d5d9c5e4b59096563706f280 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Thu, 28 May 2026 06:05:21 +0200 Subject: [PATCH] Improve tab styling and accessibility in linker UI - Redesign tabs with new visual styling (active/hover states, separators) - Add ARIA attributes for tab accessibility (aria-selected, aria-disabled, aria-current) - Update header styling in linker-shell.css - Refactor tab switching logic to use centralized state management --- web/css/linker-main.css | 103 +++++++++++++++++++++++++++++---------- web/css/linker-shell.css | 11 +++-- web/linker.js | 46 +++++++++++++---- 3 files changed, 120 insertions(+), 40 deletions(-) diff --git a/web/css/linker-main.css b/web/css/linker-main.css index 0d8803e..999acad 100644 --- a/web/css/linker-main.css +++ b/web/css/linker-main.css @@ -2012,55 +2012,96 @@ /* Tabs */ .ml-tabs { display: flex; - align-items: center; - gap: 3px; + align-self: stretch; + align-items: flex-end; + gap: 0; min-width: 0; + height: 100%; padding: 0; margin: 0; background: transparent; position: relative; z-index: 2; + --ml-tab-active-bg: var(--ml-bg, #121212); + --ml-tab-hover-bg: rgba(255,255,255,0.045); + --ml-tab-separator: rgba(255,255,255,0.125); } .ml-tab { + display: inline-flex; + align-items: center; + justify-content: center; position: relative; - min-height: 30px; - padding: 0 14px; - background: rgba(255,255,255,0.018); - border: 1px solid rgba(255,255,255,0.065); - border-radius: 6px; - color: var(--ml-text-muted, #888); + align-self: flex-end; + height: 36px; + min-height: 36px; + padding: 0 21px; + margin-bottom: 0; + background: transparent !important; + border: 0; + border-radius: 0; + color: rgba(255,255,255,0.68); font-size: 12px; - font-weight: 650; + font-weight: 600; cursor: pointer; white-space: nowrap; box-shadow: none; transform: none; - opacity: 0.82; - transition: opacity 0.16s ease, color 0.16s ease, background 0.16s ease, border-color 0.16s ease; + opacity: 1; + appearance: none; + -webkit-appearance: none; + transition: color 0.16s ease, background 0.16s ease; } .ml-tab:hover { color: var(--ml-text, #eef2f7); - background: rgba(255,255,255,0.028); - border-color: rgba(255,255,255,0.10); - opacity: 1; + background: var(--ml-tab-hover-bg) !important; + border-radius: 9px 9px 0 0; +} +.ml-tab:not(.ml-tab-active) + .ml-tab:not(.ml-tab-active)::before { + content: ""; + position: absolute; + left: 0; + top: 11px; + bottom: 11px; + width: 1px; + background: var(--ml-tab-separator); + pointer-events: none; } .ml-tab.ml-tab-active { + height: 36px; + min-height: 36px; + padding: 0 24px; + margin-bottom: 0; color: var(--ml-text, #eef2f7); - background: rgba(255,255,255,0.045); - border-color: rgba(255,255,255,0.14); - box-shadow: none; + background: var(--ml-tab-active-bg) !important; + border-radius: 10px 10px 0 0; + cursor: default; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.07) !important; transform: none; - opacity: 1; - z-index: 3; + z-index: 4; } +.ml-tab.ml-tab-active:hover { + color: var(--ml-text, #eef2f7); + background: var(--ml-tab-active-bg) !important; +} +.ml-tab.ml-tab-active::before, .ml-tab.ml-tab-active::after { content: ""; position: absolute; - left: 8px; - right: 8px; - bottom: 3px; - height: 1px; - background: rgba(255,255,255,0.58); + bottom: 0; + width: 10px; + height: 10px; + background: transparent; + pointer-events: none; +} +.ml-tab.ml-tab-active::before { + left: -10px; + border-bottom-right-radius: 10px; + box-shadow: 5px 5px 0 5px var(--ml-tab-active-bg); +} +.ml-tab.ml-tab-active::after { + right: -10px; + border-bottom-left-radius: 10px; + box-shadow: -5px 5px 0 5px var(--ml-tab-active-bg); } #model-linker-modal .ml-tab, #model-linker-modal .ml-footer-btn, @@ -2070,9 +2111,21 @@ outline: none; } #model-linker-modal .ml-tab { - border-width: 1px !important; + border-width: 0 !important; margin: 0 !important; } +#model-linker-modal .ml-tab:not(.ml-tab-active) { + background: transparent !important; + border-radius: 0 !important; +} +#model-linker-modal .ml-tab:not(.ml-tab-active):hover { + background: var(--ml-tab-hover-bg) !important; + border-radius: 9px 9px 0 0 !important; +} +#model-linker-modal .ml-tab.ml-tab-active { + margin-bottom: 0 !important; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.07) !important; +} /* Loaded Models Tab */ .ml-loaded-models { diff --git a/web/css/linker-shell.css b/web/css/linker-shell.css index 5f1af7f..11df04a 100644 --- a/web/css/linker-shell.css +++ b/web/css/linker-shell.css @@ -56,10 +56,10 @@ justify-content: space-between; align-items: center; gap: var(--ml-spacing-10); - min-height: 40px; - padding: 5px 10px; - border-bottom: 1px solid rgba(255, 255, 255, 0.09); - background: linear-gradient(180deg, rgba(255,255,255,0.035) 0%, rgba(255,255,255,0.012) 100%); + min-height: 42px; + padding: 6px 10px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.075); + background: #1f1f1f; } .ml-dialog-brand, @@ -73,13 +73,14 @@ .ml-dialog-brand { min-width: 0; flex: 1 1 auto; + align-self: stretch; } .ml-dialog-controls { flex: 0 0 auto; align-self: stretch; gap: 0; - margin: -5px -10px -5px 0; + margin: -6px -10px 0 0; } #model-linker-drag-handle { diff --git a/web/linker.js b/web/linker.js index fbe507f..9bcb7de 100644 --- a/web/linker.js +++ b/web/linker.js @@ -2027,6 +2027,9 @@ class LinkerManagerDialog extends ComfyDialog { textContent: "Options", onclick: () => this.switchTab('options') }); + if (this.activeTab === 'missing') { + this.updateTabButtonStates(); + } const dragHandle = $el("div", { id: "model-linker-drag-handle", @@ -2983,19 +2986,48 @@ class LinkerManagerDialog extends ComfyDialog { } + getTabButton(tab) { + return { + missing: this.missingTab, + loaded: this.loadedTab, + options: this.optionsTab + }[tab] || null; + } + + updateTabButtonStates() { + ['missing', 'loaded', 'options'].forEach((tab) => { + const button = this.getTabButton(tab); + if (!button) return; + const isActive = tab === this.activeTab; + button.classList.toggle('ml-tab-active', isActive); + button.setAttribute('aria-selected', isActive ? 'true' : 'false'); + button.setAttribute('aria-disabled', isActive ? 'true' : 'false'); + if (isActive) { + button.setAttribute('aria-current', 'page'); + } else { + button.removeAttribute('aria-current'); + } + }); + } + switchTab(tab) { - this.activeTab = this.getValidTab(tab); + const nextTab = this.getValidTab(tab); + const nextTabButton = this.getTabButton(nextTab); + if (nextTab === this.activeTab && nextTabButton?.classList.contains('ml-tab-active')) { + this.hideTooltip(); + return; + } + + this.activeTab = nextTab; this.persistActiveTab(this.activeTab); this.hideTooltip(); this.animateTabContentTransition(); + this.updateTabButtonStates(); if (this.activeTab === 'missing') { if (this.contentElement) { this.contentElement.style.overflowY = 'auto'; } - this.missingTab.classList.add('ml-tab-active'); - this.loadedTab.classList.remove('ml-tab-active'); - this.optionsTab.classList.remove('ml-tab-active'); this.downloadAllButton.style.display = 'inline-flex'; this.autoResolveButton.style.display = 'inline-flex'; this.applyPendingBtn.style.display = 'inline-flex'; @@ -3011,9 +3043,6 @@ class LinkerManagerDialog extends ComfyDialog { if (this.contentElement) { this.contentElement.style.overflowY = 'auto'; } - this.missingTab.classList.remove('ml-tab-active'); - this.loadedTab.classList.add('ml-tab-active'); - this.optionsTab.classList.remove('ml-tab-active'); this.downloadAllButton.style.display = 'none'; this.autoResolveButton.style.display = 'none'; this.applyPendingBtn.style.display = 'none'; @@ -3029,9 +3058,6 @@ class LinkerManagerDialog extends ComfyDialog { if (this.contentElement) { this.contentElement.style.overflowY = 'hidden'; } - this.missingTab.classList.remove('ml-tab-active'); - this.loadedTab.classList.remove('ml-tab-active'); - this.optionsTab.classList.add('ml-tab-active'); this.downloadAllButton.style.display = 'none'; this.autoResolveButton.style.display = 'none'; this.applyPendingBtn.style.display = 'none';