Skip to content

Commit

Permalink
Merge pull request #33336 from rayane-djouah/Migrate-Icon-and-Floatin…
Browse files Browse the repository at this point in the history
…gActionButton-to-function-component

Migrate Icon and FloatingActionButton to function component
  • Loading branch information
roryabraham authored Dec 29, 2023
2 parents 4ac7b3f + 0fb740a commit 9ac1c6e
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 213 deletions.
31 changes: 3 additions & 28 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -802,35 +802,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
Expand Down Expand Up @@ -1333,7 +1308,7 @@ SPEC CHECKSUMS:
rnmapbox-maps: 6f638ec002aa6e906a6f766d69cd45f968d98e64
RNPermissions: 9b086c8f05b2e2faa587fdc31f4c5ab4509728aa
RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c
RNReanimated: ab2e96c6d5591c3dfbb38a464f54c8d17fb34a87
RNReanimated: fdbaa9c964bbab7fac50c90862b6cc5f041679b9
RNScreens: d037903436160a4b039d32606668350d2a808806
RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396
SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,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",
Expand Down
214 changes: 106 additions & 108 deletions src/components/FloatingActionButton.js
Original file line number Diff line number Diff line change
@@ -1,132 +1,130 @@
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 React, {useEffect, useRef} from 'react';
import {Platform, View} from 'react-native';
import Animated, {createAnimatedPropAdapter, Easing, interpolateColor, processColor, useAnimatedProps, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import Svg, {Path} from 'react-native-svg';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
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 AnimatedPath = Animated.createAnimatedComponent(Path);
AnimatedPath.displayName = 'AnimatedPath';

const AnimatedPressable = Animated.createAnimatedComponent(PressableWithFeedback);
AnimatedPressable.displayName = 'AnimatedPressable';

const adapter = createAnimatedPropAdapter(
(props) => {
// eslint-disable-next-line rulesdir/prefer-underscore-method
if (Object.keys(props).includes('fill')) {
// eslint-disable-next-line no-param-reassign
props.fill = {type: 0, payload: processColor(props.fill)};
}
// eslint-disable-next-line rulesdir/prefer-underscore-method
if (Object.keys(props).includes('stroke')) {
// eslint-disable-next-line no-param-reassign
props.stroke = {type: 0, payload: processColor(props.stroke)};
}
},
['fill', 'stroke'],
);
adapter.propTypes = {
fill: PropTypes.string,
stroke: PropTypes.string,
};

const propTypes = {
// Callback to fire on request to toggle the FloatingActionButton
/* Callback to fire on request to toggle the FloatingActionButton */
onPress: PropTypes.func.isRequired,

// Current state (active or not active) of the component
/* 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,
};
/* An accessibility label for the button */
accessibilityLabel: PropTypes.string.isRequired,

const defaultProps = {
buttonRef: () => {},
/* An accessibility role for the button */
role: PropTypes.string.isRequired,
};

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,
const FloatingActionButton = React.forwardRef(({onPress, isActive, accessibilityLabel, role}, ref) => {
const {success, buttonDefaultBG, textLight, textDark} = useTheme();
const styles = useThemeStyles();
const borderRadius = styles.floatingActionButton.borderRadius;
const {translate} = useLocalize();
const fabPressable = useRef(null);
const sharedValue = useSharedValue(isActive ? 1 : 0);
const buttonRef = ref;

useEffect(() => {
sharedValue.value = withTiming(isActive ? 1 : 0, {
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 (
<Tooltip text={this.props.translate('common.new')}>
<View style={this.props.themeStyles.floatingActionButtonContainer}>
<AnimatedPressable
ref={(el) => {
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)]}
}, [isActive, sharedValue]);

const animatedStyle = useAnimatedStyle(() => {
const backgroundColor = interpolateColor(sharedValue.value, [0, 1], [success, buttonDefaultBG]);

return {
transform: [{rotate: `${sharedValue.value * 135}deg`}],
backgroundColor,
borderRadius,
};
});

const animatedProps = useAnimatedProps(
() => {
const fill = interpolateColor(sharedValue.value, [0, 1], [textLight, textDark]);

return {
fill,
};
},
undefined,
Platform.OS === 'web' ? undefined : adapter,
);

return (
<Tooltip text={translate('common.new')}>
<View style={styles.floatingActionButtonContainer}>
<AnimatedPressable
ref={(el) => {
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]}
>
<Svg
width={variables.iconSizeNormal}
height={variables.iconSizeNormal}
>
<AnimatedIcon
src={Expensicons.Plus}
fill={fill}
<AnimatedPath
d="M12,3c0-1.1-0.9-2-2-2C8.9,1,8,1.9,8,3v5H3c-1.1,0-2,0.9-2,2c0,1.1,0.9,2,2,2h5v5c0,1.1,0.9,2,2,2c1.1,0,2-0.9,2-2v-5h5c1.1,0,2-0.9,2-2c0-1.1-0.9-2-2-2h-5V3z"
animatedProps={animatedProps}
/>
</AnimatedPressable>
</View>
</Tooltip>
);
}
}
</Svg>
</AnimatedPressable>
</View>
</Tooltip>
);
});

FloatingActionButton.propTypes = propTypes;
FloatingActionButton.defaultProps = defaultProps;

const FloatingActionButtonWithLocalize = withLocalize(FloatingActionButton);

const FloatingActionButtonWithLocalizeWithRef = React.forwardRef((props, ref) => (
<FloatingActionButtonWithLocalize
// eslint-disable-next-line
{...props}
buttonRef={ref}
/>
));

FloatingActionButtonWithLocalizeWithRef.displayName = 'FloatingActionButtonWithLocalizeWithRef';
FloatingActionButton.displayName = 'FloatingActionButton';

export default compose(withThemeStyles, withTheme, withStyleUtils)(FloatingActionButtonWithLocalizeWithRef);
export default FloatingActionButton;
Loading

0 comments on commit 9ac1c6e

Please sign in to comment.