From b52ae47257c61db27639a1e34aec764b659757f9 Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Thu, 23 Apr 2026 09:47:52 +0200 Subject: [PATCH] CSS: inline-filter pill-dropdown styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds five new style components beneath the existing .filter-pill rules: 1. .filter-pill--editable — pointer cursor, hover tint, chevron rotation 2. .filter-pill--dirty — accent outline + pulsing orange dot 3. .filter-popover — absolute-positioned dropdown with shadow; on mobile (<576px) anchors to bottom of viewport full-width 4. .apply-filters-group — slide-in animation on the Apply/Reset buttons 5. .filter-toast-container — fixed top-right stack for cross-filter auto-remove notices; slide-in/out animations All colours via existing design tokens (--accent, --bg-card, --text-primary, --border-default, --bg-inset) so dark + light themes work automatically. z-index layering (popover 1040, toast 1060) stays below Bootstrap modals (1055+) to avoid stacking conflicts. Co-Authored-By: Claude Opus 4.7 (1M context) --- static/css/custom.css | 155 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/static/css/custom.css b/static/css/custom.css index f103bf9..71f3840 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -1733,3 +1733,158 @@ body, .card, .modal-content, .form-control, .form-select, border-top: 2px solid var(--border-default) !important; background: var(--bg-inset); } + +/* === Inline Filters (pill-as-dropdown) on the report page === */ +/* + Layered on top of the existing .filter-pill rules (lines ~1496–1524). + Five components: + 1. .filter-pill--editable: pointer cursor, hover tint, chevron + 2. .filter-pill--dirty: accent outline + small pulsing dot when uncommitted + 3. .filter-popover: absolute-positioned dropdown beneath the pill + 4. .apply-filters-group: slide-in Apply/Reset buttons when any pill dirty + 5. .filter-toast-container: cross-filter auto-removal notices +*/ + +/* --- Wrapper keeps the popover anchored to its pill --- */ +.filter-pill-wrap { + display: inline-flex; + align-items: center; +} + +/* --- Editable pill: button, cursor, hover state, chevron --- */ +.filter-pill--editable { + cursor: pointer; + border: 1px solid var(--border-default); + background: var(--bg-inset); + color: var(--text-primary); + transition: background-color 120ms, border-color 120ms, box-shadow 120ms; +} +.filter-pill--editable:hover { + background: var(--bg-card-hover); + border-color: var(--accent); +} +.filter-pill--editable[aria-expanded="true"] { + background: var(--bg-card-hover); + border-color: var(--accent); +} +.filter-pill__chevron { + opacity: 0.7; + transition: transform 120ms; +} +.filter-pill--editable[aria-expanded="true"] .filter-pill__chevron { + transform: rotate(180deg); +} + +/* --- Dirty state: pulsing accent dot + outline --- */ +.filter-pill--dirty { + border-color: var(--accent); + box-shadow: 0 0 0 2px rgba(232, 133, 26, 0.18); +} +.filter-pill--dirty::before { + content: ''; + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent); + margin-right: 0.4rem; + animation: filter-pill-pulse 1.4s ease-in-out infinite; +} +@keyframes filter-pill-pulse { + 0%, 100% { opacity: 0.55; } + 50% { opacity: 1; } +} + +/* --- Popover positioned under the pill --- */ +.filter-popover { + position: absolute; + top: calc(100% + 6px); + left: 0; + z-index: 1040; /* below Bootstrap modal (1055) but above everything else */ + min-width: 300px; + max-width: 420px; + background: var(--bg-card); + border: 1px solid var(--border-default); + border-radius: 0.5rem; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28); + padding: 0; +} +.filter-popover[hidden] { + display: none; +} +.filter-popover__body { + padding: 1rem; +} +.filter-popover__footer { + display: flex; + justify-content: flex-end; + gap: 0.5rem; + padding: 0.6rem 1rem; + border-top: 1px solid var(--border-default); + background: var(--bg-inset); + border-radius: 0 0 0.5rem 0.5rem; +} + +/* --- Mobile: popovers stretch full-width below the pill strip --- */ +@media (max-width: 576px) { + .filter-popover { + position: fixed; + top: auto; + bottom: 0; + left: 0; + right: 0; + width: 100vw; + max-width: 100vw; + border-radius: 0.5rem 0.5rem 0 0; + z-index: 1050; + } +} + +/* --- Apply / Reset button group --- */ +.apply-filters-group { + display: flex; + align-items: center; + animation: apply-slide-in 180ms ease-out; +} +.apply-filters-group[hidden] { + display: none; +} +@keyframes apply-slide-in { + from { opacity: 0; transform: translateX(8px); } + to { opacity: 1; transform: translateX(0); } +} + +/* --- Toast container for cross-filter auto-remove notices --- */ +.filter-toast-container { + position: fixed; + top: 4.5rem; /* below topbar */ + right: 1rem; + z-index: 1060; + display: flex; + flex-direction: column; + gap: 0.5rem; + pointer-events: none; + max-width: 320px; +} +.filter-toast { + background: var(--bg-card); + color: var(--text-primary); + border: 1px solid var(--accent); + border-left: 4px solid var(--accent); + border-radius: 0.375rem; + padding: 0.75rem 1rem; + font-size: 0.875rem; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.22); + animation: filter-toast-in 200ms ease-out; + pointer-events: auto; +} +.filter-toast--exiting { + animation: filter-toast-out 200ms ease-in forwards; +} +@keyframes filter-toast-in { + from { opacity: 0; transform: translateX(12px); } + to { opacity: 1; transform: translateX(0); } +} +@keyframes filter-toast-out { + to { opacity: 0; transform: translateX(12px); } +}