Skip to content

프로그래스바 공통 컴포넌트로 분리

최수연 (SooYeon Choi) edited this page Aug 22, 2024 · 2 revisions

게임 진행 중 실시간 비율 화면

image

게임 최종 결과 비율 화면

image

막대 부분만 따로 공통 컴포넌트로 분리하고자 했다. 해당 카드 자체를 분리하려고 했는데 생각해보니 각각 필요한 데이터가 너무 다르고 구조가 조금씩 다 달라서 Props로 데이터를 전부 내려주고 내부에서 또 두 개의 카드를 분기 처리하는 방식이 너무 지저분하고 복잡할 것 같았다.

막대 부분은 거의 로직이 동일한데, 한 가지 다른 점이 있다면 결과 화면에서는 각 비율 중 더 작은 쪽에 비율 색상을 회색으로 즉, “text-n-neutral-500”으로 처리해주어야 한다. 그 외에는 동일하다.

어차피 프로그래스바 컴포넌트를 분리할 때 비율을 받아야하므로 내부에서 결과 창일 때만 처리해주면 되는데, 결과창인지 게임 진행 중인지에 대한 분기 처리를 어떻게 해야할지 고민이었다.

생각해보니 useRushGameContext() 훅에서 게임 진행 상태를 저장해두었는데, 각각 “IN_PROGRESS”, “COMPLETED”에 해당하기 때문에 굳이 Props로 각각을 분기처리할 필요 없이 프로그래스바 컴포넌트 내부에서 해당 상태를 받아 처리해주면 된다.

import { VariantProps, cva } from "class-variance-authority";
import { useRushGameContext } from "@/hooks/useRushGameContext.ts";

interface RushProgressBarProps {
    leftOptionRatio: number;
    rightOptionRatio: number;
}

const barVariants = cva(`flex items-center`, {
    variants: {
        color: {
            green: "bg-gradient-green",
            red: "bg-gradient-red",
        },
        status: {
            winning: "text-n-neutral-950",
            losing: "text-n-neutral-500",
        },
        textAlign: {
            left: "justify-start",
            right: "justify-end",
        },
    },
    defaultVariants: {
        status: "winning",
    },
});

type BarVariantsProps = VariantProps<typeof barVariants>;

interface BarProps extends BarVariantsProps {
    ratio: number;
}

function Bar({ ratio, color, status, textAlign }: BarProps) {
    return (
        <span className={barVariants({ color, status, textAlign })} style={{ width: `${ratio}%` }}>
            <p className="px-3">{ratio}%</p>
        </span>
    );
}

export default function RushProgressBar({
    leftOptionRatio,
    rightOptionRatio,
}: RushProgressBarProps) {
    const { gameState } = useRushGameContext();
    const isCompleted = gameState.phase === "COMPLETED";
    const leftStatus = isCompleted && leftOptionRatio < rightOptionRatio ? "losing" : "winning";
    const rightStatus = isCompleted && rightOptionRatio < leftOptionRatio ? "losing" : "winning";

    return (
        <div className="h-heading-3-bold h-[66px] flex justify-between">
            <Bar ratio={leftOptionRatio} color="green" status={leftStatus} textAlign="left" />
            <Bar ratio={rightOptionRatio} color="red" status={rightStatus} textAlign="right" />
        </div>
    );
}

위 코드가 프로그래스바 관련 컴포넌트인데, 우선 leftOptionRatio, rightOptionRatio를 Props로 받아서 넘겨줬다. 각 섹션별로 실시간 비율과 최종 비율을 받아서 보여줘야 하기 때문이다.

그리고 게임 진행 상태에 대한 gameState를 useRushGameContext에서 내려받아서 현재 게임이 종료됐는지, 즉 gameState.phase가 "COMPLETED"인지 판단하고, "COMPLETED"인 경우에는 각 비율의 상태에 따라서 losing과 winning을 설정해주었다. 해당 비율이 더 작은 경우 losing으로 설정해주었고, 비율이 둘 다 동일한 경우에는 그냥 winning으로 처리해주었다. 그래야 비율이 동일할 경우 둘 다 “text-n-neutral-950”로 처리할 수 있기 때문이다.

