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

feat: Add support for boxShadow #6749

Open
wants to merge 30 commits into
base: main
Choose a base branch
from

Conversation

patrycjakalinska
Copy link
Contributor

@patrycjakalinska patrycjakalinska commented Nov 22, 2024

Summary

This PR introduces support for boxShadow - a new feature from React Native 0.76+ NewArch. At the same time it address #6687 .

  • Adds parser for boxShadow prop, that transforms string into a boxShadow object.
  • Adds Jest tests
  • Fix colors flickering when run with withSpring

Runtime test are added for future fabric support in RuntimeTests.

Fixes #6687

Screen.Recording.2024-11-22.at.16.23.44.mov

Test plan

To test, paste this code snippet to EmptyExample and run.

EmptyExample code
import React from 'react';
import { Pressable, SafeAreaView, ScrollView, Text, View } from 'react-native';
import Animated, {
  Easing,
  interpolate,
  interpolateColor,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';
import processBoxShadow from 'react-native-reanimated/src/processBoxShadow';

const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
const BUTTON_TRANSITION_TIME = 500;

const EmptyExample = () => {
  const pressed = useSharedValue<number>(0);

  const animatedBoxShadow = useAnimatedStyle(() => {
    const blurRadius = interpolate(pressed.value, [0, 1], [10, 0]);
    const color = interpolateColor(
      pressed.value,
      [0, 1],
      ['#57b495', '#31775d'] 
    );

    const boxShadow = `0px 4px ${blurRadius} 0px ${color}`;

    return {
      boxShadow,
    };
  });

  const backgroundColorStyle = useAnimatedStyle(() => {
    const backgroundColor = interpolateColor(
      pressed.value,
      [0, 1],
      ['#b1dfd0', '#57b495']
    );

    return {
      backgroundColor,
    };
  });

  const handlePressIn = () => {
    pressed.value = withTiming(1, {
      duration: BUTTON_TRANSITION_TIME,
      easing: Easing.out(Easing.ease),
    });
  };

  const handlePressOut = () => {
    pressed.value = withTiming(0, {
      duration: BUTTON_TRANSITION_TIME,
      easing: Easing.out(Easing.ease),
    });
  };

  return (
    <SafeAreaView style={backgroundStyle}>
      <ScrollView style={backgroundStyle}>
        <View
          style={{
            padding: 24,
          }}>
          <Text
            style={{
              fontSize: 18,
              color: '#001a72',
              fontWeight: 'bold',
              textAlign: 'center',
              marginBottom: 16,
            }}>
            The background color animation will work and so will the shadow ✨
          </Text>
          <Text
            style={{
              fontSize: 14,
              color: '#001a72',
              marginBottom: 8,
            }}>
            BoxShadow is officially supported by Reanimated 💅
          </Text>
          <AnimatedPressable
            style={[
              backgroundColorStyle,
              animatedBoxShadow,
              {
                padding: 16,
                borderRadius: 100,
                marginVertical: 16,
              },
            ]}
            onPressIn={handlePressIn}
            onPressOut={handlePressOut}>
            <Text style={{ color: '#001a72' }}>
              Hello I am button with shadow
            </Text>
          </AnimatedPressable>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const backgroundStyle = {
  backgroundColor: '#f8f9ff',
  flex: 1,
};

export default EmptyExample;

@patrycjakalinska patrycjakalinska linked an issue Nov 22, 2024 that may be closed by this pull request
@patrycjakalinska patrycjakalinska marked this pull request as ready for review November 22, 2024 15:49
@patrycjakalinska patrycjakalinska marked this pull request as draft November 22, 2024 17:23
Copy link
Collaborator

@tjzel tjzel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix CI and we are good to go!

packages/react-native-reanimated/src/processBoxShadow.ts Outdated Show resolved Hide resolved
packages/react-native-reanimated/src/processBoxShadow.ts Outdated Show resolved Hide resolved
@patrycjakalinska patrycjakalinska marked this pull request as ready for review November 22, 2024 17:42
@piaskowyk piaskowyk self-requested a review November 22, 2024 23:13
Copy link
Collaborator

@tjzel tjzel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go a bit further. Let's see how boxShadow fares in both Jest and our runtime tests.

@patrycjakalinska patrycjakalinska force-pushed the @patrycjakalinska/support-box-shadow branch from 400f0ce to 5fa603a Compare December 12, 2024 16:04
@patrycjakalinska patrycjakalinska force-pushed the @patrycjakalinska/support-box-shadow branch from 8f2f601 to e43a655 Compare December 16, 2024 09:56
Copy link
Collaborator

@tjzel tjzel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go!! 🥳


// type BoxShadow = string | BoxShadowValue[];

// describe('animation of BoxShadow', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of commenting out the entire file, you could use the skip method from the tests

@@ -349,6 +349,10 @@ export const ColorProperties = makeShareable([
'stroke',
]);

const NestedColorProperties = makeShareable({
boxShadow: ['color'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
boxShadow: ['color'],
boxShadow: 'color',

I think we can just use a simple string instead of an array since we don't know of any more complicated structures. I understand that using an array would be a more flexible solution, but let's keep it simple for now.

Comment on lines 686 to 697
for (const groupKey in nestedPropGroup) {
const nestedProp = nestedPropGroup[groupKey] as StyleProps;

for (const propName in nestedProp) {
if (
NestedColorProperties[
key as keyof typeof NestedColorProperties
].includes(propName)
) {
nestedProp[propName] = processColor(nestedProp[propName]);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of iterating over the entire prop object, let's iterate over NestedColorProperties and check if those properties exist in the prop object because the prop object can potentially have more fields than those listed in NestedColorProperties

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prop object being the general object with all the props passed (backgroundColor, width, boxShadow etc.)? Because in current approach I use the loop that was there before (loop through keys in props), and if there is a boxShadow in props (boxShadow being an array by default), I iterate through objects in array (most cases one object), and check for if there is a color prop in boxShadow object

Can you elaborate more? Do you want to move checking the NestedProp outside the parent loop?

* in rgba format, allowing the animation to run correctly.
*/
if (typeof animation.current === 'object') {
result[key] = { ...animation.current };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also need to copy other objects from the style, such as transforms? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this approach we also copy transform yes, but it doesn't affect it - it only makes sure any nested the color property will work

Comment on lines +242 to +248
if (Array.isArray(updates[propName])) {
updates[propName].forEach((obj: StyleProps) => {
for (const prop in obj) {
last[propName][prop] = obj[prop];
}
});
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. When updates[propName] is an array?
  2. What about spreed operator?
  3. We also need a proper commentary, as this change is not obvious :/
  4. Is any change to avoid that copying here? - just wondering 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. transform and boxShadow
  2. spread operator removed problem of reference, but only copying prop to prop assured the independence of toValue and current (in other words it made sure the animation wouldn't jump)
  3. on it!
  4. in a meaning to avoid copying prop to prop? I tried some other approaches but only this guaranteed smooth animation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Can't animate boxShadow in RN 0.76+
3 participants