src/navigation.ts
import { useRef, useState, type SetStateAction } from "react";
import type { InboxTab } from "../shared/inbox-tabs";
import type { SettingsSection } from "./settings/sections";
export type MailLocation = {
page: "mail" | "settings";
view: "inbox" | "snoozed" | "archive";
tab: InboxTab;
settingsSection: SettingsSection;
accountFilter: string;
labelFilter: string;
query: string;
readFilter: "all" | "unread";
relevanceFilter: "all" | "1" | "2" | "3";
sort: "newest" | "oldest" | "relevance";
selectedId: string | null;
opened: boolean;
};
export function useMailNavigation() {
const [location, update] = useState<MailLocation>({
page: "mail", view: "inbox", tab: "primary", settingsSection: "interaction", accountFilter: "all", labelFilter: "all", query: "", readFilter: "all", relevanceFilter: "all", sort: "newest", selectedId: null, opened: false,
});
// Menu history stays in memory. Reader selection is not a visit.
const history = useRef({ entries: [location], index: 0, editingSearch: false, reader: null as MailLocation | null });
const visit = (patch: Partial<MailLocation>, search = false) => {
const next = { ...location, ...patch };
const menuKeys = ["page", "settingsSection", "view", "tab", "accountFilter", "labelFilter", "query", "readFilter", "relevanceFilter", "sort"] as const;
if (menuKeys.every((key) => next[key] === location[key])) {
update(next);
return;
}
const current = history.current;
current.reader = null;
current.entries[current.index] = location;
current.entries = current.entries.slice(0, current.index + 1);
if (search && current.editingSearch) current.entries[current.index] = next;
else {
current.entries.push(next);
current.index++;
}
current.editingSearch = search;
update(next);
};
const travel = (direction: -1 | 1, resolve: (location: MailLocation) => MailLocation) => {
const current = history.current;
if (direction === -1 && location.opened) {
current.reader = location;
update(resolve({ ...location, opened: false }));
return;
}
if (direction === 1 && current.reader && !location.opened) {
const reader = current.reader;
current.reader = null;
update(resolve(reader));
return;
}
const index = current.index + direction;
if (index < 0 || index >= current.entries.length) return;
current.reader = null;
current.entries[current.index] = location;
current.index = index;
current.editingSearch = false;
update(resolve({ ...current.entries[index], selectedId: null, opened: false }));
};
return {
location, visit, travel,
update: (next: SetStateAction<MailLocation>) => {
history.current.reader = null;
update(next);
},
canBack: location.opened || history.current.index > 0,
canForward: (!location.opened && history.current.reader !== null) || history.current.index < history.current.entries.length - 1,
};
}