diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 60a686195dc..5f338cf5c6c 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -172,6 +172,7 @@ export default { CLOSE_ACCOUNT_FORM: 'closeAccount', PROFILE_SETTINGS_FORM: 'profileSettingsForm', DISPLAY_NAME_FORM: 'displayNameForm', + NEW_ROOM_FORM: 'newRoomForm', }, // Whether we should show the compose input or not diff --git a/src/components/Form.js b/src/components/Form.js index a6320fe04b7..25db26e182f 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -174,15 +174,19 @@ class Form extends React.Component { // Look for any inputs nested in a custom component, e.g AddressForm or IdentityForm if (_.isFunction(child.type)) { - const nestedChildren = new child.type(child.props); + const childNode = new child.type(child.props); - if (!React.isValidElement(nestedChildren) || !lodashGet(nestedChildren, 'props.children')) { - return child; + // If the custom component has a render method, use it to get the nested children + const nestedChildren = _.isFunction(childNode.render) ? childNode.render() : childNode; + + // Render the custom component if it's a valid React element + // If the custom component has nested children, Loop over them and supply From props + if (React.isValidElement(nestedChildren) || lodashGet(nestedChildren, 'props.children')) { + return this.childrenWrapperWithProps(nestedChildren); } - return React.cloneElement(nestedChildren, { - children: this.childrenWrapperWithProps(lodashGet(nestedChildren, 'props.children')), - }); + // Just render the child if it's custom component not a valid React element, or if it hasn't children + return child; } // We check if the child has the inputID prop. diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index 6c015ec361d..2c27fca0e24 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -1,4 +1,5 @@ import React, {Component} from 'react'; +import _ from 'underscore'; import CONST from '../../CONST'; import withLocalize from '../withLocalize'; import TextInput from '../TextInput'; @@ -26,6 +27,11 @@ class RoomNameInput extends Component { const modifiedRoomName = RoomNameInputUtils.modifyRoomName(roomName); this.props.onChangeText(modifiedRoomName); + // if custom component has onInputChange, use it to trigger changes (Form input) + if (_.isFunction(this.props.onInputChange)) { + this.props.onInputChange(modifiedRoomName); + } + // Prevent cursor jump behaviour: // Check if newRoomNameWithHash is the same as modifiedRoomName // If it is then the room name is valid (does not contain unallowed characters); no action required @@ -65,6 +71,8 @@ class RoomNameInput extends Component { onSelectionChange={event => this.setSelection(event.nativeEvent.selection)} errorText={this.props.errorText} autoCapitalize="none" + onBlur={this.props.onBlur} + autoFocus={this.props.autoFocus} /> ); } diff --git a/src/components/RoomNameInput/index.native.js b/src/components/RoomNameInput/index.native.js index d47371a5b92..273bae8a810 100644 --- a/src/components/RoomNameInput/index.native.js +++ b/src/components/RoomNameInput/index.native.js @@ -1,4 +1,5 @@ import React, {Component} from 'react'; +import _ from 'underscore'; import CONST from '../../CONST'; import withLocalize from '../withLocalize'; import TextInput from '../TextInput'; @@ -21,6 +22,11 @@ class RoomNameInput extends Component { const roomName = event.nativeEvent.text; const modifiedRoomName = RoomNameInputUtils.modifyRoomName(roomName); this.props.onChangeText(modifiedRoomName); + + // if custom component has onInputChange, use it to trigger changes (Form input) + if (_.isFunction(this.props.onInputChange)) { + this.props.onInputChange(modifiedRoomName); + } } render() { @@ -37,6 +43,8 @@ class RoomNameInput extends Component { errorText={this.props.errorText} maxLength={CONST.REPORT.MAX_ROOM_NAME_LENGTH} keyboardType={keyboardType} // this is a bit hacky solution to a RN issue https://github.com/facebook/react-native/issues/27449 + onBlur={this.props.onBlur} + autoFocus={this.props.autoFocus} /> ); } diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js index db5ce4b3c0c..53666a6ae69 100644 --- a/src/components/RoomNameInput/roomNameInputPropTypes.js +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -18,6 +18,15 @@ const propTypes = { /** A ref forwarded to the TextInput */ forwardedRef: PropTypes.func, + + /** The ID used to uniquely identify the input in a Form */ + inputID: PropTypes.string, + + /** Callback that is called when the text input is blurred */ + onBlur: PropTypes.func, + + /** AutoFocus */ + autoFocus: PropTypes.bool, }; const defaultProps = { @@ -26,6 +35,10 @@ const defaultProps = { disabled: false, errorText: '', forwardedRef: () => {}, + + inputID: undefined, + onBlur: () => {}, + autoFocus: false, }; export {propTypes, defaultProps}; diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index 654e2ce8cc0..d4e394273db 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -1,5 +1,5 @@ import React from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; @@ -15,11 +15,10 @@ import Picker from '../../components/Picker'; import ONYXKEYS from '../../ONYXKEYS'; import CONST from '../../CONST'; import Text from '../../components/Text'; -import Button from '../../components/Button'; -import FixedFooter from '../../components/FixedFooter'; import Permissions from '../../libs/Permissions'; import Log from '../../libs/Log'; import * as ValidationUtils from '../../libs/ValidationUtils'; +import Form from '../../components/Form'; const propTypes = { /** All reports shared with the user */ @@ -48,92 +47,60 @@ class WorkspaceNewRoomPage extends React.Component { super(props); this.state = { - roomName: '', - policyID: '', - visibility: CONST.REPORT.VISIBILITY.RESTRICTED, - errors: {}, - workspaceOptions: [], + visibilityDescription: this.props.translate('newRoomPage.restrictedDescription'), }; - this.validateAndAddPolicyReport = this.validateAndAddPolicyReport.bind(this); - this.focusRoomNameInput = this.focusRoomNameInput.bind(this); + this.validate = this.validate.bind(this); + this.submit = this.submit.bind(this); + this.updateVisibilityDescription = this.updateVisibilityDescription.bind(this); } - componentDidMount() { - // Workspaces are policies with type === 'free' - const workspaces = _.filter(this.props.policies, policy => policy && policy.type === CONST.POLICY.TYPE.FREE); - this.setState({workspaceOptions: _.map(workspaces, policy => ({label: policy.name, key: policy.id, value: policy.id}))}); - } - - componentDidUpdate(prevProps) { - if (this.props.policies.length === prevProps.policies.length) { - return; - } - - // Workspaces are policies with type === 'free' - const workspaces = _.filter(this.props.policies, policy => policy && policy.type === CONST.POLICY.TYPE.FREE); - - // eslint-disable-next-line react/no-did-update-set-state - this.setState({workspaceOptions: _.map(workspaces, policy => ({label: policy.name, key: policy.id, value: policy.id}))}); + /** + * @param {Object} values - form input values passed by the Form component + */ + submit(values) { + const policyID = this.props.policies[`${ONYXKEYS.COLLECTION.POLICY}${values.policyID}`]; + Report.addPolicyReport(policyID, values.roomName, values.visibility); } - validateAndAddPolicyReport() { - if (!this.validate()) { + /** + * @param {String} visibility - form input value passed by the Form component + */ + updateVisibilityDescription(visibility) { + const visibilityDescription = this.props.translate(`newRoomPage.${visibility}Description`); + if (visibilityDescription === this.state.visibilityDescription) { return; } - const policy = this.props.policies[`${ONYXKEYS.COLLECTION.POLICY}${this.state.policyID}`]; - Report.addPolicyReport(policy, this.state.roomName, this.state.visibility); + this.setState({visibilityDescription}); } /** + * @param {Object} values - form input values passed by the Form component * @returns {Boolean} */ - validate() { + validate(values) { const errors = {}; // We error if the user doesn't enter a room name or left blank - if (!this.state.roomName || this.state.roomName === CONST.POLICY.ROOM_PREFIX) { + if (!values.roomName || values.roomName === CONST.POLICY.ROOM_PREFIX) { errors.roomName = this.props.translate('newRoomPage.pleaseEnterRoomName'); } // We error if the room name already exists. - if (ValidationUtils.isExistingRoomName(this.state.roomName, this.props.reports, this.state.policyID)) { + if (ValidationUtils.isExistingRoomName(values.roomName, this.props.reports, values.policyID)) { errors.roomName = this.props.translate('newRoomPage.roomAlreadyExistsError'); } // Certain names are reserved for default rooms and should not be used for policy rooms. - if (ValidationUtils.isReservedRoomName(this.state.roomName)) { + if (ValidationUtils.isReservedRoomName(values.roomName)) { errors.roomName = this.props.translate('newRoomPage.roomNameReservedError'); } - if (!this.state.policyID) { + if (!values.policyID) { errors.policyID = this.props.translate('newRoomPage.pleaseSelectWorkspace'); } - this.setState({errors}); - return _.isEmpty(errors); - } - - /** - * @param {String} inputKey - * @param {String} value - */ - clearErrorAndSetValue(inputKey, value) { - this.setState(prevState => ({ - [inputKey]: value, - errors: { - ...prevState.errors, - [inputKey]: '', - }, - })); - } - - focusRoomNameInput() { - if (!this.roomNameInputRef) { - return; - } - - this.roomNameInputRef.focus(); + return errors; } render() { @@ -143,6 +110,12 @@ class WorkspaceNewRoomPage extends React.Component { return null; } + // Workspaces are policies with type === 'free' + const workspaceOptions = _.map( + _.filter(this.props.policies, policy => policy && policy.type === CONST.POLICY.TYPE.FREE), + policy => ({label: policy.name, key: policy.id, value: policy.id}), + ); + const visibilityOptions = _.map(_.values(CONST.REPORT.VISIBILITY), visibilityOption => ({ label: this.props.translate(`newRoomPage.visibilityOptions.${visibilityOption}`), value: visibilityOption, @@ -150,52 +123,46 @@ class WorkspaceNewRoomPage extends React.Component { })); return ( - + Navigation.dismissModal()} /> - +
this.roomNameInputRef = el} - policyID={this.state.policyID} - errorText={this.state.errors.roomName} - onChangeText={roomName => this.clearErrorAndSetValue('roomName', roomName)} - value={this.state.roomName} + inputID="roomName" + autoFocus /> this.clearErrorAndSetValue('policyID', policyID)} + items={workspaceOptions} /> this.setState({visibility})} + onValueChange={this.updateVisibilityDescription} + defaultValue={CONST.REPORT.VISIBILITY.RESTRICTED} /> - {_.find(visibilityOptions, option => option.value === this.state.visibility).description} + {this.state.visibilityDescription} - - -