From 3be644e898ed85dae5f28cff68f1ad9e2c379eb4 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 9 Sep 2016 16:18:38 -0300 Subject: [PATCH 1/4] Ivan - Frontend - Create modals and add example to demo page [skip ci] --- client/src/actions/modal-actions.js | 16 ++++++ client/src/app-components/are-you-sure.js | 60 +++++++++++++++++++++ client/src/app-components/are-you-sure.scss | 30 +++++++++++ client/src/app/App.js | 4 ++ client/src/app/demo/components-demo-page.js | 23 +++++++- client/src/app/modal-container.js | 57 ++++++++++++++++++++ client/src/core-components/button.js | 26 +++++++-- client/src/core-components/button.scss | 37 ++++++++++--- client/src/core-components/checkbox.js | 2 +- client/src/core-components/input.scss | 2 +- client/src/core-components/modal.js | 19 +++++++ client/src/core-components/modal.scss | 18 +++++++ client/src/core-components/text-editor.js | 6 +-- client/src/data/languages/en.js | 5 +- client/src/main.scss | 1 + client/src/reducers/_reducers.js | 2 + client/src/reducers/modal-reducer.js | 36 +++++++++++++ 17 files changed, 325 insertions(+), 19 deletions(-) create mode 100644 client/src/actions/modal-actions.js create mode 100644 client/src/app-components/are-you-sure.js create mode 100644 client/src/app-components/are-you-sure.scss create mode 100644 client/src/app/modal-container.js create mode 100644 client/src/core-components/modal.js create mode 100644 client/src/core-components/modal.scss create mode 100644 client/src/reducers/modal-reducer.js diff --git a/client/src/actions/modal-actions.js b/client/src/actions/modal-actions.js new file mode 100644 index 00000000..06f6c939 --- /dev/null +++ b/client/src/actions/modal-actions.js @@ -0,0 +1,16 @@ + +export default { + openModal(content) { + return { + type: 'OPEN_MODAL', + payload: content + } + }, + + closeModal() { + return { + type: 'CLOSE_MODAL', + payload: {} + }; + } +}; \ No newline at end of file diff --git a/client/src/app-components/are-you-sure.js b/client/src/app-components/are-you-sure.js new file mode 100644 index 00000000..df76ee79 --- /dev/null +++ b/client/src/app-components/are-you-sure.js @@ -0,0 +1,60 @@ +import React from 'react'; + +import i18n from 'lib-app/i18n'; +import Button from 'core-components/button'; + +class AreYouSure extends React.Component { + static propTypes = { + description: React.PropTypes.string, + onYes: React.PropTypes.func + }; + + static contextTypes = { + closeModal: React.PropTypes.func + }; + + render() { + return ( +
+
+ {i18n('ARE_YOU_SURE')} +
+
+ {this.props.description} +
+
+
+ +
+
+ +
+
+
+ ); + } + + onYes() { + this.closeModal(); + + if (this.props.onYes) { + this.props.onYes(); + } + } + + onNo() { + this.closeModal(); + } + + closeModal() { + if (this.context.closeModal) { + this.context.closeModal(); + } + } +} + +export default AreYouSure; \ No newline at end of file diff --git a/client/src/app-components/are-you-sure.scss b/client/src/app-components/are-you-sure.scss new file mode 100644 index 00000000..921ed1c3 --- /dev/null +++ b/client/src/app-components/are-you-sure.scss @@ -0,0 +1,30 @@ +@import "../scss/vars"; + +.are-you-sure { + width: 400px; + text-align: center; + + &__header { + color: $primary-red; + font-size: $font-size--xl; + font-weight: bold; + margin-bottom: 20px; + + } + + &__description { + color: $dark-grey; + font-size: $font-size--md; + margin-bottom: 50px; + } + + &__butttons { + margin: 0 auto; + } + + &__yes-button, + &__no-button { + display: inline-block; + margin-right: 10px; + } +} \ No newline at end of file diff --git a/client/src/app/App.js b/client/src/app/App.js index d6639e06..e35a0be7 100644 --- a/client/src/app/App.js +++ b/client/src/app/App.js @@ -3,6 +3,8 @@ import _ from 'lodash'; import { connect } from 'react-redux' import { browserHistory } from 'react-router'; +import ModalContainer from 'app/modal-container'; + class App extends React.Component { static contextTypes = { router: React.PropTypes.object, @@ -21,6 +23,7 @@ class App extends React.Component { return (
{React.cloneElement(this.props.children, {})} +
); } @@ -49,6 +52,7 @@ class App extends React.Component { export default connect((store) => { return { config: store.config, + modal: store.modal, session: store.session, routing: store.routing }; diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 2d64b834..874edabb 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -3,6 +3,11 @@ const React = require('react'); const DocumentTitle = require('react-document-title'); +const ModalActions = require('actions/modal-actions'); +const store = require('app/store'); + +const AreYouSure = require('app-components/are-you-sure'); + const Button = require('core-components/button'); const Input = require('core-components/input'); const Checkbox = require('core-components/checkbox'); @@ -82,7 +87,23 @@ let DemoPage = React.createClass({ { title: 'Tooltip', render: ( - hola +
+ hola +
+ ) + }, + { + title: 'ModalTrigger', + render: ( + ) } ], diff --git a/client/src/app/modal-container.js b/client/src/app/modal-container.js new file mode 100644 index 00000000..5f6ca33c --- /dev/null +++ b/client/src/app/modal-container.js @@ -0,0 +1,57 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import keyCode from 'keycode'; + +import ModalActions from 'actions/modal-actions'; +import Modal from 'core-components/modal'; + +class ModalContainer extends React.Component { + + static childContextTypes = { + closeModal: React.PropTypes.func + }; + + getChildContext() { + return { + closeModal: this.closeModal.bind(this) + }; + }; + + componentDidMount() { + window.addEventListener('keydown', this.onKeyDown.bind(this)); + } + + componentWillUnMount() { + window.removeEventListener('keydown', this.onKeyDown.bind(this)); + } + + render() { + return ( +
+ {(this.props.modal.opened) ? this.renderModal() : null} +
+ ); + } + + renderModal() { + return ( + + ); + } + + onKeyDown(event) { + if (event.keyCode === keyCode('ESCAPE')) { + this.closeModal(); + } + } + + closeModal() { + this.props.dispatch(ModalActions.closeModal()); + } +} + +export default connect((store) => { + return { + modal: store.modal + }; +})(ModalContainer); \ No newline at end of file diff --git a/client/src/core-components/button.js b/client/src/core-components/button.js index 8ad69522..13dc4298 100644 --- a/client/src/core-components/button.js +++ b/client/src/core-components/button.js @@ -17,9 +17,16 @@ class Button extends React.Component { static propTypes = { children: React.PropTypes.node, + size: React.PropTypes.oneOf([ + 'small', + 'medium', + 'large', + 'auto' + ]), type: React.PropTypes.oneOf([ 'primary', - 'primary-icon', + 'secondary', + 'tertiary', 'clean', 'link' ]), @@ -32,7 +39,8 @@ class Button extends React.Component { }; static defaultProps = { - type: 'primary' + type: 'primary', + size: 'medium' }; render() { @@ -59,10 +67,20 @@ class Button extends React.Component { getClass() { let classes = { 'button': true, - 'button_disabled': this.props.disabled + 'button_disabled': this.props.disabled, + + 'button_primary': (this.props.type === 'primary'), + 'button_secondary': (this.props.type === 'secondary'), + 'button_tertiary': (this.props.type === 'tertiary'), + 'button_clean': (this.props.type === 'clean'), + 'button_link': (this.props.type === 'link'), + + 'button_small': (this.props.size === 'small'), + 'button_medium': (this.props.size === 'medium'), + 'button_large': (this.props.size === 'large'), + 'button_auto': (this.props.size === 'auto') }; - classes['button-' + this.props.type] = (this.props.type); classes[this.props.className] = (this.props.className); return classNames(classes); diff --git a/client/src/core-components/button.scss b/client/src/core-components/button.scss index 34c58bea..a446402e 100644 --- a/client/src/core-components/button.scss +++ b/client/src/core-components/button.scss @@ -2,28 +2,31 @@ .button { - &-primary, - &-primary-icon { + &_primary, + &_secondary, + &_tertiary { background-color: $primary-red; border: solid transparent; border-radius: 4px; color: white; height: 47px; text-transform: uppercase; - width: 239px; } - &-primary-icon { - width: initial; - height: initial; + &_secondary { + background-color: $primary-green; } - &-clean { + &_tertiary { + background-color: $secondary-blue; + } + + &_clean { background: none; border: none; } - &-link { + &_link { background: none; border: none; color: $dark-grey; @@ -37,4 +40,22 @@ &_disabled { background-color: #ec9696; } + + &_small { + width: 100px; + height: 47px; + } + + &_medium { + width: 239px; + } + + &_large { + //width: 239px; + } + + &_auto { + width: initial; + height: initial; + } } \ No newline at end of file diff --git a/client/src/core-components/checkbox.js b/client/src/core-components/checkbox.js index 28a48ffa..4c4bfe32 100644 --- a/client/src/core-components/checkbox.js +++ b/client/src/core-components/checkbox.js @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import _ from 'lodash'; -import keyCode from 'keycode'; +import keyCode from 'keycode'; import callback from 'lib-core/callback'; import getIcon from 'lib-core/get-icon'; diff --git a/client/src/core-components/input.scss b/client/src/core-components/input.scss index d3b88eee..2bcb1f54 100644 --- a/client/src/core-components/input.scss +++ b/client/src/core-components/input.scss @@ -44,7 +44,7 @@ } .input__text { - padding-left: 40px; + padding-left: 48px; } } diff --git a/client/src/core-components/modal.js b/client/src/core-components/modal.js new file mode 100644 index 00000000..612893f5 --- /dev/null +++ b/client/src/core-components/modal.js @@ -0,0 +1,19 @@ +import React from 'react'; + +class Modal extends React.Component { + static propTypes = { + content: React.PropTypes.node + }; + + render() { + return ( +
+
+ {this.props.content} +
+
+ ); + } +} + +export default Modal; \ No newline at end of file diff --git a/client/src/core-components/modal.scss b/client/src/core-components/modal.scss new file mode 100644 index 00000000..123bda25 --- /dev/null +++ b/client/src/core-components/modal.scss @@ -0,0 +1,18 @@ +.modal { + position: absolute; + top: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.8); + width: 100%; + height: 100%; + display: flex; + + &__content { + position: relative; + margin: auto; + background-color: white; + border-radius: 4px; + padding: 50px; + box-shadow: 0 0 11px white; + } +} \ No newline at end of file diff --git a/client/src/core-components/text-editor.js b/client/src/core-components/text-editor.js index d546d8a0..9c203463 100644 --- a/client/src/core-components/text-editor.js +++ b/client/src/core-components/text-editor.js @@ -51,9 +51,9 @@ class TextEditor extends React.Component { return (
-
) } diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 6efc2e4b..c4da923d 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -32,6 +32,8 @@ export default { 'NO_ATTACHMENT': 'No file attachment', 'STAFF': 'Staff', 'CUSTOMER': 'Customer', + 'YES': 'Yes', + 'CANCEL': 'Cancel', //ERRORS 'EMAIL_NOT_EXIST': 'Email does not exist', @@ -49,5 +51,6 @@ export default { 'SIGNUP_SUCCESS': 'You have registered successfully in our support system.', 'TICKET_SENT': 'Ticket has been created successfully.', 'VALID_RECOVER': 'Password recovered successfully', - 'EMAIL_EXISTS': 'Email already exists, please try to log in or recover password' + 'EMAIL_EXISTS': 'Email already exists, please try to log in or recover password', + 'ARE_YOU_SURE': 'Are you sure?' }; \ No newline at end of file diff --git a/client/src/main.scss b/client/src/main.scss index b7bd9324..9bc26c11 100644 --- a/client/src/main.scss +++ b/client/src/main.scss @@ -5,4 +5,5 @@ @import 'scss/font_awesome/font-awesome'; @import 'core-components/*'; +@import 'app-components/*'; @import 'app/*'; diff --git a/client/src/reducers/_reducers.js b/client/src/reducers/_reducers.js index b5745f14..b63ba602 100644 --- a/client/src/reducers/_reducers.js +++ b/client/src/reducers/_reducers.js @@ -3,9 +3,11 @@ import { routerReducer } from 'react-router-redux'; import sessionReducer from 'reducers/session-reducer'; import configReducer from 'reducers/config-reducer'; +import modalReducer from 'reducers/modal-reducer'; export default combineReducers({ session: sessionReducer, config: configReducer, + modal: modalReducer, routing: routerReducer }); \ No newline at end of file diff --git a/client/src/reducers/modal-reducer.js b/client/src/reducers/modal-reducer.js new file mode 100644 index 00000000..d49459e6 --- /dev/null +++ b/client/src/reducers/modal-reducer.js @@ -0,0 +1,36 @@ +import _ from 'lodash'; + +import Reducer from 'reducers/reducer'; + +class ModalReducer extends Reducer { + + getInitialState() { + return { + opened: false, + content: null + }; + } + + getTypeHandlers() { + return { + 'OPEN_MODAL': this.onOpenModal, + 'CLOSE_MODAL': this.onCloseModal + }; + } + + onOpenModal(state, payload) { + return _.extend({}, state, { + opened: true, + content: payload + }); + } + + onCloseModal(state) { + return _.extend({}, state, { + opened: false, + content: null + }); + } +} + +export default ModalReducer.getInstance(); \ No newline at end of file From 8f5852a0d0842b513937752cef1f2c418e91c589 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 9 Sep 2016 16:27:31 -0300 Subject: [PATCH 2/4] Ivan - Frontend - Add animation to modal [skip ci] --- client/src/core-components/modal.js | 34 ++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/client/src/core-components/modal.js b/client/src/core-components/modal.js index 612893f5..bbed32e0 100644 --- a/client/src/core-components/modal.js +++ b/client/src/core-components/modal.js @@ -1,5 +1,7 @@ import React from 'react'; +import {Motion, spring} from 'react-motion'; + class Modal extends React.Component { static propTypes = { content: React.PropTypes.node @@ -8,12 +10,38 @@ class Modal extends React.Component { render() { return (
-
- {this.props.content} -
+ + {this.renderContent.bind(this)} +
); } + + getAnimations() { + return { + defaultStyle: { + scale: spring(0.7), + fade: spring(0) + }, + style: { + scale: spring(1), + fade: spring(1) + } + } + } + + renderContent(animation) { + const style = { + transform: 'scale(' + animation.scale + ')', + opacity: animation.fade + }; + + return ( +
+ {this.props.content} +
+ ) + } } export default Modal; \ No newline at end of file From a0b4457676d7ba7fc24d0f14fce5e4c699e875cd Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 9 Sep 2016 18:22:17 -0300 Subject: [PATCH 3/4] Ivan - Frontend - Create static method for open modal, improve animation, add accessibility [skip ci] --- client/src/app-components/are-you-sure.js | 14 +++++--- client/src/app/App.js | 16 +++++++-- client/src/app/app.scss | 12 +++++++ client/src/app/demo/components-demo-page.js | 10 ++---- client/src/app/modal-container.js | 11 ++++++- client/src/core-components/button.js | 5 +++ client/src/core-components/button.scss | 36 ++++++++++++--------- client/src/core-components/modal.js | 23 ++++++------- client/src/core-components/modal.scss | 2 +- client/src/scss/_base.scss | 1 - 10 files changed, 83 insertions(+), 47 deletions(-) create mode 100644 client/src/app/app.scss diff --git a/client/src/app-components/are-you-sure.js b/client/src/app-components/are-you-sure.js index df76ee79..a1502c09 100644 --- a/client/src/app-components/are-you-sure.js +++ b/client/src/app-components/are-you-sure.js @@ -13,23 +13,27 @@ class AreYouSure extends React.Component { closeModal: React.PropTypes.func }; + componentDidMount() { + this.refs.yesButton && this.refs.yesButton.focus(); + } + render() { return ( -
-
+
+
{i18n('ARE_YOU_SURE')}
-
+
{this.props.description}
-
-
diff --git a/client/src/app/App.js b/client/src/app/App.js index e35a0be7..00f2efdf 100644 --- a/client/src/app/App.js +++ b/client/src/app/App.js @@ -1,5 +1,6 @@ import React from 'react'; import _ from 'lodash'; +import classNames from 'classnames'; import { connect } from 'react-redux' import { browserHistory } from 'react-router'; @@ -21,13 +22,24 @@ class App extends React.Component { render() { return ( -
- {React.cloneElement(this.props.children, {})} +
+
+ {React.cloneElement(this.props.children, {})} +
); } + getClass() { + let classes = { + 'application': true, + 'application_modal-opened': (this.props.modal.opened) + }; + + return classNames(classes); + } + redirectIfPathIsNotValid(props) { const validations = { languageChanged: props.config.language !== this.props.config.language, diff --git a/client/src/app/app.scss b/client/src/app/app.scss new file mode 100644 index 00000000..feccbaf6 --- /dev/null +++ b/client/src/app/app.scss @@ -0,0 +1,12 @@ +@import "../scss/vars"; + +.application { + padding: $half-space; + + &_modal-opened { + .application__content { + position: fixed; + overflow: hidden; + } + } +} \ No newline at end of file diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 874edabb..04473348 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -3,9 +3,7 @@ const React = require('react'); const DocumentTitle = require('react-document-title'); -const ModalActions = require('actions/modal-actions'); -const store = require('app/store'); - +const ModalContainer = require('app/modal-container'); const AreYouSure = require('app-components/are-you-sure'); const Button = require('core-components/button'); @@ -96,10 +94,8 @@ let DemoPage = React.createClass({ title: 'ModalTrigger', render: ( ); - expect(ReactDOM.findDOMNode(button).getAttribute('class')).to.include('button-' + type); + expect(ReactDOM.findDOMNode(button).getAttribute('class')).to.include('button_' + type); }); }); });