Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
10b825d
feat: 조합 생성 1,2,3단계 섹션 컴포넌트 구현
waldls Jan 15, 2026
f54b570
Merge branch 'develop' of https://github.com/DeviceLife-Official/Fron…
waldls Jan 15, 2026
b45014c
test: 디자인 수정본에 맞게 조합 생성 화면 ui 테스트
waldls Jan 15, 2026
c5c5a8c
refactor: 조합 생성하기 페이지 간격 수정
waldls Jan 15, 2026
b8f1419
Merge branch 'develop' of https://github.com/DeviceLife-Official/Fron…
waldls Jan 15, 2026
21b4a8f
feat: 조합 생성 인터랙션 구현
waldls Jan 15, 2026
a519031
del: 조합 생성 완료 페이지 제거
waldls Jan 15, 2026
6bdb78d
refactor: 조합 생성 인터랙션 훅, 유틸로 분리
waldls Jan 15, 2026
e8bea95
del: 불필요한 코드 및 파일 삭제
waldls Jan 15, 2026
f70c9fa
refactor: 조합 생성 페이지 모션 로직 훅으로 분리하고 ResultOverlay 컴포넌트로 분리
waldls Jan 15, 2026
dfcf6d4
feat: 조합명 입력 유효성 검사 훅 구현
waldls Jan 15, 2026
489b074
fix: shrink 애니메이션 시 텍스트 크기 부드럽게 축소
waldls Jan 15, 2026
9aaeaf6
refactor: 완료 버튼 클릭 시 기기검색 페이지로 이동
waldls Jan 15, 2026
01ef35d
Merge branch 'develop' of https://github.com/DeviceLife-Official/Fron…
waldls Jan 15, 2026
88866c2
Merge branch 'develop' of https://github.com/DeviceLife-Official/Fron…
waldls Jan 16, 2026
f3b2963
Merge branch 'develop' of https://github.com/DeviceLife-Official/Fron…
waldls Jan 16, 2026
ee8cfe6
refactor: 인터랙션 수치 조정 및 홈 인디케이터에서 게스트 제거
waldls Jan 16, 2026
4d7e2b2
chore: 4의 배수로 값 보정 및 빈줄 정리
waldls Jan 16, 2026
14c905a
fix: 조합 생성 시 드롭 -> shrink 전 위치 이동 현상 수정
waldls Jan 16, 2026
9dbc321
chore: 중앙으로 드랍되는 속도 늘리기
waldls Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/assets/images/combination/stage1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/images/combination/stage2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/images/combination/stage3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions src/components/Button/.gitkeep

This file was deleted.

116 changes: 116 additions & 0 deletions src/components/Combination/CombinationResultOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import PrimaryButton from '@/components/Button/PrimaryButton';
import { COMBO_MOTION as M } from '@/constants/combination';
import { useNavigate } from 'react-router-dom';

type Props = {
centerText: string;
resultOn: boolean;
phase: 'idle' | 'shrink' | 'stack' | 'done';
showDouble: boolean;
showExtras: boolean;
targetRef: React.RefObject<HTMLDivElement | null>;
};

const CombinationResultOverlay = ({
centerText,
resultOn,
phase,
showDouble,
showExtras,
targetRef,
}: Props) => {
const innerSize =
phase === 'shrink' ? { w: M.SHRINK_W, h: M.SHRINK_H } : { w: M.INNER_W, h: M.INNER_H };

const liftActive = phase === 'done';

const liftStyle: React.CSSProperties = liftActive
? {
transform: `translate3d(0, -${M.LIFT_DISTANCE}px, 0)`,
transitionProperty: 'transform',
transitionDuration: `${M.LIFT_DURATION}ms`,
transitionDelay: `${M.LIFT_DELAY}ms`,
transitionTimingFunction: M.LIFT_EASING,
willChange: 'transform',
}
: {
transform: 'translate3d(0, 0, 0)',
transitionProperty: 'transform',
transitionDuration: `260ms`,
transitionTimingFunction: 'ease-out',
willChange: undefined,
};

const navigate = useNavigate();

return (
<div
className="fixed left-0 right-0 bottom-0 z-900 flex items-center justify-center pointer-events-none"
style={{ top: `${M.HEADER_H}px` }}
>
<div className="relative w-800 h-520 flex items-center justify-center">
<div
className={`
transition-opacity duration-260 ease-out
${resultOn ? 'opacity-100' : 'opacity-0'}
`}
style={liftStyle}
>
<div className="relative" style={{ width: `${M.OUTER_W}px`, height: `${M.OUTER_H}px` }}>
<div
className={`
absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2
rounded-button border-shadow-blue-double
transition-opacity duration-320 ease-out
${showDouble ? 'opacity-100' : 'opacity-0'}
`}
style={{ width: `${M.OUTER_W}px`, height: `${M.OUTER_H}px` }}
/>
<div
ref={targetRef}
className={`
absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2
flex flex-col justify-center items-center gap-8
rounded-button bg-white border-shadow-blue font-heading-2 text-black
whitespace-nowrap overflow-hidden text-ellipsis
transition-all
`}
style={{
width: `${innerSize.w}px`,
height: `${innerSize.h}px`,
padding: '20px',
fontSize: phase === 'shrink' ? '30px' : undefined,
transitionDuration: `${phase === 'shrink' ? M.T_SHRINK : M.T_STACK}ms`,
transitionTimingFunction: 'cubic-bezier(0.22,1,0.36,1)',
}}
>
{centerText}
</div>
</div>
</div>
<div
className={`
absolute left-1/2 -translate-x-1/2
top-[calc(50%+4px)]
flex flex-col items-center
transition-all duration-420 ease-out
${showExtras ? 'opacity-100 translate-y-0 pointer-events-auto' : 'opacity-0 translate-y-6'}
`}
>
<p className="w-600 text-center font-body-2-sm text-blue-600">
이제 기기검색 창에서 원하는 기기들을 골라 내가 만든 조합에 담아보세요!
</p>
<div className="mt-120">
<PrimaryButton
text="완료"
className="w-280 bg-blue-600 hover:bg-blue-500"
onClick={() => navigate('/devices')}
/>
</div>
</div>
</div>
</div>
);
};

