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>
                    (...)

results matching ""

    No results matching ""