nandorojo / moti

🐼 The React Native (+ Web) animation library, powered by Reanimated 3.
https://moti.fyi
MIT License
3.9k stars 120 forks source link

moti requires expo-linear-gradient when im using react native cli only without expo @nandorojo #329

Open husam868maher opened 6 months ago

husam868maher commented 6 months ago

Is there an existing issue for this?

Do you want this issue prioritized?

Current Behavior

getting this weird error while trying to use moti with react native only

my package json contains these packages

"react-native-reanimated": "^3.6.1",
"react-native": "0.72.7",
"moti": "^0.27.2",
Screenshot 2023-12-18 at 7 29 38 PM

Expected Behavior

getting this weird error while trying to use moti with react native only

my package json contains these packages

"react-native-reanimated": "^3.6.1",
"react-native": "0.72.7",
"moti": "^0.27.2",
Screenshot 2023-12-18 at 7 29 38 PM

Steps To Reproduce

create new react native project with npx react-native init project install moti package instal reanimated package use in in the code

Versions

"react-native-reanimated": "^3.6.1",
    "react-native": "0.72.7",
    "moti": "^0.27.2",

Screenshots

Screenshot 2023-12-18 at 7 29 38 PM Screenshot 2023-12-18 at 7 32 35 PM

Reproduction

npx create-react-native-app -t with-moti

yamak-app commented 6 months ago

@husam868maher @nandorojo same here any fix for this ?

nandorojo commented 6 months ago

Did you use the non-expo approach from the skeleton on docs?

https://moti.fyi/skeleton#non-expo-users

VladyslavMartynov10 commented 6 months ago

Hey @nandorojo!

Recently I've also tried to use Skeleton component with react-native-cli & react-native-lineal-gradient but got the same error as @yamak-app

nandorojo commented 6 months ago

Seems like your Babel plugin isn't working

nandorojo commented 6 months ago

You added the module resolver one from the docs?

VladyslavMartynov10 commented 5 months ago

Hi @nandorojo !

Thanks again for such great package for animations. I've resolved the problem for react-native cli by using this small patch changes:

moti+0.27.2.patch

nandorojo commented 5 months ago

Can you please send the code inline?

VladyslavMartynov10 commented 5 months ago

expo.tsx file:

import { LinearGradient } from 'react-native-linear-gradient'
import React from 'react'

import SkeletonNative from './skeleton-new'
import { MotiSkeletonProps } from './types'

export default function SkeletonExpo(
  props: Omit<MotiSkeletonProps, 'Gradient'>
) {
  return <SkeletonNative {...props} Gradient={LinearGradient as any} />
}

SkeletonExpo.Group = SkeletonNative.Group

skeleton.tsx:


import { LinearGradient } from 'react-native-linear-gradient'
import React, { useState, createContext, useContext } from 'react'
import { View, StyleSheet } from 'react-native'

import { View as MotiView } from '../components'
import { AnimatePresence, MotiTransitionProp } from '../core'
import {
  DEFAULT_SKELETON_SIZE as DEFAULT_SIZE,
  defaultDarkColors,
  defaultLightColors,
  baseColors,
} from './shared'
import { MotiSkeletonProps } from './types'

export default function Skeleton(props: MotiSkeletonProps) {
  const skeletonGroupContext = useContext(SkeletonGroupContext)
  const {
    radius = 8,
    children,
    show = skeletonGroupContext ?? !children,
    width,
    height = children ? undefined : DEFAULT_SIZE,
    boxHeight,
    colorMode = 'dark',
    colors = colorMode === 'dark' ? defaultDarkColors : defaultLightColors,
    backgroundColor = colors[0] ??
      colors[1] ??
      baseColors[colorMode]?.secondary,
    backgroundSize = 6,
    disableExitAnimation,
    transition,
  } = props

  const [measuredWidth, setMeasuredWidth] = useState(0)

  const getBorderRadius = () => {
    if (radius === 'square') {
      return 0
    }
    if (radius === 'round') {
      return 99999
    }
    return radius
  }

  const borderRadius = getBorderRadius()

  const getOuterHeight = () => {
    if (boxHeight != null) return boxHeight
    if (show && !children) {
      return height
    }
    return undefined
  }

  const outerHeight = getOuterHeight()

  return (
    <View
      style={{
        height: outerHeight,
        minHeight: height,
        minWidth: width ?? (children ? undefined : DEFAULT_SIZE),
      }}
    >
      {children}
      <AnimatePresence>
        {show && (
          <MotiView
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              borderRadius,
              width: width ?? (children ? '100%' : DEFAULT_SIZE),
              height: height ?? '100%',
              overflow: 'hidden',
            }}
            animate={{
              backgroundColor,
              opacity: 1,
            }}
            transition={{
              type: 'timing',
            }}
            exit={
              !disableExitAnimation && {
                opacity: 0,
              }
            }
            onLayout={({ nativeEvent }) => {
              if (measuredWidth === nativeEvent.layout.width) return

              setMeasuredWidth(nativeEvent.layout.width)
            }}
            pointerEvents="none"
          >
            <AnimatedGradient
              // force a key change to make the loop animation re-mount
              key={`${JSON.stringify(colors)}-${measuredWidth}-${JSON.stringify(
                transition || null
              )}`}
              colors={colors}
              backgroundSize={backgroundSize}
              measuredWidth={measuredWidth}
              transition={transition}
            />
          </MotiView>
        )}
      </AnimatePresence>
    </View>
  )
}

