Merge pull request #4482 from picklesrus/m-modal-draft

Initial skeleton of mute modal.
This commit is contained in:
picklesrus 2020-10-13 09:02:47 -04:00 committed by GitHub
commit 966df020f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 372 additions and 0 deletions

View file

@ -0,0 +1,115 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const Modal = require('../base/modal.jsx');
const ModalInnerContent = require('../base/modal-inner-content.jsx');
const Button = require('../../forms/button.jsx');
const Progression = require('../../progression/progression.jsx');
const FlexRow = require('../../flex-row/flex-row.jsx');
const MuteStep = require('./mute-step.jsx');
const classNames = require('classnames');
require('./modal.scss');
class MuteModal extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleNext',
'handlePrevious'
]);
this.state = {
step: 0
};
}
handleNext () {
this.setState({
step: this.state.step + 1
});
}
handlePrevious () {
// This shouldn't get called when we're on the first step, but
// the Math.max is here as a safeguard so state doesn't go negative.
this.setState({
step: Math.max(0, this.state.step - 1)
});
}
render () {
return (
<Modal
isOpen
useStandardSizes
className="modal-mute"
showCloseButton={false}
onRequestClose={this.props.onRequestClose}
>
<div className="mute-modal-header modal-header" />
<ModalInnerContent className="mute-inner-content">
<Progression step={this.state.step}>
<MuteStep
bottomImg="/images/bottom_placeholder.png"
bottomImgClass="bottom-img"
header="The Scratch comment filter thinks your comment was unconstructive."
>
<p>
If you think something could be better, you can say something you like about the project,
and make a suggestion about how to improve it. For example, you could say:
</p>
</MuteStep>
<MuteStep
header="For the next X minutes you won't be able to post comments"
sideImg="/images/side_placeholder.png"
sideImgClass="side-img"
>
<p>
Once X minutes have passed, you will be able to comment again.
</p>
<p>
If you would like more information, you can read the Scratch community guidelines.
</p>
</MuteStep>
</Progression>
<FlexRow className={classNames('nav-divider')} />
<FlexRow className={classNames('mute-nav')}>
{this.state.step > 0 ? (
<Button
className={classNames(
'back-button',
)}
onClick={this.handlePrevious}
>
<div className="action-button-text">
Back
</div>
</Button>
) : null }
{this.state.step >= 1 ? (
<Button
className={classNames('close-button')}
onClick={this.props.onRequestClose}
>
<div className="action-button-text">
Close
</div>
</Button>
) : (
<Button
className={classNames('next-button')}
onClick={this.handleNext}
>
<div className="action-button-text">
Next
</div>
</Button>
)}
</FlexRow>
</ModalInnerContent>
</Modal>
);
}
}
MuteModal.propTypes = {
onRequestClose: PropTypes.func
};
module.exports = MuteModal;

View file

@ -0,0 +1,60 @@
@import "../../../colors";
@import "../../../frameless";
.modal-mute {
width: 30rem;
.mute-modal-header {
box-shadow: inset 0 -1px 0 0 $ui-mint-green;
background-color: $ui-mint-green;
border-radius: 1rem 1rem 0 0;
}
.mute-step {
display: flex;
padding: 48px 16px;
}
.mute-content {
padding-top: 16px;
}
.mute-inner-content {
padding: 0 32px;
}
.left-column {
padding-right: 32px;
}
.mute-header {
font-size: 1.5rem;
font-weight: bold;
line-height: 2rem;
}
.mute-bottom-row {
padding-top: 32px;
}
.bottom-img {
width: 100%;
}
.mute-side-image {
margin-left: -49px;
}
.side-img {
height: 212px;
width: 129px;
}
.nav-divider {
border-top: 1px solid $ui-blue-25percent;
}
.mute-nav {
display:flex;
justify-content: space-between;
padding: 24px 0;
}
.back-button {
margin-top: 0;
margin-bottom: 0;
}
.next-button, .close-button {
margin-left: auto;
margin-top: 0;
margin-bottom: 0;
}
}

View file

@ -0,0 +1,55 @@
const PropTypes = require('prop-types');
const React = require('react');
const classNames = require('classnames');
const FlexRow = require('../../flex-row/flex-row.jsx');
require('./modal.scss');
const MuteStep = ({
bottomImg,
bottomImgClass,
children,
header,
sideImg,
sideImgClass
}) => (
<div className="mute-step">
{sideImg &&
<FlexRow className={classNames('left-column')}>
<div className={classNames('mute-side-image')}>
<img
className={sideImgClass}
src={sideImg}
/>
</div>
</FlexRow>
}
<FlexRow className={classNames('mute-right-column')}>
<FlexRow className={classNames('mute-header')}>
{header}
</FlexRow>
<FlexRow className={classNames('mute-content')}>
{children}
</FlexRow>
<FlexRow className={classNames('mute-bottom-row')}>
{bottomImg &&
<img
className={bottomImgClass}
src={bottomImg}
/>
}
</FlexRow>
</FlexRow>
</div>
);
MuteStep.propTypes = {
bottomImg: PropTypes.string,
bottomImgClass: PropTypes.string,
children: PropTypes.node,
header: PropTypes.string,
sideImg: PropTypes.string,
sideImgClass: PropTypes.string
};
module.exports = MuteStep;

