mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
feat(renderer): Zen mode (Esc exits, hides chrome)
This commit is contained in:
@@ -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 (
|
||||
<main className="h-screen w-screen overflow-hidden bg-background">
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
onLayout={(sizes) => setPaneSizes({ sidebar: 0, editor: sizes[0], preview: sizes[1] })}
|
||||
>
|
||||
<ResizablePanel defaultSize={previewVisible ? 50 : 100} minSize={20}>
|
||||
<section className="h-full bg-background">
|
||||
<EditorPane />
|
||||
</section>
|
||||
</ResizablePanel>
|
||||
{previewVisible && (
|
||||
<>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel defaultSize={50} minSize={20}>
|
||||
<section className="h-full border-l border-border bg-card/10">
|
||||
<PreviewPane />
|
||||
</section>
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)}
|
||||
</ResizablePanelGroup>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen flex-col bg-background text-foreground">
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user