diff --git a/components/ActionSheet/ActionSheet.js b/components/ActionSheet/ActionSheet.js index 79532cbf..90ead9f5 100644 --- a/components/ActionSheet/ActionSheet.js +++ b/components/ActionSheet/ActionSheet.js @@ -33,14 +33,6 @@ class ActionSheet extends PureComponent { return null; } - static propTypes = { - style: PropTypes.any, - confirmOptions: PropTypes.arrayOf(optionPropType), - cancelOptions: PropTypes.arrayOf(optionPropType), - active: PropTypes.bool, - onDismiss: PropTypes.func, - }; - constructor(props) { super(props); @@ -187,4 +179,19 @@ class ActionSheet extends PureComponent { } } +ActionSheet.propTypes = { + style: PropTypes.object.isRequired, + active: PropTypes.bool, + cancelOptions: PropTypes.arrayOf(optionPropType), + confirmOptions: PropTypes.arrayOf(optionPropType), + onDismiss: PropTypes.func, +}; + +ActionSheet.defaultProps = { + active: false, + cancelOptions: undefined, + confirmOptions: undefined, + onDismiss: undefined, +}; + export default connectStyle('shoutem.ui.ActionSheet')(ActionSheet); diff --git a/components/ActionSheet/ActionSheetOption.js b/components/ActionSheet/ActionSheetOption.js index 1e884aa5..e89a053b 100644 --- a/components/ActionSheet/ActionSheetOption.js +++ b/components/ActionSheet/ActionSheetOption.js @@ -26,9 +26,14 @@ function ActionSheetOption({ style, option, cancelOption }) { } ActionSheetOption.propTypes = { - style: PropTypes.any, - option: optionPropType, + style: PropTypes.object.isRequired, cancelOption: PropTypes.bool, + option: optionPropType, +}; + +ActionSheetOption.defaultProps = { + option: undefined, + cancelOption: false, }; export default connectStyle('shoutem.ui.ActionSheetOption')(ActionSheetOption); diff --git a/components/Button.js b/components/Button.js index 662c4168..eabcc9e5 100644 --- a/components/Button.js +++ b/components/Button.js @@ -5,18 +5,14 @@ import { connectStyle } from '@shoutem/theme'; class Button extends PureComponent { render() { - // The underlayColor is not a valid RN style - // property, so we have to unset it here. - const style = { - ...this.props.style, - }; - delete style.underlayColor; + const { style, ...otherProps } = this.props; + const { underlayColor, ...otherStyle } = style; return ( ); } diff --git a/components/CategoryPicker/Category.js b/components/CategoryPicker/Category.js new file mode 100644 index 00000000..8e0cec2b --- /dev/null +++ b/components/CategoryPicker/Category.js @@ -0,0 +1,45 @@ +import React, { useMemo } from 'react'; +import PropTypes from 'prop-types'; +import { connectStyle } from '@shoutem/theme'; +import { Text } from '../Text'; +import { TouchableOpacity } from '../TouchableOpacity'; +import { categoryShape } from './shapes'; + +function Category({ category, style, isSelected, onPress }) { + const textStyle = useMemo( + () => [style.category, !isSelected && style.selectedCategory], + + [isSelected, style.category, style.selectedCategory], + ); + + function handlePress() { + if (isSelected) { + return; + } + + if (onPress) { + onPress(category); + } + } + + return ( + + {category.name} + + ); +} + +Category.propTypes = { + category: categoryShape.isRequired, + isSelected: PropTypes.bool, + style: PropTypes.object, + onPress: PropTypes.func, +}; + +Category.defaultProps = { + style: {}, + isSelected: false, + onPress: undefined, +}; + +export default React.memo(connectStyle('shoutem.ui.Category')(Category)); diff --git a/components/CategoryPicker/CategoryPicker.js b/components/CategoryPicker/CategoryPicker.js new file mode 100644 index 00000000..f34fa382 --- /dev/null +++ b/components/CategoryPicker/CategoryPicker.js @@ -0,0 +1,62 @@ +import React, { useCallback } from 'react'; +import { FlatList } from 'react-native'; +import _ from 'lodash'; +import PropTypes from 'prop-types'; +import { connectStyle } from '@shoutem/theme'; +import { View } from '../View'; +import Category from './Category'; +import { categoryShape } from './shapes'; + +export function CategoryPicker({ + categories, + onCategorySelected, + style, + selectedCategory, +}) { + const renderItem = useCallback( + ({ item: category }) => ( + + ), + [selectedCategory.id, onCategorySelected], + ); + + if (_.size(categories) < 2) { + return null; + } + + return ( + + + + ); +} + +CategoryPicker.propTypes = { + categories: PropTypes.arrayOf(categoryShape), + selectedCategory: categoryShape, + style: PropTypes.object, + onCategorySelected: PropTypes.func, +}; + +CategoryPicker.defaultProps = { + categories: [], + style: {}, + selectedCategory: undefined, + onCategorySelected: undefined, +}; + +export default React.memo( + connectStyle('shoutem.ui.CategoryPicker')(CategoryPicker), +); diff --git a/components/CategoryPicker/index.js b/components/CategoryPicker/index.js new file mode 100644 index 00000000..2a8495e3 --- /dev/null +++ b/components/CategoryPicker/index.js @@ -0,0 +1 @@ +export { default as CategoryPicker } from './CategoryPicker'; diff --git a/components/CategoryPicker/shapes.js b/components/CategoryPicker/shapes.js new file mode 100644 index 00000000..89d56db8 --- /dev/null +++ b/components/CategoryPicker/shapes.js @@ -0,0 +1,7 @@ +import PropTypes from 'prop-types'; + +export const categoryShape = PropTypes.shape({ + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + name: PropTypes.string.isRequired, + description: PropTypes.string, +}); diff --git a/components/DateTimePicker.js b/components/DateTimePicker.js index bde0530a..1757a4f2 100644 --- a/components/DateTimePicker.js +++ b/components/DateTimePicker.js @@ -198,13 +198,14 @@ class DateTimePicker extends PureComponent { } DateTimePicker.propTypes = { + style: PropTypes.object.isRequired, cancelButtonText: PropTypes.string, confirmButtonText: PropTypes.string, is24Hour: PropTypes.bool, mode: PropTypes.oneOf(Object.values(DATEPICKER_MODES)), - onValueChanged: PropTypes.func, textValue: PropTypes.string, value: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), + onValueChanged: PropTypes.func, }; DateTimePicker.defaultProps = { @@ -212,7 +213,9 @@ DateTimePicker.defaultProps = { confirmButtonText: 'Confirm', is24Hour: false, mode: 'datetime', + textValue: undefined, value: new Date(), + onValueChanged: undefined, }; const StyledDateTimePicker = connectStyle('shoutem.ui.DateTimePicker')( diff --git a/components/DropDownMenu/DropDownMenu.js b/components/DropDownMenu/DropDownMenu.js index a2b92b27..d0dc0ac2 100644 --- a/components/DropDownMenu/DropDownMenu.js +++ b/components/DropDownMenu/DropDownMenu.js @@ -10,31 +10,8 @@ import { View } from '../View'; import { DropDownModal } from './DropDownModal'; const modalSpecificProps = ['visible', 'onClose']; -const dropDownMenuPropTypes = { - ..._.omit(DropDownModal.propTypes, modalSpecificProps), -}; class DropDownMenu extends PureComponent { - /** - * @see DropDownModal.propTypes - */ - static propTypes = { - /** - * Icon displayed on dropdown menu button - */ - iconName: PropTypes.string, - /** - * Whether the text should be displayed next to dropdown icon or not - */ - showSelectedOption: PropTypes.bool, - ...dropDownMenuPropTypes, - }; - - static defaultProps = { - iconName: 'drop-down', - showSelectedOption: true, - }; - constructor(props) { super(props); @@ -105,6 +82,26 @@ class DropDownMenu extends PureComponent { } } +/** + * @see DropDownModal.propTypes + */ +DropDownMenu.propTypes = { + /** + * Icon displayed on dropdown menu button + */ + iconName: PropTypes.string, + /** + * Whether the text should be displayed next to dropdown icon or not + */ + showSelectedOption: PropTypes.bool, + ..._.omit(DropDownModal.propTypes, modalSpecificProps), +}; + +DropDownMenu.defaultProps = { + iconName: 'drop-down', + showSelectedOption: true, +}; + const StyledDropDownMenu = connectStyle('shoutem.ui.DropDownMenu')( DropDownMenu, ); diff --git a/components/DropDownMenu/DropDownModal.js b/components/DropDownMenu/DropDownModal.js index a5574f80..2ac1ce14 100644 --- a/components/DropDownMenu/DropDownModal.js +++ b/components/DropDownMenu/DropDownModal.js @@ -15,57 +15,6 @@ import { View } from '../View'; const window = Dimensions.get('window'); class DropDownModal extends PureComponent { - static propTypes = { - /** - * Callback that is called when dropdown option is selected - */ - onOptionSelected: PropTypes.func, - /** - * Collection of objects which will be shown as options in DropDownMenu - */ - options: PropTypes.array.isRequired, - /** - * Selected option that will be shown. - */ - selectedOption: PropTypes.any.isRequired, - /** - * Key name that represents option's string value, - * and it will be displayed to the user in the UI - */ - titleProperty: PropTypes.string.isRequired, - /** - * Key name that represents option's value - */ - valueProperty: PropTypes.string.isRequired, - /** - * Number of options shown without scroll. - * Can be set trough DropDown style.visibleOptions. - * Prop definition overrides style. - */ - visibleOptions: PropTypes.number, - /** - * Optional render function, for every item in the list. - * Input parameter should be shaped as one of the items from the - * options object - */ - renderOption: PropTypes.func, - /** - * Visibility flag, controling the modal visibility - */ - visible: PropTypes.bool, - /** - * Callback that is called when modal should be closed - */ - onClose: PropTypes.func, - style: PropTypes.object, - }; - - static defaultProps = { - renderOption: (option, titleProperty) => ( - {option[titleProperty].toUpperCase()} - ), - }; - static DEFAULT_VISIBLE_OPTIONS = 8; constructor(props) { @@ -111,14 +60,16 @@ class DropDownModal extends PureComponent { close() { const { onClose } = this.props; - if (onClose) { + if (_.isFunction(onClose)) { onClose(); } } emitOnOptionSelectedEvent(option) { - if (this.props.onOptionSelected) { - this.props.onOptionSelected(option); + const { onOptionSelected } = this.props; + + if (_.isFunction(onOptionSelected)) { + onOptionSelected(option); } } @@ -299,6 +250,57 @@ class DropDownModal extends PureComponent { } } +DropDownModal.propTypes = { + /** + * Collection of objects which will be shown as options in DropDownMenu + */ + options: PropTypes.array.isRequired, + /** + * Selected option that will be shown. + */ + selectedOption: PropTypes.any.isRequired, + style: PropTypes.object.isRequired, + /** + * Key name that represents option's string value, + * and it will be displayed to the user in the UI + */ + titleProperty: PropTypes.string.isRequired, + /** + * Optional render function, for every item in the list. + * Input parameter should be shaped as one of the items from the + * options object + */ + renderOption: PropTypes.func, + /** + * Visibility flag, controling the modal visibility + */ + visible: PropTypes.bool, + /** + * Number of options shown without scroll. + * Can be set trough DropDown style.visibleOptions. + * Prop definition overrides style. + */ + visibleOptions: PropTypes.number, + /** + * Callback that is called when modal should be closed + */ + onClose: PropTypes.func, + /** + * Callback that is called when dropdown option is selected + */ + onOptionSelected: PropTypes.func, +}; + +DropDownModal.defaultProps = { + renderOption: (option, titleProperty) => ( + {option[titleProperty].toUpperCase()} + ), + visible: false, + visibleOptions: undefined, + onClose: undefined, + onOptionSelected: undefined, +}; + const StyledModal = connectStyle('shoutem.ui.DropDownModal')(DropDownModal); export { StyledModal as DropDownModal }; diff --git a/components/EmptyStateView.js b/components/EmptyStateView.js index b203c331..cc4b7528 100644 --- a/components/EmptyStateView.js +++ b/components/EmptyStateView.js @@ -8,11 +8,6 @@ import { Subtitle, Text } from './Text'; import { View } from './View'; class EmptyStateView extends PureComponent { - static defaultProps = { - retryButtonTitle: 'TRY AGAIN', - icon: 'error', - }; - constructor(props) { super(props); @@ -20,13 +15,15 @@ class EmptyStateView extends PureComponent { } onRetry() { - this.props.onRetry(); + const { onRetry } = this.props; + + onRetry(); } renderRetryButton() { const { retryButtonTitle } = this.props; - // Show retry button at the bottom only if there is an onRetry action passed. + // Show retry button at the bottom only if there is an onRetry action passed return (