Skip to content

Commit

Permalink
[Feature] Stepper 컴포넌트를 구현해요. (#65)
Browse files Browse the repository at this point in the history
* feature: progressBar 기본 틀 잡기

* fix: 비율 수정

* feature: progressBarCircle 개발

* feature: ProgressBar Label 개발

* chore: a11y test 오류 해결

* fix: 쓸데없는 경로 수정

* fix: 접근성 테스트 실패 개선

* fix: 코드리뷰 반영

* fix : 롤업 export 파일 설정 변경

* fix: 코드리뷰 반영

* fix: 코드리뷰 반영

* fix : Storybook 이름 변경

* fix: progressBar 변수 흔적 지우기

* fix: changeset 내역 변경

* fix: ProgressBar aria-label 수정
  • Loading branch information
eugene028 authored Jul 5, 2024
1 parent a631741 commit bd5369b
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/.grumpy-llamas-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wowds-ui": patch
---

Stepper UI를 배포합니다.
5 changes: 5 additions & 0 deletions packages/wow-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
"require": "./dist/Switch.cjs",
"import": "./dist/Switch.js"
},
"./Stepper": {
"types": "./dist/components/Stepper/index.d.ts",
"require": "./dist/Stepper.cjs",
"import": "./dist/Stepper.js"
},
"./RadioButton": {
"types": "./dist/components/RadioGroup/RadioButton.d.ts",
"require": "./dist/RadioButton.cjs",
Expand Down
1 change: 1 addition & 0 deletions packages/wow-ui/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default {
TextField: "./src/components/TextField",
TextButton: "./src/components/TextButton",
Switch: "./src/components/Switch",
Stepper: "./src/components/Stepper",
RadioButton: "./src/components/RadioGroup/RadioButton",
RadioGroup: "./src/components/RadioGroup/RadioGroup",
MultiGroup: "./src/components/MultiGroup",
Expand Down
76 changes: 76 additions & 0 deletions packages/wow-ui/src/components/Stepper/Stepper.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { Meta, StoryObj } from "@storybook/react";

import Stepper from "@/components/Stepper";

const meta = {
title: "UI/Stepper",
component: Stepper,
parameters: {
componentSubtitle: "스텝퍼 컴포넌트",
a11y: {
config: {
rules: [{ id: "color-contrast", enabled: false }],
},
},
},
tags: ["autodocs"],
argTypes: {
step: {
description: "프로그래스 바의 현재 스텝을 나타냅니다.",
table: {
type: { summary: "number" },
},
control: {
type: "number",
},
},
labels: {
description: "프로그래스 바 하단에 나타낼 라벨의 배열을 나타냅니다.",
table: {
type: { summary: "LabelType[]" },
},
control: false,
},
maxStep: {
description: "프로그래스 바가 가질 수 있는 최대 스텝을 나타냅니다.",
table: {
type: { summary: "number" },
},
control: {
type: "number",
},
},
width: {
description: "프로그래스 바의 너비를 지정합니다.",
table: {
type: { summary: "number" },
},
control: {
type: "text",
},
},
},
} satisfies Meta<typeof Stepper>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
step: 1,
maxStep: 5,
},
};

export const StepperWithMarkers: Story = {
args: {
step: 1,
maxStep: 3,
labels: [
{ value: 1, label: "Label" },
{ value: 2, label: "Label" },
{ value: 3, label: "Label" },
],
},
};
202 changes: 202 additions & 0 deletions packages/wow-ui/src/components/Stepper/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { cva } from "@styled-system/css";
import { styled } from "@styled-system/jsx";
import type { ReactNode } from "react";
import { useCallback, useMemo } from "react";

import { calcPercent } from "@/utils/calcPercent";

export interface StepperProps {
step: number;
maxStep?: number;
labels?: LabelType[];
width?: number;
}

export type LabelType = {
value: number;
label: ReactNode;
};

const checkStepperStatus = (number: number, step: number) => {
if (step === number) return "currentStep";
if (step > number) return "checkedStep";
return "default";
};

