feat(managers): shared sort across the table and card layouts
Cards have no column headers to sort by, so lift the sort out of DataTable into one shared state that BOTH views drive and reflect. New lib/columnSort.ts holds the comparator + click-cycle (one source of truth); DataTable gains an optional controlled sort (sort + onSortChange) and falls back to its internal persistKey sort when they're omitted, so every other table is untouched. The subscribed sort lives in ChannelsProvider, discovery keeps its own (its columns differ); a new ColumnSortControl dropdown (options derived from the columns' sortable metadata, feed-style + asc/desc toggle) drives it in cards mode. Per-account, persisted, per-tab.
This commit is contained in:
@@ -29,7 +29,9 @@ import Tooltip from "./Tooltip";
|
||||
import DataTable, { type Column } from "./DataTable";
|
||||
import ChannelLayoutGrid from "./ChannelLayoutGrid";
|
||||
import ViewSwitcher, { type ViewOption } from "./ViewSwitcher";
|
||||
import ColumnSortControl from "./ColumnSortControl";
|
||||
import { CHANNEL_LAYOUTS, type ChannelLayout } from "../lib/channelLayout";
|
||||
import { sortRows } from "../lib/columnSort";
|
||||
import { PageToolbar } from "./PageShell";
|
||||
import ChannelLink from "./ChannelLink";
|
||||
import AboutCell from "./AboutCell";
|
||||
@@ -72,11 +74,12 @@ export default function Channels() {
|
||||
statusFilter,
|
||||
view,
|
||||
layout,
|
||||
sort,
|
||||
filtersResetToken,
|
||||
focusName: focusChannelName,
|
||||
focusToken: focusChannelToken,
|
||||
} = useChannels();
|
||||
const { setSearch, setStatusFilter, setView, setLayout, focusChannel: onFocusChannel } =
|
||||
const { setSearch, setStatusFilter, setView, setLayout, setSort, focusChannel: onFocusChannel } =
|
||||
useChannelsActions();
|
||||
const { openChannel: onViewChannel, setPage } = useNavigationActions();
|
||||
const feedFilters = useFeedFilters();
|
||||
@@ -497,6 +500,19 @@ export default function Channels() {
|
||||
/>
|
||||
);
|
||||
const hasLayout = view === "subscribed" || view === "discovery";
|
||||
// In cards there are no column headers to sort by, so a dropdown drives the SAME shared sort state
|
||||
// the table header uses (table ↔ cards stay in sync). Table mode keeps the header clicks.
|
||||
const sortControl = (
|
||||
<ColumnSortControl
|
||||
columns={columns}
|
||||
sort={sort}
|
||||
onChange={setSort}
|
||||
label={t("channels.sort.label")}
|
||||
defaultLabel={t("channels.sort.default")}
|
||||
ascLabel={t("channels.sort.asc")}
|
||||
descLabel={t("channels.sort.desc")}
|
||||
/>
|
||||
);
|
||||
|
||||
const tabs = (
|
||||
<div className="px-4 pt-4 max-w-[96rem] mx-auto flex items-center justify-between gap-4 flex-wrap">
|
||||
@@ -517,6 +533,7 @@ export default function Channels() {
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{view === "subscribed" && statsBlock}
|
||||
{view === "subscribed" && layout === "cards" && sortControl}
|
||||
{hasLayout && layoutSwitcher}
|
||||
</div>
|
||||
</div>
|
||||
@@ -651,12 +668,14 @@ export default function Channels() {
|
||||
controlsLeading={statusChips}
|
||||
controlsInBand
|
||||
controlsBandClassName="px-4 max-w-[96rem] mx-auto"
|
||||
sort={sort}
|
||||
onSortChange={setSort}
|
||||
rowClassName={(c) => (c.hidden ? "opacity-60" : "")}
|
||||
emptyText={t("channels.empty")}
|
||||
/>
|
||||
) : (
|
||||
<ChannelLayoutGrid
|
||||
rows={visibleChannels}
|
||||
rows={sortRows(visibleChannels, columns, sort)}
|
||||
columns={columns}
|
||||
rowKey={(c) => c.id}
|
||||
leadKey="priority"
|
||||
|
||||
Reference in New Issue
Block a user