Skip to content

Commit

Permalink
Feat: 회원 가입 플로우 퍼블리싱을 통한 공통 컴포넌트 생성 및 수정 (#35)
Browse files Browse the repository at this point in the history
* feat: 회원 가입 라우팅 설정

* feat: 공통 컴포넌트 반영

* feat:바텀 버튼 공통 컴포넌트 구현

* refactor: 마이페이지에 공통 버튼 컴포넌트 적용

* feat: Header 공통 컴포넌트 생성

* feat: 회원 가입 플로우에 헤더 적용

* chore: 오타 수정

* refactor: 선택지 컴포넌트 분리

* chore: 변수명 변경

* refactor: 마이페이지 공통 컴포넌트 적용 및 상태 리팩토링

* refactor: 공통 컴포넌트 지역 선택 드롭다운 리팩토링

* feat:사용자의  클릭 영억에 따른 기능 추가

* refacotr:중복되는 코드 단축

* feat:네비게이션 설정

* feat:여행자 유형 설정 상태 관리

* feat: 회원가입 플로우 여행자 유형 설정

* chore: 빼먹은 코드 추가 및 불필요한 코드 삭제

* design: 바텀 버튼 레이아웃 수정

* design: 디자인 요소 수정

* feat:context api 생성

* feat: 지역 상태 저장

* feat: 여행자 유형 상태 관리

* fix: 여행자 유형 상태 관리 수정

* feat: 지역 입력 추상화
  • Loading branch information
aazkgh committed Sep 16, 2024
1 parent ce21070 commit e34b364
Show file tree
Hide file tree
Showing 16 changed files with 611 additions and 347 deletions.
2 changes: 2 additions & 0 deletions src/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Settings from './components/Settings';
import DetailPage from './views/Detail/pages/DetailPage';
import ErrorReportPage from './views/ErrorReport/pages/ErrorReportPage';
import LoginCallBack from './views/Login/components/LoginCallBack';
import SignUpPage from './views/Login/pages/SignUpPage';
import MainPage from './views/Main/pages/MainPage';
import Mypage from './views/Mypage/pages/Mypage';
import SearchPage from './views/Search/pages/SearchPage';
Expand All @@ -16,6 +17,7 @@ const router = createBrowserRouter([
{ path: '/', element: <MainPage /> },
{ path: '/auth/callback', element: <LoginCallBack /> },
{ path: '/detail', element: <DetailPage /> },
{ path: '/sign-up', element: <SignUpPage /> },
],
},
{ path: '/detail', element: <DetailPage /> },
Expand Down
44 changes: 44 additions & 0 deletions src/components/BottomButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { css } from '@emotion/react';

import { COLORS, FONTS } from '@/styles/constants';

interface BottomButtonProps {
text: string;
clickedFn: () => void;
disabled?: boolean;
}

const BottomButton = ({ text, clickedFn, disabled }: BottomButtonProps) => {
return (
<div css={bottomBtnLayout}>
<button
type="submit"
onClick={clickedFn}
disabled={disabled}
css={bottomBtn(disabled)}>
{text}
</button>
</div>
);
};

export default BottomButton;

const bottomBtnLayout = css`
position: fixed;
bottom: 0;
width: 100%;
padding: 1.2rem 2rem;
`;

const bottomBtn = (disabled?: boolean) => css`
width: 100%;
padding: 1.7rem 0;
border-radius: 1.2rem;
background-color: ${disabled ? COLORS.gray1 : COLORS.brand1};
color: ${disabled ? COLORS.gray4 : COLORS.white};
${FONTS.Body2};
`;
55 changes: 55 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { css } from '@emotion/react';

import { COLORS, FONTS } from '@/styles/constants';

interface HeaderProps {
title?: string;
leftIcon?: React.FunctionComponent<
React.ComponentProps<'svg'> & { title?: string }
>;
leftFn?: () => void;
rightIcon?: React.FunctionComponent<
React.ComponentProps<'svg'> & { title?: string }
>;
rightFn?: () => void;
}

const Header = ({
title,
leftIcon: LeftIcon,
leftFn,
rightIcon: RightIcon,
rightFn,
}: HeaderProps) => {
return (
<header css={header}>
<div css={leftSide}>
{LeftIcon && <LeftIcon onClick={leftFn} />}
<span>{title}</span>
</div>
{RightIcon && <RightIcon onClick={rightFn} />}
</header>
);
};

export default Header;

const header = css`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 5rem;
padding: 1.2rem 2rem;
`;

const leftSide = css`
display: flex;
gap: 1rem;
align-items: center;
color: ${COLORS.brand1};
${FONTS.H4};
`;
198 changes: 139 additions & 59 deletions src/components/SelectRegion.tsx
Original file line number Diff line number Diff line change
@@ -1,74 +1,125 @@
import { css } from '@emotion/react';
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';

import { ArrowToggleClosed, ArrowToggleOpen } from '@/assets/icon';
import { REGION_LIST } from '@/constants/REGION_LIST';
import { COLORS, FONTS } from '@/styles/constants';

const SelectRegion = () => {
export type Region = {
city: string;
town: string;
};

interface SelectRegionProps {
region: Region;
setRegion: React.Dispatch<React.SetStateAction<Region>>;
}

const SelectRegion = ({ region, setRegion }: SelectRegionProps) => {
const [locationList, setLocationList] = useState<string[]>([]);
const [inputState, setInputState] = useState({ city: false, town: false });

const onChangeRegion = (selected: string) => {
const town = REGION_LIST.find((item) => item.city === selected)?.town || [];
setLocationList(town);
};

const onChangeInput = (input: string) => {
if (input === 'city') {
setInputState((prev) => {
const cityRef = useRef<HTMLDivElement>(null);
const townRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const handleFocus = (e: MouseEvent) => {
if (cityRef.current && !cityRef.current.contains(e.target as Node))
setInputState((prev) => {
return { city: false, town: prev.town };
});
if (townRef.current && !townRef.current.contains(e.target as Node))
setInputState((prev) => {
return { city: prev.city, town: false };
});
};

document.addEventListener('mouseup', handleFocus);

return () => {
document.removeEventListener('mouseup', handleFocus);
};
}, []);

const onClickDropDown = (inputType: 'city' | 'town', regionName: string) => {
if (inputType === 'city') {
setRegion(() => {
return {
...prev,
city: true,
city: regionName,
town: '',
};
});
} else if (input === 'town') {
setInputState((prev) => {

const town =
REGION_LIST.find((item) => item.city === regionName)?.town || [];
setLocationList(town);
} else if (inputType === 'town') {
setRegion((prev) => {
return {
...prev,
town: true,
town: regionName,
};
});
}
setInputState(() => {
return { city: false, town: false };
});
};

const renderDropdown = (items: string[], onClick: (item: string) => void) => (
<div css={scrollBox}>
<ul css={dropDownBox}>
{items.map((item) => (
<li key={item} onClick={() => onClick(item)}>
<button type="button">{item}</button>
</li>
))}
</ul>
</div>
);

return (
<li css={formItem}>
<span css={title}>지역*</span>

<div css={multiInputSection}>
<div css={region}>
<select
name="city"
defaultValue="default"
css={inputState.city ? selectTab : beforeSelectTab}
onChange={(e) => {
onChangeRegion(e.target.value);
onChangeInput('city');
<div data-type="city" ref={cityRef}>
<div
css={inputBox(!region.city)}
onClick={() => {
setInputState((prev) => {
return { city: !prev.city, town: false };
});
}}>
<option value="default" disabled>
지역
</option>
{REGION_LIST.map((item, idx) => (
<option value={item.city} key={idx}>
{item.city}
</option>
))}
</select>
<input type="button" value={region.city || '시'} />
{inputState.city ? <ArrowToggleOpen /> : <ArrowToggleClosed />}
</div>
{inputState.city &&
renderDropdown(
REGION_LIST.map((item) => item.city),
(item) => onClickDropDown('city', item),
)}
</div>

<div css={region}>
<select
name="town"
defaultValue="default"
css={inputState.town ? selectTab : beforeSelectTab}
onChange={() => onChangeInput('town')}>
<option value="default">시/군/구</option>
{locationList.map((item) => (
<option value={item} key={item}>
{item}
</option>
))}
</select>
<div data-type="town" ref={townRef}>
<div
css={inputBox(!region.town)}
onClick={() => {
setInputState((prev) => {
return { city: false, town: !prev.town };
});
}}>
<input type="button" value={region.town || '군/구'} />
{region.city && inputState.town ? (
<ArrowToggleOpen />
) : (
<ArrowToggleClosed />
)}
</div>
{region.city &&
inputState.town &&
renderDropdown(locationList, (item) =>
onClickDropDown('town', item),
)}
</div>
</div>
</li>
Expand All @@ -79,12 +130,20 @@ export default SelectRegion;

const formItem = css`
display: flex;
flex: 1;
gap: 1.2rem;
flex-direction: column;
width: 100%;
overflow-y: hidden;
${FONTS.Body2};
& input {
border: none;
}
`;

const title = css`
Expand All @@ -93,29 +152,50 @@ const title = css`

const multiInputSection = css`
display: flex;
gap: 1.2rem;
justify-content: space-between;
overflow-y: hidden;
& > div {
display: flex;
gap: 0.8rem;
flex-direction: column;
flex: 1;
}
`;

const inputDefault = css`
const inputBox = (initial: boolean) => css`
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.6rem;
border: 1px solid ${COLORS.gray3};
border-radius: 1rem;
color: ${COLORS.gray9};
color: ${initial ? COLORS.gray4 : COLORS.gray9};
`;

const region = css`
${inputDefault};
width: 48%;
`;
const scrollBox = css`
border: 1px solid ${COLORS.gray3};
border-radius: 1rem;
flex: 1;
const selectTab = css`
width: 100%;
border: none;
outline: none;
overflow-y: scroll;
`;

const beforeSelectTab = css`
${selectTab};
color: ${COLORS.gray4};
const dropDownBox = css`
display: flex;
gap: 0.8rem;
flex-direction: column;
padding: 1.6rem;
color: ${COLORS.gray9};
& > li {
padding: 0.6rem;
}
`;
Loading

0 comments on commit e34b364

Please sign in to comment.