From bfa6ad558e9f507dfd969384337b6320d74ee5f3 Mon Sep 17 00:00:00 2001 From: Julien Thibeaut Date: Mon, 25 Nov 2024 16:22:25 -0400 Subject: [PATCH] feat: add tilt component (#82) * feat: add Tiltable component with examples and documentation * docs: add usage examples to illustrate the Tilt component's features * feat: add isRevese prop to Tilt component for reverse rotation * docs: add isRevese prop description to Tilt component documentation * feat: update tilt API * feat: add tilt-spotlight * feat: tilt UI examples update * fix: build --------- Co-authored-by: afpedreros --- app/docs/layout.tsx | 5 ++ app/docs/tilt/page.mdx | 52 ++++++++++++++++++ app/docs/tilt/tilt-card-1.tsx | 26 +++++++++ app/docs/tilt/tilt-spotlight.tsx | 43 +++++++++++++++ components/core/tilt.tsx | 92 ++++++++++++++++++++++++++++++++ 5 files changed, 218 insertions(+) create mode 100644 app/docs/tilt/page.mdx create mode 100644 app/docs/tilt/tilt-card-1.tsx create mode 100644 app/docs/tilt/tilt-spotlight.tsx create mode 100644 components/core/tilt.tsx diff --git a/app/docs/layout.tsx b/app/docs/layout.tsx index 0d53a76..b265407 100644 --- a/app/docs/layout.tsx +++ b/app/docs/layout.tsx @@ -152,6 +152,11 @@ const NAVIGATION: NavigationGroup[] = [ name: 'Spinning Text', href: '/docs/spinning-text', }, + { + name: 'Tilt', + href: '/docs/tilt', + isNew: true, + }, ], }, ]; diff --git a/app/docs/tilt/page.mdx b/app/docs/tilt/page.mdx new file mode 100644 index 0000000..3636977 --- /dev/null +++ b/app/docs/tilt/page.mdx @@ -0,0 +1,52 @@ +export const metadata = { + title: 'Tilt - Motion-Primitives', + description: + 'A component that creates an interactive 3D tilt effect that responds to mouse movement, enhancing UI elements with a dynamic depth effect and customizable rotation factors and spring options.', +}; + +import { TiltCard1 } from './tilt-card-1'; +import { TiltSpotlight } from './tilt-spotlight'; +import ComponentCodePreview from '@/components/website/component-code-preview'; +import CodeBlock from '@/components/website/code-block'; + +# Tilt + +A component that creates an interactive 3D tilt effect that responds to mouse movement, enhancing UI elements with a dynamic depth effect and customizable rotation factors and spring options. + +## Examples + +### Basic Tilt Card + +} + filePath='app/docs/tilt/tilt-card-1.tsx' +/> + +### Tilt with Spotlight + +Example of the [Spotlight component](/docs/spotlight) used with the Tilt component. + +} + filePath='app/docs/tilt/tilt-spotlight.tsx' +/> + +## Code + + + +## Component API + +### Border Trail + +| Prop | Type | Default | Description | +| :------------- | :------------ | :------ | :---------------------------------------------- | +| className | string | | Additional CSS classes for styling the tilt. | +| style | MotionStyle | | Additional CSS classes for styling the tilt. | +| rotationFactor | number | 15 | Controls the maximum rotation angle in degrees. | +| isRevese | boolean | false | Reverses the tilt effect's rotation direction. | +| springOptions | SpringOptions | | Spring options for the tilt effect. | + +## Credits + +Initiated by [@AFPedreros](https://github.com/AFPedreros) diff --git a/app/docs/tilt/tilt-card-1.tsx b/app/docs/tilt/tilt-card-1.tsx new file mode 100644 index 0000000..1e65307 --- /dev/null +++ b/app/docs/tilt/tilt-card-1.tsx @@ -0,0 +1,26 @@ +import { Tilt } from '@/components/core/tilt'; + +export function TiltCard1() { + return ( + +
+ Ghost in the shell - Kôkaku kidôtai +
+

+ Ghost in the Shell +

+

Kôkaku kidôtai

+
+
+
+ ); +} diff --git a/app/docs/tilt/tilt-spotlight.tsx b/app/docs/tilt/tilt-spotlight.tsx new file mode 100644 index 0000000..a6a0ee0 --- /dev/null +++ b/app/docs/tilt/tilt-spotlight.tsx @@ -0,0 +1,43 @@ +import { Spotlight } from '@/components/core/spotlight'; +import { Tilt } from '@/components/core/tilt'; + +export function TiltSpotlight() { + return ( +
+ + + Ghost in the shell - Kôkaku kidôtai + +
+

+ Ghost in the Shell +

+

Kôkaku kidôtai

+
+
+ ); +} diff --git a/components/core/tilt.tsx b/components/core/tilt.tsx new file mode 100644 index 0000000..d87cb26 --- /dev/null +++ b/components/core/tilt.tsx @@ -0,0 +1,92 @@ +'use client'; + +import React, { useRef } from 'react'; +import { + motion, + useMotionTemplate, + useMotionValue, + useSpring, + useTransform, + MotionStyle, + SpringOptions, +} from 'framer-motion'; + +type TiltProps = { + children: React.ReactNode; + className?: string; + style?: MotionStyle; + rotationFactor?: number; + isRevese?: boolean; + springOptions?: SpringOptions; +}; + +export function Tilt({ + children, + className, + style, + rotationFactor = 15, + isRevese = false, + springOptions, +}: TiltProps) { + const ref = useRef(null); + + const x = useMotionValue(0); + const y = useMotionValue(0); + + const xSpring = useSpring(x, springOptions); + const ySpring = useSpring(y, springOptions); + + const rotateX = useTransform( + ySpring, + [-0.5, 0.5], + isRevese + ? [rotationFactor, -rotationFactor] + : [-rotationFactor, rotationFactor] + ); + const rotateY = useTransform( + xSpring, + [-0.5, 0.5], + isRevese + ? [-rotationFactor, rotationFactor] + : [rotationFactor, -rotationFactor] + ); + + const transform = useMotionTemplate`perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`; + + const handleMouseMove = (e: React.MouseEvent) => { + if (!ref.current) return; + + const rect = ref.current.getBoundingClientRect(); + const width = rect.width; + const height = rect.height; + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + + const xPos = mouseX / width - 0.5; + const yPos = mouseY / height - 0.5; + + x.set(xPos); + y.set(yPos); + }; + + const handleMouseLeave = () => { + x.set(0); + y.set(0); + }; + + return ( + + {children} + + ); +}