Skip to content

Commit 99c2d73

Browse files
Merge pull request #302 from dreamyPatisiel/DP-562
[DP-562] 픽픽픽 메인 / 홈의 픽픽픽 컴포넌트 교체
2 parents 414d11e + 2ce5b52 commit 99c2d73

26 files changed

Lines changed: 1056 additions & 149 deletions

components/common/skeleton/pickSkeleton.tsx

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import { MobilePickInfo, PickInfo } from '@pages/pickpickpick/components/PickInfo';
1+
import { MobilePickInfoV1, PickInfoV1 } from '@pages/pickpickpick/components/PickInfo';
22

3-
export const PickSkeleton = () => {
3+
import AngleRightIcon from '@components/svgs/AngleRightIcon';
4+
5+
// ------------------------------픽픽픽 메인 스켈레톤 v1------------------------------
6+
export const PickSkeletonV1 = () => {
47
return (
58
<div className='px-[2.4rem] py-[3.2rem] flex flex-col gap-[3.2rem] rounded-[1.6rem] border-gray600 border-solid border'>
69
<div className='h-[3.7rem] w-[100%] rounded-[1.6rem] bg-gray600 relative overflow-hidden skeleton-item' />
@@ -13,56 +16,130 @@ export const PickSkeleton = () => {
1316
);
1417
};
1518

16-
interface PickSkeletonListProps {
19+
interface PickSkeletonListV1Props {
1720
rows: number;
1821
itemsInRows: number;
1922
hasInfo?: boolean;
2023
}
2124

22-
export const PickSkeletonList = ({ rows, itemsInRows, hasInfo }: PickSkeletonListProps) => {
25+
export const PickSkeletonListV1 = ({ rows, itemsInRows, hasInfo }: PickSkeletonListV1Props) => {
2326
return (
2427
<div className='grid grid-cols-3 gap-8'>
2528
{hasInfo ? (
2629
<>
27-
<PickInfo />
30+
<PickInfoV1 />
2831
{Array.from({ length: rows * itemsInRows - 1 }, (_, index) => (
29-
<PickSkeleton key={index} />
32+
<PickSkeletonV1 key={index} />
3033
))}
3134
</>
3235
) : (
3336
<>
3437
{Array.from({ length: rows * itemsInRows }, (_, index) => (
35-
<PickSkeleton key={index} />
38+
<PickSkeletonV1 key={index} />
3639
))}
3740
</>
3841
)}
3942
</div>
4043
);
4144
};
4245

43-
export const MobilePickSkeletonList = ({ rows, hasInfo }: { rows: number; hasInfo?: boolean }) => {
46+
export const MobilePickSkeletonListV1 = ({
47+
rows,
48+
hasInfo,
49+
}: {
50+
rows: number;
51+
hasInfo?: boolean;
52+
}) => {
4453
const arr = Array.from({ length: rows });
4554

4655
return (
4756
<div className='grid grid-cols-1 gap-8'>
48-
{hasInfo && <MobilePickInfo />}
57+
{hasInfo && <MobilePickInfoV1 />}
58+
59+
{arr.map((_, index) => (
60+
<PickSkeletonV1 key={index} />
61+
))}
62+
</div>
63+
);
64+
};
65+
66+
export const MyPickSkeletonListV1 = ({ rows, itemsInRows }: PickSkeletonListV1Props) => {
67+
return (
68+
<div className='grid grid-cols-2 gap-8'>
69+
{Array.from({ length: rows * itemsInRows }, (_, index) => (
70+
<PickSkeletonV1 key={index} />
71+
))}
72+
</div>
73+
);
74+
};
75+
// -------------------------------------------------------------------------------
76+
77+
// ------------------------------픽픽픽 메인 스켈레톤 v2------------------------------
78+
79+
export const PickSkeletonV2 = () => {
80+
return (
81+
<div className='pt-[3.2rem] pb-[2.4rem] px-[3.2rem] flex flex-col gap-[3.2rem] rounded-[1.6rem] bg-gray600'>
82+
<div className='flex flex-col gap-[2.4rem]'>
83+
<div className='flex flex-row items-start justify-between gap-[1.6rem]'>
84+
<div className='bg-black h-[4.3rem] w-[100%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
85+
<AngleRightIcon color='var(--gray300)' />
86+
</div>
87+
<div className='bg-black h-[1rem] w-[40%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
88+
</div>
4989

90+
<div className='flex flex-row gap-[1.6rem]'>
91+
<div className='bg-black flex flex-col gap-[1rem] p-[2.4rem] w-[50%] rounded-[1.6rem] relative overflow-hidden border border-gray500'>
92+
<div className='bg-gray500 h-[1rem] w-[50%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
93+
<div className='bg-gray500 h-[5rem] w-[100%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
94+
<div className='bg-gray500 h-[12rem] w-[100%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
95+
</div>
96+
<div className='bg-black flex flex-col gap-[1rem] p-[2.4rem] w-[50%] rounded-[1.6rem] relative overflow-hidden border border-gray500'>
97+
<div className='bg-gray500 h-[1rem] w-[50%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
98+
<div className='bg-gray500 h-[5rem] w-[100%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
99+
<div className='bg-gray500 h-[12rem] w-[100%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
100+
</div>
101+
</div>
102+
</div>
103+
);
104+
};
105+
106+
interface PickSkeletonListV2Props {
107+
rows: number;
108+
itemsInRows: number;
109+
}
110+
111+
export const PickSkeletonListV2 = ({ rows, itemsInRows }: PickSkeletonListV2Props) => {
112+
return (
113+
<div className='grid grid-cols-2 gap-8'>
114+
{Array.from({ length: rows * itemsInRows }, (_, index) => (
115+
<PickSkeletonV2 key={index} />
116+
))}
117+
</div>
118+
);
119+
};
120+
121+
export const MobilePickSkeletonListV2 = ({ rows }: { rows: number }) => {
122+
const arr = Array.from({ length: rows });
123+
124+
return (
125+
<div className='grid grid-cols-1 gap-8'>
50126
{arr.map((_, index) => (
51-
<PickSkeleton key={index} />
127+
<PickSkeletonV2 key={index} />
52128
))}
53129
</div>
54130
);
55131
};
56132

57-
export const MyPickSkeletonList = ({ rows, itemsInRows }: PickSkeletonListProps) => {
133+
export const MyPickSkeletonListV2 = ({ rows, itemsInRows }: PickSkeletonListV2Props) => {
58134
return (
59135
<div className='grid grid-cols-2 gap-8'>
60136
{Array.from({ length: rows * itemsInRows }, (_, index) => (
61-
<PickSkeleton key={index} />
137+
<PickSkeletonV2 key={index} />
62138
))}
63139
</div>
64140
);
65141
};
142+
// -------------------------------------------------------------------------------
66143

67144
/** 메인페이지 픽픽픽 스켈레톤 */
68145
export const MainPickSkeleton = () => {
@@ -86,7 +163,7 @@ export const MainPickSkeletonList = ({ itemsInRows }: MainPickSkeletonListProps)
86163
return (
87164
<>
88165
{Array.from({ length: itemsInRows }, (_, index) => (
89-
<MainPickSkeleton key={index} />
166+
<PickSkeletonV2 key={index} />
90167
))}
91168
</>
92169
);

components/common/title/ArrowWithTitle.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const ArrowWithTitleVariants = cva(ARROW_TITLE_CLASSES, {
1717
mainTitle: ['st2', 'text-gray200'],
1818
similarPick: ['st2', 'text-white'],
1919
defaultPick: ['p1', 'text-gray100'],
20+
defaultPickV2: ['st2', 'text-white'],
2021
},
2122
},
2223
});
@@ -26,8 +27,8 @@ interface ArrowWithTitleProps extends VariantProps<typeof ArrowWithTitleVariants
2627
iconText?: string;
2728
routeURL?: string;
2829
className?: string;
29-
iconSize?: string;
3030
ArrowClassName?: string;
31+
iconSize?: { width: number; height: number };
3132
}
3233

3334
const ArrowWithTitle: FC<ArrowWithTitleProps> = ({
@@ -37,6 +38,7 @@ const ArrowWithTitle: FC<ArrowWithTitleProps> = ({
3738
routeURL,
3839
className,
3940
ArrowClassName,
41+
iconSize = { width: 7, height: 14 },
4042
}) => {
4143
return (
4244
<div className='grid grid-flow-col items-baseline gap-6 justify-between'>
@@ -51,8 +53,8 @@ const ArrowWithTitle: FC<ArrowWithTitleProps> = ({
5153
<Image
5254
src={AngleRight}
5355
alt={'오른쪽 화살표'}
54-
width={7}
55-
height={14}
56+
width={iconSize.width}
57+
height={iconSize.height}
5658
className={ArrowClassName}
5759
/>
5860
</div>

components/features/main/dynamicPickComponent.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33
import Link from 'next/link';
44

55
import { useInfinitePickData } from '@pages/pickpickpick/api/useInfinitePickData';
6-
import PickContainer from '@pages/pickpickpick/components/PickContainer';
6+
import PickContainerV2 from '@pages/pickpickpick/components/PickContainerV2';
77
import {
88
PICK_VIEW_SIZE,
99
MOBILE_MAIN_PICK_VIEW_SIZE,
@@ -26,7 +26,7 @@ export default function DynamicPickComponent() {
2626
const getStatusComponent = () => {
2727
switch (status) {
2828
case 'pending':
29-
return <MainPickSkeletonList itemsInRows={2} />;
29+
return <MainPickSkeletonList itemsInRows={1} />;
3030

3131
default:
3232
return (
@@ -38,7 +38,7 @@ export default function DynamicPickComponent() {
3838
<div key={index}>
3939
{group?.data.content.map((data: PickDataProps) => (
4040
<Link href={`${ROUTES.PICKPICKPICK.MAIN}/${data.id}`} key={data.id}>
41-
<PickContainer pickData={data} type='main' />
41+
<PickContainerV2 pickData={data} />
4242
</Link>
4343
))}
4444
</div>

components/features/techblog/BookmarkComponent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export default function BookmarkComponent() {
2828
<MobileDropdown type='bookmark' />
2929
</div>
3030
) : (
31-
<Dropdown type='bookmark' disable={!hasData} />
31+
<Dropdown type='bookmark' disable={!hasData} line />
3232
)}
3333
</div>
3434
);

hooks/useVerticalStepLoop.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { useAnimationControls } from 'framer-motion';
2+
3+
import { useEffect, useRef, useState } from 'react';
4+
5+
type Options = {
6+
itemCount: number;
7+
dwellMs: number;
8+
slideMs: number;
9+
reduceMotion: boolean | null;
10+
};
11+
12+
export function useVerticalStepLoop({ itemCount, dwellMs, slideMs, reduceMotion }: Options) {
13+
const controls = useAnimationControls();
14+
const firstItemRef = useRef<HTMLParagraphElement | null>(null);
15+
const [rowHeight, setRowHeight] = useState(0);
16+
17+
useEffect(() => {
18+
if (firstItemRef.current) setRowHeight(firstItemRef.current.offsetHeight);
19+
}, []);
20+
21+
useEffect(() => {
22+
if (reduceMotion === true || rowHeight === 0) return;
23+
24+
let i = 0;
25+
let mounted = true;
26+
// 한 칸씩 위로 이동 후, 마지막(복제된 첫 항목)까지 도달하면 즉시 y=0으로 스냅하여
27+
// 시각적 깜빡임 없이 처음 상태로 되돌립니다. (복제된 첫 항목과 실제 첫 항목은 동일 내용)
28+
const tick = async () => {
29+
try {
30+
i += 1;
31+
await controls.start({
32+
y: -(i * rowHeight),
33+
transition: { duration: slideMs / 1000, ease: 'easeInOut' },
34+
});
35+
if (!mounted) return;
36+
if (i === itemCount) {
37+
// set()은 마운트 이후에만 안전합니다. 언마운트/미바인딩 크래시 방지를 위해
38+
// start() + duration: 0으로 즉시 스냅 처리합니다.
39+
await controls.start({ y: 0, transition: { duration: 0 } });
40+
i = 0;
41+
}
42+
} catch (e) {
43+
// 언마운트 타이밍 등으로 발생 가능한 경합 에러 무시
44+
}
45+
};
46+
47+
const id = setInterval(tick, dwellMs + slideMs);
48+
return () => {
49+
mounted = false;
50+
clearInterval(id);
51+
};
52+
}, [controls, reduceMotion, rowHeight, itemCount, dwellMs, slideMs]);
53+
54+
return { controls, firstItemRef } as const;
55+
}

pages/myinfo/mywriting/mypick/components/MyPickStatusComponent.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { PickDataProps } from '@pages/pickpickpick/types/pick';
99
import { useObserver } from '@hooks/useObserver';
1010

1111
import {
12-
MobilePickSkeletonList,
13-
MyPickSkeletonList,
12+
MobilePickSkeletonListV1,
13+
MyPickSkeletonListV1,
1414
} from '@components/common/skeleton/pickSkeleton';
1515

1616
import { ROUTES } from '@/constants/routes';
@@ -38,9 +38,9 @@ export default function MyPickStatusComponent() {
3838
return (
3939
<>
4040
{isMobile ? (
41-
<MobilePickSkeletonList rows={3} />
41+
<MobilePickSkeletonListV1 rows={3} />
4242
) : (
43-
<MyPickSkeletonList rows={3} itemsInRows={2} />
43+
<MyPickSkeletonListV1 rows={3} itemsInRows={2} />
4444
)}
4545
</>
4646
);
@@ -77,9 +77,9 @@ export default function MyPickStatusComponent() {
7777
{isFetchingNextPage && hasNextPage && (
7878
<div className='mt-[2rem]'>
7979
{isMobile ? (
80-
<MobilePickSkeletonList rows={3} />
80+
<MobilePickSkeletonListV1 rows={3} />
8181
) : (
82-
<MyPickSkeletonList rows={3} itemsInRows={2} />
82+
<MyPickSkeletonListV1 rows={3} itemsInRows={2} />
8383
)}
8484
</div>
8585
)}

0 commit comments

Comments
 (0)