[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 { hideDialog } from '../actions';
|
||||||
import { DIALOG_PROP_TYPES } from '../constants';
|
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 {
|
export default class AbstractDialog extends Component {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Dialog component's property types.
|
* <tt>AbstractDialog</tt> React <tt>Component</tt>'s prop types.
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
...DIALOG_PROP_TYPES,
|
...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.
|
* 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
|
* @param {Object} props - The read-only React <tt>Component</tt> props with
|
||||||
* instance is to be initialized.
|
* which the new instance is to be initialized.
|
||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(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}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onCancel() {
|
_onCancel() {
|
||||||
let hide = true;
|
const { onCancel } = this.props;
|
||||||
|
|
||||||
if (this.props.onCancel) {
|
if (!onCancel || onCancel()) {
|
||||||
hide = this.props.onCancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hide) {
|
|
||||||
this.props.dispatch(hideDialog());
|
this.props.dispatch(hideDialog());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches the action when submitting the dialog.
|
* Dispatches a redux action to hide this dialog when it's submitted.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {string} value - The submitted value if any.
|
* @param {string} value - The submitted value if any.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onSubmit(value) {
|
_onSubmit(value) {
|
||||||
let hide = true;
|
const { onSubmit } = this.props;
|
||||||
|
|
||||||
if (this.props.onSubmit) {
|
if (!onSubmit || onSubmit(value)) {
|
||||||
hide = this.props.onSubmit(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hide) {
|
|
||||||
this.props.dispatch(hideDialog());
|
this.props.dispatch(hideDialog());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { TextInput } from 'react-native';
|
||||||
import Prompt from 'react-native-prompt';
|
import Prompt from 'react-native-prompt';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
@ -7,20 +9,21 @@ import { translate } from '../../i18n';
|
||||||
import AbstractDialog from './AbstractDialog';
|
import AbstractDialog from './AbstractDialog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Native dialog using Prompt.
|
* Implements <tt>AbstractDialog</tt> on react-native using <tt>Prompt</tt>.
|
||||||
*/
|
*/
|
||||||
class Dialog extends AbstractDialog {
|
class Dialog extends AbstractDialog {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Native sialog component's property types.
|
* <tt>AbstractDialog</tt>'s React <tt>Component</tt> prop types.
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
...AbstractDialog.propTypes,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* I18n key to put as body title.
|
* I18n key to put as body title.
|
||||||
*/
|
*/
|
||||||
bodyKey: React.PropTypes.string
|
bodyKey: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,27 +34,109 @@ class Dialog extends AbstractDialog {
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
cancelDisabled,
|
|
||||||
cancelTitleKey,
|
|
||||||
bodyKey,
|
bodyKey,
|
||||||
|
cancelDisabled,
|
||||||
|
cancelTitleKey = 'dialog.Cancel',
|
||||||
|
children,
|
||||||
okDisabled,
|
okDisabled,
|
||||||
okTitleKey,
|
okTitleKey = 'dialog.Ok',
|
||||||
t,
|
t,
|
||||||
titleKey
|
titleKey,
|
||||||
|
titleString
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
/* eslint-disable react/jsx-wrap-multilines */
|
||||||
<Prompt
|
|
||||||
cancelText = { cancelDisabled
|
let element
|
||||||
? undefined : t(cancelTitleKey || 'dialog.Cancel') }
|
= <Prompt
|
||||||
|
cancelText = { cancelDisabled ? undefined : t(cancelTitleKey) }
|
||||||
onCancel = { this._onCancel }
|
onCancel = { this._onCancel }
|
||||||
onSubmit = { this._onSubmit }
|
onSubmit = { this._onSubmit }
|
||||||
placeholder = { t(bodyKey) }
|
placeholder = { t(bodyKey) }
|
||||||
submitText = { okDisabled
|
submitText = { okDisabled ? undefined : t(okTitleKey) }
|
||||||
? undefined : t(okTitleKey || 'dialog.Ok') }
|
title = { titleString || t(titleKey) }
|
||||||
title = { t(titleKey) }
|
visible = { true } />;
|
||||||
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 React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
@ -8,7 +9,6 @@ import StatelessDialog from './StatelessDialog';
|
||||||
* Web dialog that uses atlaskit modal-dialog to display dialogs.
|
* Web dialog that uses atlaskit modal-dialog to display dialogs.
|
||||||
*/
|
*/
|
||||||
class Dialog extends AbstractDialog {
|
class Dialog extends AbstractDialog {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web dialog component's property types.
|
* Web dialog component's property types.
|
||||||
*
|
*
|
||||||
|
@ -17,21 +17,16 @@ class Dialog extends AbstractDialog {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
...AbstractDialog.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
|
* Whether the dialog is modal. This means clicking on the blanket will
|
||||||
* leave the dialog open. No cancel button.
|
* leave the dialog open. No cancel button.
|
||||||
*/
|
*/
|
||||||
isModal: React.PropTypes.bool,
|
isModal: PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables rendering of the submit button.
|
* Disables rendering of the submit button.
|
||||||
*/
|
*/
|
||||||
submitDisabled: React.PropTypes.bool,
|
submitDisabled: PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Width of the dialog, can be:
|
* Width of the dialog, can be:
|
||||||
|
@ -40,7 +35,7 @@ class Dialog extends AbstractDialog {
|
||||||
* - integer value for pixel width
|
* - integer value for pixel width
|
||||||
* - string value for percentage
|
* - string value for percentage
|
||||||
*/
|
*/
|
||||||
width: React.PropTypes.string
|
width: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,8 +60,8 @@ class Dialog extends AbstractDialog {
|
||||||
render() {
|
render() {
|
||||||
const props = {
|
const props = {
|
||||||
...this.props,
|
...this.props,
|
||||||
onSubmit: this._onSubmit,
|
onCancel: this._onCancel,
|
||||||
onCancel: this._onCancel
|
onSubmit: this._onSubmit
|
||||||
};
|
};
|
||||||
|
|
||||||
delete props.dispatch;
|
delete props.dispatch;
|
||||||
|
@ -80,11 +75,7 @@ class Dialog extends AbstractDialog {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onCancel() {
|
_onCancel() {
|
||||||
if (this.props.isModal) {
|
this.props.isModal || super._onCancel();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
super._onCancel();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,50 +1,50 @@
|
||||||
import React from 'react';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export const DIALOG_PROP_TYPES = {
|
export const DIALOG_PROP_TYPES = {
|
||||||
/**
|
/**
|
||||||
* Whether cancel button is disabled. Enabled by default.
|
* Whether cancel button is disabled. Enabled by default.
|
||||||
*/
|
*/
|
||||||
cancelDisabled: React.PropTypes.bool,
|
cancelDisabled: PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional i18n key to change the cancel button title.
|
* Optional i18n key to change the cancel button title.
|
||||||
*/
|
*/
|
||||||
cancelTitleKey: React.PropTypes.string,
|
cancelTitleKey: PropTypes.string,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is ok button enabled/disabled. Enabled by default.
|
* Is ok button enabled/disabled. Enabled by default.
|
||||||
*/
|
*/
|
||||||
okDisabled: React.PropTypes.bool,
|
okDisabled: PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional i18n key to change the ok button title.
|
* Optional i18n key to change the ok button title.
|
||||||
*/
|
*/
|
||||||
okTitleKey: React.PropTypes.string,
|
okTitleKey: PropTypes.string,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The handler for onCancel event.
|
* The handler for onCancel event.
|
||||||
*/
|
*/
|
||||||
onCancel: React.PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The handler for the event when submitting the dialog.
|
* The handler for the event when submitting the dialog.
|
||||||
*/
|
*/
|
||||||
onSubmit: React.PropTypes.func,
|
onSubmit: PropTypes.func,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to obtain translations in children classes.
|
* Used to obtain translations in children classes.
|
||||||
*/
|
*/
|
||||||
t: React.PropTypes.func,
|
t: PropTypes.func,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key to use for showing a title.
|
* 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
|
* 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.
|
* value is specified, it takes precedence over {@code titleKey} i.e.
|
||||||
* the latter is unused.
|
* the latter is unused.
|
||||||
*/
|
*/
|
||||||
titleString: React.PropTypes.string
|
titleString: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue