NarraLeaf

Adding Page Overlay (Settings Page)

Route Shape

A page overlay is UI displayed on top of the main game screen (e.g. settings, save/load, gallery) without interrupting the game flow. By defining the route structure with Layout and Page, and navigating with useRouter, you can implement overlays like a settings page.

The example uses a settings overlay opened from the Quick Menu.

1. Define Layout and Page Structure

Define a settings layout with child pages such as general and audio. Player already creates the root layout, so place Page and Layout directly inside it. Page name={null} is the default page for its parent layout.

import { GameProviders, Layout, Page, Player } from "narraleaf-react";

function AppLayout() {
  return (
    <GameProviders>
      <Player story={story} onReady={({ liveGame }) => liveGame.newGame()}>
        {/* Default page (game stage) - path: / */}
        <Page name={null}>
          <GameStage />
        </Page>

        {/* Settings overlay - path: /settings, /settings/general, etc. */}
        <Layout name="settings">
          <Page name={null}>
            <SettingsHome />
            {/* /settings - settings entry or default tab */}
          </Page>
          <Page name="general">
            <SettingsGeneral />
            {/* /settings/general - general settings */}
          </Page>
          <Page name="audio">
            <SettingsAudio />
            {/* /settings/audio - audio settings */}
          </Page>
        </Layout>
      </Player>
    </GameProviders>
  );
}

Do not add RootLayout yourself. Nesting another root layout inside Player creates the wrong route boundary.

2. Implement the Settings Page Component

The settings page acts as an overlay with a semi-transparent backdrop and centered panel. Clicking the backdrop or a close button returns to the game.

import { useRouter } from "narraleaf-react";

function SettingsGeneral() {
  const router = useRouter();

  return (
    <div
      className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
      onClick={() => router.back()}
      // Click overlay to close
    >
      <div
        className="bg-gray-900 rounded-lg p-6 w-96 max-h-[80vh] overflow-y-auto"
        onClick={(e) => e.stopPropagation()}
        // Prevent closing when clicking panel
      >
        <h2 className="text-xl font-bold mb-4">General Settings</h2>
        {/* Settings form content */}
        <button
          className="mt-4 px-4 py-2 bg-amber-500 rounded"
          onClick={() => router.back()}
        >
          Back
        </button>
      </div>
    </div>
  );
}

3. Open Settings from Quick Menu

In the Quick Menu, call router.navigate("/settings") to open the settings overlay:

import { useRouter } from "narraleaf-react";

function QuickMenu() {
  const router = useRouter();

  const openSettings = () => router.navigate("/settings");
  // Navigate to settings overlay

  return (
    <div className="fixed bottom-5 left-0 right-0 flex justify-center">
      <button onClick={openSettings}>Settings</button>
    </div>
  );
}

4. Router API Quick Reference

MethodDescription
router.navigate(path)Navigate to path, e.g. "/settings/general"
router.back()Go back
router.forward()Go forward
router.clear().navigate(path)Clear history then navigate (e.g. return to main)
router.getCurrentPath()Get current path
router.replace(path)Replace the current history entry
router.canGoBack()Check whether back() can navigate

5. Add Enter/Exit Animations

Wrap page content with Motion's motion.div; LayoutRouter keeps the exiting page mounted until its animation finishes:

import { motion } from "motion/react";

function SettingsGeneral() {
  const router = useRouter();

  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
      onClick={() => router.back()}
    >
      <motion.div
        initial={{ scale: 0.9, opacity: 0 }}
        animate={{ scale: 1, opacity: 1 }}
        exit={{ scale: 0.9, opacity: 0 }}
        className="bg-gray-900 rounded-lg p-6 w-96"
        onClick={(e) => e.stopPropagation()}
      >
        {/* ... */}
      </motion.div>
    </motion.div>
  );
}

See Also

On this page