fix(managers): two-way menu flip + extract useCardPager
- The tag menu's flip was one-way: once anchored above it stayed there, so a row scrolling toward the top dragged the menu off the screen. It now re-decides both ways each pass — prefer the side it is on (no flapping), leave it as soon as that side stops fitting and the other does. Verified: trigger at y=229 (no room above) flips back below, menuTop 253 = btnBottom 249 + 4, nothing off-screen. - Extract the card pagination into lib/useCardPager: size (persisted, validated), page, the out-of-range clamp-commit, the slice and the Pager props lived twice, and every review round had to fix both copies in lockstep. The two tabs now keep only what differs — which rows, which storage key, and when to reset. The clamp is gated on the card layout too, so it no longer rewrites card state while the table is the one on screen.
This commit is contained in:
@@ -6,7 +6,7 @@ import { api, type DiscoveredChannel } from "../lib/api";
|
||||
import { accountKey, LS, readAccount, writeAccount } from "../lib/storage";
|
||||
import { formatCountOrDash } from "../lib/format";
|
||||
import { parseSortState, sortRows, type SortState } from "../lib/columnSort";
|
||||
import { CARD_PAGE_SIZES, parseCardSize } from "../lib/channelLayout";
|
||||
import { useCardPager } from "../lib/useCardPager";
|
||||
import { subsColumn } from "./channelColumns";
|
||||
import { notify } from "../lib/notifications";
|
||||
import { notifyYouTubeActionError } from "../lib/youtubeErrors";
|
||||
@@ -49,11 +49,6 @@ export default function ChannelDiscovery({
|
||||
setSortState(s);
|
||||
writeAccount(LS.channelDiscoverySort, s);
|
||||
};
|
||||
// Card layout pagination (its own; the table keeps its rows-per-page inside DataTable).
|
||||
const [cardSize, setCardSize] = useState(() =>
|
||||
parseCardSize(readAccount(LS.channelDiscoveryCardSize, null)),
|
||||
);
|
||||
const [cardPage, setCardPage] = useState(0);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ["discovered-channels"],
|
||||
@@ -98,13 +93,6 @@ export default function ChannelDiscovery({
|
||||
const rows = (query.data ?? []).filter(
|
||||
(c) => !q || `${c.title ?? ""} ${c.handle ?? ""}`.toLowerCase().includes(q)
|
||||
);
|
||||
// Changing what you're looking at starts over at the first page, as the table does on its own
|
||||
// filter/sort change. NOT keyed on the row count: subscribing drops a row from this tab, and being
|
||||
// thrown to page 1 for it would punish the very action you just took. A list that shrank past your
|
||||
// page is handled by committing the clamp instead (below).
|
||||
useEffect(() => {
|
||||
setCardPage(0);
|
||||
}, [search, sort]);
|
||||
|
||||
const columns: Column<DiscoveredChannel>[] = [
|
||||
// Actions first, mirroring the Subscriptions table (where it sits right after Prio) so the two
|
||||
@@ -201,16 +189,17 @@ export default function ChannelDiscovery({
|
||||
// copy+sort the whole list every render for a result nothing reads.
|
||||
const isCards = layout === "cards";
|
||||
const sortedRows = isCards ? sortRows(rows, columns, sort) : rows;
|
||||
const cardTotalPages = cardSize <= 0 ? 1 : Math.max(1, Math.ceil(sortedRows.length / cardSize));
|
||||
const cardSafePage = Math.min(cardPage, cardTotalPages - 1);
|
||||
// Commit the clamp when the list shrinks past your page, so it can't spring back later.
|
||||
const { paged: pagedRows, pagerProps, resetPage } = useCardPager(
|
||||
sortedRows,
|
||||
LS.channelDiscoveryCardSize,
|
||||
isCards,
|
||||
);
|
||||
// Changing what you're looking at starts over at the first page. NOT keyed on the row count:
|
||||
// subscribing drops a row from this tab, and being thrown to page 1 for it would punish the very
|
||||
// action you just took — a list that shrank past your page is handled by the hook's clamp.
|
||||
useEffect(() => {
|
||||
if (cardPage > cardTotalPages - 1) setCardPage(cardTotalPages - 1);
|
||||
}, [cardPage, cardTotalPages]);
|
||||
const pagedRows =
|
||||
!isCards || cardSize <= 0
|
||||
? sortedRows
|
||||
: sortedRows.slice(cardSafePage * cardSize, cardSafePage * cardSize + cardSize);
|
||||
resetPage();
|
||||
}, [search, sort, resetPage]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -221,19 +210,7 @@ export default function ChannelDiscovery({
|
||||
{/* Cards has no column headers, so a dropdown drives the sort + a pager (table uses its own). */}
|
||||
{layout === "cards" && (
|
||||
<div className="flex items-center gap-3 shrink-0">
|
||||
<Pager
|
||||
page={cardSafePage}
|
||||
size={cardSize}
|
||||
total={sortedRows.length}
|
||||
pageSizeOptions={CARD_PAGE_SIZES}
|
||||
onPageChange={setCardPage}
|
||||
onSizeChange={(s) => {
|
||||
setCardSize(s);
|
||||
writeAccount(LS.channelDiscoveryCardSize, s);
|
||||
setCardPage(0);
|
||||
}}
|
||||
sizeLabel={t("channels.cardsPerPage")}
|
||||
/>
|
||||
<Pager {...pagerProps} />
|
||||
<ColumnSortControl
|
||||
columns={columns}
|
||||
sort={sort}
|
||||
|
||||
Reference in New Issue
Block a user