diff --git a/src/app/test/Staging.tsx b/src/app/test/Staging.tsx index 7b5f5dec..18bd04ea 100644 --- a/src/app/test/Staging.tsx +++ b/src/app/test/Staging.tsx @@ -2,11 +2,9 @@ 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 { Input, InputLabelContainer, Label } from "@/component/common/Input"; -import Radio from "@/component/common/RadioButton/Radio"; -import RadioButtonGroup from "@/component/common/RadioButton/RadioButtonGroup"; +import { CheckBox, CheckBoxGroup } from "@/component/common/checkBox"; +import { Input, InputLabelContainer, Label } from "@/component/common/input"; +import { Radio, RadioButtonGroup } from "@/component/common/radioButton"; import { useCheckBox } from "@/hooks/useCheckBox"; import { useInput } from "@/hooks/useInput"; import { useRadioButton } from "@/hooks/useRadioButton"; diff --git a/src/component/common/CheckBox/CheckBox.tsx b/src/component/common/CheckBox/CheckBox.tsx index 8118a9c8..3b982d4c 100644 --- a/src/component/common/CheckBox/CheckBox.tsx +++ b/src/component/common/CheckBox/CheckBox.tsx @@ -10,7 +10,7 @@ type CheckBoxProps = { children: React.ReactNode; }; -const CheckBox = ({ value, children }: CheckBoxProps) => { +export function CheckBox({ value, children }: CheckBoxProps) { const checkboxContext = useContext(CheckBoxContext); return ( <ListItemCard variant={checkboxContext?.isChecked(value) ? "theme" : "default"}> @@ -41,6 +41,4 @@ const CheckBox = ({ value, children }: CheckBoxProps) => { /> </ListItemCard> ); -}; - -export default CheckBox; +} diff --git a/src/component/common/CheckBox/CheckBoxGroup.tsx b/src/component/common/CheckBox/CheckBoxGroup.tsx index 0bb6e58c..98599793 100644 --- a/src/component/common/CheckBox/CheckBoxGroup.tsx +++ b/src/component/common/CheckBox/CheckBoxGroup.tsx @@ -8,11 +8,11 @@ export type CheckBoxContextState = { export const CheckBoxContext = createContext<CheckBoxContextState | undefined>(undefined); -type CheckBoxGroupProps = { +type CheckBoxProps = { children: React.ReactNode; } & CheckBoxContextState; -const CheckBoxGroup = ({ children, ...props }: CheckBoxGroupProps) => { +export function CheckBoxGroup({ children, ...props }: CheckBoxProps) { return ( <div css={css` @@ -24,6 +24,4 @@ const CheckBoxGroup = ({ children, ...props }: CheckBoxGroupProps) => { <CheckBoxContext.Provider value={props}>{children}</CheckBoxContext.Provider> </div> ); -}; - -export default CheckBoxGroup; +} diff --git a/src/component/common/CheckBox/index.ts b/src/component/common/CheckBox/index.ts new file mode 100644 index 00000000..1c96a44e --- /dev/null +++ b/src/component/common/CheckBox/index.ts @@ -0,0 +1,2 @@ +export { CheckBox } from "./CheckBox"; +export { CheckBoxGroup } from "./CheckBoxGroup"; diff --git a/src/component/common/RadioButton/Radio.tsx b/src/component/common/RadioButton/Radio.tsx index 1b129636..6c668d62 100644 --- a/src/component/common/RadioButton/Radio.tsx +++ b/src/component/common/RadioButton/Radio.tsx @@ -10,7 +10,7 @@ type RadioProps = { children: React.ReactNode; }; -const Radio = ({ value, children }: RadioProps) => { +export function Radio({ value, children }: RadioProps) { const radioContext = useContext(RadioContext); return ( <ListItemCard variant={radioContext?.isChecked(value) ? "theme" : "default"}> @@ -42,6 +42,4 @@ const Radio = ({ value, children }: RadioProps) => { /> </ListItemCard> ); -}; - -export default Radio; +} diff --git a/src/component/common/RadioButton/RadioButtonGroup.tsx b/src/component/common/RadioButton/RadioButtonGroup.tsx index a009bea0..1093bc5d 100644 --- a/src/component/common/RadioButton/RadioButtonGroup.tsx +++ b/src/component/common/RadioButton/RadioButtonGroup.tsx @@ -13,7 +13,7 @@ type RadioButtonGroupProps = { children: React.ReactNode; } & RadioContextState; -const RadioButtonGroup = ({ children, ...props }: RadioButtonGroupProps) => { +export function RadioButtonGroup({ children, ...props }: RadioButtonGroupProps) { return ( <div css={css` @@ -25,6 +25,4 @@ const RadioButtonGroup = ({ children, ...props }: RadioButtonGroupProps) => { <RadioContext.Provider value={props}>{children}</RadioContext.Provider> </div> ); -}; - -export default RadioButtonGroup; +} diff --git a/src/component/common/RadioButton/index.ts b/src/component/common/RadioButton/index.ts new file mode 100644 index 00000000..054777c4 --- /dev/null +++ b/src/component/common/RadioButton/index.ts @@ -0,0 +1,2 @@ +export { Radio } from "./Radio"; +export { RadioButtonGroup } from "./RadioButtonGroup"; diff --git a/src/component/common/checkBox/CheckBox.tsx b/src/component/common/checkBox/CheckBox.tsx new file mode 100644 index 00000000..3b982d4c --- /dev/null +++ b/src/component/common/checkBox/CheckBox.tsx @@ -0,0 +1,44 @@ +import { css } from "@emotion/react"; +import { useContext } from "react"; + +import { CheckBoxContext } from "./CheckBoxGroup"; + +import ListItemCard from "@/component/common/Card/ListItemCard"; + +type CheckBoxProps = { + value: string; + children: React.ReactNode; +}; + +export function CheckBox({ value, children }: CheckBoxProps) { + const checkboxContext = useContext(CheckBoxContext); + return ( + <ListItemCard variant={checkboxContext?.isChecked(value) ? "theme" : "default"}> + <label + htmlFor={value} + 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> + ); +} diff --git a/src/component/common/checkBox/CheckBoxGroup.tsx b/src/component/common/checkBox/CheckBoxGroup.tsx new file mode 100644 index 00000000..98599793 --- /dev/null +++ b/src/component/common/checkBox/CheckBoxGroup.tsx @@ -0,0 +1,27 @@ +import { css } from "@emotion/react"; +import { createContext } from "react"; + +export type CheckBoxContextState = { + isChecked: (value: string) => boolean; + onChange: (value: string) => void; +}; + +export const CheckBoxContext = createContext<CheckBoxContextState | undefined>(undefined); + +type CheckBoxProps = { + children: React.ReactNode; +} & CheckBoxContextState; + +export function CheckBoxGroup({ children, ...props }: CheckBoxProps) { + return ( + <div + css={css` + display: flex; + flex-direction: column; + gap: 1rem; + `} + > + <CheckBoxContext.Provider value={props}>{children}</CheckBoxContext.Provider> + </div> + ); +} diff --git a/src/component/common/checkBox/index.ts b/src/component/common/checkBox/index.ts new file mode 100644 index 00000000..1c96a44e --- /dev/null +++ b/src/component/common/checkBox/index.ts @@ -0,0 +1,2 @@ +export { CheckBox } from "./CheckBox"; +export { CheckBoxGroup } from "./CheckBoxGroup"; diff --git a/src/component/common/input/Input.tsx b/src/component/common/input/Input.tsx new file mode 100644 index 00000000..7fc19dcd --- /dev/null +++ b/src/component/common/input/Input.tsx @@ -0,0 +1,34 @@ +import { css } from "@emotion/react"; +import { forwardRef, useContext } from "react"; + +import { InputContext } from "./InputLabelContainer"; + +type InputProps = { + width?: string; +} & React.InputHTMLAttributes<HTMLInputElement>; + +export const Input = forwardRef(function ({ id, width = "100%", ...props }: InputProps) { + const inputContext = useContext(InputContext); + return ( + <div> + <div + css={css` + width: ${width}; + border: 1px solid #e3e6ea; // FIXME: 디자인 토큰 적용하기 + border-radius: 0.8rem; + padding: 1.6rem; + `} + > + <input + id={id || inputContext?.id} + css={css` + width: 100%; + `} + {...props} + /> + </div> + </div> + ); +}); + +Input.displayName = "Input"; diff --git a/src/component/common/input/InputLabelContainer.tsx b/src/component/common/input/InputLabelContainer.tsx new file mode 100644 index 00000000..c868b195 --- /dev/null +++ b/src/component/common/input/InputLabelContainer.tsx @@ -0,0 +1,27 @@ +import { css } from "@emotion/react"; +import { createContext } from "react"; + +type InputLabelContainerProps = { + id: string; + children: React.ReactNode; +}; + +export const InputContext = createContext<{ id: string } | undefined>(undefined); + +export function InputLabelContainer({ id, children }: InputLabelContainerProps) { + return ( + <InputContext.Provider value={{ id }}> + <div + css={[ + css` + display: flex; + flex-direction: column; + gap: 2rem; + `, + ]} + > + {children} + </div> + </InputContext.Provider> + ); +} diff --git a/src/component/common/input/Label.tsx b/src/component/common/input/Label.tsx new file mode 100644 index 00000000..1c7486a5 --- /dev/null +++ b/src/component/common/input/Label.tsx @@ -0,0 +1,63 @@ +import { css, Interpolation, Theme } from "@emotion/react"; +import { useContext } from "react"; + +import { InputContext } from "./InputLabelContainer"; + +type LabelProps = { + order?: number; + styles?: Interpolation<Theme>; +} & React.LabelHTMLAttributes<HTMLLabelElement>; + +export function Label({ id, children, order, styles }: LabelProps) { + const inputContext = useContext(InputContext); + + return ( + <label + htmlFor={id || inputContext?.id} + css={ + order + ? css` + display: flex; + align-items: center; + gap: 0.8rem; + ` + : null + } + > + {order && ( + <div + css={[ + css` + background-color: #212529; // FIXME: 디자인 토큰 적용하기 + width: 2rem; + height: 2rem; + border-radius: 0.4rem; + vertical-align: middle; + text-align: center; + `, + styles, + ]} + > + {/* FIXME text 컴포넌트 적용하기 */} + <span + css={css` + color: #fff; + font-size: 1rem; + font-weight: 600; + `} + > + {order} + </span> + </div> + )} + <span + css={css` + font-size: 1.4rem; + font-weight: 600; + `} + > + {children} + </span> + </label> + ); +} diff --git a/src/component/common/input/index.ts b/src/component/common/input/index.ts new file mode 100644 index 00000000..836ad51f --- /dev/null +++ b/src/component/common/input/index.ts @@ -0,0 +1,3 @@ +export { Input } from "./Input"; +export { Label } from "./Label"; +export { InputLabelContainer } from "./InputLabelContainer"; diff --git a/src/component/common/radioButton/Radio.tsx b/src/component/common/radioButton/Radio.tsx new file mode 100644 index 00000000..6c668d62 --- /dev/null +++ b/src/component/common/radioButton/Radio.tsx @@ -0,0 +1,45 @@ +import { css } from "@emotion/react"; +import { useContext } from "react"; + +import { RadioContext } from "./RadioButtonGroup"; + +import ListItemCard from "@/component/common/Card/ListItemCard"; + +type RadioProps = { + value: string; + children: React.ReactNode; +}; + +export function Radio({ value, children }: RadioProps) { + const radioContext = useContext(RadioContext); + return ( + <ListItemCard variant={radioContext?.isChecked(value) ? "theme" : "default"}> + <label + htmlFor={value} + css={css` + font-weight: 600; + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + cursor: pointer; + `} + > + {children} + </label> + <input + type="radio" + name={radioContext?.radioName} + id={value} + value={value} + onChange={(e) => { + radioContext?.onChange && radioContext.onChange(e.target.value); + }} + css={css` + display: none; + `} + /> + </ListItemCard> + ); +} diff --git a/src/component/common/radioButton/RadioButtonGroup.tsx b/src/component/common/radioButton/RadioButtonGroup.tsx new file mode 100644 index 00000000..1093bc5d --- /dev/null +++ b/src/component/common/radioButton/RadioButtonGroup.tsx @@ -0,0 +1,28 @@ +import { css } from "@emotion/react"; +import { createContext } from "react"; + +export type RadioContextState = { + radioName: string; + isChecked: (value: string) => boolean; + onChange: (value: string) => void; +}; + +export const RadioContext = createContext<RadioContextState | undefined>(undefined); + +type RadioButtonGroupProps = { + children: React.ReactNode; +} & RadioContextState; + +export function RadioButtonGroup({ children, ...props }: RadioButtonGroupProps) { + return ( + <div + css={css` + display: flex; + flex-direction: column; + gap: 1rem; + `} + > + <RadioContext.Provider value={props}>{children}</RadioContext.Provider> + </div> + ); +} diff --git a/src/component/common/radioButton/index.ts b/src/component/common/radioButton/index.ts new file mode 100644 index 00000000..054777c4 --- /dev/null +++ b/src/component/common/radioButton/index.ts @@ -0,0 +1,2 @@ +export { Radio } from "./Radio"; +export { RadioButtonGroup } from "./RadioButtonGroup"; diff --git a/tsconfig.json b/tsconfig.json index b165dbc9..317c408c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", + "forceConsistentCasingInFileNames": true, /* Linting */ "strict": true,