diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 0fac30a26430..eebd6ad532d4 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -777,35 +777,10 @@ PODS:
- React-Core
- RNReactNativeHapticFeedback (1.14.0):
- React-Core
- - RNReanimated (3.5.4):
- - DoubleConversion
- - FBLazyVector
- - glog
- - hermes-engine
- - RCT-Folly
- - RCTRequired
- - RCTTypeSafety
- - React-callinvoker
+ - RNReanimated (3.6.1):
+ - RCT-Folly (= 2021.07.22.00)
- React-Core
- - React-Core/DevSupport
- - React-Core/RCTWebSocket
- - React-CoreModules
- - React-cxxreact
- - React-hermes
- - React-jsi
- - React-jsiexecutor
- - React-jsinspector
- - React-RCTActionSheet
- - React-RCTAnimation
- - React-RCTAppDelegate
- - React-RCTBlob
- - React-RCTImage
- - React-RCTLinking
- - React-RCTNetwork
- - React-RCTSettings
- - React-RCTText
- ReactCommon/turbomodule/core
- - Yoga
- RNScreens (3.21.0):
- React-Core
- React-RCTImage
@@ -1280,7 +1255,7 @@ SPEC CHECKSUMS:
rnmapbox-maps: 6f638ec002aa6e906a6f766d69cd45f968d98e64
RNPermissions: 9b086c8f05b2e2faa587fdc31f4c5ab4509728aa
RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c
- RNReanimated: ab2e96c6d5591c3dfbb38a464f54c8d17fb34a87
+ RNReanimated: fdbaa9c964bbab7fac50c90862b6cc5f041679b9
RNScreens: d037903436160a4b039d32606668350d2a808806
RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
diff --git a/package-lock.json b/package-lock.json
index ebe9d98ecefb..5206b9bf8618 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -100,7 +100,7 @@
"react-native-plaid-link-sdk": "10.8.0",
"react-native-qrcode-svg": "^6.2.0",
"react-native-quick-sqlite": "^8.0.0-beta.2",
- "react-native-reanimated": "3.5.4",
+ "react-native-reanimated": "^3.6.1",
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "3.21.0",
@@ -44561,9 +44561,9 @@
}
},
"node_modules/react-native-reanimated": {
- "version": "3.5.4",
- "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.5.4.tgz",
- "integrity": "sha512-8we9LLDO1o4Oj9/DICeEJ2K1tjfqkJagqQUglxeUAkol/HcEJ6PGxIrpBcNryLqCDYEcu6FZWld/FzizBIw6bg==",
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.6.1.tgz",
+ "integrity": "sha512-F4vG9Yf9PKmE3GaWtVGUpzj3SM6YY2cx1yRHCwiMd1uY7W0gU017LfcVUorboJnj0y5QZqEriEK1Usq2Y8YZqg==",
"dependencies": {
"@babel/plugin-transform-object-assign": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
@@ -84883,9 +84883,9 @@
"requires": {}
},
"react-native-reanimated": {
- "version": "3.5.4",
- "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.5.4.tgz",
- "integrity": "sha512-8we9LLDO1o4Oj9/DICeEJ2K1tjfqkJagqQUglxeUAkol/HcEJ6PGxIrpBcNryLqCDYEcu6FZWld/FzizBIw6bg==",
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.6.1.tgz",
+ "integrity": "sha512-F4vG9Yf9PKmE3GaWtVGUpzj3SM6YY2cx1yRHCwiMd1uY7W0gU017LfcVUorboJnj0y5QZqEriEK1Usq2Y8YZqg==",
"requires": {
"@babel/plugin-transform-object-assign": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
diff --git a/package.json b/package.json
index 06380982ed42..8432a773fdf7 100644
--- a/package.json
+++ b/package.json
@@ -148,7 +148,7 @@
"react-native-plaid-link-sdk": "10.8.0",
"react-native-qrcode-svg": "^6.2.0",
"react-native-quick-sqlite": "^8.0.0-beta.2",
- "react-native-reanimated": "3.5.4",
+ "react-native-reanimated": "^3.6.1",
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "3.21.0",
diff --git a/src/components/FloatingActionButton.js b/src/components/FloatingActionButton.js
deleted file mode 100644
index 791eb150f8c9..000000000000
--- a/src/components/FloatingActionButton.js
+++ /dev/null
@@ -1,132 +0,0 @@
-import PropTypes from 'prop-types';
-import React, {PureComponent} from 'react';
-import {Animated, Easing, View} from 'react-native';
-import compose from '@libs/compose';
-import Icon from './Icon';
-import * as Expensicons from './Icon/Expensicons';
-import PressableWithFeedback from './Pressable/PressableWithFeedback';
-import Tooltip from './Tooltip/PopoverAnchorTooltip';
-import withLocalize, {withLocalizePropTypes} from './withLocalize';
-import withStyleUtils, {withStyleUtilsPropTypes} from './withStyleUtils';
-import withTheme, {withThemePropTypes} from './withTheme';
-import withThemeStyles, {withThemeStylesPropTypes} from './withThemeStyles';
-
-const AnimatedIcon = Animated.createAnimatedComponent(Icon);
-AnimatedIcon.displayName = 'AnimatedIcon';
-
-const AnimatedPressable = Animated.createAnimatedComponent(PressableWithFeedback);
-AnimatedPressable.displayName = 'AnimatedPressable';
-
-const propTypes = {
- // Callback to fire on request to toggle the FloatingActionButton
- onPress: PropTypes.func.isRequired,
-
- // Current state (active or not active) of the component
- isActive: PropTypes.bool.isRequired,
-
- // Ref for the button
- buttonRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
-
- ...withLocalizePropTypes,
- ...withThemePropTypes,
- ...withThemeStylesPropTypes,
- ...withStyleUtilsPropTypes,
-};
-
-const defaultProps = {
- buttonRef: () => {},
-};
-
-class FloatingActionButton extends PureComponent {
- constructor(props) {
- super(props);
- this.animatedValue = new Animated.Value(props.isActive ? 1 : 0);
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.isActive === this.props.isActive) {
- return;
- }
-
- this.animateFloatingActionButton();
- }
-
- /**
- * Animates the floating action button
- * Method is called when the isActive prop changes
- */
- animateFloatingActionButton() {
- const animationFinalValue = this.props.isActive ? 1 : 0;
-
- Animated.timing(this.animatedValue, {
- toValue: animationFinalValue,
- duration: 340,
- easing: Easing.inOut(Easing.ease),
- useNativeDriver: false,
- }).start();
- }
-
- render() {
- const rotate = this.animatedValue.interpolate({
- inputRange: [0, 1],
- outputRange: ['0deg', '135deg'],
- });
-
- const backgroundColor = this.animatedValue.interpolate({
- inputRange: [0, 1],
- outputRange: [this.props.theme.success, this.props.theme.buttonDefaultBG],
- });
-
- const fill = this.animatedValue.interpolate({
- inputRange: [0, 1],
- outputRange: [this.props.theme.textLight, this.props.theme.textDark],
- });
-
- return (
-
-
- {
- this.fabPressable = el;
- if (this.props.buttonRef) {
- this.props.buttonRef.current = el;
- }
- }}
- accessibilityLabel={this.props.accessibilityLabel}
- role={this.props.role}
- pressDimmingValue={1}
- onPress={(e) => {
- // Drop focus to avoid blue focus ring.
- this.fabPressable.blur();
- this.props.onPress(e);
- }}
- onLongPress={() => {}}
- style={[this.props.themeStyles.floatingActionButton, this.props.StyleUtils.getAnimatedFABStyle(rotate, backgroundColor)]}
- >
-
-
-
-
- );
- }
-}
-
-FloatingActionButton.propTypes = propTypes;
-FloatingActionButton.defaultProps = defaultProps;
-
-const FloatingActionButtonWithLocalize = withLocalize(FloatingActionButton);
-
-const FloatingActionButtonWithLocalizeWithRef = React.forwardRef((props, ref) => (
-
-));
-
-FloatingActionButtonWithLocalizeWithRef.displayName = 'FloatingActionButtonWithLocalizeWithRef';
-
-export default compose(withThemeStyles, withTheme, withStyleUtils)(FloatingActionButtonWithLocalizeWithRef);
diff --git a/src/components/FloatingActionButton/FabPlusIcon.js b/src/components/FloatingActionButton/FabPlusIcon.js
new file mode 100644
index 000000000000..09afa00f119d
--- /dev/null
+++ b/src/components/FloatingActionButton/FabPlusIcon.js
@@ -0,0 +1,49 @@
+import PropTypes from 'prop-types';
+import React, {useEffect} from 'react';
+import Animated, {Easing, interpolateColor, useAnimatedProps, useSharedValue, withTiming} from 'react-native-reanimated';
+import Svg, {Path} from 'react-native-svg';
+import useTheme from '@hooks/useTheme';
+
+const AnimatedPath = Animated.createAnimatedComponent(Path);
+
+const propTypes = {
+ /* Current state (active or not active) of the component */
+ isActive: PropTypes.bool.isRequired,
+};
+
+function FabPlusIcon({isActive}) {
+ const theme = useTheme();
+ const animatedValue = useSharedValue(isActive ? 1 : 0);
+
+ useEffect(() => {
+ animatedValue.value = withTiming(isActive ? 1 : 0, {
+ duration: 340,
+ easing: Easing.inOut(Easing.ease),
+ });
+ }, [isActive, animatedValue]);
+
+ const animatedProps = useAnimatedProps(() => {
+ const fill = interpolateColor(animatedValue.value, [0, 1], [theme.textLight, theme.textDark]);
+
+ return {
+ fill,
+ };
+ });
+
+ return (
+
+ );
+}
+
+FabPlusIcon.propTypes = propTypes;
+FabPlusIcon.displayName = 'FabPlusIcon';
+
+export default FabPlusIcon;
diff --git a/src/components/FloatingActionButton/index.js b/src/components/FloatingActionButton/index.js
new file mode 100644
index 000000000000..d341396c44b7
--- /dev/null
+++ b/src/components/FloatingActionButton/index.js
@@ -0,0 +1,85 @@
+import PropTypes from 'prop-types';
+import React, {useEffect, useRef} from 'react';
+import {View} from 'react-native';
+import Animated, {Easing, interpolateColor, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
+import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
+import Tooltip from '@components/Tooltip/PopoverAnchorTooltip';
+import useLocalize from '@hooks/useLocalize';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import FabPlusIcon from './FabPlusIcon';
+
+const AnimatedPressable = Animated.createAnimatedComponent(PressableWithFeedback);
+AnimatedPressable.displayName = 'AnimatedPressable';
+
+const propTypes = {
+ /* Callback to fire on request to toggle the FloatingActionButton */
+ onPress: PropTypes.func.isRequired,
+
+ /* Current state (active or not active) of the component */
+ isActive: PropTypes.bool.isRequired,
+
+ /* An accessibility label for the button */
+ accessibilityLabel: PropTypes.string.isRequired,
+
+ /* An accessibility role for the button */
+ role: PropTypes.string.isRequired,
+};
+
+const FloatingActionButton = React.forwardRef(({onPress, isActive, accessibilityLabel, role}, ref) => {
+ const theme = useTheme();
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+ const fabPressable = useRef(null);
+ const animatedValue = useSharedValue(isActive ? 1 : 0);
+ const buttonRef = ref;
+
+ useEffect(() => {
+ animatedValue.value = withTiming(isActive ? 1 : 0, {
+ duration: 340,
+ easing: Easing.inOut(Easing.ease),
+ });
+ }, [isActive, animatedValue]);
+
+ const animatedStyle = useAnimatedStyle(() => {
+ const backgroundColor = interpolateColor(animatedValue.value, [0, 1], [theme.success, theme.buttonDefaultBG]);
+
+ return {
+ transform: [{rotate: `${animatedValue.value * 135}deg`}],
+ backgroundColor,
+ borderRadius: styles.floatingActionButton.borderRadius,
+ };
+ });
+
+ return (
+
+
+ {
+ fabPressable.current = el;
+ if (buttonRef) {
+ buttonRef.current = el;
+ }
+ }}
+ accessibilityLabel={accessibilityLabel}
+ role={role}
+ pressDimmingValue={1}
+ onPress={(e) => {
+ // Drop focus to avoid blue focus ring.
+ fabPressable.current.blur();
+ onPress(e);
+ }}
+ onLongPress={() => {}}
+ style={[styles.floatingActionButton, animatedStyle]}
+ >
+
+
+
+
+ );
+});
+
+FloatingActionButton.propTypes = propTypes;
+FloatingActionButton.displayName = 'FloatingActionButton';
+
+export default FloatingActionButton;
diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx
index 80abe1872c12..5f82421c0e8e 100644
--- a/src/components/Icon/index.tsx
+++ b/src/components/Icon/index.tsx
@@ -1,8 +1,8 @@
-import React, {PureComponent} from 'react';
+import React from 'react';
import {StyleProp, View, ViewStyle} from 'react-native';
-import withStyleUtils, {WithStyleUtilsProps} from '@components/withStyleUtils';
-import withTheme, {WithThemeProps} from '@components/withTheme';
-import withThemeStyles, {type WithThemeStylesProps} from '@components/withThemeStyles';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import IconWrapperStyles from './IconWrapperStyles';
@@ -41,65 +41,63 @@ type IconProps = {
/** Additional styles to add to the Icon */
additionalStyles?: StyleProp;
-} & WithThemeStylesProps &
- WithThemeProps &
- WithStyleUtilsProps;
-
-// We must use a class component to create an animatable component with the Animated API
-// eslint-disable-next-line react/prefer-stateless-function
-class Icon extends PureComponent {
- // eslint-disable-next-line react/static-property-placement
- public static defaultProps = {
- width: variables.iconSizeNormal,
- height: variables.iconSizeNormal,
- fill: undefined,
- small: false,
- inline: false,
- additionalStyles: [],
- hovered: false,
- pressed: false,
- };
-
- render() {
- const width = this.props.small ? variables.iconSizeSmall : this.props.width;
- const height = this.props.small ? variables.iconSizeSmall : this.props.height;
- const iconStyles = [this.props.StyleUtils.getWidthAndHeightStyle(width ?? 0, height), IconWrapperStyles, this.props.themeStyles.pAbsolute, this.props.additionalStyles];
- const fill = this.props.fill ?? this.props.theme.icon;
+};
- if (this.props.inline) {
- return (
-
-
-
-
-
- );
- }
+function Icon({
+ src,
+ width = variables.iconSizeNormal,
+ height = variables.iconSizeNormal,
+ fill = undefined,
+ small = false,
+ inline = false,
+ hovered = false,
+ pressed = false,
+ additionalStyles = [],
+}: IconProps) {
+ const theme = useTheme();
+ const StyleUtils = useStyleUtils();
+ const styles = useThemeStyles();
+ const iconWidth = small ? variables.iconSizeSmall : width;
+ const iconHeight = small ? variables.iconSizeSmall : height;
+ const iconStyles = [StyleUtils.getWidthAndHeightStyle(width ?? 0, height), IconWrapperStyles, styles.pAbsolute, additionalStyles];
+ const iconFill = fill ?? theme.icon;
+ const IconComponent = src;
+ if (inline) {
return (
-
+
+
+
);
}
+
+ return (
+
+
+
+ );
}
-export default withTheme(withThemeStyles(withStyleUtils(Icon)));
+Icon.displayName = 'Icon';
+
+export default Icon;
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 1dbe0b8587fd..a48724e72fc9 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -446,13 +446,6 @@ function getBackgroundColorWithOpacityStyle(backgroundColor: string, opacity: nu
return {};
}
-function getAnimatedFABStyle(rotate: Animated.Value, backgroundColor: Animated.Value): Animated.WithAnimatedValue {
- return {
- transform: [{rotate}],
- backgroundColor,
- };
-}
-
function getWidthAndHeightStyle(width: number, height?: number): ViewStyle {
return {
width,
@@ -1015,7 +1008,6 @@ const staticStyleUtils = {
combineStyles,
displayIfTrue,
getAmountFontSizeAndLineHeight,
- getAnimatedFABStyle,
getAutoCompleteSuggestionContainerStyle,
getAvatarBorderRadius,
getAvatarBorderStyle,