From d95f0bf5c0f05cb396f31f346760f80114b44c7d Mon Sep 17 00:00:00 2001 From: Grovy311 Date: Sat, 26 Sep 2026 10:30:15 +0200 Subject: [PATCH] Compact circuit list column headers with filter icons - Replace the "Filtern" text buttons with a funnel icon next to the column label; an active filter is highlighted and shows its value count. - Header labels no longer break inside words (e.g. "Bemerkung"); hyphens are non-breaking and a trailing unit moves to its own line, so all headers share one height. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 4 ++ src/app/globals.css | 53 +++++++++++++++---- .../components/circuit-tree-editor.tsx | 29 +++++++++- 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0ec3242..ab8393e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -173,6 +173,10 @@ tooltip; the toolbar shows icon plus text, group headers and rows show icons only, separated into logical blocks with `IconButtonSeparator`. Destructive actions use `variant="danger"` (trash icon for circuits and groups, `ListX` for removing a device row). +Column headers show the label (click to sort) with a funnel icon for the +value filter; an active filter is highlighted and shows its value count. +Header labels break only between words and put a trailing unit such as +`[kW]` on a second line. ## Bootstrap / Styling Rule diff --git a/src/app/globals.css b/src/app/globals.css index ec92d91..fe81d07 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -892,24 +892,37 @@ a.kpi:hover { white-space: nowrap; } +/* One header row: label (sort) on the left, filter icon on the right. */ .tree-grid .header-cell { display: flex; - flex-direction: column; - align-items: stretch; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; gap: 0.2rem; min-width: 0; } .tree-grid .header-filter-btn { - display: block; - width: 100%; - border: 1px solid var(--panel-border-strong); - background: var(--panel-bg); - color: var(--text-strong); + display: inline-flex; + flex: none; + align-items: center; + gap: 0.15rem; + border: 1px solid transparent; + background: transparent; + color: var(--text-faint); border-radius: 3px; - font-size: 0.75rem; - padding: 0.15rem 0.35rem; + font-size: 0.7rem; + font-weight: 700; + line-height: 1; + padding: 0.15rem 0.2rem; white-space: nowrap; + cursor: pointer; +} + +.tree-grid .header-filter-btn:hover, +.tree-grid .header-filter-btn:focus-visible { + border-color: var(--panel-border-strong); + color: var(--text-strong); } .tree-grid .header-sort-btn { @@ -927,7 +940,10 @@ a.kpi:hover { font-size: 0.78rem; font-weight: 600; line-height: 1.15; - overflow-wrap: anywhere; + /* Break only between words; never inside a word such as "Bemerkung". */ + overflow-wrap: normal; + word-break: normal; + hyphens: manual; white-space: normal; text-align: left; cursor: pointer; @@ -967,6 +983,18 @@ a.kpi:hover { font-size: 0.78rem; } +.tree-grid .header-cell .header-sort-btn { + flex: 1 1 auto; + width: auto; + align-items: flex-start; +} + +.tree-grid .header-unit { + display: block; + font-weight: 500; + color: var(--text-faint); +} + .tree-grid .header-sort-btn:hover span:first-child, .tree-grid .header-sort-btn:focus-visible span:first-child { text-decoration: underline; @@ -983,6 +1011,11 @@ a.kpi:hover { background: var(--accent-blue-soft-bg); } +.tree-grid .header-filter-count { + min-width: 0.7rem; + text-align: center; +} + .tree-grid .header-filter-menu { position: absolute; z-index: 7; diff --git a/src/frontend/components/circuit-tree-editor.tsx b/src/frontend/components/circuit-tree-editor.tsx index cc3d522..e559303 100644 --- a/src/frontend/components/circuit-tree-editor.tsx +++ b/src/frontend/components/circuit-tree-editor.tsx @@ -9,6 +9,7 @@ import { Columns3, FilterX, FolderPlus, + Funnel, ListOrdered, ListPlus, ListX, @@ -268,6 +269,24 @@ function createValuesFromEditPatch( ); } +/** + * Header labels break only between words: hyphens become non-breaking and a + * trailing unit such as "[kW]" is always shown on its own second line, so + * header heights stay uniform. + */ +function renderColumnHeaderLabel(label: string) { + const match = /^(.*?)s*([[^]]+])$/.exec(label); + const name = (match ? match[1] : label).replace(/-/g, "‑"); + return match ? ( + <> + {name} + {match[2]} + + ) : ( + name + ); +} + export function CircuitTreeEditor(props: { projectId: string; circuitListId: string }) { /* Circuit-tree editor invariant overview: @@ -3440,7 +3459,9 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str }); }} > - {column.label} + + {renderColumnHeaderLabel(column.label)} + {sortState?.key === column.key ? ( {sortState.direction === "asc" ? "↑" : "↓"} @@ -3451,6 +3472,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str type="button" className={`header-filter-btn ${(columnFilters[column.key]?.length ?? 0) > 0 ? "active" : ""}`} onClick={() => toggleColumnFilterMenu(column.key)} + aria-label={`${getFullColumnLabel(column)} filtern`} aria-pressed={(columnFilters[column.key]?.length ?? 0) > 0} title={ (columnFilters[column.key]?.length ?? 0) > 0 @@ -3458,7 +3480,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str : `${getFullColumnLabel(column)} filtern` } > - Filtern{(columnFilters[column.key]?.length ?? 0) > 0 ? ` (${columnFilters[column.key]?.length})` : ""} +