Phase 10: command palette, settings, backup/restore, sample campaign
- Command palette (Ctrl/Cmd+K): fuzzy search over pages + active-campaign characters/notes/NPCs/quests + actions; keyboard nav; header ⌘K button - Settings page: theme, full backup export/import (restore replaces all in one transaction), load sample campaign, danger-zone clear-all - Backup lib (src/lib/io/backup.ts) snapshots every table; sample seeder (src/lib/sample.ts) builds a demo campaign - Header gets ⌘K + settings gear; routes + nav wired Backup round-trip unit tests + command-palette/settings e2e. (Deferred from this phase, noted in plan: audio, Discord/Foundry/Roll20, i18n, push notifications.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link, Outlet, useRouterState } from '@tanstack/react-router';
|
||||
import { useUiStore } from '@/stores/uiStore';
|
||||
import { CommandPalette } from '@/components/CommandPalette';
|
||||
import { useActiveCampaign, useCampaigns } from '@/features/campaigns/hooks';
|
||||
import { cn } from '@/lib/cn';
|
||||
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
|
||||
@@ -53,6 +55,19 @@ function ThemeToggle() {
|
||||
|
||||
export function RootLayout() {
|
||||
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
||||
const [paletteOpen, setPaletteOpen] = useState(false);
|
||||
|
||||
// Global Ctrl/Cmd+K opens the command palette.
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
||||
e.preventDefault();
|
||||
setPaletteOpen((v) => !v);
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
@@ -78,6 +93,10 @@ export function RootLayout() {
|
||||
})}
|
||||
</nav>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button size="sm" variant="ghost" onClick={() => setPaletteOpen(true)} title="Command palette (Ctrl/Cmd+K)" aria-label="Open command palette">
|
||||
⌘K
|
||||
</Button>
|
||||
<Link to="/settings" className="rounded-md p-1.5 text-sm text-muted hover:text-ink" title="Settings" aria-label="Settings">⚙</Link>
|
||||
<CampaignSwitcher />
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
@@ -90,6 +109,7 @@ export function RootLayout() {
|
||||
</main>
|
||||
|
||||
<RollTray />
|
||||
{paletteOpen && <CommandPalette onClose={() => setPaletteOpen(false)} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user