sixfwa / shiny-button

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Fixing Framer Motion TypeScript Definitions To Allow Custom CSS Variables in `style` Property

mazwrld opened this issue · comments

commented

Issue: Framer Motion TypeScript Definitions Should Allow Custom CSS Variables in style Property

Description

The current TypeScript definitions for Framer Motion do not allow for the use of custom CSS variables in the style property of initial and animate objects. This limitation causes TypeScript errors when using custom CSS variables in these properties. The error message received is: "Object literal may only specify known properties, and ''--x'' does not exist in type 'string[] | AnimationControls | TargetAndTransition'."

Proposed Fix

Modify the TypeScript definitions to allow for custom CSS variables in the style property by using a custom type definition for the style property.

Steps to Reproduce

  1. Use custom CSS variables in the style property of initial or animate objects in a Framer Motion component.
  2. Run TypeScript checks or linting with ESLint.
  3. Observe TypeScript errors related to the use of custom CSS variables.

Example

import { motion, MotionProps } from 'framer-motion'

type CustomStyle = {
  '--x': string;
};

type CustomMotionProps = MotionProps & {
  initial: {
    style: CustomStyle;
    scale: number;
  };
  animate: {
    style: CustomStyle;
  };
};

const initialProps: CustomMotionProps = {
  initial: {
    style: {
      '--x': '100%',
    },
    scale: 1,
  },
  animate: {
    style: {
      '--x': '-100%',
    },
  },
};

// Use `initialProps` in a Framer Motion component

Seems like a good solution, but it didn't work for me.

I avoided the typescript error just by adding a @ts-ignore flag on top of both errors.
Like so:

const ShinyButton = () => {
  return (
    <motion.button
      // @ts-ignore
      initial={{ "--x": "100%", scale: 1 }}
      // @ts-ignore
      animate={{ "--x": "-100%" }}
      whileTap={{ scale: 0.97 }}
      transition={{
        repeat: Infinity,
        repeatType: "loop",
        repeatDelay: 1,
        type: "spring",
        stiffness: 20,
        damping: 15,
        mass: 2,
        scale: {
          type: "spring",
          stiffness: 10,
          damping: 5,
          mass: 0.1,
        },
      }}
      className="px-6 py-2 rounded-md relative radial-gradient"
    >
      <span className="text-neutral-100 tracking-wide font-light h-full w-full block relative linear-mask">
        Start now
      </span>
      <span className="block absolute inset-0 rounded-md p-px linear-overlay" />
    </motion.button>
  );
};

It will build with no errors. Hope it works!

commented

Oh, I totally missed a bug because I didn't test the code in my repo. My bad!

I've fixed it now. In my case, using @ts-ignore would have worked, but my eslint rules are pretty strict, so I had to come up with a different solution.

type ExtendedMotionProps = HTMLMotionProps<'button'> & {
  initial?: Record<string, unknown>
  animate?: Record<string, unknown>
}

type ShinyButtonProps = ExtendedMotionProps & {
  children: React.ReactNode
}

export default function ShinyButton({
  children,
  type = 'button', // Default to "button" type
  ...rest
}: ShinyButtonProps) {
  return (
    <motion.button
      type={type}
      initial={{ '--x': '100%', scale: 1 }}
      animate={{ '--x': '-100%' }}
      whileTap={{ scale: 0.88 }}
      transition={{
        repeat: Infinity,
        repeatType: 'loop',
        repeatDelay: 1,
        type: 'spring',
        stiffness: 20,
        damping: 15,
        mass: 2,
        scale: {
          type: 'spring',
          stiffness: 10,
          damping: 5,
          mass: 0.1,
        },
      }}
      className="radial-gradient relative w-40 rounded-md px-6 py-2"
      {...rest}
    >
      <span className="linear-mask relative block h-full w-full tracking-wide">
        {children}
      </span>
      <span className="linear-overlay absolute inset-0 block rounded-md p-px" />
    </motion.button>
  )
}

@estebanarriaga I used your code to fix the issue but didn't work. @mazwrld Can you share the full code for this component?

@sixfwa I'd request you to put up a solution for this in the comments of YouTube channel.

commented

@Susmita-Dey here it is.

'use client' // for nextjs 

import { motion, type HTMLMotionProps } from 'framer-motion'

type ExtendedMotionProps = HTMLMotionProps<'button'> & {
  initial?: Record<string, unknown>
  animate?: Record<string, unknown>
}

type ShinyButtonProps = ExtendedMotionProps & {
  children: React.ReactNode
}

export default function ShinyButton({
  children,
  type = 'button', // Default to "button" type
  ...rest
}: ShinyButtonProps) {
  return (
    <motion.button
      type={type}
      initial={{ '--x': '100%', scale: 1 }}
      animate={{ '--x': '-100%' }}
      whileTap={{ scale: 0.88 }}
      transition={{
        repeat: Infinity,
        repeatType: 'loop',
        repeatDelay: 1,
        type: 'spring',
        stiffness: 20,
        damping: 15,
        mass: 2,
        scale: {
          type: 'spring',
          stiffness: 10,
          damping: 5,
          mass: 0.1,
        },
      }}
      className="radial-gradient relative w-40 rounded-md px-6 py-2"
      {...rest}
    >
      <span className="linear-mask relative block h-full w-full tracking-wide">
        {children}
      </span>
      <span className="linear-overlay absolute inset-0 block rounded-md p-px" />
    </motion.button>
  )
}

@mazwrld Thanks for sharing this. The button is being rendered now but there's issue with the button style and not showing up as shown in the video. Can you share your repo link?