Skip to content

Commit a613b2b

Browse files
authored
Merge pull request #7984 from parasharrajat/textinput-final
Rename TextInputFocusable to `Composer` ⌨️ & add Stories For TextInputs and fix bugs
2 parents f916215 + ee1ecba commit a613b2b

File tree

16 files changed

+255
-63
lines changed

16 files changed

+255
-63
lines changed

.storybook/preview.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import Onyx from 'react-native-onyx';
33
import '../assets/css/fonts.css';
44
import ComposeProviders from '../src/components/ComposeProviders';
5+
import HTMLEngineProvider from '../src/components/HTMLEngineProvider';
56
import OnyxProvider from '../src/components/OnyxProvider';
67
import {LocaleContextProvider} from '../src/components/withLocalize';
78
import ONYXKEYS from '../src/ONYXKEYS';
@@ -16,6 +17,7 @@ const decorators = [
1617
components={[
1718
OnyxProvider,
1819
LocaleContextProvider,
20+
HTMLEngineProvider,
1921
]}
2022
>
2123
<Story />

src/components/TextInputFocusable/index.android.js renamed to src/components/Composer/index.android.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const defaultProps = {
3939
forwardedRef: null,
4040
};
4141

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

79-
TextInputFocusable.displayName = 'TextInputFocusable';
80-
TextInputFocusable.propTypes = propTypes;
81-
TextInputFocusable.defaultProps = defaultProps;
79+
Composer.displayName = 'Composer';
80+
Composer.propTypes = propTypes;
81+
Composer.defaultProps = defaultProps;
8282

8383
export default React.forwardRef((props, ref) => (
8484
/* eslint-disable-next-line react/jsx-props-no-spreading */
85-
<TextInputFocusable {...props} forwardedRef={ref} />
85+
<Composer {...props} forwardedRef={ref} />
8686
));

src/components/TextInputFocusable/index.ios.js renamed to src/components/Composer/index.ios.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const defaultProps = {
4949
},
5050
};
5151

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

91-
TextInputFocusable.propTypes = propTypes;
92-
TextInputFocusable.defaultProps = defaultProps;
91+
Composer.propTypes = propTypes;
92+
Composer.defaultProps = defaultProps;
9393

9494
export default React.forwardRef((props, ref) => (
9595
/* eslint-disable-next-line react/jsx-props-no-spreading */
96-
<TextInputFocusable {...props} forwardedRef={ref} />
96+
<Composer {...props} forwardedRef={ref} />
9797
));

src/components/TextInputFocusable/index.js renamed to src/components/Composer/index.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,10 @@ const IMAGE_EXTENSIONS = {
9999
};
100100

