Unified header menus with components

This commit is contained in:
Jori Lallo
2016-05-30 11:36:43 -07:00
parent 3714e1fd7c
commit b6ab92dbb1
12 changed files with 193 additions and 131 deletions

View File

@@ -5,6 +5,8 @@ import { createAction } from 'redux-actions';
export const resetDocument = createAction('RESET_DOCUMENT');
// GET
export const FETCH_DOCUMENT_PENDING = 'FETCH_DOCUMENT_PENDING';
export const FETCH_DOCUMENT_SUCCESS = 'FETCH_DOCUMENT_SUCCESS';
export const FETCH_DOCUMENT_FAILURE = 'FETCH_DOCUMENT_FAILURE';
@@ -29,6 +31,8 @@ export function fetchDocumentAsync(documentId) {
};
};
// POST/UPDATE
export const SAVE_DOCUMENT_PENDING = 'SAVE_DOCUMENT_PENDING';
export const SAVE_DOCUMENT_SUCCESS = 'SAVE_DOCUMENT_SUCCESS';
export const SAVE_DOCUMENT_FAILURE = 'SAVE_DOCUMENT_FAILURE';
@@ -65,3 +69,23 @@ export function saveDocumentAsync(atlasId, documentId, title, text) {
};
};
// documents.delete
export const deleteDocumentPending = createAction('DELETE_DOCUMENT_PENDING');
export const deleteDocumentSuccess = createAction('DELETE_DOCUMENT_SUCCESS');
export const deleteDocumentFailure = createAction('DELETE_DOCUMENT_FAILURE');
export const deleteDocument = (documentId, returnPath) => {
return (dispatch) => {
dispatch(deleteDocumentPending());
client.post('/documents.delete', { id: documentId })
.then(data => {
dispatch(deleteDocumentSuccess(documentId));
dispatch(replace(returnPath));
})
.catch((err) => {
dispatch(deleteDocumentFailure(err));
})
};
};

View File

@@ -0,0 +1,66 @@
import React from 'react';
import styles from './DropdownMenu.scss';
const MenuItem = (props) => {
return (
<div
className={ styles.menuItem }
onClick={ props.onClick}
>{ props.children }</div>
);
};
MenuItem.propTypes = {
onClick: React.PropTypes.func,
children: React.PropTypes.node.isRequired,
};
//
class DropdownMenu extends React.Component {
static propTypes = {
label: React.PropTypes.string.isRequired,
children: React.PropTypes.node.isRequired,
}
state = {
menuVisible: false,
}
onMouseEnter = () => {
this.setState({ menuVisible: true });
}
onMouseLeave = () => {
this.setState({ menuVisible: false });
}
onClick = () => {
this.setState({ menuVisible: !this.state.menuVisible });
}
render() {
return (
<div
className={ styles.menuContainer }
onMouseEnter={ this.onMouseEnter }
onMouseLeave={ this.onMouseLeave }
>
<div className={ styles.label } onClick={ this.onClick }>
{ this.props.label }
</div>
{ this.state.menuVisible ? (
<div className={ styles.menu }>
{ this.props.children }
</div>
) : null }
</div>
);
}
};
export default DropdownMenu;
export {
MenuItem,
}

View File

@@ -0,0 +1,40 @@
@import '../../utils/constants.scss';
.label {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
min-height: 43px;
padding: 0 0.5rem;
color: $linkColor;
}
.menuContainer {
position: relative;
.menu {
position: absolute;
top: 42px;
right: 0;
z-index: 1000;
border: 1px solid #eee;
min-width: 150px;
padding: 5px 0;
}
}
.menuItem {
margin: 0;
padding: 5px 10px;
display: flex;
justify-content: flex-start;
align-items: center;
cursor: pointer;
a {
color: $textColor;
text-decoration: none;
}
}

View File

@@ -0,0 +1,5 @@
import DropdownMenu, { MenuItem } from './DropdownMenu';
export default DropdownMenu;
export {
MenuItem,
};

View File

@@ -1,10 +1,13 @@
import React from 'react';
import { connect } from 'react-redux';
import Link from 'react-router/lib/Link';
import { bindActionCreators } from 'redux';
import { logoutUser } from 'actions/UserActions';
import HeaderMenu from './components/HeaderMenu';
import DropdownMenu, { MenuItem } from 'components/DropdownMenu';
import Flex from 'components/Flex';
import LoadingIndicator from 'components/LoadingIndicator';
import { Avatar } from 'rebass';
import styles from './Layout.scss';
import classNames from 'classnames/bind';
@@ -18,6 +21,10 @@ class Layout extends React.Component {
loading: React.PropTypes.bool,
}
onLogout = () => {
this.props.logoutUser();
}
render() {
return (
<div className={ styles.container }>
@@ -35,11 +42,19 @@ class Layout extends React.Component {
<Flex align="center" className={ styles.actions }>
{ this.props.actions }
</Flex>
<HeaderMenu>
<img src={ this.props.avatarUrl } />
</HeaderMenu>
<DropdownMenu label={
<Avatar
circle
size={24}
src={ this.props.avatarUrl }
/>
}>
<MenuItem onClick={ this.onLogout }>Logout</MenuItem>
</DropdownMenu>
</Flex>
</div>
<div className={ styles.content }>
{ this.props.children }
</div>
@@ -55,6 +70,13 @@ const mapStateToProps = (state) => {
}
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
logoutUser,
}, dispatch)
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(Layout);

View File

@@ -43,7 +43,3 @@
justify-content: center;
}
.actions a {
text-decoration: none;
margin-right: 15px;
}