새로운 Bar 컴포넌트를 분리해주었는데, 각 프로그래스바의 공통 로직을 분리하기 위해서였다. Props로 각각의 비율(ratio)과 프로그래스바 색상(color), losing/winning 상태(status), 프로그래스바 내부에서 비율을 표시할 좌우 위치(textAlign)를 받도록 했다.

Props로 받은 비율대로 프로그래스바의 width를 설정해주고 text로 표기해주었다.

그리고 나머지 color, status, textAlign들은 cva를 사용하여 각 상태에 따라 tailwindCSS Variants를 다르게 해주었다.

컴포넌트 분리 및 게임 참여자가 0명인 경우 고려

나중에 테스트하면서 발견했는데, 게임 참여자가 0명인 경우 프로그래스바가 나타나지 않았다. 따라서, 0명인 경우에도 회색 바가 뜨도록 isAllZero라는 Props를 따로 넘겨주어 임의로 가로 width를 넣어주고 색상도 회색으로 설정해주었다. 그리고 해당 Bar 컴포넌트가 길어진다고 판단하여 프로그래스바 컴포넌트에서 Bar만 따로 분리해주었다.

image
import { CARD_COLOR, CARD_PHASE } from "@/constants/Rush/rushCard.ts";
import RushBar from "@/features/RushGame/RushGameComponents/RushBar.tsx";
import useRushGameStateContext from "@/hooks/Contexts/useRushGameStateContext.ts";

interface RushProgressBarProps {
    leftOptionRatio: number;
    rightOptionRatio: number;
}

export default function RushProgressBar({
    leftOptionRatio,
    rightOptionRatio,
}: RushProgressBarProps) {
    const gameState = useRushGameStateContext();

    const isCompleted = gameState.phase === CARD_PHASE.COMPLETED;
    const isAllZero = leftOptionRatio === 0 && rightOptionRatio === 0;

    const leftStatus = isCompleted && leftOptionRatio < rightOptionRatio ? "losing" : "winning";
    const rightStatus = isCompleted && rightOptionRatio < leftOptionRatio ? "losing" : "winning";

    return (
        <div className="h-heading-3-bold h-[66px] flex justify-between">
            <RushBar
                ratio={leftOptionRatio}
                color={CARD_COLOR.GREEN}
                status={leftStatus}
                textAlign="left"
                isAllZero={isAllZero}
            />
            <RushBar
                ratio={rightOptionRatio}
                color={CARD_COLOR.RED}
                status={rightStatus}
                textAlign="right"
                isAllZero={isAllZero}
            />
        </div>
    );
}
import { VariantProps, cva } from "class-variance-authority";

const barVariants = cva(`flex items-center`, {
    variants: {
        color: {
            green: "bg-gradient-green",
            red: "bg-gradient-red",
            blue: "bg-gradient-blue",
            yellow: "bg-gradient-yellow",
            gray: "bg-n-neutral-100",
        },
        status: {
            winning: "text-n-neutral-950",
            losing: "text-n-neutral-500",
        },
        textAlign: {
            left: "justify-start",
            right: "justify-end",
        },
    },
    defaultVariants: {
        status: "winning",
    },
});

type BarVariantsProps = VariantProps<typeof barVariants>;

interface BarProps extends BarVariantsProps {
    ratio: number;
    isAllZero: boolean;
}

export default function RushBar({ ratio, color, status, textAlign, isAllZero }: BarProps) {
    const barColor = isAllZero ? "gray" : color;
    const barStatus = isAllZero ? "losing" : status;
    const barWidth = isAllZero ? "50%" : `${ratio}%`;

    return (
        <span
            className={barVariants({ color: barColor, status: barStatus, textAlign })}
            style={{ width: barWidth }}
        >
            <p className="px-3 z-40">{ratio}%</p>
        </span>
    );
}

📚 학습 정리

🗂️ 멘토링

Clone this wiki locally