diff --git a/src/alert/card.js b/src/alert/card.js
new file mode 100644
index 0000000..d2ac7d1
--- /dev/null
+++ b/src/alert/card.js
@@ -0,0 +1,42 @@
+import devboard from 'devboard';
+import React from 'react';
+import Alert from './index.js';
+
+const devcard = devboard.ns('alerts');
+
+devcard(
+ 'Alerts',
+ `
+ Patternity Alerts
+
+ * success
+ * error
+ * info
+ * warning
+ `,
+
+
Success
+
Error
+
Info
+
Warning
+
+);
+
+
+devcard(
+ 'Alerts with close',
+ ' ',
+
+);
+
+
+devcard(
+ 'Alerts with title and close',
+ ' ',
+
+);
+
diff --git a/src/alert/index.js b/src/alert/index.js
new file mode 100644
index 0000000..4048566
--- /dev/null
+++ b/src/alert/index.js
@@ -0,0 +1,116 @@
+import React, { PropTypes, Component } from 'react';
+import styles from './styles.css';
+import cn from 'classnames';
+import { Icon } from 'infl-icons';
+
+export default class Alert extends Component {
+ static propTypes = {
+ title: PropTypes.string,
+ type: PropTypes.oneOf(['success', 'error', 'info', 'warning']),
+ showIcon: PropTypes.bool,
+ closeable: PropTypes.bool,
+ showAlert: PropTypes.bool,
+ onClose: PropTypes.func,
+ hideIn: PropTypes.number
+ };
+
+ static defaultProps = {
+ title: '',
+ showIcon: false,
+ closeable: false,
+ showAlert: true,
+ onClose() {},
+ hideIn: 0
+ }
+
+ hideInTimeout = null;
+
+ state = {
+ showAlert: this.props.showAlert,
+ closeable: this.props.closeable
+ };
+
+ componentWillReceiveProps = (newProps) => {
+ this.clearHideInTimeout();
+ this.setState({
+ showAlert: newProps.showAlert,
+ closeable: newProps.closeable
+ });
+ };
+
+ componentDidMount = () => {
+ this.hideAlert();
+ };
+
+ componentWillUpdate = (nextProps) => {
+ this.clearHideInTimeout();
+ this.hideAlert(nextProps.hideIn);
+ };
+
+ clearHideInTimeout = () =>{
+ window.clearTimeout(this.hideInTimeout);
+ };
+
+ hideAlert = () => {
+ const { hideIn } = this.props;
+ if (hideIn > 0) {
+ this.hideInTimeout = setTimeout(this.close, hideIn * 1000);
+ }
+ };
+
+ render = () =>
+
+ {this.title()}
+
+ {this.props.children}
+
+
+ {this.closeable()}
+
;
+
+ classes = () =>
+ cn(
+ styles.alertMsg,
+ {
+ [styles.hasIcon]: this.props.showIcon,
+ [styles.hide]: !this.props.showAlert,
+ [styles[this.props.type]]: this.props.type
+ }
+ );
+
+ title = () => {
+ if (this.props.title) {
+ return (
+
+ {this.icon()}
+ {this.props.title}
+
+ );
+ }
+ return null;
+ };
+
+ icon = () => {
+ const { showIcon } = this.props;
+
+ if (!showIcon)
+ return null;
+
+ return
+
+ ;
+ };
+
+ close = () => {
+ this.setState({ showAlert: false });
+ this.props.onClose();
+ };
+
+ closeable = () => {
+ return this.state.closeable
+ ?
+
+
+ : null;
+ };
+}
\ No newline at end of file
diff --git a/src/alert/styles.css b/src/alert/styles.css
new file mode 100644
index 0000000..cd548b3
--- /dev/null
+++ b/src/alert/styles.css
@@ -0,0 +1,198 @@
+.alertMsg {
+ padding: 10px 20px;
+ border-radius: 2px;
+ background-color: lighten($info-color, 0.55);
+ display: flex;
+ justify-content: space-between;
+ position: relative;
+ color: $info-color;
+
+ &.hide {
+ display: none;
+ }
+
+ h4 {
+ margin: 0px;
+ line-height: 35px;
+ font-size: 16px;
+ text-align: left;
+
+ span {
+ vertical-align: middle;
+ }
+ }
+
+ .alertIcon {
+ margin-right: 10px;
+ }
+
+ .close {
+ padding: 10px;
+ position: absolute;
+ right: 5px;
+ top: 0px;
+ color: $info-color;
+ cursor: pointer;
+ font-weight: bold;
+ z-index: 3;
+
+ &:hover {
+ color: darken($info-color, 0.1);
+ transition: color 0.5s ease-out;
+ }
+ }
+
+ .alertBody {
+ overflow: hidden;
+ position: relative;
+ line-height: 1.2em;
+ }
+
+ &.hasIcon {
+ .alertBody {
+ padding-left: 26px;
+
+ .detailed {
+ margin-left: -26px;
+ }
+ }
+ }
+
+ &.warning {
+ background-color: lighten($warning-color, 0.45);
+ color: darken($warning-color, 0.1);
+
+ .close {
+ color: darken($warning-color, 0.1);
+
+ &:hover {
+ color: darken($warning-color, 0.15);
+ }
+ }
+
+ .alertAction {
+ button {
+ border-color: darken($warning-color, 0.1);
+ color: darken($warning-color, 0.1) !important;
+
+ &:hover {
+ border-color: darken($warning-color, 0.15);
+ color: darken($warning-color, 0.15) !important;
+ }
+ }
+ }
+ }
+
+ &.success {
+ background-color: lighten($success-color, 0.4);
+ color: darken($success-color, 0.15);
+
+ .close {
+ color: darken($success-color, 0.15);
+
+ &:hover {
+ color : darken($success-color, 0.1);
+ }
+ }
+
+ .alertAction {
+ button {
+ border-color: darken($success-color, 0.15);
+ color: darken($success-color, 0.15) !important;
+
+ &:hover {
+ border-color: darken($success-color, 0.1);
+ color: darken($success-color, 0.1) !important;
+ }
+ }
+ }
+ }
+
+ &.error {
+ background-color: lighten($error-color, 0.4);
+ color: $error-color;
+
+ .close {
+ color: $error-color;
+
+ &:hover {
+ color : darken($error-color, 0.1);
+ }
+ }
+
+ .alertAction {
+ button {
+ border-color: $error-color;
+ color: $error-color !important;
+
+ &:hover {
+ border-color: darken($error-color, 0.1);
+ color: darken($error-color, 0.1) !important;
+ }
+ }
+ }
+ }
+
+ .alertAction {
+ position: absolute;
+ top: 10px;
+ right: 20px;
+ z-index: 3;
+
+ button {
+ border-color: $info-color;
+ color: $info-color !important;
+
+ &:hover {
+ border-color: lighten($info-color, 0.02);
+ color: lighten($info-color, 0.02) !important;
+ }
+ }
+
+ & + .alertBody {
+ padding-right: 0.4;
+ min-height: 12px;
+ }
+ }
+
+ .alertDetailed {
+ background-color: #fff;
+ padding: 20px;
+ margin-top: 20px;
+ color: $darker-gray;
+ margin-bottom: 10px;
+ position: relative;
+ overflow: hidden;
+
+ h4 {
+ margin-bottom: 1.33em;
+ }
+
+ .alertDetailedAction {
+ position: absolute;
+ right: 20px;
+ top: 15px;
+ }
+
+ a {
+ text-decoration: none;
+ color: $info-color;
+ }
+ }
+}
+
+@media only screen and (max-width : 480px) {
+ .alertMsg {
+ .alertAction {
+ bottom: 15px;
+ left: 46px;
+ right: auto;
+ top: auto;
+
+ & + .alertBody {
+ padding-right: 0px;
+ padding-bottom: 50px;
+ }
+ }
+ }
+}
diff --git a/src/shared/form-styles.css b/src/shared/form-styles.css
new file mode 100644
index 0000000..f034d0a
--- /dev/null
+++ b/src/shared/form-styles.css
@@ -0,0 +1,94 @@
+.isRequired {
+ position: relative;
+ display: inline-block;
+
+ .requiredInput {
+ position: absolute;
+ top: 11px;
+ left: 2px;
+ color: $error-color;
+ font-size: 15px;
+ }
+
+ input {
+ padding-left: 15px;
+ }
+}
+
+.isError {
+ input,
+ textarea,
+ select,
+ .selectBox {
+ border-color: $error-color;
+ color: $error-color;
+ background-color: lighten($error-color, 0.4);
+ }
+
+ .inputMessage {
+ color: $error-color;
+ }
+}
+
+.isValid {
+ input,
+ textarea,
+ select,
+ .selectBox {
+ border-color: $success-color;
+ color: darken($success-color, 0.07);
+ background-color: lighten($success-color, 0.4);
+ }
+
+ .inputMessage {
+ color: $success-color;
+ }
+}
+
+.isClearable {
+ position: relative;
+ display: inline-block;
+
+ input {
+ padding-right: 30px;
+ }
+
+ .clearInput {
+ position: absolute;
+ right: 0px;
+ top: 0px;
+ cursor: pointer;
+ padding: 10px;
+
+ &:hover {
+ color: $darker-gray;
+ }
+ }
+}
+
+.isDisabled {
+ input,
+ .selectBox {
+ background-color: $lighter-gray;
+ color: $medium-gray;
+ }
+}
+
+.searchInput {
+ position: relative;
+ display: inline-block;
+
+ .searchInput {
+ position: absolute;
+ top: 5px;
+ z-index: 1;
+ left: 10px;
+ font-size: 20px;
+ }
+
+ input[type='search'] {
+ padding-left: 40px;
+ height: 2.21em;
+ line-height: 2.21em;
+ }
+}
\ No newline at end of file
diff --git a/src/text-area/card.js b/src/text-area/card.js
new file mode 100644
index 0000000..9566dd1
--- /dev/null
+++ b/src/text-area/card.js
@@ -0,0 +1,35 @@
+import devboard from 'devboard';
+import React from 'react';
+import TextArea from './index.js';
+
+const devcard = devboard.ns('textarea');
+
+devcard(
+ 'TextArea',
+ `
+ Patternity TextArea
+ `,
+
+
+);
+
+devcard(
+ 'TextArea with messages',
+ '',
+
+
+);
+
+devcard(
+ 'TextArea with required',
+ '',
+
+ {}} placeholder='Mandatory' />
+
+);
+
+
diff --git a/src/text-area/index.js b/src/text-area/index.js
new file mode 100644
index 0000000..47b0ed8
--- /dev/null
+++ b/src/text-area/index.js
@@ -0,0 +1,105 @@
+import React, { PropTypes } from 'react';
+import cn from 'classnames';
+import InputIcon from './input-icon';
+import InputMessage from './input-message';
+import styles from './styles.css';
+import formStyles from '../shared/form-styles.css';
+
+const TextAreaContainer = ({
+ required = false,
+ error = false,
+ valid = false,
+ disabled = false,
+ message = '',
+ clearable = false,
+ children,
+ classNames
+}) =>
+
+
+ {children}
+
+
+;
+
+TextAreaContainer.propTypes = {
+ required: PropTypes.bool,
+ error: PropTypes.bool,
+ valid: PropTypes.bool,
+ disabled: PropTypes.bool,
+ message: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.node,
+ PropTypes.array
+ ]),
+ clearable: PropTypes.bool
+};
+
+
+class TextArea extends React.Component {
+ static defaultProps = {
+ id: '',
+ value: '',
+ placeholder: '',
+ readOnly: false,
+ autofocus: false,
+ autoexpand: true,
+
+ style: {
+ height: 'inherit'
+ }
+ };
+
+ textRef = null;
+
+ static propTypes = {
+ id: PropTypes.string,
+ name: PropTypes.string.isRequired,
+ value: PropTypes.string,
+ placeholder: PropTypes.string,
+ readOnly: PropTypes.bool,
+ disabled: PropTypes.bool,
+ autofocus: PropTypes.bool,
+ autoexpand: PropTypes.bool,
+ onChange: PropTypes.func.isRequired,
+ style: PropTypes.shape({
+ height: PropTypes.string
+ }),
+ ...TextAreaContainer.propTypes
+ };
+
+ componentWillReceiveProps(newProps) {
+ this.setInputFocus(newProps.autofocus);
+ }
+
+ componentDidMount() {
+ this.setInputFocus(this.props.autofocus);
+ }
+
+ render() {
+ return (
+
+ this.textRef = node} />
+
+ );
+ }
+
+ setInputFocus = (autofocus) => {
+ if (autofocus && this.textRef) { this.textRef.focus(); }
+ }
+
+ classNames = () => {
+ return {
+ [styles.textarea]: true,
+ [styles.autoexpand]: this.props.autoexpand
+ };
+ }
+}
+
+export default TextArea;
diff --git a/src/text-area/input-icon.js b/src/text-area/input-icon.js
new file mode 100644
index 0000000..26bdb37
--- /dev/null
+++ b/src/text-area/input-icon.js
@@ -0,0 +1,29 @@
+import React, { PropTypes } from 'react';
+import { Icon } from 'infl-icons';
+import styles from '../shared/form-styles.css';
+
+const InputIcon = ({
+ type = 'text',
+ required = false
+}) => {
+ if (type === 'search') {
+ return
+
+ ;
+ }
+
+ if (required) {
+ return
+
+ ;
+ }
+
+ return ;
+};
+
+InputIcon.propTypes = {
+ type: PropTypes.oneOf(['text', 'password', 'url', 'email', 'search', 'number', '']),
+ required: PropTypes.bool
+};
+
+export default InputIcon;
diff --git a/src/text-area/input-message.js b/src/text-area/input-message.js
new file mode 100644
index 0000000..a36269a
--- /dev/null
+++ b/src/text-area/input-message.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import styles from '../shared/form-styles.css';
+
+const InputMessage = ({
+ message = null
+}) => {
+ if (message === null) return ;
+ const messages = Array.isArray(message) ? message : [message];
+
+ return
+ {messages.map((message, i) =>
+
+ {message}
+
+ )}
+
;
+};
+
+InputMessage.propTypes = {
+ message: React.PropTypes.oneOfType([
+ React.PropTypes.string,
+ React.PropTypes.node,
+ React.PropTypes.array
+ ])
+};
+
+export default InputMessage;
diff --git a/src/text-area/styles.css b/src/text-area/styles.css
new file mode 100644
index 0000000..34b6445
--- /dev/null
+++ b/src/text-area/styles.css
@@ -0,0 +1,15 @@
+.textarea {
+ textarea {
+ height: 5em;
+ }
+
+ &.autoexpand {
+ textarea {
+ height: 3em;
+
+ &:focus {
+ height: 5em;
+ }
+ }
+ }
+}
diff --git a/src/toggle-switch/card.js b/src/toggle-switch/card.js
new file mode 100644
index 0000000..6d99473
--- /dev/null
+++ b/src/toggle-switch/card.js
@@ -0,0 +1,17 @@
+import devboard from 'devboard';
+import React from 'react';
+import ToggleSwitch from './index.js';
+
+const devcard = devboard.ns('toggleswitch');
+
+devcard(
+ 'Toggle Switch',
+ `
+ Patternity Toggle Switch
+ `,
+
+
+
+
+
+);
\ No newline at end of file
diff --git a/src/toggle-switch/index.js b/src/toggle-switch/index.js
new file mode 100644
index 0000000..2b9443a
--- /dev/null
+++ b/src/toggle-switch/index.js
@@ -0,0 +1,83 @@
+import React, { Component, PropTypes } from 'react';
+import ReactDOM from 'react-dom';
+import classNames from 'classnames';
+import styles from './styles.css';
+
+export default class ToggleSwitch extends Component {
+ static displayName = 'ToggleSwitch';
+
+ static propTypes = {
+ id: PropTypes.string,
+ enabled: PropTypes.bool,
+ isOn: PropTypes.bool,
+ onChange: PropTypes.func,
+ inputName: PropTypes.string
+ }
+
+ static defaultProps = {
+ id: '',
+ enabled: true,
+ isOn: false,
+ onChange: () => {},
+ inputName: ''
+ }
+
+ state = { isOn: this.props.isOn }
+
+ componentWillReceiveProps(newProps) {
+ this.setState({
+ isOn: newProps.isOn
+ });
+ }
+
+ checkbox = null;
+
+ render() {
+ return (
+
+ {this._toggleText()}
+
+
+
+
+
+ this.checkbox = node}
+ className={styles.toggleCheckbox}
+ checked={this._isChecked()}
+ name={this.props.inputName}
+ onChange={this._handleChange}
+ id={this.props.id} />
+
+ );
+ }
+
+ _toggleText() {
+ return this.state.isOn ? 'On' : 'Off';
+ }
+
+ _isChecked() {
+ return this.state.isOn;
+ }
+
+ _switchCSSClasses() {
+ return classNames(styles.toggleSwitch, {
+ [styles.on]: this.state.isOn,
+ [styles.off]: !this.state.isOn,
+ [styles.disabled]: !this.props.enabled
+ });
+ }
+
+ _clickCheckBox = (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ if (this.props.enabled) {
+ ReactDOM.findDOMNode(this.checkbox).click();
+ }
+ }
+
+ _handleChange = (event) => {
+ this.setState({ isOn: !this.state.isOn });
+ this.props.onChange(event);
+ }
+}
\ No newline at end of file
diff --git a/src/toggle-switch/styles.css b/src/toggle-switch/styles.css
new file mode 100644
index 0000000..8f198f0
--- /dev/null
+++ b/src/toggle-switch/styles.css
@@ -0,0 +1,88 @@
+.toggleSwitch {
+ width: 80px;
+ height:30px;
+ border-radius: 2px;
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ line-height: 1em;
+
+ transition: background-color 0.3s ease-in-out;
+
+ &.off {
+ background-color: lighten($error-color, 0.07);
+ box-shadow: 0px -1px $error-color;
+
+ .switch {
+ left: 2px;
+ }
+
+ .toggleText {
+ right: 9px;
+ }
+ }
+
+ &.on {
+ background-color: $success-color;
+ box-shadow: 0px -1px darken($success-color, 0.1);
+
+ .switch {
+ left: 38px;
+ }
+
+ .toggleText {
+ left: 9px;
+ }
+ }
+
+ &.disabled {
+ background-color: $light-gray;
+ box-shadow: 0px -1px $dark-gray;
+
+ .toggleText {
+ color: $dark-gray;
+ }
+ }
+
+ .toggleText {
+ color: #fff;
+ position: absolute;
+ top: 8px;
+ font-size: 15px;
+ font-weight: normal;
+ }
+
+ .switch {
+ display: block;
+ position: absolute;
+ height: 28px;
+ width: 40px;
+ border-radius: 2px;
+ top: 0px;
+ background-color: $lighter-gray;
+ box-shadow: 0px 1px $dark-gray;
+
+ transition: left 0.3s ease-in-out;
+
+ .switchLine {
+ width: 1px;
+ height: 15px;
+ background-color: $light-gray;
+ position: absolute;
+ top: 6px;
+ left: 20px;
+
+ &:first-child {
+ left: 12px;
+ }
+
+ &:last-child {
+ left: 28px;
+ }
+ }
+ }
+
+ .toggleCheckbox {
+ visibility: hidden;
+ }
+}