npm
Screens

Settings

Grouped rows, footer dock, and live controls.

Settings
MaxCourt member since 2026
Audio
Music
Sounds
Volume
Game
Notifications
royal-games-ui · v1.3.0
9:41▮▮▮
"use client";

import {
  Avatar,
  Body,
  Bolt,
  BottomStack,
  BundleCell,
  BundleCells,
  Button,
  Chrome,
  ChromeRow,
  Coin,
  CornerRibbon,
  Counter,
  Counters,
  Dim,
  Dock,
  FloatChrome,
  Gem,
  GroupKicker,
  Heart,
  LevelBadge,
  MapPin,
  MapScene,
  Meter,
  Pack,
  PackRow,
  Panel,
  PageDots,
  Podium,
  PodiumColumn,
  ProfileChip,
  RankRow,
  Ribbon,
  Screen,
  ScreenName,
  SettingsRow,
  ShopRow,
  StarBurst,
  Strike,
  Tab,
  TabBar,
  TimerPill,
  Toggle,
  TopRibbon,
  Slider,
  Version,
  Well,
  Wordmark,
  YouBadge,
} from "@objectifthunes/royal-games-ui";
import { useState, type ReactNode } from "react";

const ignoreAction = () => undefined;

interface ResourceCountersProps {
  interactive?: boolean;
  onAddCoins?: () => void;
  onAddGems?: () => void;
}

interface NavigationProps {
  onNavigate?: (value: string) => void;
}

function ResourceCounters({
  interactive = true,
  onAddCoins = ignoreAction,
  onAddGems = ignoreAction,
}: ResourceCountersProps) {
  return (
    <Counters aria-label="Player resources">
      {interactive ? (
        <>
          <Counter graphic={<Coin />} onAdd={onAddCoins} addLabel="Add coins">
            2,450
          </Counter>
          <Counter graphic={<Gem />} onAdd={onAddGems} addLabel="Add gems">
            12
          </Counter>
        </>
      ) : (
        <>
          <Counter graphic={<Coin />}>2,450</Counter>
          <Counter graphic={<Gem />}>12</Counter>
        </>
      )}
    </Counters>
  );
}

interface RoyalChromeProps {
  name: ReactNode;
  onAddCoins?: () => void;
  onAddGems?: () => void;
  onOpenSettings?: () => void;
}

function RoyalChrome({
  name,
  onAddCoins = ignoreAction,
  onAddGems = ignoreAction,
  onOpenSettings = ignoreAction,
}: RoyalChromeProps) {
  return (
    <Chrome>
      <ChromeRow tone="velvet">
        <ProfileChip
          avatar={
            <Avatar decorative size="small">
              ♛
            </Avatar>
          }
          name="Max"
          caption="LEVEL 12"
        />
        <ResourceCounters onAddCoins={onAddCoins} onAddGems={onAddGems} />
      </ChromeRow>
      <ChromeRow tone="stone">
        <ScreenName>{name}</ScreenName>
        <Button
          tone="primary"
          size="small"
          iconOnly
          aria-label="Settings"
          onClick={onOpenSettings}
        >
          ⚙
        </Button>
      </ChromeRow>
    </Chrome>
  );
}

interface RoyalTabsProps extends NavigationProps {
  initial: string;
}

function RoyalTabs({ initial, onNavigate = ignoreAction }: RoyalTabsProps) {
  const [tab, setTab] = useState(initial);

  const handleTabChange = (value: string) => {
    setTab(value);
    onNavigate(value);
  };

  return (
    <TabBar
      aria-label="Primary navigation"
      value={tab}
      onValueChange={handleTabChange}
    >
      <Tab value="shop" icon="🛒">
        Shop
      </Tab>
      <Tab value="ranks" icon="♛">
        Ranks
      </Tab>
      <Tab value="home" icon="⌂">
        Home
      </Tab>
      <Tab value="map" icon="⚑">
        Map
      </Tab>
      <Tab value="more" icon="⚙" dot>
        More
      </Tab>
    </TabBar>
  );
}

