From fdb7a450d058ab9637539f92b4950a54921dab2f Mon Sep 17 00:00:00 2001 From: Slavko Date: Fri, 12 Feb 2021 19:12:31 +0100 Subject: [PATCH 01/14] add icon and animated dropdown --- .../Icon/assets/checkbox-rectangle-off.svg | 4 + .../Icon/assets/checkbox-rectangle-on.svg | 5 + components/Icon/assets/index.js | 4 + .../InlineDropDownMenu/InlineDropDownMenu.js | 130 ++++++++++++++++++ .../InlineDropDownMenuItem.js | 84 +++++++++++ components/InlineDropDownMenu/index.js | 2 + index.js | 1 + theme.js | 39 ++++++ 8 files changed, 269 insertions(+) create mode 100644 components/Icon/assets/checkbox-rectangle-off.svg create mode 100644 components/Icon/assets/checkbox-rectangle-on.svg create mode 100644 components/InlineDropDownMenu/InlineDropDownMenu.js create mode 100644 components/InlineDropDownMenu/InlineDropDownMenuItem.js create mode 100644 components/InlineDropDownMenu/index.js diff --git a/components/Icon/assets/checkbox-rectangle-off.svg b/components/Icon/assets/checkbox-rectangle-off.svg new file mode 100644 index 00000000..8e25f0cd --- /dev/null +++ b/components/Icon/assets/checkbox-rectangle-off.svg @@ -0,0 +1,4 @@ + + + + diff --git a/components/Icon/assets/checkbox-rectangle-on.svg b/components/Icon/assets/checkbox-rectangle-on.svg new file mode 100644 index 00000000..ced67461 --- /dev/null +++ b/components/Icon/assets/checkbox-rectangle-on.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/components/Icon/assets/index.js b/components/Icon/assets/index.js index 943803d7..617408b8 100644 --- a/components/Icon/assets/index.js +++ b/components/Icon/assets/index.js @@ -13,6 +13,8 @@ import camSwitch from './cam-switch.svg'; import cart from './cart.svg'; import checkboxOff from './checkbox-off.svg'; import checkboxOn from './checkbox-on.svg'; +import checkbocRectangleOff from './checkbox-rectangle-off'; +import checkbocRectangleOn from './checkbox-rectangle-on'; import clearText from './clear-text.svg'; import close from './close.svg'; import comment from './comment.svg'; @@ -111,6 +113,8 @@ export const defaultConfig = [ { name: 'cart', icon: cart }, { name: 'checkbox-off', icon: checkboxOff }, { name: 'checkbox-on', icon: checkboxOn }, + { name: 'checkbox-rectangle-off', icon: checkbocRectangleOff }, + { name: 'checkbox-rectangle-on', icon: checkbocRectangleOn }, { name: 'clear-text', icon: clearText }, { name: 'close', icon: close }, { name: 'comment', icon: comment }, diff --git a/components/InlineDropDownMenu/InlineDropDownMenu.js b/components/InlineDropDownMenu/InlineDropDownMenu.js new file mode 100644 index 00000000..52ad59fd --- /dev/null +++ b/components/InlineDropDownMenu/InlineDropDownMenu.js @@ -0,0 +1,130 @@ +import React, { PureComponent } from 'react'; +import autoBindReact from 'auto-bind/react'; +import { FlatList, Animated } from 'react-native'; +import _ from 'lodash'; +import PropTypes from 'prop-types'; +import { connectStyle } from '@shoutem/theme'; +import { TouchableOpacity } from '../TouchableOpacity'; +import { View } from '../View'; +import { Caption, Text } from '../Text'; +import { Icon } from '../Icon'; +import { InlineDropDownMenuItem } from './InlineDropDownMenuItem'; + +const AnimatedIcon = Animated.createAnimatedComponent(Icon); + +const optionShape = PropTypes.shape({ + title: PropTypes.string, + key: PropTypes.string, +}); + +class InlineDropDownMenu extends PureComponent { + static propTypes = { + heading: PropTypes.string, + selectedDescriptor: PropTypes.string, + options: PropTypes.arrayOf(optionShape).isRequired, + onOptionSelected: PropTypes.func, + selectedOption: optionShape, + }; + + constructor(props) { + super(props); + + autoBindReact(this); + + this.dropDownIconValue = new Animated.Value(0); + + this.state = { + collapsed: false, + }; + } + + handleToggleMenuPress() { + const { collapsed } = this.state; + + const toValue = collapsed ? 0 : 1; + + this.setState( + { collapsed: !collapsed }, + () => Animated.timing( + this.dropDownIconValue, + { + toValue, + useNativeDriver: true, + duration: 300, + } + ).start()); + } + + handleOptionPress(option) { + const { onOptionSelected } = this.props; + + if (onOptionSelected) { + onOptionSelected(option); + } + + this.handleToggleMenuPress(); + } + + renderOption({ item, index }) { + const { selectedOption, selectedDescriptor } = this.props; + + const isSelected = selectedOption.key === item.key; + + return ( + + ) + } + + render() { + const { + style, + heading, + selectedOption, + options, + } = this.props; + const { collapsed } = this.state; + + return ( + + + {heading} + + {selectedOption?.title} + + + + {collapsed && ( + + )} + + ); + } +} + +const StyledComponent = connectStyle('shoutem.ui.InlineDropDownMenu')( + InlineDropDownMenu, +); + +export { StyledComponent as InlineDropDownMenu }; diff --git a/components/InlineDropDownMenu/InlineDropDownMenuItem.js b/components/InlineDropDownMenu/InlineDropDownMenuItem.js new file mode 100644 index 00000000..56569814 --- /dev/null +++ b/components/InlineDropDownMenu/InlineDropDownMenuItem.js @@ -0,0 +1,84 @@ +import React, { PureComponent } from 'react'; +import autoBindReact from 'auto-bind/react'; +import { Animated, Dimensions } from 'react-native'; +import _ from 'lodash'; +import PropTypes from 'prop-types'; +import { connectStyle } from '@shoutem/theme'; +import { TouchableOpacity } from '../TouchableOpacity'; +import { Text } from '../Text'; + +const window = Dimensions.get('window'); +const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity); + +class InlineDropDownMenuItem extends PureComponent { + static propTypes = { + item: PropTypes.object, + index: PropTypes.number, + selectedDescriptor: PropTypes.string, + onItemPressed: PropTypes.func, + isSelected: PropTypes.bool, + }; + + constructor(props) { + super(props); + + autoBindReact(this); + + this.animatedValue = new Animated.Value(0); + } + + componentDidMount() { + const { index } = this.props; + + Animated.timing( + this.animatedValue, + { + toValue: 1, + useNativeDriver: true, + duration: 300 + index * 15, + } + ).start(); + } + + handlePress() { + const { onItemPressed, isSelected, item } = this.props; + + if (onItemPressed && !isSelected) { + onItemPressed(item); + } + } + + render() { + const { isSelected, selectedDescriptor, item, style } = this.props; + + const resolvedText = isSelected ? `${item.title} (${selectedDescriptor})` : item.title; + const textStyle = isSelected ? 'muted' : ''; + + return ( + + {resolvedText} + + ); + } +} + +const StyledComponent = connectStyle('shoutem.ui.InlineDropDownMenuItem')( + InlineDropDownMenuItem, +); + +export { StyledComponent as InlineDropDownMenuItem }; diff --git a/components/InlineDropDownMenu/index.js b/components/InlineDropDownMenu/index.js new file mode 100644 index 00000000..b95d8e64 --- /dev/null +++ b/components/InlineDropDownMenu/index.js @@ -0,0 +1,2 @@ +export { InlineDropDownMenu } from './InlineDropDownMenu'; +export { InlineDropDownMenuItem } from './InlineDropDownMenuItem'; diff --git a/index.js b/index.js index d96653f5..c8200822 100644 --- a/index.js +++ b/index.js @@ -72,6 +72,7 @@ export { Tile } from './components/Tile'; export { Lightbox } from './components/Lightbox'; +export { InlineDropDownMenu } from './components/InlineDropDownMenu'; export { EmptyStateView } from './components/EmptyStateView'; export { EmptyListImage } from './components/EmptyListImage'; export { NumberInput } from './components/NumberInput'; diff --git a/theme.js b/theme.js index e73ee85c..0f1f7c92 100644 --- a/theme.js +++ b/theme.js @@ -2040,6 +2040,11 @@ export default (variables = defaultThemeVariables) => ({ withoutBorder: { borderWidth: 0, }, + + '.small': { + paddingVertical: 6, + height: 42, + }, }, 'shoutem.ui.NumberInput': { @@ -2551,6 +2556,17 @@ export default (variables = defaultThemeVariables) => ({ }, }, + '.relative': { + container: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + position: 'relative', + bottom: 0, + paddingVertical: 16, + }, + }, + container: { flexDirection: 'row', alignItems: 'center', @@ -2863,4 +2879,27 @@ export default (variables = defaultThemeVariables) => ({ }, }, }, + + 'shoutem.ui.InlineDropDownMenu': { + container: { + paddingTop: 12, + paddingHorizontal: variables.mediumGutter, + paddingBottom: 4, + backgroundColor: variables.paperColor, + }, + icon: { + color: variables.text.color, + } + }, + + 'shoutem.ui.InlineDropDownMenuItem': { + container: { + paddingTop: 10, + paddingHorizontal: variables.mediumGutter, + paddingBottom: 4, + backgroundColor: variables.paperColor, + borderTopWidth: 1, + borderTopColor: variables.backgroundColor, + }, + }, }); From baf6dd5cdfdf45b1b5f324f9a18693af8075b318 Mon Sep 17 00:00:00 2001 From: Slavko Date: Fri, 12 Feb 2021 19:15:03 +0100 Subject: [PATCH 02/14] resolve styling issue --- components/InlineDropDownMenu/InlineDropDownMenuItem.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/InlineDropDownMenu/InlineDropDownMenuItem.js b/components/InlineDropDownMenu/InlineDropDownMenuItem.js index 56569814..d2634695 100644 --- a/components/InlineDropDownMenu/InlineDropDownMenuItem.js +++ b/components/InlineDropDownMenu/InlineDropDownMenuItem.js @@ -41,9 +41,9 @@ class InlineDropDownMenuItem extends PureComponent { } handlePress() { - const { onItemPressed, isSelected, item } = this.props; + const { onItemPressed, item } = this.props; - if (onItemPressed && !isSelected) { + if (onItemPressed) { onItemPressed(item); } } @@ -69,6 +69,7 @@ class InlineDropDownMenuItem extends PureComponent { ] } ]} + disabled={isSelected} onPress={this.handlePress} > {resolvedText} From 1a85bec6f94215ff3768e9d1bc1a590d51f65a09 Mon Sep 17 00:00:00 2001 From: Slavko Date: Mon, 15 Feb 2021 17:23:04 +0100 Subject: [PATCH 03/14] minor adjustments --- theme.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/theme.js b/theme.js index 0f1f7c92..7c07dd63 100644 --- a/theme.js +++ b/theme.js @@ -485,6 +485,10 @@ export default (variables = defaultThemeVariables) => ({ opacity: 0.5, }, + '.link': { + ...variables.links, + }, + backgroundColor: 'transparent', }, @@ -598,7 +602,7 @@ export default (variables = defaultThemeVariables) => ({ '.medium-avatar': { width: dimensionRelativeToIphone(145), height: dimensionRelativeToIphone(145), - borderRadius: 72.5, + borderRadius: dimensionRelativeToIphone(72.5), borderWidth: 0, }, @@ -661,7 +665,7 @@ export default (variables = defaultThemeVariables) => ({ }, }, 'shoutem.ui.Image': { - [INCLUDE]: ['commonVariants', 'imageSizes', 'fill-parent'], + [INCLUDE]: ['commonVariants', 'imageSizes', 'fill-parent', 'guttersMargin'], '.placeholder': { backgroundColor: inverseColorBrightnessForAmount( @@ -694,7 +698,11 @@ export default (variables = defaultThemeVariables) => ({ }, }, 'shoutem.ui.ImageBackground': { - [INCLUDE]: ['commonVariants', 'imageSizes', 'fill-parent'], + [INCLUDE]: ['commonVariants', 'imageSizes', 'fill-parent', 'guttersMargin'], + + '.overflow-hidden': { + overflow: 'hidden', + }, '.placeholder': { backgroundColor: inverseColorBrightnessForAmount( From f0e9201dfeb85fb38b8742f566e10f2affeb6e11 Mon Sep 17 00:00:00 2001 From: Slavko Date: Fri, 19 Feb 2021 14:04:41 +0100 Subject: [PATCH 04/14] tabMenu --- components/TabMenu/TabMenu.js | 63 ++++++++++++++++++++++++++++++ components/TabMenu/TabMenuItem.js | 65 +++++++++++++++++++++++++++++++ components/TabMenu/const.js | 6 +++ components/TabMenu/index.js | 1 + index.js | 1 + theme.js | 32 +++++++++++++++ 6 files changed, 168 insertions(+) create mode 100644 components/TabMenu/TabMenu.js create mode 100644 components/TabMenu/TabMenuItem.js create mode 100644 components/TabMenu/const.js create mode 100644 components/TabMenu/index.js diff --git a/components/TabMenu/TabMenu.js b/components/TabMenu/TabMenu.js new file mode 100644 index 00000000..2700ade4 --- /dev/null +++ b/components/TabMenu/TabMenu.js @@ -0,0 +1,63 @@ +import PropTypes from 'prop-types'; +import autoBindReact from 'auto-bind/react'; +import React, { PureComponent } from 'react'; +import _ from 'lodash'; +import { ScrollView, LayoutAnimation } from 'react-native'; +import { connectStyle } from '@shoutem/theme'; +import { optionShape } from './const'; +import { TabMenuItem } from './TabMenuItem'; + +class TabMenu extends PureComponent { + static propTypes = { + options: PropTypes.arrayOf(optionShape).isRequired, + onOptionSelected: PropTypes.func, + selectedOption: optionShape, + style: PropTypes.object, + }; + + constructor(props) { + super(props); + + autoBindReact(this); + } + + handleOptionSelected(option) { + const { onOptionSelected } = this.props; + + LayoutAnimation.easeInEaseOut(); + onOptionSelected(option); + } + + renderOption(option) { + const { selectedOption } = this.props; + + const isSelected = selectedOption && option.title === selectedOption.title; + + return ( + + ); + } + + render() { + const { style, options } = this.props; + + return ( + + {_.map(options, this.renderOption)} + + ); + } +} + +const StyledTabMenu = connectStyle('shoutem.ui.TabMenu')(TabMenu); + +export { StyledTabMenu as TabMenu }; diff --git a/components/TabMenu/TabMenuItem.js b/components/TabMenu/TabMenuItem.js new file mode 100644 index 00000000..55c9adc4 --- /dev/null +++ b/components/TabMenu/TabMenuItem.js @@ -0,0 +1,65 @@ +import React, { PureComponent } from 'react'; +import { LayoutAnimation } from 'react-native'; +import PropTypes from 'prop-types'; +import autoBindReact from 'auto-bind/react'; +import _ from 'lodash'; +import { connectStyle } from '@shoutem/theme'; +import { Text } from '../Text'; +import { View } from '../View'; +import { TouchableOpacity } from '../TouchableOpacity'; +import { optionShape } from './const'; + +class TabMenuItem extends PureComponent { + static propTypes = { + item: optionShape, + onItemPressed: PropTypes.any, + isSelected: PropTypes.bool, + style: PropTypes.object, + }; + + constructor(props) { + super(props); + + autoBindReact(this); + + this.state = { + baseWidth: 0, + }; + } + + handleItemPressed() { + const { onItemPressed, item } = this.props; + + onItemPressed(item); + } + + handleLayout({ nativeEvent: { layout: { width } } }) { + + LayoutAnimation.easeInEaseOut(); + this.setState({ baseWidth: width }); + } + + render() { + const { style, isSelected, item } = this.props; + const { baseWidth } = this.state; + + return ( + + + {item.title} + + {isSelected && } + + ); + } +} + +const StyledTabMenuItem = connectStyle('shoutem.ui.TabMenuItem')(TabMenuItem); + +export { StyledTabMenuItem as TabMenuItem }; diff --git a/components/TabMenu/const.js b/components/TabMenu/const.js new file mode 100644 index 00000000..b3c8be74 --- /dev/null +++ b/components/TabMenu/const.js @@ -0,0 +1,6 @@ +import PropTypes from 'prop-types'; + +export const optionShape = PropTypes.shape({ + title: PropTypes.string, + value: PropTypes.any, +}); diff --git a/components/TabMenu/index.js b/components/TabMenu/index.js new file mode 100644 index 00000000..2f185a51 --- /dev/null +++ b/components/TabMenu/index.js @@ -0,0 +1 @@ +export { TabMenu } from './TabMenu'; diff --git a/index.js b/index.js index c8200822..f2184db0 100644 --- a/index.js +++ b/index.js @@ -77,6 +77,7 @@ export { EmptyStateView } from './components/EmptyStateView'; export { EmptyListImage } from './components/EmptyListImage'; export { NumberInput } from './components/NumberInput'; export { SearchField } from './components/SearchField'; +export { TabMenu } from './components/TabMenu'; export { Examples } from './examples/components'; diff --git a/theme.js b/theme.js index 7c07dd63..74f77e9a 100644 --- a/theme.js +++ b/theme.js @@ -1945,6 +1945,8 @@ export default (variables = defaultThemeVariables) => ({ borderColor: variables.lineColor, }, 'shoutem.ui.Divider': { + [INCLUDE]: ['guttersMargin'], + '.line': { '.small': { width: 55, @@ -2105,6 +2107,7 @@ export default (variables = defaultThemeVariables) => ({ backgroundColor: '#f0f0f0', color: '#888888', flex: 1, + minWidth: 330, fontSize: 15, height: 30, paddingVertical: 6, @@ -2910,4 +2913,33 @@ export default (variables = defaultThemeVariables) => ({ borderTopColor: variables.backgroundColor, }, }, + + 'shoutem.ui.TabMenu': { + container: { + paddingHorizontal: variables.smallGutter, + backgroundColor: variables.backgroundColor, + height: 44, + }, + }, + + 'shoutem.ui.TabMenuItem': { + tabulator: { + backgroundColor: variables.text.color, + height: 1, + borderRadius: 1, + flexDirection: 'row', + marginBottom: 8, + marginLeft: 8, + }, + text: { + marginTop: 12, + marginHorizontal: 8, + marginBottom: 12, + opacity: 0.3, + }, + selectedText: { + marginBottom: 4, + opacity: 1, + } + } }); From af29adf47e33ae639e36dbfa1d89de11cb8a23b5 Mon Sep 17 00:00:00 2001 From: Slavko Date: Tue, 23 Feb 2021 00:39:16 +0100 Subject: [PATCH 05/14] year picker --- .../Icon/assets/checkbox-rectangle-off.svg | 3 +- .../Icon/assets/checkbox-rectangle-on.svg | 4 +- components/YearPicker/YearPicker.js | 97 +++++++++ components/YearPicker/YearPickerButton.js | 82 +++++++ components/YearPicker/YearPickerModal.js | 206 ++++++++++++++++++ components/YearPicker/index.js | 1 + index.js | 1 + 7 files changed, 390 insertions(+), 4 deletions(-) create mode 100644 components/YearPicker/YearPicker.js create mode 100644 components/YearPicker/YearPickerButton.js create mode 100644 components/YearPicker/YearPickerModal.js create mode 100644 components/YearPicker/index.js diff --git a/components/Icon/assets/checkbox-rectangle-off.svg b/components/Icon/assets/checkbox-rectangle-off.svg index 8e25f0cd..ced67461 100644 --- a/components/Icon/assets/checkbox-rectangle-off.svg +++ b/components/Icon/assets/checkbox-rectangle-off.svg @@ -1,4 +1,5 @@ - + + diff --git a/components/Icon/assets/checkbox-rectangle-on.svg b/components/Icon/assets/checkbox-rectangle-on.svg index ced67461..eba3dc93 100644 --- a/components/Icon/assets/checkbox-rectangle-on.svg +++ b/components/Icon/assets/checkbox-rectangle-on.svg @@ -1,5 +1,3 @@ - - - + diff --git a/components/YearPicker/YearPicker.js b/components/YearPicker/YearPicker.js new file mode 100644 index 00000000..30ba3e61 --- /dev/null +++ b/components/YearPicker/YearPicker.js @@ -0,0 +1,97 @@ +import React, { PureComponent } from 'react'; +import { Dimensions } from 'react-native'; +import PropTypes from 'prop-types'; +import _ from 'lodash'; +import autoBindReact from 'auto-bind/react'; +import { connectStyle } from '@shoutem/theme'; +import { View } from '@shoutem/ui'; +import YearPickerButton from './YearPickerButton'; +import YearPickerModal from './YearPickerModal'; + +const window = Dimensions.get('window'); + +function formatButtonTooltip(props) { + const { selectedYears } = props; + + if (_.isEmpty(selectedYears)) { + return '-'; + } + + const leadYear = _.head(selectedYears); + const lastYear = _.last(selectedYears); + + if (leadYear === lastYear) { + return leadYear.toString(); + } + + return `${leadYear}-${lastYear}`; +} + +class YearPicker extends PureComponent { + static propTypes = { + onRangeConfirmed: PropTypes.func, + onReset: PropTypes.func, + resetButtonTitle: PropTypes.string, + confirmButtonTitle: PropTypes.string, + selectedYears: PropTypes.arrayOf(PropTypes.number), + rangeStart: PropTypes.number, + rangeEnd: PropTypes.number, + }; + + static defaultProps = { + selectedYears: [], + }; + + constructor(props) { + super(props); + + autoBindReact(this); + + this.state = { + collapsed: false, + selectedYears: props.selectedYears, + buttonTooltip: formatButtonTooltip(props), + }; + } + + handleButtonPressed() { + const { collapsed } = this.state; + + this.setState({ collapsed: !collapsed }); + } + + handleLayout({ nativeEvent: { layout: { x, y, width, height } } }) { + this.setState({ + modalStyle: { + position: 'absolute', + top: 4 + height, + left: 0, + width: window.width - (2 * x), + } + }); + } + + render() { + const { rangeEnd, rangeStart, resetButtonTitle, confirmButtonTitle } = this.props; + const { buttonTooltip, collapsed, modalStyle } = this.state; + + return ( + + + + + ); + } +} + +export default connectStyle('shoutem.ui.YearPicker')(YearPicker); diff --git a/components/YearPicker/YearPickerButton.js b/components/YearPicker/YearPickerButton.js new file mode 100644 index 00000000..f7b7ece5 --- /dev/null +++ b/components/YearPicker/YearPickerButton.js @@ -0,0 +1,82 @@ +import React, { PureComponent } from 'react'; +import { Animated } from 'react-native'; +import PropTypes from 'prop-types'; +import _ from 'lodash'; +import autoBindReact from 'auto-bind/react'; +import { connectStyle } from '@shoutem/theme'; +import { TouchableOpacity } from '../TouchableOpacity'; +import { Icon } from '../Icon'; +import { Text } from '../Text'; + +const AnimatedIcon = Animated.createAnimatedComponent(Icon); + +class YearPickerButton extends PureComponent { + static propTypes = { + onPress: PropTypes.func, + tooltip: PropTypes.string, + style: PropTypes.any, + }; + + constructor(props) { + super(props); + + autoBindReact(this); + + this.dropDownIconValue = new Animated.Value(0); + + this.state = { + collapsed: false, + }; + } + + handlePress() { + const { collapsed } = this.state; + const { onPress } = this.props; + + const toValue = collapsed ? 0 : 1; + + this.setState( + { collapsed: !collapsed }, + () => Animated.timing( + this.dropDownIconValue, + { + toValue, + useNativeDriver: true, + duration: 300, + } + ).start()); + + if (onPress) { + onPress(); + } + } + + render() { + const { style, tooltip } = this.props; + + return ( + + + {tooltip} + + + + ); + } +} + +export default connectStyle('shoutem.ui.YearPickerButton')(YearPickerButton); diff --git a/components/YearPicker/YearPickerModal.js b/components/YearPicker/YearPickerModal.js new file mode 100644 index 00000000..e10d03c2 --- /dev/null +++ b/components/YearPicker/YearPickerModal.js @@ -0,0 +1,206 @@ +import React, { PureComponent } from 'react'; +import { TextInput, LayoutAnimation } from 'react-native'; +import PropTypes from 'prop-types'; +import _ from 'lodash'; +import autoBindReact from 'auto-bind/react'; +import { connectStyle } from '@shoutem/theme'; +import { View } from '../View'; +import { Text } from '../Text'; +import { Icon } from '../Icon'; +import { Button } from '../Button'; +import { TouchableOpacity } from '../TouchableOpacity'; + +const NUMBER_OF_ROWS = 4; +const YEARS_IN_ROW = 5; + +function resolveVisibleYears(props) { + const { selectedYears, rangeStart, rangeEnd } = props; + + const yearsPerPage = NUMBER_OF_ROWS * YEARS_IN_ROW; + + if (_.isEmpty(selectedYears)) { + const fullRange = _.times( + yearsPerPage, + index => rangeStart + index, + ); + + return _.filter(fullRange, year => year <= rangeEnd); + } + + const firstSelectedYear = _.head(selectedYears); + const prevPagesNumber = Math.trunc(firstSelectedYear / yearsPerPage); + + const fullRangeStart = (prevPagesNumber * yearsPerPage) + 1; + const fullRange = _.times( + yearsPerPage, + index => fullRangeStart + index, + ); + + return _.filter(fullRange, year => year <= rangeEnd); +} + +function resolveRangeTooltip(visibleYears) { + const startYear = _.head(visibleYears).toString(); + const endYear = _.last(visibleYears).toString(); + + return `${startYear}-${endYear}`; +} + +class YearPickerModal extends PureComponent { + static propTypes = { + onRangeConfirmed: PropTypes.func, + onReset: PropTypes.func, + resetButtonTitle: PropTypes.string, + confirmButtonTitle: PropTypes.string, + selectedYears: PropTypes.arrayOf(PropTypes.number), + rangeStart: PropTypes.number, + rangeEnd: PropTypes.number, + visible: PropTypes.bool, + style: PropTypes.any, + containerStyle: PropTypes.any, + }; + + static defaultProps = { + selectedYears: [], + }; + + constructor(props) { + super(props); + + autoBindReact(this); + + this.YEARS_PER_PAGE = NUMBER_OF_ROWS * YEARS_IN_ROW; + + this.state = { + collapsed: false, + visibleYears: resolveVisibleYears(props), + selectedYears: props.selectedYears, + }; + } + + handleYearsForwardPress() { + const { rangeEnd } = this.props; + const { visibleYears } = this.state; + + const nextYearStart = _.last(visibleYears) + 1; + const nextYearEnd = Math.min(rangeEnd, nextYearStart + this.YEARS_PER_PAGE - 1); + + const nextVisibleYears = _.times(nextYearEnd + 1 - nextYearStart, index => nextYearStart + index); + + this.setState({ visibleYears: nextVisibleYears }); + } + + handleYearsBackPress() { + const { visibleYears } = this.state; + + const prevYearStart = _.head(visibleYears) - this.YEARS_PER_PAGE; + + this.setState({ visibleYears: _.times(this.YEARS_PER_PAGE, index => prevYearStart + index) }); + } + + handleYearPress(year) { + const { selectedYears } = this.state; + + const size = _.size(selectedYears); + const index = _.indexOf(selectedYears, year); + + return () => { + if (_.includes(selectedYears, year)) { + if (_.last(selectedYears) === year || _.head(selectedYears) === year) { + this.setState({ selectedYears: _.without(selectedYears, year) }); + return; + } + + const cutFromEnd = size / 2 <= index; + const newYears = _.filter(selectedYears, value => cutFromEnd ? value <= year : value >= year); + + this.setState({ selectedYears: newYears }); + return; + } + + if (_.isEmpty(selectedYears)) { + this.setState({ selectedYears: [year] }); + return; + } + + const addToEnd = size / 2 >= index; + const yearsToAdd = addToEnd ? year - _.last(selectedYears) : _.head(selectedYears) - year; + const newYears = addToEnd + ? [...selectedYears, ..._.times(yearsToAdd, index => _.last(selectedYears) + index + 1)] + : [...selectedYears, ..._.times(yearsToAdd, index => _.last(selectedYears) - index + -1)] + const sortedYears = _.sortBy(newYears, item => item); + + this.setState({ selectedYears: sortedYears }); + } + } + + renderYear(year) { + const { style } = this.props; + const { selectedYears } = this.state; + + const isSelected = _.includes(selectedYears, year); + const isFirst = _.head(selectedYears) === year; + const isLast = _.last(selectedYears) === year; + + return ( + + + {year.toString()} + + + ); + } + + renderYearRow(row) { + return ( + + {_.map(row, this.renderYear)} + + ) + } + + render() { + const { style, visible, containerStyle, confirmButtonTitle, resetButtonTitle } = this.props; + const { visibleYears } = this.state; + + const buttonTooltip = resolveRangeTooltip(visibleYears); + const data = _.chunk(visibleYears, YEARS_IN_ROW); + + if (!visible) { + return null; + } + + return ( + + + + {buttonTooltip} + + + {_.times(NUMBER_OF_ROWS, (row) => this.renderYearRow(data[row]))} + + + + + + ); + } +} + +export default connectStyle('shoutem.ui.YearPickerModal')(YearPickerModal); diff --git a/components/YearPicker/index.js b/components/YearPicker/index.js new file mode 100644 index 00000000..3ce80336 --- /dev/null +++ b/components/YearPicker/index.js @@ -0,0 +1 @@ +export { default as YearPicker } from './YearPicker'; diff --git a/index.js b/index.js index f2184db0..7bd64f8c 100644 --- a/index.js +++ b/index.js @@ -78,6 +78,7 @@ export { EmptyListImage } from './components/EmptyListImage'; export { NumberInput } from './components/NumberInput'; export { SearchField } from './components/SearchField'; export { TabMenu } from './components/TabMenu'; +export { YearPicker } from './components/YearPicker'; export { Examples } from './examples/components'; From ef7165d58126997cdf8968475005e17e6f6ab674 Mon Sep 17 00:00:00 2001 From: Slavko Date: Tue, 23 Feb 2021 08:21:19 +0100 Subject: [PATCH 06/14] year picker adjustments --- components/YearPicker/YearPicker.js | 24 +++++++- components/YearPicker/YearPickerModal.js | 29 ++++++++-- theme.js | 72 +++++++++++++++++++++++- 3 files changed, 117 insertions(+), 8 deletions(-) diff --git a/components/YearPicker/YearPicker.js b/components/YearPicker/YearPicker.js index 30ba3e61..78a9a0d0 100644 --- a/components/YearPicker/YearPicker.js +++ b/components/YearPicker/YearPicker.js @@ -54,13 +54,22 @@ class YearPicker extends PureComponent { }; } + componentDidUpdate(prevProps) { + const { selectedYears: prevSelectedYears } = prevProps; + const { selectedYears } = this.props; + + if (!_.isEqual(selectedYears, prevSelectedYears)) { + this.setState({ buttonTooltip: formatButtonTooltip(this.props) }); + } + } + handleButtonPressed() { const { collapsed } = this.state; this.setState({ collapsed: !collapsed }); } - handleLayout({ nativeEvent: { layout: { x, y, width, height } } }) { + handleLayout({ nativeEvent: { layout: { x, height } } }) { this.setState({ modalStyle: { position: 'absolute', @@ -71,8 +80,18 @@ class YearPicker extends PureComponent { }); } + handleRangeConfirmed(range) { + const { onRangeConfirmed } = this.props; + + this.setState({ collapsed: false }); + + if (onRangeConfirmed) { + onRangeConfirmed(range); + } + } + render() { - const { rangeEnd, rangeStart, resetButtonTitle, confirmButtonTitle } = this.props; + const { rangeEnd, rangeStart, resetButtonTitle, confirmButtonTitle, onRangeConfirmed } = this.props; const { buttonTooltip, collapsed, modalStyle } = this.state; return ( @@ -88,6 +107,7 @@ class YearPicker extends PureComponent { containerStyle={modalStyle} resetButtonTitle={resetButtonTitle} confirmButtonTitle={confirmButtonTitle} + onRangeConfirmed={this.handleRangeConfirmed} /> ); diff --git a/components/YearPicker/YearPickerModal.js b/components/YearPicker/YearPickerModal.js index e10d03c2..85217ee7 100644 --- a/components/YearPicker/YearPickerModal.js +++ b/components/YearPicker/YearPickerModal.js @@ -49,7 +49,6 @@ function resolveRangeTooltip(visibleYears) { class YearPickerModal extends PureComponent { static propTypes = { onRangeConfirmed: PropTypes.func, - onReset: PropTypes.func, resetButtonTitle: PropTypes.string, confirmButtonTitle: PropTypes.string, selectedYears: PropTypes.arrayOf(PropTypes.number), @@ -78,6 +77,25 @@ class YearPickerModal extends PureComponent { }; } + handleConfirmPress() { + const { selectedYears } = this.state; + const { onRangeConfirmed } = this.props; + + if (onRangeConfirmed) { + onRangeConfirmed(selectedYears); + } + } + + handleResetPress() { + const { onRangeConfirmed } = this.props; + + this.setState({ selectedYears: [] }); + + if (onRangeConfirmed) { + onRangeConfirmed([]); + } + } + handleYearsForwardPress() { const { rangeEnd } = this.props; const { visibleYears } = this.state; @@ -123,11 +141,12 @@ class YearPickerModal extends PureComponent { return; } - const addToEnd = size / 2 >= index; + const addToEnd = _.last(selectedYears) < year; const yearsToAdd = addToEnd ? year - _.last(selectedYears) : _.head(selectedYears) - year; + const newYears = addToEnd ? [...selectedYears, ..._.times(yearsToAdd, index => _.last(selectedYears) + index + 1)] - : [...selectedYears, ..._.times(yearsToAdd, index => _.last(selectedYears) - index + -1)] + : [...selectedYears, ..._.times(yearsToAdd, index => _.head(selectedYears) - index + -1)] const sortedYears = _.sortBy(newYears, item => item); this.setState({ selectedYears: sortedYears }); @@ -191,10 +210,10 @@ class YearPickerModal extends PureComponent { {_.times(NUMBER_OF_ROWS, (row) => this.renderYearRow(data[row]))} - - diff --git a/theme.js b/theme.js index 74f77e9a..35aba90b 100644 --- a/theme.js +++ b/theme.js @@ -2941,5 +2941,75 @@ export default (variables = defaultThemeVariables) => ({ marginBottom: 4, opacity: 1, } - } + }, + + 'shoutem.ui.YearPickerButton': { + container: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#F9F9F9', + borderWidth: 1, + borderRadius: 8, + borderColor: 'rgba(130, 130, 130, 0.1)', + paddingLeft: 15, + paddingVertical: 8, + paddingRight: 8, + marginRight: 8, + }, + icon: { + color: variables.text.color, + } + }, + + 'shoutem.ui.YearPickerModal': { + container: { + padding: 8, + backgroundColor: '#F9F9F9', + borderWidth: 1, + borderRadius: 8, + borderColor: 'rgba(130, 130, 130, 0.1)', + zIndex: 5, + }, + tooltipContainer: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 8, + minHeight: 56, + }, + buttonContainer: { + flexDirection: 'row', + padding: 8, + marginTop: 8, + }, + yearContainer: { + flex: 1, + flexDirection: 'row', + marginTop: 5, + }, + year: { + flexDirection: 'row', + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingVertical: 18, + }, + yearSelected: { + backgroundColor: '#FBD05C', + }, + yearFirst: { + paddingLeft: 5, + borderTopLeftRadius: 8, + borderBottomLeftRadius: 8, + }, + yearLast: { + paddingRight: 5, + borderTopRightRadius: 8, + borderBottomRightRadius: 8, + }, + icon: { + color: variables.text.color, + } + }, }); From e85c904f0180ed2d20396ab802d2cbec49ce7234 Mon Sep 17 00:00:00 2001 From: Slavko Date: Tue, 23 Feb 2021 08:38:59 +0100 Subject: [PATCH 07/14] resolve warnings --- components/TabMenu/TabMenu.js | 1 + components/YearPicker/YearPickerModal.js | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/components/TabMenu/TabMenu.js b/components/TabMenu/TabMenu.js index 2700ade4..4a50d537 100644 --- a/components/TabMenu/TabMenu.js +++ b/components/TabMenu/TabMenu.js @@ -35,6 +35,7 @@ class TabMenu extends PureComponent { return ( nextYearStart + index); + LayoutAnimation.easeInEaseOut(); this.setState({ visibleYears: nextVisibleYears }); } @@ -113,6 +116,7 @@ class YearPickerModal extends PureComponent { const prevYearStart = _.head(visibleYears) - this.YEARS_PER_PAGE; + LayoutAnimation.easeInEaseOut(); this.setState({ visibleYears: _.times(this.YEARS_PER_PAGE, index => prevYearStart + index) }); } @@ -122,6 +126,8 @@ class YearPickerModal extends PureComponent { const size = _.size(selectedYears); const index = _.indexOf(selectedYears, year); + LayoutAnimation.easeInEaseOut(); + return () => { if (_.includes(selectedYears, year)) { if (_.last(selectedYears) === year || _.head(selectedYears) === year) { @@ -165,6 +171,7 @@ class YearPickerModal extends PureComponent { + {_.map(row, this.renderYear)} ) @@ -208,7 +215,7 @@ class YearPickerModal extends PureComponent { - {_.times(NUMBER_OF_ROWS, (row) => this.renderYearRow(data[row]))} + {_.times(NUMBER_OF_ROWS, (row) => this.renderYearRow(data[row], row))} {buttonTooltip} - {_.times(NUMBER_OF_ROWS, (row) => this.renderYearRow(data[row], row))} diff --git a/theme.js b/theme.js index d8dae8fe..3d76af14 100644 --- a/theme.js +++ b/theme.js @@ -3013,6 +3013,10 @@ export default (variables = defaultThemeVariables) => ({ }, icon: { color: variables.text.color, + opacity: 1, + }, + iconDisabled: { + opacity: 0.3, } }, }); From b480917b0732680d25dc7c7e0d0b3bba109f345e Mon Sep 17 00:00:00 2001 From: Slavko Date: Mon, 1 Mar 2021 14:17:05 +0100 Subject: [PATCH 11/14] add modal to dependencies --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 28429e18..6e572947 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "react-native-lightbox": "shoutem/react-native-lightbox#v0.7.2", "react-native-linear-gradient": "~2.5.6", "react-native-photo-view": "shoutem/react-native-photo-view#0ffa1481f6b6cb8663cb291b7db1d6644440584d", + "react-native-modal": "11.7.0", "react-native-render-html": "~4.2.0", "react-native-status-bar-height": "2.5.0", "react-native-svg": "^9.13.0", From 1fcce6337c2c901b17cd3c8887f44bad0e60999d Mon Sep 17 00:00:00 2001 From: Slavko Date: Mon, 1 Mar 2021 16:28:16 +0100 Subject: [PATCH 12/14] use modal for year picker --- components/YearRangePicker/YearRangePicker.js | 18 +---- .../YearRangePicker/YearRangePickerModal.js | 69 ++++++++++--------- theme.js | 33 ++++++--- 3 files changed, 63 insertions(+), 57 deletions(-) diff --git a/components/YearRangePicker/YearRangePicker.js b/components/YearRangePicker/YearRangePicker.js index 2d5f295b..766c5518 100644 --- a/components/YearRangePicker/YearRangePicker.js +++ b/components/YearRangePicker/YearRangePicker.js @@ -1,5 +1,4 @@ import React, { PureComponent } from 'react'; -import { Dimensions } from 'react-native'; import PropTypes from 'prop-types'; import _ from 'lodash'; import autoBindReact from 'auto-bind/react'; @@ -8,8 +7,6 @@ import { View } from '../View'; import YearPickerButton from './YearRangePickerButton'; import YearPickerModal from './YearRangePickerModal'; -const window = Dimensions.get('window'); - function formatButtonTooltip(props) { const { selectedYears, buttonPlaceholder } = props; @@ -71,17 +68,6 @@ class YearRangePicker extends PureComponent { this.setState({ collapsed: !collapsed }); } - handleLayout({ nativeEvent: { layout: { x, height } } }) { - this.setState({ - modalStyle: { - position: 'absolute', - top: 4 + height, - left: 0, - width: window.width - (2 * x), - } - }); - } - handleRangeConfirmed(range) { const { onRangeConfirmed } = this.props; @@ -94,7 +80,7 @@ class YearRangePicker extends PureComponent { render() { const { rangeEnd, rangeStart, resetButtonTitle, confirmButtonTitle } = this.props; - const { buttonTooltip, collapsed, modalStyle } = this.state; + const { buttonTooltip, collapsed } = this.state; return ( @@ -106,10 +92,10 @@ class YearRangePicker extends PureComponent { visible={collapsed} rangeStart={rangeStart} rangeEnd={rangeEnd} - containerStyle={modalStyle} resetButtonTitle={resetButtonTitle} confirmButtonTitle={confirmButtonTitle} onRangeConfirmed={this.handleRangeConfirmed} + onDismiss={this.handleButtonPressed} /> ); diff --git a/components/YearRangePicker/YearRangePickerModal.js b/components/YearRangePicker/YearRangePickerModal.js index fede756c..f7e8d1fd 100644 --- a/components/YearRangePicker/YearRangePickerModal.js +++ b/components/YearRangePicker/YearRangePickerModal.js @@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'; import { LayoutAnimation } from 'react-native'; import PropTypes from 'prop-types'; import _ from 'lodash'; +import Modal from 'react-native-modal'; import autoBindReact from 'auto-bind/react'; import { connectStyle } from '@shoutem/theme'; import { View } from '../View'; @@ -49,6 +50,7 @@ function resolveRangeTooltip(visibleYears) { class YearRangePickerModal extends PureComponent { static propTypes = { onRangeConfirmed: PropTypes.func, + onDismiss: PropTypes.func, resetButtonTitle: PropTypes.string, confirmButtonTitle: PropTypes.string, selectedYears: PropTypes.arrayOf(PropTypes.number), @@ -56,7 +58,6 @@ class YearRangePickerModal extends PureComponent { rangeEnd: PropTypes.number, visible: PropTypes.bool, style: PropTypes.any, - containerStyle: PropTypes.any, }; static defaultProps = { @@ -187,7 +188,7 @@ class YearRangePickerModal extends PureComponent { renderYearRow(row, index) { return ( - + {_.map(row, this.renderYear)} ) @@ -197,11 +198,11 @@ class YearRangePickerModal extends PureComponent { const { style, visible, - containerStyle, confirmButtonTitle, resetButtonTitle, rangeEnd, rangeStart, + onDismiss, } = this.props; const { visibleYears } = this.state; @@ -215,34 +216,40 @@ class YearRangePickerModal extends PureComponent { } return ( - - - - {buttonTooltip} - - - {_.times(NUMBER_OF_ROWS, (row) => this.renderYearRow(data[row], row))} - - - - - + + + + + {buttonTooltip} + + + {_.times(NUMBER_OF_ROWS, (row) => this.renderYearRow(data[row], row))} + + + + + + ); } } diff --git a/theme.js b/theme.js index 3d76af14..bcc432a5 100644 --- a/theme.js +++ b/theme.js @@ -2965,49 +2965,62 @@ export default (variables = defaultThemeVariables) => ({ }, 'shoutem.ui.YearRangePickerModal': { + outerContainer: { + flex: 1, + }, container: { + height: 360, padding: 8, backgroundColor: '#F9F9F9', borderWidth: 1, borderRadius: 8, borderColor: 'rgba(130, 130, 130, 0.1)', - zIndex: 5, + marginHorizontal: 15, }, tooltipContainer: { - flex: 1, flexDirection: 'row', + flex: 1, justifyContent: 'space-between', alignItems: 'center', padding: 8, - minHeight: 56, + height: 56, + }, + yearRow: { + flexDirection: 'row', + flex: 1, }, buttonContainer: { flexDirection: 'row', padding: 8, - marginTop: 8, + height: 56, }, yearContainer: { flex: 1, + alignSelf: 'stretch', flexDirection: 'row', - marginTop: 5, + marginBottom: 5, + justifyContent: 'center', + alignItems: 'center', }, year: { - flexDirection: 'row', - flex: 1, + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, justifyContent: 'center', alignItems: 'center', - paddingVertical: 18, }, yearSelected: { backgroundColor: variables.featuredColor, }, yearFirst: { - paddingLeft: 5, + left: 5, borderTopLeftRadius: 8, borderBottomLeftRadius: 8, }, yearLast: { - paddingRight: 5, + right: 5, borderTopRightRadius: 8, borderBottomRightRadius: 8, }, From 6a745d3f8f8305f6fd972a9ec34c1448af9801e7 Mon Sep 17 00:00:00 2001 From: Slavko Date: Tue, 2 Mar 2021 10:39:05 +0100 Subject: [PATCH 13/14] handle android backdrop issue --- components/YearRangePicker/YearRangePickerModal.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/YearRangePicker/YearRangePickerModal.js b/components/YearRangePicker/YearRangePickerModal.js index f7e8d1fd..295324e2 100644 --- a/components/YearRangePicker/YearRangePickerModal.js +++ b/components/YearRangePicker/YearRangePickerModal.js @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import { LayoutAnimation } from 'react-native'; +import { LayoutAnimation, Platform } from 'react-native'; import PropTypes from 'prop-types'; import _ from 'lodash'; import Modal from 'react-native-modal'; @@ -220,6 +220,7 @@ class YearRangePickerModal extends PureComponent { isVisible={visible} onBackdropPress={onDismiss} useNativeDriver + backdropOpacity={Platform.OS === 'ios' ? 0.7 : 0} > From d63302263b87a8392047501b16f58163197dc846 Mon Sep 17 00:00:00 2001 From: Slavko Date: Tue, 2 Mar 2021 11:32:21 +0100 Subject: [PATCH 14/14] changes per CRs --- components/InlineDropDownMenu/InlineDropDownMenu.js | 4 ++-- components/InlineDropDownMenu/InlineDropDownMenuItem.js | 4 ++-- components/TabMenu/TabMenu.js | 2 +- components/YearRangePicker/YearRangePickerButton.js | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/components/InlineDropDownMenu/InlineDropDownMenu.js b/components/InlineDropDownMenu/InlineDropDownMenu.js index 52ad59fd..27ac6677 100644 --- a/components/InlineDropDownMenu/InlineDropDownMenu.js +++ b/components/InlineDropDownMenu/InlineDropDownMenu.js @@ -13,8 +13,8 @@ import { InlineDropDownMenuItem } from './InlineDropDownMenuItem'; const AnimatedIcon = Animated.createAnimatedComponent(Icon); const optionShape = PropTypes.shape({ - title: PropTypes.string, - key: PropTypes.string, + title: PropTypes.string.isRequired, + key: PropTypes.string.isRequired, }); class InlineDropDownMenu extends PureComponent { diff --git a/components/InlineDropDownMenu/InlineDropDownMenuItem.js b/components/InlineDropDownMenu/InlineDropDownMenuItem.js index d2634695..deadf676 100644 --- a/components/InlineDropDownMenu/InlineDropDownMenuItem.js +++ b/components/InlineDropDownMenu/InlineDropDownMenuItem.js @@ -63,10 +63,10 @@ class InlineDropDownMenuItem extends PureComponent { { translateX: this.animatedValue.interpolate({ inputRange: [0, 1], - outputRange: [window.width, 0] + outputRange: [window.width, 0], }) } - ] + ], } ]} disabled={isSelected} diff --git a/components/TabMenu/TabMenu.js b/components/TabMenu/TabMenu.js index d56c6073..cb08b9bc 100644 --- a/components/TabMenu/TabMenu.js +++ b/components/TabMenu/TabMenu.js @@ -1,6 +1,6 @@ +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import autoBindReact from 'auto-bind/react'; -import React, { PureComponent } from 'react'; import _ from 'lodash'; import { ScrollView, LayoutAnimation } from 'react-native'; import { connectStyle } from '@shoutem/theme'; diff --git a/components/YearRangePicker/YearRangePickerButton.js b/components/YearRangePicker/YearRangePickerButton.js index 455b300b..02a67004 100644 --- a/components/YearRangePicker/YearRangePickerButton.js +++ b/components/YearRangePicker/YearRangePickerButton.js @@ -69,9 +69,9 @@ class YearRangePickerButton extends PureComponent { transform: [{ rotate: this.dropDownIconValue.interpolate({ inputRange: [0, 1], - outputRange: ['0deg', '180deg'] - }) - }] + outputRange: ['0deg', '180deg'], + }), + }], }} />