Skip to content

Commit 799bc57

Browse files
committed
feat: add BrowseComponentsButton and ComponentIcon for enhanced UI interaction in HomeCTAs
1 parent 4f0bde1 commit 799bc57

2 files changed

Lines changed: 155 additions & 13 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"use client";
2+
3+
import type { Variants } from "motion/react";
4+
import { motion, useAnimation } from "motion/react";
5+
import type { HTMLAttributes } from "react";
6+
import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
7+
8+
import { cn } from "@/lib/utils";
9+
10+
export interface ComponentIconHandle {
11+
startAnimation: () => void;
12+
stopAnimation: () => void;
13+
}
14+
15+
interface ComponentIconProps extends HTMLAttributes<HTMLDivElement> {
16+
size?: number;
17+
}
18+
19+
const makePathVariants = (x: number, y: number): Variants => ({
20+
animate: {
21+
transition: { duration: 0.8, ease: "easeInOut", times: [0, 0.4, 0.6, 1] },
22+
translateX: [0, x, x, 0],
23+
translateY: [0, y, y, 0],
24+
},
25+
normal: { translateX: 0, translateY: 0 },
26+
});
27+
28+
const TOP_VARIANTS = makePathVariants(6.6, 6.6);
29+
const RIGHT_VARIANTS = makePathVariants(-6.6, 6.6);
30+
const BOTTOM_VARIANTS = makePathVariants(-6.6, -6.6);
31+
const LEFT_VARIANTS = makePathVariants(6.6, -6.6);
32+
33+
const ComponentIcon = forwardRef<ComponentIconHandle, ComponentIconProps>(
34+
({ onMouseEnter, onMouseLeave, className, size = 24, ...props }, ref) => {
35+
const controls = useAnimation();
36+
const isControlledRef = useRef(false);
37+
38+
useImperativeHandle(ref, () => {
39+
isControlledRef.current = true;
40+
41+
return {
42+
startAnimation: () => controls.start("animate"),
43+
stopAnimation: () => controls.start("normal"),
44+
};
45+
});
46+
47+
const handleMouseEnter = useCallback(
48+
(e: React.MouseEvent<HTMLDivElement>) => {
49+
if (isControlledRef.current) {
50+
onMouseEnter?.(e);
51+
} else {
52+
controls.start("animate");
53+
}
54+
},
55+
[controls, onMouseEnter]
56+
);
57+
58+
const handleMouseLeave = useCallback(
59+
(e: React.MouseEvent<HTMLDivElement>) => {
60+
if (isControlledRef.current) {
61+
onMouseLeave?.(e);
62+
} else {
63+
controls.start("normal");
64+
}
65+
},
66+
[controls, onMouseLeave]
67+
);
68+
69+
return (
70+
<div
71+
className={cn(className)}
72+
onMouseEnter={handleMouseEnter}
73+
onMouseLeave={handleMouseLeave}
74+
{...props}
75+
>
76+
<svg
77+
fill="none"
78+
height={size}
79+
stroke="currentColor"
80+
strokeLinecap="round"
81+
strokeLinejoin="round"
82+
strokeWidth="2"
83+
viewBox="0 0 24 24"
84+
width={size}
85+
xmlns="http://www.w3.org/2000/svg"
86+
>
87+
<motion.path
88+
animate={controls}
89+
d="M8.916 4.674a1 1 0 0 0 0 1.414l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z"
90+
initial="normal"
91+
variants={TOP_VARIANTS}
92+
/>
93+
<motion.path
94+
animate={controls}
95+
d="M15.536 11.293a1 1 0 0 0 0 1.414l2.376 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z"
96+
initial="normal"
97+
variants={RIGHT_VARIANTS}
98+
/>
99+
<motion.path
100+
animate={controls}
101+
d="M8.916 17.912a1 1 0 0 0 0 1.415l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.415l-2.377-2.376a1 1 0 0 0-1.414 0z"
102+
initial="normal"
103+
variants={BOTTOM_VARIANTS}
104+
/>
105+
<motion.path
106+
animate={controls}
107+
d="M2.297 11.293a1 1 0 0 0 0 1.414l2.377 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414L6.088 8.916a1 1 0 0 0-1.414 0z"
108+
initial="normal"
109+
variants={LEFT_VARIANTS}
110+
/>
111+
</svg>
112+
</div>
113+
);
114+
}
115+
);
116+
117+
ComponentIcon.displayName = "ComponentIcon";
118+
119+
export { ComponentIcon };

components/home-ctas.tsx

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { cn } from "@/lib/utils";
99

1010
import { ArrowRightIcon } from "./animated-icons/arrow-right";
1111
import type { ArrowRightIconHandle } from "./animated-icons/arrow-right";
12+
import { ComponentIcon } from "./animated-icons/component";
13+
import type { ComponentIconHandle } from "./animated-icons/component";
1214

1315
const GetStartedButton = () => {
1416
const arrowRightRef = useRef<ArrowRightIconHandle>(null);
@@ -24,38 +26,59 @@ const GetStartedButton = () => {
2426
return (
2527
<Button
2628
asChild
27-
size="lg"
2829
sound="click"
29-
className="px-4 sm:px-6"
30+
className="px-4"
3031
onMouseEnter={handleMouseEnter}
3132
onMouseLeave={handleMouseLeave}
3233
>
3334
<Link href={ROUTES.DOCS_INSTALLATION} transitionTypes={["nav-forward"]}>
3435
Get Started
35-
<ArrowRightIcon className="hidden sm:inline ml-2" ref={arrowRightRef} />
36+
<ArrowRightIcon className="hidden sm:inline" ref={arrowRightRef} />
3637
</Link>
3738
</Button>
3839
);
3940
};
4041

41-
export const HomeCtas = ({ className }: { className?: string }) => (
42-
<div
43-
className={cn(
44-
"flex flex-wrap items-center justify-center gap-4",
45-
className
46-
)}
47-
>
48-
<GetStartedButton />
42+
const BrowseComponentsButton = () => {
43+
const componentIconRef = useRef<ComponentIconHandle>(null);
44+
45+
const handleMouseEnter = useCallback(() => {
46+
componentIconRef.current?.startAnimation();
47+
}, []);
48+
49+
const handleMouseLeave = useCallback(() => {
50+
componentIconRef.current?.stopAnimation();
51+
}, []);
52+
53+
return (
4954
<Button
5055
asChild
51-
size="lg"
5256
variant="outline"
5357
sound="click"
54-
className="px-4 sm:px-6"
58+
className="px-4"
59+
onMouseEnter={handleMouseEnter}
60+
onMouseLeave={handleMouseLeave}
5561
>
5662
<Link href={ROUTES.DOCS_COMPONENTS} transitionTypes={["nav-forward"]}>
63+
<ComponentIcon
64+
className="hidden sm:inline"
65+
ref={componentIconRef}
66+
size={22}
67+
/>
5768
Browse Components
5869
</Link>
5970
</Button>
71+
);
72+
};
73+
74+
export const HomeCtas = ({ className }: { className?: string }) => (
75+
<div
76+
className={cn(
77+
"flex flex-wrap items-center justify-center gap-4",
78+
className
79+
)}
80+
>
81+
<GetStartedButton />
82+
<BrowseComponentsButton />
6083
</div>
6184
);

0 commit comments

Comments
 (0)