From 13a94ef42d8e4fdbe7591b6f02b8c465b97a99c1 Mon Sep 17 00:00:00 2001 From: corpi Date: Tue, 17 Mar 2026 14:11:11 +0900 Subject: [PATCH] =?UTF-8?q?feat(space):=20thought=20orb=20capture=20ui=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/space-focus-hud/ui/ThoughtOrb.tsx | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/widgets/space-focus-hud/ui/ThoughtOrb.tsx 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" + /> + + +
+
+ ); +};