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
"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.