View file

@ -0,0 +1,93 @@
import React from 'react';
import {shallowWithIntl} from '../../helpers/intl-helpers.jsx';
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
import MuteModal from '../../../src/components/modal/mute/modal';
import Modal from '../../../src/components/modal/base/modal';
describe('MuteModalTest', () => {
test('Mute Modal rendering', () => {
const component = shallowWithIntl(
<MuteModal />
);
expect(component.find('div.mute-modal-header').exists()).toEqual(true);
});
test('Mute Modal only shows next button on initial step', () => {
const component = mountWithIntl(
<MuteModal />
);
expect(component.find('div.mute-nav').exists()).toEqual(true);
expect(component.find('button.next-button').exists()).toEqual(true);
expect(component.find('button.next-button').getElements()[0].props.onClick)
.toEqual(component.instance().handleNext);
expect(component.find('button.close-button').exists()).toEqual(false);
expect(component.find('button.back-button').exists()).toEqual(false);
});
test('Mute Modal shows back & close button on last step', () => {
const component = mountWithIntl(
<MuteModal />
);
// Step 1 is the last step.
component.instance().setState({step: 1});
component.update();
expect(component.find('div.mute-nav').exists()).toEqual(true);
expect(component.find('button.next-button').exists()).toEqual(false);
expect(component.find('button.back-button').exists()).toEqual(true);
expect(component.find('button.back-button').getElements()[0].props.onClick)
.toEqual(component.instance().handlePrevious);
expect(component.find('button.close-button').exists()).toEqual(true);
expect(component.find('button.close-button').getElements()[0].props.onClick)
.toEqual(component.instance().props.onRequestClose);
});
test('Mute modal sends correct props to Modal', () => {
const closeFn = jest.fn();
const component = shallowWithIntl(
<MuteModal
onRequestClose={closeFn}
/>
);
const modal = component.find(Modal);
expect(modal).toHaveLength(1);
expect(modal.props().showCloseButton).toBe(false);
expect(modal.props().isOpen).toBe(true);
expect(modal.props().className).toBe('modal-mute');
expect(modal.props().onRequestClose).toBe(closeFn);
});
test('Mute modal handle next step', () => {
const closeFn = jest.fn();
const component = shallowWithIntl(
<MuteModal
onRequestClose={closeFn}
/>
);
expect(component.instance().state.step).toBe(0);
component.instance().handleNext();
expect(component.instance().state.step).toBe(1);
});
test('Mute modal handle previous step', () => {
const component = shallowWithIntl(
<MuteModal />
);
component.instance().setState({step: 1});
component.instance().handlePrevious();
expect(component.instance().state.step).toBe(0);
});
test('Mute modal handle previous step stops at 0', () => {
const component = shallowWithIntl(
<MuteModal />
);
component.instance().setState({step: 0});
component.instance().handlePrevious();
expect(component.instance().state.step).toBe(0);
});
});

View file

@ -0,0 +1,49 @@
import React from 'react';
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
import MuteStep from '../../../src/components/modal/mute/mute-step';
describe('MuteStepTest', () => {
test('Mute Step with no images ', () => {
const component = mountWithIntl(
<MuteStep
header="header text"
/>
);
expect(component.find('div.mute-step').exists()).toEqual(true);
expect(component.find('div.mute-header').exists()).toEqual(true);
expect(component.find('div.mute-right-column').exists()).toEqual(true);
// No images and no left column.
expect(component.find('img').exists()).toEqual(false);
expect(component.find('div.left-column').exists()).toEqual(false);
});
test('Mute Step with side image ', () => {
const component = mountWithIntl(
<MuteStep
sideImg="/path/to/img.png"
sideImgClass="side-img"
/>
);
expect(component.find('div.mute-step').exists()).toEqual(true);
expect(component.find('div.mute-header').exists()).toEqual(true);
expect(component.find('div.mute-right-column').exists()).toEqual(true);
expect(component.find('div.left-column').exists()).toEqual(true);
expect(component.find('img.side-img').exists()).toEqual(true);
});
test('Mute Step with bottom image ', () => {
const component = mountWithIntl(
<MuteStep
bottomImg="/path/to/img.png"
bottomImgClass="bottom-image"
/>
);
expect(component.find('div.mute-step').exists()).toEqual(true);
expect(component.find('div.mute-header').exists()).toEqual(true);
expect(component.find('div.mute-right-column').exists()).toEqual(true);
expect(component.find('img.bottom-image').exists()).toEqual(true);
});
});