feat(managers): paginate the card layout + its top scroll-fade

UAT round 3 feedback on the card layout:
- Add a pager + "Cards per page" to the card view (it infinitely-scrolled before).
  Extracted the pager cluster out of DataTable into a reusable Pager component
  (the table now renders it too — no behaviour change); the card view slices the
  sorted rows and drives its own per-account page size (subscribed + discovery).
- Restore the top edge-fade in the card layout. The channels page registers
  fadeTop:false for its sticky-header table; the card layout has no sticky header,
  so App now re-enables the top fade when the channel layout is cards.
This commit is contained in:
2026-07-19 23:28:43 +02:00
parent 9c4dd0163a
commit dff4152522
8 changed files with 209 additions and 88 deletions
+7 -1
View File
@@ -27,6 +27,7 @@ import {
} from "./lib/storage";
import { useNavigation, useNavigationActions } from "./components/NavigationProvider";
import { useFeedFilters, useFeedFiltersActions } from "./components/FeedFiltersProvider";
import { useChannels } from "./components/ChannelsProvider";
import { useFeedView, useFeedViewActions } from "./components/FeedViewProvider";
import { moduleDef } from "./lib/modules";
import { PAGE_CONTENT, PAGE_RAIL } from "./components/moduleRegistry";
@@ -62,6 +63,9 @@ export default function App() {
// handlers that mutate the feed view (onShowInFeed, tag-click, the feed scrollKey).
const filters = useFeedFilters();
const { setFilters } = useFeedFiltersActions();
// The channel manager turns its top fade OFF for the table (its sticky header sits at the scroller
// top) but wants it ON for the card layout (no sticky header) — so the fade depends on the layout.
const { layout: channelLayout } = useChannels();
// The feed's view mode (card/row/tile density) lives in FeedViewProvider — server-synced, shared
// by the feed page and the channel page. App reads it only to hand to <Feed>/<ChannelPage>.
const view = useFeedView();
@@ -387,7 +391,9 @@ export default function App() {
) : (
<PageShell
scrollKey={normalScrollKey}
fadeTop={fadeTop ?? true}
// Channels: the registry disables the top fade for its sticky-header table, but the card
// layout has no sticky header, so re-enable it there.
fadeTop={activePage === "channels" ? channelLayout === "cards" : fadeTop ?? true}
header={<Header me={meQuery.data!} onYtSearch={enterYtSearch} />}
banners={
<>