Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Icon and FloatingActionButton to function component #33336

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 3 additions & 28 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1280,7 +1255,7 @@ SPEC CHECKSUMS:
rnmapbox-maps: 6f638ec002aa6e906a6f766d69cd45f968d98e64
RNPermissions: 9b086c8f05b2e2faa587fdc31f4c5ab4509728aa
RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c
RNReanimated: ab2e96c6d5591c3dfbb38a464f54c8d17fb34a87
RNReanimated: fdbaa9c964bbab7fac50c90862b6cc5f041679b9
RNScreens: d037903436160a4b039d32606668350d2a808806
RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
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 @@ -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",
Expand Down
132 changes: 0 additions & 132 deletions src/components/FloatingActionButton.js

This file was deleted.

74 changes: 74 additions & 0 deletions src/components/FloatingActionButton/FabPlusIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import PropTypes from 'prop-types';
import React, {useEffect} from 'react';
import Animated, {createAnimatedPropAdapter, Easing, interpolateColor, processColor, useAnimatedProps, useSharedValue, withTiming} from 'react-native-reanimated';
import Svg, {Path} from 'react-native-svg';
import useTheme from '@hooks/useTheme';
import variables from '@styles/variables';

const AnimatedPath = Animated.createAnimatedComponent(Path);

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)};
rayane-djouah marked this conversation as resolved.
Show resolved Hide resolved
}
// 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 = {
/* Current state (active or not active) of the component */
isActive: PropTypes.bool.isRequired,
};

function FabPlusIcon({isActive}) {
const {textLight, textDark} = useTheme();
const sharedValue = useSharedValue(isActive ? 1 : 0);

useEffect(() => {
sharedValue.value = withTiming(isActive ? 1 : 0, {
duration: 340,
easing: Easing.inOut(Easing.ease),
});
}, [isActive, sharedValue]);

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

return {
fill,
};
},
[],
adapter,
);

return (
<Svg
width={variables.iconSizeNormal}
height={variables.iconSizeNormal}
>
<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}
/>
</Svg>
);
}

FabPlusIcon.propTypes = propTypes;
FabPlusIcon.displayName = 'FabPlusIcon';

export default FabPlusIcon;
86 changes: 86 additions & 0 deletions src/components/FloatingActionButton/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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 {success, buttonDefaultBG} = 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),
});
}, [isActive, sharedValue]);

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

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

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]}
>
<FabPlusIcon isActive={isActive} />
</AnimatedPressable>
</View>
</Tooltip>
);
});

FloatingActionButton.propTypes = propTypes;
FloatingActionButton.displayName = 'FloatingActionButton';

export default FloatingActionButton;
Loading
Loading