From ef9cc63cc5dce80c8210e3205023a1e187289161 Mon Sep 17 00:00:00 2001 From: corpi Date: Tue, 3 Mar 2026 14:27:14 +0900 Subject: [PATCH] =?UTF-8?q?style(space):=20=EB=A6=AC=EC=B6=94=EC=96=BC=20?= =?UTF-8?q?=EC=A7=84=EC=9E=85=20UX=EC=99=80=20=ED=8F=AC=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=20=EC=A0=84=ED=99=98=20=ED=9D=90=EB=A6=84=EC=9D=84=20=EA=B3=A0?= =?UTF-8?q?=EA=B8=89=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 맥락: - /space 진입 경험이 설정 패널처럼 보여 몰입형 라운지 톤이 약했습니다. - 목표 입력 후 시작 전환 동선을 더 빠르고 일관되게 만들 필요가 있었습니다. 변경사항: - 도크 아이콘을 이모지에서 단일 라인 SVG 세트로 통일해 시각 언어 일관성을 맞췄습니다. - Setup Drawer 밀도를 낮추고(타이포/테두리/칩 크기) 3-step 리추얼 흐름을 더 간결하게 정리했습니다. - 목표 입력 필드 자동 포커스를 추가해 진입 즉시 타이핑이 가능하도록 했습니다. - 시작 버튼을 form submit으로 연결해 Enter 입력과 버튼 클릭이 동일하게 동작하도록 변경했습니다. - SpaceSideSheet에 300ms 닫힘 전환(오버레이/시트 opacity+translate) 애니메이션을 적용했습니다. - Focus 진입 토스트 카피를 목표 중심 문구로 바꾸고 Setup 선택지를 최소 개수로 제한했습니다. - 배경에 미세 stage-pan/light-drift 키프레임을 추가해 정적인 평면감을 줄였습니다. 검증: - npx tsc --noEmit - npm run build 세션-상태: /space에서 목표 입력 후 10초 내 Focus 전환 가능한 리추얼 흐름이 정리되었습니다. 세션-다음: 실제 브라우저에서 애니메이션 강도와 드로어 밀도 체감 QA를 진행합니다. 세션-리스크: 저사양 환경에서 배경 미세 모션이 과하게 느껴질 수 있어 추후 reduce-motion 강화를 검토할 수 있습니다. --- src/app/globals.css | 20 +++++ .../session-goal/ui/SessionGoalField.tsx | 50 ++++++------ .../ui/SpaceSetupDrawerWidget.tsx | 54 +++++++------ .../space-sheet-shell/ui/SpaceSideSheet.tsx | 76 +++++++++++++++--- .../ui/SpaceToolsDockWidget.tsx | 78 +++++++++++++++---- .../ui/SpaceWorkspaceWidget.tsx | 31 +++++--- 6 files changed, 226 insertions(+), 83 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 7e4990c..bcd9eee 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -49,3 +49,23 @@ body { transform: scaleX(1); } } + +@keyframes space-stage-pan { + 0% { + transform: scale(1.02) translate3d(-0.4%, -0.25%, 0); + } + 100% { + transform: scale(1.07) translate3d(0.55%, 0.35%, 0); + } +} + +@keyframes space-light-drift { + 0% { + transform: translate3d(-1.2%, -0.8%, 0); + opacity: 0.86; + } + 100% { + transform: translate3d(1.2%, 0.9%, 0); + opacity: 1; + } +} diff --git a/src/features/session-goal/ui/SessionGoalField.tsx b/src/features/session-goal/ui/SessionGoalField.tsx index 5627dfd..7ce85cb 100644 --- a/src/features/session-goal/ui/SessionGoalField.tsx +++ b/src/features/session-goal/ui/SessionGoalField.tsx @@ -1,10 +1,11 @@ 'use client'; -import { useMemo, useState } from 'react'; +import { useEffect, useRef } from 'react'; import type { GoalChip } from '@/entities/session'; import { cn } from '@/shared/lib/cn'; interface SessionGoalFieldProps { + autoFocus?: boolean; goalInput: string; selectedGoalId: string | null; goalChips: GoalChip[]; @@ -13,40 +14,49 @@ interface SessionGoalFieldProps { } export const SessionGoalField = ({ + autoFocus = false, goalInput, selectedGoalId, goalChips, onGoalChange, onGoalChipSelect, }: SessionGoalFieldProps) => { - const [expanded, setExpanded] = useState(false); + const inputRef = useRef(null); - const visibleChips = useMemo(() => { - if (expanded) { - return goalChips; + useEffect(() => { + if (!autoFocus) { + return; } - return goalChips.slice(0, 4); - }, [expanded, goalChips]); + const raf = window.requestAnimationFrame(() => { + inputRef.current?.focus(); + }); + + return () => { + window.cancelAnimationFrame(raf); + }; + }, [autoFocus]); return ( -
-
-