101101
/**
102+
* Enable Markdown parsing.
102103
* On web we like to have the Text Input field always focused so the user can easily type a new chat
103104
*/
104-
class TextInputFocusable extends React.Component {
105+
class Composer extends React.Component {
105106
constructor(props) {
106107
super(props);
107108

@@ -197,7 +198,6 @@ class TextInputFocusable extends React.Component {
197198
* Handles all types of drag-N-drop events on the composer
198199
*
199200
* @param {Object} e native Event
200-
* @memberof TextInputFocusable
201201
*/
202202
dragNDropListener(e) {
203203
let isOriginComposer = false;
@@ -237,7 +237,6 @@ class TextInputFocusable extends React.Component {
237237
* Manually place the pasted HTML into Composer
238238
*
239239
* @param {String} html - pasted HTML
240-
* @memberof TextInputFocusable
241240
*/
242241
handlePastedHTML(html) {
243242
const parser = new ExpensiMark();
@@ -285,14 +284,14 @@ class TextInputFocusable extends React.Component {
285284
.then((x) => {
286285
const extension = IMAGE_EXTENSIONS[x.type];
287286
if (!extension) {
288-
throw new Error(this.props.translate('textInputFocusable.noExtentionFoundForMimeType'));
287+
throw new Error(this.props.translate('composer.noExtentionFoundForMimeType'));
289288
}
290289

291290
return new File([x], `pasted_image.${extension}`, {});
292291
})
293292
.then(this.props.onPasteFile)
294293
.catch(() => {
295-
const errorDesc = this.props.translate('textInputFocusable.problemGettingImageYouPasted');
294+
const errorDesc = this.props.translate('composer.problemGettingImageYouPasted');
296295
Growl.error(errorDesc);
297296

298297
/*
@@ -366,10 +365,10 @@ class TextInputFocusable extends React.Component {
366365
}
367366
}
368367

369-
TextInputFocusable.propTypes = propTypes;
370-
TextInputFocusable.defaultProps = defaultProps;
368+
Composer.propTypes = propTypes;
369+
Composer.defaultProps = defaultProps;
371370

372371
export default withLocalize(React.forwardRef((props, ref) => (
373372
/* eslint-disable-next-line react/jsx-props-no-spreading */
374-
<TextInputFocusable {...props} forwardedRef={ref} />
373+
<Composer {...props} forwardedRef={ref} />
375374
)));

src/components/EmojiPicker/EmojiPickerMenu/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import themeColors from '../../../styles/themes/default';
1212
import emojis from '../../../../assets/emojis';
1313
import EmojiPickerMenuItem from '../EmojiPickerMenuItem';
1414
import Text from '../../Text';
15-
import TextInputFocusable from '../../TextInputFocusable';
15+
import Composer from '../../Composer';
1616
import withWindowDimensions, {windowDimensionsPropTypes} from '../../withWindowDimensions';
1717
import withLocalize, {withLocalizePropTypes} from '../../withLocalize';
1818
import compose from '../../../libs/compose';
@@ -432,7 +432,7 @@ class EmojiPickerMenu extends Component {
432432
>
433433
{!this.props.isSmallScreenWidth && (
434434
<View style={[styles.pt4, styles.ph4, styles.pb1]}>
435-
<TextInputFocusable
435+
<Composer
436436
textAlignVertical="top"
437437
placeholder={this.props.translate('common.search')}
438438
placeholderTextColor={themeColors.textSupporting}

src/components/TextInput/BaseTextInput.js

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ class BaseTextInput extends Component {
1919
constructor(props) {
2020
super(props);
2121

22-
this.value = props.value || props.defaultValue || '';
23-
const activeLabel = props.forceActiveLabel || this.value.length > 0 || props.prefixCharacter;
22+
const value = props.value || props.defaultValue || '';
23+
const activeLabel = props.forceActiveLabel || value.length > 0 || props.prefixCharacter;
2424

2525
this.state = {
2626
isFocused: false,
@@ -29,6 +29,7 @@ class BaseTextInput extends Component {
2929
passwordHidden: props.secureTextEntry,
3030
textInputWidth: 0,
3131
prefixWidth: 0,
32+
value,
3233
};
3334

3435
this.input = null;
@@ -61,12 +62,13 @@ class BaseTextInput extends Component {
6162
componentDidUpdate() {
6263
// Activate or deactivate the label when value is changed programmatically from outside
6364
// Only update when value prop is provided
64-
if (this.props.value === undefined || this.value === this.props.value) {
65+
if (this.props.value === undefined || this.state.value === this.props.value) {
6566
return;
6667
}
6768

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

7173
// 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.
7274
if (this.props.value === '') {
@@ -124,13 +126,13 @@ class BaseTextInput extends Component {
124126
if (this.props.onInputChange) {
125127
this.props.onInputChange(value);
126128
}
127-
this.value = value;
129+
this.setState({value});
128130
Str.result(this.props.onChangeText, value);
129131
this.activateLabel();
130132
}
131133

132134
activateLabel() {
133-
if (this.value.length < 0 || this.isLabelActive) {
135+
if (this.state.value.length < 0 || this.isLabelActive) {
134136
return;
135137
}
136138

@@ -142,7 +144,7 @@ class BaseTextInput extends Component {
142144
}
143145

144146
deactivateLabel() {
145-
if (this.props.forceActiveLabel || this.value.length !== 0 || this.props.prefixCharacter) {
147+
if (this.props.forceActiveLabel || this.state.value.length !== 0 || this.props.prefixCharacter) {
146148
return;
147149
}
148150

@@ -187,6 +189,14 @@ class BaseTextInput extends Component {
187189
const hasLabel = Boolean(this.props.label.length);
188190
const inputHelpText = this.props.errorText || this.props.hint;
189191
const formHelpStyles = this.props.errorText ? styles.formError : styles.formHelp;
192+
const placeholder = (this.props.prefixCharacter || this.state.isFocused || !hasLabel || (hasLabel && this.props.forceActiveLabel)) ? this.props.placeholder : null;
193+
const textInputContainerStyles = _.reduce([
194+
styles.textInputContainer,
195+
...this.props.textInputContainerStyles,
196+
this.props.autoGrow && StyleUtils.getAutoGrowTextInputStyle(this.state.textInputWidth),
197+
!this.props.hideFocusedState && this.state.isFocused && styles.borderColorFocus,
198+
(this.props.hasError || this.props.errorText) && styles.borderColorDanger,
199+
], (finalStyles, s) => ({...finalStyles, ...s}), {});
190200

191201
return (
192202
<>
@@ -200,11 +210,10 @@ class BaseTextInput extends Component {
200210
<TouchableWithoutFeedback onPress={this.onPress} focusable={false}>
201211
<View
202212
style={[
203-
styles.textInputContainer,
204-
...this.props.textInputContainerStyles,
205-
this.props.autoGrow && StyleUtils.getAutoGrowTextInputStyle(this.state.textInputWidth),
206-
!this.props.hideFocusedState && this.state.isFocused && styles.borderColorFocus,
207-
(this.props.hasError || this.props.errorText) && styles.borderColorDanger,
213+
textInputContainerStyles,
214+
215+
// When autoGrow is on and minWidth is not supplied, add a minWidth to allow the input to be focusable.
216+
this.props.autoGrow && !textInputContainerStyles.minWidth && styles.mnw2,
208217
]}
209218
>
210219
{hasLabel ? (
@@ -241,8 +250,8 @@ class BaseTextInput extends Component {
241250
}}
242251
// eslint-disable-next-line
243252
{...inputProps}
244-
defaultValue={this.value}
245-
placeholder={(this.props.prefixCharacter || this.state.isFocused || !this.props.label) ? this.props.placeholder : null}
253+
defaultValue={this.state.value}
254+
placeholder={placeholder}
246255
placeholderTextColor={themeColors.placeholderText}
247256
underlineColorAndroid="transparent"
248257
style={[
@@ -292,7 +301,7 @@ class BaseTextInput extends Component {
292301
)}
293302
{!_.isNull(this.props.maxLength) && (
294303
<Text style={[formHelpStyles, styles.flex1, styles.textAlignRight]}>
295-
{this.value.length}
304+
{this.state.value.length}
296305
/
297306
{this.props.maxLength}
298307
</Text>
@@ -308,10 +317,10 @@ class BaseTextInput extends Component {
308317
*/}
309318
{this.props.autoGrow && (
310319
<Text
311-
style={[...this.props.inputStyle, styles.hiddenElementOutsideOfWindow]}
320+
style={[...this.props.inputStyle, styles.hiddenElementOutsideOfWindow, styles.visibilityHidden]}
312321
onLayout={e => this.setState({textInputWidth: e.nativeEvent.layout.width})}
313322
>
314-
{this.props.value || this.props.placeholder}
323+
{this.state.value || this.props.placeholder}
315324
</Text>
316325
)}
317326
</>

src/components/TextInput/baseTextInputPropTypes.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ const propTypes = {
5757
prefixCharacter: PropTypes.string,
5858

5959
/** Form props */
60-
6160
/** Indicates that the input is being used with the Form component */
6261
isFormInput: PropTypes.bool,
6362

src/languages/en.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export default {
112112
attachmentTooLarge: 'Attachment too large',
113113
sizeExceeded: 'Attachment size is larger than 50 MB limit.',
114114
},
115-
textInputFocusable: {
115+
composer: {
116116
noExtentionFoundForMimeType: 'No extension found for mime type',
117117
problemGettingImageYouPasted: 'There was a problem getting the image you pasted',
118118
},

src/languages/es.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export default {
112112
attachmentTooLarge: 'Archivo adjunto demasiado grande',
113113
sizeExceeded: 'El archivo adjunto supera el límite de 50 MB.',
114114
},
115-
textInputFocusable: {
115+
composer: {
116116
noExtentionFoundForMimeType: 'No se encontró una extension para este tipo de contenido',
117117
problemGettingImageYouPasted: 'Ha ocurrido un problema al obtener la imagen que has pegado',
118118
},

src/pages/home/report/ReportActionCompose.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {withOnyx} from 'react-native-onyx';
1212
import lodashIntersection from 'lodash/intersection';
1313
import styles from '../../../styles/styles';
1414
import themeColors from '../../../styles/themes/default';
15-
import TextInputFocusable from '../../../components/TextInputFocusable';
15+
import Composer from '../../../components/Composer';
1616
import ONYXKEYS from '../../../ONYXKEYS';
1717
import Icon from '../../../components/Icon';
1818
import * as Expensicons from '../../../components/Icon/Expensicons';
@@ -514,7 +514,7 @@ class ReportActionCompose extends React.Component {
514514
</>
515515
)}
516516
</AttachmentPicker>
517-
<TextInputFocusable
517+
<Composer
518518
autoFocus={this.shouldFocusInputOnScreenFocus || _.size(this.props.reportActions) === 1}
519519
multiline
520520
ref={this.setTextInputRef}

src/pages/home/report/ReportActionItemMessageEdit.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import _ from 'underscore';
55
import ExpensiMark from 'expensify-common/lib/ExpensiMark';
66
import reportActionPropTypes from './reportActionPropTypes';
77
import styles from '../../../styles/styles';
8-
import TextInputFocusable from '../../../components/TextInputFocusable';
8+
import Composer from '../../../components/Composer';
99
import * as Report from '../../../libs/actions/Report';
1010
import * as ReportScrollManager from '../../../libs/ReportScrollManager';
1111
import toggleReportActionComposeView from '../../../libs/toggleReportActionComposeView';
@@ -158,7 +158,7 @@ class ReportActionItemMessageEdit extends React.Component {
158158
return (
159159
<View style={styles.chatItemMessage}>
160160
<View style={[styles.chatItemComposeBox, styles.flexRow, styles.chatItemComposeBoxColor]}>
161-
<TextInputFocusable
161+
<Composer
162162
multiline
163163
ref={(el) => {
164164
this.textInput = el;

0 commit comments

Comments
 (0)