Merged in frontend-modal-are-you-sure (pull request #46)

Frontend modal are you sure
This commit is contained in:
Ivan Diaz 2016-09-13 21:59:21 -03:00
commit aa30cd1718
20 changed files with 405 additions and 35 deletions

View File

@ -0,0 +1,16 @@
export default {
openModal(content) {
return {
type: 'OPEN_MODAL',
payload: content
}
},
closeModal() {
return {
type: 'CLOSE_MODAL',
payload: {}
};
}
};

View File

@ -0,0 +1,64 @@
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
};
componentDidMount() {
this.refs.yesButton && this.refs.yesButton.focus();
}
render() {
return (
<div className="are-you-sure" role="dialog" aria-labelledby="are-you-sure__header" aria-describedby="are-you-sure__description">
<div className="are-you-sure__header" id="are-you-sure__header">
{i18n('ARE_YOU_SURE')}
</div>
<div className="are-you-sure__description" id="are-you-sure__description">
{this.props.description}
</div>
<div className="are-you-sure__buttons">
<div className="are-you-sure__yes-button">
<Button type="secondary" size="small" onClick={this.onYes.bind(this)} ref="yesButton" tabIndex="2">
{i18n('YES')}
</Button>
</div>
<div className="are-you-sure__no-button">
<Button type="link" size="auto" onClick={this.onNo.bind(this)} tabIndex="2">
{i18n('CANCEL')}
</Button>
</div>
</div>
</div>
);
}
onYes() {
this.closeModal();
if (this.props.onYes) {
this.props.onYes();
}
}
onNo() {
this.closeModal();
}
closeModal() {
if (this.context.closeModal) {
this.context.closeModal();
}
}
}
export default AreYouSure;

View File

@ -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;
}
}

View File

@ -1,8 +1,11 @@
import React from 'react';
import _ from 'lodash';
import classNames from 'classnames';
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,
@ -19,12 +22,24 @@ class App extends React.Component {
render() {
return (
<div>
{React.cloneElement(this.props.children, {})}
<div className={this.getClass()}>
<div className="application__content">
{React.cloneElement(this.props.children, {})}
</div>
<ModalContainer />
</div>
);
}
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,
@ -49,6 +64,7 @@ class App extends React.Component {
export default connect((store) => {
return {
config: store.config,
modal: store.modal,
session: store.session,
routing: store.routing
};

12
client/src/app/app.scss Normal file
View File

@ -0,0 +1,12 @@
@import "../scss/vars";
.application {
padding: $half-space;
&_modal-opened {
.application__content {
position: fixed;
overflow: hidden;
}
}
}

View File

@ -3,6 +3,9 @@
const React = require('react');
const DocumentTitle = require('react-document-title');
const ModalContainer = require('app/modal-container');
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 +85,21 @@ let DemoPage = React.createClass({
{
title: 'Tooltip',
render: (
<Tooltip content="mensaje mensa jemensajemens ajem ensaje nsaje adicionals">hola</Tooltip>
<div>
<Tooltip content="mensaje mensa jemensajemens ajem ensaje nsaje adicionals">hola</Tooltip>
</div>
)
},
{
title: 'ModalTrigger',
render: (
<Button onClick={function () {
ModalContainer.openModal(
<AreYouSure description="I confirm I want to perform this action." onYes={()=> {alert('yes');}} />
);
}}>
Open Modal
</Button>
)
}
],

View File

@ -0,0 +1,66 @@
import React from 'react';
import { connect } from 'react-redux';
import keyCode from 'keycode';
import store from 'app/store';
import ModalActions from 'actions/modal-actions';
import Modal from 'core-components/modal';
class ModalContainer extends React.Component {
static openModal(content) {
store.dispatch(
ModalActions.openModal(
content
)
);
}
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 (
<div className="modal-container">
{(this.props.modal.opened) ? this.renderModal() : null}
</div>
);
}
renderModal() {
return (
<Modal content={this.props.modal.content} />
);
}
onKeyDown(event) {
if (event.keyCode === keyCode('ESCAPE')) {
this.closeModal();
}
}
closeModal() {
this.props.dispatch(ModalActions.closeModal());
}
}
export default connect((store) => {
return {
modal: store.modal
};
})(ModalContainer);

View File

@ -25,7 +25,7 @@ describe('Button component', function () {
</Button>
);
expect(ReactDOM.findDOMNode(button).getAttribute('class')).to.include('button-' + type);
expect(ReactDOM.findDOMNode(button).getAttribute('class')).to.include('button_' + type);
});
});
});

View File

@ -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() {
@ -48,6 +56,7 @@ class Button extends React.Component {
props.onClick = callback(this.handleClick.bind(this), this.props.onClick);
props.className = this.getClass();
props.ref = 'button';
delete props.route;
delete props.iconName;
@ -59,10 +68,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);
@ -73,6 +92,10 @@ class Button extends React.Component {
this.context.router.push(this.props.route.to);
}
}
focus() {
this.refs.button.focus();
}
}
export default Button;

View File

@ -2,39 +2,64 @@
.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 {
background: none;
border: none;
}
&-link {
background: none;
border: none;
color: $dark-grey;
text-decoration: underline;
&:focus {
outline: none;
}
&_tertiary {
background-color: $secondary-blue;
}
&_disabled {
background-color: #ec9696;
}
&_small {
width: 100px;
height: 47px;
}
&_medium {
width: 239px;
}
&_large {
//width: 239px;
}
&_auto {
width: initial;
height: initial;
}
&_clean {
background: none;
border: none;
width: initial;
height: initial;
}
&_link {
background: none;
border: none;
color: $dark-grey;
width: initial;
height: initial;
&:focus, &:hover {
outline: none;
text-decoration: underline;
}
}
}

View File

@ -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';

View File

@ -44,7 +44,7 @@
}
.input__text {
padding-left: 40px;
padding-left: 48px;
}
}

View File

@ -0,0 +1,42 @@
import React from 'react';
import {Motion, spring} from 'react-motion';
class Modal extends React.Component {
static propTypes = {
content: React.PropTypes.node
};
render() {
return (
<Motion {...this.getAnimations()}>
{this.renderModal.bind(this)}
</Motion>
);
}
getAnimations() {
return {
defaultStyle: {
scale: spring(0.7),
fade: spring(0.5)
},
style: {
scale: spring(1),
fade: spring(1)
}
}
}
renderModal(animation) {
return (
<div className="modal" style={{opacity: animation.fade}}>
<div className="modal__content" style={{transform: 'scale(' + animation.scale + ')'}}>
{this.props.content}
</div>
</div>
)
}
}
export default Modal;

View File

@ -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 10px white;
}
}

View File

@ -51,9 +51,9 @@ class TextEditor extends React.Component {
return (
<div className="text-editor__options">
<Button type="primary-icon" iconName="bold" onClick={onBoldClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
<Button type="primary-icon" iconName="italic" onClick={onItalicsClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
<Button type="primary-icon" iconName="underline" onClick={onUnderlineClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
<Button type="primary" size="auto" iconName="bold" onClick={onBoldClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
<Button type="primary" size="auto" iconName="italic" onClick={onItalicsClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
<Button type="primary" size="auto" iconName="underline" onClick={onUnderlineClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
</div>
)
}

View File

@ -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?'
};

View File

@ -5,4 +5,5 @@
@import 'scss/font_awesome/font-awesome';
@import 'core-components/*';
@import 'app-components/*';
@import 'app/*';

View File

@ -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
});

View File

@ -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();

View File

@ -5,5 +5,4 @@
body {
font-family: Helvetica, sans-serif;
background-color: $light-grey;
padding: $half-space;
}