Tiles view: +/- control for cards per row
Adjusts the tile grid's column count as an offset from the existing width-based auto-fill default, shared across Library, Decks, Card search, and set card lists. Clicking the count resets to the default.
This commit is contained in:
@@ -1709,6 +1709,106 @@ export function ViewToggle({
|
||||
);
|
||||
}
|
||||
|
||||
// --- Tiles per row --------------------------------------------------------
|
||||
|
||||
// Mirror `.tile-grid`'s `repeat(auto-fill, minmax(190px, 1fr))` and `gap` in
|
||||
// styles.css — used to work out how many columns that default fills.
|
||||
const TILE_MIN_WIDTH = 190;
|
||||
const TILE_GAP = 16;
|
||||
const TILE_MAX_COLUMNS = 12;
|
||||
|
||||
export interface TileColumns {
|
||||
/** Columns currently shown (the width-based default plus the adjustment). */
|
||||
columns: number;
|
||||
/** True while the user has moved away from the width-based default. */
|
||||
adjusted: boolean;
|
||||
adjust: (delta: number) => void;
|
||||
reset: () => void;
|
||||
/** Spread onto each `.tile-grid` (`<div className="tile-grid" {...gridProps}>`). */
|
||||
gridProps: { ref: (el: HTMLDivElement | null) => void; className: string; style?: React.CSSProperties };
|
||||
}
|
||||
|
||||
/** Per-row tile count for the Tiles view, adjustable with `TileColumnsControl`.
|
||||
*
|
||||
* The default stays the CSS auto-fill (as many ~190px tiles as fit), so the
|
||||
* persisted setting is an *offset* from that rather than a fixed count: +1
|
||||
* means "one more than would fit by default" on a phone and a desktop alike,
|
||||
* and it keeps tracking the width across resizes and rotation. One offset is
|
||||
* shared by every tiles view. */
|
||||
export function useTileColumns(): TileColumns {
|
||||
const [offset, setOffset] = usePersistedState<number>("tile-columns-offset", 0);
|
||||
const [auto, setAuto] = useState(0);
|
||||
const observer = useRef<ResizeObserver | null>(null);
|
||||
|
||||
// Every grid in a view (one per group) shares the same width, so whichever
|
||||
// mounted last is the one measured.
|
||||
const ref = useCallback((el: HTMLDivElement | null) => {
|
||||
if (!el) return;
|
||||
observer.current?.disconnect();
|
||||
// A grid that's been unmounted (e.g. switching to List) reports width 0;
|
||||
// keep the last real measurement instead.
|
||||
const measure = (w: number) => {
|
||||
if (w > 0) setAuto(Math.max(1, Math.floor((w + TILE_GAP) / (TILE_MIN_WIDTH + TILE_GAP))));
|
||||
};
|
||||
measure(el.clientWidth);
|
||||
observer.current = new ResizeObserver((entries) => measure(entries[0].contentRect.width));
|
||||
observer.current.observe(el);
|
||||
}, []);
|
||||
useEffect(() => () => observer.current?.disconnect(), []);
|
||||
|
||||
const base = auto || 1;
|
||||
const columns = Math.min(TILE_MAX_COLUMNS, Math.max(1, base + offset));
|
||||
const adjusted = offset !== 0 && auto > 0;
|
||||
return {
|
||||
columns,
|
||||
adjusted,
|
||||
// Clamp against the visible count, so pressing − at one column doesn't
|
||||
// bank an offset that has to be undone before + does anything.
|
||||
adjust: (delta) => {
|
||||
const next = Math.min(TILE_MAX_COLUMNS, Math.max(1, columns + delta));
|
||||
setOffset(next - base);
|
||||
},
|
||||
reset: () => setOffset(0),
|
||||
gridProps: adjusted
|
||||
? {
|
||||
ref,
|
||||
className: columns === 1 ? "tile-grid tile-grid-single" : "tile-grid",
|
||||
style: { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` },
|
||||
}
|
||||
: { ref, className: "tile-grid" },
|
||||
};
|
||||
}
|
||||
|
||||
/** − N + stepper for the tiles-per-row count; clicking N resets to the default. */
|
||||
export function TileColumnsControl({ tiles, disabled }: { tiles: TileColumns; disabled?: boolean }) {
|
||||
return (
|
||||
<span className="view-toggle tile-columns" style={disabled ? { opacity: 0.5 } : undefined}>
|
||||
<button
|
||||
onClick={() => tiles.adjust(-1)}
|
||||
disabled={disabled || tiles.columns <= 1}
|
||||
title="Fewer cards per row (bigger tiles)"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<button
|
||||
className="tile-columns-count"
|
||||
onClick={tiles.reset}
|
||||
disabled={disabled || !tiles.adjusted}
|
||||
title={tiles.adjusted ? "Cards per row — click to reset to the default" : "Cards per row"}
|
||||
>
|
||||
{tiles.columns}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => tiles.adjust(1)}
|
||||
disabled={disabled || tiles.columns >= TILE_MAX_COLUMNS}
|
||||
title="More cards per row (smaller tiles)"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A single card rendered as a full image tile, with optional corner + footer.
|
||||
* Clicking the image opens a larger click-to-preview modal.
|
||||
|
||||
Reference in New Issue
Block a user