Skip to content
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: 카드 플레이 화면 #33

Merged
merged 13 commits into from
Jul 16, 2023
9 changes: 2 additions & 7 deletions packages/service-frontend/app/deck/[id]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
'use client'
import { ContentWrapper, Header } from '@/components'
import { ContentWrapper } from '@/components'

interface Props {
children: React.ReactNode
}

export default function Layout({ children }: Props): JSX.Element {
return (
<ContentWrapper>
<Header title="로그인" className="justify-end" />
{children}
</ContentWrapper>
)
return <ContentWrapper>{children}</ContentWrapper>
}
9 changes: 8 additions & 1 deletion packages/service-frontend/app/deck/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import { Header } from '@/components'

export default function DeckDetail(): JSX.Element {
return <div className="pt-[52px] text-black">Deck Detail Page</div>
return (
<div>
<Header title="로그인" className="justify-end" />
<div className="pt-[52px] text-black">Deck Detail Page</div>
</div>
)
}
87 changes: 87 additions & 0 deletions packages/service-frontend/app/deck/[id]/play/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use client'
import type { JSX, MouseEventHandler } from 'react'

import { Icon } from '@ppoba/ui'

import { CardIcon, CardStyle } from './constant'

export type CardType =
| 'flower'
| 'feather'
| 'sprout'
| 'turnip'
| 'plug'
| 'nail'
| 'swallow'
| 'clover'

interface Props {
type: CardType
number: number
text?: string
isShowBack?: boolean
className?: string
onClick: MouseEventHandler<HTMLDivElement>
}

function Card({
type,
className,
number,
text,
isShowBack,
onClick,
}: Props): JSX.Element {
return (
<div
role="button"
className={`relative w-full h-full flex shrink-0 cursor-pointer text-center absolute w-full h-full box-border rounded-[24px] shadow-[4px_4px_20px_rgba(0,0,0,0.16)] ${
isShowBack ? CardStyle[type].background : 'bg-grey-700'
} ${className}`}
onClick={onClick}
>
{isShowBack ? (
<>
<div
className={`flex flex-col gap-[2px] absolute left-5 top-5 text-grey-700`}
>
<Icon type={CardIcon[type].normalSideIcon} width={24} height={24} />
<span>{String(number).padStart(2, '0')}</span>
</div>
<div className="absolute px-[40px] break-keep left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 headline-2 text-center text-black">
{text}
</div>
<div
className={`flex flex-col gap-[2px] absolute right-5 bottom-5 text-grey-700`}
>
<Icon type={CardIcon[type].normalSideIcon} width={24} height={24} />
<span>{String(number).padStart(2, '0')}</span>
</div>
</>
) : (
<>
<div
className={`flex flex-col absolute left-4 top-4 ${CardStyle[type].color}`}
>
<Icon type={CardIcon[type].colorSideIcon} width={32} height={32} />
<span>{String(number).padStart(2, '0')}</span>
</div>
<Icon
type={CardIcon[type].mainIcon}
width={200}
height={200}
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
/>
<div
className={`flex flex-col absolute right-4 bottom-4 rotate-180 ${CardStyle[type].color}`}
>
<Icon type={CardIcon[type].colorSideIcon} width={32} height={32} />
<span>{String(number).padStart(2, '0')}</span>
</div>
</>
)}
</div>
)
}

export default Card
13 changes: 13 additions & 0 deletions packages/service-frontend/app/deck/[id]/play/EmptyCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { JSX } from 'react'

function EmptyCard(): JSX.Element {
return (
<div
className={`flex items-center justify-center w-full h-full shrink-0 rounded-[24px] bg-white border-[5px] border-grey-200 border-dashed text-grey-800 headline-3 text-center whitespace-pre shadow-[inset_0px_0px_24px_rgba(0,0,0,0.3)]`}
>
{`남은 카드가 없어\n다른 게임하러 갈래?`}
</div>
)
}

export default EmptyCard
87 changes: 87 additions & 0 deletions packages/service-frontend/app/deck/[id]/play/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { IconType } from '@ppoba/ui/dist/src/components/Icon'

import type { CardType } from './Card'

export const CardIcon: Record<
CardType,
Record<'mainIcon' | 'colorSideIcon' | 'normalSideIcon', IconType>
> = {
flower: {
normalSideIcon: 'flowerNormal',
colorSideIcon: 'flowerDot',
mainIcon: 'flowerColor',
},
swallow: {
normalSideIcon: 'swallowNormal',
colorSideIcon: 'swallowDot',
mainIcon: 'swallowColor',
},
sprout: {
normalSideIcon: 'sproutNormal',
colorSideIcon: 'sproutDot',
mainIcon: 'sproutColor',
},
feather: {
normalSideIcon: 'featherNormal',
colorSideIcon: 'featherDot',
mainIcon: 'featherColor',
},
plug: {
normalSideIcon: 'plugNormal',
colorSideIcon: 'plugDot',
mainIcon: 'plugColor',
},
nail: {
normalSideIcon: 'nailNormal',
colorSideIcon: 'nailDot',
mainIcon: 'nailColor',
},
clover: {
normalSideIcon: 'cloverNormal',
colorSideIcon: 'cloverDot',
mainIcon: 'cloverColor',
},
turnip: {
normalSideIcon: 'turnipNormal',
colorSideIcon: 'turnipDot',
mainIcon: 'turnipColor',
},
}

