7-2. 유저 메뉴 만들기
이제 로그인 상태에서 썸네일을 클릭 하였을 때 보여줄 유저메뉴를 만들겠습니다.
유저메뉴 관련 컴포넌트는 src/components/Base/UserMenu 디렉토리에 저장하겠습니다.
UserMenu 컴포넌트 만들기
src/components/Base/UserMenu/UserMenu.js
import React from 'react';
import styled from 'styled-components';
// 유저 메뉴를 우측 상단에 위치시킵니다
const Positioner = styled.div`
position: absolute;
right: 0px;
top: 55px;
`;
// 흰색 메뉴박스
const MenuWrapper = styled.div`
background: white;
min-width: 140px;
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
`;
const UserMenu = ({children}) => (
<Positioner>
<MenuWrapper>
{children}
</MenuWrapper>
</Positioner>
);
export default UserMenu;
UserMenuItem 컴포넌트 만들기
유저메뉴 안에 들어갈 각 메뉴 항목을 위한 컴포넌트인 UserMenuItem 을 만들겠습니다.
src/components/Base/UserMenu/UserMenuItem.js
import React from 'react';
import styled from 'styled-components';
import oc from 'open-color';
const MenuItem = styled.div`
& + & {
border-top: 1px solid ${oc.gray[3]};
}
padding-left: 1rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: ${oc.gray[7]};
cursor: pointer;
&:hover {
background: ${oc.gray[0]};
font-weight: 500;
color: ${oc.cyan[6]};
}
`;
const UserMenuItem = ({onClick, children}) => (
<MenuItem onClick={onClick}>
{children}
</MenuItem>
);
export default UserMenuItem;
Username 컴포넌트 만들기
메뉴에서 최상단에 위치 할, 유저명을 보여주는 컴포넌트를 만들겠습니다.
src/components/Base/UserMenu/Username.js
import React from 'react';
import styled from 'styled-components';
import oc from 'open-color';
const Wrapper = styled.div`
background: ${oc.gray[1]};
border-bottom: 1px solid ${oc.gray[3]};
padding-right: 1rem;
padding-left: 1rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: ${oc.gray[9]};
font-weight: 500;
font-size: 0.9rem;
`;
const Username = ({username}) => (
<Wrapper>
@{username}
</Wrapper>
);
export default Username;
유저 메뉴에서 사용하는 세가지 컴포넌트를 만들었습니다. 이제 이 컴포넌트들의 인덱스를 만드세요.
src/components/Base/UserMenu/index.js
export { default } from './UserMenu';
export { default as UserMenuItem } from './UserMenuItem';
export { default as Username } from './Username'
유저메뉴 관련 리덕스 액션 작성
우리가 유저 메뉴를 화면에 보여주기 전에, 이 때 필요하게 될 리덕스 액션 코드를 base 모듈에 작성하겠습니다.
src/redux/modules/base
import { Map } from 'immutable';
import { handleActions, createAction } from 'redux-actions';
const SET_HEADER_VISIBILITY = 'base/SET_HEADER_VISIBILITY'; // 헤더 렌더링 여부 설정
const SET_USER_MENU_VISIBILITY = 'base/SET_USER_MENU_VISIBILITY'; // 유저메뉴 렌더링 여부 설정
export const setHeaderVisibility = createAction(SET_HEADER_VISIBILITY); // visible
export const setUserMenuVisibility = createAction(SET_USER_MENU_VISIBILITY); // visible
const initialState = Map({
header: Map({
visible: true
}),
userMenu: Map({
visible: false
})
});
export default handleActions({
[SET_HEADER_VISIBILITY]: (state, action) => state.setIn(['header', 'visible'], action.payload),
[SET_USER_MENU_VISIBILITY]: (state, action) => state.setIn(['userMenu', 'visible'], action.payload)
}, initialState);
onClickOutside 설치
유저메뉴는 열린 다음에 바깥을 클릭하면 사라집니다. 이 기능을 구현 하기 위하여, react-onclickoutside
라이브러리를 사용하겠습니다.
$ yarn add react-onclickoutside
UserMenuContainer 만들기
그럼 이제, 준비가 다 되었으니 리덕스에 상태가 연결 된 UserMenuContainer 를 만들어보세요.
src/containers/Base/UserMenuContainer.js
import React, { Component } from 'react';
import UserMenu, { UserMenuItem, Username } from 'components/Base/UserMenu';
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
import * as baseActions from 'redux/modules/base';
import * as userActions from 'redux/modules/user';
import storage from 'lib/storage';
import onClickOutside from 'react-onclickoutside';
class UserMenuContainer extends Component {
handleClickOutside = (e) => {
const { BaseActions } = this.props;
BaseActions.setUserMenuVisibility(false);
}
handleLogout = async () => {
const { UserActions } = this.props;
try {
await UserActions.logout();
} catch (e) {
console.log(e);
}
storage.remove('loggedInfo');
window.location.href = '/';
}
render() {
const { visible, username } = this.props;
const { handleLogout } = this;
if(!visible) {
return null;
}
return (
<UserMenu>
<Username username={username}/>
<UserMenuItem>나의 흐름</UserMenuItem>
<UserMenuItem>설정</UserMenuItem>
<UserMenuItem onClick={handleLogout}>로그아웃</UserMenuItem>
</UserMenu>
);
}
}
export default connect(
(state) => ({
visible: state.base.getIn(['userMenu', 'visible']),
username: state.user.getIn(['loggedInfo', 'username'])
}),
(dispatch) => ({
BaseActions: bindActionCreators(baseActions, dispatch),
UserActions: bindActionCreators(userActions, dispatch)
})
)(onClickOutside(UserMenuContainer));
컴포넌트 하단에서는, 컴포넌트를 먼저 onClickOutside 를 통하여 감싸줌으로써 컴포넌트의 바깥이 클릭되면 handleClickOutside 가 호출 되도록 설정하였습니다. 그리고, 그 컴포넌트를 connect 로 다시 감싸서 리덕스에 연결하였습니다.
컴포넌트의 바깥이 클릭되면, setMenuVisibility 를 통하여 유저메뉴를 숨겨주며, 메뉴 아이템들 중에서는 로그아웃 기능을 handleLogout 을 통하여 구현을 했고 나머지는 나중에 구현됩니다.
유저 썸네일 클릭 시 유저 메뉴 보여주기
이제 유저 메뉴를 다 만들었습니다. 썸네일 클릭 시 유저메뉴를 렌더링하도록 설정하세요.
src/containers/Base/HeaderContainer.js
import React, { Component } from 'react';
import Header, { LoginButton, UserThumbnail } from 'components/Base/Header';
import { connect } from 'react-redux';
import * as userActions from 'redux/modules/user';
import * as baseActions from 'redux/modules/base';
import { bindActionCreators } from 'redux';
import UserMenuContainer from './UserMenuContainer';
class HeaderContainer extends Component {
handleThumbnailClick = () => {
const { BaseActions } = this.props;
BaseActions.setUserMenuVisibility(true);
}
render() {
const { visible, user } = this.props;
const { handleThumbnailClick } = this;
if(!visible) return null;
return (
<Header>
{ user.get('logged')
? (<UserThumbnail thumbnail={user.getIn(['loggedInfo', 'thumbnail'])} onClick={handleThumbnailClick}/>)
: <LoginButton/>
}
<UserMenuContainer eventTypes="click"/>
</Header>
);
}
}
export default connect(
(state) => ({
visible: state.base.getIn(['header', 'visible']),
user: state.user
}),
(dispatch) => ({
UserActions: bindActionCreators(userActions, dispatch),
BaseActions: bindActionCreators(baseActions, dispatch)
})
)(HeaderContainer);
여기까지 작업을 하고 나면, 화면이 데스크탑 크기라면 유저 메뉴가 썸네일 하단이 아닌 헤더 하단 최 우측에 보여지게 됩니다.
Header 컴포넌트의 HeaderContents 에 position: relative;
값을 설정하여 유저메뉴 right: 0
속성이 HeaderContents 크기에 기반하여 적용되도록 설정하세요.
src/components/Header/Header.js
- HeaderContents
const HeaderContents = styled.div`
width: 1200px;
height: 55px;
display: flex;
flex-direction: row;
align-items: center;
position: relative;
padding-right: 1rem;
padding-left: 1rem;
${media.wide`
width: 992px;
`}
${media.tablet`
width: 100%;
`}
`;
이제 썸네일을 클릭하면 유저메뉴가 보여지게 됩니다. 방금 구현 한 로그아웃도 잘되는지 테스팅을 해보세요
헤더 로고 링크로 만들기
헤더의 로고를 클릭하면 홈 화면으로 이동하도록 설정을 해보겠습니다. styled()
를 통하여 스타일링된 링크를 만들고 이를 렌더링 할 때 to
값을 설정하여 이동할 경로를 / 로 정해주세요
src/components/Base/Header/Header.js
import { Link } from 'react-router-dom';
(...)
const Logo = styled(Link)`
font-size: 1.4rem;
letter-spacing: 2px;
color: ${oc.teal[7]};
font-family: 'Rajdhani';
text-decoration: none;
`;
(...)
const Header = ({children}) => {
return (
<Positioner>
<WhiteBackground>
<HeaderContents>
<Logo to="/">HEURM</Logo>
(...)