기상 시간을 선택하면 예상 수면 시간을 계산해주는 기능을 구현하는 중에, 셀렉트 박스를 만들어야 했는데 라이브러리를 쓰면서는 디자인 시안과 완전히 똑같게 구현할 수가 없을 것 같아 걍 직접 만들었다.
import { useState, useContext, useEffect, useRef } from "react";
import {
DispatchContext,
WakeUpTimeContext,
} from "../contexts/wakeupTimeReducer.context";
import { hours, minutes } from "../utils/select-options";
import { ReactComponent as Expand } from "../svg/Expand_Down.svg";
import {
ContentsBox,
SelectArea,
SelectBox,
SelectOptions,
Wrapper,
Option,
ButtonBox,
MDbox,
} from "../styles/wakeupTime.component.styles";
const WakeupTime = () => {
const hourSelectRef = useRef() as React.MutableRefObject<HTMLLIElement>;
const minuteSelectRef = useRef() as React.MutableRefObject<HTMLLIElement>;
const { hour, minute, md } = useContext(WakeUpTimeContext);
const dispatch = useContext(DispatchContext);
if (!dispatch) throw new Error("dispatch is null");
const [currentHour, setCurrentHour] = useState(`${hour}시`);
const [currentMinute, setCurrentMinute] = useState(
`${minute.toString().padStart(2, "0")}분`
);
const [selected, setSelected] = useState({
selectHour: hour,
selectMinute: minute,
selectMd: md,
});
useEffect(() => {
dispatch({ type: "update", payload: selected });
}, [selected]);
const [showHours, setShowHours] = useState(false);
const [showMinutes, setShowMinutes] = useState(false);
const handleOnClick = (e: any) => {
const { innerText, id } = e.target;
if (id === "hour") {
setCurrentHour(innerText);
setSelected({ ...selected, selectHour: innerText });
} else {
setCurrentMinute(innerText);
setSelected({ ...selected, selectMinute: innerText });
}
setShowHours(false);
setShowMinutes(false);
};
...
let [active, setActive] = useState(true);
if (md === "AM") {
active = true;
} else {
active = false;
}
return (
<ContentsBox>
<SelectArea>
<SelectBox ref={hourSelectRef}>
<Wrapper onClick={() => setShowHours((prev) => !prev)}>
<label>{currentHour}</label>
<Expand />
</Wrapper>
<SelectOptions show={showHours} height="12.3rem" >
{hours.map((item: number) => (
<Option
value={item}
key={item}
onClick={handleOnClick}
id="hour"
>
{`${item}시`}
</Option>
))}
</SelectOptions>
</SelectBox>
<SelectBox ref={minuteSelectRef}>
<Wrapper onClick={() => setShowMinutes((prev) => !prev)}>
<label>{currentMinute}</label>
<Expand />
</Wrapper>
<SelectOptions show={showMinutes}>
{minutes.map((item: number) => (
<Option
value={item}
key={item}
onClick={handleOnClick}
id="minute"
>
{`${item.toString().padStart(2, "0")}분`}
</Option>
))}
</SelectOptions>
</SelectBox>
</SelectArea>
<ButtonBox>
<MDbox
active={active}
className="am"
onClick={() => {
setSelected({ ...selected, selectMd: "AM" });
setActive((prev) => !prev);
}}
>
AM
</MDbox>
<MDbox
active={!active}
className="pm"
onClick={() => {
setSelected({ ...selected, selectMd: "PM" });
setActive((prev) => !prev);
}}
>
PM
</MDbox>
</ButtonBox>
</ContentsBox>
);
};
export default WakeupTime;
시간,분 각각의 셀렉트 박스가 필요해서 하나의 컴포넌트로 만들고 불러와서 렌더링 하고 싶었으나 Context 와 코드가 충돌해서 그냥 중복코드로 작성했다.
SelectBox 내부는 선택된 부분을 보여주는 Wrapper 와 드롭다운 옵션 박스인 SelectOptions로 나뉜다.
Wrapper 부분을 클릭하면 드롭다운이 열려야 하기 때문에 onClick함수를 넣었다.
SelectOption 안에는 시간과 분 배열이 맵핑되어 각각의 옵션으로 나열된다. 특정 옵션을 선택했을 때 Wrapper 부분의 현재 선택값이 바뀌어야 하기 때문에 onClick 함수가 들어있다.
0분을 00분으로 표시하기 위해 padStart 메소드를 추가했다.
AM, PM 버튼은 선택에 따라 색이 변화하며, useState로 불리언 상태를 관리해주었다.
👀 후에 중복되는 코드 최대한 없애며 리팩토링 예정