feat(member): condense the overview into composite name and address cells

This commit is contained in:
Simon 2026-07-03 11:35:46 +02:00
parent b79d7ac9ea
commit af2cc2e0d4
17 changed files with 716 additions and 93 deletions

View file

@ -678,6 +678,78 @@
/* This file is for your main application CSS */
/* ============================================
Table header density scaling
============================================ */
/*
* DaisyUI table size modifiers (table-xs, table-sm, etc.) scope their font-size
* rule to ":not(thead, tfoot) tr", so the header row stays at the base 0.875 rem
* regardless of the chosen density. We extend each modifier to thead so the
* header scales together with the body.
*/
.table.table-xs thead tr { font-size: 0.6875rem; }
.table.table-sm thead tr { font-size: 0.75rem; }
.table.table-lg thead tr { font-size: 1.125rem; }
.table.table-xl thead tr { font-size: 1.375rem; }
/*
* Density-tracking badges and sort icons in the member overview.
*
* The member table renders its rows via LiveView streams, so toggling density
* only swaps the <table> size class (table-xs <-> table-md); it does NOT
* re-render the streamed rows. A per-row `size=` conditional therefore bakes a
* stale badge size into the streamed DOM. To make badges (and, for visual
* consistency, the header sort glyphs) follow the density toggle without any
* row re-render, their size is driven purely from the table size class here.
*
* Badge values mirror daisyUI's own .badge-xs / .badge-md (font-size, --size
* which the base .badge uses for height, and padding-inline). Scoped to the
* member overview via #members-keyboard so unrelated badges are unaffected.
*/
#members-keyboard .table.table-xs .badge {
--size: calc(var(--size-selector, 0.25rem) * 4);
font-size: 0.625rem;
padding-inline: calc(0.25rem * 2 - var(--border));
}
#members-keyboard .table.table-md .badge {
--size: calc(var(--size-selector, 0.25rem) * 6);
font-size: 0.875rem;
padding-inline: calc(0.25rem * 3 - var(--border));
}
/* Sort glyphs scale with the header text (11px compact / 14px comfortable). */
#members-keyboard .table.table-md thead .sort-icon {
width: 1.5rem;
height: 1.5rem;
}
#members-keyboard .table.table-xs thead .sort-icon {
width: 1.125rem;
height: 1.125rem;
}
/*
* Infinite-scroll loading row.
*
* The loading indicator must read like a full, accented data row rather than a
* narrow pinned bar. The accent (base-200 background + a top separator) and the
* padding live on the full-width footer cell, which spans every column via
* colspan, so the accent is visible edge-to-edge even while the table is scrolled
* horizontally. The spinner + label inside stay pinned to the visible viewport
* width (StickyViewportWidth hook) and transparent, so they remain centered on
* horizontal scroll while the cell accent shows through behind them.
*
* The row height tracks the active density (table-xs compact / table-md
* comfortable) so it matches a normal data row in each mode.
*/
#members-footer > tr > td {
background-color: var(--color-base-200);
border-top: 1px solid var(--color-base-300);
padding: 0;
}
.table.table-xs #members-loading-bar { min-height: 3.375rem; }
.table.table-md #members-loading-bar { min-height: 4.1875rem; }
/* ============================================
SortableList: drag-and-drop table rows
============================================ */
@ -773,3 +845,115 @@
[data-sticky-first-col-rows="true"] .table.table-zebra tbody tr input.checkbox:focus:not(:focus-visible) {
outline: none;
}
/*
* Sticky checkbox column: border-collapse: separate avoids the Chromium artifact
* where a collapsed row border is repainted (a spurious 1px line) under a
* position: sticky cell. The row separators are moved onto the cells so they
* still render in the separate model (matching DaisyUI's faint divider color).
*/
[data-sticky-first-col-rows="true"] .table.table-zebra {
border-collapse: separate;
border-spacing: 0;
}
[data-sticky-first-col-rows="true"]
.table.table-zebra
:where(thead tr, tbody tr:not(:last-child))
> :where(th, td) {
border-bottom: var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000);
}
/*
* Horizontal-scroll fade for the pinned checkbox column: instead of hiding the
* scrolled-away content behind a hard opaque block, a gradient on the right edge
* of the sticky cell fades the incoming content, signalling there is more behind
* it. overflow: visible lets the ::after gradient extend past the cell; the cell
* keeps its own (zebra) background so the checkbox stays legible.
*/
[data-sticky-first-col-rows="true"] .table.table-zebra td.sticky-first-col-cell {
overflow: visible;
}
[data-sticky-first-col-rows="true"] .table.table-zebra td.sticky-first-col-cell::after,
[data-sticky-first-col-rows="true"] .table.table-zebra thead th.sticky-first-col-th::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 100%;
width: 1.25rem;
pointer-events: none;
}
[data-sticky-first-col-rows="true"]
.table.table-zebra
tbody
tr:nth-child(odd)
> td.sticky-first-col-cell::after {
background: linear-gradient(to right, var(--color-base-100), transparent);
}
[data-sticky-first-col-rows="true"]
.table.table-zebra
tbody
tr:nth-child(even)
> td.sticky-first-col-cell::after {
background: linear-gradient(to right, var(--color-base-200), transparent);
}
[data-sticky-first-col-rows="true"]
.table.table-zebra
tbody
tr[data-row-interactive="true"]:is(:hover, :has(:focus-visible))
> td.sticky-first-col-cell::after {
background: linear-gradient(to right, var(--color-base-300), transparent);
}
[data-sticky-first-col-rows="true"] .table.table-zebra thead th.sticky-first-col-th::after {
background: linear-gradient(to right, var(--color-base-100), transparent);
}
/* Compact density: narrower fade so the gradient does not visually reach into the
checkbox column's own content area in the tighter table-xs cell. */
[data-sticky-first-col-rows="true"] .table.table-zebra.table-xs td.sticky-first-col-cell::after,
[data-sticky-first-col-rows="true"] .table.table-zebra.table-xs thead th.sticky-first-col-th::after {
width: 0.5rem;
}
/*
* Sort-header tooltip. Rendered via the native Popover API (see the SortTooltip
* JS hook), so it lives in the browser TOP LAYER: it is not clipped by the
* members table's overflow (overflow-x:auto forces overflow-y:auto, which used
* to clip a CSS pseudo-tooltip below the header) and paints above the sticky
* header, neighbor cells, and the pinned checkbox column's fade without any
* z-index hacks. This block only resets the popover UA defaults and gives it a
* small daisyUI-flavoured tooltip look; the JS hook sets top/left each show.
*/
.sort-tooltip {
position: fixed;
inset: auto;
margin: 0;
border: 0;
padding: 0.25rem 0.5rem;
width: max-content;
max-width: 16rem;
overflow: visible;
background: var(--color-neutral, oklch(0.2 0 0));
color: var(--color-neutral-content, oklch(0.98 0 0));
font-size: 0.75rem;
line-height: 1rem;
border-radius: 0.25rem;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.3);
pointer-events: none;
}
/*
* Vertically center the row checkbox in the sticky first column using the
* table-native vertical-align: middle. This keeps display: table-cell intact so
* the cell background fills the full row height (no white gap at the bottom).
* The fieldset margin-bottom reset removes the mb-2 from the shared input
* component so no residual margin shifts the checkbox off-center.
*/
[data-sticky-first-col-rows="true"] .table.table-zebra td.sticky-first-col-cell,
[data-sticky-first-col-rows="true"] .table.table-zebra thead th.sticky-first-col-th {
vertical-align: middle;
}
[data-sticky-first-col-rows="true"] .table.table-zebra td.sticky-first-col-cell fieldset,
[data-sticky-first-col-rows="true"] .table.table-zebra thead th.sticky-first-col-th fieldset {
margin-bottom: 0;
}