Move TopRightMenu component out of Comment and into own file

This commit is contained in:
Benjamin Goering
2017-04-10 16:13:53 -07:00
parent 4d2e5ed81f
commit 58ffd61884
4 changed files with 197 additions and 228 deletions
+1 -63
View File
@@ -16,71 +16,9 @@
.topRightMenu {
float: right;
text-align: right;
cursor: pointer;
}
.topRightMenu > * {
text-align: initial;
}
.topRightMenu .toggler {
cursor: pointer;
}
.topRightMenu .Menu {
background-color: white;
text-align: initial;
}
.Toggleable:focus {
outline: none;
}
.Menu {
border: 1px solid #ddd;
margin: 0;
}
ul.Menu {
list-style-type: none;
padding: 0;
}
.MenuItem {
cursor: pointer;
padding: 1em;
}
.IgnoreUserWizard {
background-color: #2E343B;
color: white;
padding: 1em;
max-width: 220px;
}
.IgnoreUserWizard header {
font-weight: bold;
}
.IgnoreUserWizard .textAlignRight {
text-align: right;
}
/**
* Up/Down Chevrons for the top right menu
*/
.chevron {
}
.chevron:before {
content: '⌃';
display: inline-block;
position: relative;
top: 0.25em;
}
/* Down Arrow */
.chevron.down:before {
display: inline-block;
position: relative;
transform: rotate(180deg);
top: 0;
/*top: -0.25em;*/
}
+1 -165
View File
@@ -11,7 +11,6 @@ import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton';
import AuthorName from 'coral-plugin-author-name/AuthorName';
import {Button} from 'coral-ui';
import TagLabel from 'coral-plugin-tag-label/TagLabel';
import Content from 'coral-plugin-commentcontent/CommentContent';
import PubDate from 'coral-plugin-pubdate/PubDate';
@@ -22,9 +21,9 @@ import {BestButton, IfUserCanModifyBest, BEST_TAG, commentIsBest, BestIndicator}
import LoadMore from 'coral-embed-stream/src/LoadMore';
import {Slot} from 'coral-framework';
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
import {TopRightMenu} from './TopRightMenu';
import styles from './Comment.css';
import classnames from 'classnames';
const getActionSummary = (type, comment) => comment.action_summaries
.filter((a) => a.__typename === type)[0];
@@ -152,126 +151,6 @@ class Comment extends React.Component {
tag: BEST_TAG,
}), () => 'Failed to remove best comment tag');
class IgnoreUserWizard extends React.Component {
static propTypes = {
// comment on which this menu appears
user: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
}).isRequired,
cancel: PropTypes.func.isRequired,
// actually submit the ignore. Provide {id: user id to ignore}
ignoreUser: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
this.state = {
// what step of the wizard is the user on
step: 1
};
this.onClickCancel = this.onClickCancel.bind(this);
}
onClickCancel() {
this.props.cancel();
}
render() {
const {user, ignoreUser} = this.props;
const goToStep = (stepNum) => this.setState({step: stepNum});
const step1 = (
<div>
<header>Ignore User</header>
<p>When you ignore a user, all comments they wrote on the site will be hidden from you. You can undo this later from the Profile tab.</p>
<div className={styles.textAlignRight}>
<Button cStyle='cancel' onClick={this.onClickCancel}>Cancel</Button>
<Button onClick={() => goToStep(2)}>Ignore user</Button>
</div>
</div>
);
const onClickIgnoreUser = async () => {
await ignoreUser({id: user.id});
};
const step2Confirmation = (
<div>
<header>Ignore User</header>
<p>Are you sure you want to ignore { user.name }?</p>
<div className={styles.textAlignRight}>
<Button cStyle='cancel' onClick={this.onClickCancel}>Cancel</Button>
<Button onClick={onClickIgnoreUser}>Ignore user</Button>
</div>
</div>
);
const elsForStep = [step1, step2Confirmation];
const {step} = this.state;
const elForThisStep = elsForStep[step - 1];
return (
<div className={styles.IgnoreUserWizard}>
{ elForThisStep }
</div>
);
}
}
// TopRightMenu appears as a dropdown in the top right of the comment.
// when you click the down cehvron, it expands and shows IgnoreUserWizard
// when you click 'cancel' in the wizard, it closes the menu
class TopRightMenu extends React.Component {
static propTypes = {
// comment on which this menu appears
comment: PropTypes.shape({
user: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
}).isRequired
}).isRequired,
ignoreUser: PropTypes.func,
// show notification to the user (e.g. for errors)
addNotification: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
this.state = {
timesReset: 0
};
}
render() {
const {comment, ignoreUser, addNotification} = this.props;
// timesReset is used as Toggleable key so it re-renders on reset (closing the toggleable)
const reset = () => this.setState({timesReset: this.state.timesReset + 1});
const ignoreUserAndCloseMenuAndNotifyOnError = async ({id}) => {
// close menu
reset();
// ignore user
let errorToThrow;
try {
await ignoreUser({id});
} catch (error) {
addNotification('error', 'Failed to ignore user');
errorToThrow = error;
}
throw errorToThrow;
};
return (
<Toggleable key={this.state.timesReset}>
<div style={{position: 'absolute', right: 0, zIndex: 1}}>
<IgnoreUserWizard
user={comment.user}
cancel={reset}
ignoreUser={ignoreUserAndCloseMenuAndNotifyOnError}
/>
</div>
</Toggleable>
);
}
}
return (
<div
className={commentClass}
@@ -409,47 +288,4 @@ class Comment extends React.Component {
}
}
// TODO (bengo): use arrows that match designs, probably with css borders http://stackoverflow.com/questions/15938933/creating-a-chevron-in-css
const upArrow = <span className={classnames(styles.chevron, styles.up)}></span>;
const downArrow = <span className={classnames(styles.chevron, styles.down)}></span>;
class Toggleable extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.close = this.close.bind(this);
this.state = {
isOpen: false
};
}
toggle() {
this.setState({isOpen: ! this.state.isOpen});
}
close() {
this.setState({isOpen: false});
}
render() {
const {children} = this.props;
const {isOpen} = this.state;
return (
// /*onBlur={ this.close } */
<span className={styles.Toggleable} tabIndex="0" >
<span className={styles.toggler}
onClick={this.toggle}>{isOpen ? upArrow : downArrow}</span>
{isOpen ? children : null}
</span>
);
}
}
const Menu = ({children}) => (
<ul className={styles.Menu}>
{ children }
</ul>
);
Menu.Item = ({children, onClick}) => (
<li className={styles.MenuItem} onClick={onClick}>
{ children }
</li>
);
export default Comment;
@@ -0,0 +1,39 @@
.Toggleable:focus {
outline: none;
}
.IgnoreUserWizard {
background-color: #2E343B;
color: white;
padding: 1em;
max-width: 220px;
}
.IgnoreUserWizard header {
font-weight: bold;
}
.IgnoreUserWizard .textAlignRight {
text-align: right;
}
/**
* Up/Down Chevrons for the top right menu
*/
.chevron {
}
.chevron:before {
content: '⌃';
display: inline-block;
position: relative;
top: 0.25em;
}
/* Down Arrow */
.chevron.down:before {
display: inline-block;
position: relative;
transform: rotate(180deg);
top: 0;
/*top: -0.25em;*/
}
@@ -0,0 +1,156 @@
import React, {PropTypes} from 'react';
import classnames from 'classnames';
import {Button} from 'coral-ui';
import styles from './TopRightMenu.css';
// TopRightMenu appears as a dropdown in the top right of the comment.
// when you click the down cehvron, it expands and shows IgnoreUserWizard
// when you click 'cancel' in the wizard, it closes the menu
export class TopRightMenu extends React.Component {
static propTypes = {
// comment on which this menu appears
comment: PropTypes.shape({
user: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
}).isRequired
}).isRequired,
ignoreUser: PropTypes.func,
// show notification to the user (e.g. for errors)
addNotification: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
this.state = {
timesReset: 0
};
}
render() {
const {comment, ignoreUser, addNotification} = this.props;
// timesReset is used as Toggleable key so it re-renders on reset (closing the toggleable)
const reset = () => this.setState({timesReset: this.state.timesReset + 1});
const ignoreUserAndCloseMenuAndNotifyOnError = async ({id}) => {
// close menu
reset();
// ignore user
try {
await ignoreUser({id});
} catch (error) {
addNotification('error', 'Failed to ignore user');
throw error;
}
};
return (
<Toggleable key={this.state.timesReset}>
<div style={{position: 'absolute', right: 0, zIndex: 1}}>
<IgnoreUserWizard
user={comment.user}
cancel={reset}
ignoreUser={ignoreUserAndCloseMenuAndNotifyOnError}
/>
</div>
</Toggleable>
);
}
}
class IgnoreUserWizard extends React.Component {
static propTypes = {
// comment on which this menu appears
user: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
}).isRequired,
cancel: PropTypes.func.isRequired,
// actually submit the ignore. Provide {id: user id to ignore}
ignoreUser: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
this.state = {
// what step of the wizard is the user on
step: 1
};
this.onClickCancel = this.onClickCancel.bind(this);
}
onClickCancel() {
this.props.cancel();
}
render() {
const {user, ignoreUser} = this.props;
const goToStep = (stepNum) => this.setState({step: stepNum});
const step1 = (
<div>
<header>Ignore User</header>
<p>When you ignore a user, all comments they wrote on the site will be hidden from you. You can undo this later from the Profile tab.</p>
<div className={styles.textAlignRight}>
<Button cStyle='cancel' onClick={this.onClickCancel}>Cancel</Button>
<Button onClick={() => goToStep(2)}>Ignore user</Button>
</div>
</div>
);
const onClickIgnoreUser = async () => {
await ignoreUser({id: user.id});
};
const step2Confirmation = (
<div>
<header>Ignore User</header>
<p>Are you sure you want to ignore { user.name }?</p>
<div className={styles.textAlignRight}>
<Button cStyle='cancel' onClick={this.onClickCancel}>Cancel</Button>
<Button onClick={onClickIgnoreUser}>Ignore user</Button>
</div>
</div>
);
const elsForStep = [step1, step2Confirmation];
const {step} = this.state;
const elForThisStep = elsForStep[step - 1];
return (
<div className={styles.IgnoreUserWizard}>
{ elForThisStep }
</div>
);
}
}
const upArrow = <span className={classnames(styles.chevron, styles.up)}></span>;
const downArrow = <span className={classnames(styles.chevron, styles.down)}></span>;
class Toggleable extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.close = this.close.bind(this);
this.state = {
isOpen: false
};
}
toggle() {
this.setState({isOpen: ! this.state.isOpen});
}
close() {
this.setState({isOpen: false});
}
render() {
const {children} = this.props;
const {isOpen} = this.state;
return (
// /*onBlur={ this.close } */
<span className={styles.Toggleable} tabIndex="0" >
<span className={styles.toggler}
onClick={this.toggle}>{isOpen ? upArrow : downArrow}</span>
{isOpen ? children : null}
</span>
);
}
}