export default CombinationResultOverlay;
16 changes: 16 additions & 0 deletions src/components/Combination/CombinationStyleProbe.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { forwardRef } from 'react';

const CombinationStyleProbe = forwardRef<HTMLDivElement>((_, ref) => {
return (
<div
ref={ref}
className="fixed -left-10000 -top-10000 flex flex-col justify-center items-center gap-8 w-600 h-72 p-20 rounded-button bg-white border-shadow-blue font-heading-2 text-black"
>
probe
</div>
);
});

CombinationStyleProbe.displayName = 'CombinationStyleProbe';

export default CombinationStyleProbe;
21 changes: 21 additions & 0 deletions src/components/Combination/Stage1Section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Stage1 from '@/assets/images/combination/stage1.svg?react';

const Stage1Section = () => {
return (
<div className="w-360 h-264">
<div className="flex flex-col gap-60 justify-center items-center">
<div className="flex flex-col gap-20">
<p className="font-body-2-sm text-blue-600 text-center">1단계</p>
<p className="font-body-4-sm text-black text-center">
나만의 기기 조합을 만들어보세요! <br /> 조합명을 입력하고, 조합 생성하기 버튼을 누르면
<br />
나만의 조합이 생성돼요!
</p>
</div>
<Stage1 className="w-236 h-60" />
</div>
</div>
);
};

export default Stage1Section;
22 changes: 22 additions & 0 deletions src/components/Combination/Stage2Section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Stage2 from '@/assets/images/combination/stage2.svg?react';

const Stage2Section = () => {
return (
<div className="w-360 h-264">
<div className="flex flex-col gap-24 justify-center items-center">
<div className="flex flex-col gap-20">
<p className="font-body-2-sm text-blue-600 text-center">2단계</p>
<p className="font-body-4-sm text-black text-center">
기기검색 창에서 원하는 기기들을 골라
<br /> 내가 만든 조합에 담아보세요!
<br /> 이때 한 조합에는 동일한 카테고리의 기기를 <br />
하나씩만 담는 것을 추천해요!
</p>
</div>
<Stage2 className="w-176 h-128" />
</div>
</div>
);
};

export default Stage2Section;
19 changes: 19 additions & 0 deletions src/components/Combination/Stage3Section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Stage3 from '@/assets/images/combination/stage3.svg?react';

const Stage3Section = () => {
return (
<div className="w-360 h-264">
<div className="flex flex-col gap-72 justify-center items-center">
<div className="flex flex-col gap-20">
<p className="font-body-2-sm text-blue-600 text-center">3단계</p>
<p className="font-body-4-sm text-black text-center">
우측 상단 MY에 들어가서 <br /> 내가 만든 조합의 조합도를 확인해 보세요!
</p>
</div>
<Stage3 className="w-227 h-72" />
</div>
</div>
);
};

export default Stage3Section;
42 changes: 12 additions & 30 deletions src/components/Home/HomeIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { useState } from 'react';

const brandLinkClass = 'font-service-name-sm text-black hover:text-blue-500 active:text-blue-600';
const navTextClass = 'font-body-1-sm text-black hover:text-blue-500 active:text-blue-600';

type AuthStatus = 'logout' | 'login' | 'guest';
type AuthStatus = 'logout' | 'login';

