diff --git a/src/components/modal/mute/modal.jsx b/src/components/modal/mute/modal.jsx new file mode 100644 index 000000000..887abcebb --- /dev/null +++ b/src/components/modal/mute/modal.jsx @@ -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 ( + +
+ + + +

+ 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: +

+
+ +

+ Once X minutes have passed, you will be able to comment again. +

+

+ If you would like more information, you can read the Scratch community guidelines. +

+
+
+ + + {this.state.step > 0 ? ( + + ) : null } + {this.state.step >= 1 ? ( + + ) : ( + + )} + +
+ + ); + } +} + +MuteModal.propTypes = { + onRequestClose: PropTypes.func +}; + +module.exports = MuteModal; diff --git a/src/components/modal/mute/modal.scss b/src/components/modal/mute/modal.scss new file mode 100644 index 000000000..0ceabd82c --- /dev/null +++ b/src/components/modal/mute/modal.scss @@ -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; + } +} diff --git a/src/components/modal/mute/mute-step.jsx b/src/components/modal/mute/mute-step.jsx new file mode 100644 index 000000000..fe7c75fed --- /dev/null +++ b/src/components/modal/mute/mute-step.jsx @@ -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 +}) => ( +
+ {sideImg && + +
+ +
+
+ } + + + {header} + + + {children} + + + {bottomImg && + + } + + +
+); + +MuteStep.propTypes = { + bottomImg: PropTypes.string, + bottomImgClass: PropTypes.string, + children: PropTypes.node, + header: PropTypes.string, + sideImg: PropTypes.string, + sideImgClass: PropTypes.string +}; + +module.exports = MuteStep; diff --git a/test/unit/components/mute-modal.test.jsx b/test/unit/components/mute-modal.test.jsx new file mode 100644 index 000000000..f7a1e7e63 --- /dev/null +++ b/test/unit/components/mute-modal.test.jsx @@ -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( + + ); + expect(component.find('div.mute-modal-header').exists()).toEqual(true); + + }); + + test('Mute Modal only shows next button on initial step', () => { + const component = mountWithIntl( + + ); + 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( + + ); + // 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( + + ); + 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( + + ); + 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( + + ); + 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( + + ); + component.instance().setState({step: 0}); + component.instance().handlePrevious(); + expect(component.instance().state.step).toBe(0); + }); +}); diff --git a/test/unit/components/mute-step.test.jsx b/test/unit/components/mute-step.test.jsx new file mode 100644 index 000000000..1d77e5d7e --- /dev/null +++ b/test/unit/components/mute-step.test.jsx @@ -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( + + ); + 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( + + ); + 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( + + ); + 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); + }); +});