Skip to content

Commit

Permalink
[FE] 인원 모달 구현 (#52)
Browse files Browse the repository at this point in the history
* feat: 인원 모달 UI 작업

- 인원 모달에 필요한 상태를 SearchBar에 context로 생성
- 인원 모달 UI 생성

Dae-Hwa/airbnb/#48

* feat: guest type에 따라 HeadCount 컴포넌트의 내용을 다르게 렌더링

- +,- 버튼을 컴포넌트로 분리
- Counter 컴포넌트 분리

Dae-Hwa/airbnb/#48

* refactor: HeadCount에서 객체 리터럴에 바로 접근할 수 있도록 타입 추가

Dae-Hwa/airbnb/#48

* feat: counter 기본 동작 구현

- 분리해둔 PlusButton, MinusButton에 onClick 이벤트를 넣으려니 또 타입을 선언해줘야 해서
임시로 svg 컴포넌트를 가져와서 onClick 이벤트 등록함
- useReducer 사용하지 않고 그냥 만들어본 버전
- 타입.. 아찔하다.

Dae-Hwa/airbnb/#48

* feat & fix: 인원모달 관련된 상태를 SearchBar 컴포넌트로 끌어올림, 게스트 타입 별 상태 조작 로직 추가, 타입 에러 해결

- 상태 끌어올리고 Counter 컴포넌트에서 useContext를 사용하려고 하니 타입에러가 나서 Counter 컴포넌트가 위치한 곳에서 커스텀훅을 사용하여 해결

Dae-Hwa/airbnb/#48

* refactor: Counter 컴포넌트에서 useContext 부분 타입 에러 다른 방법으로 해결

- HeadCountContextType 정의 부분에서 null 타입 추가
- setGuestCountState null 체크하는 코드 추가 (Counter.tsx 18라인)
- createContext 부분에서 초기값을 key별로 null 설정
- 커스텀훅 삭제

Dae-Hwa/airbnb/#48
  • Loading branch information
deprecated-hongbiii authored Jun 1, 2021
1 parent e734380 commit 1466f2f
Show file tree
Hide file tree
Showing 12 changed files with 214 additions and 27 deletions.
14 changes: 14 additions & 0 deletions FE/fe-airbnb/src/components/MinusButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import styled from 'styled-components';
import { ReactComponent as MinusIcon } from '../icon/minus-circle.svg';

function MinusButton() {
return (
<Button>
<MinusIcon />
</Button>
)
}

const Button = styled.button``

export default MinusButton
14 changes: 14 additions & 0 deletions FE/fe-airbnb/src/components/PlusButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import styled from 'styled-components';
import { ReactComponent as PlusIcon } from '../icon/plus-circle.svg';

function PlusButton() {
return (
<Button>
<PlusIcon />
</Button>
)
}

const Button = styled.button``

export default PlusButton
3 changes: 1 addition & 2 deletions FE/fe-airbnb/src/components/calendar/Day.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import styled from 'styled-components'
import moment, { Moment } from 'moment';

import { CalendarContext } from '@components/searchBar/SearchBar'
import { CalendarContextType } from '@components/searchBar/SearchBar'
import { DayContainerProps } from '@components/searchBar/searchBarTypes';
import { CalendarContextType, DayContainerProps } from '@components/searchBar/searchBarTypes';

type DayProps = {
day: Moment;
Expand Down
46 changes: 46 additions & 0 deletions FE/fe-airbnb/src/components/headcount/Counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useContext } from 'react';

import { Center, Flex } from '@chakra-ui/layout';
import styled from 'styled-components';

// import PlusButton from '@components/PlusButton';
// import MinusButton from '@components/MinusButton';
import { ReactComponent as PlusIcon } from '../../icon/plus-circle.svg';
import { ReactComponent as MinusIcon } from '../../icon/minus-circle.svg';
import { HandleCountType, HeadCountProps } from './HeadCountTypes';
import { HeadCountContext } from '@components/searchBar/SearchBar';
import { guestCountStateType, HeadCountContextType } from '@components/searchBar/searchBarTypes';

function Counter({ guestType }: HeadCountProps) {
const { guestCountState, setGuestCountState } = useContext<HeadCountContextType>(HeadCountContext);

const handleCount = ({ guestType, count }: HandleCountType) => {
if(setGuestCountState === null) throw Error('setGuestCountState가 null임!');
setGuestCountState((guestCountState: guestCountStateType) => {
const checkYoung = guestType === 'children' || guestType === 'infants';
const checkParents = guestCountState.adults === 0;
const result = { ...guestCountState, [guestType]: guestCountState[guestType] + count};
return (checkYoung && checkParents && count === 1) ? { ...result, adults: 1 } : result;
})
}

return (
<Flex align="center">
<MinusIcon onClick={() => handleCount({ guestType, count: -1 })}/>
<Count>
<Center>{guestCountState?.[guestType]}</Center>
</Count>
<PlusIcon onClick={() => handleCount({ guestType, count: 1 })}/>
</Flex>
)
}

const Count = styled.div`
margin: 0 16px;
width: 32px;
font-weight: bold;
font-size: ${({ theme }) => theme.fontSizes.L};
color: ${({ theme }) => theme.colors.gray1};
`

export default Counter
50 changes: 50 additions & 0 deletions FE/fe-airbnb/src/components/headcount/HeadCount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import styled from 'styled-components';

import Counter from '@components/headcount/Counter';
import { HeadCountProps } from './HeadCountTypes';

function HeadCount({ guestType }: HeadCountProps) {
const guestTypeTitle: string = {
adults: '성인',
children: '어린이',
infants: '유아',
}[guestType];

const guestTypeCaption: string = {
adults: '만 13세 이상',
children: '만 2~12세',
infants: '만 2세 미만',
}[guestType];

return (
<HeadCountContainer>
<GuestTypeContainer>
<GuestTypeTitle>{guestTypeTitle}</GuestTypeTitle>
<GuestTypeCaption>{guestTypeCaption}</GuestTypeCaption>
</GuestTypeContainer>
<Counter guestType={guestType}/>
</HeadCountContainer>
)
}

const HeadCountContainer = styled.li`
display: flex;
justify-content: space-between;
`

const GuestTypeContainer = styled.div`
width: 80px;
`

const GuestTypeTitle = styled.div`
font-size: ${({ theme }) => theme.fontSizes.SM};
font-weight: bold;
color: ${({ theme }) => theme.colors.black};
`

const GuestTypeCaption = styled.div`
font-size: ${({ theme }) => theme.fontSizes.S};
color: ${({ theme }) => theme.colors.gray3};
`

export default HeadCount
15 changes: 11 additions & 4 deletions FE/fe-airbnb/src/components/headcount/HeadCountModal.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import styled from 'styled-components';
import HeadCount from "@components/headcount/HeadCount"
import { GuestType } from './HeadCountTypes';

function HeadCountModal() {
const guestTypes: GuestType[] = ['adults', 'children', 'infants'];

return (
<HeadCountContainer>
인원 모달
</HeadCountContainer>
<HeadCountModalContainer>
{guestTypes.map((guestType, i) => <HeadCount guestType={guestType} key={i}/>)}
</HeadCountModalContainer>
);
}

const HeadCountContainer = styled.div`
const HeadCountModalContainer = styled.ul`
display: flex;
flex-direction: column;
gap: 42px;
width: 400px;
height: fit-content;
border-radius: ${({ theme }) => theme.borders.L};
Expand Down
14 changes: 14 additions & 0 deletions FE/fe-airbnb/src/components/headcount/HeadCountTypes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type PlusButtonProps = {
disabled: boolean;
};

export type HeadCountProps = {
guestType: GuestType;
}

export type GuestType = 'adults' | 'children' | 'infants';

export type HandleCountType = {
guestType: GuestType;
count: number;
}
37 changes: 24 additions & 13 deletions FE/fe-airbnb/src/components/searchBar/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, Dispatch, ReactElement, SetStateAction, useState } from 'react';
import { createContext, ReactElement, useState } from 'react';
import styled from 'styled-components';
import moment, { Moment } from 'moment';
import { Center, Flex } from '@chakra-ui/layout';
Expand All @@ -8,22 +8,16 @@ import HeadCountModal from '@components/headcount/HeadCountModal';
import PriceModal from '@components/price/PriceModal';
import SearchButton from '../SearchButton';
import SearchBarBtn from './SearchBarBtn';
import { SearchBarBtnType, SelectedContentProps } from './searchBarTypes';

export type CalendarContextType = {
calendars: Moment[];
setCalendars: Dispatch<SetStateAction<Moment[]>>;
checkInMoment: Moment | null;
setCheckInMoment: Dispatch<SetStateAction<Moment | null>>;
checkOutMoment: Moment | null;
setCheckOutMoment: Dispatch<SetStateAction<Moment | null>>;
}
import { CalendarContextType, HeadCountContextType, SearchBarBtnType, SelectedContentProps } from './searchBarTypes';

export const CalendarContext = createContext<CalendarContextType | null >(null);
export const HeadCountContext = createContext<HeadCountContextType>({guestCountState: null, setGuestCountState: null});

function SearchBar() {
const [selectedBtn, setSelectedBtn] = useState<string | null>(null);

// ============================ calendar 상태 ============================

const initialCalendars = [
moment().add(-1, 'M'),
moment(),
Expand All @@ -46,6 +40,17 @@ function SearchBar() {
},
};

// ============================ headcount 상태 ============================

const [guestCountState, setGuestCountState] = useState({ adults: 0, children: 0, infants: 0 });
const { adults, children, infants } = guestCountState;
const headCountState = {
values: {
guestCountState,
setGuestCountState
}
}

const renderModal = (): ReactElement | void => {
switch (selectedBtn) {
case SearchBarBtnType.CHECK_IN_OUT:
Expand All @@ -62,7 +67,9 @@ function SearchBar() {

case SearchBarBtnType.HEAD_COUNT:
return (
<HeadCountModal />
<HeadCountContext.Provider value={headCountState.values}>
<HeadCountModal />
</HeadCountContext.Provider>
);
}
}
Expand Down Expand Up @@ -108,7 +115,11 @@ function SearchBar() {
<SearchBarBtn onClick={() => handleClickSearchBarBtn(SearchBarBtnType.HEAD_COUNT)}>
<Flex direction="column">
<SearchBarSubTitle>인원</SearchBarSubTitle>
<SelectedContent contentType="placeholder">게스트 추가</SelectedContent>
{
guestCountState.adults || guestCountState.children || guestCountState.infants
? <SelectedContent contentType="guests">게스트 {adults + children}명, 유아 {infants}</SelectedContent>
: <SelectedContent contentType="placeholder">게스트 추가</SelectedContent>
}
</Flex>
</SearchBarBtn>

Expand Down
29 changes: 28 additions & 1 deletion FE/fe-airbnb/src/components/searchBar/searchBarTypes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Dispatch, SetStateAction } from 'react'
import { Moment } from 'moment';

export enum SearchBarBtnType {
CHECK_IN_OUT = 'check-in-out',
PRICE = 'price',
Expand All @@ -12,4 +15,28 @@ export type DayContainerProps = {
isSelected: boolean,
isBetween: boolean,
disabled: boolean
}
}

export type CalendarContextType = {
calendars: Moment[];
setCalendars: Dispatch<SetStateAction<Moment[]>>;
checkInMoment: Moment | null;
setCheckInMoment: Dispatch<SetStateAction<Moment | null>>;
checkOutMoment: Moment | null;
setCheckOutMoment: Dispatch<SetStateAction<Moment | null>>;
}

export type guestCountStateType = {
adults: number,
children: number,
infants: number
}

export type HeadCountContextType = {
guestCountState: guestCountStateType | null,
setGuestCountState: React.Dispatch<React.SetStateAction<{
adults: number;
children: number;
infants: number;
}>> | null
}
6 changes: 3 additions & 3 deletions FE/fe-airbnb/src/icon/minus-circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions FE/fe-airbnb/src/icon/plus-circle-disabled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions FE/fe-airbnb/src/icon/plus-circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 1466f2f

Please sign in to comment.