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..a1502c09
--- /dev/null
+++ b/client/src/app-components/are-you-sure.js
@@ -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 (
+
- {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,
@@ -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
};
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 2d64b834..04473348 100644
--- a/client/src/app/demo/components-demo-page.js
+++ b/client/src/app/demo/components-demo-page.js
@@ -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: (
-
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..c9591c31
--- /dev/null
+++ b/client/src/app/modal-container.js
@@ -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 (
+
+ {(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/__tests__/button-test.js b/client/src/core-components/__tests__/button-test.js
index d60d41e6..a65ad853 100644
--- a/client/src/core-components/__tests__/button-test.js
+++ b/client/src/core-components/__tests__/button-test.js
@@ -25,7 +25,7 @@ describe('Button component', function () {
);
- expect(ReactDOM.findDOMNode(button).getAttribute('class')).to.include('button-' + type);
+ expect(ReactDOM.findDOMNode(button).getAttribute('class')).to.include('button_' + type);
});
});
});
diff --git a/client/src/core-components/button.js b/client/src/core-components/button.js
index 8ad69522..222bedab 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() {
@@ -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;
diff --git a/client/src/core-components/button.scss b/client/src/core-components/button.scss
index 34c58bea..89295775 100644
--- a/client/src/core-components/button.scss
+++ b/client/src/core-components/button.scss
@@ -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;
+ }
+ }
}
\ 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..64fb8b15
--- /dev/null
+++ b/client/src/core-components/modal.js
@@ -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 (
+
+ {this.renderModal.bind(this)}
+
+ );
+ }
+
+ getAnimations() {
+ return {
+ defaultStyle: {
+ scale: spring(0.7),
+ fade: spring(0.5)
+ },
+ style: {
+ scale: spring(1),
+ fade: spring(1)
+ }
+ }
+ }
+
+ renderModal(animation) {
+ 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..061142c6
--- /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 10px white;
+ }
+}
\ No newline at end of file
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
diff --git a/client/src/scss/_base.scss b/client/src/scss/_base.scss
index cba21b56..11a6fffb 100644
--- a/client/src/scss/_base.scss
+++ b/client/src/scss/_base.scss
@@ -5,5 +5,4 @@
body {
font-family: Helvetica, sans-serif;
background-color: $light-grey;
- padding: $half-space;
}
\ No newline at end of file