/**
* @param {number} step Stepper의 현재 스텝
* @param {number} [maxStep] Stepper가 가질 수 있는 최대 스텝
* @param {LabelType[]} [labels] Stepper에 하단에 입력할 라벨의 배열
* @param {number} width Stepper의 가로 길이를 자유롭게 정할 수 있어요. 단, 278px 이상이어야 합니다.
*/

const Stepper = ({ step, maxStep = 3, labels, width }: StepperProps) => {
const fillStepper = useCallback((maxStep: number, step: number) => {
const ratio = (step - 1) / (maxStep - 1);
return ratio > 1 ? "100%" : `${ratio * 100}%`;
}, []);

const circleNumbers = useMemo(
() => Array.from({ length: maxStep }, (_, i) => i + 1),
[maxStep]
);

return (
<div
aria-label="stepper"
aria-valuemax={maxStep}
aria-valuemin={1}
aria-valuetext={String(step)}
role="progressbar"
>
<styled.div
backgroundColor="outline"
height="1.2px"
minWidth="17.375rem"
position="relative"
style={{ width: width && width > 278 ? `${width}px` : "17.375rem" }}
>
<styled.div
backgroundColor="primary"
height="1.2px"
style={{ width: fillStepper(maxStep, step) }}
/>
<styled.ul position="relative" width="100%">
{circleNumbers.map((circleNumber) => (
<StepperCircle
circleNumber={circleNumber}
currentStep={step}
key={`circle-${circleNumber}`}
maxStep={maxStep}
/>
))}
</styled.ul>
{labels && (
<styled.ul pointerEvents="none" position="relative" userSelect="none">
{labels.map((label) => (
<StepperLabel
currentStep={step}
key={`label-${label.value}`}
labelObject={label}
maxStep={maxStep}
/>
))}
</styled.ul>
)}
</styled.div>
</div>
);
};

export default Stepper;

const StepperCircle = ({
maxStep,
circleNumber,
currentStep,
}: {
maxStep: number;
circleNumber: number;
currentStep: number;
}) => {
return (
<styled.li
className={stepperCircleStyle({
status: checkStepperStatus(circleNumber, currentStep),
})}
style={{
left: `${calcPercent(maxStep, circleNumber - 1)}%`,
top: "-12px",
}}
>
{circleNumber}
</styled.li>
);
};

const stepperCircleStyle = cva({
base: {
textStyle: "label2",
alignItems: "center",
borderRadius: "full",
display: "flex",
height: "1.5rem",
justifyContent: "center",
pointerEvents: "none",
position: "absolute",
width: "1.5rem",
borderWidth: "1px",
transform: "translateX(-50%)",
},
variants: {
status: {
default: {
borderWidth: "0.0625rem",
borderColor: "outline",
backgroundColor: "backgroundNormal",
color: "sub",
},
checkedStep: {
borderWidth: "0.0625rem",
borderColor: "primary",
color: "primary",
backgroundColor: "backgroundNormal",
},
currentStep: {
backgroundColor: "primary",
color: "textWhite",
},
},
},
});

const StepperLabel = ({
labelObject,
maxStep,
currentStep,
}: {
labelObject: LabelType;
maxStep: number;
currentStep: number;
}) => {
const { value, label } = labelObject;

return (
<styled.li
marginTop="14px"
pointerEvents="none"
position="absolute"
transform="translateX(-50%)"
style={{
left: `${calcPercent(maxStep, value - 1)}%`,
}}
>
<styled.span
className={stepperLabelStyle({
status: checkStepperStatus(value, currentStep),
})}
>
{label}
</styled.span>
</styled.li>
);
};

const stepperLabelStyle = cva({
base: {
textStyle: "label2",
},
variants: {
status: {
default: {
color: "sub",
},
checkedStep: {
color: "primary",
},
currentStep: {
color: "textBlack",
},
},
},
});
3 changes: 3 additions & 0 deletions packages/wow-ui/src/utils/calcPercent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const calcPercent = (maxValue: number, value: number) => {
return (value / (maxValue - 1)) * 100;
};

0 comments on commit bd5369b

Please sign in to comment.