mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-27 09:35:56 -05:00
Merge pull request #4482 from picklesrus/m-modal-draft
Initial skeleton of mute modal.
This commit is contained in:
commit
966df020f2
5 changed files with 372 additions and 0 deletions
115
src/components/modal/mute/modal.jsx
Normal file
115
src/components/modal/mute/modal.jsx
Normal 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;
|
60
src/components/modal/mute/modal.scss
Normal file
60
src/components/modal/mute/modal.scss
Normal 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;
|
||||
}
|
||||
}
|
55
src/components/modal/mute/mute-step.jsx
Normal file
55
src/components/modal/mute/mute-step.jsx
Normal 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;
|
93
test/unit/components/mute-modal.test.jsx
Normal file
93
test/unit/components/mute-modal.test.jsx
Normal 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);
|
||||
});
|
||||
});
|
49
test/unit/components/mute-step.test.jsx
Normal file
49
test/unit/components/mute-step.test.jsx
Normal 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);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue