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

중복선택 버튼 컴포넌트 (체크박스) 작성 #27

Merged
merged 10 commits into from
Jul 14, 2024
37 changes: 37 additions & 0 deletions src/app/test/Staging.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,52 @@
import { useEffect } from "react";

import Button from "@/component/Button/Button.tsx";
import { ButtonProvider } from "@/component/Button/ButtonProvider.tsx";
import CheckBox from "@/component/common/CheckBox/CheckBox";
import CheckBoxGroup from "@/component/common/CheckBox/CheckBoxGroup";
import Radio from "@/component/common/RadioButton/Radio";
import RadioButtonGroup from "@/component/common/RadioButton/RadioButtonGroup";
import { useCheckBox } from "@/hooks/useCheckBox";
import { useRadioButton } from "@/hooks/useRadioButton";
import { DefaultLayout } from "@/layout/DefaultLayout.tsx";

export default function Staging() {
const [isRadioChecked, onChange, selectedValue] = useRadioButton();
const [isCheckBoxChecked, toggle, selectedValues] = useCheckBox();

useEffect(() => {
console.log("라디오 버튼 선택 value:", selectedValue);
}, [selectedValue]);

useEffect(() => {
console.log("체크박스 선택 values:", selectedValues);
}, [selectedValues]);

return (
<DefaultLayout>
<Button> 그냥 그저 그런 버튼 </Button>
<Button colorSchema={"gray"}> 그냥 그저 그런 버튼 </Button>
<Button colorSchema={"sky"}> 그냥 그저 그런 버튼 </Button>
<Button colorSchema={"primary"}> 그냥 그저 그런 버튼 </Button>

<br />
<h3>라디오버튼</h3>
<RadioButtonGroup isChecked={isRadioChecked} onChange={onChange} radioName={"프로젝트 주기"}>
<Radio value={"0"}>주 1회</Radio>
<Radio value={"1"}>월 1회</Radio>
<Radio value={"2"}>분기별</Radio>
<Radio value={"3"}>프로젝트 끝난 후</Radio>
</RadioButtonGroup>

<br />
<h3>체크박스</h3>
<CheckBoxGroup isChecked={isCheckBoxChecked} onChange={toggle}>
<CheckBox value={"00"}>주 1회</CheckBox>
<CheckBox value={"10"}>월 1회</CheckBox>
<CheckBox value={"20"}>분기별</CheckBox>
<CheckBox value={"30"}>프로젝트 끝난 후</CheckBox>
</CheckBoxGroup>
Comment on lines +34 to +48
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용방법이 직관적이어서 좋네요!!


<ButtonProvider>
<ButtonProvider.Primary>기본 버튼</ButtonProvider.Primary>
<ButtonProvider.Sky>하늘색 버튼</ButtonProvider.Sky>
45 changes: 45 additions & 0 deletions src/component/common/CheckBox/CheckBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { css } from "@emotion/react";
import { useContext } from "react";

import ListItemCard from "@/component/common/Card/ListItemCard";
import { CheckBoxContext } from "@/store/context/CheckBoxContext";

type CheckBoxProps = {
value: string;
children: React.ReactNode;
};

const CheckBox = ({ value, children }: CheckBoxProps) => {
const checkboxContext = useContext(CheckBoxContext);
return (
<ListItemCard variant={checkboxContext?.isChecked(value) ? "theme" : "default"}>
<label
htmlFor={value}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

htmlFor이라는 속성도 있군요!! 하나 또 알아갑니다.

css={css`
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
cursor: pointer;
`}
>
{children}
</label>
<input
type="checkbox"
id={value}
value={value}
onChange={(e) => {
checkboxContext?.onChange && checkboxContext.onChange(e.target.value);
}}
css={css`
display: none;
`}
/>
</ListItemCard>
);
};

export default CheckBox;
23 changes: 23 additions & 0 deletions src/component/common/CheckBox/CheckBoxGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { css } from "@emotion/react";

import { CheckBoxContext, CheckBoxContextState } from "@/store/context/CheckBoxContext";

type CheckBoxGroupProps = {
children: React.ReactNode;
} & CheckBoxContextState;

const CheckBoxGroup = ({ children, ...props }: CheckBoxGroupProps) => {
return (
<div
css={css`
display: flex;
flex-direction: column;
gap: 1rem;
`}
>
<CheckBoxContext.Provider value={props}>{children}</CheckBoxContext.Provider>
</div>
);
};

export default CheckBoxGroup;
3 changes: 2 additions & 1 deletion src/component/common/RadioButton/Radio.tsx
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ type RadioProps = {
const Radio = ({ value, children }: RadioProps) => {
const radioContext = useContext(RadioContext);
return (
<ListItemCard variant={radioContext?.selectedValue === value ? "theme" : "default"}>
<ListItemCard variant={radioContext?.isChecked(value) ? "theme" : "default"}>
<label
htmlFor={value}
css={css`
@@ -22,6 +22,7 @@ const Radio = ({ value, children }: RadioProps) => {
align-items: center;
height: 100%;
width: 100%;
cursor: pointer;
`}
>
{children}
11 changes: 4 additions & 7 deletions src/component/common/RadioButton/RadioButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { css } from "@emotion/react";

import { RadioContext } from "@/store/context/RadioContext";
import { RadioContext, RadioContextState } from "@/store/context/RadioContext";

type RadioButtonGroupProps = {
children: React.ReactNode;
selectedValue: string | undefined;
onChange: React.Dispatch<React.SetStateAction<string | undefined>>;
radioName: string;
};
} & RadioContextState;

const RadioButtonGroup = ({ children, ...rest }: RadioButtonGroupProps) => {
const RadioButtonGroup = ({ children, ...props }: RadioButtonGroupProps) => {
return (
<div
css={css`
@@ -18,7 +15,7 @@ const RadioButtonGroup = ({ children, ...rest }: RadioButtonGroupProps) => {
gap: 1rem;
`}
>
<RadioContext.Provider value={rest}>{children}</RadioContext.Provider>
<RadioContext.Provider value={props}>{children}</RadioContext.Provider>
</div>
);
};
13 changes: 13 additions & 0 deletions src/hooks/useCheckBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useState } from "react";

import { CheckBoxContextState } from "@/store/context/CheckBoxContext";

type UseCheckBoxReturn = [CheckBoxContextState["isChecked"], CheckBoxContextState["onChange"], string[]];

export const useCheckBox = (): UseCheckBoxReturn => {
const [checkedStates, setCheckedStates] = useState<Record<string, boolean>>({});
const isChecked = (value: string) => checkedStates[value];
const toggle = (value: string) => setCheckedStates((prev) => ({ ...prev, [value]: !prev[value] }));
const selectedValues = Object.keys(checkedStates).filter((key) => checkedStates[key]);
return [isChecked, toggle, selectedValues];
};
13 changes: 13 additions & 0 deletions src/hooks/useRadioButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useState } from "react";

import { RadioContextState } from "@/store/context/RadioContext";

type UseRadioButtonReturn = [RadioContextState["isChecked"], RadioContextState["onChange"], string | undefined];

export const useRadioButton = (): UseRadioButtonReturn => {
const [selectedValue, setSelectedValue] = useState<string>();
const isChecked = (value: string) => selectedValue === value;
const onChange = (value: string) => setSelectedValue(value);

return [isChecked, onChange, selectedValue];
Copy link
Collaborator

@donghunee donghunee Jul 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { RadioContextState } from "@/store/context/RadioContext";
type UseRadioButtonReturn = [RadioContextState["isChecked"], RadioContextState["onChange"], string | undefined];
export const useRadioButton = (): UseRadioButtonReturn => {
const [selectedValue, setSelectedValue] = useState<string>();
const isChecked = (value: string) => selectedValue === value;
const onChange = (value: string) => setSelectedValue(value);
return [isChecked, onChange, selectedValue];
export const useRadioButton = () => {
const [selectedValue, setSelectedValue] = useState<string>();
const isChecked = (value: string) => selectedValue === value;
const onChange = (value: string) => setSelectedValue(value);
return [isChecked, onChange, selectedValue] as const;
}

저는 custom hook에서 배열로 반환 시 return type을 설정해줄 때 as const로 명시해주는게 깔끔하더라구요~!!

별개로 object return 이 아닌 array return을 채택하신 이유도 궁금해요~!!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아!! 맞네요 as const로 수정하겠습니다! 감사합니다 🙇‍♀️

+) Staging.tsx 파일에 예시 작성하면서 라디오버튼과 체크박스를 같이 사용할 때 useRadioButton, useCheckBox 훅이 같은 이름(isChecked)을 반환하다보니 충돌이 나더라구요! 저희 프로젝트에서는 아직까지 그럴 일은 없겠지만, 그래도 이름을 사용하는 쪽에서 바꿔서 사용하면 좋겠다는 생각으로 이렇게 작성했습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자세한 설명 감사합니다👍👍

};
2 changes: 1 addition & 1 deletion src/layout/GlobalLayout.tsx
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ export default function GlobalLayout() {

display: flex;
flex-direction: column;
background-color: #f1f3f5;
background-color: #ffffff;
`}
>
<Outlet />
8 changes: 8 additions & 0 deletions src/store/context/CheckBoxContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createContext } from "react";

export type CheckBoxContextState = {
isChecked: (value: string) => boolean;
onChange: (value: string) => void;
};

export const CheckBoxContext = createContext<CheckBoxContextState | undefined>(undefined);
6 changes: 3 additions & 3 deletions src/store/context/RadioContext.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createContext } from "react";

type RadioContextState = {
export type RadioContextState = {
radioName: string;
selectedValue?: string;
onChange?: (value: string) => void;
isChecked: (value: string) => boolean;
onChange: (value: string) => void;
};

export const RadioContext = createContext<RadioContextState | undefined>(undefined);