diff --git a/src/components/Checkbox.tsx b/src/components/Checkbox.tsx index 23bc068e8fe0..715603ea362e 100644 --- a/src/components/Checkbox.tsx +++ b/src/components/Checkbox.tsx @@ -9,7 +9,7 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; -type CheckboxProps = ChildrenProps & { +type CheckboxProps = Partial & { /** Whether checkbox is checked */ isChecked?: boolean; @@ -91,7 +91,7 @@ function Checkbox( ref={ref} style={[StyleUtils.getCheckboxPressableStyle(containerBorderRadius + 2), style]} // to align outline on focus, border-radius of pressable should be 2px more than Checkbox onKeyDown={handleSpaceKey} - role={CONST.ACCESSIBILITY_ROLE.CHECKBOX} + role={CONST.ROLE.CHECKBOX} aria-checked={isChecked} accessibilityLabel={accessibilityLabel} pressDimmingValue={1} diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js deleted file mode 100644 index 24f61c305dda..000000000000 --- a/src/components/CheckboxWithLabel.js +++ /dev/null @@ -1,146 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {useState} from 'react'; -import {View} from 'react-native'; -import _ from 'underscore'; -import useThemeStyles from '@hooks/useThemeStyles'; -import variables from '@styles/variables'; -import Checkbox from './Checkbox'; -import FormHelpMessage from './FormHelpMessage'; -import PressableWithFeedback from './Pressable/PressableWithFeedback'; -import refPropTypes from './refPropTypes'; -import Text from './Text'; - -/** - * Returns an error if the required props are not provided - * @param {Object} props - * @returns {Error|null} - */ -const requiredPropsCheck = (props) => { - if (!props.label && !props.LabelComponent) { - return new Error('One of "label" or "LabelComponent" must be provided'); - } - - if (props.label && typeof props.label !== 'string') { - return new Error('Prop "label" must be a string'); - } - - if (props.LabelComponent && typeof props.LabelComponent !== 'function') { - return new Error('Prop "LabelComponent" must be a function'); - } -}; - -const propTypes = { - /** Whether the checkbox is checked */ - isChecked: PropTypes.bool, - - /** Called when the checkbox or label is pressed */ - onInputChange: PropTypes.func, - - /** Container styles */ - style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), - - /** Text that appears next to check box */ - label: requiredPropsCheck, - - /** Component to display for label */ - LabelComponent: requiredPropsCheck, - - /** Error text to display */ - errorText: PropTypes.string, - - /** Value for checkbox. This prop is intended to be set by Form.js only */ - value: PropTypes.bool, - - /** The default value for the checkbox */ - defaultValue: PropTypes.bool, - - /** React ref being forwarded to the Checkbox input */ - forwardedRef: refPropTypes, - - /** The ID used to uniquely identify the input in a Form */ - /* eslint-disable-next-line react/no-unused-prop-types */ - inputID: PropTypes.string, - - /** Saves a draft of the input value when used in a form */ - /* eslint-disable-next-line react/no-unused-prop-types */ - shouldSaveDraft: PropTypes.bool, - - /** An accessibility label for the checkbox */ - accessibilityLabel: PropTypes.string, -}; - -const defaultProps = { - inputID: undefined, - style: [], - label: undefined, - LabelComponent: undefined, - errorText: '', - shouldSaveDraft: false, - isChecked: false, - value: undefined, - defaultValue: false, - forwardedRef: () => {}, - accessibilityLabel: undefined, - onInputChange: () => {}, -}; - -function CheckboxWithLabel(props) { - const styles = useThemeStyles(); - // We need to pick the first value that is strictly a boolean - // https://github.com/Expensify/App/issues/16885#issuecomment-1520846065 - const [isChecked, setIsChecked] = useState(() => _.find([props.value, props.defaultValue, props.isChecked], (value) => _.isBoolean(value))); - - const toggleCheckbox = () => { - const newState = !isChecked; - props.onInputChange(newState); - setIsChecked(newState); - }; - - const LabelComponent = props.LabelComponent; - - return ( - - - - - {props.label && {props.label}} - {LabelComponent && } - - - - - ); -} - -CheckboxWithLabel.propTypes = propTypes; -CheckboxWithLabel.defaultProps = defaultProps; -CheckboxWithLabel.displayName = 'CheckboxWithLabel'; - -const CheckboxWithLabelWithRef = React.forwardRef((props, ref) => ( - -)); - -CheckboxWithLabelWithRef.displayName = 'CheckboxWithLabelWithRef'; - -export default CheckboxWithLabelWithRef; diff --git a/src/components/CheckboxWithLabel.tsx b/src/components/CheckboxWithLabel.tsx new file mode 100644 index 000000000000..9660c9e1a2e5 --- /dev/null +++ b/src/components/CheckboxWithLabel.tsx @@ -0,0 +1,107 @@ +import React, {ComponentType, ForwardedRef, useState} from 'react'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; +import Checkbox from './Checkbox'; +import FormHelpMessage from './FormHelpMessage'; +import PressableWithFeedback from './Pressable/PressableWithFeedback'; +import Text from './Text'; + +type RequiredLabelProps = + | { + /** Text that appears next to check box */ + label: string; + + /** Component to display for label + * If label is provided, LabelComponent is not required + */ + LabelComponent?: ComponentType; + } + | { + /** Component to display for label */ + LabelComponent: ComponentType; + + /** Text that appears next to check box + * If LabelComponent is provided, label is not required + */ + label?: string; + }; + +type CheckboxWithLabelProps = RequiredLabelProps & { + /** Whether the checkbox is checked */ + isChecked?: boolean; + + /** Called when the checkbox or label is pressed */ + onInputChange?: (value?: boolean) => void; + + /** Container styles */ + style?: StyleProp; + + /** Error text to display */ + errorText?: string; + + /** Value for checkbox. This prop is intended to be set by Form.js only */ + value?: boolean; + + /** The default value for the checkbox */ + defaultValue?: boolean; + + /** The ID used to uniquely identify the input in a Form */ + /* eslint-disable-next-line react/no-unused-prop-types */ + inputID?: string; + + /** Saves a draft of the input value when used in a form */ + // eslint-disable-next-line react/no-unused-prop-types + shouldSaveDraft?: boolean; + + /** An accessibility label for the checkbox */ + accessibilityLabel?: string; +}; + +function CheckboxWithLabel( + {errorText = '', isChecked: isCheckedProp = false, defaultValue = false, onInputChange = () => {}, LabelComponent, label, accessibilityLabel, style, value}: CheckboxWithLabelProps, + ref: ForwardedRef, +) { + const styles = useThemeStyles(); + // We need to pick the first value that is strictly a boolean + // https://github.com/Expensify/App/issues/16885#issuecomment-1520846065 + const [isChecked, setIsChecked] = useState(() => [value, defaultValue, isCheckedProp].find((item) => typeof item === 'boolean')); + + const toggleCheckbox = () => { + onInputChange(!isChecked); + setIsChecked(!isChecked); + }; + + return ( + + + + + {label && {label}} + {LabelComponent && } + + + + + ); +} + +CheckboxWithLabel.displayName = 'CheckboxWithLabel'; + +export default React.forwardRef(CheckboxWithLabel);