diff --git a/frontend/components/Note/NoteFocusMode.tsx b/frontend/components/Note/NoteFocusMode.tsx new file mode 100644 index 0000000..87ffb6f --- /dev/null +++ b/frontend/components/Note/NoteFocusMode.tsx @@ -0,0 +1,200 @@ +import React, { useEffect, useRef } from 'react'; +import ReactDOM from 'react-dom'; +import { XMarkIcon } from '@heroicons/react/24/outline'; +import MarkdownRenderer from '../Shared/MarkdownRenderer'; +import { Note } from '../../entities/Note'; +import { ENABLE_NOTE_COLOR } from '../../config/featureFlags'; + +const shouldUseLightText = (hexColor: string | undefined): boolean => { + if (!hexColor) return false; + const hex = hexColor.replace('#', ''); + const r = parseInt(hex.substring(0, 2), 16); + const g = parseInt(hex.substring(2, 4), 16); + const b = parseInt(hex.substring(4, 6), 16); + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + return luminance < 0.4; +}; + +interface NoteFocusModeProps { + note: Note; + isEditing: boolean; + saveStatus: 'saved' | 'saving' | 'unsaved'; + onNoteChange: (updates: Partial) => void; + onContentChange?: (newContent: string) => void; + onEditNote: () => void; + onExitEditing: () => void; + onClose: () => void; +} + +const NoteFocusMode: React.FC = ({ + note, + isEditing, + saveStatus, + onNoteChange, + onContentChange, + onEditNote, + onExitEditing, + onClose, +}) => { + const textareaRef = useRef(null); + const noteColor = ENABLE_NOTE_COLOR ? note.color : undefined; + const lightText = shouldUseLightText(noteColor); + + useEffect(() => { + const prev = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + return () => { + document.body.style.overflow = prev; + }; + }, []); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault(); + e.stopPropagation(); + if (isEditing) { + onExitEditing(); + } else { + onClose(); + } + } + }; + document.addEventListener('keydown', handleKeyDown, true); + return () => document.removeEventListener('keydown', handleKeyDown, true); + }, [isEditing, onExitEditing, onClose]); + + useEffect(() => { + if (isEditing && textareaRef.current) { + textareaRef.current.focus(); + } + }, [isEditing]); + + const textColor = noteColor + ? lightText + ? '#ffffff' + : '#333333' + : undefined; + const mutedColor = noteColor + ? lightText + ? '#e0e0e0' + : '#666666' + : undefined; + + return ReactDOM.createPortal( +
+ {/* Minimal header */} +
+
+ {isEditing && note.title && ( + <> + {saveStatus === 'saving' && ( + + Saving... + + )} + {saveStatus === 'saved' && ( + + Saved + + )} + {saveStatus === 'unsaved' && ( + + Unsaved + + )} + + )} +
+ +
+ + {/* Centered content */} +
+
+ {isEditing ? ( + <> + + onNoteChange({ title: e.target.value }) + } + placeholder="Note title..." + className="w-full bg-transparent text-gray-900 dark:text-gray-100 border-none focus:outline-none focus:ring-0 mb-6" + style={{ + ...(textColor ? { color: textColor } : {}), + fontSize: '2.25rem', + lineHeight: '2.5rem', + fontWeight: 500, + paddingLeft: 0, + paddingRight: 0, + }} + /> +