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:
@@ -7,8 +7,9 @@ import {
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { getAccountRaw, LS, setAccountRaw } from "../lib/storage";
|
||||
import { getAccountRaw, LS, readAccount, setAccountRaw, writeAccount } from "../lib/storage";
|
||||
import { parseChannelLayout, type ChannelLayout } from "../lib/channelLayout";
|
||||
import { type SortState } from "../lib/columnSort";
|
||||
import { useNavigation, useNavigationActions } from "./NavigationProvider";
|
||||
import type { ChannelStatusFilter, ChannelsView } from "./Channels";
|
||||
|
||||
@@ -42,8 +43,10 @@ type ChannelsState = {
|
||||
search: string;
|
||||
statusFilter: ChannelStatusFilter;
|
||||
view: ChannelsView;
|
||||
// The table/cards/rows density (shared by the subscribed + discovery tabs).
|
||||
// The table/cards density (shared by the subscribed + discovery tabs).
|
||||
layout: ChannelLayout;
|
||||
// The subscribed table's sort, lifted out of DataTable so the card layout shares it.
|
||||
sort: SortState;
|
||||
// Bumped to tell the manager to drop a stale column filter when an intent sends the user to a
|
||||
// specific set (the header's "without full history" link).
|
||||
filtersResetToken: number;
|
||||
@@ -58,6 +61,7 @@ interface ChannelsActions {
|
||||
setStatusFilter: (f: ChannelStatusFilter) => void;
|
||||
setView: (v: ChannelsView) => void;
|
||||
setLayout: (l: ChannelLayout) => void;
|
||||
setSort: (s: SortState) => void;
|
||||
// "Focus this channel in the manager": jump to Channels (subscriptions tab) and seed its name
|
||||
// filter so the channel is isolated. Called from the feed sidebar and the notifications inbox.
|
||||
focusChannel: (name: string) => void;
|
||||
@@ -70,6 +74,7 @@ const StateContext = createContext<ChannelsState>({
|
||||
statusFilter: "all",
|
||||
view: "subscribed",
|
||||
layout: "table",
|
||||
sort: null,
|
||||
filtersResetToken: 0,
|
||||
focusName: null,
|
||||
focusToken: 0,
|
||||
@@ -79,6 +84,7 @@ const ActionsContext = createContext<ChannelsActions>({
|
||||
setStatusFilter: () => {},
|
||||
setView: () => {},
|
||||
setLayout: () => {},
|
||||
setSort: () => {},
|
||||
focusChannel: () => {},
|
||||
goToFullHistory: () => {},
|
||||
});
|
||||
@@ -104,6 +110,7 @@ export function ChannelsProvider({ children }: { children: ReactNode }) {
|
||||
const [layout, setLayoutState] = useState<ChannelLayout>(() =>
|
||||
parseChannelLayout(getAccountRaw(LS.channelLayout)),
|
||||
);
|
||||
const [sort, setSortState] = useState<SortState>(() => readAccount<SortState>(LS.channelSort, null));
|
||||
const [filtersResetToken, setFiltersResetToken] = useState(0);
|
||||
const [focusName, setFocusName] = useState<string | null>(null);
|
||||
const [focusToken, setFocusToken] = useState(0);
|
||||
@@ -121,6 +128,10 @@ export function ChannelsProvider({ children }: { children: ReactNode }) {
|
||||
setLayoutState(l);
|
||||
setAccountRaw(LS.channelLayout, l);
|
||||
}, []);
|
||||
const setSort = useCallback((s: SortState) => {
|
||||
setSortState(s);
|
||||
writeAccount(LS.channelSort, s);
|
||||
}, []);
|
||||
|
||||
const focusChannel = useCallback(
|
||||
(name: string) => {
|
||||
@@ -144,12 +155,12 @@ export function ChannelsProvider({ children }: { children: ReactNode }) {
|
||||
}, [page]);
|
||||
|
||||
const state = useMemo<ChannelsState>(
|
||||
() => ({ search, statusFilter, view, layout, filtersResetToken, focusName, focusToken }),
|
||||
[search, statusFilter, view, layout, filtersResetToken, focusName, focusToken],
|
||||
() => ({ search, statusFilter, view, layout, sort, filtersResetToken, focusName, focusToken }),
|
||||
[search, statusFilter, view, layout, sort, filtersResetToken, focusName, focusToken],
|
||||
);
|
||||
const actions = useMemo<ChannelsActions>(
|
||||
() => ({ setSearch, setStatusFilter, setView, setLayout, focusChannel, goToFullHistory }),
|
||||
[setSearch, setStatusFilter, setView, setLayout, focusChannel, goToFullHistory],
|
||||
() => ({ setSearch, setStatusFilter, setView, setLayout, setSort, focusChannel, goToFullHistory }),
|
||||
[setSearch, setStatusFilter, setView, setLayout, setSort, focusChannel, goToFullHistory],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user