Initial Commit Rework

This commit is contained in:
Qstick 2017-09-03 22:20:56 -04:00
parent 74a4cc048c
commit 95051cbd63
2483 changed files with 101351 additions and 111396 deletions

View file

@ -0,0 +1,88 @@
import PropTypes from 'prop-types';
import React from 'react';
import { kinds, sizes } from 'Helpers/Props';
import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
import Modal from 'Components/Modal/Modal';
import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
function ConfirmModal(props) {
const {
isOpen,
kind,
size,
title,
message,
confirmLabel,
cancelLabel,
hideCancelButton,
isSpinning,
onConfirm,
onCancel
} = props;
return (
<Modal
isOpen={isOpen}
size={size}
onModalClose={onCancel}
>
<ModalContent onModalClose={onCancel}>
<ModalHeader>{title}</ModalHeader>
<ModalBody>
{message}
</ModalBody>
<ModalFooter>
{
!hideCancelButton &&
<Button
kind={kinds.DEFAULT}
onPress={onCancel}
>
{cancelLabel}
</Button>
}
<SpinnerButton
data-autofocus={true}
kind={kind}
isSpinning={isSpinning}
onPress={onConfirm}
>
{confirmLabel}
</SpinnerButton>
</ModalFooter>
</ModalContent>
</Modal>
);
}
ConfirmModal.propTypes = {
className: PropTypes.string,
isOpen: PropTypes.bool.isRequired,
kind: PropTypes.oneOf(kinds.all),
size: PropTypes.oneOf(sizes.all),
title: PropTypes.string.isRequired,
message: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
confirmLabel: PropTypes.string,
cancelLabel: PropTypes.string,
hideCancelButton: PropTypes.bool,
isSpinning: PropTypes.bool.isRequired,
onConfirm: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired
};
ConfirmModal.defaultProps = {
kind: kinds.PRIMARY,
size: sizes.MEDIUM,
confirmLabel: 'OK',
cancelLabel: 'Cancel',
isSpinning: false
};
export default ConfirmModal;

View file

@ -0,0 +1,79 @@
.modalContainer {
position: absolute;
top: 0;
z-index: 1000;
width: 100%;
height: 100%;
}
.modalBackdrop {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background-color: $modalBackdropBackgroundColor;
opacity: 1;
}
.modal {
position: relative;
display: flex;
max-height: 90%;
border-radius: 6px;
opacity: 1;
}
.modalOpen {
/* Prevent the body from scrolling when the modal is open */
overflow: hidden !important;
}
/*
* Sizes
*/
.small {
composes: modal;
width: 480px;
}
.medium {
composes: modal;
width: 720px;
}
.large {
composes: modal;
width: 1080px;
}
@media only screen and (max-width: $breakpointLarge) {
.modal.large {
width: 90%;
}
}
@media only screen and (max-width: $breakpointMedium) {
.modal.small,
.modal.medium {
width: 90%;
}
}
@media only screen and (max-width: $breakpointSmall) {
.modalContainer {
position: fixed;
}
.modal.small,
.modal.medium,
.modal.large {
max-height: 100%;
width: 100%;
height: 100%;
}
}

View file

