feat(tables): name a filter whose column is off screen, and let its chip clear it
A tabletHidden/narrowHidden column below its width, or any column in a card layout, took its header funnel with it: the rows were narrowed by a filter nobody could see. The cue is shown by the inverse of the column's own container query, so it agrees with the table at every width and text size. Wired into DataTable, the playlist table and the channel cards (E28 F2).
This commit is contained in:
@@ -10,6 +10,7 @@ import { columnHideClass, filterRows, type Column, type FilterMap } from "../../
|
||||
import { navItemAttrs, useListNav } from "../../lib/listNav";
|
||||
import { useMediaQuery } from "../../lib/useMediaQuery";
|
||||
import { SURFACE } from "./surfaces";
|
||||
import { HiddenFilterCue } from "./HiddenFilterCue";
|
||||
|
||||
// A reusable, client-side data table: per-column sort + filter (in the header) + pagination,
|
||||
// with its sort/filter/page state optionally persisted to localStorage so a reload (F5) keeps
|
||||
@@ -70,6 +71,7 @@ export default function DataTable<T>({
|
||||
fixedLayout = false,
|
||||
keyboardNav = false,
|
||||
reveal,
|
||||
surfacedFilters,
|
||||
}: {
|
||||
rows: T[];
|
||||
columns: Column<T>[];
|
||||
@@ -126,6 +128,9 @@ export default function DataTable<T>({
|
||||
// `seq` turns to the page that now holds the row `key`. Focus is the caller's (it knows which of
|
||||
// the row's controls it came from); the page is the table's, so it is decided here.
|
||||
reveal?: { key: string; seq: number } | null;
|
||||
// Filter keys the caller shows somewhere besides their column (the channel manager's tag chips):
|
||||
// the hidden-filter cue leaves those out, it would name them twice.
|
||||
surfacedFilters?: string[];
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const initial = loadPersist(persistKey);
|
||||
@@ -234,6 +239,15 @@ export default function DataTable<T>({
|
||||
commitFilters({ ...filters, [key]: value });
|
||||
setPage(0);
|
||||
}
|
||||
const cue = (by: "width" | "cards") => (
|
||||
<HiddenFilterCue
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
by={by}
|
||||
surfaced={surfacedFilters}
|
||||
onFilter={applyFilter}
|
||||
/>
|
||||
);
|
||||
|
||||
// The pager + rows-per-page cluster on its own, so it can be rendered a second time at the bottom
|
||||
// (controlsPosition "both") WITHOUT repeating the leading controls (e.g. the status chips) — those
|
||||
@@ -288,6 +302,7 @@ export default function DataTable<T>({
|
||||
{wide ? (
|
||||
// A container, so a column hides by the table's own width (`tabletHidden`), not the window's.
|
||||
<div className="@container">
|
||||
{cue("width")}
|
||||
<table className={`w-full border-collapse text-sm ${fixedLayout ? "table-fixed" : ""}`}>
|
||||
{/* In fixed-layout mode a colgroup drives the column widths: those with an explicit
|
||||
`width` keep it, and the unspecified (fluid) columns split the remainder — so the
|
||||
@@ -337,36 +352,39 @@ export default function DataTable<T>({
|
||||
) : (
|
||||
/* Narrow screens: a compact card per row — the primary column as the heading, the
|
||||
rest flowing in a single wrapping meta line (no full-width label/value gaps). */
|
||||
<div ref={cardsRef} className="flex flex-col gap-2">
|
||||
{paged.map((row, i) => (
|
||||
<div
|
||||
key={rowKey(row)}
|
||||
{...rowProps?.(row)}
|
||||
{...(keyboardNav && navItemAttrs(i))}
|
||||
className={`${SURFACE.card} p-3 ${rowClassName?.(row) ?? ""}`}
|
||||
>
|
||||
{columns
|
||||
.filter((c) => c.cardPrimary)
|
||||
.map((col) => (
|
||||
<div key={col.key} className="font-medium">
|
||||
{col.render(row)}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1.5 mt-2 text-sm">
|
||||
<>
|
||||
{cue("cards")}
|
||||
<div ref={cardsRef} className="flex flex-col gap-2">
|
||||
{paged.map((row, i) => (
|
||||
<div
|
||||
key={rowKey(row)}
|
||||
{...rowProps?.(row)}
|
||||
{...(keyboardNav && navItemAttrs(i))}
|
||||
className={`${SURFACE.card} p-3 ${rowClassName?.(row) ?? ""}`}
|
||||
>
|
||||
{columns
|
||||
.filter((c) => !c.hideInCard && !c.cardPrimary)
|
||||
.filter((c) => c.cardPrimary)
|
||||
.map((col) => (
|
||||
<span key={col.key} className="inline-flex items-center gap-1">
|
||||
{col.cardLabel !== false && (
|
||||
<span className="text-muted text-xs">{col.header}</span>
|
||||
)}
|
||||
<div key={col.key} className="font-medium">
|
||||
{col.render(row)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1.5 mt-2 text-sm">
|
||||
{columns
|
||||
.filter((c) => !c.hideInCard && !c.cardPrimary)
|
||||
.map((col) => (
|
||||
<span key={col.key} className="inline-flex items-center gap-1">
|
||||
{col.cardLabel !== false && (
|
||||
<span className="text-muted text-xs">{col.header}</span>
|
||||
)}
|
||||
{col.render(row)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{paged.length === 0 && (
|
||||
|
||||
Reference in New Issue
Block a user