73 lines
2.6 KiB
TypeScript
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;
|
|
}
|