From c8162ff3ccfcbd0f06407736f1aa2b3cc78b52fd Mon Sep 17 00:00:00 2001 From: Johnny Date: Sun, 16 Nov 2025 23:15:36 +0800 Subject: [PATCH] feat(web): add Focus Mode for distraction-free writing Add keyboard-activated Focus Mode to provide an immersive writing experience: Features: - Toggle with Cmd/Ctrl+Shift+F (matches GitHub, Google Docs) - Exit with Escape, toggle shortcut, button click, or backdrop click - Expands editor to ~80-90% of viewport with centered layout - Semi-transparent backdrop with blur effect - Maintains all editor functionality (attachments, shortcuts, etc.) - Smooth 300ms transitions Responsive Design: - Mobile (< 640px): 8px margins, 50vh min-height - Tablet (640-768px): 16px margins - Desktop (> 768px): 32px margins, 60vh min-height, 1024px max-width Implementation: - Centralized constants for easy maintenance (FOCUS_MODE_STYLES) - Extracted keyboard shortcuts and heights to named constants - JSDoc documentation for all new functions and interfaces - TypeScript type safety with 'as const' - Explicit positioning (top/left/right/bottom) to avoid width overflow Files Modified: - web/src/components/MemoEditor/index.tsx - Main Focus Mode logic - web/src/components/MemoEditor/Editor/index.tsx - Height adjustments - web/src/locales/en.json - Translation keys Design follows industry standards (GitHub Focus Mode, Notion, Obsidian) and maintains code quality with single source of truth pattern. --- .../components/MemoEditor/Editor/index.tsx | 30 ++++++- web/src/components/MemoEditor/index.tsx | 84 ++++++++++++++++++- web/src/locales/en.json | 4 +- 3 files changed, 112 insertions(+), 6 deletions(-) diff --git a/web/src/components/MemoEditor/Editor/index.tsx b/web/src/components/MemoEditor/Editor/index.tsx index e77aaedf8..c5cca4a5a 100644 --- a/web/src/components/MemoEditor/Editor/index.tsx +++ b/web/src/components/MemoEditor/Editor/index.tsx @@ -6,6 +6,19 @@ import { editorCommands } from "./commands"; import TagSuggestions from "./TagSuggestions"; import { useListAutoCompletion } from "./useListAutoCompletion"; +/** + * Editor height constraints + * - Normal mode: Limited to 50% viewport height to avoid excessive scrolling + * - Focus mode: Minimum 50vh on mobile, 60vh on desktop for immersive writing + */ +const EDITOR_HEIGHT = { + normal: "max-h-[50vh]", + focusMode: { + mobile: "min-h-[50vh]", + desktop: "md:min-h-[60vh]", + }, +} as const; + export interface EditorRefActions { getEditor: () => HTMLTextAreaElement | null; focus: FunctionType; @@ -30,10 +43,12 @@ interface Props { commands?: Command[]; onContentChange: (content: string) => void; onPaste: (event: React.ClipboardEvent) => void; + /** Whether Focus Mode is active - adjusts height constraints for immersive writing */ + isFocusMode?: boolean; } const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef) { - const { className, initialContent, placeholder, onPaste, onContentChange: handleContentChangeCallback } = props; + const { className, initialContent, placeholder, onPaste, onContentChange: handleContentChangeCallback, isFocusMode } = props; const [isInIME, setIsInIME] = useState(false); const editorRef = useRef(null); @@ -160,9 +175,18 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef< }); return ( -
+