Skip to content

Commit

Permalink
Merge pull request #8009 from Expensify/nat-radiobutton
Browse files Browse the repository at this point in the history
Add RadioButton components
  • Loading branch information
marcaaron authored Mar 8, 2022
2 parents 47b4541 + 67d7c43 commit 0de8590
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/components/CheckboxWithLabel.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const CheckboxWithLabel = (props) => {
]}
>
{props.label && (
<Text style={[styles.ml2]}>
<Text style={[styles.ml1]}>
{props.label}
</Text>
)}
Expand Down
49 changes: 49 additions & 0 deletions src/components/RadioButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import {View, Pressable} from 'react-native';
import PropTypes from 'prop-types';
import styles from '../styles/styles';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';

const propTypes = {
/** Whether radioButton is checked */
isChecked: PropTypes.bool.isRequired,

/** A function that is called when the box/label is pressed */
onPress: PropTypes.func.isRequired,

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

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

const defaultProps = {
hasError: false,
disabled: false,
};

const RadioButton = props => (
<Pressable
disabled={props.disabled}
onPress={props.onPress}
>
<View
style={[
styles.radioButtonContainer,
props.isChecked && styles.checkedContainer,
props.hasError && styles.borderColorDanger,
props.disabled && styles.cursorDisabled,
]}
>
<Icon src={Expensicons.Checkmark} fill="white" height={14} width={14} />
</View>
</Pressable>
);

RadioButton.propTypes = propTypes;
RadioButton.defaultProps = defaultProps;
RadioButton.displayName = 'RadioButton';

export default RadioButton;
91 changes: 91 additions & 0 deletions src/components/RadioButtonWithLabel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';
import PropTypes from 'prop-types';
import {View, TouchableOpacity} from 'react-native';
import _ from 'underscore';
import styles from '../styles/styles';
import RadioButton from './RadioButton';
import Text from './Text';
import InlineErrorText from './InlineErrorText';

const propTypes = {
/** Whether the radioButton is checked */
isChecked: PropTypes.bool.isRequired,

/** Called when the radioButton or label is pressed */
onPress: PropTypes.func.isRequired,

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

/** Text that appears next to check box */
label: PropTypes.string,

/** 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,
};

const defaultProps = {
style: [],
label: undefined,
LabelComponent: undefined,
hasError: false,
errorText: '',
};

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

if (!props.label && !LabelComponent) {
throw new Error('Must provide at least label or LabelComponent prop');
}
return (
<>
<View style={wrapperStyles}>
<RadioButton
isChecked={props.isChecked}
onPress={props.onPress}
label={props.label}
hasError={props.hasError}
/>
<TouchableOpacity
onPress={() => props.onPress()}
style={[
styles.ml3,
styles.pr2,
styles.w100,
styles.flexRow,
styles.flexWrap,
styles.flexShrink1,
styles.alignItemsCenter,
]}
>
{props.label && (
<Text style={[styles.ml1]}>
{props.label}
</Text>
)}
{LabelComponent && (<LabelComponent />)}
</TouchableOpacity>
</View>
{!_.isEmpty(props.errorText) && (
<InlineErrorText>
{props.errorText}
</InlineErrorText>
)}
</>
);
};

RadioButtonWithLabel.propTypes = propTypes;
RadioButtonWithLabel.defaultProps = defaultProps;
RadioButtonWithLabel.displayName = 'RadioButtonWithLabel';

export default RadioButtonWithLabel;
49 changes: 49 additions & 0 deletions src/components/RadioButtons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import RadioButtonWithLabel from './RadioButtonWithLabel';
import styles from '../styles/styles';

const propTypes = {
/** List of choices to display via radio buttons */
items: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
})).isRequired,

/** Callback to fire when selecting a radio button */
onPress: PropTypes.func.isRequired,
};

class RadioButtons extends React.Component {
constructor(props) {
super(props);

this.state = {
checkedValue: '',
};
}

render() {
return (
<View>
{_.map(this.props.items, item => (
<RadioButtonWithLabel
isChecked={item.value === this.state.checkedValue}
style={[styles.mt4]}
onPress={() => {
this.setState({checkedValue: item.value});
return this.props.onPress(item.value);
}}
label={item.label}
/>
))}
</View>
);
}
}

RadioButtons.propTypes = propTypes;

export default RadioButtons;
13 changes: 12 additions & 1 deletion src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -1947,12 +1947,23 @@ const styles = {
backgroundColor: colors.white,
},

radioButtonContainer: {
backgroundColor: themeColors.componentBG,
borderRadius: 10,
height: 20,
width: 20,
borderColor: themeColors.icon,
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center',
},

checkboxContainer: {
backgroundColor: themeColors.componentBG,
borderRadius: 2,
height: 20,
width: 20,
borderColor: themeColors.border,
borderColor: themeColors.icon,
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center',
Expand Down

0 comments on commit 0de8590

Please sign in to comment.