[RN] Support children in Dialog
This commit is contained in:
parent
2496b3ec02
commit
d0476991a6
|
@ -1,32 +1,38 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Component } from 'react';
|
||||
|
||||
import { hideDialog } from '../actions';
|
||||
import { DIALOG_PROP_TYPES } from '../constants';
|
||||
|
||||
/**
|
||||
* Abstract dialog to display dialogs.
|
||||
* An abstract implementation of a dialog on Web/React and mobile/react-native.
|
||||
*/
|
||||
export default class AbstractDialog extends Component {
|
||||
|
||||
/**
|
||||
* Abstract Dialog component's property types.
|
||||
* <tt>AbstractDialog</tt> React <tt>Component</tt>'s prop types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
...DIALOG_PROP_TYPES,
|
||||
|
||||
/**
|
||||
* The React <tt>Component</tt> children of <tt>AbstractDialog</tt>
|
||||
* which represents the dialog's body.
|
||||
*/
|
||||
children: PropTypes.node,
|
||||
|
||||
/**
|
||||
* Used to show/hide the dialog on cancel.
|
||||
*/
|
||||
dispatch: React.PropTypes.func
|
||||
dispatch: PropTypes.func
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new Dialog instance.
|
||||
* Initializes a new <tt>AbstractDialog</tt> instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
* @param {Object} props - The read-only React <tt>Component</tt> props with
|
||||
* which the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -36,37 +42,30 @@ export default class AbstractDialog extends Component {
|
|||
}
|
||||
|
||||
/**
|
||||
* Dispatches action to hide the dialog.
|
||||
* Dispatches a redux action to hide this dialog when it's canceled.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancel() {
|
||||
let hide = true;
|
||||
const { onCancel } = this.props;
|
||||
|
||||
if (this.props.onCancel) {
|
||||
hide = this.props.onCancel();
|
||||
}
|
||||
|
||||
if (hide) {
|
||||
if (!onCancel || onCancel()) {
|
||||
this.props.dispatch(hideDialog());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the action when submitting the dialog.
|
||||
* Dispatches a redux action to hide this dialog when it's submitted.
|
||||
*
|
||||
* @private
|
||||
* @param {string} value - The submitted value if any.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSubmit(value) {
|
||||
let hide = true;
|
||||
const { onSubmit } = this.props;
|
||||
|
||||
if (this.props.onSubmit) {
|
||||
hide = this.props.onSubmit(value);
|
||||
}
|
||||
|
||||
if (hide) {
|
||||
if (!onSubmit || onSubmit(value)) {
|
||||
this.props.dispatch(hideDialog());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { TextInput } from 'react-native';
|
||||
import Prompt from 'react-native-prompt';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
@ -7,20 +9,21 @@ import { translate } from '../../i18n';
|
|||
import AbstractDialog from './AbstractDialog';
|
||||
|
||||
/**
|
||||
* Native dialog using Prompt.
|
||||
* Implements <tt>AbstractDialog</tt> on react-native using <tt>Prompt</tt>.
|
||||
*/
|
||||
class Dialog extends AbstractDialog {
|
||||
|
||||
/**
|
||||
* Native sialog component's property types.
|
||||
* <tt>AbstractDialog</tt>'s React <tt>Component</tt> prop types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
...AbstractDialog.propTypes,
|
||||
|
||||
/**
|
||||
* I18n key to put as body title.
|
||||
*/
|
||||
bodyKey: React.PropTypes.string
|
||||
bodyKey: PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -31,27 +34,109 @@ class Dialog extends AbstractDialog {
|
|||
*/
|
||||
render() {
|
||||
const {
|
||||
cancelDisabled,
|
||||
cancelTitleKey,
|
||||
bodyKey,
|
||||
cancelDisabled,
|
||||
cancelTitleKey = 'dialog.Cancel',
|
||||
children,
|
||||
okDisabled,
|
||||
okTitleKey,
|
||||
okTitleKey = 'dialog.Ok',
|
||||
t,
|
||||
titleKey
|
||||
titleKey,
|
||||
titleString
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Prompt
|
||||
cancelText = { cancelDisabled
|
||||
? undefined : t(cancelTitleKey || 'dialog.Cancel') }
|
||||
/* eslint-disable react/jsx-wrap-multilines */
|
||||
|
||||
let element
|
||||
= <Prompt
|
||||
cancelText = { cancelDisabled ? undefined : t(cancelTitleKey) }
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onSubmit }
|
||||
placeholder = { t(bodyKey) }
|
||||
submitText = { okDisabled
|
||||
? undefined : t(okTitleKey || 'dialog.Ok') }
|
||||
title = { t(titleKey) }
|
||||
visible = { true } />
|
||||
);
|
||||
submitText = { okDisabled ? undefined : t(okTitleKey) }
|
||||
title = { titleString || t(titleKey) }
|
||||
visible = { true } />;
|
||||
|
||||
/* eslint-enable react/jsx-wrap-multilines */
|
||||
|
||||
if (React.Children.count(children)) {
|
||||
// XXX The following implements a workaround with knowledge of the
|
||||
// implementation of react-native-prompt.
|
||||
element
|
||||
= this._replaceFirstElementOfType(
|
||||
// eslint-disable-next-line no-extra-parens, new-cap
|
||||
(new (element.type)(element.props)).render(),
|
||||
TextInput,
|
||||
children);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a deep clone of a specific <tt>ReactElement</tt> with the results
|
||||
* of calling a specific function on every node of a specific
|
||||
* <tt>ReactElement</tt> tree.
|
||||
*
|
||||
* @param {ReactElement} element - The <tt>ReactElement</tt> to clone and
|
||||
* call the specified <tt>f</tt> on.
|
||||
* @param {Function} f - The function to call on every node of the
|
||||
* <tt>ReactElement</tt> tree represented by the specified <tt>element</tt>.
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_mapReactElement(element, f) {
|
||||
if (!element || !element.props || !element.type) {
|
||||
return element;
|
||||
}
|
||||
|
||||
let mapped = f(element);
|
||||
|
||||
if (mapped === element) {
|
||||
mapped
|
||||
= React.cloneElement(
|
||||
element,
|
||||
/* props */ undefined,
|
||||
...React.Children.toArray(React.Children.map(
|
||||
element.props.children,
|
||||
function(element) { // eslint-disable-line no-shadow
|
||||
// eslint-disable-next-line no-invalid-this
|
||||
return this._mapReactElement(element, f);
|
||||
},
|
||||
this)));
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the first <tt>ReactElement</tt> of a specific type found in a
|
||||
* specific <tt>ReactElement</tt> tree with a specific replacement
|
||||
* <tt>ReactElement</tt>.
|
||||
*
|
||||
* @param {ReactElement} element - The <tt>ReactElement</tt> tree to search
|
||||
* through and replace in.
|
||||
* @param {*} type - The type of the <tt>ReactElement</tt> to be replaced.
|
||||
* @param {ReactElement} replacement - The <tt>ReactElement</tt> to replace
|
||||
* the first <tt>ReactElement</tt> in <tt>element</tt> of the specified
|
||||
* <tt>type</tt>.
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_replaceFirstElementOfType(element, type, replacement) {
|
||||
// eslint-disable-next-line no-shadow
|
||||
return this._mapReactElement(element, element => {
|
||||
if (replacement && element.type === type) {
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
element = replacement;
|
||||
replacement = undefined;
|
||||
|
||||
/* eslint-enable no-param-reassign */
|
||||
}
|
||||
|
||||
return element;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
@ -8,7 +9,6 @@ import StatelessDialog from './StatelessDialog';
|
|||
* Web dialog that uses atlaskit modal-dialog to display dialogs.
|
||||
*/
|
||||
class Dialog extends AbstractDialog {
|
||||
|
||||
/**
|
||||
* Web dialog component's property types.
|
||||
*
|
||||
|
@ -17,21 +17,16 @@ class Dialog extends AbstractDialog {
|
|||
static propTypes = {
|
||||
...AbstractDialog.propTypes,
|
||||
|
||||
/**
|
||||
* This is the body of the dialog, the component children.
|
||||
*/
|
||||
children: React.PropTypes.node,
|
||||
|
||||
/**
|
||||
* Whether the dialog is modal. This means clicking on the blanket will
|
||||
* leave the dialog open. No cancel button.
|
||||
*/
|
||||
isModal: React.PropTypes.bool,
|
||||
isModal: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Disables rendering of the submit button.
|
||||
*/
|
||||
submitDisabled: React.PropTypes.bool,
|
||||
submitDisabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Width of the dialog, can be:
|
||||
|
@ -40,7 +35,7 @@ class Dialog extends AbstractDialog {
|
|||
* - integer value for pixel width
|
||||
* - string value for percentage
|
||||
*/
|
||||
width: React.PropTypes.string
|
||||
width: PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -65,8 +60,8 @@ class Dialog extends AbstractDialog {
|
|||
render() {
|
||||
const props = {
|
||||
...this.props,
|
||||
onSubmit: this._onSubmit,
|
||||
onCancel: this._onCancel
|
||||
onCancel: this._onCancel,
|
||||
onSubmit: this._onSubmit
|
||||
};
|
||||
|
||||
delete props.dispatch;
|
||||
|
@ -80,11 +75,7 @@ class Dialog extends AbstractDialog {
|
|||
* @returns {void}
|
||||
*/
|
||||
_onCancel() {
|
||||
if (this.props.isModal) {
|
||||
return;
|
||||
}
|
||||
|
||||
super._onCancel();
|
||||
this.props.isModal || super._onCancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const DIALOG_PROP_TYPES = {
|
||||
/**
|
||||
* Whether cancel button is disabled. Enabled by default.
|
||||
*/
|
||||
cancelDisabled: React.PropTypes.bool,
|
||||
cancelDisabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Optional i18n key to change the cancel button title.
|
||||
*/
|
||||
cancelTitleKey: React.PropTypes.string,
|
||||
cancelTitleKey: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Is ok button enabled/disabled. Enabled by default.
|
||||
*/
|
||||
okDisabled: React.PropTypes.bool,
|
||||
okDisabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Optional i18n key to change the ok button title.
|
||||
*/
|
||||
okTitleKey: React.PropTypes.string,
|
||||
okTitleKey: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The handler for onCancel event.
|
||||
*/
|
||||
onCancel: React.PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
|
||||
/**
|
||||
* The handler for the event when submitting the dialog.
|
||||
*/
|
||||
onSubmit: React.PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Used to obtain translations in children classes.
|
||||
*/
|
||||
t: React.PropTypes.func,
|
||||
t: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Key to use for showing a title.
|
||||
*/
|
||||
titleKey: React.PropTypes.string,
|
||||
titleKey: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The string to use as a title instead of {@code titleKey}. If a truthy
|
||||
* value is specified, it takes precedence over {@code titleKey} i.e.
|
||||
* the latter is unused.
|
||||
*/
|
||||
titleString: React.PropTypes.string
|
||||
titleString: PropTypes.string
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue