@@ -16,6 +16,7 @@ import LanguageDropdown from './LanguageDropdown';
1616import PythonExecutionPanel from './PythonExecutionPanel' ;
1717import ScriptExecutionPanel from './ScriptExecutionPanel' ;
1818import EmojiPickerOverlay from './EmojiPickerOverlay' ;
19+ import Tooltip from './Tooltip' ;
1920
2021interface DocumentEditorProps {
2122 documentNode : DocumentOrFolder ;
@@ -179,6 +180,7 @@ const DocumentEditor: React.FC<DocumentEditorProps> = ({
179180 const [ isGeneratingTitle , setIsGeneratingTitle ] = useState ( false ) ;
180181 const [ isGeneratingEmoji , setIsGeneratingEmoji ] = useState ( false ) ;
181182 const [ isTitleEmojiPickerOpen , setIsTitleEmojiPickerOpen ] = useState ( false ) ;
183+ const [ showUnsavedTooltip , setShowUnsavedTooltip ] = useState ( false ) ;
182184 const [ titleEmojiAnchor , setTitleEmojiAnchor ] = useState < { x : number ; y : number } | null > ( null ) ;
183185 const [ isCopied , setIsCopied ] = useState ( false ) ;
184186 const [ viewMode , setViewMode ] = useState < ViewMode > ( resolveDefaultViewMode ( documentNode . default_view_mode , documentNode . language_hint ) ) ;
@@ -236,6 +238,7 @@ const DocumentEditor: React.FC<DocumentEditorProps> = ({
236238 const acceptButtonRef = useRef < HTMLButtonElement > ( null ) ;
237239 const titleInputRef = useRef < HTMLInputElement > ( null ) ;
238240 const languageButtonRef = useRef < HTMLButtonElement | null > ( null ) ;
241+ const unsavedIndicatorRef = useRef < HTMLDivElement > ( null ) ;
239242 const isContentInitialized = useRef ( false ) ;
240243 const editorRef = useRef < CodeEditorHandle > ( null ) ;
241244 const richTextEditorRef = useRef < RichTextEditorHandle > ( null ) ;
@@ -1035,7 +1038,25 @@ const DocumentEditor: React.FC<DocumentEditorProps> = ({
10351038 { isGeneratingTitle ? < Spinner /> : < RefreshIcon className = "w-4 h-4 text-primary" /> }
10361039 </ IconButton >
10371040 ) }
1038- { isDirty && < div className = "relative group flex-shrink-0" > < div className = "w-2 h-2 bg-primary rounded-full animate-pulse" > </ div > < span className = "absolute bottom-full mb-2 left-1/2 -translate-x-1/2 z-50 w-max px-2 py-1 text-xs font-semibold text-tooltip-text bg-tooltip-bg rounded-md opacity-0 group-hover:opacity-100" > Unsaved changes</ span > </ div > }
1041+ { isDirty && (
1042+ < >
1043+ < div
1044+ ref = { unsavedIndicatorRef }
1045+ className = "relative flex-shrink-0"
1046+ onMouseEnter = { ( ) => setShowUnsavedTooltip ( true ) }
1047+ onMouseLeave = { ( ) => setShowUnsavedTooltip ( false ) }
1048+ onFocus = { ( ) => setShowUnsavedTooltip ( true ) }
1049+ onBlur = { ( ) => setShowUnsavedTooltip ( false ) }
1050+ tabIndex = { 0 }
1051+ aria-label = "Unsaved changes"
1052+ >
1053+ < div className = "w-2 h-2 bg-primary rounded-full animate-pulse" />
1054+ </ div >
1055+ { showUnsavedTooltip && unsavedIndicatorRef . current && (
1056+ < Tooltip targetRef = { unsavedIndicatorRef } content = "Unsaved changes" position = "bottom" />
1057+ ) }
1058+ </ >
1059+ ) }
10391060 { isLocked && (
10401061 < div className = "flex items-center gap-1 text-xs font-semibold text-primary flex-shrink-0" >
10411062 < LockClosedIcon className = "w-3.5 h-3.5" />
0 commit comments