From 739a9cee99bab02da7c70688ca5db15ccf64c537 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 19 Feb 2026 19:45:51 +0200 Subject: [PATCH] add skip navigation, page title, and global ARIA live region --- src/App.tsx | 10 ++++++++++ src/components/layout/AppShell.tsx | 8 +++++++- src/hooks/useAnnounce.ts | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useAnnounce.ts diff --git a/src/App.tsx b/src/App.tsx index 4753c06..37b6ee6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,12 +13,15 @@ import { SettingsDialog } from "@/components/settings/SettingsDialog"; import { ToastContainer } from "@/components/toast/ToastContainer"; import { ShortcutHelpModal } from "@/components/shortcuts/ShortcutHelpModal"; import { useKeyboardShortcuts } from "@/hooks/useKeyboardShortcuts"; +import { useAnnounceStore } from "@/hooks/useAnnounce"; export default function App() { const initialized = useAppStore((s) => s.initialized); const init = useAppStore((s) => s.init); const view = useAppStore((s) => s.view); const reduceMotion = useAppStore((s) => s.settings.reduceMotion); + const boardTitle = useBoardStore((s) => s.board?.title); + const announcement = useAnnounceStore((s) => s.message); const [settingsOpen, setSettingsOpen] = useState(false); const [shortcutHelpOpen, setShortcutHelpOpen] = useState(false); @@ -111,6 +114,10 @@ export default function App() { }; }, []); + useEffect(() => { + document.title = boardTitle ? `${boardTitle} - OpenPylon` : "OpenPylon"; + }, [boardTitle, view]); + const handleOpenSettings = useCallback(() => { setSettingsOpen(true); }, []); @@ -129,6 +136,9 @@ export default function App() { return ( +
+ {announcement} +
{view.type === "board-list" ? ( diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx index d6a9969..614fd7b 100644 --- a/src/components/layout/AppShell.tsx +++ b/src/components/layout/AppShell.tsx @@ -10,8 +10,14 @@ export function AppShell({ children }: AppShellProps) { return (
+ + Skip to main content + -
{children}
+
{children}
); diff --git a/src/hooks/useAnnounce.ts b/src/hooks/useAnnounce.ts new file mode 100644 index 0000000..5a707a5 --- /dev/null +++ b/src/hooks/useAnnounce.ts @@ -0,0 +1,19 @@ +import { create } from "zustand"; + +interface AnnounceState { + message: string; + announce: (text: string) => void; +} + +export const useAnnounceStore = create((set) => ({ + message: "", + announce: (text) => { + // Clear first to ensure re-announcement of identical messages + set({ message: "" }); + requestAnimationFrame(() => set({ message: text })); + }, +})); + +export function useAnnounce() { + return useAnnounceStore((s) => s.announce); +}