export const CardStyle: Record<
CardType,
Record<'color' | 'background', string>
> = {
nail: {
background: 'bg-purple-02',
color: 'text-purple-02',
},
sprout: {
background: 'bg-teal-02',
color: 'text-teal-02',
},
feather: {
background: 'bg-yellow-02',
color: 'text-yellow-02',
},
turnip: {
background: 'bg-orange-02',
color: 'text-orange-02',
},
swallow: {
background: 'bg-blue-02',
color: 'text-blue-02',
},
clover: {
background: 'bg-green-02',
color: 'text-green-02',
},
flower: {
background: 'bg-pink-02',
color: 'text-pink-02',
},
plug: {
background: 'bg-red-02',
color: 'text-red-02',
},
}
6 changes: 1 addition & 5 deletions packages/service-frontend/app/deck/[id]/play/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,5 @@ interface Props {
}

export default function Layout({ children }: Props): JSX.Element {
return (
<>
<ContentWrapper>{children}</ContentWrapper>
</>
)
return <>{children}</>
}
125 changes: 122 additions & 3 deletions packages/service-frontend/app/deck/[id]/play/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,126 @@
'use client'
import { useCallback, useState } from 'react'

import { Button, SecondaryButton } from '@ppoba/ui'

import { Header } from '@/components'

import Card, { CardType } from './Card'
import { CardStyle } from './constant'
import EmptyCard from './EmptyCard'

const cardTypes: CardType[] = [
'flower',
'feather',
'sprout',
'turnip',
'plug',
'nail',
'swallow',
'clover',
]

const generateCards = (size: number) => {
return [...Array(size)].map((_, i) => ({
id: Math.random().toString() + Math.random().toString(),
number: i + 1,
type: cardTypes[i % cardTypes.length],
text: '김가나다라마바사아자 김가나다라마바사아자 김가나다라마바사아자 김가나다라마바사아자 김가나다라마바사아자',
}))
}

export default function DeckPlay(): JSX.Element {
const [isShowBack, setIsShowBack] = useState(false)
const [currentIndex, setCurrentIndex] = useState(0)
const [cards, setCard] = useState(generateCards(24))

const handleClickShuffleButton = useCallback(() => {
// currentIndex부터 마지막카드까지만 섞는다.
// 이때 currentIndex는 0으로 처음 시작으로 바꾼다
const nextCards = [...cards]
.slice(currentIndex)
.sort(() => (Math.random() > 0.5 ? 1 : -1))
setCard(nextCards)
setCurrentIndex(0)
setIsShowBack(false)
}, [cards, currentIndex])

const handleClickNextButton = useCallback(() => {
setCurrentIndex(prev => Math.min(prev + 1, cards.length))
setIsShowBack(false)
}, [cards.length])

return (
<div>
<h1 className="text-black">Deck Play Page</h1>
</div>
<>
<Header rightIconType="close" />
<div className="flex flex-col min-h-screen justify-around">
{/* 게임 정보 */}
<div className="flex flex-col gap-[4px] pt-[52px] px-[8px] text-center">
<strong className="headline-1 text-grey-800">
뉴 매시업 이미지 게임
</strong>
<p className="subtitle-3 text-grey-600">
남은 카드 {cards.length - currentIndex}장
</p>
</div>

{/* 플레이 카드 */}
<div className="relative mx-auto w-[270px] h-[360px] z-30">
{/* 카드가 있는 경우 */}
{cards.slice(currentIndex, currentIndex + 1).map(card => (
<Card
key={card.id}
number={card.number}
type={card.type}
text={card.text}
className="z-30"
isShowBack={isShowBack}
onClick={() => setIsShowBack(prev => !prev)}
/>
))}
{/* 남은카드가 1개 이상인 경우 */}
{currentIndex < cards.length - 1 && (
<div
className={`absolute w-[255px] h-[340px] top-[-16px] left-1/2 -translate-x-1/2 z-10 rounded-[24px] ${
CardStyle[cards[currentIndex + 1].type].background
}`}
/>
)}
{/* 남은카드가 2개 이상인 경우 */}
{currentIndex < cards.length - 2 && (
<div
className={`absolute w-[225px] h-[300px] top-[-32px] left-1/2 -translate-x-1/2 z-0 opacity-60 rounded-[24px] ${
CardStyle[cards[currentIndex + 2].type].background
}`}
/>
)}

{/* 카드가 없는 경우 */}
{cards.length === currentIndex && <EmptyCard />}
</div>

{/* 버튼 */}
<div className="flex gap-[10px] justify-center">
{cards.length === currentIndex ? (
// 남은 카드가 없는 경우
<Button size="medium">리스트로 가기</Button>
) : (
<>
{/* 카드가 남은 경우 */}
<SecondaryButton
size="small"
rightIcon="shuffle"
onClick={handleClickShuffleButton}
>
섞기
</SecondaryButton>
<Button size="large" onClick={handleClickNextButton}>
다음 카드 보기
</Button>
</>
)}
</div>
</div>
</>
)
}
Loading