Skip to content
8 changes: 8 additions & 0 deletions src/components/Checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,25 @@ const propTypes = {

/** Should the input be disabled */
disabled: PropTypes.bool,

/** A ref to forward to the Pressable */
forwardedRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({current: PropTypes.instanceOf(React.Component)}),
]),
};

const defaultProps = {
hasError: false,
disabled: false,
forwardedRef: undefined,
Comment thread
thesahindia marked this conversation as resolved.
};

const Checkbox = props => (
<Pressable
disabled={props.disabled}
onPress={() => props.onPress(!props.isChecked)}
ref={props.forwardedRef}
>
<View
style={[
Expand Down
46 changes: 37 additions & 9 deletions src/components/CheckboxWithLabel.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import styles from '../styles/styles';
import Checkbox from './Checkbox';
import Text from './Text';
import InlineErrorText from './InlineErrorText';
import * as FormUtils from '../libs/FormUtils';

const propTypes = {
/** Whether the checkbox is checked */
Expand All @@ -14,6 +15,9 @@ const propTypes = {
/** Called when the checkbox or label is pressed */
onPress: PropTypes.func.isRequired,

/** Called when the checkbox or label is pressed */
onChange: PropTypes.func,

/** Container styles */
style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),

Expand All @@ -23,26 +27,46 @@ const propTypes = {
/** Component to display for label */
LabelComponent: PropTypes.func,

/** Should the input be styled for errors */
hasError: PropTypes.bool,

/** Error text to display */
errorText: PropTypes.string,

/** Indicates that the input is being used with the Form component */
isFormInput: PropTypes.bool,

/**
* The ID used to uniquely identify the input
*
* @param {Object} props - props passed to the input
* @returns {Object} - returns an Error object if isFormInput is supplied but inputID is falsey or not a string
*/
inputID: props => FormUtils.getInputIDPropTypes(props),

/** Saves a draft of the input value when used in a form */
shouldSaveDraft: PropTypes.bool,
};

const defaultProps = {
onChange: () => {},
isFormInput: false,
inputID: undefined,
style: [],
label: undefined,
LabelComponent: undefined,
hasError: false,
errorText: '',
shouldSaveDraft: false,
};

const CheckboxWithLabel = (props) => {
const CheckboxWithLabel = React.forwardRef((props, ref) => {
const LabelComponent = props.LabelComponent;
const defaultStyles = [styles.flexRow, styles.alignItemsCenter];
const wrapperStyles = _.isArray(props.style) ? [...defaultStyles, ...props.style] : [...defaultStyles, props.style];

function toggleCheckbox() {
Comment thread
thesahindia marked this conversation as resolved.
props.onPress(!props.isChecked);
props.onChange(!props.isChecked);
}

if (!props.label && !LabelComponent) {
throw new Error('Must provide at least label or LabelComponent prop');
}
Expand All @@ -51,12 +75,16 @@ const CheckboxWithLabel = (props) => {
<View style={wrapperStyles}>
<Checkbox
isChecked={props.isChecked}
onPress={() => props.onPress(!props.isChecked)}
onPress={toggleCheckbox}
label={props.label}
hasError={props.hasError}
hasError={Boolean(props.errorText)}
forwardedRef={ref}
isFormInput={props.isFormInput}
inputID={props.inputID}
shouldSaveDraft={props.shouldSaveDraft}
/>
<TouchableOpacity
onPress={() => props.onPress(!props.isChecked)}
onPress={toggleCheckbox}
style={[
styles.ml3,
styles.pr2,
Expand All @@ -76,13 +104,13 @@ const CheckboxWithLabel = (props) => {
</TouchableOpacity>
</View>
{!_.isEmpty(props.errorText) && (
<InlineErrorText>
<InlineErrorText styles={[styles.ml8]}>
{props.errorText}
</InlineErrorText>
)}
</>
);
};
});

CheckboxWithLabel.propTypes = propTypes;
CheckboxWithLabel.defaultProps = defaultProps;
Expand Down
6 changes: 5 additions & 1 deletion src/components/InlineErrorText.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import Text from './Text';
const propTypes = {
/** Text to display */
children: PropTypes.string,

/** Styling for inline error text */
styles: PropTypes.arrayOf(PropTypes.object),
};

const defaultProps = {
children: 'Error',
styles: [],
};

const InlineErrorText = (props) => {
Expand All @@ -19,7 +23,7 @@ const InlineErrorText = (props) => {
}

return (
<Text style={[styles.formError, styles.mt1]}>{props.children}</Text>
<Text style={[...props.styles, styles.formError, styles.mt1]}>{props.children}</Text>
);
};

Expand Down
1 change: 0 additions & 1 deletion src/pages/ReimbursementAccount/ACHContractStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ class ACHContractStep extends React.Component {
<Text>{this.props.translate('beneficialOwnersStep.certifyTrueAndAccurate')}</Text>
)}
errorText={this.getErrorText('certifyTrueInformation')}
hasError={this.getErrors().certifyTrueInformation}
/>
</ReimbursementAccountForm>
</>
Expand Down
1 change: 0 additions & 1 deletion src/pages/ReimbursementAccount/BankAccountStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,6 @@ class BankAccountStep extends React.Component {
</View>
)}
errorText={this.getErrorText('hasAcceptedTerms')}
hasError={this.getErrors().hasAcceptedTerms}
/>
</ReimbursementAccountForm>
)}
Expand Down
1 change: 0 additions & 1 deletion src/pages/ReimbursementAccount/CompanyStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,6 @@ class CompanyStep extends React.Component {
)}
style={[styles.mt4]}
errorText={this.getErrorText('hasNoConnectionToCannabis')}
hasError={this.getErrors().hasNoConnectionToCannabis}
/>
</ReimbursementAccountForm>
</>
Expand Down
1 change: 0 additions & 1 deletion src/pages/ReimbursementAccount/RequestorStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@ class RequestorStep extends React.Component {
</View>
)}
style={[styles.mt4]}
hasError={Boolean(this.getErrors().isControllingOfficer)}
errorText={this.getErrors().isControllingOfficer ? this.props.translate('requestorStep.isControllingOfficerError') : ''}
/>
<Text style={[styles.mt3, styles.textMicroSupporting]}>
Expand Down
1 change: 0 additions & 1 deletion src/pages/settings/Payments/AddDebitCardPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,6 @@ class DebitCardPage extends Component {
)}
style={[styles.mt4, styles.mb4]}
errorText={this.getErrorText('acceptedTerms')}
hasError={Boolean(this.state.errors.acceptedTerms)}
/>
</View>
{!_.isEmpty(this.props.addDebitCardForm.error) && (
Expand Down
23 changes: 21 additions & 2 deletions src/stories/Form.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import AddressSearch from '../components/AddressSearch';
import Form from '../components/Form';
import * as FormActions from '../libs/actions/FormActions';
import styles from '../styles/styles';
import CheckboxWithLabel from '../components/CheckboxWithLabel';
import Text from '../components/Text';

/**
Expand All @@ -15,10 +16,12 @@ import Text from '../components/Text';
const story = {
title: 'Components/Form',
component: Form,
subcomponents: {TextInput, AddressSearch},
subcomponents: {TextInput, AddressSearch, CheckboxWithLabel},
};

const Template = (args) => {
const [isChecked, setIsChecked] = useState(args.draftValues.checkbox);

// Form consumes data from Onyx, so we initialize Onyx with the necessary data here
FormActions.setIsSubmitting(args.formID, args.formState.isSubmitting);
FormActions.setServerErrorMessage(args.formID, args.formState.serverErrorMessage);
Expand Down Expand Up @@ -47,6 +50,18 @@ const Template = (args) => {
containerStyles={[styles.mt4]}
isFormInput
/>
<CheckboxWithLabel
inputID="checkbox"
isChecked={isChecked}
defaultValue={isChecked}
style={[styles.mb4, styles.mt5]}
onPress={() => { setIsChecked(prev => !prev); }}
isFormInput
shouldSaveDraft
LabelComponent={() => (
<Text>I accept the Expensify Terms of Service</Text>
Comment thread
tgolen marked this conversation as resolved.
)}
/>
</Form>
);
};
Expand Down Expand Up @@ -99,6 +114,9 @@ const defaultArgs = {
if (!values.accountNumber) {
errors.accountNumber = 'Please enter an account number';
}
if (!values.checkbox) {
errors.checkbox = 'You must accept the Terms of Service to continue';
Comment thread
tgolen marked this conversation as resolved.
}
return errors;
},
onSubmit: (values) => {
Expand All @@ -114,13 +132,14 @@ const defaultArgs = {
draftValues: {
routingNumber: '00001',
accountNumber: '1111222233331111',
checkbox: false,
},
};

Default.args = defaultArgs;
Loading.args = {...defaultArgs, formState: {isSubmitting: true}};
ServerError.args = {...defaultArgs, formState: {isSubmitting: false, serverErrorMessage: 'There was an unexpected error. Please try again later.'}};
InputError.args = {...defaultArgs, draftValues: {routingNumber: '', accountNumber: ''}};
InputError.args = {...defaultArgs, draftValues: {routingNumber: '', accountNumber: '', checkbox: false}};
WithNativeEventHandler.args = {...defaultArgs, draftValues: {routingNumber: '', accountNumber: ''}};

export default story;
Expand Down
4 changes: 4 additions & 0 deletions src/styles/utilities/spacing.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ export default {
marginLeft: 20,
},

ml8: {
marginLeft: 32,
},

mln5: {
marginLeft: -20,
},
Expand Down