View File

@@ -1,67 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { logoutUser } from 'actions/UserActions';
import styles from './HeaderMenu.scss';
class HeaderMenu extends React.Component {
static propTypes = {
children: React.PropTypes.node.isRequired,
}
state = {
menuVisible: false,
}
onMouseEnter = () => {
this.setState({ menuVisible: true });
}
onMouseLeave = () => {
this.setState({ menuVisible: false });
}
onClick = () => {
this.setState({ menuVisible: !this.state.menuVisible });
}
logout = (event) => {
event.preventDefault();
this.props.logout();
}
render() {
return (
<div
className={ styles.menu }
onMouseEnter={ this.onMouseEnter }
onMouseLeave={ this.onMouseLeave }
>
<div className={ styles.content } onClick={ this.onClick }>
{ this.props.children }
</div>
{ this.state.menuVisible ? (
<div className={ styles.menu }>
<ul>
<li>
<a href='/' onClick={ this.logout }>Logout</a>
</li>
</ul>
</div>
) : null }
</div>
);
}
};
const mapDispatchToProps = (dispatch) => {
return {
logout: () => {
dispatch(logoutUser())
},
}
};
export default connect(null, mapDispatchToProps)(HeaderMenu);

View File

@@ -1,51 +0,0 @@
@import '../../../../utils/constants.scss';
.content {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
min-height: 43px;
min-width: 43px;
}
.menu {
position: relative;
img {
height: 24px;
width: 24px;
border-radius: 12px;
}
.menu {
position: absolute;
top: 42px;
right: 0;
z-index: 1000;
border: 1px solid #eee;
ul {
margin: 0;
padding: 5px 0;
font-size: 0.9em;
}
li {
margin: 0;
padding: 0;
list-style-type: none;
a {
display: flex;
margin: 0;
padding: 5px 10px;
min-width: 150px;
color: $textColor;
text-decoration: none;
}
}
}
}

View File

@@ -1,2 +0,0 @@
import HeaderMenu from './HeaderMenu';
export default HeaderMenu;

View File

@@ -1,8 +1,10 @@
import Layout from './Layout';
import Title from './components/Title';
import HeaderLink from './components/HeaderLink';
export default Layout;
export {
Title,
HeaderLink,
};

View File

@@ -2,12 +2,16 @@ import React from 'react';
import { connect } from 'react-redux';
import Link from 'react-router/lib/Link';
import { bindActionCreators } from 'redux';
import { fetchDocumentAsync } from 'actions/DocumentActions';
import {
fetchDocumentAsync,
deleteDocument,
} from 'actions/DocumentActions';
import Layout from 'components/Layout';
import Layout, { HeaderLink } from 'components/Layout';
import AtlasPreviewLoading from 'components/AtlasPreviewLoading';
import CenteredContent from 'components/CenteredContent';
import Document from 'components/Document';
import DropdownMenu, { MenuItem } from 'components/DropdownMenu';
import styles from './DocumentScene.scss';
@@ -35,12 +39,30 @@ class DocumentScene extends React.Component {
}
}
onDelete = () => {
if (confirm("Are you sure you want to delete this document?")) {
this.props.deleteDocument(
this.props.document.id,
`/atlas/${ this.props.document.atlas.id }`,
);
};
}
render() {
const document = this.props.document;
let title;
let actions;
if (document) {
actions = <Link to={ `/documents/${document.id}/edit` }>Edit</Link>;
actions = (
<div className={ styles.actions }>
<HeaderLink>
<Link to={ `/documents/${document.id}/edit` }>Edit</Link>
</HeaderLink>
<DropdownMenu label="More">
<MenuItem onClick={ this.onDelete }>Delete</MenuItem>
</DropdownMenu>
</div>
);
title = `${document.atlas.name} - ${document.title}`;
}
@@ -72,6 +94,7 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
fetchDocumentAsync,
deleteDocument,
}, dispatch)
}

View File

@@ -0,0 +1,4 @@
.actions {
display: flex;
flex-direction: row;
}