const AnimatedGradient = React.memo(
  function AnimatedGradient({
    measuredWidth,
    colors,
    backgroundSize,
    transition = {},
  }: {
    measuredWidth: number
    colors: string[]
    backgroundSize: number
    transition?: MotiTransitionProp
  }) {
    return (
      <MotiView
        style={StyleSheet.absoluteFillObject}
        from={{ opacity: 0 }}
        transition={{
          type: 'timing',
          duration: 200,
        }}
        animate={
          measuredWidth
            ? {
                opacity: 1,
              }
            : undefined
        }
      >
        <MotiView
          style={[
            StyleSheet.absoluteFillObject,
            {
              width: measuredWidth * backgroundSize,
            },
          ]}
          from={{
            translateX: 0,
          }}
          animate={
            measuredWidth
              ? {
                  translateX: -measuredWidth * (backgroundSize - 1),
                }
              : undefined
          }
          transition={{
            loop: true,
            delay: 200,
            type: 'timing',
            duration: 3000,
            ...(transition as any),
          }}
        >
          <LinearGradient
            colors={colors}
            start={{
              x: 0.1,
              y: 1,
            }}
            end={{
              x: 1,
              y: 1,
            }}
            style={StyleSheet.absoluteFillObject}
          />
        </MotiView>
      </MotiView>
    )
  },
  function propsAreEqual(prev, next) {
    if (prev.measuredWidth !== next.measuredWidth) return false

    if (prev.backgroundSize !== next.backgroundSize) return false

    const didColorsChange = prev.colors.some((color, index) => {
      return color !== next.colors[index]
    })

    if (didColorsChange) return false

    // transition changes will not be respected, but it'll be in the key
    return true
  }
)

const SkeletonGroupContext = createContext<boolean | undefined>(undefined)

function SkeletonGroup({
  children,
  show,
}: {
  children: React.ReactNode
  /**
   * If `true`, all `Skeleton` children components will be shown.
   *
   * If `false`, the `Skeleton` children will be hidden.
   */
  show: boolean
}) {
  return (
    <SkeletonGroupContext.Provider value={show}>
      {children}
    </SkeletonGroupContext.Provider>
  )
}

Skeleton.Group = SkeletonGroup

The only important change here is this new import { LinearGradient } from 'react-native-linear-gradient' instead of expo-linear-gradient.

babel.config.js:

module.exports = function (api) {
  api.cache(true);

 return {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    'react-native-reanimated/plugin',
    [
      'babel-plugin-module-resolver',
      {
        root: ['./src/'],
        alias: [
          { 'moti/skeleton': 'moti/skeleton/react-native-linear-gradient' },
        ],
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },
    ],
  ],
  };
};
VladyslavMartynov10 commented 5 months ago

I didn't properly tested it with other RN versions. In my case it's working like a charm for both Android/iOS "react-native": "0.72.7"

VladyslavMartynov10 commented 5 months ago

@nandorojo Let me know if it's working on your side?

By the way, maybe it's better to override this react-native-linear-gradient/expo-linear-gradient dependency directly in json with "overrides" or something similar to it. What do you think?

nandorojo commented 5 months ago

Please send the patch file inline

What's wrong with the current docs for the skeleton using the Babel plugin? Doesn't it do this for you?

VladyslavMartynov10 commented 5 months ago
diff --git a/node_modules/moti/src/skeleton/expo.tsx b/node_modules/moti/src/skeleton/expo.tsx
index 1674b89..083824c 100644
--- a/node_modules/moti/src/skeleton/expo.tsx
+++ b/node_modules/moti/src/skeleton/expo.tsx
@@ -1,4 +1,4 @@
-import { LinearGradient } from 'expo-linear-gradient'
+import { LinearGradient } from 'react-native-linear-gradient'
 import React from 'react'

 import SkeletonNative from './skeleton-new'
diff --git a/node_modules/moti/src/skeleton/skeleton.tsx b/node_modules/moti/src/skeleton/skeleton.tsx
index 3ef5dd1..0e3bed3 100644
--- a/node_modules/moti/src/skeleton/skeleton.tsx
+++ b/node_modules/moti/src/skeleton/skeleton.tsx
@@ -1,4 +1,4 @@
-import { LinearGradient } from 'expo-linear-gradient'
+import { LinearGradient } from 'react-native-linear-gradient'
 import React, { useState, createContext, useContext } from 'react'
 import { View, StyleSheet } from 'react-native'

diff --git a/node_modules/moti/src/skeleton/types.ts b/node_modules/moti/src/skeleton/types.ts
index 7fa9d78..176f224 100644
--- a/node_modules/moti/src/skeleton/types.ts
+++ b/node_modules/moti/src/skeleton/types.ts
@@ -1,4 +1,4 @@
-import { LinearGradient } from 'expo-linear-gradient'
+import { LinearGradient } from 'react-native-linear-gradient'
 import { MotiTransitionProp } from '../core'
 import { baseColors } from './shared'

babel-plugin is working as expected now. Before, I had some troubles with changing babel.config.js.