Files
MathApp_frontend/src/nav/Navigator.tsx

73 lines
2.6 KiB
TypeScript

import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
import type { Route, TabName } from './routes';
/**
* ناوبریِ پشته‌ای، هماهنگ با تاریخچه‌ی مرورگر.
*
* هر push یک ورودی به history اضافه می‌کند، پس دکمه‌ی back گوشی یا مرورگر
* یک صفحه عقب می‌رود به‌جای بیرون انداختنِ کاربر از اپ. تعویضِ تب پشته را
* از نو می‌سازد (مثلِ اپ‌های بومی).
*/
interface Nav {
route: Route;
/** تبی که ریشه‌ی پشته‌ی فعلی است؛ برای روشن کردنِ نوارِ پایین. */
tab: TabName | null;
canGoBack: boolean;
push: (route: Route) => void;
/** صفحه‌ی فعلی را عوض می‌کند بدونِ اضافه کردن به تاریخچه. */
replace: (route: Route) => void;
back: () => void;
switchTab: (tab: TabName) => void;
}
const NavContext = createContext<Nav | null>(null);
export function NavigatorProvider({ initial, children }: { initial: Route; children: ReactNode }) {
const [stack, setStack] = useState<Route[]>([initial]);
useEffect(() => {
const onPop = () => setStack((s) => (s.length > 1 ? s.slice(0, -1) : s));
window.addEventListener('popstate', onPop);
return () => window.removeEventListener('popstate', onPop);
}, []);
const push = useCallback((route: Route) => {
setStack((s) => [...s, route]);
window.history.pushState({ tadrisino: true }, '');
}, []);
const replace = useCallback((route: Route) => {
setStack((s) => [...s.slice(0, -1), route]);
}, []);
const back = useCallback(() => {
// popstate خودش صفحه را برمی‌دارد؛ پس دو منبعِ حقیقت نداریم.
window.history.back();
}, []);
const switchTab = useCallback((tab: TabName) => {
setStack([{ name: tab } as Route]);
}, []);
const value = useMemo<Nav>(() => {
const root = stack[0].name;
return {
route: stack[stack.length - 1],
tab: ['home', 'lessons', 'ask', 'subscription', 'profile'].includes(root) ? (root as TabName) : null,
canGoBack: stack.length > 1,
push,
replace,
back,
switchTab,
};
}, [stack, push, replace, back, switchTab]);
return <NavContext.Provider value={value}>{children}</NavContext.Provider>;
}
export function useNav(): Nav {
const nav = useContext(NavContext);
if (!nav) throw new Error('useNav بیرون از NavigatorProvider صدا زده شد');
return nav;
}