social components
inlay.at
atproto
components
sdui
1"use client";
2
3import {
4 createContext,
5 useState,
6 useContext,
7 useEffect,
8 type ReactNode,
9} from "react";
10import NextLink from "next/link";
11
12// ── Context ──
13
14const BlockerContext = createContext<{
15 isBlocked: boolean;
16 setIsBlocked: (v: boolean) => void;
17}>({ isBlocked: false, setIsBlocked: () => {} });
18
19export function NavigationBlockerProvider({
20 children,
21}: {
22 children: ReactNode;
23}) {
24 const [isBlocked, setIsBlocked] = useState(false);
25
26 useEffect(() => {
27 if (!isBlocked) return;
28 const handler = (e: BeforeUnloadEvent) => e.preventDefault();
29 window.addEventListener("beforeunload", handler);
30 return () => window.removeEventListener("beforeunload", handler);
31 }, [isBlocked]);
32
33 return (
34 <BlockerContext.Provider value={{ isBlocked, setIsBlocked }}>
35 {children}
36 </BlockerContext.Provider>
37 );
38}
39
40export function useNavigationBlocker() {
41 return useContext(BlockerContext);
42}
43
44// ── Link ──
45
46export default function Link({
47 onNavigate,
48 ...props
49}: React.ComponentProps<typeof NextLink>) {
50 const { isBlocked } = useContext(BlockerContext);
51
52 return (
53 <NextLink
54 {...props}
55 onNavigate={(e) => {
56 if (
57 isBlocked &&
58 !window.confirm("You have unsaved changes. Leave anyway?")
59 ) {
60 e.preventDefault();
61 return;
62 }
63 onNavigate?.(e);
64 }}
65 />
66 );
67}