11import { clsx } from "@lib/utils/clsx" ;
22import { WithRequired } from "@lib/utils/typeUtils/WithRequired" ;
3- import { HTMLMotionProps , motion } from "framer-motion" ;
4- import { forwardRef } from "react" ;
3+ import { HTMLMotionProps , motion , useAnimate } from "framer-motion" ;
4+ import { forwardRef , useImperativeHandle , useMemo , useRef } from "react" ;
55
66type ButtonStyleProps = {
77 variant ?: "solid" | "outline" | "link" ;
@@ -33,23 +33,60 @@ function variantClasses({
3333 }
3434}
3535
36- function commonProps < Type extends "a" | "button" > ( {
37- className,
38- ...props
39- } : Type extends "a" ? LinkProps : ButtonProps ) : ( Type extends "a"
40- ? LinkProps
41- : ButtonProps ) & { className : string } {
42- // @ts -expect-error These types do match, but TypeScript doesn't seem to like it
43- return {
44- className : clsx ( variantClasses ( props ) , className , "cursor-pointer" ) ,
45- ...props ,
36+ function mergeRefs < T > (
37+ ...refs : ( React . Ref < T > | undefined ) [ ]
38+ ) : React . RefCallback < T > {
39+ return ( value ) => {
40+ refs . forEach ( ( ref ) => {
41+ if ( typeof ref === "function" ) {
42+ ref ( value ) ;
43+ } else if ( ref !== null ) {
44+ ( ref as React . MutableRefObject < T | null > ) . current = value ;
45+ }
46+ } ) ;
4647 } ;
4748}
4849
50+ function useCommonProps < Type extends "a" | "button" > (
51+ { className, ...props } : Type extends "a" ? LinkProps : ButtonProps ,
52+ ref : Type extends "a"
53+ ? React . Ref < HTMLAnchorElement >
54+ : React . Ref < HTMLButtonElement > ,
55+ ) : ( Type extends "a" ? LinkProps : ButtonProps ) & { className : string } {
56+ const [ scope , animate ] = useAnimate ( ) ;
57+
58+ // @ts -expect-error These types do match, but TypeScript doesn't seem to like it
59+ return useMemo (
60+ ( ) => ( {
61+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62+ onClick : async ( e : any ) => {
63+ props . onClick ?.( e ) ;
64+ await animate (
65+ scope . current ,
66+ { backgroundColor : "#FF00A6" } ,
67+ { duration : 0.1 } ,
68+ ) ;
69+ await animate (
70+ scope . current ,
71+ { backgroundColor : "#DB0082" } ,
72+ { duration : 0.1 } ,
73+ ) ;
74+ } ,
75+ initial : { backgroundColor : "#DB0082" } ,
76+ whileHover : { backgroundColor : "#AC0067" } ,
77+ whileFocus : { backgroundColor : "#AC0067" } ,
78+ className : clsx ( variantClasses ( props ) , className , "cursor-pointer" ) ,
79+ ...props ,
80+ ref : mergeRefs ( ref , scope ) ,
81+ } ) ,
82+ [ animate , className , props , ref , scope ] ,
83+ ) ;
84+ }
85+
4986export const Button = forwardRef < HTMLButtonElement , ButtonProps > (
5087 ( { children, ...props } , ref ) => {
5188 return (
52- < motion . button ref = { ref } { ...commonProps < "button" > ( props ) } >
89+ < motion . button { ...useCommonProps < "button" > ( props , ref ) } >
5390 { children }
5491 </ motion . button >
5592 ) ;
@@ -59,8 +96,10 @@ Button.displayName = "Button";
5996
6097export const ButtonLink = forwardRef < HTMLAnchorElement , LinkProps > (
6198 ( { children, ...props } , ref ) => {
99+ const common = useCommonProps < "a" > ( props ) ;
100+
62101 return (
63- < motion . a ref = { ref } { ...commonProps < "a" > ( props ) } >
102+ < motion . a ref = { ref } { ...common } >
64103 { children }
65104 </ motion . a >
66105 ) ;
@@ -71,7 +110,10 @@ ButtonLink.displayName = "ButtonLink";
71110export const Link = forwardRef < HTMLAnchorElement , Omit < LinkProps , "variant" > > (
72111 ( { children, ...props } , ref ) => {
73112 return (
74- < motion . a ref = { ref } { ...commonProps < "a" > ( { variant : "link" , ...props } ) } >
113+ < motion . a
114+ ref = { ref }
115+ { ...useCommonProps < "a" > ( { variant : "link" , ...props } ) }
116+ >
75117 { children }
76118 </ motion . a >
77119 ) ;
0 commit comments