@ -0,0 +1,196 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Portal from 'react-portal';
import classNames from 'classnames';
import elementClass from 'element-class';
import getUniqueElememtId from 'Utilities/getUniqueElementId';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import { sizes } from 'Helpers/Props';
import styles from './Modal.css';
const openModals = [];
function removeFromOpenModals(id) {
const index = openModals.indexOf(id);
if (index >= 0) {
openModals.splice(index, 1);
}
}
class Modal extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this._modalId = getUniqueElememtId();
}
componentDidMount() {
if (this.props.isOpen) {
this._openModal();
}
}
componentDidUpdate(prevProps) {
const {
isOpen
} = this.props;
if (!prevProps.isOpen && isOpen) {
this._openModal();
} else if (prevProps.isOpen && !isOpen) {
this._closeModal();
}
}
componentWillUnmount() {
if (this.props.isOpen) {
this._closeModal();
}
}
//
// Control
_openModal() {
openModals.push(this._modalId);
window.addEventListener('keydown', this.onKeyDown);
if (openModals.length === 1) {
elementClass(document.body).add(styles.modalOpen);
}
}
_closeModal() {
removeFromOpenModals(this._modalId);
window.removeEventListener('keydown', this.onKeyDown);
if (openModals.length === 0) {
elementClass(document.body).remove(styles.modalOpen);
}
}
_isBackdropTarget(event) {
const targetElement = this._findEventTarget(event);
if (targetElement) {
const modalElement = ReactDOM.findDOMNode(this.refs.modal);
return !modalElement || !modalElement.contains(targetElement);
}
return false;
}
_findEventTarget(event) {
const changedTouches = event.changedTouches;
if (!changedTouches) {
return event.target;
}
if (changedTouches.length === 1) {
const touch = changedTouches[0];
return document.elementFromPoint(touch.clientX, touch.clientY);
}
}
//
// Listeners
onBackdropBeginPress = (event) => {
this._isBackdropPressed = this._isBackdropTarget(event);
}
onBackdropEndPress = (event) => {
if (this._isBackdropPressed && this._isBackdropTarget(event)) {
this.props.onModalClose();
}
this._isBackdropPressed = false;
}
onKeyDown = (event) => {
const keyCode = event.keyCode;
if (keyCode === keyCodes.ESCAPE) {
if (openModals.indexOf(this._modalId) === openModals.length - 1) {
event.preventDefault();
event.stopPropagation();
this.props.onModalClose();
}
}
}
onClosePress = (event) => {
this.props.onModalClose();
}
//
// Render
render() {
const {
className,
backdropClassName,
size,
children,
isOpen
} = this.props;
return (
<Portal
isOpened={isOpen}
>
<div>
{
isOpen &&
<div
className={styles.modalContainer}
>
<div
className={backdropClassName}
onMouseDown={this.onBackdropBeginPress}
onMouseUp={this.onBackdropEndPress}
>
<div
ref="modal"
className={classNames(
className,
styles[size]
)}
>
{children}
</div>
</div>
</div>
}
</div>
</Portal>
);
}
}
Modal.propTypes = {
className: PropTypes.string,
backdropClassName: PropTypes.string,
size: PropTypes.oneOf(sizes.all),
children: PropTypes.node,
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
Modal.defaultProps = {
className: styles.modal,
backdropClassName: styles.modalBackdrop,
size: sizes.LARGE
};
export default Modal;

View file

@ -0,0 +1,14 @@
$modalBodyPadding: 30px;
.modalBody {
flex: 1 0 1px;
padding: $modalBodyPadding;
}
.modalScroller {
flex-grow: 1;
}
.innerModalBody {
padding: $modalBodyPadding;
}

View file

@ -0,0 +1,59 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { scrollDirections } from 'Helpers/Props';
import Scroller from 'Components/Scroller/Scroller';
import styles from './ModalBody.css';
class ModalBody extends Component {
//
// Render
render() {
const {
innerClassName,
scrollDirection,
children,
...otherProps
} = this.props;
let className = this.props.className;
const hasScroller = scrollDirection !== scrollDirections.NONE;
if (!className) {
className = hasScroller ? styles.modalScroller : styles.modalBody;
}
return (
<Scroller
className={className}
scrollDirection={scrollDirection}
scrollTop={0}
{...otherProps}
>
{
hasScroller ?
<div className={innerClassName}>
{children}
</div> :
children
}
</Scroller>
);
}
}
ModalBody.propTypes = {
className: PropTypes.string,
innerClassName: PropTypes.string,
children: PropTypes.node,
scrollDirection: PropTypes.oneOf([scrollDirections.NONE, scrollDirections.HORIZONTAL, scrollDirections.VERTICAL])
};
ModalBody.defaultProps = {
innerClassName: styles.innerModalBody,
scrollDirection: scrollDirections.VERTICAL
};
export default ModalBody;

View file

@ -0,0 +1,23 @@
.modalContent {
position: relative;
display: flex;
flex-direction: column;
flex-grow: 1;
width: 100%;
background-color: $modalBackgroundColor;
}
.closeButton {
position: absolute;
top: 0;
right: 0;
z-index: 1;
width: 60px;
height: 60px;
text-align: center;
line-height: 60px;
&:hover {
color: $modalCloseButtonHoverColor;
}
}

View file

@ -0,0 +1,46 @@
import PropTypes from 'prop-types';
import React from 'react';
import { icons } from 'Helpers/Props';
import Link from 'Components/Link/Link';
import Icon from 'Components/Icon';
import styles from './ModalContent.css';
function ModalContent(props) {
const {
className,
children,
onModalClose,
...otherProps
} = props;
return (
<div
className={className}
{...otherProps}
>
<Link
className={styles.closeButton}
onPress={onModalClose}
>
<Icon
name={icons.CLOSE}
size={18}
/>
</Link>
{children}
</div>
);
}
ModalContent.propTypes = {
className: PropTypes.string,
children: PropTypes.node,
onModalClose: PropTypes.func.isRequired
};
ModalContent.defaultProps = {
className: styles.modalContent
};
export default ModalContent;

View file

@ -0,0 +1,23 @@
.modalFooter {
display: flex;
align-items: center;
justify-content: flex-end;
flex-shrink: 0;
padding: 15px 30px;
border-top: 1px solid $borderColor;
a,
button {
margin-left: 10px;
&:first-child {
margin-left: 0;
}
}
}
@media only screen and (max-width: $breakpointSmall) {
.modalFooter {
padding: 15px;
}
}

View file

@ -0,0 +1,32 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import styles from './ModalFooter.css';
class ModalFooter extends Component {
//
// Render
render() {
const {
children,
...otherProps
} = this.props;
return (
<div
className={styles.modalFooter}
{...otherProps}
>
{children}
</div>
);
}
}
ModalFooter.propTypes = {
children: PropTypes.node
};
export default ModalFooter;

View file

@ -0,0 +1,8 @@
.modalHeader {
composes: truncate from 'Styles/Mixins/truncate.css';
flex-shrink: 0;
padding: 15px 50px 15px 30px;
border-bottom: 1px solid $borderColor;
font-size: 18px;
}

View file

@ -0,0 +1,32 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import styles from './ModalHeader.css';
class ModalHeader extends Component {
//
// Render
render() {
const {
children,
...otherProps
} = this.props;
return (
<div
className={styles.modalHeader}
{...otherProps}
>
{children}
</div>
);
}
}
ModalHeader.propTypes = {
children: PropTypes.node
};
export default ModalHeader;