Unified header menus with components
This commit is contained in:
@@ -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));
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
66
src/components/DropdownMenu/DropdownMenu.js
Normal file
66
src/components/DropdownMenu/DropdownMenu.js
Normal 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,
|
||||
}
|
||||
40
src/components/DropdownMenu/DropdownMenu.scss
Normal file
40
src/components/DropdownMenu/DropdownMenu.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
5
src/components/DropdownMenu/index.js
Normal file
5
src/components/DropdownMenu/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import DropdownMenu, { MenuItem } from './DropdownMenu';
|
||||
export default DropdownMenu;
|
||||
export {
|
||||
MenuItem,
|
||||
};
|
||||
@@ -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);
|
||||
@@ -43,7 +43,3 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.actions a {
|
||||
text-decoration: none;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
import HeaderMenu from './HeaderMenu';
|
||||
export default HeaderMenu;
|
||||
@@ -1,8 +1,10 @@
|
||||
import Layout from './Layout';
|
||||
import Title from './components/Title';
|
||||
import HeaderLink from './components/HeaderLink';
|
||||
|
||||
export default Layout;
|
||||
|
||||
export {
|
||||
Title,
|
||||
HeaderLink,
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
Reference in New Issue
Block a user