diff --git a/src/widgets/space-focus-hud/ui/ThoughtOrb.tsx b/src/widgets/space-focus-hud/ui/ThoughtOrb.tsx new file mode 100644 index 0000000..f8e3020 --- /dev/null +++ b/src/widgets/space-focus-hud/ui/ThoughtOrb.tsx @@ -0,0 +1,115 @@ +'use client'; + +import { useState, useRef, useEffect } from 'react'; +import { cn } from '@/shared/lib/cn'; + +interface ThoughtOrbProps { + isFocusMode: boolean; + onCaptureThought: (note: string) => void; +} + +export const ThoughtOrb = ({ isFocusMode, onCaptureThought }: ThoughtOrbProps) => { + const [isOpen, setIsOpen] = useState(false); + const [draft, setDraft] = useState(''); + const [isAbsorbing, setIsAbsorbing] = useState(false); + const inputRef = useRef(null); + + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus(); + } + }, [isOpen]); + + useEffect(() => { + if (!isOpen) return; + + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + setIsOpen(false); + setDraft(''); + } + }; + + window.addEventListener('keydown', handleEscape); + return () => window.removeEventListener('keydown', handleEscape); + }, [isOpen]); + + if (!isFocusMode) return null; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const trimmed = draft.trim(); + if (!trimmed) { + setIsOpen(false); + return; + } + + setIsAbsorbing(true); + onCaptureThought(trimmed); + + // Provide a satisfying "sucking" animation delay before closing + setTimeout(() => { + setIsOpen(false); + setDraft(''); + setIsAbsorbing(false); + }, 600); + }; + + return ( +
+
+ {/* The Orb */} + + + {/* Tooltip */} +
+ + Brain Dump + +
+
+ + {/* Input Field */} +
+
+
+ setDraft(e.target.value)} + disabled={isAbsorbing} + placeholder="Dump a distracting thought..." + className="relative z-10 w-full bg-transparent px-6 py-5 text-[15px] font-medium text-white outline-none placeholder:text-white/30 disabled:opacity-50" + /> + + +
+
+ ); +};