// SCREEN_RECIPE: SettingsScreen
export interface SettingsScreenProps extends NavigationProps {
  onBack?: () => void;
  onEditProfile?: () => void;
  onOpenCloudSave?: () => void;
  onOpenLanguage?: () => void;
  onRestore?: () => void;
  onSupport?: () => void;
}

/** Copy-ready controlled settings recipe with a dedicated scrolling form slot. */
export function SettingsScreen({
  onBack = ignoreAction,
  onEditProfile = ignoreAction,
  onNavigate = ignoreAction,
  onOpenCloudSave = ignoreAction,
  onOpenLanguage = ignoreAction,
  onRestore = ignoreAction,
  onSupport = ignoreAction,
}: SettingsScreenProps = {}) {
  const [music, setMusic] = useState(true);
  const [sounds, setSounds] = useState(true);
  const [notifications, setNotifications] = useState(true);

  return (
    <Screen aria-label="Settings">
      <Dock edge="top" tone="stone" flush>
        <SettingsRow
          variant="control"
          icon={
            <Button
              tone="primary"
              size="small"
              iconOnly
              aria-label="Back"
              onClick={onBack}
            >
              ‹
            </Button>
          }
          align="balanced"
          label={<Ribbon size="screen">Settings</Ribbon>}
          control={<span />}
        />
      </Dock>
      <Dock edge="top" tone="paper">
        <SettingsRow
          variant="control"
          icon={
            <Avatar decorative size="large">
              ♛
            </Avatar>
          }
          label="Max"
          caption="Court member since 2026"
          control={
            <Button tone="primary" size="small" onClick={onEditProfile}>
              EDIT
            </Button>
          }
        />
      </Dock>
      <Body layout="form">
        <Panel tone="paper">
          <GroupKicker>Audio</GroupKicker>
          <SettingsRow
            variant="control"
            icon="♪"
            label="Music"
            control={
              <Toggle
                aria-label="Music"
                checked={music}
                onChange={(event) => setMusic(event.currentTarget.checked)}
              />
            }
          />
          <SettingsRow
            variant="control"
            icon="🔔︎"
            label="Sounds"
            control={
              <Toggle
                aria-label="Sounds"
                checked={sounds}
                onChange={(event) => setSounds(event.currentTarget.checked)}
              />
            }
          />
          <SettingsRow
            variant="control"
            icon="♪"
            label="Volume"
            control={<Slider aria-label="Volume" defaultValue={62} />}
          />
        </Panel>
        <Panel tone="paper">
          <GroupKicker>Game</GroupKicker>
          <SettingsRow
            variant="action"
            icon="🌐︎"
            label="Language"
            trailing="English"
            onPress={onOpenLanguage}
          />
          <SettingsRow
            variant="control"
            icon="🔔︎"
            label="Notifications"
            control={
              <Toggle
                aria-label="Notifications"
                checked={notifications}
                onChange={(event) =>
                  setNotifications(event.currentTarget.checked)
                }
              />
            }
          />
          <SettingsRow
            variant="action"
            icon="☁︎"
            label="Cloud save"
            trailing="Synced ✓"
            onPress={onOpenCloudSave}
          />
        </Panel>
      </Body>
      <BottomStack>
        <Dock tone="stone" density="compact">
          <Counters aria-label="Settings support actions" layout="spread">
            <Button tone="paper" size="small" width="full" onClick={onRestore}>
              RESTORE
            </Button>
            <Button
              tone="primary"
              size="small"
              width="full"
              onClick={onSupport}
            >
              SUPPORT
            </Button>
          </Counters>
          <Version>royal-games-ui · v1.3.0</Version>
        </Dock>
        <RoyalTabs initial="more" onNavigate={onNavigate} />
      </BottomStack>
    </Screen>
  );
}

This is the exact controlled React component rendered above. The two top docks, scrolling form region, support dock, and navigation each consume their own layout slot without local CSS.