Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename TextInputFocusable to Composer ⌨️ & add Stories For TextInputs and fix bugs #7984

Merged
merged 17 commits into from
Mar 11, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import Onyx from 'react-native-onyx';
import '../assets/css/fonts.css';
import ComposeProviders from '../src/components/ComposeProviders';
import HTMLEngineProvider from '../src/components/HTMLEngineProvider';
import OnyxProvider from '../src/components/OnyxProvider';
import {LocaleContextProvider} from '../src/components/withLocalize';
import ONYXKEYS from '../src/ONYXKEYS';
Expand All @@ -16,6 +17,7 @@ const decorators = [
components={[
OnyxProvider,
LocaleContextProvider,
HTMLEngineProvider,
]}
>
<Story />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const defaultProps = {
forwardedRef: null,
};

class TextInputFocusable extends React.Component {
class Composer extends React.Component {
componentDidMount() {
// This callback prop is used by the parent component using the constructor to
// get a ref to the inner textInput element e.g. if we do
Expand Down Expand Up @@ -76,11 +76,11 @@ class TextInputFocusable extends React.Component {
}
}

TextInputFocusable.displayName = 'TextInputFocusable';
TextInputFocusable.propTypes = propTypes;
TextInputFocusable.defaultProps = defaultProps;
Composer.displayName = 'Composer';
Composer.propTypes = propTypes;
Composer.defaultProps = defaultProps;

export default React.forwardRef((props, ref) => (
/* eslint-disable-next-line react/jsx-props-no-spreading */
<TextInputFocusable {...props} forwardedRef={ref} />
<Composer {...props} forwardedRef={ref} />
));
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const defaultProps = {
},
};

class TextInputFocusable extends React.Component {
class Composer extends React.Component {
componentDidMount() {
// This callback prop is used by the parent component using the constructor to
// get a ref to the inner textInput element e.g. if we do
Expand Down Expand Up @@ -88,10 +88,10 @@ class TextInputFocusable extends React.Component {
}
}

TextInputFocusable.propTypes = propTypes;
TextInputFocusable.defaultProps = defaultProps;
Composer.propTypes = propTypes;
Composer.defaultProps = defaultProps;

export default React.forwardRef((props, ref) => (
/* eslint-disable-next-line react/jsx-props-no-spreading */
<TextInputFocusable {...props} forwardedRef={ref} />
<Composer {...props} forwardedRef={ref} />
));
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,10 @@ const IMAGE_EXTENSIONS = {
};

/**
* Enable Markdown parsing.
* On web we like to have the Text Input field always focused so the user can easily type a new chat
*/
class TextInputFocusable extends React.Component {
class Composer extends React.Component {
constructor(props) {
super(props);

Expand Down Expand Up @@ -197,7 +198,6 @@ class TextInputFocusable extends React.Component {
* Handles all types of drag-N-drop events on the composer
*
* @param {Object} e native Event
* @memberof TextInputFocusable
*/
dragNDropListener(e) {
let isOriginComposer = false;
Expand Down Expand Up @@ -237,7 +237,6 @@ class TextInputFocusable extends React.Component {
* Manually place the pasted HTML into Composer
*
* @param {String} html - pasted HTML
* @memberof TextInputFocusable
*/
handlePastedHTML(html) {
const parser = new ExpensiMark();
Expand Down Expand Up @@ -285,14 +284,14 @@ class TextInputFocusable extends React.Component {
.then((x) => {
const extension = IMAGE_EXTENSIONS[x.type];
if (!extension) {
throw new Error(this.props.translate('textInputFocusable.noExtentionFoundForMimeType'));
throw new Error(this.props.translate('composer.noExtentionFoundForMimeType'));
}

return new File([x], `pasted_image.${extension}`, {});
})
.then(this.props.onPasteFile)
.catch(() => {
const errorDesc = this.props.translate('textInputFocusable.problemGettingImageYouPasted');
const errorDesc = this.props.translate('composer.problemGettingImageYouPasted');
Growl.error(errorDesc);

/*
Expand Down Expand Up @@ -366,10 +365,10 @@ class TextInputFocusable extends React.Component {
}
}

TextInputFocusable.propTypes = propTypes;
TextInputFocusable.defaultProps = defaultProps;
Composer.propTypes = propTypes;
Composer.defaultProps = defaultProps;

export default withLocalize(React.forwardRef((props, ref) => (
/* eslint-disable-next-line react/jsx-props-no-spreading */
<TextInputFocusable {...props} forwardedRef={ref} />
<Composer {...props} forwardedRef={ref} />
)));
4 changes: 2 additions & 2 deletions src/components/EmojiPicker/EmojiPickerMenu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import themeColors from '../../../styles/themes/default';
import emojis from '../../../../assets/emojis';
import EmojiPickerMenuItem from '../EmojiPickerMenuItem';
import Text from '../../Text';
import TextInputFocusable from '../../TextInputFocusable';
import Composer from '../../Composer';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../withWindowDimensions';
import withLocalize, {withLocalizePropTypes} from '../../withLocalize';
import compose from '../../../libs/compose';
Expand Down Expand Up @@ -434,7 +434,7 @@ class EmojiPickerMenu extends Component {
>
{!this.props.isSmallScreenWidth && (
<View style={[styles.pt4, styles.ph4, styles.pb1]}>
<TextInputFocusable
<Composer
textAlignVertical="top"
placeholder={this.props.translate('common.search')}
placeholderTextColor={themeColors.textSupporting}
Expand Down
53 changes: 35 additions & 18 deletions src/components/TextInput/BaseTextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ class BaseTextInput extends Component {
constructor(props) {
super(props);

this.value = props.value || props.defaultValue || '';
const activeLabel = props.forceActiveLabel || this.value.length > 0 || props.prefixCharacter;
const value = props.value || props.defaultValue || '';
const activeLabel = props.forceActiveLabel || value.length > 0 || props.prefixCharacter;

this.state = {
isFocused: false,
Expand All @@ -29,6 +29,7 @@ class BaseTextInput extends Component {
passwordHidden: props.secureTextEntry,
textInputWidth: 0,
prefixWidth: 0,
value,
};

this.input = null;
Expand Down Expand Up @@ -61,12 +62,13 @@ class BaseTextInput extends Component {
componentDidUpdate() {
// Activate or deactivate the label when value is changed programmatically from outside
// Only update when value prop is provided
if (this.props.value === undefined || this.value === this.props.value) {
if (this.props.value === undefined || this.state.value === this.props.value) {
return;
}

this.value = this.props.value;
this.input.setNativeProps({text: this.value});
// eslint-disable-next-line react/no-did-update-set-state
this.setState({value: this.props.value});
this.input.setNativeProps({text: this.props.value});

// In some cases, When the value prop is empty, it is not properly updated on the TextInput due to its uncontrolled nature, thus manually clearing the TextInput.
if (this.props.value === '') {
Expand Down Expand Up @@ -125,13 +127,13 @@ class BaseTextInput extends Component {
if (this.props.onInputChange) {
this.props.onInputChange(value);
}
this.value = value;
this.setState({value});
Str.result(this.props.onChangeText, value);
this.activateLabel();
}

activateLabel() {
if (this.value.length < 0 || this.isLabelActive) {
if (this.state.value.length < 0 || this.isLabelActive) {
return;
}

Expand All @@ -143,7 +145,7 @@ class BaseTextInput extends Component {
}

deactivateLabel() {
if (this.props.forceActiveLabel || this.value.length !== 0 || this.props.prefixCharacter) {
if (this.props.forceActiveLabel || this.state.value.length !== 0 || this.props.prefixCharacter) {
return;
}

Expand Down Expand Up @@ -188,6 +190,13 @@ class BaseTextInput extends Component {
const hasLabel = Boolean(this.props.label.length);
const inputHelpText = this.props.errorText || this.props.hint;
const formHelpStyles = this.props.errorText ? styles.formError : styles.formHelp;
const textInputContainerStyles = _.reduce([
styles.textInputContainer,
...this.props.textInputContainerStyles,
this.props.autoGrow && StyleUtils.getAutoGrowTextInputStyle(this.state.textInputWidth),
!this.props.hideFocusedState && this.state.isFocused && styles.borderColorFocus,
(this.props.hasError || this.props.errorText) && styles.borderColorDanger,
], (finalStyles, s) => ({...finalStyles, ...s}), {});

return (
<>
Expand All @@ -201,11 +210,10 @@ class BaseTextInput extends Component {
<TouchableWithoutFeedback onPress={this.onPress} focusable={false}>
<View
style={[
styles.textInputContainer,
...this.props.textInputContainerStyles,
this.props.autoGrow && StyleUtils.getAutoGrowTextInputStyle(this.state.textInputWidth),
!this.props.hideFocusedState && this.state.isFocused && styles.borderColorFocus,
(this.props.hasError || this.props.errorText) && styles.borderColorDanger,
textInputContainerStyles,

// When autoGrow is on and minWidth is not supplied, add a minWidth to allow the input to be focusable.
this.props.autoGrow && !textInputContainerStyles.minWidth && styles.mnw2,
NikkiWines marked this conversation as resolved.
Show resolved Hide resolved
]}
>
{hasLabel ? (
Expand Down Expand Up @@ -242,8 +250,17 @@ class BaseTextInput extends Component {
}}
// eslint-disable-next-line
{...inputProps}
defaultValue={this.value}
placeholder={(this.props.prefixCharacter || this.state.isFocused || !this.props.label) ? this.props.placeholder : null}
defaultValue={this.state.value}
placeholder={
(
NikkiWines marked this conversation as resolved.
Show resolved Hide resolved
this.props.prefixCharacter
|| this.state.isFocused
|| !hasLabel
|| (hasLabel && this.props.forceActiveLabel)
)
? this.props.placeholder
: null
}
placeholderTextColor={themeColors.placeholderText}
underlineColorAndroid="transparent"
style={[
Expand Down Expand Up @@ -293,7 +310,7 @@ class BaseTextInput extends Component {
)}
{!_.isNull(this.props.maxLength) && (
<Text style={[formHelpStyles, styles.flex1, styles.textAlignRight]}>
{this.value.length}
{this.state.value.length}
/
{this.props.maxLength}
</Text>
Expand All @@ -309,10 +326,10 @@ class BaseTextInput extends Component {
*/}
{this.props.autoGrow && (
<Text
style={[...this.props.inputStyle, styles.hiddenElementOutsideOfWindow]}
style={[...this.props.inputStyle, styles.hiddenElementOutsideOfWindow, styles.visibilityHidden]}
onLayout={e => this.setState({textInputWidth: e.nativeEvent.layout.width})}
>
{this.props.value || this.props.placeholder}
{this.state.value || this.props.placeholder}
</Text>
)}
</>
Expand Down
2 changes: 1 addition & 1 deletion src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default {
attachmentTooLarge: 'Attachment too large',
sizeExceeded: 'Attachment size is larger than 50 MB limit.',
},
textInputFocusable: {
composer: {
noExtentionFoundForMimeType: 'No extension found for mime type',
problemGettingImageYouPasted: 'There was a problem getting the image you pasted',
},
Expand Down
2 changes: 1 addition & 1 deletion src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default {
attachmentTooLarge: 'Archivo adjunto demasiado grande',
sizeExceeded: 'El archivo adjunto supera el límite de 50 MB.',
},
textInputFocusable: {
composer: {
noExtentionFoundForMimeType: 'No se encontró una extension para este tipo de contenido',
problemGettingImageYouPasted: 'Ha ocurrido un problema al obtener la imagen que has pegado',
},
Expand Down
4 changes: 2 additions & 2 deletions src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {withOnyx} from 'react-native-onyx';
import lodashIntersection from 'lodash/intersection';
import styles from '../../../styles/styles';
import themeColors from '../../../styles/themes/default';
import TextInputFocusable from '../../../components/TextInputFocusable';
import Composer from '../../../components/Composer';
import ONYXKEYS from '../../../ONYXKEYS';
import Icon from '../../../components/Icon';
import * as Expensicons from '../../../components/Icon/Expensicons';
Expand Down Expand Up @@ -510,7 +510,7 @@ class ReportActionCompose extends React.Component {
</>
)}
</AttachmentPicker>
<TextInputFocusable
<Composer
autoFocus={this.shouldFocusInputOnScreenFocus || _.size(this.props.reportActions) === 1}
multiline
ref={this.setTextInputRef}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/home/report/ReportActionItemMessageEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import _ from 'underscore';
import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import reportActionPropTypes from './reportActionPropTypes';
import styles from '../../../styles/styles';
import TextInputFocusable from '../../../components/TextInputFocusable';
import Composer from '../../../components/Composer';
import * as Report from '../../../libs/actions/Report';
import * as ReportScrollManager from '../../../libs/ReportScrollManager';
import toggleReportActionComposeView from '../../../libs/toggleReportActionComposeView';
Expand Down Expand Up @@ -158,7 +158,7 @@ class ReportActionItemMessageEdit extends React.Component {
return (
<View style={styles.chatItemMessage}>
<View style={[styles.chatItemComposeBox, styles.flexRow, styles.chatItemComposeBoxColor]}>
<TextInputFocusable
<Composer
multiline
ref={(el) => {
this.textInput = el;
Expand Down
Loading