InputBoard 컴포넌트를 본격적으로 만들어 보았다. 작업은 다음과 같은 순서로 진행하였다.
1. 컴포넌트 스켈레톤 제작
2. 반응형 디자인 적용하기
3. 기본 기능 붙이기
디자인 시안은 다음과 같다.
필요한 엘리먼트를 나열해보면 헤딩, 날짜 input, 금액 input, 금액 증가 버튼, 코인 선택 input
과 submit버튼이 있다.
우선, 먼저 만들어 둔 코인 선택 드롭다운 컴포넌트와 디자인이 동일한 것을 활용하여, 모든 인풋들이 들어가는 자리에 드롭다운 컴포넌트를 넣어 시안에 맞게 배치시켰다.
// InputBoardPresenter.jsx
<S.BoardWrapper
col
ast
sb
bg={colors.gray9}
br={24}
pd="60px 40px 70px 40px"
>
<S.Form>
<S.BoardBody col ast full sb g={55}>
<SHeading2>
<SText mgb={7}> 내가 만약</SText>
<S.Br first />
<SText white mgb={7}>
{historyDate || "0000년 00월 00일"}
</SText>
에<S.Br second />
<SText white mgb={7}>
{selectMoney || "0"}
</SText>
원으로
<S.Br third />
<SText white>{selectedCoinId || "Bitcoin"}</SText>을 샀다면,
</SHeading2>
<S.InputArea col g={25} full>
<Dropdown options={dropdownCoinOptionList} />
<SDiv col act g={12}>
<Dropdown options={dropdownCoinOptionList} />
<S.IncreaseButtonListWrapper row sb full>
{INCREASE_MONEY_UNITS.map((unit) => (
<IncreaseMoneyButton key={unit} money={unit} />
))}
</S.IncreaseButtonListWrapper>
</SDiv>
<Dropdown options={dropdownCoinOptionList} />
</S.InputArea>
</S.BoardBody>
<S.SubmitArea ct>
<S.SubmitButton
full
h={64}
br={35}
white
type="submit"
onClick={handleClickSubmit}
>
<S.SubmitButtonText s1 black>
오늘 얼마가 되었을까?
</S.SubmitButtonText>
</S.SubmitButton>
</S.SubmitArea>
</S.Form>
</S.BoardWrapper>
비주얼은 아래와 같다.
우선적으로 배치가 잘 된 것을 알 수 있다.
이제 반응형 디자인을 적용시켜 보자.
다음은 반응형 디자인 시안이다.
모바일에서는 보드의 헤딩만 남고, 입력 폼 전체가 필터 버튼을 누르면 바텀시트처럼 올라오는 구조임을 알 수 있다.
인풋과 서브밋 버튼을 form
태그로 묶어 관리하고 있어서, 이 form
태그를 다음과 같이 기본적으로 모바일에서는 display: none
을 적용시켜 숨겨준다.
form {
width: 100%;
display: flex;
flex-direction: column;
gap: 189px;
@media only screen and (max-width: 768px) {
display: ${(props) => (props.isOpen ? "flex" : "none")};
position: fixed;
bottom: ${(props) => (props.isOpen ? "-556px" : "0")};
left: 0;
z-index: 10;
}
또한 styled component
를 사용하고 있으므로 props로 열린 상태에 따라 display
속성을 flex
로 바꿔준다. 위의 isOpen
props는 모바일 인풋보드의 필터 버튼을 누르면 true
로 바뀌게 되어있다.
결과는 다음과 같다.
위에 dropdown으로 모양을 잡았던 부분들을 전부 실제 인풋 타입에 맞는 input
태그들로 교체적용 시켜줘야 한다. 물론 디자인은 드롭다운의 디자인을 따라갔다.
먼저, 날짜를 선택하는 부분은 <input type="date"../>
, 금액은 <input type="number" ../>
로 교체해준다. 또한 금액 input의 경우 드롭다운 핸들 대신 화폐 단위 라벨이 있다. 이것 또한 반영해준다.
// InputBoardPresenter.jsx
<S.InputArea col g={25} full className="GI">
<DateInput isOpen={false} selectedDate="2022년 10월 12일" />
<SDiv col act g={12}>
<MoneyInput selectedMoney={"15000"} isOpen={false} />
<S.IncreaseButtonListWrapper row sb full>
{INCREASE_MONEY_UNITS.map((unit) => (
<IncreaseMoneyButton
key={unit}
money={unit}
onClick={() => {
handleClickIncreaseMoney(unit);
}}
/>
))}
</S.IncreaseButtonListWrapper>
</SDiv>
다음과 같이 input이 발생할 때마다 사용자의 입력값을 state에 반영시켜 준다.
MoneyInput (금액 입력창)을 예로 들면, 부모 컴포넌트는 inputMoney라는 state를 가지고 있다. 그리고 이 state를 바꿔주는 함수인 setInputMoney를 MoneyInput컴포넌트에 onChange라는 이름의 props로 넘겨주고, 다음과 같이 input태그의 onChange속성에 넣어준다.
const MoneyInput = ({..., onChange, ...}) => {
...
return (
<S.InputWrapper isOpen={isOpen}>
<SDiv row sb>
<S.Input
name="money"
type="number"
...
onChange={(e) => {
onChange(Number(e.target.value));
}}
...
/>
...
);
};
이런식으로 사용자의 입력값과 내부 state를 연동시켜주는 것을
controlled input
이라고 한다. 만약 controll되고 있지 않은 input이 있다면 리액트에서 다음과 같은 경고를 콘솔에 출력하기도 한다.
위와 같은 방법으로 DateInput과 CoinDropdown도 기본 기능을 붙여준다.
이제 남은것은 submit기능이다. 다만 이 기능은 api호출을 동반하고, 전역 상태를 구독하여 input과 전역 상태값들을 싱크해주는 작업이 필요하여, zustand 스토어를 만들고 컴포넌트에서 구독하게 하는 작업 관련 포스팅 때 다루어 보겠다.