feat: show contextual navigation loading
This commit is contained in:
@@ -1,31 +1,50 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
useTransition,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
|
||||
const PortfolioRouteContext = createContext<(() => void) | null>(null);
|
||||
import type { PublicPortfolio } from '@/server/content';
|
||||
import { PortfolioNavigationContext } from './PortfolioNavigation';
|
||||
import { CaseStudyDrawer } from './CaseStudyDrawer';
|
||||
import { CaseStudyLoading } from './CaseStudyLoading';
|
||||
import { LoadingIndicator } from './LoadingIndicator';
|
||||
|
||||
export function PortfolioRouteShell({
|
||||
children,
|
||||
homePath,
|
||||
background,
|
||||
portfolio,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
homePath: string;
|
||||
background: ReactNode;
|
||||
portfolio: PublicPortfolio;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [target, setTarget] = useState<string | null>(null);
|
||||
const [kind, setKind] = useState<'project' | 'language' | 'dismiss'>('project');
|
||||
const pending = isPending && target !== null;
|
||||
const languagePending = pending && kind === 'language';
|
||||
const showDrawer = pathname !== homePath || (pending && kind === 'project');
|
||||
const previousPath = useRef(pathname);
|
||||
const canReturnWithBack = useRef(false);
|
||||
const returnScrollY = useRef<number | null>(null);
|
||||
const previousScrollRestoration = useRef<ScrollRestoration>('auto');
|
||||
|
||||
useEffect(() => {
|
||||
const onPageShow = (event: PageTransitionEvent) => { if (event.persisted) setTarget(null); };
|
||||
window.addEventListener('pageshow', onPageShow);
|
||||
return () => window.removeEventListener('pageshow', onPageShow);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname === homePath) {
|
||||
canReturnWithBack.current = false;
|
||||
@@ -51,22 +70,35 @@ export function PortfolioRouteShell({
|
||||
}, [homePath, pathname]);
|
||||
|
||||
const dismissCaseStudy = useCallback(() => {
|
||||
if (canReturnWithBack.current) {
|
||||
router.back();
|
||||
return;
|
||||
}
|
||||
router.replace(homePath, { scroll: false });
|
||||
setKind('dismiss');
|
||||
setTarget(homePath);
|
||||
startTransition(() => {
|
||||
if (canReturnWithBack.current) router.back();
|
||||
else router.replace(homePath, { scroll: false });
|
||||
});
|
||||
}, [homePath, router]);
|
||||
|
||||
return (
|
||||
<PortfolioRouteContext.Provider value={dismissCaseStudy}>
|
||||
{children}
|
||||
</PortfolioRouteContext.Provider>
|
||||
<PortfolioNavigationContext.Provider value={{
|
||||
dismiss: dismissCaseStudy,
|
||||
loadingLabel: portfolio.messages.loading.project,
|
||||
navigate: (href, options) => {
|
||||
if (href === pathname && !pending) return;
|
||||
setTarget(href);
|
||||
setKind(options.kind ?? 'project');
|
||||
startTransition(() => {
|
||||
if (options.replace) router.replace(href, { scroll: options.scroll });
|
||||
else router.push(href, { scroll: options.scroll });
|
||||
});
|
||||
},
|
||||
}}>
|
||||
<div inert={languagePending} aria-busy={languagePending || undefined}>
|
||||
{background}
|
||||
{showDrawer ? <CaseStudyDrawer portfolio={portfolio}>
|
||||
{pending && kind === 'project' ? <CaseStudyLoading /> : children}
|
||||
</CaseStudyDrawer> : children}
|
||||
</div>
|
||||
{languagePending && <LoadingIndicator label={portfolio.messages.loading.language} fullPage />}
|
||||
</PortfolioNavigationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useDismissCaseStudy() {
|
||||
const dismiss = useContext(PortfolioRouteContext);
|
||||
if (!dismiss) throw new Error('useDismissCaseStudy must be used within PortfolioRouteShell');
|
||||
return dismiss;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user