-
Notifications
You must be signed in to change notification settings - Fork 594
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature (Components): New Pointer component (#544)
* feat: add Pointer component to registry and documentation * fix: format and lint pointer registry and demo files * fix: update pointer demo * fix: bump * fix: update registry * fix: update date --------- Co-authored-by: Dillion Verma <[email protected]>
- Loading branch information
1 parent
e5ae0b2
commit 27ff381
Showing
10 changed files
with
347 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
--- | ||
title: Pointer | ||
date: 2025-02-17 | ||
description: A component that displays a pointer when hovering over an element | ||
author: h3rmel | ||
published: true | ||
--- | ||
|
||
<ComponentPreview name="pointer-demo-1" /> | ||
|
||
## Installation | ||
|
||
<Tabs defaultValue="cli"> | ||
|
||
<TabsList> | ||
<TabsTrigger value="cli">CLI</TabsTrigger> | ||
<TabsTrigger value="manual">Manual</TabsTrigger> | ||
</TabsList> | ||
<TabsContent value="cli"> | ||
|
||
```bash | ||
npx shadcn@latest add "https://magicui.design/r/pointer" | ||
``` | ||
|
||
</TabsContent> | ||
|
||
<TabsContent value="manual"> | ||
|
||
<Step>Copy and paste the following code into your project.</Step> | ||
|
||
<ComponentSource name="pointer" /> | ||
|
||
<Step>Update the import paths to match your project setup.</Step> | ||
|
||
</TabsContent> | ||
|
||
</Tabs> | ||
|
||
## Examples | ||
|
||
## Props | ||
|
||
| Property | Type | Default | Description | | ||
| ----------- | ----------------- | ------- | ----------------------------------------------- | | ||
| `children` | `React.ReactNode` | - | The content that will be wrapped by the pointer | | ||
| `className` | `string` | - | The className of the pointer | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"$schema": "https://ui.shadcn.com/schema/registry-item.json", | ||
"name": "pointer-demo-1", | ||
"type": "registry:example", | ||
"title": "Pointer Demo 1", | ||
"description": "Example showing a pointer effect component", | ||
"registryDependencies": [ | ||
"https://magicui.design/r/pointer" | ||
], | ||
"files": [ | ||
{ | ||
"path": "registry/example/pointer-demo-1.tsx", | ||
"content": "\"use client\";\n\nimport { PointerWrapper } from \"@/components/magicui/pointer\";\n\nexport default function PointerDemo1() {\n return (\n <PointerWrapper>\n <div className=\"relative flex size-[500px] items-center justify-center overflow-hidden rounded-lg border bg-background\">\n <span className=\"pointer-events-none whitespace-pre-wrap bg-gradient-to-b from-black to-gray-300/80 bg-clip-text text-center text-8xl font-semibold leading-none text-transparent dark:from-white dark:to-slate-900/10\">\n Pointer\n </span>\n </div>\n </PointerWrapper>\n );\n}\n", | ||
"type": "registry:example", | ||
"target": "components/pointer-demo-1.tsx" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"$schema": "https://ui.shadcn.com/schema/registry-item.json", | ||
"name": "pointer", | ||
"type": "registry:ui", | ||
"title": "Pointer", | ||
"description": "A component that displays a pointer when hovering over an element", | ||
"dependencies": [ | ||
"motion" | ||
], | ||
"files": [ | ||
{ | ||
"path": "registry/magicui/pointer.tsx", | ||
"content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { AnimatePresence, motion, useMotionValue } from \"motion/react\";\nimport React, { useEffect, useRef, useState } from \"react\";\n\n/**\n * @property {React.ReactNode} children - The child elements to be wrapped\n */\ninterface PointerWrapperProps extends React.HTMLAttributes<HTMLDivElement> {\n children?: React.ReactNode;\n}\n\n/**\n * A component that wraps content and adds a custom pointer animation when hovering\n * over the wrapped area. The pointer follows the mouse movement within the wrapped area.\n *\n * @component\n * @param {PointerWrapperProps} props - The component props\n */\nexport function PointerWrapper({\n children,\n className,\n ...props\n}: PointerWrapperProps) {\n const x = useMotionValue(0);\n const y = useMotionValue(0);\n\n const ref = useRef<HTMLDivElement>(null);\n const [rect, setRect] = useState<DOMRect | null>(null);\n const [isInside, setIsInside] = useState<boolean>(false);\n\n useEffect(() => {\n function updateRect() {\n if (ref.current) {\n setRect(ref.current.getBoundingClientRect());\n }\n }\n\n // Initial rect calculation\n updateRect();\n\n // Update rect on window resize\n window.addEventListener(\"resize\", updateRect);\n\n return () => {\n window.removeEventListener(\"resize\", updateRect);\n };\n }, []);\n\n function handleMouseMove(e: React.MouseEvent<HTMLDivElement>) {\n if (rect) {\n x.set(e.clientX - rect.left);\n y.set(e.clientY - rect.top);\n }\n }\n\n function handleMouseLeave() {\n setIsInside(false);\n }\n\n function handleMouseEnter() {\n if (ref.current) {\n setRect(ref.current.getBoundingClientRect());\n setIsInside(true);\n }\n }\n\n return (\n <div\n ref={ref}\n className={cn(\"relative cursor-none\", className)}\n onMouseLeave={handleMouseLeave}\n onMouseEnter={handleMouseEnter}\n onMouseMove={handleMouseMove}\n {...props}\n >\n <AnimatePresence>{isInside && <Pointer x={x} y={y} />}</AnimatePresence>\n {children}\n </div>\n );\n}\n\n/**\n * @property {MotionValue<number>} x - The x-coordinate position of the pointer\n * @property {MotionValue<number>} y - The y-coordinate position of the pointer\n */\ninterface PointerProps {\n x: any;\n y: any;\n}\n\n/**\n * A custom pointer component that displays an animated arrow cursor\n *\n * @description Used internally by PointerWrapper to show the custom cursor\n *\n * @component\n * @param {PointerProps} props - The component props\n */\nfunction Pointer({ x, y }: PointerProps): JSX.Element {\n return (\n <motion.div\n className=\"pointer-events-none absolute z-50 h-4 w-4 rounded-full\"\n style={{\n top: y,\n left: x,\n }}\n initial={{\n scale: 1,\n opacity: 1,\n }}\n animate={{\n scale: 1,\n opacity: 1,\n }}\n exit={{\n scale: 0,\n opacity: 0,\n }}\n >\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"1\"\n viewBox=\"0 0 16 16\"\n className=\"h-6 w-6 translate-x-[-12px] translate-y-[-10px] rotate-[-70deg] stroke-white text-black\"\n height=\"1em\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M14.082 2.182a.5.5 0 0 1 .103.557L8.528 15.467a.5.5 0 0 1-.917-.007L5.57 10.694.803 8.652a.5.5 0 0 1-.006-.916l12.728-5.657a.5.5 0 0 1 .556.103z\"></path>\n </svg>\n </motion.div>\n );\n}\n", | ||
"type": "registry:ui", | ||
"target": "components/magicui/pointer.tsx" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
"use client"; | ||
|
||
import { PointerWrapper } from "@/registry/magicui/pointer"; | ||
|
||
export default function PointerDemo1() { | ||
return ( | ||
<PointerWrapper> | ||
<div className="relative flex size-[500px] items-center justify-center overflow-hidden rounded-lg border bg-background"> | ||
<span className="pointer-events-none whitespace-pre-wrap bg-gradient-to-b from-black to-gray-300/80 bg-clip-text text-center text-8xl font-semibold leading-none text-transparent dark:from-white dark:to-slate-900/10"> | ||
Pointer | ||
</span> | ||
</div> | ||
</PointerWrapper> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
"use client"; | ||
|
||
import { cn } from "@/lib/utils"; | ||
import { AnimatePresence, motion, useMotionValue } from "motion/react"; | ||
import React, { useEffect, useRef, useState } from "react"; | ||
|
||
/** | ||
* @property {React.ReactNode} children - The child elements to be wrapped | ||
*/ | ||
interface PointerWrapperProps extends React.HTMLAttributes<HTMLDivElement> { | ||
children?: React.ReactNode; | ||
} | ||
|
||
/** | ||
* A component that wraps content and adds a custom pointer animation when hovering | ||
* over the wrapped area. The pointer follows the mouse movement within the wrapped area. | ||
* | ||
* @component | ||
* @param {PointerWrapperProps} props - The component props | ||
*/ | ||
export function PointerWrapper({ | ||
children, | ||
className, | ||
...props | ||
}: PointerWrapperProps) { | ||
const x = useMotionValue(0); | ||
const y = useMotionValue(0); | ||
|
||
const ref = useRef<HTMLDivElement>(null); | ||
const [rect, setRect] = useState<DOMRect | null>(null); | ||
const [isInside, setIsInside] = useState<boolean>(false); | ||
|
||
useEffect(() => { | ||
function updateRect() { | ||
if (ref.current) { | ||
setRect(ref.current.getBoundingClientRect()); | ||
} | ||
} | ||
|
||
// Initial rect calculation | ||
updateRect(); | ||
|
||
// Update rect on window resize | ||
window.addEventListener("resize", updateRect); | ||
|
||
return () => { | ||
window.removeEventListener("resize", updateRect); | ||
}; | ||
}, []); | ||
|
||
function handleMouseMove(e: React.MouseEvent<HTMLDivElement>) { | ||
if (rect) { | ||
x.set(e.clientX - rect.left); | ||
y.set(e.clientY - rect.top); | ||
} | ||
} | ||
|
||
function handleMouseLeave() { | ||
setIsInside(false); | ||
} | ||
|
||
function handleMouseEnter() { | ||
if (ref.current) { | ||
setRect(ref.current.getBoundingClientRect()); | ||
setIsInside(true); | ||
} | ||
} | ||
|
||
return ( | ||
<div | ||
ref={ref} | ||
className={cn("relative cursor-none", className)} | ||
onMouseLeave={handleMouseLeave} | ||
onMouseEnter={handleMouseEnter} | ||
onMouseMove={handleMouseMove} | ||
{...props} | ||
> | ||
<AnimatePresence>{isInside && <Pointer x={x} y={y} />}</AnimatePresence> | ||
{children} | ||
</div> | ||
); | ||
} | ||
|
||
/** | ||
* @property {MotionValue<number>} x - The x-coordinate position of the pointer | ||
* @property {MotionValue<number>} y - The y-coordinate position of the pointer | ||
*/ | ||
interface PointerProps { | ||
x: any; | ||
y: any; | ||
} | ||
|
||
/** | ||
* A custom pointer component that displays an animated arrow cursor | ||
* | ||
* @description Used internally by PointerWrapper to show the custom cursor | ||
* | ||
* @component | ||
* @param {PointerProps} props - The component props | ||
*/ | ||
function Pointer({ x, y }: PointerProps): JSX.Element { | ||
return ( | ||
<motion.div | ||
className="pointer-events-none absolute z-50 h-4 w-4 rounded-full" | ||
style={{ | ||
top: y, | ||
left: x, | ||
}} | ||
initial={{ | ||
scale: 1, | ||
opacity: 1, | ||
}} | ||
animate={{ | ||
scale: 1, | ||
opacity: 1, | ||
}} | ||
exit={{ | ||
scale: 0, | ||
opacity: 0, | ||
}} | ||
> | ||
<svg | ||
stroke="currentColor" | ||
fill="currentColor" | ||
strokeWidth="1" | ||
viewBox="0 0 16 16" | ||
className="h-6 w-6 translate-x-[-12px] translate-y-[-10px] rotate-[-70deg] stroke-white text-black" | ||
height="1em" | ||
width="1em" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path d="M14.082 2.182a.5.5 0 0 1 .103.557L8.528 15.467a.5.5 0 0 1-.917-.007L5.57 10.694.803 8.652a.5.5 0 0 1-.006-.916l12.728-5.657a.5.5 0 0 1 .556.103z"></path> | ||
</svg> | ||
</motion.div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.