interface HomeIndicatorProps {
paddingRight?: number;
Expand All @@ -25,16 +24,13 @@ const HomeIndicator = ({ paddingRight = 0 }: HomeIndicatorProps) => {
`${brandLinkClass} ${isActive ? 'text-blue-600 hover:text-blue-500' : ''}`;

return (
<header
className="fixed top-0 left-0 w-full z-50 bg-white h-80"
style={{ paddingRight: `${paddingRight}px` }}
>
<header className="fixed top-0 left-0 w-full z-50 bg-white h-80">
<div className="mx-auto w-full max-w-1920 h-full">
<div className="min-w-1440 h-full">
<div className="flex items-center justify-between h-full pl-44 pr-[clamp(60px,calc(60px+(100vw-1440px)*0.208333),160px)]">
<div className="flex justify-end items-center gap-108 whitespace-nowrap shrink-0">
<div className="flex items-center gap-108 shrink-0">
<div className="flex items-center gap-20">
<Logo className="w-48 h-48" aria-label="Logo" />
<Logo className="w-48 h-48" />
<NavLink to="/" end className={brandClass}>
Device Life
</NavLink>
Expand All @@ -49,8 +45,8 @@ const HomeIndicator = ({ paddingRight = 0 }: HomeIndicatorProps) => {
조합 생성하기
</NavLink>
</div>
<div className="flex items-center gap-56 whitespace-nowrap shrink-0">
{authStatus === 'logout' && (
<div className="flex items-center gap-56 shrink-0">
{authStatus === 'logout' ? (
<>
<NavLink to="/auth/login" className={navClass}>
로그인
Expand All @@ -59,8 +55,7 @@ const HomeIndicator = ({ paddingRight = 0 }: HomeIndicatorProps) => {
회원가입
</NavLink>
</>
)}
{authStatus !== 'logout' && (
) : (
<>
<NavLink
to="/my"
Expand All @@ -74,35 +69,22 @@ const HomeIndicator = ({ paddingRight = 0 }: HomeIndicatorProps) => {
<>
<span className="relative w-32 h-32">
<UserBlack
className={`absolute inset-0 w-32 h-32 transition-opacity duration-200 ease-out
${isActive ? 'opacity-0' : 'opacity-100 group-hover:opacity-0'}`}
/>
<UserBlue500
className={`absolute inset-0 w-32 h-32 transition-opacity duration-200 ease-out
${
isActive
? 'opacity-0 group-hover:opacity-100'
: 'opacity-0 group-hover:opacity-100'
}`}
className={`absolute inset-0 ${isActive ? 'opacity-0' : 'opacity-100 group-hover:opacity-0'}`}
/>
<UserBlue500 className="absolute inset-0 opacity-0 group-hover:opacity-100" />
<UserBlue600
className={`absolute inset-0 w-32 h-32 transition-opacity duration-200 ease-out
${
isActive
? 'opacity-100 group-hover:opacity-0'
: 'opacity-0 group-active:opacity-100'
}`}
className={`absolute inset-0 ${isActive ? 'opacity-100' : 'opacity-0 group-active:opacity-100'}`}
/>
</span>
{authStatus === 'guest' ? 'MY(guest)' : 'MY'}
MY
</>
)}
</NavLink>
<NavLink
to="/"
className={navTextClass}
onClick={() => {
// TODO: 로그아웃 로직 작성
// TODO: 로그아웃 로직
}}
>
로그아웃
Expand Down
27 changes: 27 additions & 0 deletions src/constants/combination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,30 @@ export const COMBINATION_STATUSES = ['최적', '보통', '미흡', '-'] as const

export type CombinationName = (typeof COMBINATION_NAMES)[number];
export type CombinationStatus = (typeof COMBINATION_STATUSES)[number];

export const COMBO_MOTION = {
HEADER_H: 80,

INNER_W: 600,
INNER_H: 72,

SHRINK_W: 558,
SHRINK_H: 66,

OUTER_W: 638,
OUTER_H: 111,

T_SHRINK: 420,
T_STACK: 520,

DROP_DURATION: 2000,
DROP_EASING: 'cubic-bezier(0.12, 0.95, 0.18, 1)',

LIFT_DISTANCE: 90,
LIFT_DURATION: 1500,
LIFT_DELAY: 180,
LIFT_EASING: 'cubic-bezier(0.12, 0.9, 0.18, 1)',

DOUBLE_DELAY: 160,
EXTRAS_AT_LIFT_PROGRESS: 0.01,
} as const;
1 change: 0 additions & 1 deletion src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export const ROUTES = {
// combination
combination: {
create: '/combination/create',
complete: '/combination/complete',
},

// my
Expand Down
2 changes: 0 additions & 2 deletions src/hooks/.gitkeep

This file was deleted.

Loading