From e4615e19fae497a32b154e20d9bbb9f59eecddda Mon Sep 17 00:00:00 2001 From: Peter Hurst Date: Wed, 19 Aug 2026 15:22:31 +0100 Subject: [PATCH] feat(docs): demo page for cancelling navigation on dirty edits Adds /demos/cancel-navigation: a form whose in-app links confirm before leaving while it has unsaved edits, built by wrapping useLink's onClick rather than the Link component. Ticket: 676 --- packages/docs/src/App.tsx | 5 ++ .../docs/src/demos/CancelNavigationApp.tsx | 89 +++++++++++++++++++ .../docs/src/pages/CancelNavigationDemo.tsx | 15 ++++ packages/docs/src/pages/DemosIndex.tsx | 15 +++- packages/docs/src/router/routes.ts | 10 +++ 5 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 packages/docs/src/demos/CancelNavigationApp.tsx create mode 100644 packages/docs/src/pages/CancelNavigationDemo.tsx diff --git a/packages/docs/src/App.tsx b/packages/docs/src/App.tsx index b31e7dc..ce30f6f 100644 --- a/packages/docs/src/App.tsx +++ b/packages/docs/src/App.tsx @@ -13,6 +13,7 @@ import { basicRoutingDemoRoute, blogRoutingDemoRoute, dataGridDemoRoute, + cancelNavigationDemoRoute, asyncLookupDemoRoute, } from "./router/routes"; import Home from "./pages/Home"; @@ -24,6 +25,7 @@ import DemosIndex from "./pages/DemosIndex"; import BasicRoutingDemo from "./pages/BasicRoutingDemo"; import BlogRoutingDemo from "./pages/BlogRoutingDemo"; import DataGridDemo from "./pages/DataGridDemo"; +import CancelNavigationDemo from "./pages/CancelNavigationDemo"; import AsyncLookupDemo from "./pages/AsyncLookupDemo"; import NotFound from "./pages/NotFound"; @@ -67,6 +69,9 @@ export const App = () => ( + + + diff --git a/packages/docs/src/demos/CancelNavigationApp.tsx b/packages/docs/src/demos/CancelNavigationApp.tsx new file mode 100644 index 0000000..82ec88e --- /dev/null +++ b/packages/docs/src/demos/CancelNavigationApp.tsx @@ -0,0 +1,89 @@ +import { ReactNode, useState } from "react"; +import { Route, useLink } from "jarl-react"; +import { DefaultParams, RouteAtom } from "jarl-atoms"; +import { cancelNavigationDemoRoute, cancelNavigationOtherRoute } from "../router/routes"; + +/** A nav link that confirms before leaving while `dirty` is true, discarding the click if declined. */ +const GuardedLink = ({ + route, + to, + dirty, + children, +}: { + route: RouteAtom; + to: T; + dirty: boolean; + children: ReactNode; +}) => { + const { href, active, onClick } = useLink(route, to, { exact: true }); + return ( + { + event.preventDefault(); + if (dirty && !window.confirm("You have unsaved changes. Leave without saving?")) return; + onClick(); + }} + > + {children} + + ); +}; + +const Editor = ({ dirty, setDirty }: { dirty: boolean; setDirty: (dirty: boolean) => void }) => { + const [text, setText] = useState(""); + return ( +
+

Editor

+