Skip to content

Commit d3e1d55

Browse files
Add Cmd+Shift+E focus mode and Escape exit behaviors
- Cmd+Shift+E toggles focus mode (hides sidebar + notes panel) - Cmd+E exits focus mode fully when both panels are hidden - Escape in editor exits focus mode before focusing notes pane - Escape in notes pane reopens sidebar if hidden - Editor header gets left padding in focus mode for traffic lights - Suppress note row layout animations on panel visibility changes - Preserve editor cursor position during focus mode toggle
1 parent 703670b commit d3e1d55

6 files changed

Lines changed: 67 additions & 20 deletions

File tree

app/src/app.tsx

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
getPaneFocusShortcut,
3535
isCommandPaletteShortcut,
3636
isEditorFindShortcut,
37+
isFocusModeShortcut,
3738
isNotesSearchShortcut,
3839
isSidebarToggleShortcut,
3940
} from "@/shared/lib/keyboard";
@@ -85,7 +86,9 @@ function App() {
8586

8687
const setSettingsOpen = useUIStore((s) => s.setSettingsOpen);
8788
const sidebarVisible = useUIStore((s) => s.sidebarVisible);
89+
const notesPanelVisible = useUIStore((s) => s.notesPanelVisible);
8890
const toggleSidebar = useUIStore((s) => s.toggleSidebar);
91+
const toggleFocusMode = useUIStore((s) => s.toggleFocusMode);
8992
const setFocusedPane = useShellStore((s) => s.setFocusedPane);
9093

9194
const openCommandPalette = useEffectEvent(() => {
@@ -123,11 +126,23 @@ function App() {
123126
return;
124127
}
125128

129+
if (isFocusModeShortcut(event)) {
130+
event.preventDefault();
131+
event.stopPropagation();
132+
event.stopImmediatePropagation();
133+
toggleFocusMode();
134+
return;
135+
}
136+
126137
if (isSidebarToggleShortcut(event)) {
127138
event.preventDefault();
128139
event.stopPropagation();
129140
event.stopImmediatePropagation();
130-
toggleSidebar();
141+
if (useUIStore.getState().notesPanelVisible) {
142+
toggleSidebar();
143+
} else {
144+
toggleFocusMode();
145+
}
131146
return;
132147
}
133148

@@ -291,11 +306,7 @@ function App() {
291306
data-tauri-drag-region
292307
/>
293308
) : null}
294-
<Container
295-
className="h-full w-full"
296-
id="comet-shell"
297-
key={sidebarVisible ? "s" : "h"}
298-
>
309+
<Container className="h-full w-full" id="comet-shell">
299310
{sidebarVisible ? (
300311
<>
301312
<Section
@@ -314,21 +325,25 @@ function App() {
314325
</>
315326
) : null}
316327

317-
<Section
318-
defaultSize={280}
319-
disableResponsive
320-
maxSize={340}
321-
minSize={220}
322-
className="select-none"
323-
>
324-
<NotesPane {...notesPaneProps} />
325-
</Section>
328+
{notesPanelVisible ? (
329+
<>
330+
<Section
331+
defaultSize={280}
332+
disableResponsive
333+
maxSize={340}
334+
minSize={220}
335+
className="select-none"
336+
>
337+
<NotesPane {...notesPaneProps} />
338+
</Section>
326339

327-
<Bar
328-
className="bg-accent/35 z-30 cursor-col-resize"
329-
expandInteractiveArea={{ left: 5, right: 5 }}
330-
size={1}
331-
/>
340+
<Bar
341+
className="bg-accent/35 z-30 cursor-col-resize"
342+
expandInteractiveArea={{ left: 5, right: 5 }}
343+
size={1}
344+
/>
345+
</>
346+
) : null}
332347

333348
<Section minSize={300}>
334349
<EditorPane {...editorPaneProps} />

app/src/features/editor/note-editor.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import {
7979
} from "@/features/editor/lib/note-editor-selection";
8080
import { useNoteEditorSearchSync } from "@/features/editor/hooks/use-note-editor-search-sync";
8181
import { useNoteEditorToolbarActions } from "@/features/editor/hooks/use-note-editor-toolbar-actions";
82+
import { useUIStore } from "@/features/settings/store/use-ui-store";
8283
import { useShellStore } from "@/features/shell/store/use-shell-store";
8384
import {
8485
isEditorFindShortcut,
@@ -551,6 +552,10 @@ export const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
551552
{
552553
key: "Escape",
553554
run(view) {
555+
const uiState = useUIStore.getState();
556+
if (!uiState.notesPanelVisible) {
557+
uiState.toggleFocusMode();
558+
}
554559
useShellStore.getState().setFocusedPane("notes");
555560
blurEditorView(view);
556561
return true;

app/src/features/notes/ui/notes-pane.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,13 @@ export function NotesPane({
685685
skipAnimationUntilRef.current = Date.now() + 500;
686686
}
687687

688+
// Suppress layout animations when panel visibility changes.
689+
const prevSidebarVisibleRef = useRef(sidebarVisible);
690+
if (prevSidebarVisibleRef.current !== sidebarVisible) {
691+
prevSidebarVisibleRef.current = sidebarVisible;
692+
skipAnimationUntilRef.current = Date.now() + 500;
693+
}
694+
688695
const shouldSkipAnimation = Date.now() < skipAnimationUntilRef.current;
689696
const searchInputRef = useRef<HTMLInputElement | null>(null);
690697
const pendingNotesPaneSelectionRef = useRef<

app/src/features/settings/store/use-ui-store.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ type UIStore = {
4949
sidebarVisible: boolean;
5050
toggleSidebar(): void;
5151

52+
notesPanelVisible: boolean;
53+
toggleFocusMode(): void;
54+
5255
sidebarNotesChildrenOpen: boolean;
5356
setSidebarNotesChildrenOpen(open: boolean): void;
5457

@@ -121,6 +124,16 @@ export const useUIStore = create<UIStore>()(
121124
set((state) => ({ sidebarVisible: !state.sidebarVisible }));
122125
},
123126

127+
notesPanelVisible: true,
128+
toggleFocusMode: () => {
129+
set((state) => {
130+
const entering = state.sidebarVisible || state.notesPanelVisible;
131+
return entering
132+
? { sidebarVisible: false, notesPanelVisible: false }
133+
: { sidebarVisible: true, notesPanelVisible: true };
134+
});
135+
},
136+
124137
sidebarNotesChildrenOpen: true,
125138
setSidebarNotesChildrenOpen: (sidebarNotesChildrenOpen) => {
126139
set({ sidebarNotesChildrenOpen });
@@ -135,6 +148,7 @@ export const useUIStore = create<UIStore>()(
135148
editorVimMode: state.editorVimMode,
136149
themeName: state.themeName,
137150
sidebarVisible: state.sidebarVisible,
151+
notesPanelVisible: state.notesPanelVisible,
138152
noteSortPrefs: state.noteSortPrefs,
139153
expandedSidebarTagPaths: state.expandedSidebarTagPaths,
140154
sidebarNotesChildrenOpen: state.sidebarNotesChildrenOpen,

app/src/features/shell/editor-pane.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ export function EditorPane({
480480
null,
481481
);
482482
const editorFontSize = useUIStore((s) => s.editorFontSize);
483+
const notesPanelVisible = useUIStore((s) => s.notesPanelVisible);
483484
const showToolbar = useUIStore((s) => s.showEditorToolbar);
484485
const editorSpellCheck = useUIStore((s) => s.editorSpellCheck);
485486
const editorVimMode = useUIStore((s) => s.editorVimMode);
@@ -856,6 +857,7 @@ export function EditorPane({
856857
<header
857858
className={cn(
858859
"flex h-13 shrink-0 items-center justify-between gap-3 px-4",
860+
!notesPanelVisible && "pl-24",
859861
showHeaderBorder && !findOpen && "border-separator border-b",
860862
)}
861863
>

app/src/shared/lib/keyboard.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export function isSidebarToggleShortcut(event: ShortcutEvent) {
4040
return matchesShortcut(event, { code: "KeyE", key: "e" });
4141
}
4242

43+
export function isFocusModeShortcut(event: ShortcutEvent) {
44+
return matchesShortcut(event, { code: "KeyE", key: "e", shift: true });
45+
}
46+
4347
export function isNotesSearchShortcut(event: ShortcutEvent) {
4448
return matchesShortcut(event, {
4549
code: "KeyF",

0 commit comments

Comments
 (0)