-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feat] UX 개선 #161
[Feat] UX 개선 #161
Changes from 14 commits
0bf706a
2867700
6c346e1
0010c36
15d8c6c
964223e
75507cf
bdd05e4
349ab4c
c88c631
9fe3bf9
17d0435
2f718ba
77110d8
7b3cc4b
d779294
64cf244
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -165,7 +165,12 @@ export function CasperCardBackUI({ | |
style={{ width: CARD_WIDTH - 100 }} | ||
> | ||
<p className="text-n-neutral-500">작성한 기대평</p> | ||
<p className="text-n-black">{expectations}</p> | ||
<p | ||
className="text-n-black max-w-full text-center" | ||
style={{ overflowWrap: "break-word" }} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 이런 속성 있는줄 몰랐네용 적용했어요! 7b3cc4b |
||
> | ||
{expectations} | ||
</p> | ||
</div> | ||
)} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { useState } from "react"; | ||
import { CASPER_CARD_SIZE, CASPER_SIZE_OPTION } from "@/constants/CasperCustom/casper"; | ||
import { CasperFlipCard } from "@/features/CasperCustom/CasperCard/CasperFlipCard"; | ||
import type { CasperCardType } from "@/types/casper"; | ||
|
||
interface TransitionCasperCardItemProps { | ||
cardItem: CasperCardType; | ||
id: string; | ||
stopAnimation?: () => void; | ||
startAnimation?: () => void; | ||
} | ||
|
||
export function TransitionCasperCardItem({ | ||
cardItem, | ||
id, | ||
stopAnimation, | ||
startAnimation, | ||
}: TransitionCasperCardItemProps) { | ||
const [isFlipped, setIsFlipped] = useState<boolean>(false); | ||
|
||
const handleMouseEnter = () => { | ||
stopAnimation && stopAnimation(); | ||
setIsFlipped(true); | ||
}; | ||
|
||
const handleMouseLeave = () => { | ||
startAnimation && startAnimation(); | ||
setIsFlipped(false); | ||
}; | ||
|
||
return ( | ||
<li | ||
key={id} | ||
onMouseEnter={handleMouseEnter} | ||
onMouseLeave={handleMouseLeave} | ||
style={{ | ||
width: CASPER_CARD_SIZE[CASPER_SIZE_OPTION.SM].CARD_WIDTH, | ||
height: CASPER_CARD_SIZE[CASPER_SIZE_OPTION.SM].CARD_HEIGHT, | ||
}} | ||
> | ||
<CasperFlipCard card={cardItem} size={CASPER_SIZE_OPTION.SM} isFlipped={isFlipped} /> | ||
</li> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,41 @@ | ||
import { useEffect, useRef, useState } from "react"; | ||
import { AnimatePresence, motion, useAnimation } from "framer-motion"; | ||
import { CASPER_CARD_SIZE, CASPER_SIZE_OPTION } from "@/constants/CasperCustom/casper"; | ||
import { useEffect, useMemo, useRef, useState } from "react"; | ||
import { AnimatePresence, type ResolvedValues, motion, useAnimation } from "framer-motion"; | ||
import { CARD_TRANSITION } from "@/constants/CasperShowCase/showCase"; | ||
import { CasperFlipCard } from "@/features/CasperCustom/CasperCard/CasperFlipCard"; | ||
import useLazyLoading from "@/hooks/useLazyLoading"; | ||
import { SelectedCasperIdxType } from "@/types/casperCustom"; | ||
import type { CasperCardType } from "@/types/casper"; | ||
import { TransitionCasperCardItem } from "./TransitionCasperCardItem"; | ||
|
||
export interface CasperCardType { | ||
id: number; | ||
casperName: string; | ||
expectations: string; | ||
selectedCasperIdx: SelectedCasperIdxType; | ||
} | ||
interface TransitionCasperCardsProps { | ||
cardList: CasperCardType[]; | ||
initialX: number; | ||
diffX: number; | ||
totalWidth: number; | ||
visibleCardCount: number; | ||
gap: number; | ||
isEndCard: (latestX: number) => boolean; | ||
isReverseCards?: boolean; | ||
} | ||
|
||
export function TransitionCasperCards({ | ||
cardList, | ||
initialX, | ||
diffX, | ||
totalWidth, | ||
gap, | ||
visibleCardCount, | ||
isEndCard, | ||
isReverseCards = false, | ||
}: TransitionCasperCardsProps) { | ||
const isAnimated = visibleCardCount <= cardList.length; | ||
const expandedCardList = useMemo(() => [...cardList, ...cardList, ...cardList], [cardList]); | ||
|
||
const containerRef = useRef<HTMLUListElement>(null); | ||
const transitionControls = useAnimation(); | ||
|
||
const [x, setX] = useState<number>(initialX); | ||
const [visibleCardListIdx, setVisibleCardListIdx] = useState(0); | ||
|
||
const startAnimation = (x: number) => { | ||
transitionControls.start({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. startAnimation, stopAnimation 함수는 useCallback으로 감싸줘도 될 것 같은데 어떻게 생각하시나용..? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 64cf244 좋아용 수정햇습니다! |
||
x: [x, x + diffX], | ||
transition: CARD_TRANSITION(cardList.length), | ||
x: [x, x + diffX * 2], | ||
transition: CARD_TRANSITION(visibleCardCount * 2), | ||
}); | ||
}; | ||
|
||
|
@@ -50,62 +48,75 @@ export function TransitionCasperCards({ | |
} | ||
}; | ||
|
||
const visibleCardList = useMemo(() => { | ||
const list = expandedCardList.slice( | ||
visibleCardListIdx, | ||
visibleCardListIdx + visibleCardCount * 2 | ||
); | ||
|
||
if (isAnimated && isReverseCards) { | ||
return list.reverse(); | ||
} | ||
|
||
return isAnimated ? list : cardList; | ||
}, [ | ||
isReverseCards, | ||
expandedCardList, | ||
cardList, | ||
isAnimated, | ||
visibleCardCount, | ||
visibleCardListIdx, | ||
]); | ||
|
||
useEffect(() => { | ||
startAnimation(x); | ||
}, [transitionControls, totalWidth]); | ||
|
||
const renderCardItem = (cardItem: CasperCardType, id: string) => { | ||
const [isFlipped, setIsFlipped] = useState<boolean>(false); | ||
const { isInView, cardRef } = useLazyLoading<HTMLLIElement>(); | ||
}, []); | ||
|
||
const handleMouseEnter = () => { | ||
stopAnimation(); | ||
setIsFlipped(true); | ||
}; | ||
const handleUpdateAnimation = (latest: ResolvedValues) => { | ||
if (isEndCard(parseInt(String(latest.x)))) { | ||
let nextIdx = visibleCardListIdx + visibleCardCount; | ||
|
||
const handleMouseLeave = () => { | ||
startAnimation(x); | ||
setIsFlipped(false); | ||
}; | ||
// 만약 nextIdx가 cardList의 길이를 초과하면 배열의 처음부터 다시 index를 카운트하도록 함 | ||
if (nextIdx >= cardList.length) { | ||
nextIdx = nextIdx % cardList.length; | ||
} | ||
|
||
return ( | ||
<li | ||
ref={cardRef} | ||
key={id} | ||
onMouseEnter={handleMouseEnter} | ||
onMouseLeave={handleMouseLeave} | ||
style={{ | ||
width: CASPER_CARD_SIZE[CASPER_SIZE_OPTION.SM].CARD_WIDTH, | ||
height: CASPER_CARD_SIZE[CASPER_SIZE_OPTION.SM].CARD_HEIGHT, | ||
}} | ||
> | ||
{isInView && ( | ||
<CasperFlipCard | ||
card={cardItem} | ||
size={CASPER_SIZE_OPTION.SM} | ||
isFlipped={isFlipped} | ||
/> | ||
)} | ||
</li> | ||
); | ||
setVisibleCardListIdx(nextIdx); | ||
startAnimation(initialX); | ||
} | ||
}; | ||
|
||
return ( | ||
<AnimatePresence> | ||
<motion.ul | ||
ref={containerRef} | ||
className="flex" | ||
animate={transitionControls} | ||
style={{ gap: `${gap}px` }} | ||
onUpdate={(latest) => { | ||
if (isEndCard(parseInt(String(latest.x)))) { | ||
startAnimation(initialX); | ||
} | ||
}} | ||
> | ||
{cardList.map((card) => renderCardItem(card, `${card.id}`))} | ||
{cardList.map((card) => renderCardItem(card, `${card.id}-clone`))} | ||
</motion.ul> | ||
{isAnimated ? ( | ||
<motion.ul | ||
ref={containerRef} | ||
className="flex" | ||
animate={transitionControls} | ||
style={{ gap: `${gap}px` }} | ||
onUpdate={handleUpdateAnimation} | ||
> | ||
{visibleCardList.map((card, idx) => ( | ||
<TransitionCasperCardItem | ||
key={`${card.id}-${idx}`} | ||
cardItem={card} | ||
id={`${card.id}-${idx}`} | ||
stopAnimation={stopAnimation} | ||
startAnimation={() => startAnimation(x)} | ||
/> | ||
))} | ||
</motion.ul> | ||
) : ( | ||
<ul className="flex w-screen justify-center" style={{ gap: `${gap}px` }}> | ||
{visibleCardList.map((card, idx) => ( | ||
<TransitionCasperCardItem | ||
key={`${card.id}-${idx}`} | ||
cardItem={card} | ||
id={`${card.id}-${idx}`} | ||
/> | ||
))} | ||
</ul> | ||
)} | ||
</AnimatePresence> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스프레드 연산자를 사용하는 이유가 따로 있을까용?
제 생각에는 스프레드 연산자 말고, 그냥 조건부로 넣어줘도 클래스 속성 자체 텍스트가 분리 되지는 않아서 동적 스타일 적용 오류는 안날 것 같은데 아래처럼 하는건 어떨까여?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아하 tailwind 안 쓰던 시절에 사용하던 방식이 익숙해서 저렇게 했었던 것 같네용,, 수정했습니다!
d779294