From 1ccde4baa3449810bbcaef08fabd9e7826adf224 Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Sat, 6 Jun 2026 08:13:38 +0530 Subject: [PATCH] feat(renderer): Zen mode (Esc exits, hides chrome) --- src/renderer/components/layout/AppShell.tsx | 30 ++++++++++++++++++++ src/renderer/hooks/use-zen-mode.ts | 22 +++++++++++++++ tests/unit/hooks/use-zen-mode.test.ts | 31 +++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 src/renderer/hooks/use-zen-mode.ts create mode 100644 tests/unit/hooks/use-zen-mode.test.ts diff --git a/src/renderer/components/layout/AppShell.tsx b/src/renderer/components/layout/AppShell.tsx index 4805a2b..ef1eab3 100644 --- a/src/renderer/components/layout/AppShell.tsx +++ b/src/renderer/components/layout/AppShell.tsx @@ -11,13 +11,43 @@ import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/componen import { useFileShortcuts } from '@/hooks/use-file-shortcuts'; import { useRestoreLastFolder } from '@/hooks/use-restore-last-folder'; import { useRegisterMenuCommands, useBridgeNativeMenu } from '@/lib/commands/register-menu-commands'; +import { useZenMode } from '@/hooks/use-zen-mode'; export function AppShell() { useFileShortcuts(); useRestoreLastFolder(); useRegisterMenuCommands(); useBridgeNativeMenu(); + useZenMode(); const { sidebarVisible, previewVisible, paneSizes, setPaneSizes } = useAppStore(); + const zenMode = useAppStore((s) => s.zenMode); + + if (zenMode) { + return ( +
+ setPaneSizes({ sidebar: 0, editor: sizes[0], preview: sizes[1] })} + > + +
+ +
+
+ {previewVisible && ( + <> + + +
+ +
+
+ + )} +
+
+ ); + } return (
diff --git a/src/renderer/hooks/use-zen-mode.ts b/src/renderer/hooks/use-zen-mode.ts new file mode 100644 index 0000000..f3109a6 --- /dev/null +++ b/src/renderer/hooks/use-zen-mode.ts @@ -0,0 +1,22 @@ +import { useEffect } from 'react'; +import { useAppStore } from '@/stores/app-store'; + +/** + * Mounts a global keydown listener that exits zen mode when the user + * presses Escape. Should be called once at the App root. + */ +export function useZenMode() { + const zenMode = useAppStore((s) => s.zenMode); + const setZenMode = useAppStore((s) => s.setZenMode); + + useEffect(() => { + if (!zenMode) return; + const handler = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + setZenMode(false); + } + }; + window.addEventListener('keydown', handler); + return () => window.removeEventListener('keydown', handler); + }, [zenMode, setZenMode]); +} diff --git a/tests/unit/hooks/use-zen-mode.test.ts b/tests/unit/hooks/use-zen-mode.test.ts new file mode 100644 index 0000000..6f97fbe --- /dev/null +++ b/tests/unit/hooks/use-zen-mode.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { renderHook } from '@testing-library/react'; +import { useZenMode } from '@/hooks/use-zen-mode'; +import { useAppStore } from '@/stores/app-store'; + +describe('useZenMode', () => { + beforeEach(() => { + localStorage.clear(); + useAppStore.setState({ ...useAppStore.getInitialState(), zenMode: true }); + }); + + it('attaches a keydown listener that exits zen mode on Escape', () => { + renderHook(() => useZenMode()); + expect(useAppStore.getState().zenMode).toBe(true); + + // Simulate Escape keydown + const event = new KeyboardEvent('keydown', { key: 'Escape' }); + window.dispatchEvent(event); + + expect(useAppStore.getState().zenMode).toBe(false); + }); + + it('does nothing if zen mode is already off', () => { + useAppStore.setState({ ...useAppStore.getInitialState(), zenMode: false }); + const setZenMode = vi.fn(); + useAppStore.setState({ setZenMode }); + renderHook(() => useZenMode()); + // No Escape dispatched; setZenMode should not be called + expect(setZenMode).not.toHaveBeenCalled(); + }); +});