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: added spotlight effect on border in magic card #467

Merged
merged 2 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions content/docs/components/magic-card.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,15 @@ npx shadcn@latest add "https://magicui.design/r/magic-card"

### MagicCard

| Prop name | Type | Default | Description |
| -------------------- | --------------- | --------- | ------------------------------------------- |
| children | React.ReactNode | - | The content to be rendered inside the card |
| className | string | - | Additional CSS classes to apply to the card |
| gradientSize | number | 200 | Size of the gradient effect |
| gradientColor | string | "#262626" | Color of the gradient effect |
| gradientOpacity | number | - | Opacity of the gradient effect |
| gradientTransparency | number | 80 | Transparency level of the gradient effect |
| Prop name | Type | Default | Description |
| --------------- | --------------- | --------- | ------------------------------------------- |
| children | React.ReactNode | - | The content to be rendered inside the card |
| className | string | - | Additional CSS classes to apply to the card |
| gradientSize | number | 200 | Size of the gradient effect |
| gradientColor | string | "#262626" | Color of the gradient effect |
| gradientOpacity | number | 0.8 | Opacity of the gradient effect |
| gradientFrom | string | "#9E7AFF" | Start color of the gradient border |
| gradientTo | string | "#FE8BBB" | End color of the gradient border |

## Credits

