feat(managers): actions-first discovery table, tabs in the back chain, pager scrolls to top
UAT round 4: - Discovery table: move Actions to the first column, mirroring the Subscriptions table (where it sits right after Prio) so both tabs' actions line up. - The manager's tabs now ride in history.state (`_chTab`), the same way the channel page (`_chan`) and the live search (`_yt`) already do — so Back steps back through the tabs you visited before it leaves the module. Arriving from elsewhere stamps the entry setPage pushed (one navigation never costs two Back presses). - Turning a page now scrolls the list back to its top (both the tables and the card grid, since they share Pager) — landing mid-list in an unseen page is disorienting.
This commit is contained in:
@@ -120,9 +120,19 @@ export function ChannelsProvider({ children }: { children: ReactNode }) {
|
||||
setStatusFilterState(f);
|
||||
setAccountRaw(LS.channelFilter, f);
|
||||
}, []);
|
||||
// The active tab rides in history.state (`_chTab`), the same way the channel page (`_chan`) and the
|
||||
// live YouTube search (`_yt`) do — so Back steps back through the tabs you actually visited instead
|
||||
// of jumping straight out to the previous module.
|
||||
const setView = useCallback((v: ChannelsView) => {
|
||||
setViewState(v);
|
||||
setAccountRaw(LS.channelsView, v);
|
||||
const st = window.history.state || {};
|
||||
// Only own an entry while the manager is already on screen. Arriving from elsewhere
|
||||
// (focusChannel / goToFullHistory) lets setPage push the entry — the stamp effect labels it — so
|
||||
// one navigation never costs two Back presses.
|
||||
if (st.sfPage === "channels") {
|
||||
window.history.pushState({ ...st, sfPage: "channels", _chTab: v }, "");
|
||||
}
|
||||
}, []);
|
||||
const setLayout = useCallback((l: ChannelLayout) => {
|
||||
setLayoutState(l);
|
||||
@@ -154,6 +164,27 @@ export function ChannelsProvider({ children }: { children: ReactNode }) {
|
||||
if (page !== "channels") setFocusName(null);
|
||||
}, [page]);
|
||||
|
||||
// Label the entry the manager was REACHED on (setPage pushes a bare { sfPage }), so stepping back
|
||||
// onto it later restores the tab it belonged to rather than leaving whatever tab is current.
|
||||
useEffect(() => {
|
||||
if (page !== "channels") return;
|
||||
const st = window.history.state || {};
|
||||
if (st._chTab === undefined) window.history.replaceState({ ...st, _chTab: view }, "");
|
||||
}, [page, view]);
|
||||
|
||||
// Back/Forward across tab entries: restore the tab the entry carries.
|
||||
useEffect(() => {
|
||||
function onPop(e: PopStateEvent) {
|
||||
const tab = e.state?._chTab;
|
||||
if (!tab) return; // an entry outside the manager — leave the tab alone
|
||||
const next = clampView(tab as string);
|
||||
setViewState(next);
|
||||
setAccountRaw(LS.channelsView, next);
|
||||
}
|
||||
window.addEventListener("popstate", onPop);
|
||||
return () => window.removeEventListener("popstate", onPop);
|
||||
}, []);
|
||||
|
||||
const state = useMemo<ChannelsState>(
|
||||
() => ({ search, statusFilter, view, layout, sort, filtersResetToken, focusName, focusToken }),
|
||||
[search, statusFilter, view, layout, sort, filtersResetToken, focusName, focusToken],
|
||||
|
||||
Reference in New Issue
Block a user