UNPKG

ldx-widgets

Version:

widgets

391 lines (302 loc) 10.1 kB
React = require 'react' createClass = require 'create-react-class' PropTypes = require 'prop-types' {StyleSheet, css} = require 'aphrodite/no-important' assign = require 'lodash/assign' dialogueMixin = require '../mixins/dialogue_mixin' {ESCAPE, KEY_S} = require '../constants/keyboard' ConfirmSave = React.createFactory(require('./confirm_save')) Spinner = React.createFactory(require('./spinner')) {div, header, span, button} = require 'react-dom-factories' ###& @props.title - OPTIONAL - [String] title for the modal header @props.buttons - OPTIONAL - [Array] Array of button objects with name, handler to be called on click, and disabled boolean, eg... ``` [ { name: 'Save' handler: @save disabled: no } ] ``` @props.children - REQUIRED - [Element] React element (or array of elements) to inserted as the modal body @props.styleOverride - OPTIONAL - [Object] aphrodite style object, optionally can contain .modal, .header, .title, .actionButton class defs, eg... ``` { modalBase: {} modalEnter: {} modal: {} header: {} title: {} actionButton: {} } ``` @props.close - OPTIONAL - [Function] Function that closes the overlay, passed automatically by the overlay framework @props.onClose - OPTIONAL - [Function] Function that is called right before the modal closes @props.showClose - OPTIONAL - [Boolean] Defaults to yes, set it to no to not show the close button @props.closeAfterSave - OPTIONAL - [Boolean] Defaults to yes, set it to no to prevent the modal from calling @props.close after a save is complete Note: in this case you MUST pass an onSaveComplete handler that sets the saveState to null after a save @props.onSaveComplete - OPTIONAL - [Function] Function that is called right after the saveState is set to complete and the success indicator finishes animating @props.closeBtnText - OPTIONAL - [String] Defaults to 'Cancel', text to display in the close button @props.animateIn - OPTIONAL - [Boolean] Defaults to yes, whether or not to bouce the modal on enter @props.loading - OPTIONAL - [Boolean] Defaults to no, whether or not to show the spinner instead of the children @props.draggable - OPTIONAL - [Boolean] Defaults to yes, whether or not the modal can be dragged from it's header @props.stopPropagation - OPTIONAL - [Boolean] Defaults to yes, whether or not the modal stop click from bubbling above it @props.displayProgressBar - OPTIONAL - [Boolean] Defaults to no, whether or not the confirm/save show the progress bar instead of the spinner @props.uploadProgress -OPTIONAL Progress of a file being uploaded &### Modal = createClass displayName: 'Modal' mixins: [dialogueMixin] propTypes: styleOverride: PropTypes.shape modal: PropTypes.object header: PropTypes.object title: PropTypes.object actionButton: PropTypes.object title: PropTypes.string buttons: PropTypes.array close: PropTypes.func.isRequired onClose: PropTypes.func onSaveComplete: PropTypes.func showClose: PropTypes.bool closeAfterSave: PropTypes.bool closeBtnText: PropTypes.string unSavedMessage: PropTypes.string unSavedDialogueHeight: PropTypes.number unSavedChanges: PropTypes.bool onSaveFail: PropTypes.func inLineStyle: PropTypes.object animateIn: PropTypes.bool loading: PropTypes.bool draggable: PropTypes.bool stopPropagation: PropTypes.bool spinnerProps: PropTypes.object displayProgressBar: PropTypes.bool uploadProgress: PropTypes.oneOfType [ PropTypes.string PropTypes.number ] getDefaultProps: -> styleOverride: {} inLineStyle: {} buttons: [] showClose: yes closeAfterSave: yes unSavedDialogueHeight: 100 saveState: null saveMessage: null unSavedChanges: no animateIn: yes loading: no draggable: yes stopPropagation: yes spinnerProps: length: 7 radius: 7 lines: 12 width: 2 displayProgressBar: no uploadProgress: '' getInitialState: -> dragX: 0 dragY: 0 componentWillMount: -> # This is necessary becasue translation is not available on app load # So this cannot live in default props @closeBtnText = t 'Cancel' document.addEventListener 'keydown', @handleKeyPress componentDidMount: -> setTimeout => do @calculateMaxDrags , 0 window.addEventListener 'resize', @calculateMaxDrags componentWillUnmount: -> document.removeEventListener 'keydown', @handleKeyPress window.removeEventListener 'resize', @calculateMaxDrags render: -> {styleOverride, title, buttons, closeBtnText, showClose, children, saveState, saveMessage, onSaveFail, inLineStyle, animateIn, loading, spinnerProps, draggable, stopPropagation, displayProgressBar, uploadProgress} = @props {dragX, dragY} = @state closeBtnText = closeBtnText or @closeBtnText assign styles, styleOverride inLineStyle = assign {}, inLineStyle, transform: "translate(#{dragX}px, #{dragY}px) translateZ(0px)" WebkitTransform: "translate(#{dragX}px, #{dragY}px) translateZ(0px)" msTransform: "translate(#{dragX}px, #{dragY}px)" assign spinnerProps, key: 'spinner' headerChildren = [] # Modal Title headerChildren.push span { key: 'title' className: css(styles.title) }, title if title? # Close Button headerChildren.push button { key: 'close' className: css(styles.actionButton) onClick: @closeWithCheck }, closeBtnText if showClose # Other buttons headerChildren.push button { key: b.name className: css(styles.actionButton) onClick: b.handler disabled: b.disabled }, b.name for b in buttons by -1 animateStyle = if animateIn then styles.modalEnter else null div { className: css(styles.modalBase, animateStyle, styles.modal) style: inLineStyle onClick: if stopPropagation then @handleClick else null }, [ header { key: 'header' className: css(styles.header) onMouseDown: if draggable then @handleMouseDown else null ref: (@header) => }, headerChildren @dialogueBox() ConfirmSave { key: 'confirm' done: @saveComplete fail: -> onSaveFail?() saveMessage: saveMessage saveState: saveState displayProgressBar: displayProgressBar uploadProgress: uploadProgress } if saveState? if loading then Spinner(spinnerProps) else children ] handleClick: (e) -> do e.stopPropagation closeWithCheck: -> {unSavedMessage, unSavedDialogueHeight, unSavedChanges} = @props if unSavedChanges @showDialogue message: unSavedMessage or t 'There are unsaved changes. How do you want to proceed?' confirmText: t 'Discard Changes' height: unSavedDialogueHeight confirmCallback: @close else do @close close: -> {close, onClose} = @props onClose?() do close saveComplete: -> {close, onSaveComplete, closeAfterSave} = @props onSaveComplete?() do close if closeAfterSave handleKeyPress: (e) -> {keyCode, metaKey} = e if keyCode is ESCAPE then do @closeWithCheck if keyCode is KEY_S and metaKey do e.preventDefault {buttons} = @props if buttons[0]?.name is t 'Save' then do buttons[0].handler handleMouseDown: (e) -> do e.preventDefault {dragX, dragY} = @state @startDragX = dragX @startDragY = dragY @startX = e.clientX @startY = e.clientY document.addEventListener 'mousemove', @handleMouseMove document.addEventListener 'mouseup', @handleMouseUp handleMouseMove: (e) -> do e.preventDefault dragX = @startDragX + (e.clientX - @startX) dragY = @startDragY + (e.clientY - @startY) dragY = if dragY < @minDragY then @minDragY else dragY dragY = if dragY > @maxDragY then @maxDragY else dragY dragX = if dragX < @minDragX then @minDragX else dragX dragX = if dragX > @maxDragX then @maxDragX else dragX @setState {dragX, dragY} handleMouseUp: -> document.removeEventListener 'mousemove', @handleMouseMove document.removeEventListener 'mouseup', @handleMouseUp calculateMaxDrags: -> {offsetTop, offsetLeft} = @header.parentNode {offsetHeight, offsetWidth} = @header {innerHeight, innerWidth} = window @maxDragX = innerWidth - (offsetLeft + offsetWidth) @maxDragY = innerHeight - (offsetTop + offsetHeight) @minDragX = 0 - offsetLeft @minDragY = 0 - offsetTop enterKeyFrames = '0%': transform: 'scale(0.9)' opacity: '0' '40%': opacity: '1' transform: 'scale(1.05)' '100%': transform: 'scale(1)' styles = StyleSheet.create modalBase: position: 'absolute' backgroundColor: 'white' borderRadius: '8px' overflow: 'hidden' modalEnter: animationName: [enterKeyFrames] animationDuration: '.15s' animationIterationCount: '1' animationTimingFunction: 'ease-out' modal: top: '10%' left: '50%' width: '600px' height: '500px' marginLeft: '-300px' header: position: 'relative' width: '100%' height: '44px' lineHeight: '44px' backgroundColor: 'rgb(246,246,246)' borderBottom: '1px solid rgb(190,190,190)' borderTopLeftRadius: '8px' borderTopRightRadius: '8px' margin: '0px' cursor: 'pointer' title: display: 'inline-block' fontSize: '16px' fontWeight: 'normal' textAlign: 'left' color: 'rgb(113,113,113)' overflow: 'hidden' whiteSpace: 'nowrap' textOverflow: 'ellipsis' marginLeft: '15px' actionButton: float: 'right' color: 'rgb(0,127,255)' height: '28px' textAlign: 'center' lineHeight: '26px' marginTop: '8px' marginRight: '15px' fontSize: '13px' ':disabled': color: 'rgb(204,204,204)' module.exports = Modal