NarraLeaf

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 / exit to achieve fade, slide, scale, etc.
  • Staggered items – For more elaborate effects, wrap each button in its own motion.button or use Motion variants.
  • Presence management – The NarraLeaf page router already manages presence for Page content. Add your own <AnimatePresence> only when conditionally rendering outside a Page.

On this page