Expand Down
2 changes: 1 addition & 1 deletion public/r/styles/default/magic-card.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"files": [
{
"path": "magicui/magic-card.tsx",
"content": "\"use client\";\n\nimport React, { useCallback, useEffect, useRef } from \"react\";\nimport { motion, useMotionTemplate, useMotionValue } from \"framer-motion\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface MagicCardProps extends React.HTMLAttributes<HTMLDivElement> {\n gradientSize?: number;\n gradientColor?: string;\n gradientOpacity?: number;\n}\n\nexport function MagicCard({\n children,\n className,\n gradientSize = 200,\n gradientColor = \"#262626\",\n gradientOpacity = 0.8,\n}: MagicCardProps) {\n const cardRef = useRef<HTMLDivElement>(null);\n const mouseX = useMotionValue(-gradientSize);\n const mouseY = useMotionValue(-gradientSize);\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (cardRef.current) {\n const { left, top } = cardRef.current.getBoundingClientRect();\n const clientX = e.clientX;\n const clientY = e.clientY;\n mouseX.set(clientX - left);\n mouseY.set(clientY - top);\n }\n },\n [mouseX, mouseY],\n );\n\n const handleMouseOut = useCallback(\n (e: MouseEvent) => {\n if (!e.relatedTarget) {\n document.removeEventListener(\"mousemove\", handleMouseMove);\n mouseX.set(-gradientSize);\n mouseY.set(-gradientSize);\n }\n },\n [handleMouseMove, mouseX, gradientSize, mouseY],\n );\n\n const handleMouseEnter = useCallback(() => {\n document.addEventListener(\"mousemove\", handleMouseMove);\n mouseX.set(-gradientSize);\n mouseY.set(-gradientSize);\n }, [handleMouseMove, mouseX, gradientSize, mouseY]);\n\n useEffect(() => {\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseout\", handleMouseOut);\n document.addEventListener(\"mouseenter\", handleMouseEnter);\n\n return () => {\n document.removeEventListener(\"mousemove\", handleMouseMove);\n document.removeEventListener(\"mouseout\", handleMouseOut);\n document.removeEventListener(\"mouseenter\", handleMouseEnter);\n };\n }, [handleMouseEnter, handleMouseMove, handleMouseOut]);\n\n useEffect(() => {\n mouseX.set(-gradientSize);\n mouseY.set(-gradientSize);\n }, [gradientSize, mouseX, mouseY]);\n\n return (\n <div\n ref={cardRef}\n className={cn(\n \"group relative flex size-full overflow-hidden rounded-xl bg-neutral-100 dark:bg-neutral-900 border text-black dark:text-white\",\n className,\n )}\n >\n <div className=\"relative z-10\">{children}</div>\n <motion.div\n className=\"pointer-events-none absolute -inset-px rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100\"\n style={{\n background: useMotionTemplate`\n radial-gradient(${gradientSize}px circle at ${mouseX}px ${mouseY}px, ${gradientColor}, transparent 100%)\n `,\n opacity: gradientOpacity,\n }}\n />\n </div>\n );\n}\n",
"content": "\"use client\";\n\nimport React, { useCallback, useEffect, useRef } from \"react\";\nimport { motion, useMotionTemplate, useMotionValue } from \"framer-motion\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface MagicCardProps extends React.HTMLAttributes<HTMLDivElement> {\n gradientSize?: number;\n gradientColor?: string;\n gradientOpacity?: number;\n gradientColors?: [string, string, string];\n}\n\nexport function MagicCard({\n children,\n className,\n gradientSize = 200,\n gradientColor = \"#262626\",\n gradientOpacity = 0.8,\n gradientColors = [\"#FF0080\", \"#4096FF\", \"#78FF78\"],\n}: MagicCardProps) {\n const cardRef = useRef<HTMLDivElement>(null);\n const mouseX = useMotionValue(-gradientSize);\n const mouseY = useMotionValue(-gradientSize);\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (cardRef.current) {\n const { left, top } = cardRef.current.getBoundingClientRect();\n const clientX = e.clientX;\n const clientY = e.clientY;\n mouseX.set(clientX - left);\n mouseY.set(clientY - top);\n }\n },\n [mouseX, mouseY]\n );\n\n const handleMouseOut = useCallback(\n (e: MouseEvent) => {\n if (!e.relatedTarget) {\n document.removeEventListener(\"mousemove\", handleMouseMove);\n mouseX.set(-gradientSize);\n mouseY.set(-gradientSize);\n }\n },\n [handleMouseMove, mouseX, gradientSize, mouseY]\n );\n\n const handleMouseEnter = useCallback(() => {\n document.addEventListener(\"mousemove\", handleMouseMove);\n mouseX.set(-gradientSize);\n mouseY.set(-gradientSize);\n }, [handleMouseMove, mouseX, gradientSize, mouseY]);\n\n useEffect(() => {\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseout\", handleMouseOut);\n document.addEventListener(\"mouseenter\", handleMouseEnter);\n\n return () => {\n document.removeEventListener(\"mousemove\", handleMouseMove);\n document.removeEventListener(\"mouseout\", handleMouseOut);\n document.removeEventListener(\"mouseenter\", handleMouseEnter);\n };\n }, [handleMouseEnter, handleMouseMove, handleMouseOut]);\n\n useEffect(() => {\n mouseX.set(-gradientSize);\n mouseY.set(-gradientSize);\n }, [gradientSize, mouseX, mouseY]);\n\n return (\n <div\n ref={cardRef}\n className={cn(\n \"group relative flex size-full rounded-xl backdrop-blur-lg\",\n className\n )}\n >\n <div className=\"absolute inset-0.5 z-10 rounded-xl bg-neutral-100 dark:bg-neutral-900\" />\n <div className=\"relative z-30\">{children}</div>\n <motion.div\n className=\"pointer-events-none absolute inset-px z-10 rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100\"\n style={{\n background: useMotionTemplate`\n radial-gradient(${gradientSize}px circle at ${mouseX}px ${mouseY}px, ${gradientColor}, transparent 100%)\n `,\n opacity: gradientOpacity,\n }}\n />\n <motion.div\n className=\"pointer-events-none absolute inset-px rounded-xl duration-300 group-hover:opacity-100\"\n style={{\n background: useMotionTemplate`\n radial-gradient(${gradientSize}px circle at ${mouseX}px ${mouseY}px,\n ${gradientColors[0]}, \n ${gradientColors[1]}, \n ${gradientColors[2]}, \n transparent 100%\n )\n `,\n opacity: 1,\n }}\n />\n </div>\n );\n}\n",
"type": "registry:ui",
"target": ""
}
Expand Down
4 changes: 2 additions & 2 deletions registry/default/example/magic-card-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ export default function MagicCardDemo() {
}
>
<MagicCard
className="cursor-pointer flex-col items-center justify-center shadow-2xl whitespace-nowrap text-4xl"
className="cursor-pointer flex-col items-center justify-center whitespace-nowrap text-4xl shadow-2xl"
gradientColor={theme === "dark" ? "#262626" : "#D9D9D955"}
>
Magic
</MagicCard>
<MagicCard
className="cursor-pointer flex-col items-center justify-center shadow-2xl whitespace-nowrap text-4xl"
className="cursor-pointer flex-col items-center justify-center whitespace-nowrap text-4xl shadow-2xl"
gradientColor={theme === "dark" ? "#262626" : "#D9D9D955"}
>
Card
Expand Down
30 changes: 22 additions & 8 deletions registry/default/magicui/magic-card.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
"use client";

import { motion, useMotionTemplate, useMotionValue } from "motion/react";
import React, { useCallback, useEffect, useRef } from "react";
import { motion, useMotionTemplate, useMotionValue } from "framer-motion";

import { cn } from "@/lib/utils";

export interface MagicCardProps extends React.HTMLAttributes<HTMLDivElement> {
interface MagicCardProps extends React.HTMLAttributes<HTMLDivElement> {
gradientSize?: number;
gradientColor?: string;
gradientOpacity?: number;
gradientFrom?: string;
gradientTo?: string;
}

export function MagicCard({
Expand All @@ -17,6 +19,8 @@ export function MagicCard({
gradientSize = 200,
gradientColor = "#262626",
gradientOpacity = 0.8,
gradientFrom = "#9E7AFF",
gradientTo = "#FE8BBB",
}: MagicCardProps) {
const cardRef = useRef<HTMLDivElement>(null);
const mouseX = useMotionValue(-gradientSize);
Expand Down Expand Up @@ -72,21 +76,31 @@ export function MagicCard({
return (
<div
ref={cardRef}
className={cn(
"group relative flex size-full overflow-hidden rounded-xl border bg-neutral-100 text-black dark:bg-neutral-900 dark:text-white",
className,
)}
className={cn("group relative flex size-full rounded-xl", className)}
>
<div className="relative z-10">{children}</div>
<div className="absolute inset-px z-10 rounded-xl bg-neutral-100 dark:bg-neutral-900" />
<div className="relative z-30">{children}</div>
<motion.div
className="pointer-events-none absolute -inset-px rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100"
className="pointer-events-none absolute inset-px z-10 rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100"
style={{
background: useMotionTemplate`
radial-gradient(${gradientSize}px circle at ${mouseX}px ${mouseY}px, ${gradientColor}, transparent 100%)
`,
opacity: gradientOpacity,
}}
/>
<motion.div
className="pointer-events-none absolute inset-0 rounded-xl duration-300 group-hover:opacity-100"
style={{
background: useMotionTemplate`
radial-gradient(${gradientSize}px circle at ${mouseX}px ${mouseY}px,
${gradientFrom},
${gradientTo},
transparent 100%
)
`,
}}
/>
</div>
);
}
Loading