Quick Menu
Scope
The Quick Menu is a context panel that lets the player access common actions (undo, history, auto, save/load, settings, exit) with one click or key.
The component below covers undo, history, auto-forward, save, load, settings, and exit. Route names and exit behavior remain application code.
1. Create the component
import { useGame, usePreference, useRouter } from "narraleaf-react";
import { ArrowLeft, History, Play, Save, FileText, Settings, Home } from "lucide-react";
export default function QuickMenu() {
const game = useGame();
const router = useRouter();
const liveGame = game.getLiveGame();
const [autoForward, setAutoForward] = usePreference("autoForward");
// helpers ------------------------------------------------
const undo = () => liveGame.undo();
const toHistory = () => router.navigate("/history");
const toggleAuto = () => setAutoForward(!autoForward);
const save = () => router.navigate("/save");
const load = () => router.navigate("/load");
const openSettings = () => router.navigate("/settings");
const exitGame = () => /* your own exit logic (e.g. useApp().exitGame()) */ undefined;
// menu item ---------------------------------------------
const Item = ({ icon: Icon, label, onClick }: { icon: any; label: string; onClick: () => void }) => (
<button onClick={onClick} className="flex items-center gap-1 px-2 py-1 rounded-full hover:bg-white/20">
<Icon className="w-4 h-4" />
<span className="text-xs">{label}</span>
</button>
);
return (
<div className="fixed bottom-5 left-0 right-0 flex justify-center pointer-events-none">
<div className="flex gap-2 bg-black/40 rounded-full px-4 py-1 pointer-events-auto">
<Item icon={ArrowLeft} label="Undo" onClick={undo} />
<Item icon={History} label="History" onClick={toHistory} />
<Item icon={Play} label={autoForward ? "Auto: On" : "Auto: Off"} onClick={toggleAuto} />
<Item icon={Save} label="Save" onClick={save} />
<Item icon={FileText} label="Load" onClick={load} />
<Item icon={Settings} label="Settings" onClick={openSettings} />
<Item icon={Home} label="Exit" onClick={exitGame} />
</div>
</div>
);
}2. Use in LayoutRouter
Place the menu in the root layout's default page (/). Player creates the root layout automatically.
import { GameProviders, Page, Player } from "narraleaf-react";
import QuickMenu from "./QuickMenu";
function MyApp() {
return (
<GameProviders>
<Player story={story} onReady={({ liveGame }) => liveGame.newGame()}>
{/* QuickMenu will be displayed when the router is at `/`, which is the default page */}
{/* You can also use `router.clear().navigate("/")` to display the quick menu in-game */}
{/* This means the quick menu will be displayed when all other pages are closed */}
<Page name={null}>
<QuickMenu />
</Page>
</Player>
</GameProviders>
);
}3. Transitions
The component above is static. Turn the top-level element into a Motion element to add enter and exit animation. The page router keeps the component mounted until its exit animation finishes.
import { motion } from "motion/react";
export default function QuickMenu() {
// ...hooks & helpers...
return (
<motion.div
/* initial ➜ before enter */
initial={{ opacity: 0, scale: 0.8, y: 20 }}
/* animate ➜ after enter */
animate={{ opacity: 1, scale: 1, y: 0 }}
/* exit ➜ before unmount */
exit={{ opacity: 0, scale: 0.8, y: 20 }}
/* timing curve */
transition={{ type: "spring", stiffness: 300, damping: 25, duration: 0.3 }}
className="fixed bottom-5 left-0 right-0 flex justify-center pointer-events-none"
>
<div className="flex gap-2 bg-black/40 rounded-full px-4 py-1 pointer-events-auto">
{/* menu items */}
</div>
</motion.div>
);
}Guidelines:
- Root motion element – Only the top-level node needs to be a
motion.*element; inner content can stay regular JSX. - Customize
initial / animate / exitto achieve fade, slide, scale, etc. - Staggered items – For more elaborate effects, wrap each button in its own
motion.buttonor use Motion variants. - Presence management – The NarraLeaf page router already manages presence for
Pagecontent. Add your own<AnimatePresence>only when conditionally rendering outside aPage.