맥락: - 기존의 우측 상단 Exit(나가기) 버튼이 너무 동떨어져 있었음. - 목표(Goal) 패널 하단에 Exit 버튼을 두려는 시도가 있었으나, '목표 유지'와 '목표 포기(Exit)'라는 상반된 의미가 한 공간에 묶여 인지적 충돌을 발생시킴. - 몰입을 방해하지 않는 투명함(Invisible UI)과 본능적인 이탈 경로가 필요함. 변경사항: - SpaceToolsDockWidget에 새로운 좌측 하단(Bottom-Left) 모서리 Exit 버튼 렌더링 영역 추가. - 평소에는 투명한 Escape(⎋) 아이콘만 노출하여 배경 공간의 방해 최소화. - 사용자가 마우스를 Hover할 때만 알약(Pill) 형태로 부드럽게 확장(Expansion)되며 ExitHoldButton(Bar)이 나타나는 고급 인터랙션 구현. - FloatingGoalWidget에 테스트로 추가했던 Exit 버튼 코드 원복(제거) 및 SpaceFocusHudWidget, SpaceWorkspaceWidget의 불필요한 prop 전달 정리. 검증: - npm run build 정상 통과. 세션-상태: 몰입 공간(/space)의 하이엔드 UI 레이아웃 재배치 및 디자인 고도화 완료. 세션-다음: 향후 필요 시 통계(Analytics) 또는 결제(Paywall) 세부 기능 구현. 세션-리스크: 없음.
64 lines
2.7 KiB
TypeScript
64 lines
2.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { cn } from '@/shared/lib/cn';
|
|
import { copy } from '@/shared/i18n';
|
|
|
|
interface FloatingGoalWidgetProps {
|
|
goal: string;
|
|
microStep?: string | null;
|
|
onGoalCompleteRequest?: () => void;
|
|
hasActiveSession?: boolean;
|
|
sessionPhase?: 'focus' | 'break' | null;
|
|
}
|
|
|
|
export const FloatingGoalWidget = ({
|
|
goal,
|
|
microStep,
|
|
onGoalCompleteRequest,
|
|
hasActiveSession,
|
|
sessionPhase,
|
|
}: FloatingGoalWidgetProps) => {
|
|
const [isMicroStepCompleted, setIsMicroStepCompleted] = useState(false);
|
|
const normalizedGoal = goal.trim().length > 0 ? goal.trim() : copy.space.timerHud.goalFallback;
|
|
|
|
return (
|
|
<div className="pointer-events-none fixed left-0 top-0 z-20 w-full max-w-[800px] h-48 bg-[radial-gradient(ellipse_at_top_left,rgba(0,0,0,0.6)_0%,rgba(0,0,0,0)_60%)] group">
|
|
<div className="flex flex-col items-start gap-4 p-8 md:p-12">
|
|
{/* Main Goal */}
|
|
<div className="pointer-events-auto flex items-center gap-4">
|
|
<h2 className="text-2xl md:text-[1.75rem] font-medium tracking-tight text-white drop-shadow-[0_2px_4px_rgba(0,0,0,0.8)] [text-shadow:0_4px_24px_rgba(0,0,0,0.6)]">
|
|
{normalizedGoal}
|
|
</h2>
|
|
{hasActiveSession && sessionPhase === 'focus' ? (
|
|
<button
|
|
type="button"
|
|
onClick={onGoalCompleteRequest}
|
|
className="opacity-0 group-hover:opacity-100 shrink-0 rounded-full border border-white/20 bg-black/40 backdrop-blur-md px-3.5 py-1.5 text-[11px] font-medium text-white/90 shadow-lg transition-all hover:bg-black/60 hover:text-white focus-visible:opacity-100"
|
|
>
|
|
목표 달성
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
|
|
{/* Micro Step */}
|
|
{microStep && !isMicroStepCompleted && (
|
|
<div className="pointer-events-auto flex items-center gap-3.5 animate-in fade-in slide-in-from-top-2 duration-500 bg-black/10 backdrop-blur-[2px] rounded-full pr-4 py-1 -ml-1 border border-white/5">
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsMicroStepCompleted(true)}
|
|
className="flex h-6 w-6 ml-1 items-center justify-center rounded-full border border-white/40 bg-black/20 shadow-inner transition-all hover:bg-white/20 hover:scale-110 active:scale-95"
|
|
aria-label="첫 단계 완료"
|
|
>
|
|
<span className="sr-only">첫 단계 완료</span>
|
|
</button>
|
|
<span className="text-[15px] font-medium text-white/95 drop-shadow-[0_2px_4px_rgba(0,0,0,0.6)] [text-shadow:0_2px_12px_rgba(0,0,0,0.5)]">
|
|
{microStep}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|