Files
viberoom-web/src/widgets/space-focus-hud/ui/FloatingGoalWidget.tsx
corpi 88bb4f40b8 feat(space/hud): Exit 버튼 좌측 하단 Invisible Door UI로 재배치
맥락:
- 기존의 우측 상단 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) 세부 기능 구현.
세션-리스크: 없음.
2026-03-13 15:26:53 +09:00

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>
);
};