diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index 8b02f40..4d5db66 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -313,6 +313,39 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "logo-carousel": { + name: "logo-carousel", + type: "components:ui", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/ui/logo-carousel")), + source: "", + files: ["registry/default/ui/logo-carousel.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "loading-carousel": { + name: "loading-carousel", + type: "components:ui", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/ui/loading-carousel")), + source: "", + files: ["registry/default/ui/loading-carousel.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "hover-video-player": { + name: "hover-video-player", + type: "components:ui", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/ui/hover-video-player")), + source: "", + files: ["registry/default/ui/hover-video-player.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "text-animate-demo": { name: "text-animate-demo", type: "components:example", @@ -621,6 +654,39 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "logo-carousel-demo": { + name: "logo-carousel-demo", + type: "components:example", + registryDependencies: ["logo-carousel","gradient-heading"], + component: React.lazy(() => import("@/registry/default/example/logo-carousel-demo")), + source: "", + files: ["registry/default/example/logo-carousel-demo.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "loading-carousel-demo": { + name: "loading-carousel-demo", + type: "components:example", + registryDependencies: ["loading-carousel"], + component: React.lazy(() => import("@/registry/default/example/loading-carousel-demo")), + source: "", + files: ["registry/default/example/loading-carousel-demo.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "hover-video-player-demo": { + name: "hover-video-player-demo", + type: "components:example", + registryDependencies: ["hover-video-player"], + component: React.lazy(() => import("@/registry/default/example/hover-video-player-demo")), + source: "", + files: ["registry/default/example/hover-video-player-demo.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "authentication-01": { name: "authentication-01", type: "components:block", diff --git a/apps/www/app/(app)/page.tsx b/apps/www/app/(app)/page.tsx index d3f8020..30da0b5 100644 --- a/apps/www/app/(app)/page.tsx +++ b/apps/www/app/(app)/page.tsx @@ -5,7 +5,6 @@ import { siteConfig } from "@/config/site" import { cn } from "@/lib/utils" import { Badge } from "@/components/ui/badge" import { buttonVariants } from "@/components/ui/button" -import { Card, CardContent } from "@/components/ui/card" import { Announcement } from "@/components/announcement" import { FadeIn } from "@/components/fade-in" import { @@ -15,63 +14,48 @@ import { TypeScriptIcon, } from "@/components/icons" import { FeaturesSection } from "@/components/landing/feature-section" -import { - FeaturedComponent, - LatestComponent, -} from "@/components/landing/featured-component" +import { LatestComponentVertical } from "@/components/landing/featured-component" import { PlugCardGrid } from "@/components/landing/plug-grid" import { TemplateGrid } from "@/components/landing/template-grid" import { PageActions, PageHeader } from "@/components/page-header" -import BgNoiseWrapper from "@/components/texture-wrapper" -import DockAnimation from "@/registry/default/example/dock-demo" -import ShaderBlurDemo from "@/registry/default/example/shader-lens-blur-demo" -import CanvasFractalGrid from "@/registry/default/ui/canvas-fractal-grid" import { GradientHeading } from "@/registry/default/ui/gradient-heading" export default function IndexPage() { return (
-
- {/* */} - - {/* */} - {/* - - - - */} -
+ {/*
*/} {/* */} -
+
- - Components crafted for
Design - Engineers -
+
+ + Components crafted for + + +
Design Engineers +
+
-
+
Ready-to-use
- components for your React apps. + components for your react apps. Shadcn compatible.
@@ -81,7 +65,7 @@ export default function IndexPage() {
- Copy and paste. Open Source. Typed. + Copy and paste, open source, typed.
@@ -109,7 +93,7 @@ export default function IndexPage() {
{/*
*/}
- + {/* */}
diff --git a/apps/www/app/(blocks)/blocks/[style]/[name]/page.tsx b/apps/www/app/(blocks)/blocks/[style]/[name]/page.tsx index f5d6f80..7d0784e 100644 --- a/apps/www/app/(blocks)/blocks/[style]/[name]/page.tsx +++ b/apps/www/app/(blocks)/blocks/[style]/[name]/page.tsx @@ -8,7 +8,7 @@ import { Style, styles } from "@/registry/styles" import "@/styles/mdx.css" import "public/registry/themes.css" -import { AnimatePresence } from "framer-motion" +import { AnimatePresence } from "motion/react" import { BlockChunk } from "@/components/block-chunk" import { BlockWrapper } from "@/components/block-wrapper" diff --git a/apps/www/components/animate/animated-number.tsx b/apps/www/components/animate/animated-number.tsx index 45560de..cb3a3d0 100644 --- a/apps/www/components/animate/animated-number.tsx +++ b/apps/www/components/animate/animated-number.tsx @@ -1,8 +1,8 @@ "use client" import { useEffect, useState } from "react" -import { MotionValue, motion, useSpring, useTransform } from "framer-motion" import { Minus, Plus } from "lucide-react" +import { MotionValue, motion, useSpring, useTransform } from "motion/react" import { toast } from "sonner" import { GradientHeading } from "@/registry/default/ui/gradient-heading" diff --git a/apps/www/components/animate/fade-in.tsx b/apps/www/components/animate/fade-in.tsx index 47f95ec..1d60d1c 100644 --- a/apps/www/components/animate/fade-in.tsx +++ b/apps/www/components/animate/fade-in.tsx @@ -1,7 +1,7 @@ "use client" import { createContext, useContext } from "react" -import { motion, useReducedMotion } from "framer-motion" +import { motion, useReducedMotion } from "motion/react" const FadeInStaggerContext = createContext(false) diff --git a/apps/www/components/animate/feature-card.tsx b/apps/www/components/animate/feature-card.tsx index 53ce598..5191bf1 100644 --- a/apps/www/components/animate/feature-card.tsx +++ b/apps/www/components/animate/feature-card.tsx @@ -10,7 +10,7 @@ import { useMotionValue, type MotionStyle, type MotionValue, -} from "framer-motion" +} from "motion/react" import Balancer from "react-wrap-balancer" import { cn } from "@/lib/utils" diff --git a/apps/www/components/animate/list.tsx b/apps/www/components/animate/list.tsx index a045241..0653e83 100644 --- a/apps/www/components/animate/list.tsx +++ b/apps/www/components/animate/list.tsx @@ -2,14 +2,14 @@ // https://blog.maximeheckel.com/posts/framer-motion-layout-animations/ import React, { useCallback, useState } from "react" +import { CogIcon, Plus, RepeatIcon, Trash, XIcon } from "lucide-react" import { AnimatePresence, LayoutGroup, Reorder, motion, useDragControls, -} from "framer-motion" -import { CogIcon, Plus, RepeatIcon, Trash, XIcon } from "lucide-react" +} from "motion/react" import useMeasure from "react-use-measure" import { toast } from "sonner" diff --git a/apps/www/components/animate/type-animate.tsx b/apps/www/components/animate/type-animate.tsx index 05f5bc7..f1c6a8c 100644 --- a/apps/www/components/animate/type-animate.tsx +++ b/apps/www/components/animate/type-animate.tsx @@ -1,7 +1,7 @@ "use client" import { useEffect, useState } from "react" -import { animate, motion, useMotionValue, useTransform } from "framer-motion" +import { animate, motion, useMotionValue, useTransform } from "motion/react" export interface ITextAnimationProps { delay: number diff --git a/apps/www/components/background-guides.tsx b/apps/www/components/background-guides.tsx index 5f53bb6..4cb1cdc 100644 --- a/apps/www/components/background-guides.tsx +++ b/apps/www/components/background-guides.tsx @@ -1,7 +1,7 @@ "use client" import React, { useCallback, useEffect, useMemo, useState } from "react" -import { AnimatePresence, motion } from "framer-motion" +import { AnimatePresence, motion } from "motion/react" type AnimationDirection = "top-to-bottom" | "bottom-to-top" | "both" | "random" type AnimationEasing = "linear" | "easeIn" | "easeOut" | "easeInOut" | "spring" diff --git a/apps/www/components/block-chunk.tsx b/apps/www/components/block-chunk.tsx index 708d86a..88d42df 100644 --- a/apps/www/components/block-chunk.tsx +++ b/apps/www/components/block-chunk.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import { AnimatePresence, motion } from "framer-motion" +import { AnimatePresence, motion } from "motion/react" import { cn } from "@/lib/utils" import { useLiftMode } from "@/hooks/use-lift-mode" diff --git a/apps/www/components/block-wrapper.tsx b/apps/www/components/block-wrapper.tsx index 088053d..ff4920d 100644 --- a/apps/www/components/block-wrapper.tsx +++ b/apps/www/components/block-wrapper.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import { AnimatePresence, motion } from "framer-motion" +import { AnimatePresence, motion } from "motion/react" import { useLiftMode } from "@/hooks/use-lift-mode" import { Block } from "@/registry/schema" diff --git a/apps/www/components/fade-in.tsx b/apps/www/components/fade-in.tsx index 47f95ec..1d60d1c 100644 --- a/apps/www/components/fade-in.tsx +++ b/apps/www/components/fade-in.tsx @@ -1,7 +1,7 @@ "use client" import { createContext, useContext } from "react" -import { motion, useReducedMotion } from "framer-motion" +import { motion, useReducedMotion } from "motion/react" const FadeInStaggerContext = createContext(false) diff --git a/apps/www/components/landing/featured-component.tsx b/apps/www/components/landing/featured-component.tsx index 4727b26..13c26c4 100644 --- a/apps/www/components/landing/featured-component.tsx +++ b/apps/www/components/landing/featured-component.tsx @@ -1,10 +1,12 @@ import Link from "next/link" import { ArrowRight, SparklesIcon } from "lucide-react" +import LogoCarouselDemo from "@/registry/default/example/logo-carousel-demo" import { ContactFormExample } from "@/registry/default/example/popover-form-demo" import ShaderLensBlurDemo from "@/registry/default/example/shader-lens-blur-demo" import { GradientHeading } from "@/registry/default/ui/gradient-heading" import { LightBoard } from "@/registry/default/ui/lightboard" +import LogoCarousel from "@/registry/default/ui/logo-carousel" import ShaderLensBlur from "@/registry/default/ui/shader-lens-blur" import { Badge } from "../ui/badge" @@ -127,3 +129,40 @@ export function LatestComponent() {
) } + +export function LatestComponentVertical() { + return ( + //
+
+ + {" "} + Latest component + +
+
+
+ Logo Carousel + + Headless logo carousel animation. + {/* */} + +
+
+
+ + {/*
*/} +
+
+ +
+
+ {/*
*/} +
+ ) +} diff --git a/apps/www/components/landing/plug-grid.tsx b/apps/www/components/landing/plug-grid.tsx index 734ec87..705389f 100644 --- a/apps/www/components/landing/plug-grid.tsx +++ b/apps/www/components/landing/plug-grid.tsx @@ -3,7 +3,6 @@ import { StickerIcon } from "lucide-react" import { MinimalCard, MinimalCardDescription, - MinimalCardImage, MinimalCardTitle, } from "@/registry/default/ui/minimal-card" @@ -11,6 +10,13 @@ import { Badge } from "../ui/badge" export function PlugCardGrid() { const cards = [ + { + title: "Free AI Marketing ", + description: + "An AI Cofounder that knows your brand. Start creating marketing copy that converts.", + href: "https://www.newcopy.ai", + img: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExMXFpaG1vaG83YTgxdTdxc2ZreHNtaGphYjF4aXd6c3JvbXNodW9ubSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/7bzrBMHEsgPb20T3C5/giphy.gif", + }, { title: "Free SEO Improvement Tool", description: @@ -25,13 +31,6 @@ export function PlugCardGrid() { href: "https://www.newcult.co", img: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExc3p0Nm1xcnE2eDNkOTJ6NndxaTJlejFodGozZ3RpcXc4MW80OHkwYSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/AdRaGoL5xT1SdI6J5v/giphy.gif", }, - { - title: "Open Source Directory Template", - description: - "Curated bookmarks for design engineers: design tools, JavaScript resources, React.js libraries, and more.", - href: "https://github.com/nolly-studio/cult-directory-template", - img: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExMXFpaG1vaG83YTgxdTdxc2ZreHNtaGphYjF4aXd6c3JvbXNodW9ubSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/7bzrBMHEsgPb20T3C5/giphy.gif", - }, ] return ( @@ -53,7 +52,6 @@ export function PlugCardGrid() { href={card.href} > - {/* */} {card.title} diff --git a/apps/www/components/landing/template-grid.tsx b/apps/www/components/landing/template-grid.tsx index cb0f794..15c0c94 100644 --- a/apps/www/components/landing/template-grid.tsx +++ b/apps/www/components/landing/template-grid.tsx @@ -70,10 +70,10 @@ export function TemplateGrid() { ) : null}
- + {card.name} - + {card.description} @@ -94,7 +94,7 @@ export const TEMPLATES_GRID = [ liveUrl: "https://cult-logo.vercel.app", // replace with the actual live URL meta: "fullstack", description: - "Dalle Logo Generator. Managing authentication and storage with supabase, and implementing a token-based currency system.", + "AI-powered logo generation platform with Dalle integration, token-based currency system, and secure image storage using Supabase.", features: [ { name: "Dalle 2 + 3 Image Generation", @@ -130,7 +130,7 @@ export const TEMPLATES_GRID = [ liveUrl: "https://nextjs.design", // replace with the actual live URL meta: "fullstack", description: - "Ship your own directory startup in 5 minutes with a 3 stage scraping and ai enrichment pipeline. Great for building SEO backlinks and selling ad space. ", + "Automated directory platform with AI enrichment, web scraping pipeline, and built-in authentication for rapid deployment of SEO-optimized listings.", features: [ { name: "Scraping", @@ -171,7 +171,7 @@ export const TEMPLATES_GRID = [ liveUrl: "https://dub.sh/travl", // replace with the actual live URL meta: "fullstack", description: - "Offline First Travel App - A pwa designed to manage and plan travel goals using claude haiku ai and the rxdb to store data locally in the browser, regardless of connectivity.", + "Progressive web app for travel planning with offline capabilities, Claude AI integration, and real-time data synchronization across devices.", features: [ { name: "Offline Capabilities", @@ -209,7 +209,7 @@ export const TEMPLATES_GRID = [ gradient: "bg-gradient-to-b from-white/10 to-[#FF9150] via-[#FFD0B7]/30", slug: "https://www.newcult.co/templates/cult-landing-page", description: - "Fully designed landing page template. Framer motion animations, unique navigation, and more.", + "Modern landing page template featuring Framer Motion animations, custom navigation components, and responsive design optimized for conversions.", features: [ { name: "Animation", @@ -236,7 +236,7 @@ export const TEMPLATES_GRID = [ liveUrl: "https://cleanmyseo.com", meta: "fullstack", description: - "Crawl websites, SEO grading algorithm, test site performance, check OG images, and get AI improvements.", + "Comprehensive SEO analysis tool with web crawling, performance testing, and AI-powered optimization recommendations for website improvement.", features: [ { name: "RSC Web Scraping", @@ -273,7 +273,7 @@ export const TEMPLATES_GRID = [ gradient: "bg-gradient-to-b from-white/10 to-green-400 via-green-50", new: false, description: - "Vector Embedding Template - Full stack template for shipping perplexity style AI apps.", + "Vector embedding solution for building Perplexity-style AI applications with RAG retrieval, real-time source citations, and pgvector search functionality.", features: [ { name: "Vector embeddings", diff --git a/apps/www/components/page-header.tsx b/apps/www/components/page-header.tsx index 961e4de..2a39657 100644 --- a/apps/www/components/page-header.tsx +++ b/apps/www/components/page-header.tsx @@ -10,7 +10,7 @@ function PageHeader({ return (
+ +## Installation + + + + + Manual + + + + + + +Install the required dependencies: + +```bash +npm install motion +``` + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { HoverVideoPlayer } from "@/components/ui/hover-video-player" +``` + +```tsx +export default function Example() { + return ( + + ) +} +``` + +## Props + +| Prop | Type | Default | Description | +| ------------------ | ----------- | ---------- | ---------------------------------------- | +| videoSrc | `string` | `required` | URL of the video to play | +| thumbnailSrc | `string` | - | URL of the thumbnail image | +| hoverOverlay | `ReactNode` | - | Custom overlay shown while hovering | +| pausedOverlay | `ReactNode` | - | Custom overlay shown when paused | +| loadingOverlay | `ReactNode` | - | Custom overlay shown while loading | +| playbackStartDelay | `number` | `0` | Delay before playing video on hover (ms) | +| restartOnPaused | `boolean` | `false` | Reset video position when paused | +| enableControls | `boolean` | `false` | Show playback controls | +| muted | `boolean` | `true` | Start video muted | +| loop | `boolean` | `true` | Loop video playback | +| cropTop | `number` | `0` | Percentage to crop from top | +| cropBottom | `number` | `0` | Percentage to crop from bottom | + +## Examples + +### Basic Player + +```tsx + +``` + +### With Controls + +```tsx + +``` + +### With Custom Overlays + +```tsx +import { PlayIcon } from "lucide-react" + +import { Spinner } from "@/components/ui/spinner" + +;} + loadingOverlay={} + hoverOverlay={
Now Playing
} +/> +``` + +### With Cropping + +```tsx + +``` + +## Features + +- Lazy loading with Intersection Observer +- Mobile touch support with tap-to-play +- Picture-in-Picture mode +- Custom overlay support for different states +- Thumbnail support with smooth transitions +- Playback controls with volume and progress +- Responsive design that works across devices +- Video cropping support for custom positioning +- Automatic pause when out of viewport + +``` + +``` diff --git a/apps/www/content/docs/components/loading-carousel.mdx b/apps/www/content/docs/components/loading-carousel.mdx new file mode 100644 index 0000000..14a1e41 --- /dev/null +++ b/apps/www/content/docs/components/loading-carousel.mdx @@ -0,0 +1,114 @@ +--- +title: Loading Carousel +description: An animated carousel component with loading indicators and customizable display options. +component: true +--- + + + +## Installation + + + + + Manual + + + + + + +Install the required dependencies: + +```bash +npm install embla-carousel-autoplay framer-motion lucide-react +``` + +Install the carousel component: + +```bash +npx shadcn@latest add carousel +``` + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { LoadingCarousel } from "@/components/ui/loading-carousel" +``` + +```tsx +export default function LoadingCarouselDemo() { + return +} +``` + +## Props + +| Prop | Type | Default | Description | +| ------------------ | ------------------------------- | ------------- | ---------------------------------------- | +| tips | `Tip[]` | `defaultTips` | Array of tips to display in the carousel | +| autoplayInterval | `number` | `4000` | Time in milliseconds between slides | +| showNavigation | `boolean` | `false` | Show previous/next navigation buttons | +| showIndicators | `boolean` | `true` | Show slide indicator dots | +| showProgress | `boolean` | `true` | Show progress bar for current slide | +| aspectRatio | `"video" \| "square" \| "wide"` | `"video"` | Aspect ratio of the carousel | +| textPosition | `"top" \| "bottom"` | `"bottom"` | Position of the tip text | +| onTipChange | `(index: number) => void` | - | Callback function when tip changes | +| backgroundTips | `boolean` | `false` | Show tips on the image background | +| backgroundGradient | `boolean` | `false` | Show gradient overlay on background | +| shuffleTips | `boolean` | `false` | Randomly shuffle the order of tips | + +## Examples + +### Default + +```tsx + +``` + +### Custom Interval and Navigation + +```tsx + +``` + +### Square Aspect Ratio with Background Tips + +```tsx + +``` + +### Wide Aspect Ratio with Top Text + +```tsx + +``` + +### Shuffled Tips + +```tsx + +``` diff --git a/apps/www/content/docs/components/logo-carousel.mdx b/apps/www/content/docs/components/logo-carousel.mdx new file mode 100644 index 0000000..d590766 --- /dev/null +++ b/apps/www/content/docs/components/logo-carousel.mdx @@ -0,0 +1,101 @@ +--- +title: Logo Carousel +description: An animated carousel component for displaying brand logos with smooth transitions. +component: true +--- + + + +## Installation + + + + + Manual + + + + + + +Install the required dependencies: + +```bash +npm install motion +``` + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { LogoCarousel } from "@/components/ui/logo-carousel" +``` + +```tsx +export default function LogoCarouselDemo() { + return +} +``` + +## Props + +| Prop | Type | Default | Description | +| ----------- | -------- | ------- | ------------------------------------- | +| columnCount | `number` | `2` | Number of columns to display logos in | + +## Examples + +### Default Two-Column Layout + +```tsx + +``` + +### Custom Column Count + +```tsx + +``` + +## Adding Custom Logos + +To add custom logos to the carousel, modify the `allLogos` array in the component: + +```tsx +const allLogos: Logo[] = [ + { name: "CustomLogo", id: 1, img: CustomLogoComponent }, + // Add more logos... +] +``` + +Each logo should implement the `Logo` interface: + +```tsx +interface Logo { + name: string + id: number + img: React.ComponentType> +} +``` + +## Features + +- Smooth animations using Framer Motion +- Automatic logo cycling with configurable columns +- Randomized logo distribution for visual variety +- SVG logo support with consistent sizing +- Responsive design that works across different screen sizes diff --git a/apps/www/next.config.mjs b/apps/www/next.config.mjs index 3077b90..4eadbb0 100644 --- a/apps/www/next.config.mjs +++ b/apps/www/next.config.mjs @@ -18,6 +18,22 @@ const nextConfig = { protocol: "https", hostname: "images.unsplash.com/random/*", }, + { + protocol: "https", + hostname: "player.vimeo.com", + port: "", + pathname: "**", + }, + { + protocol: "https", + hostname: "openaicomproductionae4b.blob.core.windows.net", + }, + { + protocol: "https", + hostname: "cdn.sanity.io", + port: "", + pathname: "**", + }, ], }, redirects() { diff --git a/apps/www/package.json b/apps/www/package.json index 05d96dd..bf17812 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -68,6 +68,7 @@ "lodash.template": "^4.5.0", "lucide-react": "0.359.0", "markdown-wasm": "^1.2.0", + "motion": "^11.13.5", "next": "14.1.3", "next-contentlayer": "0.3.4", "next-themes": "^0.2.1", diff --git a/apps/www/public/placeholders/blocks-1.png b/apps/www/public/placeholders/blocks-1.png new file mode 100644 index 0000000..cf5337d Binary files /dev/null and b/apps/www/public/placeholders/blocks-1.png differ diff --git a/apps/www/public/placeholders/blocks-2.png b/apps/www/public/placeholders/blocks-2.png new file mode 100644 index 0000000..e4df6af Binary files /dev/null and b/apps/www/public/placeholders/blocks-2.png differ diff --git a/apps/www/public/placeholders/blocks-3.png b/apps/www/public/placeholders/blocks-3.png new file mode 100644 index 0000000..4af2a84 Binary files /dev/null and b/apps/www/public/placeholders/blocks-3.png differ diff --git a/apps/www/public/placeholders/blocks-4.png b/apps/www/public/placeholders/blocks-4.png new file mode 100644 index 0000000..9e8dcb7 Binary files /dev/null and b/apps/www/public/placeholders/blocks-4.png differ diff --git a/apps/www/public/placeholders/brand-1.png b/apps/www/public/placeholders/brand-1.png new file mode 100644 index 0000000..c4820f2 Binary files /dev/null and b/apps/www/public/placeholders/brand-1.png differ diff --git a/apps/www/public/placeholders/cult-cards.png b/apps/www/public/placeholders/cult-cards.png new file mode 100644 index 0000000..572caa0 Binary files /dev/null and b/apps/www/public/placeholders/cult-cards.png differ diff --git a/apps/www/public/placeholders/cult-dir.png b/apps/www/public/placeholders/cult-dir.png new file mode 100644 index 0000000..ae2b314 Binary files /dev/null and b/apps/www/public/placeholders/cult-dir.png differ diff --git a/apps/www/public/placeholders/cult-manifest.png b/apps/www/public/placeholders/cult-manifest.png new file mode 100644 index 0000000..71308a8 Binary files /dev/null and b/apps/www/public/placeholders/cult-manifest.png differ diff --git a/apps/www/public/placeholders/cult-rune.png b/apps/www/public/placeholders/cult-rune.png new file mode 100644 index 0000000..d3360ec Binary files /dev/null and b/apps/www/public/placeholders/cult-rune.png differ diff --git a/apps/www/public/placeholders/cult-seo-2.png b/apps/www/public/placeholders/cult-seo-2.png new file mode 100644 index 0000000..1210d15 Binary files /dev/null and b/apps/www/public/placeholders/cult-seo-2.png differ diff --git a/apps/www/public/placeholders/cult-seo.png b/apps/www/public/placeholders/cult-seo.png new file mode 100644 index 0000000..67eca16 Binary files /dev/null and b/apps/www/public/placeholders/cult-seo.png differ diff --git a/apps/www/public/placeholders/cult-snips.png b/apps/www/public/placeholders/cult-snips.png new file mode 100644 index 0000000..bfa6cc7 Binary files /dev/null and b/apps/www/public/placeholders/cult-snips.png differ diff --git a/apps/www/public/placeholders/newcopy-thumbnail.png b/apps/www/public/placeholders/newcopy-thumbnail.png new file mode 100644 index 0000000..6ad554b Binary files /dev/null and b/apps/www/public/placeholders/newcopy-thumbnail.png differ diff --git a/apps/www/public/registry/index.json b/apps/www/public/registry/index.json index 9b3c11c..a37e4e1 100644 --- a/apps/www/public/registry/index.json +++ b/apps/www/public/registry/index.json @@ -281,5 +281,35 @@ "ui/expandable.tsx" ], "type": "components:ui" + }, + { + "name": "logo-carousel", + "dependencies": [ + "framer-motion" + ], + "files": [ + "ui/logo-carousel.tsx" + ], + "type": "components:ui" + }, + { + "name": "loading-carousel", + "dependencies": [ + "framer-motion" + ], + "files": [ + "ui/loading-carousel.tsx" + ], + "type": "components:ui" + }, + { + "name": "hover-video-player", + "dependencies": [ + "framer-motion" + ], + "files": [ + "ui/hover-video-player.tsx" + ], + "type": "components:ui" } ] \ No newline at end of file diff --git a/apps/www/public/registry/styles/default/animated-number.json b/apps/www/public/registry/styles/default/animated-number.json index 2678dc8..030abc8 100644 --- a/apps/www/public/registry/styles/default/animated-number.json +++ b/apps/www/public/registry/styles/default/animated-number.json @@ -6,7 +6,7 @@ "files": [ { "name": "animated-number.tsx", - "content": "\"use client\"\n\nimport { useEffect } from \"react\"\nimport { MotionValue, motion, useSpring, useTransform } from \"framer-motion\"\n\ninterface AnimatedNumberProps {\n value: number\n mass?: number\n stiffness?: number\n damping?: number\n precision?: number\n format?: (value: number) => string\n onAnimationStart?: () => void\n onAnimationComplete?: () => void\n}\n\nexport function AnimatedNumber({\n value,\n mass = 0.8,\n stiffness = 75,\n damping = 15,\n precision = 0,\n format = (num) => num.toLocaleString(),\n onAnimationStart,\n onAnimationComplete,\n}: AnimatedNumberProps) {\n const spring = useSpring(value, { mass, stiffness, damping })\n const display: MotionValue = useTransform(spring, (current) =>\n format(parseFloat(current.toFixed(precision)))\n )\n\n useEffect(() => {\n spring.set(value)\n if (onAnimationStart) onAnimationStart()\n const unsubscribe = spring.onChange(() => {\n if (spring.get() === value && onAnimationComplete) onAnimationComplete()\n })\n return () => unsubscribe()\n }, [spring, value, onAnimationStart, onAnimationComplete])\n\n return {display}\n}\n" + "content": "\"use client\"\n\nimport { useEffect } from \"react\"\nimport { MotionValue, motion, useSpring, useTransform } from \"motion/react\"\n\ninterface AnimatedNumberProps {\n value: number\n mass?: number\n stiffness?: number\n damping?: number\n precision?: number\n format?: (value: number) => string\n onAnimationStart?: () => void\n onAnimationComplete?: () => void\n}\n\nexport function AnimatedNumber({\n value,\n mass = 0.8,\n stiffness = 75,\n damping = 15,\n precision = 0,\n format = (num) => num.toLocaleString(),\n onAnimationStart,\n onAnimationComplete,\n}: AnimatedNumberProps) {\n const spring = useSpring(value, { mass, stiffness, damping })\n const display: MotionValue = useTransform(spring, (current) =>\n format(parseFloat(current.toFixed(precision)))\n )\n\n useEffect(() => {\n spring.set(value)\n if (onAnimationStart) onAnimationStart()\n const unsubscribe = spring.onChange(() => {\n if (spring.get() === value && onAnimationComplete) onAnimationComplete()\n })\n return () => unsubscribe()\n }, [spring, value, onAnimationStart, onAnimationComplete])\n\n return {display}\n}\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/bg-animated-fractal-dot-grid.json b/apps/www/public/registry/styles/default/bg-animated-fractal-dot-grid.json index e13b35e..f57e01f 100644 --- a/apps/www/public/registry/styles/default/bg-animated-fractal-dot-grid.json +++ b/apps/www/public/registry/styles/default/bg-animated-fractal-dot-grid.json @@ -6,7 +6,7 @@ "files": [ { "name": "bg-animated-fractal-dot-grid.tsx", - "content": "\"use client\"\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport { AnimatePresence, motion } from \"framer-motion\"\n\ninterface FractalDotGridProps {\n /** Size of each dot in pixels */\n dotSize?: number\n /** Spacing between dots in pixels */\n dotSpacing?: number\n /** Opacity of dots (0-1) */\n dotOpacity?: number\n /** Intensity of the wave effect when hovering */\n waveIntensity?: number\n /** Radius of the wave effect in pixels */\n waveRadius?: number\n /** Color of the dots (supports any valid CSS color) */\n dotColor?: string\n /** Color of the dot glow effect (supports any valid CSS color) */\n glowColor?: string\n /** Enable or disable the noise overlay */\n enableNoise?: boolean\n /** Opacity of the noise overlay (0-1) */\n noiseOpacity?: number\n /** Enable or disable the mouse glow effect */\n enableMouseGlow?: boolean\n /** Set the initial performance level */\n initialPerformance?: \"low\" | \"medium\" | \"high\"\n}\n\nconst NoiseSVG = React.memo(() => (\n \n \n \n \n \n \n))\n\nNoiseSVG.displayName = \"NoiseSVG\"\n\nconst NoiseOverlay: React.FC<{ opacity: number }> = ({ opacity }) => (\n \n \n
\n)\n\nconst useResponsive = () => {\n const [windowSize, setWindowSize] = useState({\n width: typeof window !== \"undefined\" ? window.innerWidth : 0,\n height: typeof window !== \"undefined\" ? window.innerHeight : 0,\n })\n\n useEffect(() => {\n const handleResize = () => {\n setWindowSize({\n width: window.innerWidth,\n height: window.innerHeight,\n })\n }\n\n window.addEventListener(\"resize\", handleResize)\n return () => window.removeEventListener(\"resize\", handleResize)\n }, [])\n\n return {\n isMobile: windowSize.width < 768,\n isTablet: windowSize.width >= 768 && windowSize.width < 1024,\n isDesktop: windowSize.width >= 1024,\n }\n}\n\nconst usePerformance = (\n initialPerformance: \"low\" | \"medium\" | \"high\" = \"medium\"\n) => {\n const [performance, setPerformance] = useState(initialPerformance)\n const [fps, setFps] = useState(60)\n\n useEffect(() => {\n let frameCount = 0\n let lastTime = globalThis.performance.now()\n let framerId: number\n\n const measureFps = (time: number) => {\n frameCount++\n if (time - lastTime > 1000) {\n setFps(Math.round((frameCount * 1000) / (time - lastTime)))\n frameCount = 0\n lastTime = time\n }\n framerId = requestAnimationFrame(measureFps)\n }\n\n framerId = requestAnimationFrame(measureFps)\n\n return () => cancelAnimationFrame(framerId)\n }, [])\n\n useEffect(() => {\n if (fps < 30 && performance !== \"low\") {\n setPerformance(\"low\")\n } else if (fps >= 30 && fps < 50 && performance !== \"medium\") {\n setPerformance(\"medium\")\n } else if (fps >= 50 && performance !== \"high\") {\n setPerformance(\"high\")\n }\n }, [fps, performance])\n\n return { performance, fps }\n}\n\nconst DotCanvas: React.FC<{\n dotSize: number\n dotSpacing: number\n dotOpacity: number\n waveIntensity: number\n waveRadius: number\n dotColor: string\n glowColor: string\n performance: \"low\" | \"medium\" | \"high\"\n mousePos: { x: number; y: number }\n}> = React.memo(\n ({\n dotSize,\n dotSpacing,\n dotOpacity,\n waveIntensity,\n waveRadius,\n dotColor,\n glowColor,\n performance,\n mousePos,\n }) => {\n const canvasRef = useRef(null)\n const animationRef = useRef()\n\n const drawDots = useCallback(\n (ctx: CanvasRenderingContext2D, time: number) => {\n const { width, height } = ctx.canvas\n ctx.clearRect(0, 0, width, height)\n\n const performanceSettings = {\n low: { skip: 3 },\n medium: { skip: 2 },\n high: { skip: 1 },\n }\n\n const skip = performanceSettings[performance].skip\n\n const cols = Math.ceil(width / dotSpacing)\n const rows = Math.ceil(height / dotSpacing)\n\n const centerX = mousePos.x * width\n const centerY = mousePos.y * height\n\n for (let i = 0; i < cols; i += skip) {\n for (let j = 0; j < rows; j += skip) {\n const x = i * dotSpacing\n const y = j * dotSpacing\n\n const distanceX = x - centerX\n const distanceY = y - centerY\n const distance = Math.sqrt(\n distanceX * distanceX + distanceY * distanceY\n )\n\n let dotX = x\n let dotY = y\n\n if (distance < waveRadius) {\n const waveStrength = Math.pow(1 - distance / waveRadius, 2)\n const angle = Math.atan2(distanceY, distanceX)\n const waveOffset =\n Math.sin(distance * 0.05 - time * 0.005) *\n waveIntensity *\n waveStrength\n dotX += Math.cos(angle) * waveOffset\n dotY += Math.sin(angle) * waveOffset\n\n const glowRadius = dotSize * (1 + waveStrength)\n const gradient = ctx.createRadialGradient(\n dotX,\n dotY,\n 0,\n dotX,\n dotY,\n glowRadius\n )\n gradient.addColorStop(\n 0,\n glowColor.replace(\"1)\", `${dotOpacity * (1 + waveStrength)})`)\n )\n gradient.addColorStop(1, glowColor.replace(\"1)\", \"0)\"))\n ctx.fillStyle = gradient\n } else {\n ctx.fillStyle = dotColor.replace(\"1)\", `${dotOpacity})`)\n }\n\n ctx.beginPath()\n ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)\n ctx.fill()\n }\n }\n },\n [\n dotSize,\n dotSpacing,\n dotOpacity,\n waveIntensity,\n waveRadius,\n dotColor,\n glowColor,\n performance,\n mousePos,\n ]\n )\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const ctx = canvas.getContext(\"2d\")\n if (!ctx) return\n\n const resizeCanvas = () => {\n canvas.width = window.innerWidth\n canvas.height = window.innerHeight\n }\n\n resizeCanvas()\n window.addEventListener(\"resize\", resizeCanvas)\n\n let lastTime = 0\n const animate = (time: number) => {\n if (time - lastTime > 16) {\n drawDots(ctx, time)\n lastTime = time\n }\n animationRef.current = requestAnimationFrame(animate)\n }\n\n animationRef.current = requestAnimationFrame(animate)\n\n return () => {\n window.removeEventListener(\"resize\", resizeCanvas)\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current)\n }\n }\n }, [drawDots])\n\n return (\n \n )\n }\n)\n\nDotCanvas.displayName = \"DotCanvas\"\n\nconst MouseGlow: React.FC<{\n glowColor: string\n mousePos: { x: number; y: number }\n}> = React.memo(({ glowColor, mousePos }) => (\n <>\n \n \n \n))\n\nMouseGlow.displayName = \"MouseGlow\"\n\nexport function FractalDotGrid({\n dotSize = 4,\n dotSpacing = 20,\n dotOpacity = 0.3,\n waveIntensity = 30,\n waveRadius = 200,\n dotColor = \"rgba(100, 100, 255, 1)\",\n glowColor = \"rgba(100, 100, 255, 1)\",\n enableNoise = true,\n noiseOpacity = 0.03,\n enableMouseGlow = true,\n initialPerformance = \"medium\",\n}: FractalDotGridProps) {\n const containerRef = useRef(null)\n const { isMobile, isTablet } = useResponsive()\n const { performance } = usePerformance(initialPerformance)\n const [mousePos, setMousePos] = useState({ x: 0, y: 0 })\n\n const handleMouseMove = useCallback((event: MouseEvent) => {\n const { clientX, clientY } = event\n const { left, top, width, height } =\n containerRef.current?.getBoundingClientRect() ?? {\n left: 0,\n top: 0,\n width: 0,\n height: 0,\n }\n const x = (clientX - left) / width\n const y = (clientY - top) / height\n setMousePos({ x, y })\n }, [])\n\n useEffect(() => {\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n }, [handleMouseMove])\n\n const responsiveDotSize = useMemo(() => {\n if (isMobile) return dotSize * 0.75\n if (isTablet) return dotSize * 0.9\n return dotSize\n }, [isMobile, isTablet, dotSize])\n\n const responsiveDotSpacing = useMemo(() => {\n if (isMobile) return dotSpacing * 1.5\n if (isTablet) return dotSpacing * 1.25\n return dotSpacing\n }, [isMobile, isTablet, dotSpacing])\n\n return (\n \n \n \n {enableNoise && }\n {enableMouseGlow && (\n \n )}\n \n \n )\n}\n\nexport default FractalDotGrid\n" + "content": "\"use client\"\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport { AnimatePresence, motion } from \"motion/react\"\n\ninterface FractalDotGridProps {\n /** Size of each dot in pixels */\n dotSize?: number\n /** Spacing between dots in pixels */\n dotSpacing?: number\n /** Opacity of dots (0-1) */\n dotOpacity?: number\n /** Intensity of the wave effect when hovering */\n waveIntensity?: number\n /** Radius of the wave effect in pixels */\n waveRadius?: number\n /** Color of the dots (supports any valid CSS color) */\n dotColor?: string\n /** Color of the dot glow effect (supports any valid CSS color) */\n glowColor?: string\n /** Enable or disable the noise overlay */\n enableNoise?: boolean\n /** Opacity of the noise overlay (0-1) */\n noiseOpacity?: number\n /** Enable or disable the mouse glow effect */\n enableMouseGlow?: boolean\n /** Set the initial performance level */\n initialPerformance?: \"low\" | \"medium\" | \"high\"\n}\n\nconst NoiseSVG = React.memo(() => (\n \n \n \n \n \n \n))\n\nNoiseSVG.displayName = \"NoiseSVG\"\n\nconst NoiseOverlay: React.FC<{ opacity: number }> = ({ opacity }) => (\n \n \n
\n)\n\nconst useResponsive = () => {\n const [windowSize, setWindowSize] = useState({\n width: typeof window !== \"undefined\" ? window.innerWidth : 0,\n height: typeof window !== \"undefined\" ? window.innerHeight : 0,\n })\n\n useEffect(() => {\n const handleResize = () => {\n setWindowSize({\n width: window.innerWidth,\n height: window.innerHeight,\n })\n }\n\n window.addEventListener(\"resize\", handleResize)\n return () => window.removeEventListener(\"resize\", handleResize)\n }, [])\n\n return {\n isMobile: windowSize.width < 768,\n isTablet: windowSize.width >= 768 && windowSize.width < 1024,\n isDesktop: windowSize.width >= 1024,\n }\n}\n\nconst usePerformance = (\n initialPerformance: \"low\" | \"medium\" | \"high\" = \"medium\"\n) => {\n const [performance, setPerformance] = useState(initialPerformance)\n const [fps, setFps] = useState(60)\n\n useEffect(() => {\n let frameCount = 0\n let lastTime = globalThis.performance.now()\n let framerId: number\n\n const measureFps = (time: number) => {\n frameCount++\n if (time - lastTime > 1000) {\n setFps(Math.round((frameCount * 1000) / (time - lastTime)))\n frameCount = 0\n lastTime = time\n }\n framerId = requestAnimationFrame(measureFps)\n }\n\n framerId = requestAnimationFrame(measureFps)\n\n return () => cancelAnimationFrame(framerId)\n }, [])\n\n useEffect(() => {\n if (fps < 30 && performance !== \"low\") {\n setPerformance(\"low\")\n } else if (fps >= 30 && fps < 50 && performance !== \"medium\") {\n setPerformance(\"medium\")\n } else if (fps >= 50 && performance !== \"high\") {\n setPerformance(\"high\")\n }\n }, [fps, performance])\n\n return { performance, fps }\n}\n\nconst DotCanvas: React.FC<{\n dotSize: number\n dotSpacing: number\n dotOpacity: number\n waveIntensity: number\n waveRadius: number\n dotColor: string\n glowColor: string\n performance: \"low\" | \"medium\" | \"high\"\n mousePos: { x: number; y: number }\n}> = React.memo(\n ({\n dotSize,\n dotSpacing,\n dotOpacity,\n waveIntensity,\n waveRadius,\n dotColor,\n glowColor,\n performance,\n mousePos,\n }) => {\n const canvasRef = useRef(null)\n const animationRef = useRef()\n\n const drawDots = useCallback(\n (ctx: CanvasRenderingContext2D, time: number) => {\n const { width, height } = ctx.canvas\n ctx.clearRect(0, 0, width, height)\n\n const performanceSettings = {\n low: { skip: 3 },\n medium: { skip: 2 },\n high: { skip: 1 },\n }\n\n const skip = performanceSettings[performance].skip\n\n const cols = Math.ceil(width / dotSpacing)\n const rows = Math.ceil(height / dotSpacing)\n\n const centerX = mousePos.x * width\n const centerY = mousePos.y * height\n\n for (let i = 0; i < cols; i += skip) {\n for (let j = 0; j < rows; j += skip) {\n const x = i * dotSpacing\n const y = j * dotSpacing\n\n const distanceX = x - centerX\n const distanceY = y - centerY\n const distance = Math.sqrt(\n distanceX * distanceX + distanceY * distanceY\n )\n\n let dotX = x\n let dotY = y\n\n if (distance < waveRadius) {\n const waveStrength = Math.pow(1 - distance / waveRadius, 2)\n const angle = Math.atan2(distanceY, distanceX)\n const waveOffset =\n Math.sin(distance * 0.05 - time * 0.005) *\n waveIntensity *\n waveStrength\n dotX += Math.cos(angle) * waveOffset\n dotY += Math.sin(angle) * waveOffset\n\n const glowRadius = dotSize * (1 + waveStrength)\n const gradient = ctx.createRadialGradient(\n dotX,\n dotY,\n 0,\n dotX,\n dotY,\n glowRadius\n )\n gradient.addColorStop(\n 0,\n glowColor.replace(\"1)\", `${dotOpacity * (1 + waveStrength)})`)\n )\n gradient.addColorStop(1, glowColor.replace(\"1)\", \"0)\"))\n ctx.fillStyle = gradient\n } else {\n ctx.fillStyle = dotColor.replace(\"1)\", `${dotOpacity})`)\n }\n\n ctx.beginPath()\n ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)\n ctx.fill()\n }\n }\n },\n [\n dotSize,\n dotSpacing,\n dotOpacity,\n waveIntensity,\n waveRadius,\n dotColor,\n glowColor,\n performance,\n mousePos,\n ]\n )\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const ctx = canvas.getContext(\"2d\")\n if (!ctx) return\n\n const resizeCanvas = () => {\n canvas.width = window.innerWidth\n canvas.height = window.innerHeight\n }\n\n resizeCanvas()\n window.addEventListener(\"resize\", resizeCanvas)\n\n let lastTime = 0\n const animate = (time: number) => {\n if (time - lastTime > 16) {\n drawDots(ctx, time)\n lastTime = time\n }\n animationRef.current = requestAnimationFrame(animate)\n }\n\n animationRef.current = requestAnimationFrame(animate)\n\n return () => {\n window.removeEventListener(\"resize\", resizeCanvas)\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current)\n }\n }\n }, [drawDots])\n\n return (\n \n )\n }\n)\n\nDotCanvas.displayName = \"DotCanvas\"\n\nconst MouseGlow: React.FC<{\n glowColor: string\n mousePos: { x: number; y: number }\n}> = React.memo(({ glowColor, mousePos }) => (\n <>\n \n \n \n))\n\nMouseGlow.displayName = \"MouseGlow\"\n\nexport function FractalDotGrid({\n dotSize = 4,\n dotSpacing = 20,\n dotOpacity = 0.3,\n waveIntensity = 30,\n waveRadius = 200,\n dotColor = \"rgba(100, 100, 255, 1)\",\n glowColor = \"rgba(100, 100, 255, 1)\",\n enableNoise = true,\n noiseOpacity = 0.03,\n enableMouseGlow = true,\n initialPerformance = \"medium\",\n}: FractalDotGridProps) {\n const containerRef = useRef(null)\n const { isMobile, isTablet } = useResponsive()\n const { performance } = usePerformance(initialPerformance)\n const [mousePos, setMousePos] = useState({ x: 0, y: 0 })\n\n const handleMouseMove = useCallback((event: MouseEvent) => {\n const { clientX, clientY } = event\n const { left, top, width, height } =\n containerRef.current?.getBoundingClientRect() ?? {\n left: 0,\n top: 0,\n width: 0,\n height: 0,\n }\n const x = (clientX - left) / width\n const y = (clientY - top) / height\n setMousePos({ x, y })\n }, [])\n\n useEffect(() => {\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n }, [handleMouseMove])\n\n const responsiveDotSize = useMemo(() => {\n if (isMobile) return dotSize * 0.75\n if (isTablet) return dotSize * 0.9\n return dotSize\n }, [isMobile, isTablet, dotSize])\n\n const responsiveDotSpacing = useMemo(() => {\n if (isMobile) return dotSpacing * 1.5\n if (isTablet) return dotSpacing * 1.25\n return dotSpacing\n }, [isMobile, isTablet, dotSpacing])\n\n return (\n \n \n \n {enableNoise && }\n {enableMouseGlow && (\n \n )}\n \n \n )\n}\n\nexport default FractalDotGrid\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/bg-animated-gradient.json b/apps/www/public/registry/styles/default/bg-animated-gradient.json index ce67140..5915386 100644 --- a/apps/www/public/registry/styles/default/bg-animated-gradient.json +++ b/apps/www/public/registry/styles/default/bg-animated-gradient.json @@ -6,7 +6,7 @@ "files": [ { "name": "bg-animated-gradient.tsx", - "content": "\"use client\"\n\nimport React, { useEffect } from \"react\"\nimport { motion, useAnimation } from \"framer-motion\"\n\ninterface GradientStop {\n color: string\n position: number\n}\n\ninterface GradientType {\n stops: GradientStop[]\n centerX: number\n centerY: number\n}\n\ninterface GradientAnimationProps {\n gradients: GradientType[]\n animationDuration: number\n className?: string\n}\n\nexport const GradientAnimation: React.FC = ({\n gradients,\n animationDuration,\n className = \"\",\n}) => {\n const controls = useAnimation()\n\n useEffect(() => {\n controls.start({\n background: gradients.map(\n (g) =>\n `radial-gradient(circle at ${g.centerX}% ${g.centerY}%, ${g.stops\n .map((s) => `${s.color} ${s.position}%`)\n .join(\", \")})`\n ),\n transition: {\n duration: animationDuration,\n repeat: Infinity,\n repeatType: \"reverse\",\n ease: \"linear\",\n },\n })\n }, [controls, gradients, animationDuration])\n\n return (\n \n )\n}\n\nexport default React.memo(GradientAnimation)\n" + "content": "\"use client\"\n\nimport React, { useEffect } from \"react\"\nimport { motion, useAnimation } from \"motion/react\"\n\ninterface GradientStop {\n color: string\n position: number\n}\n\ninterface GradientType {\n stops: GradientStop[]\n centerX: number\n centerY: number\n}\n\ninterface GradientAnimationProps {\n gradients: GradientType[]\n animationDuration: number\n className?: string\n}\n\nexport const GradientAnimation: React.FC = ({\n gradients,\n animationDuration,\n className = \"\",\n}) => {\n const controls = useAnimation()\n\n useEffect(() => {\n controls.start({\n background: gradients.map(\n (g) =>\n `radial-gradient(circle at ${g.centerX}% ${g.centerY}%, ${g.stops\n .map((s) => `${s.color} ${s.position}%`)\n .join(\", \")})`\n ),\n transition: {\n duration: animationDuration,\n repeat: Infinity,\n repeatType: \"reverse\",\n ease: \"linear\",\n },\n })\n }, [controls, gradients, animationDuration])\n\n return (\n \n )\n}\n\nexport default React.memo(GradientAnimation)\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/canvas-fractal-grid.json b/apps/www/public/registry/styles/default/canvas-fractal-grid.json index effec69..9f99407 100644 --- a/apps/www/public/registry/styles/default/canvas-fractal-grid.json +++ b/apps/www/public/registry/styles/default/canvas-fractal-grid.json @@ -6,7 +6,7 @@ "files": [ { "name": "canvas-fractal-grid.tsx", - "content": "\"use client\"\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport { AnimatePresence, motion, useAnimation } from \"framer-motion\"\n\ninterface GradientStop {\n color: string\n position: number\n}\n\ninterface GradientType {\n stops: GradientStop[]\n centerX: number\n centerY: number\n}\n\ninterface CanvasFractalGridProps {\n /** Size of each dot in pixels */\n dotSize?: number\n /** Spacing between dots in pixels */\n dotSpacing?: number\n /** Opacity of dots (0-1) */\n dotOpacity?: number\n /** Duration of the background gradient animation in seconds */\n gradientAnimationDuration?: number\n /** Stiffness of mouse tracking (higher values make it more responsive) */\n mouseTrackingStiffness?: number\n /** Damping of mouse tracking (higher values make it less bouncy) */\n mouseTrackingDamping?: number\n /** Intensity of the wave effect when hovering */\n waveIntensity?: number\n /** Radius of the wave effect in pixels */\n waveRadius?: number\n /** Array of gradient configurations for the background */\n gradients?: GradientType[]\n /** Color of the dots (supports any valid CSS color) */\n dotColor?: string\n /** Color of the dot glow effect (supports any valid CSS color) */\n glowColor?: string\n /** Enable or disable the noise overlay */\n enableNoise?: boolean\n /** Opacity of the noise overlay (0-1) */\n noiseOpacity?: number\n /** Enable or disable the mouse glow effect */\n enableMouseGlow?: boolean\n /** Set the initial performance level */\n initialPerformance?: \"low\" | \"medium\" | \"high\"\n /** Enable or disable the gradient animation */\n enableGradient?: boolean\n}\n\nconst NoiseSVG = React.memo(() => (\n \n \n \n \n \n \n))\n\nNoiseSVG.displayName = \"NoiseSVG\"\n\nconst NoiseOverlay: React.FC<{ opacity: number }> = ({ opacity }) => (\n \n \n
\n)\n\nconst useResponsive = () => {\n const [windowSize, setWindowSize] = useState({\n width: typeof window !== \"undefined\" ? window.innerWidth : 0,\n height: typeof window !== \"undefined\" ? window.innerHeight : 0,\n })\n\n useEffect(() => {\n const handleResize = () => {\n setWindowSize({\n width: window.innerWidth,\n height: window.innerHeight,\n })\n }\n\n window.addEventListener(\"resize\", handleResize)\n return () => window.removeEventListener(\"resize\", handleResize)\n }, [])\n\n return {\n isMobile: windowSize.width < 768,\n isTablet: windowSize.width >= 768 && windowSize.width < 1024,\n isDesktop: windowSize.width >= 1024,\n }\n}\n\nconst usePerformance = (\n initialPerformance: \"low\" | \"medium\" | \"high\" = \"medium\"\n) => {\n const [performance, setPerformance] = useState(initialPerformance)\n const [fps, setFps] = useState(60)\n\n useEffect(() => {\n let frameCount = 0\n let lastTime = globalThis.performance.now()\n let framerId: number\n\n const measureFps = (time: number) => {\n frameCount++\n if (time - lastTime > 1000) {\n setFps(Math.round((frameCount * 1000) / (time - lastTime)))\n frameCount = 0\n lastTime = time\n }\n framerId = requestAnimationFrame(measureFps)\n }\n\n framerId = requestAnimationFrame(measureFps)\n\n return () => cancelAnimationFrame(framerId)\n }, [])\n\n useEffect(() => {\n if (fps < 30 && performance !== \"low\") {\n setPerformance(\"low\")\n } else if (fps >= 30 && fps < 50 && performance !== \"medium\") {\n setPerformance(\"medium\")\n } else if (fps >= 50 && performance !== \"high\") {\n setPerformance(\"high\")\n }\n }, [fps, performance])\n\n return { performance, fps }\n}\n\nconst Gradient: React.FC<{\n gradients: GradientType[]\n animationDuration: number\n}> = React.memo(({ gradients, animationDuration }) => {\n const controls = useAnimation()\n\n useEffect(() => {\n controls.start({\n background: gradients.map(\n (g) =>\n `radial-gradient(circle at ${g.centerX}% ${g.centerY}%, ${g.stops\n .map((s) => `${s.color} ${s.position}%`)\n .join(\", \")})`\n ),\n transition: {\n duration: animationDuration,\n repeat: Infinity,\n repeatType: \"reverse\",\n ease: \"linear\",\n },\n })\n }, [controls, gradients, animationDuration])\n\n return (\n \n )\n})\n\nGradient.displayName = \"Gradient\"\n\nconst DotCanvas: React.FC<{\n dotSize: number\n dotSpacing: number\n dotOpacity: number\n waveIntensity: number\n waveRadius: number\n dotColor: string\n glowColor: string\n performance: \"low\" | \"medium\" | \"high\"\n mousePos: { x: number; y: number }\n}> = React.memo(\n ({\n dotSize,\n dotSpacing,\n dotOpacity,\n waveIntensity,\n waveRadius,\n dotColor,\n glowColor,\n performance,\n mousePos,\n }) => {\n const canvasRef = useRef(null)\n const animationRef = useRef()\n\n const drawDots = useCallback(\n (ctx: CanvasRenderingContext2D, time: number) => {\n const { width, height } = ctx.canvas\n ctx.clearRect(0, 0, width, height)\n\n const performanceSettings = {\n low: { skip: 3 },\n medium: { skip: 2 },\n high: { skip: 1 },\n }\n\n const skip = performanceSettings[performance].skip\n\n const cols = Math.ceil(width / dotSpacing)\n const rows = Math.ceil(height / dotSpacing)\n\n const centerX = mousePos.x * width\n const centerY = mousePos.y * height\n\n for (let i = 0; i < cols; i += skip) {\n for (let j = 0; j < rows; j += skip) {\n const x = i * dotSpacing\n const y = j * dotSpacing\n\n const distanceX = x - centerX\n const distanceY = y - centerY\n const distance = Math.sqrt(\n distanceX * distanceX + distanceY * distanceY\n )\n\n let dotX = x\n let dotY = y\n\n if (distance < waveRadius) {\n const waveStrength = Math.pow(1 - distance / waveRadius, 2)\n const angle = Math.atan2(distanceY, distanceX)\n const waveOffset =\n Math.sin(distance * 0.05 - time * 0.005) *\n waveIntensity *\n waveStrength\n dotX += Math.cos(angle) * waveOffset\n dotY += Math.sin(angle) * waveOffset\n\n const glowRadius = dotSize * (1 + waveStrength)\n const gradient = ctx.createRadialGradient(\n dotX,\n dotY,\n 0,\n dotX,\n dotY,\n glowRadius\n )\n gradient.addColorStop(\n 0,\n glowColor.replace(\"1)\", `${dotOpacity * (1 + waveStrength)})`)\n )\n gradient.addColorStop(1, glowColor.replace(\"1)\", \"0)\"))\n ctx.fillStyle = gradient\n } else {\n ctx.fillStyle = dotColor.replace(\"1)\", `${dotOpacity})`)\n }\n\n ctx.beginPath()\n ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)\n ctx.fill()\n }\n }\n },\n [\n dotSize,\n dotSpacing,\n dotOpacity,\n waveIntensity,\n waveRadius,\n dotColor,\n glowColor,\n performance,\n mousePos,\n ]\n )\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const ctx = canvas.getContext(\"2d\")\n if (!ctx) return\n\n const resizeCanvas = () => {\n canvas.width = window.innerWidth\n canvas.height = window.innerHeight\n }\n\n resizeCanvas()\n window.addEventListener(\"resize\", resizeCanvas)\n\n let lastTime = 0\n const animate = (time: number) => {\n if (time - lastTime > 16) {\n drawDots(ctx, time)\n lastTime = time\n }\n animationRef.current = requestAnimationFrame(animate)\n }\n\n animationRef.current = requestAnimationFrame(animate)\n\n return () => {\n window.removeEventListener(\"resize\", resizeCanvas)\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current)\n }\n }\n }, [drawDots])\n\n return (\n \n )\n }\n)\n\nDotCanvas.displayName = \"DotCanvas\"\n\nconst MouseGlow: React.FC<{\n glowColor: string\n mousePos: { x: number; y: number }\n}> = React.memo(({ glowColor, mousePos }) => (\n <>\n \n \n \n))\n\nMouseGlow.displayName = \"MouseGlow\"\n\nconst defaultGradients: GradientType[] = [\n {\n stops: [\n { color: \"#FFD6A5\", position: 0 },\n { color: \"#FFADAD\", position: 25 },\n { color: \"#FFC6FF\", position: 50 },\n { color: \"transparent\", position: 75 },\n ],\n centerX: 50,\n centerY: 50,\n },\n {\n stops: [\n { color: \"#A0C4FF\", position: 0 },\n { color: \"#BDB2FF\", position: 25 },\n { color: \"#CAFFBF\", position: 50 },\n { color: \"transparent\", position: 75 },\n ],\n centerX: 60,\n centerY: 40,\n },\n {\n stops: [\n { color: \"#9BF6FF\", position: 0 },\n { color: \"#FDFFB6\", position: 25 },\n { color: \"#FFAFCC\", position: 50 },\n { color: \"transparent\", position: 75 },\n ],\n centerX: 40,\n centerY: 60,\n },\n]\n\nexport function CanvasFractalGrid({\n dotSize = 4,\n dotSpacing = 20,\n dotOpacity = 0.3,\n gradientAnimationDuration = 20,\n waveIntensity = 30,\n waveRadius = 200,\n gradients = defaultGradients,\n dotColor = \"rgba(100, 100, 255, 1)\",\n glowColor = \"rgba(100, 100, 255, 1)\",\n enableNoise = true,\n noiseOpacity = 0.03,\n enableMouseGlow = true,\n initialPerformance = \"medium\",\n enableGradient = false,\n}: CanvasFractalGridProps) {\n const containerRef = useRef(null)\n const { isMobile, isTablet } = useResponsive()\n const { performance } = usePerformance(initialPerformance)\n const [mousePos, setMousePos] = useState({ x: 0, y: 0 })\n\n const handleMouseMove = useCallback((event: MouseEvent) => {\n const { clientX, clientY } = event\n const { left, top, width, height } =\n containerRef.current?.getBoundingClientRect() ?? {\n left: 0,\n top: 0,\n width: 0,\n height: 0,\n }\n const x = (clientX - left) / width\n const y = (clientY - top) / height\n setMousePos({ x, y })\n }, [])\n\n useEffect(() => {\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n }, [handleMouseMove])\n\n const responsiveDotSize = useMemo(() => {\n if (isMobile) return dotSize * 0.75\n if (isTablet) return dotSize * 0.9\n return dotSize\n }, [isMobile, isTablet, dotSize])\n\n const responsiveDotSpacing = useMemo(() => {\n if (isMobile) return dotSpacing * 1.5\n if (isTablet) return dotSpacing * 1.25\n return dotSpacing\n }, [isMobile, isTablet, dotSpacing])\n\n return (\n \n \n {enableGradient && (\n \n )}\n {enableGradient && (\n \n )}\n \n {enableNoise && }\n {enableMouseGlow && (\n \n )}\n \n \n )\n}\n\nexport default React.memo(CanvasFractalGrid)\n\n// export default CanvasFractalGrid\n\n// const Gradient2: React.FC<{\n// gradients: Gradient[]\n// animationDuration: number\n// }> = ({ gradients, animationDuration }) => {\n// const controls = useAnimation()\n\n// useEffect(() => {\n// controls.start({\n// background: gradients.map(\n// (g) =>\n// `radial-gradient(circle at ${g.centerX}% ${g.centerY}%, ${g.stops\n// .map((s) => `${s.color} ${s.position}%`)\n// .join(\", \")})`\n// ),\n// transition: {\n// duration: animationDuration,\n// repeat: Infinity,\n// repeatType: \"reverse\",\n// ease: \"easeInOut\",\n// },\n// })\n// }, [controls, gradients, animationDuration])\n\n// return (\n// \n// )\n// }\n\n// const DotCanvas2: React.FC<{\n// dotSize: number\n// dotSpacing: number\n// dotOpacity: number\n// waveIntensity: number\n// waveRadius: number\n// dotColor: string\n// glowColor: string\n// performance: \"low\" | \"medium\" | \"high\"\n// mouseX: number\n// mouseY: number\n// }> = ({\n// dotSize,\n// dotSpacing,\n// dotOpacity,\n// waveIntensity,\n// waveRadius,\n// dotColor,\n// glowColor,\n// performance,\n// mouseX,\n// mouseY,\n// }) => {\n// const canvasRef = useRef(null)\n// const animationRef = useRef()\n// const mouseRef = useRef({ x: mouseX, y: mouseY })\n\n// useEffect(() => {\n// mouseRef.current = { x: mouseX, y: mouseY }\n// }, [mouseX, mouseY])\n\n// const drawDots = useCallback(\n// (ctx: CanvasRenderingContext2D, time: number) => {\n// const { width, height } = ctx.canvas\n// ctx.clearRect(0, 0, width, height)\n\n// const performanceSettings = {\n// low: { skip: 3 },\n// medium: { skip: 2 },\n// high: { skip: 1 },\n// }\n\n// const skip = performanceSettings[performance].skip\n\n// const cols = Math.ceil(width / dotSpacing)\n// const rows = Math.ceil(height / dotSpacing)\n\n// const centerX = mouseRef.current.x * width\n// const centerY = mouseRef.current.y * height\n\n// for (let i = 0; i < cols; i += skip) {\n// for (let j = 0; j < rows; j += skip) {\n// const x = i * dotSpacing\n// const y = j * dotSpacing\n\n// const distanceX = x - centerX\n// const distanceY = y - centerY\n// const distance = Math.sqrt(\n// distanceX * distanceX + distanceY * distanceY\n// )\n\n// let dotX = x\n// let dotY = y\n\n// if (distance < waveRadius) {\n// const waveStrength = Math.pow(1 - distance / waveRadius, 2)\n// const angle = Math.atan2(distanceY, distanceX)\n// const waveOffset =\n// Math.sin(distance * 0.05 - time * 0.005) *\n// waveIntensity *\n// waveStrength\n// dotX += Math.cos(angle) * waveOffset\n// dotY += Math.sin(angle) * waveOffset\n\n// const glowRadius = dotSize * (1 + waveStrength)\n// const gradient = ctx.createRadialGradient(\n// dotX,\n// dotY,\n// 0,\n// dotX,\n// dotY,\n// glowRadius\n// )\n// gradient.addColorStop(\n// 0,\n// glowColor.replace(\"1)\", `${dotOpacity * (1 + waveStrength)})`)\n// )\n// gradient.addColorStop(1, glowColor.replace(\"1)\", \"0)\"))\n// ctx.fillStyle = gradient\n// } else {\n// ctx.fillStyle = dotColor.replace(\"1)\", `${dotOpacity})`)\n// }\n\n// ctx.beginPath()\n// ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)\n// ctx.fill()\n// }\n// }\n// },\n// [\n// dotSize,\n// dotSpacing,\n// dotOpacity,\n// waveIntensity,\n// waveRadius,\n// dotColor,\n// glowColor,\n// performance,\n// ]\n// )\n\n// useEffect(() => {\n// const canvas = canvasRef.current\n// if (!canvas) return\n\n// const ctx = canvas.getContext(\"2d\")\n// if (!ctx) return\n\n// const resizeCanvas = () => {\n// canvas.width = window.innerWidth\n// canvas.height = window.innerHeight\n// }\n\n// resizeCanvas()\n// window.addEventListener(\"resize\", resizeCanvas)\n\n// let lastTime = 0\n// const animate = (time: number) => {\n// if (time - lastTime > 16) {\n// drawDots(ctx, time)\n// lastTime = time\n// }\n// animationRef.current = requestAnimationFrame(animate)\n// }\n\n// animationRef.current = requestAnimationFrame(animate)\n\n// return () => {\n// window.removeEventListener(\"resize\", resizeCanvas)\n// if (animationRef.current) {\n// cancelAnimationFrame(animationRef.current)\n// }\n// }\n// }, [drawDots])\n\n// return (\n// \n// )\n// }\n\n// const MouseGlow2: React.FC<{\n// glowColor: string\n// mouseX: any\n// mouseY: any\n// }> = ({ glowColor, mouseX, mouseY }) => (\n// <>\n// \n// \n// \n// )\n// \"use client\"\n\n// import React, { useCallback, useEffect, useMemo, useRef } from \"react\"\n// import { AnimatePresence, motion, useAnimation, useSpring } from \"framer-motion\"\n\n// const NoiseSVG = React.memo(() => (\n// \n// \n// \n// \n// \n// \n// ))\n\n// NoiseSVG.displayName = \"NoiseSVG\"\n\n// interface GradientStop {\n// color: string\n// position: number\n// }\n\n// interface Gradient {\n// stops: GradientStop[]\n// centerX: number\n// centerY: number\n// }\n\n// interface CanvasFractalGridProps {\n// /** Size of each dot in pixels */\n// dotSize?: number\n// /** Spacing between dots in pixels */\n// dotSpacing?: number\n// /** Opacity of dots (0-1) */\n// dotOpacity?: number\n// /** Duration of the background gradient animation in seconds */\n// gradientAnimationDuration?: number\n// /** Stiffness of mouse tracking (higher values make it more responsive) */\n// mouseTrackingStiffness?: number\n// /** Damping of mouse tracking (higher values make it less bouncy) */\n// mouseTrackingDamping?: number\n// /** Intensity of the wave effect when hovering */\n// waveIntensity?: number\n// /** Radius of the wave effect in pixels */\n// waveRadius?: number\n// /** Array of gradient configurations for the background */\n// gradients?: Gradient[]\n// /** Color of the dots (supports any valid CSS color) */\n// dotColor?: string\n// /** Color of the dot glow effect (supports any valid CSS color) */\n// glowColor?: string\n// /** Enable or disable the noise overlay */\n// enableNoise?: boolean\n// /** Opacity of the noise overlay (0-1) */\n// noiseOpacity?: number\n// /** Enable or disable the mouse glow effect */\n// enableMouseGlow?: boolean\n// }\n\n// const defaultGradients: Gradient[] = [\n// {\n// stops: [\n// { color: \"#FFD6A5\", position: 0 },\n// { color: \"#FFADAD\", position: 25 },\n// { color: \"#FFC6FF\", position: 50 },\n// { color: \"transparent\", position: 75 },\n// ],\n// centerX: 50,\n// centerY: 50,\n// },\n// {\n// stops: [\n// { color: \"#A0C4FF\", position: 0 },\n// { color: \"#BDB2FF\", position: 25 },\n// { color: \"#CAFFBF\", position: 50 },\n// { color: \"transparent\", position: 75 },\n// ],\n// centerX: 60,\n// centerY: 40,\n// },\n// {\n// stops: [\n// { color: \"#9BF6FF\", position: 0 },\n// { color: \"#FDFFB6\", position: 25 },\n// { color: \"#FFAFCC\", position: 50 },\n// { color: \"transparent\", position: 75 },\n// ],\n// centerX: 40,\n// centerY: 60,\n// },\n// ]\n\n// export function CanvasFractalGrid({\n// dotSize = 4,\n// dotSpacing = 20,\n// dotOpacity = 0.3,\n// gradientAnimationDuration = 20,\n// mouseTrackingStiffness = 500,\n// mouseTrackingDamping = 150,\n// waveIntensity = 30,\n// waveRadius = 200,\n// gradients = defaultGradients,\n// dotColor = \"rgba(100, 100, 255, 1)\",\n// glowColor = \"rgba(100, 100, 255, 1)\",\n// enableNoise = true,\n// noiseOpacity = 0.03,\n// enableMouseGlow = true,\n// }: CanvasFractalGridProps) {\n// const containerRef = useRef(null)\n// const canvasRef = useRef(null)\n// const animationRef = useRef()\n// const mouseRef = useRef({ x: 0, y: 0 })\n\n// const mouseX = useSpring(0, {\n// stiffness: mouseTrackingStiffness,\n// damping: mouseTrackingDamping,\n// })\n// const mouseY = useSpring(0, {\n// stiffness: mouseTrackingStiffness,\n// damping: mouseTrackingDamping,\n// })\n\n// const handleMouseMove = useCallback(\n// (event: MouseEvent) => {\n// const { clientX, clientY } = event\n// const { left, top, width, height } =\n// containerRef.current?.getBoundingClientRect() ?? {\n// left: 0,\n// top: 0,\n// width: 0,\n// height: 0,\n// }\n// const x = (clientX - left) / width\n// const y = (clientY - top) / height\n// mouseX.set(x)\n// mouseY.set(y)\n// mouseRef.current = { x, y }\n// },\n// [mouseX, mouseY]\n// )\n\n// useEffect(() => {\n// window.addEventListener(\"mousemove\", handleMouseMove)\n// return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n// }, [handleMouseMove])\n\n// const controls = useAnimation()\n\n// useEffect(() => {\n// controls.start({\n// background: gradients.map(\n// (g) =>\n// `radial-gradient(circle at ${g.centerX}% ${g.centerY}%, ${g.stops\n// .map((s) => `${s.color} ${s.position}%`)\n// .join(\", \")})`\n// ),\n// transition: {\n// duration: gradientAnimationDuration,\n// repeat: Infinity,\n// repeatType: \"reverse\",\n// ease: \"easeInOut\",\n// },\n// })\n// }, [controls, gradients, gradientAnimationDuration])\n\n// const drawDots = useCallback(\n// (ctx: CanvasRenderingContext2D, time: number) => {\n// const { width, height } = ctx.canvas\n// ctx.clearRect(0, 0, width, height)\n\n// const cols = Math.ceil(width / dotSpacing)\n// const rows = Math.ceil(height / dotSpacing)\n\n// const centerX = mouseRef.current.x * width\n// const centerY = mouseRef.current.y * height\n\n// for (let i = 0; i < cols; i++) {\n// for (let j = 0; j < rows; j++) {\n// const x = i * dotSpacing\n// const y = j * dotSpacing\n\n// const distanceX = x - centerX\n// const distanceY = y - centerY\n// const distance = Math.sqrt(\n// distanceX * distanceX + distanceY * distanceY\n// )\n\n// let dotX = x\n// let dotY = y\n\n// if (distance < waveRadius) {\n// const waveStrength = Math.pow(1 - distance / waveRadius, 2)\n// const angle = Math.atan2(distanceY, distanceX)\n// const waveOffset =\n// Math.sin(distance * 0.05 - time * 0.005) *\n// waveIntensity *\n// waveStrength\n// dotX += Math.cos(angle) * waveOffset\n// dotY += Math.sin(angle) * waveOffset\n\n// const glowRadius = dotSize * (1 + waveStrength)\n// const gradient = ctx.createRadialGradient(\n// dotX,\n// dotY,\n// 0,\n// dotX,\n// dotY,\n// glowRadius\n// )\n// gradient.addColorStop(\n// 0,\n// glowColor.replace(\"1)\", `${dotOpacity * (1 + waveStrength)})`)\n// )\n// gradient.addColorStop(1, glowColor.replace(\"1)\", \"0)\"))\n// ctx.fillStyle = gradient\n// } else {\n// ctx.fillStyle = dotColor.replace(\"1)\", `${dotOpacity})`)\n// }\n\n// ctx.beginPath()\n// ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)\n// ctx.fill()\n// }\n// }\n// },\n// [\n// dotSize,\n// dotSpacing,\n// dotOpacity,\n// waveIntensity,\n// waveRadius,\n// dotColor,\n// glowColor,\n// ]\n// )\n\n// useEffect(() => {\n// const canvas = canvasRef.current\n// if (!canvas) return\n\n// const ctx = canvas.getContext(\"2d\")\n// if (!ctx) return\n\n// const resizeCanvas = () => {\n// canvas.width = window.innerWidth\n// canvas.height = window.innerHeight\n// }\n\n// resizeCanvas()\n// window.addEventListener(\"resize\", resizeCanvas)\n\n// let lastTime = 0\n// const animate = (time: number) => {\n// if (time - lastTime > 16) {\n// drawDots(ctx, time)\n// lastTime = time\n// }\n// animationRef.current = requestAnimationFrame(animate)\n// }\n\n// animationRef.current = requestAnimationFrame(animate)\n\n// return () => {\n// window.removeEventListener(\"resize\", resizeCanvas)\n// if (animationRef.current) {\n// cancelAnimationFrame(animationRef.current)\n// }\n// }\n// }, [drawDots])\n\n// const gradientStyle = useMemo(\n// () =>\n// ({\n// \"--mouse-x\": mouseX,\n// \"--mouse-y\": mouseY,\n// } as React.CSSProperties),\n// [mouseX, mouseY]\n// )\n\n// return (\n// \n// \n// \n// \n// \n// {enableNoise && (\n// \n// \n//
\n// )}\n// {enableMouseGlow && (\n// <>\n// \n// \n// \n// )}\n// \n// \n// )\n// }\n\n// export default CanvasFractalGrid\n\n// \"use client\"\n\n// import React, { useCallback, useEffect, useMemo, useRef } from \"react\"\n// import { AnimatePresence, motion, useAnimation, useSpring } from \"framer-motion\"\n\n// const NoiseSVG = React.memo(() => (\n// \n// \n// \n// \n// \n// \n// ))\n\n// NoiseSVG.displayName = \"NoiseSVG\"\n\n// interface LandingAnimationProps {\n// dotSize?: number\n// dotSpacing?: number\n// dotOpacity?: number\n// animationDuration?: number\n// mouseTrackingStiffness?: number\n// mouseTrackingDamping?: number\n// waveIntensity?: number\n// waveRadius?: number\n// }\n\n// export function CanvasFractalGrid({\n// dotSize = 4,\n// dotSpacing = 20,\n// dotOpacity = 0.3,\n// animationDuration = 5,\n// mouseTrackingStiffness = 500,\n// mouseTrackingDamping = 150,\n// waveIntensity = 30,\n// waveRadius = 200,\n// }: LandingAnimationProps) {\n// const containerRef = useRef(null)\n// const canvasRef = useRef(null)\n// const animationRef = useRef()\n// const mouseRef = useRef({ x: 0, y: 0 })\n\n// const mouseX = useSpring(0, {\n// stiffness: mouseTrackingStiffness,\n// damping: mouseTrackingDamping,\n// })\n// const mouseY = useSpring(0, {\n// stiffness: mouseTrackingStiffness,\n// damping: mouseTrackingDamping,\n// })\n\n// const handleMouseMove = useCallback(\n// (event: MouseEvent) => {\n// const { clientX, clientY } = event\n// const { left, top, width, height } =\n// containerRef.current?.getBoundingClientRect() ?? {\n// left: 0,\n// top: 0,\n// width: 0,\n// height: 0,\n// }\n// const x = (clientX - left) / width\n// const y = (clientY - top) / height\n// mouseX.set(x)\n// mouseY.set(y)\n// mouseRef.current = { x, y }\n// },\n// [mouseX, mouseY]\n// )\n\n// useEffect(() => {\n// window.addEventListener(\"mousemove\", handleMouseMove)\n// return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n// }, [handleMouseMove])\n\n// const controls = useAnimation()\n\n// useEffect(() => {\n// controls.start({\n// background: [\n// \"radial-gradient(circle at 50% 50%, #FFD6A5 0%, #FFADAD 25%, #FFC6FF 50%, transparent 75%)\",\n// \"radial-gradient(circle at 60% 40%, #A0C4FF 0%, #BDB2FF 25%, #CAFFBF 50%, transparent 75%)\",\n// \"radial-gradient(circle at 40% 60%, #9BF6FF 0%, #FDFFB6 25%, #FFAFCC 50%, transparent 75%)\",\n// ],\n// transition: {\n// duration: 20,\n// repeat: Infinity,\n// repeatType: \"reverse\",\n// ease: \"easeInOut\",\n// },\n// })\n// }, [controls])\n\n// const drawDots = useCallback(\n// (ctx: CanvasRenderingContext2D, time: number) => {\n// const { width, height } = ctx.canvas\n// ctx.clearRect(0, 0, width, height)\n\n// const cols = Math.ceil(width / dotSpacing)\n// const rows = Math.ceil(height / dotSpacing)\n\n// const centerX = mouseRef.current.x * width\n// const centerY = mouseRef.current.y * height\n\n// for (let i = 0; i < cols; i++) {\n// for (let j = 0; j < rows; j++) {\n// const x = i * dotSpacing\n// const y = j * dotSpacing\n\n// const distanceX = x - centerX\n// const distanceY = y - centerY\n// const distance = Math.sqrt(\n// distanceX * distanceX + distanceY * distanceY\n// )\n\n// let dotX = x\n// let dotY = y\n\n// if (distance < waveRadius) {\n// const waveStrength = Math.pow(1 - distance / waveRadius, 2)\n// const angle = Math.atan2(distanceY, distanceX)\n// const waveOffset =\n// Math.sin(distance * 0.05 - time * 0.005) *\n// waveIntensity *\n// waveStrength\n// dotX += Math.cos(angle) * waveOffset\n// dotY += Math.sin(angle) * waveOffset\n\n// const glowRadius = dotSize * (1 + waveStrength)\n// const gradient = ctx.createRadialGradient(\n// dotX,\n// dotY,\n// 0,\n// dotX,\n// dotY,\n// glowRadius\n// )\n// gradient.addColorStop(\n// 0,\n// `rgba(100, 100, 255, ${dotOpacity * (1 + waveStrength)})`\n// )\n// gradient.addColorStop(1, \"rgba(100, 100, 255, 0)\")\n// ctx.fillStyle = gradient\n// } else {\n// ctx.fillStyle = `rgba(100, 100, 255, ${dotOpacity})`\n// }\n\n// ctx.beginPath()\n// ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)\n// ctx.fill()\n// }\n// }\n// },\n// [dotSize, dotSpacing, dotOpacity, waveIntensity, waveRadius]\n// )\n\n// useEffect(() => {\n// const canvas = canvasRef.current\n// if (!canvas) return\n\n// const ctx = canvas.getContext(\"2d\")\n// if (!ctx) return\n\n// const resizeCanvas = () => {\n// canvas.width = window.innerWidth\n// canvas.height = window.innerHeight\n// }\n\n// resizeCanvas()\n// window.addEventListener(\"resize\", resizeCanvas)\n\n// let lastTime = 0\n// const animate = (time: number) => {\n// if (time - lastTime > 16) {\n// drawDots(ctx, time)\n// lastTime = time\n// }\n// animationRef.current = requestAnimationFrame(animate)\n// }\n\n// animationRef.current = requestAnimationFrame(animate)\n\n// let animationId: number\n\n// animationId = requestAnimationFrame(animate)\n\n// return () => {\n// window.removeEventListener(\"resize\", resizeCanvas)\n// cancelAnimationFrame(animationId)\n// }\n// }, [drawDots])\n\n// const gradientStyle = useMemo(\n// () =>\n// ({\n// \"--mouse-x\": mouseX,\n// \"--mouse-y\": mouseY,\n// } as React.CSSProperties),\n// [mouseX, mouseY]\n// )\n\n// return (\n// \n// \n// \n// \n// \n// \n//
\n// \n//
\n// \n// \n// \n//
\n// )\n// }\n\n// export default CanvasFractalGrid\n" + "content": "\"use client\"\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport { AnimatePresence, motion, useAnimation } from \"motion/react\"\n\ninterface GradientStop {\n color: string\n position: number\n}\n\ninterface GradientType {\n stops: GradientStop[]\n centerX: number\n centerY: number\n}\n\ninterface CanvasFractalGridProps {\n /** Size of each dot in pixels */\n dotSize?: number\n /** Spacing between dots in pixels */\n dotSpacing?: number\n /** Opacity of dots (0-1) */\n dotOpacity?: number\n /** Duration of the background gradient animation in seconds */\n gradientAnimationDuration?: number\n /** Stiffness of mouse tracking (higher values make it more responsive) */\n mouseTrackingStiffness?: number\n /** Damping of mouse tracking (higher values make it less bouncy) */\n mouseTrackingDamping?: number\n /** Intensity of the wave effect when hovering */\n waveIntensity?: number\n /** Radius of the wave effect in pixels */\n waveRadius?: number\n /** Array of gradient configurations for the background */\n gradients?: GradientType[]\n /** Color of the dots (supports any valid CSS color) */\n dotColor?: string\n /** Color of the dot glow effect (supports any valid CSS color) */\n glowColor?: string\n /** Enable or disable the noise overlay */\n enableNoise?: boolean\n /** Opacity of the noise overlay (0-1) */\n noiseOpacity?: number\n /** Enable or disable the mouse glow effect */\n enableMouseGlow?: boolean\n /** Set the initial performance level */\n initialPerformance?: \"low\" | \"medium\" | \"high\"\n /** Enable or disable the gradient animation */\n enableGradient?: boolean\n}\n\nconst NoiseSVG = React.memo(() => (\n \n \n \n \n \n \n))\n\nNoiseSVG.displayName = \"NoiseSVG\"\n\nconst NoiseOverlay: React.FC<{ opacity: number }> = ({ opacity }) => (\n \n \n
\n)\n\nconst useResponsive = () => {\n const [windowSize, setWindowSize] = useState({\n width: typeof window !== \"undefined\" ? window.innerWidth : 0,\n height: typeof window !== \"undefined\" ? window.innerHeight : 0,\n })\n\n useEffect(() => {\n const handleResize = () => {\n setWindowSize({\n width: window.innerWidth,\n height: window.innerHeight,\n })\n }\n\n window.addEventListener(\"resize\", handleResize)\n return () => window.removeEventListener(\"resize\", handleResize)\n }, [])\n\n return {\n isMobile: windowSize.width < 768,\n isTablet: windowSize.width >= 768 && windowSize.width < 1024,\n isDesktop: windowSize.width >= 1024,\n }\n}\n\nconst usePerformance = (\n initialPerformance: \"low\" | \"medium\" | \"high\" = \"medium\"\n) => {\n const [performance, setPerformance] = useState(initialPerformance)\n const [fps, setFps] = useState(60)\n\n useEffect(() => {\n let frameCount = 0\n let lastTime = globalThis.performance.now()\n let framerId: number\n\n const measureFps = (time: number) => {\n frameCount++\n if (time - lastTime > 1000) {\n setFps(Math.round((frameCount * 1000) / (time - lastTime)))\n frameCount = 0\n lastTime = time\n }\n framerId = requestAnimationFrame(measureFps)\n }\n\n framerId = requestAnimationFrame(measureFps)\n\n return () => cancelAnimationFrame(framerId)\n }, [])\n\n useEffect(() => {\n if (fps < 30 && performance !== \"low\") {\n setPerformance(\"low\")\n } else if (fps >= 30 && fps < 50 && performance !== \"medium\") {\n setPerformance(\"medium\")\n } else if (fps >= 50 && performance !== \"high\") {\n setPerformance(\"high\")\n }\n }, [fps, performance])\n\n return { performance, fps }\n}\n\nconst Gradient: React.FC<{\n gradients: GradientType[]\n animationDuration: number\n}> = React.memo(({ gradients, animationDuration }) => {\n const controls = useAnimation()\n\n useEffect(() => {\n controls.start({\n background: gradients.map(\n (g) =>\n `radial-gradient(circle at ${g.centerX}% ${g.centerY}%, ${g.stops\n .map((s) => `${s.color} ${s.position}%`)\n .join(\", \")})`\n ),\n transition: {\n duration: animationDuration,\n repeat: Infinity,\n repeatType: \"reverse\",\n ease: \"linear\",\n },\n })\n }, [controls, gradients, animationDuration])\n\n return (\n \n )\n})\n\nGradient.displayName = \"Gradient\"\n\nconst DotCanvas: React.FC<{\n dotSize: number\n dotSpacing: number\n dotOpacity: number\n waveIntensity: number\n waveRadius: number\n dotColor: string\n glowColor: string\n performance: \"low\" | \"medium\" | \"high\"\n mousePos: { x: number; y: number }\n}> = React.memo(\n ({\n dotSize,\n dotSpacing,\n dotOpacity,\n waveIntensity,\n waveRadius,\n dotColor,\n glowColor,\n performance,\n mousePos,\n }) => {\n const canvasRef = useRef(null)\n const animationRef = useRef()\n\n const drawDots = useCallback(\n (ctx: CanvasRenderingContext2D, time: number) => {\n const { width, height } = ctx.canvas\n ctx.clearRect(0, 0, width, height)\n\n const performanceSettings = {\n low: { skip: 3 },\n medium: { skip: 2 },\n high: { skip: 1 },\n }\n\n const skip = performanceSettings[performance].skip\n\n const cols = Math.ceil(width / dotSpacing)\n const rows = Math.ceil(height / dotSpacing)\n\n const centerX = mousePos.x * width\n const centerY = mousePos.y * height\n\n for (let i = 0; i < cols; i += skip) {\n for (let j = 0; j < rows; j += skip) {\n const x = i * dotSpacing\n const y = j * dotSpacing\n\n const distanceX = x - centerX\n const distanceY = y - centerY\n const distance = Math.sqrt(\n distanceX * distanceX + distanceY * distanceY\n )\n\n let dotX = x\n let dotY = y\n\n if (distance < waveRadius) {\n const waveStrength = Math.pow(1 - distance / waveRadius, 2)\n const angle = Math.atan2(distanceY, distanceX)\n const waveOffset =\n Math.sin(distance * 0.05 - time * 0.005) *\n waveIntensity *\n waveStrength\n dotX += Math.cos(angle) * waveOffset\n dotY += Math.sin(angle) * waveOffset\n\n const glowRadius = dotSize * (1 + waveStrength)\n const gradient = ctx.createRadialGradient(\n dotX,\n dotY,\n 0,\n dotX,\n dotY,\n glowRadius\n )\n gradient.addColorStop(\n 0,\n glowColor.replace(\"1)\", `${dotOpacity * (1 + waveStrength)})`)\n )\n gradient.addColorStop(1, glowColor.replace(\"1)\", \"0)\"))\n ctx.fillStyle = gradient\n } else {\n ctx.fillStyle = dotColor.replace(\"1)\", `${dotOpacity})`)\n }\n\n ctx.beginPath()\n ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)\n ctx.fill()\n }\n }\n },\n [\n dotSize,\n dotSpacing,\n dotOpacity,\n waveIntensity,\n waveRadius,\n dotColor,\n glowColor,\n performance,\n mousePos,\n ]\n )\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const ctx = canvas.getContext(\"2d\")\n if (!ctx) return\n\n const resizeCanvas = () => {\n canvas.width = window.innerWidth\n canvas.height = window.innerHeight\n }\n\n resizeCanvas()\n window.addEventListener(\"resize\", resizeCanvas)\n\n let lastTime = 0\n const animate = (time: number) => {\n if (time - lastTime > 16) {\n drawDots(ctx, time)\n lastTime = time\n }\n animationRef.current = requestAnimationFrame(animate)\n }\n\n animationRef.current = requestAnimationFrame(animate)\n\n return () => {\n window.removeEventListener(\"resize\", resizeCanvas)\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current)\n }\n }\n }, [drawDots])\n\n return (\n \n )\n }\n)\n\nDotCanvas.displayName = \"DotCanvas\"\n\nconst MouseGlow: React.FC<{\n glowColor: string\n mousePos: { x: number; y: number }\n}> = React.memo(({ glowColor, mousePos }) => (\n <>\n \n \n \n))\n\nMouseGlow.displayName = \"MouseGlow\"\n\nconst defaultGradients: GradientType[] = [\n {\n stops: [\n { color: \"#FFD6A5\", position: 0 },\n { color: \"#FFADAD\", position: 25 },\n { color: \"#FFC6FF\", position: 50 },\n { color: \"transparent\", position: 75 },\n ],\n centerX: 50,\n centerY: 50,\n },\n {\n stops: [\n { color: \"#A0C4FF\", position: 0 },\n { color: \"#BDB2FF\", position: 25 },\n { color: \"#CAFFBF\", position: 50 },\n { color: \"transparent\", position: 75 },\n ],\n centerX: 60,\n centerY: 40,\n },\n {\n stops: [\n { color: \"#9BF6FF\", position: 0 },\n { color: \"#FDFFB6\", position: 25 },\n { color: \"#FFAFCC\", position: 50 },\n { color: \"transparent\", position: 75 },\n ],\n centerX: 40,\n centerY: 60,\n },\n]\n\nexport function CanvasFractalGrid({\n dotSize = 4,\n dotSpacing = 20,\n dotOpacity = 0.3,\n gradientAnimationDuration = 20,\n waveIntensity = 30,\n waveRadius = 200,\n gradients = defaultGradients,\n dotColor = \"rgba(100, 100, 255, 1)\",\n glowColor = \"rgba(100, 100, 255, 1)\",\n enableNoise = true,\n noiseOpacity = 0.03,\n enableMouseGlow = true,\n initialPerformance = \"medium\",\n enableGradient = false,\n}: CanvasFractalGridProps) {\n const containerRef = useRef(null)\n const { isMobile, isTablet } = useResponsive()\n const { performance } = usePerformance(initialPerformance)\n const [mousePos, setMousePos] = useState({ x: 0, y: 0 })\n\n const handleMouseMove = useCallback((event: MouseEvent) => {\n const { clientX, clientY } = event\n const { left, top, width, height } =\n containerRef.current?.getBoundingClientRect() ?? {\n left: 0,\n top: 0,\n width: 0,\n height: 0,\n }\n const x = (clientX - left) / width\n const y = (clientY - top) / height\n setMousePos({ x, y })\n }, [])\n\n useEffect(() => {\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n }, [handleMouseMove])\n\n const responsiveDotSize = useMemo(() => {\n if (isMobile) return dotSize * 0.75\n if (isTablet) return dotSize * 0.9\n return dotSize\n }, [isMobile, isTablet, dotSize])\n\n const responsiveDotSpacing = useMemo(() => {\n if (isMobile) return dotSpacing * 1.5\n if (isTablet) return dotSpacing * 1.25\n return dotSpacing\n }, [isMobile, isTablet, dotSpacing])\n\n return (\n \n \n {enableGradient && (\n \n )}\n {enableGradient && (\n \n )}\n \n {enableNoise && }\n {enableMouseGlow && (\n \n )}\n \n \n )\n}\n\nexport default React.memo(CanvasFractalGrid)\n\n// export default CanvasFractalGrid\n\n// const Gradient2: React.FC<{\n// gradients: Gradient[]\n// animationDuration: number\n// }> = ({ gradients, animationDuration }) => {\n// const controls = useAnimation()\n\n// useEffect(() => {\n// controls.start({\n// background: gradients.map(\n// (g) =>\n// `radial-gradient(circle at ${g.centerX}% ${g.centerY}%, ${g.stops\n// .map((s) => `${s.color} ${s.position}%`)\n// .join(\", \")})`\n// ),\n// transition: {\n// duration: animationDuration,\n// repeat: Infinity,\n// repeatType: \"reverse\",\n// ease: \"easeInOut\",\n// },\n// })\n// }, [controls, gradients, animationDuration])\n\n// return (\n// \n// )\n// }\n\n// const DotCanvas2: React.FC<{\n// dotSize: number\n// dotSpacing: number\n// dotOpacity: number\n// waveIntensity: number\n// waveRadius: number\n// dotColor: string\n// glowColor: string\n// performance: \"low\" | \"medium\" | \"high\"\n// mouseX: number\n// mouseY: number\n// }> = ({\n// dotSize,\n// dotSpacing,\n// dotOpacity,\n// waveIntensity,\n// waveRadius,\n// dotColor,\n// glowColor,\n// performance,\n// mouseX,\n// mouseY,\n// }) => {\n// const canvasRef = useRef(null)\n// const animationRef = useRef()\n// const mouseRef = useRef({ x: mouseX, y: mouseY })\n\n// useEffect(() => {\n// mouseRef.current = { x: mouseX, y: mouseY }\n// }, [mouseX, mouseY])\n\n// const drawDots = useCallback(\n// (ctx: CanvasRenderingContext2D, time: number) => {\n// const { width, height } = ctx.canvas\n// ctx.clearRect(0, 0, width, height)\n\n// const performanceSettings = {\n// low: { skip: 3 },\n// medium: { skip: 2 },\n// high: { skip: 1 },\n// }\n\n// const skip = performanceSettings[performance].skip\n\n// const cols = Math.ceil(width / dotSpacing)\n// const rows = Math.ceil(height / dotSpacing)\n\n// const centerX = mouseRef.current.x * width\n// const centerY = mouseRef.current.y * height\n\n// for (let i = 0; i < cols; i += skip) {\n// for (let j = 0; j < rows; j += skip) {\n// const x = i * dotSpacing\n// const y = j * dotSpacing\n\n// const distanceX = x - centerX\n// const distanceY = y - centerY\n// const distance = Math.sqrt(\n// distanceX * distanceX + distanceY * distanceY\n// )\n\n// let dotX = x\n// let dotY = y\n\n// if (distance < waveRadius) {\n// const waveStrength = Math.pow(1 - distance / waveRadius, 2)\n// const angle = Math.atan2(distanceY, distanceX)\n// const waveOffset =\n// Math.sin(distance * 0.05 - time * 0.005) *\n// waveIntensity *\n// waveStrength\n// dotX += Math.cos(angle) * waveOffset\n// dotY += Math.sin(angle) * waveOffset\n\n// const glowRadius = dotSize * (1 + waveStrength)\n// const gradient = ctx.createRadialGradient(\n// dotX,\n// dotY,\n// 0,\n// dotX,\n// dotY,\n// glowRadius\n// )\n// gradient.addColorStop(\n// 0,\n// glowColor.replace(\"1)\", `${dotOpacity * (1 + waveStrength)})`)\n// )\n// gradient.addColorStop(1, glowColor.replace(\"1)\", \"0)\"))\n// ctx.fillStyle = gradient\n// } else {\n// ctx.fillStyle = dotColor.replace(\"1)\", `${dotOpacity})`)\n// }\n\n// ctx.beginPath()\n// ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)\n// ctx.fill()\n// }\n// }\n// },\n// [\n// dotSize,\n// dotSpacing,\n// dotOpacity,\n// waveIntensity,\n// waveRadius,\n// dotColor,\n// glowColor,\n// performance,\n// ]\n// )\n\n// useEffect(() => {\n// const canvas = canvasRef.current\n// if (!canvas) return\n\n// const ctx = canvas.getContext(\"2d\")\n// if (!ctx) return\n\n// const resizeCanvas = () => {\n// canvas.width = window.innerWidth\n// canvas.height = window.innerHeight\n// }\n\n// resizeCanvas()\n// window.addEventListener(\"resize\", resizeCanvas)\n\n// let lastTime = 0\n// const animate = (time: number) => {\n// if (time - lastTime > 16) {\n// drawDots(ctx, time)\n// lastTime = time\n// }\n// animationRef.current = requestAnimationFrame(animate)\n// }\n\n// animationRef.current = requestAnimationFrame(animate)\n\n// return () => {\n// window.removeEventListener(\"resize\", resizeCanvas)\n// if (animationRef.current) {\n// cancelAnimationFrame(animationRef.current)\n// }\n// }\n// }, [drawDots])\n\n// return (\n// \n// )\n// }\n\n// const MouseGlow2: React.FC<{\n// glowColor: string\n// mouseX: any\n// mouseY: any\n// }> = ({ glowColor, mouseX, mouseY }) => (\n// <>\n// \n// \n// \n// )\n// \"use client\"\n\n// import React, { useCallback, useEffect, useMemo, useRef } from \"react\"\n// import { AnimatePresence, motion, useAnimation, useSpring } from \"motion/react\"\n\n// const NoiseSVG = React.memo(() => (\n// \n// \n// \n// \n// \n// \n// ))\n\n// NoiseSVG.displayName = \"NoiseSVG\"\n\n// interface GradientStop {\n// color: string\n// position: number\n// }\n\n// interface Gradient {\n// stops: GradientStop[]\n// centerX: number\n// centerY: number\n// }\n\n// interface CanvasFractalGridProps {\n// /** Size of each dot in pixels */\n// dotSize?: number\n// /** Spacing between dots in pixels */\n// dotSpacing?: number\n// /** Opacity of dots (0-1) */\n// dotOpacity?: number\n// /** Duration of the background gradient animation in seconds */\n// gradientAnimationDuration?: number\n// /** Stiffness of mouse tracking (higher values make it more responsive) */\n// mouseTrackingStiffness?: number\n// /** Damping of mouse tracking (higher values make it less bouncy) */\n// mouseTrackingDamping?: number\n// /** Intensity of the wave effect when hovering */\n// waveIntensity?: number\n// /** Radius of the wave effect in pixels */\n// waveRadius?: number\n// /** Array of gradient configurations for the background */\n// gradients?: Gradient[]\n// /** Color of the dots (supports any valid CSS color) */\n// dotColor?: string\n// /** Color of the dot glow effect (supports any valid CSS color) */\n// glowColor?: string\n// /** Enable or disable the noise overlay */\n// enableNoise?: boolean\n// /** Opacity of the noise overlay (0-1) */\n// noiseOpacity?: number\n// /** Enable or disable the mouse glow effect */\n// enableMouseGlow?: boolean\n// }\n\n// const defaultGradients: Gradient[] = [\n// {\n// stops: [\n// { color: \"#FFD6A5\", position: 0 },\n// { color: \"#FFADAD\", position: 25 },\n// { color: \"#FFC6FF\", position: 50 },\n// { color: \"transparent\", position: 75 },\n// ],\n// centerX: 50,\n// centerY: 50,\n// },\n// {\n// stops: [\n// { color: \"#A0C4FF\", position: 0 },\n// { color: \"#BDB2FF\", position: 25 },\n// { color: \"#CAFFBF\", position: 50 },\n// { color: \"transparent\", position: 75 },\n// ],\n// centerX: 60,\n// centerY: 40,\n// },\n// {\n// stops: [\n// { color: \"#9BF6FF\", position: 0 },\n// { color: \"#FDFFB6\", position: 25 },\n// { color: \"#FFAFCC\", position: 50 },\n// { color: \"transparent\", position: 75 },\n// ],\n// centerX: 40,\n// centerY: 60,\n// },\n// ]\n\n// export function CanvasFractalGrid({\n// dotSize = 4,\n// dotSpacing = 20,\n// dotOpacity = 0.3,\n// gradientAnimationDuration = 20,\n// mouseTrackingStiffness = 500,\n// mouseTrackingDamping = 150,\n// waveIntensity = 30,\n// waveRadius = 200,\n// gradients = defaultGradients,\n// dotColor = \"rgba(100, 100, 255, 1)\",\n// glowColor = \"rgba(100, 100, 255, 1)\",\n// enableNoise = true,\n// noiseOpacity = 0.03,\n// enableMouseGlow = true,\n// }: CanvasFractalGridProps) {\n// const containerRef = useRef(null)\n// const canvasRef = useRef(null)\n// const animationRef = useRef()\n// const mouseRef = useRef({ x: 0, y: 0 })\n\n// const mouseX = useSpring(0, {\n// stiffness: mouseTrackingStiffness,\n// damping: mouseTrackingDamping,\n// })\n// const mouseY = useSpring(0, {\n// stiffness: mouseTrackingStiffness,\n// damping: mouseTrackingDamping,\n// })\n\n// const handleMouseMove = useCallback(\n// (event: MouseEvent) => {\n// const { clientX, clientY } = event\n// const { left, top, width, height } =\n// containerRef.current?.getBoundingClientRect() ?? {\n// left: 0,\n// top: 0,\n// width: 0,\n// height: 0,\n// }\n// const x = (clientX - left) / width\n// const y = (clientY - top) / height\n// mouseX.set(x)\n// mouseY.set(y)\n// mouseRef.current = { x, y }\n// },\n// [mouseX, mouseY]\n// )\n\n// useEffect(() => {\n// window.addEventListener(\"mousemove\", handleMouseMove)\n// return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n// }, [handleMouseMove])\n\n// const controls = useAnimation()\n\n// useEffect(() => {\n// controls.start({\n// background: gradients.map(\n// (g) =>\n// `radial-gradient(circle at ${g.centerX}% ${g.centerY}%, ${g.stops\n// .map((s) => `${s.color} ${s.position}%`)\n// .join(\", \")})`\n// ),\n// transition: {\n// duration: gradientAnimationDuration,\n// repeat: Infinity,\n// repeatType: \"reverse\",\n// ease: \"easeInOut\",\n// },\n// })\n// }, [controls, gradients, gradientAnimationDuration])\n\n// const drawDots = useCallback(\n// (ctx: CanvasRenderingContext2D, time: number) => {\n// const { width, height } = ctx.canvas\n// ctx.clearRect(0, 0, width, height)\n\n// const cols = Math.ceil(width / dotSpacing)\n// const rows = Math.ceil(height / dotSpacing)\n\n// const centerX = mouseRef.current.x * width\n// const centerY = mouseRef.current.y * height\n\n// for (let i = 0; i < cols; i++) {\n// for (let j = 0; j < rows; j++) {\n// const x = i * dotSpacing\n// const y = j * dotSpacing\n\n// const distanceX = x - centerX\n// const distanceY = y - centerY\n// const distance = Math.sqrt(\n// distanceX * distanceX + distanceY * distanceY\n// )\n\n// let dotX = x\n// let dotY = y\n\n// if (distance < waveRadius) {\n// const waveStrength = Math.pow(1 - distance / waveRadius, 2)\n// const angle = Math.atan2(distanceY, distanceX)\n// const waveOffset =\n// Math.sin(distance * 0.05 - time * 0.005) *\n// waveIntensity *\n// waveStrength\n// dotX += Math.cos(angle) * waveOffset\n// dotY += Math.sin(angle) * waveOffset\n\n// const glowRadius = dotSize * (1 + waveStrength)\n// const gradient = ctx.createRadialGradient(\n// dotX,\n// dotY,\n// 0,\n// dotX,\n// dotY,\n// glowRadius\n// )\n// gradient.addColorStop(\n// 0,\n// glowColor.replace(\"1)\", `${dotOpacity * (1 + waveStrength)})`)\n// )\n// gradient.addColorStop(1, glowColor.replace(\"1)\", \"0)\"))\n// ctx.fillStyle = gradient\n// } else {\n// ctx.fillStyle = dotColor.replace(\"1)\", `${dotOpacity})`)\n// }\n\n// ctx.beginPath()\n// ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)\n// ctx.fill()\n// }\n// }\n// },\n// [\n// dotSize,\n// dotSpacing,\n// dotOpacity,\n// waveIntensity,\n// waveRadius,\n// dotColor,\n// glowColor,\n// ]\n// )\n\n// useEffect(() => {\n// const canvas = canvasRef.current\n// if (!canvas) return\n\n// const ctx = canvas.getContext(\"2d\")\n// if (!ctx) return\n\n// const resizeCanvas = () => {\n// canvas.width = window.innerWidth\n// canvas.height = window.innerHeight\n// }\n\n// resizeCanvas()\n// window.addEventListener(\"resize\", resizeCanvas)\n\n// let lastTime = 0\n// const animate = (time: number) => {\n// if (time - lastTime > 16) {\n// drawDots(ctx, time)\n// lastTime = time\n// }\n// animationRef.current = requestAnimationFrame(animate)\n// }\n\n// animationRef.current = requestAnimationFrame(animate)\n\n// return () => {\n// window.removeEventListener(\"resize\", resizeCanvas)\n// if (animationRef.current) {\n// cancelAnimationFrame(animationRef.current)\n// }\n// }\n// }, [drawDots])\n\n// const gradientStyle = useMemo(\n// () =>\n// ({\n// \"--mouse-x\": mouseX,\n// \"--mouse-y\": mouseY,\n// } as React.CSSProperties),\n// [mouseX, mouseY]\n// )\n\n// return (\n// \n// \n// \n// \n// \n// {enableNoise && (\n// \n// \n//
\n// )}\n// {enableMouseGlow && (\n// <>\n// \n// \n// \n// )}\n// \n// \n// )\n// }\n\n// export default CanvasFractalGrid\n\n// \"use client\"\n\n// import React, { useCallback, useEffect, useMemo, useRef } from \"react\"\n// import { AnimatePresence, motion, useAnimation, useSpring } from \"motion/react\"\n\n// const NoiseSVG = React.memo(() => (\n// \n// \n// \n// \n// \n// \n// ))\n\n// NoiseSVG.displayName = \"NoiseSVG\"\n\n// interface LandingAnimationProps {\n// dotSize?: number\n// dotSpacing?: number\n// dotOpacity?: number\n// animationDuration?: number\n// mouseTrackingStiffness?: number\n// mouseTrackingDamping?: number\n// waveIntensity?: number\n// waveRadius?: number\n// }\n\n// export function CanvasFractalGrid({\n// dotSize = 4,\n// dotSpacing = 20,\n// dotOpacity = 0.3,\n// animationDuration = 5,\n// mouseTrackingStiffness = 500,\n// mouseTrackingDamping = 150,\n// waveIntensity = 30,\n// waveRadius = 200,\n// }: LandingAnimationProps) {\n// const containerRef = useRef(null)\n// const canvasRef = useRef(null)\n// const animationRef = useRef()\n// const mouseRef = useRef({ x: 0, y: 0 })\n\n// const mouseX = useSpring(0, {\n// stiffness: mouseTrackingStiffness,\n// damping: mouseTrackingDamping,\n// })\n// const mouseY = useSpring(0, {\n// stiffness: mouseTrackingStiffness,\n// damping: mouseTrackingDamping,\n// })\n\n// const handleMouseMove = useCallback(\n// (event: MouseEvent) => {\n// const { clientX, clientY } = event\n// const { left, top, width, height } =\n// containerRef.current?.getBoundingClientRect() ?? {\n// left: 0,\n// top: 0,\n// width: 0,\n// height: 0,\n// }\n// const x = (clientX - left) / width\n// const y = (clientY - top) / height\n// mouseX.set(x)\n// mouseY.set(y)\n// mouseRef.current = { x, y }\n// },\n// [mouseX, mouseY]\n// )\n\n// useEffect(() => {\n// window.addEventListener(\"mousemove\", handleMouseMove)\n// return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n// }, [handleMouseMove])\n\n// const controls = useAnimation()\n\n// useEffect(() => {\n// controls.start({\n// background: [\n// \"radial-gradient(circle at 50% 50%, #FFD6A5 0%, #FFADAD 25%, #FFC6FF 50%, transparent 75%)\",\n// \"radial-gradient(circle at 60% 40%, #A0C4FF 0%, #BDB2FF 25%, #CAFFBF 50%, transparent 75%)\",\n// \"radial-gradient(circle at 40% 60%, #9BF6FF 0%, #FDFFB6 25%, #FFAFCC 50%, transparent 75%)\",\n// ],\n// transition: {\n// duration: 20,\n// repeat: Infinity,\n// repeatType: \"reverse\",\n// ease: \"easeInOut\",\n// },\n// })\n// }, [controls])\n\n// const drawDots = useCallback(\n// (ctx: CanvasRenderingContext2D, time: number) => {\n// const { width, height } = ctx.canvas\n// ctx.clearRect(0, 0, width, height)\n\n// const cols = Math.ceil(width / dotSpacing)\n// const rows = Math.ceil(height / dotSpacing)\n\n// const centerX = mouseRef.current.x * width\n// const centerY = mouseRef.current.y * height\n\n// for (let i = 0; i < cols; i++) {\n// for (let j = 0; j < rows; j++) {\n// const x = i * dotSpacing\n// const y = j * dotSpacing\n\n// const distanceX = x - centerX\n// const distanceY = y - centerY\n// const distance = Math.sqrt(\n// distanceX * distanceX + distanceY * distanceY\n// )\n\n// let dotX = x\n// let dotY = y\n\n// if (distance < waveRadius) {\n// const waveStrength = Math.pow(1 - distance / waveRadius, 2)\n// const angle = Math.atan2(distanceY, distanceX)\n// const waveOffset =\n// Math.sin(distance * 0.05 - time * 0.005) *\n// waveIntensity *\n// waveStrength\n// dotX += Math.cos(angle) * waveOffset\n// dotY += Math.sin(angle) * waveOffset\n\n// const glowRadius = dotSize * (1 + waveStrength)\n// const gradient = ctx.createRadialGradient(\n// dotX,\n// dotY,\n// 0,\n// dotX,\n// dotY,\n// glowRadius\n// )\n// gradient.addColorStop(\n// 0,\n// `rgba(100, 100, 255, ${dotOpacity * (1 + waveStrength)})`\n// )\n// gradient.addColorStop(1, \"rgba(100, 100, 255, 0)\")\n// ctx.fillStyle = gradient\n// } else {\n// ctx.fillStyle = `rgba(100, 100, 255, ${dotOpacity})`\n// }\n\n// ctx.beginPath()\n// ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)\n// ctx.fill()\n// }\n// }\n// },\n// [dotSize, dotSpacing, dotOpacity, waveIntensity, waveRadius]\n// )\n\n// useEffect(() => {\n// const canvas = canvasRef.current\n// if (!canvas) return\n\n// const ctx = canvas.getContext(\"2d\")\n// if (!ctx) return\n\n// const resizeCanvas = () => {\n// canvas.width = window.innerWidth\n// canvas.height = window.innerHeight\n// }\n\n// resizeCanvas()\n// window.addEventListener(\"resize\", resizeCanvas)\n\n// let lastTime = 0\n// const animate = (time: number) => {\n// if (time - lastTime > 16) {\n// drawDots(ctx, time)\n// lastTime = time\n// }\n// animationRef.current = requestAnimationFrame(animate)\n// }\n\n// animationRef.current = requestAnimationFrame(animate)\n\n// let animationId: number\n\n// animationId = requestAnimationFrame(animate)\n\n// return () => {\n// window.removeEventListener(\"resize\", resizeCanvas)\n// cancelAnimationFrame(animationId)\n// }\n// }, [drawDots])\n\n// const gradientStyle = useMemo(\n// () =>\n// ({\n// \"--mouse-x\": mouseX,\n// \"--mouse-y\": mouseY,\n// } as React.CSSProperties),\n// [mouseX, mouseY]\n// )\n\n// return (\n// \n// \n// \n// \n// \n// \n//
\n// \n//
\n// \n// \n// \n//
\n// )\n// }\n\n// export default CanvasFractalGrid\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/color-picker.json b/apps/www/public/registry/styles/default/color-picker.json index 4f894b3..17988fb 100644 --- a/apps/www/public/registry/styles/default/color-picker.json +++ b/apps/www/public/registry/styles/default/color-picker.json @@ -6,7 +6,7 @@ "files": [ { "name": "color-picker.tsx", - "content": "\"use client\"\n\nimport React, { useEffect, useState } from \"react\"\nimport { AnimatePresence, motion } from \"framer-motion\"\nimport { Check, ChevronDown } from \"lucide-react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\"\n\n// Helper functions for color conversion\nconst hslToHex = (h: number, s: number, l: number) => {\n l /= 100\n const a = (s * Math.min(l, 1 - l)) / 100\n const f = (n: number) => {\n const k = (n + h / 30) % 12\n const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)\n return Math.round(255 * color)\n .toString(16)\n .padStart(2, \"0\")\n }\n return `#${f(0)}${f(8)}${f(4)}`\n}\n\nconst hexToHsl = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex)\n if (!result) return [0, 0, 0]\n\n let r = parseInt(result[1], 16) / 255\n let g = parseInt(result[2], 16) / 255\n let b = parseInt(result[3], 16) / 255\n\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n let h = 0\n let s = 0\n let l = (max + min) / 2\n\n if (max !== min) {\n const d = max - min\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n switch (max) {\n case r:\n h = (g - b) / d + (g < b ? 6 : 0)\n break\n case g:\n h = (b - r) / d + 2\n break\n case b:\n h = (r - g) / d + 4\n break\n }\n h /= 6\n }\n\n return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)]\n}\n\nconst normalizeColor = (color: string): string => {\n if (color.startsWith(\"#\")) {\n return color.toUpperCase()\n } else if (color.startsWith(\"hsl\")) {\n const [h, s, l] = color.match(/\\d+(\\.\\d+)?/g)?.map(Number) || [0, 0, 0]\n return `hsl(${Math.round(h)}, ${Math.round(s)}%, ${Math.round(l)}%)`\n }\n return color\n}\n\nconst trimColorString = (color: string, maxLength: number = 20): string => {\n if (color.length <= maxLength) return color\n return `${color.slice(0, maxLength - 3)}...`\n}\n\nexport function ColorPicker({\n color,\n onChange,\n}: {\n color: string\n onChange: (color: string) => void\n}) {\n const [hsl, setHsl] = useState<[number, number, number]>([0, 0, 0])\n const [colorInput, setColorInput] = useState(color)\n const [isOpen, setIsOpen] = useState(false)\n\n useEffect(() => {\n handleColorChange(color)\n }, [color])\n\n const handleColorChange = (newColor: string) => {\n const normalizedColor = normalizeColor(newColor)\n setColorInput(normalizedColor)\n\n let h, s, l\n if (normalizedColor.startsWith(\"#\")) {\n ;[h, s, l] = hexToHsl(normalizedColor)\n } else {\n ;[h, s, l] = normalizedColor.match(/\\d+(\\.\\d+)?/g)?.map(Number) || [\n 0, 0, 0,\n ]\n }\n\n setHsl([h, s, l])\n onChange(`hsl(${h.toFixed(1)}, ${s.toFixed(1)}%, ${l.toFixed(1)}%)`)\n }\n\n const handleHueChange = (hue: number) => {\n const newHsl: [number, number, number] = [hue, hsl[1], hsl[2]]\n setHsl(newHsl)\n handleColorChange(`hsl(${newHsl[0]}, ${newHsl[1]}%, ${newHsl[2]}%)`)\n }\n\n const handleSaturationLightnessChange = (\n event: React.MouseEvent\n ) => {\n const rect = event.currentTarget.getBoundingClientRect()\n const x = event.clientX - rect.left\n const y = event.clientY - rect.top\n const s = Math.round((x / rect.width) * 100)\n const l = Math.round(100 - (y / rect.height) * 100)\n const newHsl: [number, number, number] = [hsl[0], s, l]\n setHsl(newHsl)\n handleColorChange(`hsl(${newHsl[0]}, ${newHsl[1]}%, ${newHsl[2]}%)`)\n }\n\n const handleColorInputChange = (\n event: React.ChangeEvent\n ) => {\n const newColor = event.target.value\n setColorInput(newColor)\n if (\n /^#[0-9A-Fa-f]{6}$/.test(newColor) ||\n /^hsl$$\\d+,\\s*\\d+%,\\s*\\d+%$$$/.test(newColor)\n ) {\n handleColorChange(newColor)\n }\n }\n\n const colorPresets = [\n \"#FF3B30\",\n \"#FF9500\",\n \"#FFCC00\",\n \"#4CD964\",\n \"#5AC8FA\",\n \"#007AFF\",\n \"#5856D6\",\n \"#FF2D55\",\n \"#8E8E93\",\n \"#EFEFF4\",\n \"#E5E5EA\",\n \"#D1D1D6\",\n ]\n\n return (\n \n \n \n \n {trimColorString(colorInput)}\n \n \n \n \n \n \n \n \n handleHueChange(Number(e.target.value))}\n className=\"w-full h-3 rounded-full appearance-none cursor-pointer\"\n style={{\n background: `linear-gradient(to right, \n hsl(0, 100%, 50%), hsl(60, 100%, 50%), hsl(120, 100%, 50%), \n hsl(180, 100%, 50%), hsl(240, 100%, 50%), hsl(300, 100%, 50%), hsl(360, 100%, 50%)\n )`,\n }}\n whileHover={{ scale: 1.05 }}\n whileTap={{ scale: 0.95 }}\n />\n
\n \n \n \n
\n
\n \n {colorPresets.map((preset) => (\n handleColorChange(preset)}\n whileHover={{ scale: 1.2, zIndex: 1 }}\n whileTap={{ scale: 0.9 }}\n >\n {colorInput === preset && (\n \n \n \n )}\n \n ))}\n \n
\n \n
\n
\n )\n}\n\nexport default ColorPicker\n" + "content": "\"use client\"\n\nimport React, { useEffect, useState } from \"react\"\nimport { Check, ChevronDown } from \"lucide-react\"\nimport { AnimatePresence, motion } from \"motion/react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\"\n\n// Helper functions for color conversion\nconst hslToHex = (h: number, s: number, l: number) => {\n l /= 100\n const a = (s * Math.min(l, 1 - l)) / 100\n const f = (n: number) => {\n const k = (n + h / 30) % 12\n const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)\n return Math.round(255 * color)\n .toString(16)\n .padStart(2, \"0\")\n }\n return `#${f(0)}${f(8)}${f(4)}`\n}\n\nconst hexToHsl = (hex: string): [number, number, number] => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex)\n if (!result) return [0, 0, 0]\n\n let r = parseInt(result[1], 16) / 255\n let g = parseInt(result[2], 16) / 255\n let b = parseInt(result[3], 16) / 255\n\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n let h = 0\n let s = 0\n let l = (max + min) / 2\n\n if (max !== min) {\n const d = max - min\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n switch (max) {\n case r:\n h = (g - b) / d + (g < b ? 6 : 0)\n break\n case g:\n h = (b - r) / d + 2\n break\n case b:\n h = (r - g) / d + 4\n break\n }\n h /= 6\n }\n\n return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)]\n}\n\nconst normalizeColor = (color: string): string => {\n if (color.startsWith(\"#\")) {\n return color.toUpperCase()\n } else if (color.startsWith(\"hsl\")) {\n const [h, s, l] = color.match(/\\d+(\\.\\d+)?/g)?.map(Number) || [0, 0, 0]\n return `hsl(${Math.round(h)}, ${Math.round(s)}%, ${Math.round(l)}%)`\n }\n return color\n}\n\nconst trimColorString = (color: string, maxLength: number = 20): string => {\n if (color.length <= maxLength) return color\n return `${color.slice(0, maxLength - 3)}...`\n}\n\nexport function ColorPicker({\n color,\n onChange,\n}: {\n color: string\n onChange: (color: string) => void\n}) {\n const [hsl, setHsl] = useState<[number, number, number]>([0, 0, 0])\n const [colorInput, setColorInput] = useState(color)\n const [isOpen, setIsOpen] = useState(false)\n\n useEffect(() => {\n handleColorChange(color)\n }, [color])\n\n const handleColorChange = (newColor: string) => {\n const normalizedColor = normalizeColor(newColor)\n setColorInput(normalizedColor)\n\n let h, s, l\n if (normalizedColor.startsWith(\"#\")) {\n ;[h, s, l] = hexToHsl(normalizedColor)\n } else {\n ;[h, s, l] = normalizedColor.match(/\\d+(\\.\\d+)?/g)?.map(Number) || [\n 0, 0, 0,\n ]\n }\n\n setHsl([h, s, l])\n onChange(`hsl(${h.toFixed(1)}, ${s.toFixed(1)}%, ${l.toFixed(1)}%)`)\n }\n\n const handleHueChange = (hue: number) => {\n const newHsl: [number, number, number] = [hue, hsl[1], hsl[2]]\n setHsl(newHsl)\n handleColorChange(`hsl(${newHsl[0]}, ${newHsl[1]}%, ${newHsl[2]}%)`)\n }\n\n const handleSaturationLightnessChange = (\n event: React.MouseEvent\n ) => {\n const rect = event.currentTarget.getBoundingClientRect()\n const x = event.clientX - rect.left\n const y = event.clientY - rect.top\n const s = Math.round((x / rect.width) * 100)\n const l = Math.round(100 - (y / rect.height) * 100)\n const newHsl: [number, number, number] = [hsl[0], s, l]\n setHsl(newHsl)\n handleColorChange(`hsl(${newHsl[0]}, ${newHsl[1]}%, ${newHsl[2]}%)`)\n }\n\n const handleColorInputChange = (\n event: React.ChangeEvent\n ) => {\n const newColor = event.target.value\n setColorInput(newColor)\n if (\n /^#[0-9A-Fa-f]{6}$/.test(newColor) ||\n /^hsl$$\\d+,\\s*\\d+%,\\s*\\d+%$$$/.test(newColor)\n ) {\n handleColorChange(newColor)\n }\n }\n\n const colorPresets = [\n \"#FF3B30\",\n \"#FF9500\",\n \"#FFCC00\",\n \"#4CD964\",\n \"#5AC8FA\",\n \"#007AFF\",\n \"#5856D6\",\n \"#FF2D55\",\n \"#8E8E93\",\n \"#EFEFF4\",\n \"#E5E5EA\",\n \"#D1D1D6\",\n ]\n\n return (\n \n \n \n \n {trimColorString(colorInput)}\n \n \n \n \n \n \n \n \n handleHueChange(Number(e.target.value))}\n className=\"w-full h-3 rounded-full appearance-none cursor-pointer\"\n style={{\n background: `linear-gradient(to right, \n hsl(0, 100%, 50%), hsl(60, 100%, 50%), hsl(120, 100%, 50%), \n hsl(180, 100%, 50%), hsl(240, 100%, 50%), hsl(300, 100%, 50%), hsl(360, 100%, 50%)\n )`,\n }}\n whileHover={{ scale: 1.05 }}\n whileTap={{ scale: 0.95 }}\n />\n
\n \n \n \n
\n
\n \n {colorPresets.map((preset) => (\n handleColorChange(preset)}\n whileHover={{ scale: 1.2, zIndex: 1 }}\n whileTap={{ scale: 0.9 }}\n >\n {colorInput === preset && (\n \n \n \n )}\n \n ))}\n \n
\n \n
\n
\n )\n}\n\nexport default ColorPicker\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/direction-aware-tabs.json b/apps/www/public/registry/styles/default/direction-aware-tabs.json index ee21a7e..5418100 100644 --- a/apps/www/public/registry/styles/default/direction-aware-tabs.json +++ b/apps/www/public/registry/styles/default/direction-aware-tabs.json @@ -7,7 +7,7 @@ "files": [ { "name": "direction-aware-tabs.tsx", - "content": "\"use client\"\n\nimport { ReactNode, useMemo, useState } from \"react\"\nimport { AnimatePresence, MotionConfig, motion } from \"framer-motion\"\nimport useMeasure from \"react-use-measure\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype Tab = {\n id: number\n label: string\n content: ReactNode\n}\n\ninterface OgImageSectionProps {\n tabs: Tab[]\n className?: string\n rounded?: string\n onChange?: () => void\n}\n\nfunction DirectionAwareTabs({\n tabs,\n className,\n rounded,\n onChange,\n}: OgImageSectionProps) {\n const [activeTab, setActiveTab] = useState(0)\n const [direction, setDirection] = useState(0)\n const [isAnimating, setIsAnimating] = useState(false)\n const [ref, bounds] = useMeasure()\n\n const content = useMemo(() => {\n const activeTabContent = tabs.find((tab) => tab.id === activeTab)?.content\n return activeTabContent || null\n }, [activeTab, tabs])\n\n const handleTabClick = (newTabId: number) => {\n if (newTabId !== activeTab && !isAnimating) {\n const newDirection = newTabId > activeTab ? 1 : -1\n setDirection(newDirection)\n setActiveTab(newTabId)\n onChange ? onChange() : null\n }\n }\n\n const variants = {\n initial: (direction: number) => ({\n x: 300 * direction,\n opacity: 0,\n filter: \"blur(4px)\",\n }),\n active: {\n x: 0,\n opacity: 1,\n filter: \"blur(0px)\",\n },\n exit: (direction: number) => ({\n x: -300 * direction,\n opacity: 0,\n filter: \"blur(4px)\",\n }),\n }\n\n return (\n
\n \n {tabs.map((tab) => (\n handleTabClick(tab.id)}\n className={cn(\n \"relative rounded-full px-3.5 py-1.5 text-xs sm:text-sm font-medium text-neutral-200 transition focus-visible:outline-1 focus-visible:ring-1 focus-visible:outline-none flex gap-2 items-center \",\n activeTab === tab.id\n ? \"text-white\"\n : \"hover:text-neutral-300/60 text-neutral-200/80\",\n rounded\n )}\n style={{ WebkitTapHighlightColor: \"transparent\" }}\n >\n {activeTab === tab.id && (\n \n )}\n\n {tab.label}\n \n ))}\n
\n \n \n
\n setIsAnimating(false)}\n >\n setIsAnimating(true)}\n onAnimationComplete={() => setIsAnimating(false)}\n >\n {content}\n \n \n
\n \n
\n \n )\n}\nexport { DirectionAwareTabs }\n" + "content": "\"use client\"\n\nimport { ReactNode, useMemo, useState } from \"react\"\nimport { AnimatePresence, MotionConfig, motion } from \"motion/react\"\nimport useMeasure from \"react-use-measure\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype Tab = {\n id: number\n label: string\n content: ReactNode\n}\n\ninterface OgImageSectionProps {\n tabs: Tab[]\n className?: string\n rounded?: string\n onChange?: () => void\n}\n\nfunction DirectionAwareTabs({\n tabs,\n className,\n rounded,\n onChange,\n}: OgImageSectionProps) {\n const [activeTab, setActiveTab] = useState(0)\n const [direction, setDirection] = useState(0)\n const [isAnimating, setIsAnimating] = useState(false)\n const [ref, bounds] = useMeasure()\n\n const content = useMemo(() => {\n const activeTabContent = tabs.find((tab) => tab.id === activeTab)?.content\n return activeTabContent || null\n }, [activeTab, tabs])\n\n const handleTabClick = (newTabId: number) => {\n if (newTabId !== activeTab && !isAnimating) {\n const newDirection = newTabId > activeTab ? 1 : -1\n setDirection(newDirection)\n setActiveTab(newTabId)\n onChange ? onChange() : null\n }\n }\n\n const variants = {\n initial: (direction: number) => ({\n x: 300 * direction,\n opacity: 0,\n filter: \"blur(4px)\",\n }),\n active: {\n x: 0,\n opacity: 1,\n filter: \"blur(0px)\",\n },\n exit: (direction: number) => ({\n x: -300 * direction,\n opacity: 0,\n filter: \"blur(4px)\",\n }),\n }\n\n return (\n
\n \n {tabs.map((tab) => (\n handleTabClick(tab.id)}\n className={cn(\n \"relative rounded-full px-3.5 py-1.5 text-xs sm:text-sm font-medium text-neutral-200 transition focus-visible:outline-1 focus-visible:ring-1 focus-visible:outline-none flex gap-2 items-center \",\n activeTab === tab.id\n ? \"text-white\"\n : \"hover:text-neutral-300/60 text-neutral-200/80\",\n rounded\n )}\n style={{ WebkitTapHighlightColor: \"transparent\" }}\n >\n {activeTab === tab.id && (\n \n )}\n\n {tab.label}\n \n ))}\n
\n \n \n
\n setIsAnimating(false)}\n >\n setIsAnimating(true)}\n onAnimationComplete={() => setIsAnimating(false)}\n >\n {content}\n \n \n
\n \n
\n \n )\n}\nexport { DirectionAwareTabs }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/dock.json b/apps/www/public/registry/styles/default/dock.json index 2ac6811..87c9ccf 100644 --- a/apps/www/public/registry/styles/default/dock.json +++ b/apps/www/public/registry/styles/default/dock.json @@ -6,7 +6,7 @@ "files": [ { "name": "dock.tsx", - "content": "\"use client\"\n\nimport React, {\n ReactNode,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\"\nimport {\n AnimatePresence,\n MotionValue,\n animate,\n motion,\n useAnimation,\n useMotionValue,\n useSpring,\n useTransform,\n} from \"framer-motion\"\n\nimport { cn } from \"@/lib/utils\"\n\n// Interface to define the types for our Dock context\ninterface DockContextType {\n width: number // Width of the dock\n hovered: boolean // If the dock is hovered\n setIsZooming: (value: boolean) => void // Function to set zooming state\n zoomLevel: MotionValue // Motion value for zoom level\n mouseX: MotionValue // Motion value for mouse X position\n animatingIndexes: number[] // Array of animating indexes\n setAnimatingIndexes: (indexes: number[]) => void // Function to set animating indexes\n}\n\n// Initial width for the dock\nconst INITIAL_WIDTH = 48\n\n// Create a context to manage Dock state\nconst DockContext = createContext({\n width: 0,\n hovered: false,\n setIsZooming: () => {},\n zoomLevel: null as any,\n mouseX: null as any,\n animatingIndexes: [],\n setAnimatingIndexes: () => {},\n})\n\n// Custom hook to use Dock context\nconst useDock = () => useContext(DockContext)\n\n// Props for the Dock component\ninterface DockProps {\n className?: string\n children: ReactNode // React children to be rendered within the dock\n}\n\n// Main Dock component: orchestrating the dock's animation behavior\nfunction Dock({ className, children }: DockProps) {\n const [hovered, setHovered] = useState(false) // State to track if the dock is hovered. When the mouse hovers over the dock, this state changes to true.\n const [width, setWidth] = useState(0) // State to track the width of the dock. This dynamically updates based on the dock's current width.\n const dockRef = useRef(null) // Reference to the dock element in the DOM. This allows direct manipulation and measurement of the dock.\n const isZooming = useRef(false) // Reference to track if the zooming animation is active. This prevents conflicting animations.\n const [animatingIndexes, setAnimatingIndexes] = useState([]) // State to track which dock items are currently animating. This array holds the indices of animating items.\n\n // Callback to toggle the zooming state. This ensures that we don't trigger hover animations while zooming.\n const setIsZooming = useCallback((value: boolean) => {\n isZooming.current = value // Update the zooming reference\n setHovered(!value) // Update the hover state based on zooming\n }, [])\n\n const zoomLevel = useMotionValue(1) // Motion value for the zoom level of the dock. This provides a smooth zooming animation.\n\n // Hook to handle window resize events and update the dock's width accordingly.\n useWindowResize(() => {\n setWidth(dockRef.current?.clientWidth || 0) // Set width to the dock's current width or 0 if undefined\n })\n\n const mouseX = useMotionValue(Infinity) // Motion value to track the mouse's X position relative to the viewport. Initialized to Infinity to denote no tracking initially.\n\n return (\n // Provide the dock's state and control methods to the rest of the application through context.\n \n {\n mouseX.set(e.pageX) // Update the mouseX motion value to the current mouse position\n if (!isZooming.current) {\n // Only set hovered if not zooming\n setHovered(true) // Set hovered state to true\n }\n }}\n // Event handler for when the mouse leaves the dock\n onMouseLeave={() => {\n mouseX.set(Infinity) // Reset mouseX motion value\n setHovered(false) // Set hovered state to false\n }}\n style={{\n x: \"-50%\", // Center the dock horizontally\n scale: zoomLevel, // Bind the zoom level to the scale style property\n }}\n >\n {children} {/* Render the dock's children within the motion div */}\n \n \n )\n}\n\n// Props for the DockCardInner component\ninterface DockCardInnerProps {\n src: string // Source URL for the image\n id: string // Unique identifier for the card\n children?: ReactNode // Optional children for the card\n}\n\n// DockCardInner component to display images and handle animation states\nfunction DockCardInner({ src, id, children }: DockCardInnerProps) {\n const { animatingIndexes } = useDock() // Access the Dock context to get the animating indexes. This determines which cards are currently animating.\n\n return (\n \n {/* Background image with a blur effect to give a depth illusion */}\n \n\n {/* AnimatePresence component to handle the entrance and exit animations of children - in our case, the \"openIcon\" */}\n \n {animatingIndexes.includes(parseInt(id)) && children ? (\n \n
\n {/* Render the openIcon */}\n {children}\n
\n \n ) : null}\n
\n\n {/* Another AnimatePresence to handle layout animations */}\n \n {!animatingIndexes.includes(parseInt(id)) ? (\n \n ) : null}\n \n
\n )\n}\n\n// Props for the DockCard component\ninterface DockCardProps {\n children: ReactNode // React children to be rendered within the dock card\n id: string // Unique identifier for the dock card\n}\n\n// DockCard component: manages individual card behavior within the dock\nfunction DockCard({ children, id }: DockCardProps) {\n const cardRef = useRef(null) // Reference to the card button element for direct DOM manipulation and measurement\n const [elCenterX, setElCenterX] = useState(0) // State to store the center X position of the card for accurate mouse interaction calculations\n const dock = useDock() // Access the Dock context to get shared state and control functions\n\n // Spring animation for the size of the card, providing a smooth and responsive scaling effect\n const size = useSpring(INITIAL_WIDTH, {\n stiffness: 320,\n damping: 20,\n mass: 0.1,\n })\n\n // Spring animation for the opacity of the card, enabling smooth fade-in and fade-out effects\n const opacity = useSpring(0, {\n stiffness: 300,\n damping: 20,\n })\n\n // Custom hook to track mouse position and update the card size dynamically based on proximity to the mouse\n useMousePosition(\n {\n onChange: ({ value }) => {\n const mouseX = value.x\n if (dock.width > 0) {\n // Calculate transformed value based on mouse position and card center, using a cosine function for a smooth curve\n const transformedValue =\n INITIAL_WIDTH +\n 36 *\n Math.cos((((mouseX - elCenterX) / dock.width) * Math.PI) / 2) **\n 12\n\n // Only animate size if the dock is hovered\n if (dock.hovered) {\n animate(size, transformedValue)\n }\n }\n },\n },\n [elCenterX, dock]\n )\n\n // Hook to update the center X position of the card on window resize for accurate mouse interaction\n useWindowResize(() => {\n const { x } = cardRef.current?.getBoundingClientRect() || { x: 0 }\n setElCenterX(x + 24) // 24 is the half of INITIAL_WIDTH (48 / 2), centering the element\n })\n\n const isAnimating = useRef(false) // Reference to track if the card is currently animating to avoid conflicting animations\n const controls = useAnimation() // Animation controls for managing card's Y position during the animation loop\n const timeoutRef = useRef(null) // Reference to manage timeout cleanup on component unmount\n\n // Handle click event to start or stop the card's animation\n const handleClick = () => {\n if (!isAnimating.current) {\n isAnimating.current = true\n // Add the card's id to the animatingIndexes array in the Dock context\n dock.setAnimatingIndexes([...dock.animatingIndexes, parseInt(id)])\n opacity.set(0.5) // Set opacity for the animation\n controls.start({\n y: -24, // Move the card up by 24 pixels\n transition: {\n repeat: Infinity, // Repeat the animation indefinitely\n repeatType: \"reverse\", // Reverse the direction of the animation each cycle\n duration: 0.5, // Duration of each cycle\n },\n })\n } else {\n isAnimating.current = false\n // Remove the card's id from the animatingIndexes array in the Dock context\n dock.setAnimatingIndexes(\n dock.animatingIndexes.filter((index) => index !== parseInt(id))\n )\n opacity.set(0) // Reset opacity\n controls.start({\n y: 0, // Reset Y position to the original state\n transition: { duration: 0.5 }, // Smooth transition back to original state\n })\n }\n }\n\n // Cleanup timeout on component unmount to prevent memory leaks\n useEffect(() => {\n return () => clearTimeout(timeoutRef.current!)\n }, [])\n\n // Calculate the distance from the mouse position to the center of the card\n const distance = useTransform(dock.mouseX, (val) => {\n const bounds = cardRef.current?.getBoundingClientRect() ?? {\n x: 0,\n width: 0,\n }\n return val - bounds.x - bounds.width / 2 // Calculate distance to the center of the card\n })\n\n // Transform the calculated distance into a responsive width for the card\n let widthSync = useTransform(distance, [-150, 0, 150], [40, 80, 40])\n let width = useSpring(widthSync, { mass: 0.1, stiffness: 150, damping: 12 })\n\n return (\n
\n {/* Motion button for the card, handling click events and animations */}\n \n {children}{\" \"}\n {/* Render the children of the DockCard inside the motion button */}\n \n\n {/* AnimatePresence to manage the presence and layout animations of the card's indicator */}\n \n {dock.animatingIndexes.includes(parseInt(id)) ? (\n \n \n \n ) : null}\n \n
\n )\n}\n\n// Divider component for the dock\nfunction DockDivider() {\n return (\n \n \n \n )\n}\n\ntype UseWindowResizeCallback = (width: number, height: number) => void\n\n// Custom hook to handle window resize events and invoke a callback with the new dimensions\nfunction useWindowResize(callback: UseWindowResizeCallback) {\n // Create a stable callback reference to ensure the latest callback is always used\n const callbackRef = useCallbackRef(callback)\n\n useEffect(() => {\n // Function to handle window resize and call the provided callback with updated dimensions\n const handleResize = () => {\n callbackRef(window.innerWidth, window.innerHeight)\n }\n\n // Initial call to handleResize to capture the current window size\n handleResize()\n // Adding event listener for window resize events\n window.addEventListener(\"resize\", handleResize)\n\n // Cleanup function to remove the event listener when the component unmounts or dependencies change\n return () => {\n window.removeEventListener(\"resize\", handleResize)\n }\n }, [callbackRef]) // Dependency array includes the stable callback reference\n}\n\n// Custom hook to create a stable callback reference\nfunction useCallbackRef any>(callback: T): T {\n // Use a ref to store the callback\n const callbackRef = useRef(callback)\n\n // Update the ref with the latest callback whenever it changes\n useEffect(() => {\n callbackRef.current = callback\n })\n\n // Return a memoized version of the callback that always uses the latest callback stored in the ref\n return useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, [])\n}\n\n// Interface for mouse position options\ninterface MousePositionOptions {\n onChange?: (position: { value: { x: number; y: number } }) => void\n}\n\n// Custom hook to track mouse position and animate values accordingly\nexport function useMousePosition(\n options: MousePositionOptions = {}, // Options to customize behavior, including an onChange callback\n deps: readonly any[] = [] // Dependencies array to determine when the effect should re-run\n) {\n const { onChange } = options // Destructure onChange from options for use in the effect\n\n // Create motion values for x and y coordinates, initialized to 0\n const x = useMotionValue(0)\n const y = useMotionValue(0)\n\n useEffect(() => {\n // Function to handle mouse move events, animating the x and y motion values to the current mouse coordinates\n const handleMouseMove = (event: MouseEvent) => {\n animate(x, event.clientX)\n animate(y, event.clientY)\n }\n\n // Function to handle changes in the motion values, calling the onChange callback if it exists\n const handleChange = () => {\n if (onChange) {\n onChange({ value: { x: x.get(), y: y.get() } })\n }\n }\n\n // Subscribe to changes in the x and y motion values\n const unsubscribeX = x.on(\"change\", handleChange)\n const unsubscribeY = y.on(\"change\", handleChange)\n\n // Add event listener for mouse move events\n window.addEventListener(\"mousemove\", handleMouseMove)\n\n // Cleanup function to remove event listener and unsubscribe from motion value changes\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n unsubscribeX()\n unsubscribeY()\n }\n }, [x, y, onChange, ...deps]) // Dependency array includes x, y, onChange, and any additional dependencies\n\n // Memoize and return the motion values for x and y coordinates\n return useMemo(\n () => ({\n x, // Motion value for x coordinate\n y, // Motion value for y coordinate\n }),\n [x, y] // Dependencies for the memoized return value\n )\n}\n\nexport { Dock, DockCard, DockCardInner, DockDivider, useDock }\nexport default Dock\n" + "content": "\"use client\"\n\nimport React, {\n ReactNode,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\"\nimport {\n AnimatePresence,\n MotionValue,\n animate,\n motion,\n useAnimation,\n useMotionValue,\n useSpring,\n useTransform,\n} from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n// Interface to define the types for our Dock context\ninterface DockContextType {\n width: number // Width of the dock\n hovered: boolean // If the dock is hovered\n setIsZooming: (value: boolean) => void // Function to set zooming state\n zoomLevel: MotionValue // Motion value for zoom level\n mouseX: MotionValue // Motion value for mouse X position\n animatingIndexes: number[] // Array of animating indexes\n setAnimatingIndexes: (indexes: number[]) => void // Function to set animating indexes\n}\n\n// Initial width for the dock\nconst INITIAL_WIDTH = 48\n\n// Create a context to manage Dock state\nconst DockContext = createContext({\n width: 0,\n hovered: false,\n setIsZooming: () => {},\n zoomLevel: null as any,\n mouseX: null as any,\n animatingIndexes: [],\n setAnimatingIndexes: () => {},\n})\n\n// Custom hook to use Dock context\nconst useDock = () => useContext(DockContext)\n\n// Props for the Dock component\ninterface DockProps {\n className?: string\n children: ReactNode // React children to be rendered within the dock\n}\n\n// Main Dock component: orchestrating the dock's animation behavior\nfunction Dock({ className, children }: DockProps) {\n const [hovered, setHovered] = useState(false) // State to track if the dock is hovered. When the mouse hovers over the dock, this state changes to true.\n const [width, setWidth] = useState(0) // State to track the width of the dock. This dynamically updates based on the dock's current width.\n const dockRef = useRef(null) // Reference to the dock element in the DOM. This allows direct manipulation and measurement of the dock.\n const isZooming = useRef(false) // Reference to track if the zooming animation is active. This prevents conflicting animations.\n const [animatingIndexes, setAnimatingIndexes] = useState([]) // State to track which dock items are currently animating. This array holds the indices of animating items.\n\n // Callback to toggle the zooming state. This ensures that we don't trigger hover animations while zooming.\n const setIsZooming = useCallback((value: boolean) => {\n isZooming.current = value // Update the zooming reference\n setHovered(!value) // Update the hover state based on zooming\n }, [])\n\n const zoomLevel = useMotionValue(1) // Motion value for the zoom level of the dock. This provides a smooth zooming animation.\n\n // Hook to handle window resize events and update the dock's width accordingly.\n useWindowResize(() => {\n setWidth(dockRef.current?.clientWidth || 0) // Set width to the dock's current width or 0 if undefined\n })\n\n const mouseX = useMotionValue(Infinity) // Motion value to track the mouse's X position relative to the viewport. Initialized to Infinity to denote no tracking initially.\n\n return (\n // Provide the dock's state and control methods to the rest of the application through context.\n \n {\n mouseX.set(e.pageX) // Update the mouseX motion value to the current mouse position\n if (!isZooming.current) {\n // Only set hovered if not zooming\n setHovered(true) // Set hovered state to true\n }\n }}\n // Event handler for when the mouse leaves the dock\n onMouseLeave={() => {\n mouseX.set(Infinity) // Reset mouseX motion value\n setHovered(false) // Set hovered state to false\n }}\n style={{\n x: \"-50%\", // Center the dock horizontally\n scale: zoomLevel, // Bind the zoom level to the scale style property\n }}\n >\n {children} {/* Render the dock's children within the motion div */}\n \n \n )\n}\n\n// Props for the DockCardInner component\ninterface DockCardInnerProps {\n src: string // Source URL for the image\n id: string // Unique identifier for the card\n children?: ReactNode // Optional children for the card\n}\n\n// DockCardInner component to display images and handle animation states\nfunction DockCardInner({ src, id, children }: DockCardInnerProps) {\n const { animatingIndexes } = useDock() // Access the Dock context to get the animating indexes. This determines which cards are currently animating.\n\n return (\n \n {/* Background image with a blur effect to give a depth illusion */}\n \n\n {/* AnimatePresence component to handle the entrance and exit animations of children - in our case, the \"openIcon\" */}\n \n {animatingIndexes.includes(parseInt(id)) && children ? (\n \n
\n {/* Render the openIcon */}\n {children}\n
\n \n ) : null}\n
\n\n {/* Another AnimatePresence to handle layout animations */}\n \n {!animatingIndexes.includes(parseInt(id)) ? (\n \n ) : null}\n \n
\n )\n}\n\n// Props for the DockCard component\ninterface DockCardProps {\n children: ReactNode // React children to be rendered within the dock card\n id: string // Unique identifier for the dock card\n}\n\n// DockCard component: manages individual card behavior within the dock\nfunction DockCard({ children, id }: DockCardProps) {\n const cardRef = useRef(null) // Reference to the card button element for direct DOM manipulation and measurement\n const [elCenterX, setElCenterX] = useState(0) // State to store the center X position of the card for accurate mouse interaction calculations\n const dock = useDock() // Access the Dock context to get shared state and control functions\n\n // Spring animation for the size of the card, providing a smooth and responsive scaling effect\n const size = useSpring(INITIAL_WIDTH, {\n stiffness: 320,\n damping: 20,\n mass: 0.1,\n })\n\n // Spring animation for the opacity of the card, enabling smooth fade-in and fade-out effects\n const opacity = useSpring(0, {\n stiffness: 300,\n damping: 20,\n })\n\n // Custom hook to track mouse position and update the card size dynamically based on proximity to the mouse\n useMousePosition(\n {\n onChange: ({ value }) => {\n const mouseX = value.x\n if (dock.width > 0) {\n // Calculate transformed value based on mouse position and card center, using a cosine function for a smooth curve\n const transformedValue =\n INITIAL_WIDTH +\n 36 *\n Math.cos((((mouseX - elCenterX) / dock.width) * Math.PI) / 2) **\n 12\n\n // Only animate size if the dock is hovered\n if (dock.hovered) {\n animate(size, transformedValue)\n }\n }\n },\n },\n [elCenterX, dock]\n )\n\n // Hook to update the center X position of the card on window resize for accurate mouse interaction\n useWindowResize(() => {\n const { x } = cardRef.current?.getBoundingClientRect() || { x: 0 }\n setElCenterX(x + 24) // 24 is the half of INITIAL_WIDTH (48 / 2), centering the element\n })\n\n const isAnimating = useRef(false) // Reference to track if the card is currently animating to avoid conflicting animations\n const controls = useAnimation() // Animation controls for managing card's Y position during the animation loop\n const timeoutRef = useRef(null) // Reference to manage timeout cleanup on component unmount\n\n // Handle click event to start or stop the card's animation\n const handleClick = () => {\n if (!isAnimating.current) {\n isAnimating.current = true\n // Add the card's id to the animatingIndexes array in the Dock context\n dock.setAnimatingIndexes([...dock.animatingIndexes, parseInt(id)])\n opacity.set(0.5) // Set opacity for the animation\n controls.start({\n y: -24, // Move the card up by 24 pixels\n transition: {\n repeat: Infinity, // Repeat the animation indefinitely\n repeatType: \"reverse\", // Reverse the direction of the animation each cycle\n duration: 0.5, // Duration of each cycle\n },\n })\n } else {\n isAnimating.current = false\n // Remove the card's id from the animatingIndexes array in the Dock context\n dock.setAnimatingIndexes(\n dock.animatingIndexes.filter((index) => index !== parseInt(id))\n )\n opacity.set(0) // Reset opacity\n controls.start({\n y: 0, // Reset Y position to the original state\n transition: { duration: 0.5 }, // Smooth transition back to original state\n })\n }\n }\n\n // Cleanup timeout on component unmount to prevent memory leaks\n useEffect(() => {\n return () => clearTimeout(timeoutRef.current!)\n }, [])\n\n // Calculate the distance from the mouse position to the center of the card\n const distance = useTransform(dock.mouseX, (val) => {\n const bounds = cardRef.current?.getBoundingClientRect() ?? {\n x: 0,\n width: 0,\n }\n return val - bounds.x - bounds.width / 2 // Calculate distance to the center of the card\n })\n\n // Transform the calculated distance into a responsive width for the card\n let widthSync = useTransform(distance, [-150, 0, 150], [40, 80, 40])\n let width = useSpring(widthSync, { mass: 0.1, stiffness: 150, damping: 12 })\n\n return (\n
\n {/* Motion button for the card, handling click events and animations */}\n \n {children}{\" \"}\n {/* Render the children of the DockCard inside the motion button */}\n \n\n {/* AnimatePresence to manage the presence and layout animations of the card's indicator */}\n \n {dock.animatingIndexes.includes(parseInt(id)) ? (\n \n \n \n ) : null}\n \n
\n )\n}\n\n// Divider component for the dock\nfunction DockDivider() {\n return (\n \n \n \n )\n}\n\ntype UseWindowResizeCallback = (width: number, height: number) => void\n\n// Custom hook to handle window resize events and invoke a callback with the new dimensions\nfunction useWindowResize(callback: UseWindowResizeCallback) {\n // Create a stable callback reference to ensure the latest callback is always used\n const callbackRef = useCallbackRef(callback)\n\n useEffect(() => {\n // Function to handle window resize and call the provided callback with updated dimensions\n const handleResize = () => {\n callbackRef(window.innerWidth, window.innerHeight)\n }\n\n // Initial call to handleResize to capture the current window size\n handleResize()\n // Adding event listener for window resize events\n window.addEventListener(\"resize\", handleResize)\n\n // Cleanup function to remove the event listener when the component unmounts or dependencies change\n return () => {\n window.removeEventListener(\"resize\", handleResize)\n }\n }, [callbackRef]) // Dependency array includes the stable callback reference\n}\n\n// Custom hook to create a stable callback reference\nfunction useCallbackRef any>(callback: T): T {\n // Use a ref to store the callback\n const callbackRef = useRef(callback)\n\n // Update the ref with the latest callback whenever it changes\n useEffect(() => {\n callbackRef.current = callback\n })\n\n // Return a memoized version of the callback that always uses the latest callback stored in the ref\n return useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, [])\n}\n\n// Interface for mouse position options\ninterface MousePositionOptions {\n onChange?: (position: { value: { x: number; y: number } }) => void\n}\n\n// Custom hook to track mouse position and animate values accordingly\nexport function useMousePosition(\n options: MousePositionOptions = {}, // Options to customize behavior, including an onChange callback\n deps: readonly any[] = [] // Dependencies array to determine when the effect should re-run\n) {\n const { onChange } = options // Destructure onChange from options for use in the effect\n\n // Create motion values for x and y coordinates, initialized to 0\n const x = useMotionValue(0)\n const y = useMotionValue(0)\n\n useEffect(() => {\n // Function to handle mouse move events, animating the x and y motion values to the current mouse coordinates\n const handleMouseMove = (event: MouseEvent) => {\n animate(x, event.clientX)\n animate(y, event.clientY)\n }\n\n // Function to handle changes in the motion values, calling the onChange callback if it exists\n const handleChange = () => {\n if (onChange) {\n onChange({ value: { x: x.get(), y: y.get() } })\n }\n }\n\n // Subscribe to changes in the x and y motion values\n const unsubscribeX = x.on(\"change\", handleChange)\n const unsubscribeY = y.on(\"change\", handleChange)\n\n // Add event listener for mouse move events\n window.addEventListener(\"mousemove\", handleMouseMove)\n\n // Cleanup function to remove event listener and unsubscribe from motion value changes\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n unsubscribeX()\n unsubscribeY()\n }\n }, [x, y, onChange, ...deps]) // Dependency array includes x, y, onChange, and any additional dependencies\n\n // Memoize and return the motion values for x and y coordinates\n return useMemo(\n () => ({\n x, // Motion value for x coordinate\n y, // Motion value for y coordinate\n }),\n [x, y] // Dependencies for the memoized return value\n )\n}\n\nexport { Dock, DockCard, DockCardInner, DockDivider, useDock }\nexport default Dock\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/dynamic-island.json b/apps/www/public/registry/styles/default/dynamic-island.json index b7b0b67..69db7b2 100644 --- a/apps/www/public/registry/styles/default/dynamic-island.json +++ b/apps/www/public/registry/styles/default/dynamic-island.json @@ -6,7 +6,7 @@ "files": [ { "name": "dynamic-island.tsx", - "content": "\"use client\"\n\nimport React, {\n ReactNode,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useReducer,\n useRef,\n useState,\n} from \"react\"\nimport { AnimatePresence, motion, useWillChange } from \"framer-motion\"\n\nconst stiffness = 400\nconst damping = 30\nconst MIN_WIDTH = 691\nconst MAX_HEIGHT_MOBILE_ULTRA = 400\nconst MAX_HEIGHT_MOBILE_MASSIVE = 700\n\nconst min = (a: number, b: number) => (a < b ? a : b)\n\nexport type SizePresets =\n | \"reset\"\n | \"empty\"\n | \"default\"\n | \"compact\"\n | \"compactLong\"\n | \"large\"\n | \"long\"\n | \"minimalLeading\"\n | \"minimalTrailing\"\n | \"compactMedium\"\n | \"medium\"\n | \"tall\"\n | \"ultra\"\n | \"massive\"\n\nconst SIZE_PRESETS = {\n RESET: \"reset\",\n EMPTY: \"empty\",\n DEFAULT: \"default\",\n COMPACT: \"compact\",\n COMPACT_LONG: \"compactLong\",\n LARGE: \"large\",\n LONG: \"long\",\n MINIMAL_LEADING: \"minimalLeading\",\n MINIMAL_TRAILING: \"minimalTrailing\",\n COMPACT_MEDIUM: \"compactMedium\",\n MEDIUM: \"medium\",\n TALL: \"tall\",\n ULTRA: \"ultra\",\n MASSIVE: \"massive\",\n} as const\n\ntype Preset = {\n width: number\n height?: number\n aspectRatio: number\n borderRadius: number\n}\n\nconst DynamicIslandSizePresets: Record = {\n [SIZE_PRESETS.RESET]: {\n width: 150,\n aspectRatio: 1,\n borderRadius: 20,\n },\n [SIZE_PRESETS.EMPTY]: {\n width: 0,\n aspectRatio: 0,\n borderRadius: 0,\n },\n [SIZE_PRESETS.DEFAULT]: {\n width: 150,\n aspectRatio: 44 / 150,\n borderRadius: 46,\n },\n [SIZE_PRESETS.MINIMAL_LEADING]: {\n width: 52.33,\n aspectRatio: 44 / 52.33,\n borderRadius: 22,\n },\n [SIZE_PRESETS.MINIMAL_TRAILING]: {\n width: 52.33,\n aspectRatio: 44 / 52.33,\n borderRadius: 22,\n },\n [SIZE_PRESETS.COMPACT]: {\n width: 235,\n aspectRatio: 44 / 235,\n borderRadius: 46,\n },\n [SIZE_PRESETS.COMPACT_LONG]: {\n width: 300,\n aspectRatio: 44 / 235,\n borderRadius: 46,\n },\n [SIZE_PRESETS.COMPACT_MEDIUM]: {\n width: 351,\n aspectRatio: 64 / 371,\n borderRadius: 44,\n },\n [SIZE_PRESETS.LONG]: {\n width: 371,\n aspectRatio: 84 / 371,\n borderRadius: 42,\n },\n [SIZE_PRESETS.MEDIUM]: {\n width: 371,\n aspectRatio: 210 / 371,\n borderRadius: 22,\n },\n [SIZE_PRESETS.LARGE]: {\n width: 371,\n aspectRatio: 84 / 371,\n borderRadius: 42,\n },\n [SIZE_PRESETS.TALL]: {\n width: 371,\n aspectRatio: 210 / 371,\n borderRadius: 42,\n },\n [SIZE_PRESETS.ULTRA]: {\n width: 630,\n aspectRatio: 630 / 800,\n borderRadius: 42,\n },\n [SIZE_PRESETS.MASSIVE]: {\n width: 891,\n height: 1900,\n aspectRatio: 891 / 891,\n borderRadius: 42,\n },\n}\n\ntype BlobStateType = {\n size: SizePresets\n previousSize: SizePresets | undefined\n animationQueue: Array<{ size: SizePresets; delay: number }>\n isAnimating: boolean\n}\n\ntype BlobAction =\n | { type: \"SET_SIZE\"; newSize: SizePresets }\n | { type: \"INITIALIZE\"; firstState: SizePresets }\n | {\n type: \"SCHEDULE_ANIMATION\"\n animationSteps: Array<{ size: SizePresets; delay: number }>\n }\n | { type: \"ANIMATION_END\" }\n\ntype BlobContextType = {\n state: BlobStateType\n dispatch: React.Dispatch\n setSize: (size: SizePresets) => void\n scheduleAnimation: (\n animationSteps: Array<{ size: SizePresets; delay: number }>\n ) => void\n presets: Record\n}\n\nconst BlobContext = createContext(undefined)\n\nconst blobReducer = (\n state: BlobStateType,\n action: BlobAction\n): BlobStateType => {\n switch (action.type) {\n case \"SET_SIZE\":\n return {\n ...state,\n size: action.newSize,\n previousSize: state.size,\n isAnimating: false, // Only set isAnimating to true if there are more steps\n }\n case \"SCHEDULE_ANIMATION\":\n return {\n ...state,\n animationQueue: action.animationSteps,\n isAnimating: action.animationSteps.length > 0,\n }\n case \"INITIALIZE\":\n return {\n ...state,\n size: action.firstState,\n previousSize: SIZE_PRESETS.EMPTY,\n isAnimating: false,\n }\n case \"ANIMATION_END\":\n return {\n ...state,\n isAnimating: false,\n }\n default:\n return state\n }\n}\n\ninterface DynamicIslandProviderProps {\n children: React.ReactNode\n initialSize?: SizePresets\n initialAnimation?: Array<{ size: SizePresets; delay: number }>\n}\n\nconst DynamicIslandProvider: React.FC = ({\n children,\n initialSize = SIZE_PRESETS.DEFAULT,\n initialAnimation = [],\n}) => {\n const initialState: BlobStateType = {\n size: initialSize,\n previousSize: SIZE_PRESETS.EMPTY,\n animationQueue: initialAnimation,\n isAnimating: initialAnimation.length > 0,\n }\n\n const [state, dispatch] = useReducer(blobReducer, initialState)\n\n useEffect(() => {\n const processQueue = async () => {\n for (const step of state.animationQueue) {\n await new Promise((resolve) => setTimeout(resolve, step.delay))\n dispatch({ type: \"SET_SIZE\", newSize: step.size })\n }\n dispatch({ type: \"ANIMATION_END\" })\n }\n\n if (state.animationQueue.length > 0) {\n processQueue()\n }\n }, [state.animationQueue])\n\n const setSize = useCallback(\n (newSize: SizePresets) => {\n if (state.previousSize !== newSize && newSize !== state.size) {\n dispatch({ type: \"SET_SIZE\", newSize })\n }\n },\n [state.previousSize, state.size, dispatch]\n )\n\n const scheduleAnimation = useCallback(\n (animationSteps: Array<{ size: SizePresets; delay: number }>) => {\n dispatch({ type: \"SCHEDULE_ANIMATION\", animationSteps })\n },\n [dispatch]\n )\n\n const contextValue = {\n state,\n dispatch,\n setSize,\n scheduleAnimation,\n presets: DynamicIslandSizePresets,\n }\n\n return (\n {children}\n )\n}\n\nconst useDynamicIslandSize = () => {\n const context = useContext(BlobContext)\n if (!context) {\n throw new Error(\n \"useDynamicIslandSize must be used within a DynamicIslandProvider\"\n )\n }\n return context\n}\n\nconst useScheduledAnimations = (\n animations: Array<{ size: SizePresets; delay: number }>\n) => {\n const { scheduleAnimation } = useDynamicIslandSize()\n const animationsRef = useRef(animations)\n\n useEffect(() => {\n scheduleAnimation(animationsRef.current)\n }, [scheduleAnimation])\n}\n\nconst DynamicIslandContainer = ({ children }: { children: ReactNode }) => {\n return (\n
\n {children}\n
\n )\n}\n\nconst DynamicIsland = ({\n children,\n id,\n ...props\n}: {\n children: ReactNode\n id: string\n}) => {\n const willChange = useWillChange()\n const [screenSize, setScreenSize] = useState(\"desktop\")\n\n useEffect(() => {\n const handleResize = () => {\n if (window.innerWidth <= 640) {\n setScreenSize(\"mobile\")\n } else if (window.innerWidth <= 1024) {\n setScreenSize(\"tablet\")\n } else {\n setScreenSize(\"desktop\")\n }\n }\n\n handleResize()\n window.addEventListener(\"resize\", handleResize)\n return () => window.removeEventListener(\"resize\", handleResize)\n }, [])\n\n return (\n \n \n {children}\n \n \n )\n}\n\nconst calculateDimensions = (\n size: SizePresets,\n screenSize: string,\n currentSize: Preset\n): { width: string; height: number } => {\n const isMassiveOnMobile = size === \"massive\" && screenSize === \"mobile\"\n const isUltraOnMobile = size === \"ultra\" && screenSize === \"mobile\"\n\n if (isMassiveOnMobile) {\n return { width: \"350px\", height: MAX_HEIGHT_MOBILE_MASSIVE }\n }\n\n if (isUltraOnMobile) {\n return { width: \"350px\", height: MAX_HEIGHT_MOBILE_ULTRA }\n }\n\n const width = min(currentSize.width, MIN_WIDTH)\n return { width: `${width}px`, height: currentSize.aspectRatio * width }\n}\n\nconst DynamicIslandContent = ({\n children,\n id,\n willChange,\n screenSize,\n ...props\n}: {\n children: React.ReactNode\n id: string\n willChange: any\n screenSize: string\n [key: string]: any\n}) => {\n const { state, presets } = useDynamicIslandSize()\n const currentSize = presets[state.size]\n\n const dimensions = calculateDimensions(state.size, screenSize, currentSize)\n\n return (\n \n {children}\n \n )\n}\n\ntype DynamicContainerProps = {\n className?: string\n children?: React.ReactNode\n}\n\nconst DynamicContainer = ({ className, children }: DynamicContainerProps) => {\n const willChange = useWillChange()\n const { state } = useDynamicIslandSize()\n const { size, previousSize } = state\n\n const isSizeChanged = size !== previousSize\n\n const initialState = {\n opacity: size === previousSize ? 1 : 0,\n scale: size === previousSize ? 1 : 0.9,\n y: size === previousSize ? 0 : 5,\n }\n\n const animateState = {\n opacity: 1,\n scale: 1,\n y: 0,\n transition: {\n type: \"spring\",\n stiffness,\n damping,\n duration: isSizeChanged ? 0.5 : 0.8,\n },\n }\n\n return (\n \n {children}\n \n )\n}\n\ntype DynamicChildrenProps = {\n className?: string\n children?: React.ReactNode\n}\n\nconst DynamicDiv = ({ className, children }: DynamicChildrenProps) => {\n const { state } = useDynamicIslandSize()\n const { size, previousSize } = state\n const willChange = useWillChange()\n\n return (\n \n {children}\n \n )\n}\n\ntype MotionProps = {\n className: string\n children: React.ReactNode\n}\n\nconst DynamicTitle = ({ className, children }: MotionProps) => {\n const { state } = useDynamicIslandSize()\n const { size, previousSize } = state\n const willChange = useWillChange()\n\n return (\n \n {children}\n \n )\n}\n\nconst DynamicDescription = ({ className, children }: MotionProps) => {\n const { state } = useDynamicIslandSize()\n const { size, previousSize } = state\n const willChange = useWillChange()\n\n return (\n \n {children}\n \n )\n}\n\nexport {\n DynamicContainer,\n DynamicTitle,\n DynamicDescription,\n DynamicIsland,\n SIZE_PRESETS,\n stiffness,\n DynamicDiv,\n damping,\n DynamicIslandSizePresets,\n BlobContext,\n useDynamicIslandSize,\n useScheduledAnimations,\n DynamicIslandProvider,\n}\n\nexport default DynamicIsland\n" + "content": "\"use client\"\n\nimport React, {\n ReactNode,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useReducer,\n useRef,\n useState,\n} from \"react\"\nimport { AnimatePresence, motion, useWillChange } from \"motion/react\"\n\nconst stiffness = 400\nconst damping = 30\nconst MIN_WIDTH = 691\nconst MAX_HEIGHT_MOBILE_ULTRA = 400\nconst MAX_HEIGHT_MOBILE_MASSIVE = 700\n\nconst min = (a: number, b: number) => (a < b ? a : b)\n\nexport type SizePresets =\n | \"reset\"\n | \"empty\"\n | \"default\"\n | \"compact\"\n | \"compactLong\"\n | \"large\"\n | \"long\"\n | \"minimalLeading\"\n | \"minimalTrailing\"\n | \"compactMedium\"\n | \"medium\"\n | \"tall\"\n | \"ultra\"\n | \"massive\"\n\nconst SIZE_PRESETS = {\n RESET: \"reset\",\n EMPTY: \"empty\",\n DEFAULT: \"default\",\n COMPACT: \"compact\",\n COMPACT_LONG: \"compactLong\",\n LARGE: \"large\",\n LONG: \"long\",\n MINIMAL_LEADING: \"minimalLeading\",\n MINIMAL_TRAILING: \"minimalTrailing\",\n COMPACT_MEDIUM: \"compactMedium\",\n MEDIUM: \"medium\",\n TALL: \"tall\",\n ULTRA: \"ultra\",\n MASSIVE: \"massive\",\n} as const\n\ntype Preset = {\n width: number\n height?: number\n aspectRatio: number\n borderRadius: number\n}\n\nconst DynamicIslandSizePresets: Record = {\n [SIZE_PRESETS.RESET]: {\n width: 150,\n aspectRatio: 1,\n borderRadius: 20,\n },\n [SIZE_PRESETS.EMPTY]: {\n width: 0,\n aspectRatio: 0,\n borderRadius: 0,\n },\n [SIZE_PRESETS.DEFAULT]: {\n width: 150,\n aspectRatio: 44 / 150,\n borderRadius: 46,\n },\n [SIZE_PRESETS.MINIMAL_LEADING]: {\n width: 52.33,\n aspectRatio: 44 / 52.33,\n borderRadius: 22,\n },\n [SIZE_PRESETS.MINIMAL_TRAILING]: {\n width: 52.33,\n aspectRatio: 44 / 52.33,\n borderRadius: 22,\n },\n [SIZE_PRESETS.COMPACT]: {\n width: 235,\n aspectRatio: 44 / 235,\n borderRadius: 46,\n },\n [SIZE_PRESETS.COMPACT_LONG]: {\n width: 300,\n aspectRatio: 44 / 235,\n borderRadius: 46,\n },\n [SIZE_PRESETS.COMPACT_MEDIUM]: {\n width: 351,\n aspectRatio: 64 / 371,\n borderRadius: 44,\n },\n [SIZE_PRESETS.LONG]: {\n width: 371,\n aspectRatio: 84 / 371,\n borderRadius: 42,\n },\n [SIZE_PRESETS.MEDIUM]: {\n width: 371,\n aspectRatio: 210 / 371,\n borderRadius: 22,\n },\n [SIZE_PRESETS.LARGE]: {\n width: 371,\n aspectRatio: 84 / 371,\n borderRadius: 42,\n },\n [SIZE_PRESETS.TALL]: {\n width: 371,\n aspectRatio: 210 / 371,\n borderRadius: 42,\n },\n [SIZE_PRESETS.ULTRA]: {\n width: 630,\n aspectRatio: 630 / 800,\n borderRadius: 42,\n },\n [SIZE_PRESETS.MASSIVE]: {\n width: 891,\n height: 1900,\n aspectRatio: 891 / 891,\n borderRadius: 42,\n },\n}\n\ntype BlobStateType = {\n size: SizePresets\n previousSize: SizePresets | undefined\n animationQueue: Array<{ size: SizePresets; delay: number }>\n isAnimating: boolean\n}\n\ntype BlobAction =\n | { type: \"SET_SIZE\"; newSize: SizePresets }\n | { type: \"INITIALIZE\"; firstState: SizePresets }\n | {\n type: \"SCHEDULE_ANIMATION\"\n animationSteps: Array<{ size: SizePresets; delay: number }>\n }\n | { type: \"ANIMATION_END\" }\n\ntype BlobContextType = {\n state: BlobStateType\n dispatch: React.Dispatch\n setSize: (size: SizePresets) => void\n scheduleAnimation: (\n animationSteps: Array<{ size: SizePresets; delay: number }>\n ) => void\n presets: Record\n}\n\nconst BlobContext = createContext(undefined)\n\nconst blobReducer = (\n state: BlobStateType,\n action: BlobAction\n): BlobStateType => {\n switch (action.type) {\n case \"SET_SIZE\":\n return {\n ...state,\n size: action.newSize,\n previousSize: state.size,\n isAnimating: false, // Only set isAnimating to true if there are more steps\n }\n case \"SCHEDULE_ANIMATION\":\n return {\n ...state,\n animationQueue: action.animationSteps,\n isAnimating: action.animationSteps.length > 0,\n }\n case \"INITIALIZE\":\n return {\n ...state,\n size: action.firstState,\n previousSize: SIZE_PRESETS.EMPTY,\n isAnimating: false,\n }\n case \"ANIMATION_END\":\n return {\n ...state,\n isAnimating: false,\n }\n default:\n return state\n }\n}\n\ninterface DynamicIslandProviderProps {\n children: React.ReactNode\n initialSize?: SizePresets\n initialAnimation?: Array<{ size: SizePresets; delay: number }>\n}\n\nconst DynamicIslandProvider: React.FC = ({\n children,\n initialSize = SIZE_PRESETS.DEFAULT,\n initialAnimation = [],\n}) => {\n const initialState: BlobStateType = {\n size: initialSize,\n previousSize: SIZE_PRESETS.EMPTY,\n animationQueue: initialAnimation,\n isAnimating: initialAnimation.length > 0,\n }\n\n const [state, dispatch] = useReducer(blobReducer, initialState)\n\n useEffect(() => {\n const processQueue = async () => {\n for (const step of state.animationQueue) {\n await new Promise((resolve) => setTimeout(resolve, step.delay))\n dispatch({ type: \"SET_SIZE\", newSize: step.size })\n }\n dispatch({ type: \"ANIMATION_END\" })\n }\n\n if (state.animationQueue.length > 0) {\n processQueue()\n }\n }, [state.animationQueue])\n\n const setSize = useCallback(\n (newSize: SizePresets) => {\n if (state.previousSize !== newSize && newSize !== state.size) {\n dispatch({ type: \"SET_SIZE\", newSize })\n }\n },\n [state.previousSize, state.size, dispatch]\n )\n\n const scheduleAnimation = useCallback(\n (animationSteps: Array<{ size: SizePresets; delay: number }>) => {\n dispatch({ type: \"SCHEDULE_ANIMATION\", animationSteps })\n },\n [dispatch]\n )\n\n const contextValue = {\n state,\n dispatch,\n setSize,\n scheduleAnimation,\n presets: DynamicIslandSizePresets,\n }\n\n return (\n {children}\n )\n}\n\nconst useDynamicIslandSize = () => {\n const context = useContext(BlobContext)\n if (!context) {\n throw new Error(\n \"useDynamicIslandSize must be used within a DynamicIslandProvider\"\n )\n }\n return context\n}\n\nconst useScheduledAnimations = (\n animations: Array<{ size: SizePresets; delay: number }>\n) => {\n const { scheduleAnimation } = useDynamicIslandSize()\n const animationsRef = useRef(animations)\n\n useEffect(() => {\n scheduleAnimation(animationsRef.current)\n }, [scheduleAnimation])\n}\n\nconst DynamicIslandContainer = ({ children }: { children: ReactNode }) => {\n return (\n
\n {children}\n
\n )\n}\n\nconst DynamicIsland = ({\n children,\n id,\n ...props\n}: {\n children: ReactNode\n id: string\n}) => {\n const willChange = useWillChange()\n const [screenSize, setScreenSize] = useState(\"desktop\")\n\n useEffect(() => {\n const handleResize = () => {\n if (window.innerWidth <= 640) {\n setScreenSize(\"mobile\")\n } else if (window.innerWidth <= 1024) {\n setScreenSize(\"tablet\")\n } else {\n setScreenSize(\"desktop\")\n }\n }\n\n handleResize()\n window.addEventListener(\"resize\", handleResize)\n return () => window.removeEventListener(\"resize\", handleResize)\n }, [])\n\n return (\n \n \n {children}\n \n \n )\n}\n\nconst calculateDimensions = (\n size: SizePresets,\n screenSize: string,\n currentSize: Preset\n): { width: string; height: number } => {\n const isMassiveOnMobile = size === \"massive\" && screenSize === \"mobile\"\n const isUltraOnMobile = size === \"ultra\" && screenSize === \"mobile\"\n\n if (isMassiveOnMobile) {\n return { width: \"350px\", height: MAX_HEIGHT_MOBILE_MASSIVE }\n }\n\n if (isUltraOnMobile) {\n return { width: \"350px\", height: MAX_HEIGHT_MOBILE_ULTRA }\n }\n\n const width = min(currentSize.width, MIN_WIDTH)\n return { width: `${width}px`, height: currentSize.aspectRatio * width }\n}\n\nconst DynamicIslandContent = ({\n children,\n id,\n willChange,\n screenSize,\n ...props\n}: {\n children: React.ReactNode\n id: string\n willChange: any\n screenSize: string\n [key: string]: any\n}) => {\n const { state, presets } = useDynamicIslandSize()\n const currentSize = presets[state.size]\n\n const dimensions = calculateDimensions(state.size, screenSize, currentSize)\n\n return (\n \n {children}\n \n )\n}\n\ntype DynamicContainerProps = {\n className?: string\n children?: React.ReactNode\n}\n\nconst DynamicContainer = ({ className, children }: DynamicContainerProps) => {\n const willChange = useWillChange()\n const { state } = useDynamicIslandSize()\n const { size, previousSize } = state\n\n const isSizeChanged = size !== previousSize\n\n const initialState = {\n opacity: size === previousSize ? 1 : 0,\n scale: size === previousSize ? 1 : 0.9,\n y: size === previousSize ? 0 : 5,\n }\n\n const animateState = {\n opacity: 1,\n scale: 1,\n y: 0,\n transition: {\n type: \"spring\",\n stiffness,\n damping,\n duration: isSizeChanged ? 0.5 : 0.8,\n },\n }\n\n return (\n \n {children}\n \n )\n}\n\ntype DynamicChildrenProps = {\n className?: string\n children?: React.ReactNode\n}\n\nconst DynamicDiv = ({ className, children }: DynamicChildrenProps) => {\n const { state } = useDynamicIslandSize()\n const { size, previousSize } = state\n const willChange = useWillChange()\n\n return (\n \n {children}\n \n )\n}\n\ntype MotionProps = {\n className: string\n children: React.ReactNode\n}\n\nconst DynamicTitle = ({ className, children }: MotionProps) => {\n const { state } = useDynamicIslandSize()\n const { size, previousSize } = state\n const willChange = useWillChange()\n\n return (\n \n {children}\n \n )\n}\n\nconst DynamicDescription = ({ className, children }: MotionProps) => {\n const { state } = useDynamicIslandSize()\n const { size, previousSize } = state\n const willChange = useWillChange()\n\n return (\n \n {children}\n \n )\n}\n\nexport {\n DynamicContainer,\n DynamicTitle,\n DynamicDescription,\n DynamicIsland,\n SIZE_PRESETS,\n stiffness,\n DynamicDiv,\n damping,\n DynamicIslandSizePresets,\n BlobContext,\n useDynamicIslandSize,\n useScheduledAnimations,\n DynamicIslandProvider,\n}\n\nexport default DynamicIsland\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/expandable.json b/apps/www/public/registry/styles/default/expandable.json index 96ff6d7..04863b0 100644 --- a/apps/www/public/registry/styles/default/expandable.json +++ b/apps/www/public/registry/styles/default/expandable.json @@ -6,7 +6,7 @@ "files": [ { "name": "expandable.tsx", - "content": "\"use client\"\n\nimport React, {\n ReactNode,\n createContext,\n useContext,\n useEffect,\n useState,\n} from \"react\"\nimport {\n AnimatePresence,\n HTMLMotionProps,\n motion,\n useMotionValue,\n useSpring,\n} from \"framer-motion\"\nimport useMeasure from \"react-use-measure\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst springConfig = { stiffness: 200, damping: 20, bounce: 0.2 }\n\ninterface ExpandableContextType {\n isExpanded: boolean // Indicates whether the component is expanded\n toggleExpand: () => void // Function to toggle the expanded state\n expandDirection: \"vertical\" | \"horizontal\" | \"both\" // Direction of expansion\n expandBehavior: \"replace\" | \"push\" // How the expansion affects surrounding content\n transitionDuration: number // Duration of the expansion/collapse animation\n easeType: string // Easing function for the animation\n initialDelay: number // Delay before the animation starts\n onExpandEnd?: () => void // Callback function when expansion ends\n onCollapseEnd?: () => void // Callback function when collapse ends\n}\n\n// Create a context with default values\nconst ExpandableContext = createContext({\n isExpanded: false,\n toggleExpand: () => {},\n expandDirection: \"vertical\", // 'vertical' | 'horizontal' | 'both' // Direction of expansion\n expandBehavior: \"replace\", // How the expansion affects surrounding content\n transitionDuration: 0.3, // Duration of the expansion/collapse animation\n easeType: \"easeInOut\", // Easing function for the animation\n initialDelay: 0,\n})\n\n// Custom hook to use the ExpandableContext\nconst useExpandable = () => useContext(ExpandableContext)\n\ntype ExpandablePropsBase = Omit, \"children\">\n\ninterface ExpandableProps extends ExpandablePropsBase {\n children: ReactNode | ((props: { isExpanded: boolean }) => ReactNode)\n expanded?: boolean\n onToggle?: () => void\n transitionDuration?: number\n easeType?: string\n expandDirection?: \"vertical\" | \"horizontal\" | \"both\"\n expandBehavior?: \"replace\" | \"push\"\n initialDelay?: number\n onExpandStart?: () => void\n onExpandEnd?: () => void\n onCollapseStart?: () => void\n onCollapseEnd?: () => void\n}\n// ROOT Expand component\nconst Expandable = React.forwardRef(\n (\n {\n children,\n expanded,\n onToggle,\n transitionDuration = 0.3,\n easeType = \"easeInOut\",\n expandDirection = \"vertical\",\n expandBehavior = \"replace\",\n initialDelay = 0,\n onExpandStart,\n onExpandEnd,\n onCollapseStart,\n onCollapseEnd,\n ...props\n },\n ref\n ) => {\n // Internal state for expansion when the component is uncontrolled\n const [isExpandedInternal, setIsExpandedInternal] = useState(false)\n\n // Use the provided expanded prop if available, otherwise use internal state\n const isExpanded = expanded !== undefined ? expanded : isExpandedInternal\n\n // Use the provided onToggle function if available, otherwise use internal toggle function\n const toggleExpand =\n onToggle || (() => setIsExpandedInternal((prev) => !prev))\n\n // Effect to call onExpandStart or onCollapseStart when isExpanded changes\n useEffect(() => {\n if (isExpanded) {\n onExpandStart?.()\n } else {\n onCollapseStart?.()\n }\n }, [isExpanded, onExpandStart, onCollapseStart])\n\n // Create the context value to be provided to child components\n const contextValue: ExpandableContextType = {\n isExpanded,\n toggleExpand,\n expandDirection,\n expandBehavior,\n transitionDuration,\n easeType,\n initialDelay,\n onExpandEnd,\n onCollapseEnd,\n }\n\n return (\n \n \n {/* Render children as a function if provided, otherwise render as is */}\n {typeof children === \"function\" ? children({ isExpanded }) : children}\n \n \n )\n }\n)\n\n// Predefined animation presets\nconst ANIMATION_PRESETS = {\n fade: {\n initial: { opacity: 0 },\n animate: { opacity: 1 },\n exit: { opacity: 0 },\n transition: { duration: 0.3 },\n },\n \"slide-up\": {\n initial: { opacity: 0, y: 20 },\n animate: { opacity: 1, y: 0 },\n exit: { opacity: 0, y: 20 },\n transition: { duration: 0.3 },\n },\n \"slide-down\": {\n initial: { opacity: 0, y: -20 },\n animate: { opacity: 1, y: 0 },\n exit: { opacity: 0, y: -20 },\n transition: { duration: 0.3 },\n },\n \"slide-left\": {\n initial: { opacity: 0, x: 20 },\n animate: { opacity: 1, x: 0 },\n exit: { opacity: 0, x: 20 },\n transition: { duration: 0.3 },\n },\n \"slide-right\": {\n initial: { opacity: 0, x: -20 },\n animate: { opacity: 1, x: 0 },\n exit: { opacity: 0, x: -20 },\n transition: { duration: 0.3 },\n },\n scale: {\n initial: { opacity: 0, scale: 0.8 },\n animate: { opacity: 1, scale: 1 },\n exit: { opacity: 0, scale: 0.8 },\n transition: { duration: 0.3 },\n },\n rotate: {\n initial: { opacity: 0, rotate: -10 },\n animate: { opacity: 1, rotate: 0 },\n exit: { opacity: 0, rotate: -10 },\n transition: { duration: 0.3 },\n },\n \"blur-sm\": {\n initial: { opacity: 0, filter: \"blur(4px)\" },\n animate: { opacity: 1, filter: \"blur(0px)\" },\n exit: { opacity: 0, filter: \"blur(4px)\" },\n transition: { duration: 0.3 },\n },\n \"blur-md\": {\n initial: { opacity: 0, filter: \"blur(8px)\" },\n animate: { opacity: 1, filter: \"blur(0px)\" },\n exit: { opacity: 0, filter: \"blur(8px)\" },\n transition: { duration: 0.3 },\n },\n \"blur-lg\": {\n initial: { opacity: 0, filter: \"blur(16px)\" },\n animate: { opacity: 1, filter: \"blur(0px)\" },\n exit: { opacity: 0, filter: \"blur(16px)\" },\n transition: { duration: 0.3 },\n },\n}\n\n// Props for defining custom animations\ninterface AnimationProps {\n initial?: object // Initial state of the animation\n animate?: object // Final state of the animation\n exit?: object // State when component is removed\n transition?: object // Transition properties\n}\n\n// Wrap this around items in the card that you want to be hidden then animated in on expansion\nconst ExpandableContent = React.forwardRef<\n HTMLDivElement,\n Omit, \"ref\"> & {\n preset?: keyof typeof ANIMATION_PRESETS\n animateIn?: AnimationProps\n animateOut?: AnimationProps\n stagger?: boolean\n staggerChildren?: number\n keepMounted?: boolean\n }\n>(\n (\n {\n children,\n preset,\n animateIn,\n animateOut,\n stagger = false,\n staggerChildren = 0.1,\n keepMounted = false,\n ...props\n },\n ref\n ) => {\n const { isExpanded, transitionDuration, easeType } = useExpandable()\n // useMeasure is used to measure the height of the content\n const [measureRef, { height: measuredHeight }] = useMeasure()\n // useMotionValue creates a value that can be animated smoothly\n const animatedHeight = useMotionValue(0)\n // useSpring applies a spring animation to the height value\n const smoothHeight = useSpring(animatedHeight, springConfig)\n\n useEffect(() => {\n // Animate the height based on whether the content is expanded or collapsed\n if (isExpanded) {\n animatedHeight.set(measuredHeight)\n } else {\n animatedHeight.set(0)\n }\n }, [isExpanded, measuredHeight, animatedHeight])\n\n const presetAnimation = preset ? ANIMATION_PRESETS[preset] : {}\n const combinedAnimateIn = {\n ...presetAnimation,\n ...animateIn,\n }\n const combinedAnimateOut = animateOut || combinedAnimateIn\n\n return (\n // This motion.div animates the height of the content\n \n {/* AnimatePresence handles the entering and exiting of components */}\n \n {(isExpanded || keepMounted) && (\n // This motion.div handles the animation of the content itself\n \n {stagger ? (\n // If stagger is true, we apply a staggered animation to the children\n \n {React.Children.map(\n children as React.ReactNode,\n (child, index) => (\n \n {child}\n \n )\n )}\n \n ) : (\n children\n )}\n \n )}\n \n \n )\n }\n)\n\ninterface ExpandableCardProps {\n children: ReactNode\n className?: string\n collapsedSize?: { width?: number; height?: number } // Size when collapsed\n expandedSize?: { width?: number; height?: number } // Size when expanded\n hoverToExpand?: boolean // Whether to expand on hover\n expandDelay?: number // Delay before expanding\n collapseDelay?: number // Delay before collapsing\n}\n\nconst ExpandableCard = React.forwardRef(\n (\n {\n children,\n className = \"\",\n collapsedSize = { width: 320, height: 211 },\n expandedSize = { width: 480, height: undefined },\n hoverToExpand = false,\n expandDelay = 0,\n collapseDelay = 0,\n ...props\n },\n ref\n ) => {\n // Get the expansion state and toggle function from the ExpandableContext\n const { isExpanded, toggleExpand, expandDirection } = useExpandable()\n\n // Use useMeasure hook to get the dimensions of the content\n const [measureRef, { width, height }] = useMeasure()\n\n // Create motion values for width and height\n const animatedWidth = useMotionValue(collapsedSize.width || 0)\n const animatedHeight = useMotionValue(collapsedSize.height || 0)\n\n // Apply spring animation to the motion values\n const smoothWidth = useSpring(animatedWidth, springConfig)\n const smoothHeight = useSpring(animatedHeight, springConfig)\n\n // Effect to update the animated dimensions when expansion state changes\n useEffect(() => {\n if (isExpanded) {\n animatedWidth.set(expandedSize.width || width)\n animatedHeight.set(expandedSize.height || height)\n } else {\n animatedWidth.set(collapsedSize.width || width)\n animatedHeight.set(collapsedSize.height || height)\n }\n }, [\n isExpanded,\n collapsedSize,\n expandedSize,\n width,\n height,\n animatedWidth,\n animatedHeight,\n ])\n\n // Handler for hover start event\n const handleHover = () => {\n if (hoverToExpand && !isExpanded) {\n setTimeout(toggleExpand, expandDelay)\n }\n }\n\n // Handler for hover end event\n const handleHoverEnd = () => {\n if (hoverToExpand && isExpanded) {\n setTimeout(toggleExpand, collapseDelay)\n }\n }\n\n return (\n \n \n {/* Nested divs purely for styling and layout (the shadow ring around the card) */}\n
\n
\n
\n {/* Ref for measuring content dimensions (so we can let framer know to animate into the dimensions) */}\n
\n {children}\n
\n
\n
\n
\n \n \n )\n }\n)\n\nExpandableCard.displayName = \"ExpandableCard\"\n\n// I'm telling you we just have to expand 🤌💵\nconst ExpandableTrigger = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ children, ...props }, ref) => {\n const { toggleExpand } = useExpandable()\n return (\n
\n {children}\n
\n )\n})\n\nExpandableTrigger.displayName = \"ExpandableTrigger\"\n\nconst ExpandableCardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, children, ...props }, ref) => (\n \n \n {children}\n \n \n))\n\nExpandableCardHeader.displayName = \"ExpandableCardHeader\"\n\nconst ExpandableCardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, children, ...props }, ref) => (\n \n {children}\n \n))\nExpandableCardContent.displayName = \"ExpandableCardContent\"\n\nconst ExpandableCardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nExpandableCardFooter.displayName = \"ExpandableCardFooter\"\n\nexport {\n Expandable,\n useExpandable,\n ExpandableCard,\n ExpandableContent,\n ExpandableContext,\n ExpandableTrigger,\n ExpandableCardHeader,\n ExpandableCardContent,\n ExpandableCardFooter,\n}\n" + "content": "\"use client\"\n\nimport React, {\n ReactNode,\n createContext,\n useContext,\n useEffect,\n useState,\n} from \"react\"\nimport {\n AnimatePresence,\n HTMLMotionProps,\n motion,\n useMotionValue,\n useSpring,\n} from \"motion/react\"\nimport useMeasure from \"react-use-measure\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst springConfig = { stiffness: 200, damping: 20, bounce: 0.2 }\n\ninterface ExpandableContextType {\n isExpanded: boolean // Indicates whether the component is expanded\n toggleExpand: () => void // Function to toggle the expanded state\n expandDirection: \"vertical\" | \"horizontal\" | \"both\" // Direction of expansion\n expandBehavior: \"replace\" | \"push\" // How the expansion affects surrounding content\n transitionDuration: number // Duration of the expansion/collapse animation\n easeType: string // Easing function for the animation\n initialDelay: number // Delay before the animation starts\n onExpandEnd?: () => void // Callback function when expansion ends\n onCollapseEnd?: () => void // Callback function when collapse ends\n}\n\n// Create a context with default values\nconst ExpandableContext = createContext({\n isExpanded: false,\n toggleExpand: () => {},\n expandDirection: \"vertical\", // 'vertical' | 'horizontal' | 'both' // Direction of expansion\n expandBehavior: \"replace\", // How the expansion affects surrounding content\n transitionDuration: 0.3, // Duration of the expansion/collapse animation\n easeType: \"easeInOut\", // Easing function for the animation\n initialDelay: 0,\n})\n\n// Custom hook to use the ExpandableContext\nconst useExpandable = () => useContext(ExpandableContext)\n\ntype ExpandablePropsBase = Omit, \"children\">\n\ninterface ExpandableProps extends ExpandablePropsBase {\n children: ReactNode | ((props: { isExpanded: boolean }) => ReactNode)\n expanded?: boolean\n onToggle?: () => void\n transitionDuration?: number\n easeType?: string\n expandDirection?: \"vertical\" | \"horizontal\" | \"both\"\n expandBehavior?: \"replace\" | \"push\"\n initialDelay?: number\n onExpandStart?: () => void\n onExpandEnd?: () => void\n onCollapseStart?: () => void\n onCollapseEnd?: () => void\n}\n// ROOT Expand component\nconst Expandable = React.forwardRef(\n (\n {\n children,\n expanded,\n onToggle,\n transitionDuration = 0.3,\n easeType = \"easeInOut\",\n expandDirection = \"vertical\",\n expandBehavior = \"replace\",\n initialDelay = 0,\n onExpandStart,\n onExpandEnd,\n onCollapseStart,\n onCollapseEnd,\n ...props\n },\n ref\n ) => {\n // Internal state for expansion when the component is uncontrolled\n const [isExpandedInternal, setIsExpandedInternal] = useState(false)\n\n // Use the provided expanded prop if available, otherwise use internal state\n const isExpanded = expanded !== undefined ? expanded : isExpandedInternal\n\n // Use the provided onToggle function if available, otherwise use internal toggle function\n const toggleExpand =\n onToggle || (() => setIsExpandedInternal((prev) => !prev))\n\n // Effect to call onExpandStart or onCollapseStart when isExpanded changes\n useEffect(() => {\n if (isExpanded) {\n onExpandStart?.()\n } else {\n onCollapseStart?.()\n }\n }, [isExpanded, onExpandStart, onCollapseStart])\n\n // Create the context value to be provided to child components\n const contextValue: ExpandableContextType = {\n isExpanded,\n toggleExpand,\n expandDirection,\n expandBehavior,\n transitionDuration,\n easeType,\n initialDelay,\n onExpandEnd,\n onCollapseEnd,\n }\n\n return (\n \n \n {/* Render children as a function if provided, otherwise render as is */}\n {typeof children === \"function\" ? children({ isExpanded }) : children}\n \n \n )\n }\n)\n\n// Predefined animation presets\nconst ANIMATION_PRESETS = {\n fade: {\n initial: { opacity: 0 },\n animate: { opacity: 1 },\n exit: { opacity: 0 },\n transition: { duration: 0.3 },\n },\n \"slide-up\": {\n initial: { opacity: 0, y: 20 },\n animate: { opacity: 1, y: 0 },\n exit: { opacity: 0, y: 20 },\n transition: { duration: 0.3 },\n },\n \"slide-down\": {\n initial: { opacity: 0, y: -20 },\n animate: { opacity: 1, y: 0 },\n exit: { opacity: 0, y: -20 },\n transition: { duration: 0.3 },\n },\n \"slide-left\": {\n initial: { opacity: 0, x: 20 },\n animate: { opacity: 1, x: 0 },\n exit: { opacity: 0, x: 20 },\n transition: { duration: 0.3 },\n },\n \"slide-right\": {\n initial: { opacity: 0, x: -20 },\n animate: { opacity: 1, x: 0 },\n exit: { opacity: 0, x: -20 },\n transition: { duration: 0.3 },\n },\n scale: {\n initial: { opacity: 0, scale: 0.8 },\n animate: { opacity: 1, scale: 1 },\n exit: { opacity: 0, scale: 0.8 },\n transition: { duration: 0.3 },\n },\n rotate: {\n initial: { opacity: 0, rotate: -10 },\n animate: { opacity: 1, rotate: 0 },\n exit: { opacity: 0, rotate: -10 },\n transition: { duration: 0.3 },\n },\n \"blur-sm\": {\n initial: { opacity: 0, filter: \"blur(4px)\" },\n animate: { opacity: 1, filter: \"blur(0px)\" },\n exit: { opacity: 0, filter: \"blur(4px)\" },\n transition: { duration: 0.3 },\n },\n \"blur-md\": {\n initial: { opacity: 0, filter: \"blur(8px)\" },\n animate: { opacity: 1, filter: \"blur(0px)\" },\n exit: { opacity: 0, filter: \"blur(8px)\" },\n transition: { duration: 0.3 },\n },\n \"blur-lg\": {\n initial: { opacity: 0, filter: \"blur(16px)\" },\n animate: { opacity: 1, filter: \"blur(0px)\" },\n exit: { opacity: 0, filter: \"blur(16px)\" },\n transition: { duration: 0.3 },\n },\n}\n\n// Props for defining custom animations\ninterface AnimationProps {\n initial?: object // Initial state of the animation\n animate?: object // Final state of the animation\n exit?: object // State when component is removed\n transition?: object // Transition properties\n}\n\n// Wrap this around items in the card that you want to be hidden then animated in on expansion\nconst ExpandableContent = React.forwardRef<\n HTMLDivElement,\n Omit, \"ref\"> & {\n preset?: keyof typeof ANIMATION_PRESETS\n animateIn?: AnimationProps\n animateOut?: AnimationProps\n stagger?: boolean\n staggerChildren?: number\n keepMounted?: boolean\n }\n>(\n (\n {\n children,\n preset,\n animateIn,\n animateOut,\n stagger = false,\n staggerChildren = 0.1,\n keepMounted = false,\n ...props\n },\n ref\n ) => {\n const { isExpanded, transitionDuration, easeType } = useExpandable()\n // useMeasure is used to measure the height of the content\n const [measureRef, { height: measuredHeight }] = useMeasure()\n // useMotionValue creates a value that can be animated smoothly\n const animatedHeight = useMotionValue(0)\n // useSpring applies a spring animation to the height value\n const smoothHeight = useSpring(animatedHeight, springConfig)\n\n useEffect(() => {\n // Animate the height based on whether the content is expanded or collapsed\n if (isExpanded) {\n animatedHeight.set(measuredHeight)\n } else {\n animatedHeight.set(0)\n }\n }, [isExpanded, measuredHeight, animatedHeight])\n\n const presetAnimation = preset ? ANIMATION_PRESETS[preset] : {}\n const combinedAnimateIn = {\n ...presetAnimation,\n ...animateIn,\n }\n const combinedAnimateOut = animateOut || combinedAnimateIn\n\n return (\n // This motion.div animates the height of the content\n \n {/* AnimatePresence handles the entering and exiting of components */}\n \n {(isExpanded || keepMounted) && (\n // This motion.div handles the animation of the content itself\n \n {stagger ? (\n // If stagger is true, we apply a staggered animation to the children\n \n {React.Children.map(\n children as React.ReactNode,\n (child, index) => (\n \n {child}\n \n )\n )}\n \n ) : (\n children\n )}\n \n )}\n \n \n )\n }\n)\n\ninterface ExpandableCardProps {\n children: ReactNode\n className?: string\n collapsedSize?: { width?: number; height?: number } // Size when collapsed\n expandedSize?: { width?: number; height?: number } // Size when expanded\n hoverToExpand?: boolean // Whether to expand on hover\n expandDelay?: number // Delay before expanding\n collapseDelay?: number // Delay before collapsing\n}\n\nconst ExpandableCard = React.forwardRef(\n (\n {\n children,\n className = \"\",\n collapsedSize = { width: 320, height: 211 },\n expandedSize = { width: 480, height: undefined },\n hoverToExpand = false,\n expandDelay = 0,\n collapseDelay = 0,\n ...props\n },\n ref\n ) => {\n // Get the expansion state and toggle function from the ExpandableContext\n const { isExpanded, toggleExpand, expandDirection } = useExpandable()\n\n // Use useMeasure hook to get the dimensions of the content\n const [measureRef, { width, height }] = useMeasure()\n\n // Create motion values for width and height\n const animatedWidth = useMotionValue(collapsedSize.width || 0)\n const animatedHeight = useMotionValue(collapsedSize.height || 0)\n\n // Apply spring animation to the motion values\n const smoothWidth = useSpring(animatedWidth, springConfig)\n const smoothHeight = useSpring(animatedHeight, springConfig)\n\n // Effect to update the animated dimensions when expansion state changes\n useEffect(() => {\n if (isExpanded) {\n animatedWidth.set(expandedSize.width || width)\n animatedHeight.set(expandedSize.height || height)\n } else {\n animatedWidth.set(collapsedSize.width || width)\n animatedHeight.set(collapsedSize.height || height)\n }\n }, [\n isExpanded,\n collapsedSize,\n expandedSize,\n width,\n height,\n animatedWidth,\n animatedHeight,\n ])\n\n // Handler for hover start event\n const handleHover = () => {\n if (hoverToExpand && !isExpanded) {\n setTimeout(toggleExpand, expandDelay)\n }\n }\n\n // Handler for hover end event\n const handleHoverEnd = () => {\n if (hoverToExpand && isExpanded) {\n setTimeout(toggleExpand, collapseDelay)\n }\n }\n\n return (\n \n \n {/* Nested divs purely for styling and layout (the shadow ring around the card) */}\n
\n
\n
\n {/* Ref for measuring content dimensions (so we can let framer know to animate into the dimensions) */}\n
\n {children}\n
\n
\n
\n
\n \n \n )\n }\n)\n\nExpandableCard.displayName = \"ExpandableCard\"\n\n// I'm telling you we just have to expand 🤌💵\nconst ExpandableTrigger = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ children, ...props }, ref) => {\n const { toggleExpand } = useExpandable()\n return (\n
\n {children}\n
\n )\n})\n\nExpandableTrigger.displayName = \"ExpandableTrigger\"\n\nconst ExpandableCardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, children, ...props }, ref) => (\n \n \n {children}\n \n \n))\n\nExpandableCardHeader.displayName = \"ExpandableCardHeader\"\n\nconst ExpandableCardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, children, ...props }, ref) => (\n \n {children}\n \n))\nExpandableCardContent.displayName = \"ExpandableCardContent\"\n\nconst ExpandableCardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nExpandableCardFooter.displayName = \"ExpandableCardFooter\"\n\nexport {\n Expandable,\n useExpandable,\n ExpandableCard,\n ExpandableContent,\n ExpandableContext,\n ExpandableTrigger,\n ExpandableCardHeader,\n ExpandableCardContent,\n ExpandableCardFooter,\n}\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/family-button.json b/apps/www/public/registry/styles/default/family-button.json index e00527b..e634b51 100644 --- a/apps/www/public/registry/styles/default/family-button.json +++ b/apps/www/public/registry/styles/default/family-button.json @@ -6,7 +6,7 @@ "files": [ { "name": "family-button.tsx", - "content": "\"use client\"\n\nimport { FC, ReactNode, useState } from \"react\"\nimport { motion } from \"framer-motion\"\nimport { PlusIcon, XIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst CONTAINER_SIZE = 200\n\ninterface FamilyButtonProps {\n children: React.ReactNode\n}\n\nconst FamilyButton: React.FC = ({ children }) => {\n const [isExpanded, setIsExpanded] = useState(false)\n const toggleExpand = () => setIsExpanded(!isExpanded)\n\n return (\n \n
\n
\n
\n \n {isExpanded ? (\n \n {children}\n \n ) : null}\n \n
\n
\n
\n \n )\n}\n\n// A container that wraps content and handles animations\ninterface FamilyButtonContainerProps {\n isExpanded: boolean\n toggleExpand: () => void\n children: ReactNode\n}\n\nconst FamilyButtonContainer: FC = ({\n isExpanded,\n toggleExpand,\n children,\n}) => {\n return (\n \n {children}\n\n \n {isExpanded ? (\n \n \n \n ) : (\n \n \n \n )}\n \n \n )\n}\n\nexport { FamilyButton }\nexport default FamilyButton\n" + "content": "\"use client\"\n\nimport { FC, ReactNode, useState } from \"react\"\nimport { PlusIcon, XIcon } from \"lucide-react\"\nimport { motion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst CONTAINER_SIZE = 200\n\ninterface FamilyButtonProps {\n children: React.ReactNode\n}\n\nconst FamilyButton: React.FC = ({ children }) => {\n const [isExpanded, setIsExpanded] = useState(false)\n const toggleExpand = () => setIsExpanded(!isExpanded)\n\n return (\n \n
\n
\n
\n \n {isExpanded ? (\n \n {children}\n \n ) : null}\n \n
\n
\n
\n \n )\n}\n\n// A container that wraps content and handles animations\ninterface FamilyButtonContainerProps {\n isExpanded: boolean\n toggleExpand: () => void\n children: ReactNode\n}\n\nconst FamilyButtonContainer: FC = ({\n isExpanded,\n toggleExpand,\n children,\n}) => {\n return (\n \n {children}\n\n \n {isExpanded ? (\n \n \n \n ) : (\n \n \n \n )}\n \n \n )\n}\n\nexport { FamilyButton }\nexport default FamilyButton\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/floating-panel.json b/apps/www/public/registry/styles/default/floating-panel.json index 0c90549..4e4a8cb 100644 --- a/apps/www/public/registry/styles/default/floating-panel.json +++ b/apps/www/public/registry/styles/default/floating-panel.json @@ -6,7 +6,7 @@ "files": [ { "name": "floating-panel.tsx", - "content": "\"use client\"\n\nimport React, {\n createContext,\n useContext,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { AnimatePresence, MotionConfig, Variants, motion } from \"framer-motion\"\nimport { ArrowLeftIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst TRANSITION = {\n type: \"spring\",\n bounce: 0.1,\n duration: 0.4,\n}\n\ninterface FloatingPanelContextType {\n isOpen: boolean\n openFloatingPanel: (rect: DOMRect) => void\n closeFloatingPanel: () => void\n uniqueId: string\n note: string\n setNote: (note: string) => void\n triggerRect: DOMRect | null\n title: string\n setTitle: (title: string) => void\n}\n\nconst FloatingPanelContext = createContext<\n FloatingPanelContextType | undefined\n>(undefined)\n\nfunction useFloatingPanel() {\n const context = useContext(FloatingPanelContext)\n if (!context) {\n throw new Error(\n \"useFloatingPanel must be used within a FloatingPanelProvider\"\n )\n }\n return context\n}\n\nfunction useFloatingPanelLogic() {\n const uniqueId = useId()\n const [isOpen, setIsOpen] = useState(false)\n const [note, setNote] = useState(\"\")\n const [triggerRect, setTriggerRect] = useState(null)\n const [title, setTitle] = useState(\"\")\n\n const openFloatingPanel = (rect: DOMRect) => {\n setTriggerRect(rect)\n setIsOpen(true)\n }\n const closeFloatingPanel = () => {\n setIsOpen(false)\n setNote(\"\")\n }\n\n return {\n isOpen,\n openFloatingPanel,\n closeFloatingPanel,\n uniqueId,\n note,\n setNote,\n triggerRect,\n title,\n setTitle,\n }\n}\n\ninterface FloatingPanelRootProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FloatingPanelRoot({\n children,\n className,\n}: FloatingPanelRootProps) {\n const floatingPanelLogic = useFloatingPanelLogic()\n\n return (\n \n \n
{children}
\n
\n
\n )\n}\n\ninterface FloatingPanelTriggerProps {\n children: React.ReactNode\n className?: string\n title: string\n}\n\nexport function FloatingPanelTrigger({\n children,\n className,\n title,\n}: FloatingPanelTriggerProps) {\n const { openFloatingPanel, uniqueId, setTitle } = useFloatingPanel()\n const triggerRef = useRef(null)\n\n const handleClick = () => {\n if (triggerRef.current) {\n openFloatingPanel(triggerRef.current.getBoundingClientRect())\n setTitle(title)\n }\n }\n\n return (\n \n \n \n {children}\n \n \n \n )\n}\n\ninterface FloatingPanelContentProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FloatingPanelContent({\n children,\n className,\n}: FloatingPanelContentProps) {\n const { isOpen, closeFloatingPanel, uniqueId, triggerRect, title } =\n useFloatingPanel()\n const contentRef = useRef(null)\n\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (\n contentRef.current &&\n !contentRef.current.contains(event.target as Node)\n ) {\n closeFloatingPanel()\n }\n }\n document.addEventListener(\"mousedown\", handleClickOutside)\n return () => document.removeEventListener(\"mousedown\", handleClickOutside)\n }, [closeFloatingPanel])\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") closeFloatingPanel()\n }\n document.addEventListener(\"keydown\", handleKeyDown)\n return () => document.removeEventListener(\"keydown\", handleKeyDown)\n }, [closeFloatingPanel])\n\n const variants: Variants = {\n hidden: { opacity: 0, scale: 0.9, y: 10 },\n visible: { opacity: 1, scale: 1, y: 0 },\n }\n\n return (\n \n {isOpen && (\n <>\n \n \n {title}\n {children}\n \n \n )}\n \n )\n}\n\ninterface FloatingPanelTitleProps {\n children: React.ReactNode\n}\n\nfunction FloatingPanelTitle({ children }: FloatingPanelTitleProps) {\n const { uniqueId } = useFloatingPanel()\n\n return (\n \n \n {children}\n \n \n )\n}\n\ninterface FloatingPanelFormProps {\n children: React.ReactNode\n onSubmit?: (note: string) => void\n className?: string\n}\n\nexport function FloatingPanelForm({\n children,\n onSubmit,\n className,\n}: FloatingPanelFormProps) {\n const { note, closeFloatingPanel } = useFloatingPanel()\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault()\n onSubmit?.(note)\n closeFloatingPanel()\n }\n\n return (\n \n {children}\n \n )\n}\n\ninterface FloatingPanelLabelProps {\n children: React.ReactNode\n htmlFor: string\n className?: string\n}\n\nexport function FloatingPanelLabel({\n children,\n htmlFor,\n className,\n}: FloatingPanelLabelProps) {\n const { note } = useFloatingPanel()\n\n return (\n \n {children}\n \n )\n}\n\ninterface FloatingPanelTextareaProps {\n className?: string\n id?: string\n}\n\nexport function FloatingPanelTextarea({\n className,\n id,\n}: FloatingPanelTextareaProps) {\n const { note, setNote } = useFloatingPanel()\n\n return (\n setNote(e.target.value)}\n />\n )\n}\n\ninterface FloatingPanelHeaderProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FloatingPanelHeader({\n children,\n className,\n}: FloatingPanelHeaderProps) {\n return (\n \n {children}\n \n )\n}\n\ninterface FloatingPanelBodyProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FloatingPanelBody({\n children,\n className,\n}: FloatingPanelBodyProps) {\n return (\n \n {children}\n \n )\n}\n\ninterface FloatingPanelFooterProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FloatingPanelFooter({\n children,\n className,\n}: FloatingPanelFooterProps) {\n return (\n \n {children}\n \n )\n}\n\ninterface FloatingPanelCloseButtonProps {\n className?: string\n}\n\nexport function FloatingPanelCloseButton({\n className,\n}: FloatingPanelCloseButtonProps) {\n const { closeFloatingPanel } = useFloatingPanel()\n\n return (\n \n \n \n )\n}\n\ninterface FloatingPanelSubmitButtonProps {\n className?: string\n}\n\nexport function FloatingPanelSubmitButton({\n className,\n}: FloatingPanelSubmitButtonProps) {\n return (\n \n Submit Note\n \n )\n}\n\ninterface FloatingPanelButtonProps {\n children: React.ReactNode\n onClick?: () => void\n className?: string\n}\n\nexport function FloatingPanelButton({\n children,\n onClick,\n className,\n}: FloatingPanelButtonProps) {\n return (\n \n {children}\n \n )\n}\n\nexport default {\n Root: FloatingPanelRoot,\n Trigger: FloatingPanelTrigger,\n Content: FloatingPanelContent,\n Form: FloatingPanelForm,\n Label: FloatingPanelLabel,\n Textarea: FloatingPanelTextarea,\n Header: FloatingPanelHeader,\n Body: FloatingPanelBody,\n Footer: FloatingPanelFooter,\n CloseButton: FloatingPanelCloseButton,\n SubmitButton: FloatingPanelSubmitButton,\n Button: FloatingPanelButton,\n}\n// \"use client\"\n\n// import React, {\n// createContext,\n// useContext,\n// useEffect,\n// useId,\n// useRef,\n// useState,\n// } from \"react\"\n// import { AnimatePresence, MotionConfig, motion } from \"framer-motion\"\n// import { ArrowLeftIcon } from \"lucide-react\"\n\n// import { cn } from \"@/lib/utils\"\n\n// const TRANSITION = {\n// type: \"spring\",\n// bounce: 0.1,\n// duration: 0.4,\n// }\n\n// interface FloatingPanelContextType {\n// isOpen: boolean\n// openFloatingPanel: (rect: DOMRect) => void\n// closeFloatingPanel: () => void\n// uniqueId: string\n// note: string\n// setNote: (note: string) => void\n// triggerRect: DOMRect | null\n// title: string\n// setTitle: (title: string) => void\n// }\n\n// const FloatingPanelContext = createContext<\n// FloatingPanelContextType | undefined\n// >(undefined)\n\n// function useFloatingPanel() {\n// const context = useContext(FloatingPanelContext)\n// if (!context) {\n// throw new Error(\n// \"useFloatingPanel must be used within a FloatingPanelProvider\"\n// )\n// }\n// return context\n// }\n\n// function useFloatingPanelLogic() {\n// const uniqueId = useId()\n// const [isOpen, setIsOpen] = useState(false)\n// const [note, setNote] = useState(\"\")\n// const [triggerRect, setTriggerRect] = useState(null)\n// const [title, setTitle] = useState(\"\")\n\n// const openFloatingPanel = (rect: DOMRect) => {\n// setTriggerRect(rect)\n// setIsOpen(true)\n// }\n// const closeFloatingPanel = () => {\n// setIsOpen(false)\n// setNote(\"\")\n// }\n\n// return {\n// isOpen,\n// openFloatingPanel,\n// closeFloatingPanel,\n// uniqueId,\n// note,\n// setNote,\n// triggerRect,\n// title,\n// setTitle,\n// }\n// }\n\n// interface FloatingPanelRootProps {\n// children: React.ReactNode\n// className?: string\n// }\n\n// export function FloatingPanelRoot({\n// children,\n// className,\n// }: FloatingPanelRootProps) {\n// const floatingPanelLogic = useFloatingPanelLogic()\n\n// return (\n// \n// \n//
{children}
\n//
\n//
\n// )\n// }\n\n// interface FloatingPanelTriggerProps {\n// children: React.ReactNode\n// className?: string\n// title: string\n// }\n\n// export function FloatingPanelTrigger({\n// children,\n// className,\n// title,\n// }: FloatingPanelTriggerProps) {\n// const { openFloatingPanel, uniqueId, setTitle } = useFloatingPanel()\n// const triggerRef = useRef(null)\n\n// const handleClick = () => {\n// if (triggerRef.current) {\n// openFloatingPanel(triggerRef.current.getBoundingClientRect())\n// setTitle(title)\n// }\n// }\n\n// return (\n// \n// \n// {children}\n// \n// \n// )\n// }\n\n// interface FloatingPanelContentProps {\n// children: React.ReactNode\n// className?: string\n// }\n\n// export function FloatingPanelContent({\n// children,\n// className,\n// }: FloatingPanelContentProps) {\n// const { isOpen, closeFloatingPanel, uniqueId, triggerRect, title } =\n// useFloatingPanel()\n// const contentRef = useRef(null)\n\n// useEffect(() => {\n// const handleClickOutside = (event: MouseEvent) => {\n// if (\n// contentRef.current &&\n// !contentRef.current.contains(event.target as Node)\n// ) {\n// closeFloatingPanel()\n// }\n// }\n// document.addEventListener(\"mousedown\", handleClickOutside)\n// return () => document.removeEventListener(\"mousedown\", handleClickOutside)\n// }, [closeFloatingPanel])\n\n// useEffect(() => {\n// const handleKeyDown = (event: KeyboardEvent) => {\n// if (event.key === \"Escape\") closeFloatingPanel()\n// }\n// document.addEventListener(\"keydown\", handleKeyDown)\n// return () => document.removeEventListener(\"keydown\", handleKeyDown)\n// }, [closeFloatingPanel])\n\n// const variants = {\n// hidden: { opacity: 0, scale: 0.9, y: 10 },\n// visible: { opacity: 1, scale: 1, y: 0 },\n// }\n\n// return (\n// \n// {isOpen && (\n// <>\n// \n// \n// {title}\n// {children}\n// \n// \n// )}\n// \n// )\n// }\n\n// interface FloatingPanelTitleProps {\n// children: React.ReactNode\n// }\n\n// function FloatingPanelTitle({ children }: FloatingPanelTitleProps) {\n// const { uniqueId } = useFloatingPanel()\n\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelFormProps {\n// children: React.ReactNode\n// onSubmit?: (note: string) => void\n// className?: string\n// }\n\n// export function FloatingPanelForm({\n// children,\n// onSubmit,\n// className,\n// }: FloatingPanelFormProps) {\n// const { note, closeFloatingPanel } = useFloatingPanel()\n\n// const handleSubmit = (e: React.FormEvent) => {\n// e.preventDefault()\n// onSubmit?.(note)\n// closeFloatingPanel()\n// }\n\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelLabelProps {\n// children: React.ReactNode\n// htmlFor: string\n// className?: string\n// }\n\n// export function FloatingPanelLabel({\n// children,\n// htmlFor,\n// className,\n// }: FloatingPanelLabelProps) {\n// const { note } = useFloatingPanel()\n\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelTextareaProps {\n// className?: string\n// id?: string\n// }\n\n// export function FloatingPanelTextarea({\n// className,\n// id,\n// }: FloatingPanelTextareaProps) {\n// const { note, setNote } = useFloatingPanel()\n\n// return (\n// setNote(e.target.value)}\n// />\n// )\n// }\n\n// interface FloatingPanelHeaderProps {\n// children: React.ReactNode\n// className?: string\n// }\n\n// export function FloatingPanelHeader({\n// children,\n// className,\n// }: FloatingPanelHeaderProps) {\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelBodyProps {\n// children: React.ReactNode\n// className?: string\n// }\n\n// export function FloatingPanelBody({\n// children,\n// className,\n// }: FloatingPanelBodyProps) {\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelFooterProps {\n// children: React.ReactNode\n// className?: string\n// }\n\n// export function FloatingPanelFooter({\n// children,\n// className,\n// }: FloatingPanelFooterProps) {\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelCloseButtonProps {\n// className?: string\n// }\n\n// export function FloatingPanelCloseButton({\n// className,\n// }: FloatingPanelCloseButtonProps) {\n// const { closeFloatingPanel } = useFloatingPanel()\n\n// return (\n// \n// \n// \n// )\n// }\n\n// interface FloatingPanelSubmitButtonProps {\n// className?: string\n// }\n\n// export function FloatingPanelSubmitButton({\n// className,\n// }: FloatingPanelSubmitButtonProps) {\n// return (\n// \n// Submit Note\n// \n// )\n// }\n\n// interface FloatingPanelButtonProps {\n// children: React.ReactNode\n// onClick?: () => void\n// className?: string\n// }\n\n// export function FloatingPanelButton({\n// children,\n// onClick,\n// className,\n// }: FloatingPanelButtonProps) {\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// export default {\n// Root: FloatingPanelRoot,\n// Trigger: FloatingPanelTrigger,\n// Content: FloatingPanelContent,\n// Form: FloatingPanelForm,\n// Label: FloatingPanelLabel,\n// Textarea: FloatingPanelTextarea,\n// Header: FloatingPanelHeader,\n// Body: FloatingPanelBody,\n// Footer: FloatingPanelFooter,\n// CloseButton: FloatingPanelCloseButton,\n// SubmitButton: FloatingPanelSubmitButton,\n// Button: FloatingPanelButton,\n// }\n" + "content": "\"use client\"\n\nimport React, {\n createContext,\n useContext,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { ArrowLeftIcon } from \"lucide-react\"\nimport { AnimatePresence, MotionConfig, Variants, motion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst TRANSITION = {\n type: \"spring\",\n bounce: 0.1,\n duration: 0.4,\n}\n\ninterface FloatingPanelContextType {\n isOpen: boolean\n openFloatingPanel: (rect: DOMRect) => void\n closeFloatingPanel: () => void\n uniqueId: string\n note: string\n setNote: (note: string) => void\n triggerRect: DOMRect | null\n title: string\n setTitle: (title: string) => void\n}\n\nconst FloatingPanelContext = createContext<\n FloatingPanelContextType | undefined\n>(undefined)\n\nfunction useFloatingPanel() {\n const context = useContext(FloatingPanelContext)\n if (!context) {\n throw new Error(\n \"useFloatingPanel must be used within a FloatingPanelProvider\"\n )\n }\n return context\n}\n\nfunction useFloatingPanelLogic() {\n const uniqueId = useId()\n const [isOpen, setIsOpen] = useState(false)\n const [note, setNote] = useState(\"\")\n const [triggerRect, setTriggerRect] = useState(null)\n const [title, setTitle] = useState(\"\")\n\n const openFloatingPanel = (rect: DOMRect) => {\n setTriggerRect(rect)\n setIsOpen(true)\n }\n const closeFloatingPanel = () => {\n setIsOpen(false)\n setNote(\"\")\n }\n\n return {\n isOpen,\n openFloatingPanel,\n closeFloatingPanel,\n uniqueId,\n note,\n setNote,\n triggerRect,\n title,\n setTitle,\n }\n}\n\ninterface FloatingPanelRootProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FloatingPanelRoot({\n children,\n className,\n}: FloatingPanelRootProps) {\n const floatingPanelLogic = useFloatingPanelLogic()\n\n return (\n \n \n
{children}
\n
\n
\n )\n}\n\ninterface FloatingPanelTriggerProps {\n children: React.ReactNode\n className?: string\n title: string\n}\n\nexport function FloatingPanelTrigger({\n children,\n className,\n title,\n}: FloatingPanelTriggerProps) {\n const { openFloatingPanel, uniqueId, setTitle } = useFloatingPanel()\n const triggerRef = useRef(null)\n\n const handleClick = () => {\n if (triggerRef.current) {\n openFloatingPanel(triggerRef.current.getBoundingClientRect())\n setTitle(title)\n }\n }\n\n return (\n \n \n \n {children}\n \n \n \n )\n}\n\ninterface FloatingPanelContentProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FloatingPanelContent({\n children,\n className,\n}: FloatingPanelContentProps) {\n const { isOpen, closeFloatingPanel, uniqueId, triggerRect, title } =\n useFloatingPanel()\n const contentRef = useRef(null)\n\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (\n contentRef.current &&\n !contentRef.current.contains(event.target as Node)\n ) {\n closeFloatingPanel()\n }\n }\n document.addEventListener(\"mousedown\", handleClickOutside)\n return () => document.removeEventListener(\"mousedown\", handleClickOutside)\n }, [closeFloatingPanel])\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") closeFloatingPanel()\n }\n document.addEventListener(\"keydown\", handleKeyDown)\n return () => document.removeEventListener(\"keydown\", handleKeyDown)\n }, [closeFloatingPanel])\n\n const variants: Variants = {\n hidden: { opacity: 0, scale: 0.9, y: 10 },\n visible: { opacity: 1, scale: 1, y: 0 },\n }\n\n return (\n \n {isOpen && (\n <>\n \n \n {title}\n {children}\n \n \n )}\n \n )\n}\n\ninterface FloatingPanelTitleProps {\n children: React.ReactNode\n}\n\nfunction FloatingPanelTitle({ children }: FloatingPanelTitleProps) {\n const { uniqueId } = useFloatingPanel()\n\n return (\n \n \n {children}\n \n \n )\n}\n\ninterface FloatingPanelFormProps {\n children: React.ReactNode\n onSubmit?: (note: string) => void\n className?: string\n}\n\nexport function FloatingPanelForm({\n children,\n onSubmit,\n className,\n}: FloatingPanelFormProps) {\n const { note, closeFloatingPanel } = useFloatingPanel()\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault()\n onSubmit?.(note)\n closeFloatingPanel()\n }\n\n return (\n \n {children}\n \n )\n}\n\ninterface FloatingPanelLabelProps {\n children: React.ReactNode\n htmlFor: string\n className?: string\n}\n\nexport function FloatingPanelLabel({\n children,\n htmlFor,\n className,\n}: FloatingPanelLabelProps) {\n const { note } = useFloatingPanel()\n\n return (\n \n {children}\n \n )\n}\n\ninterface FloatingPanelTextareaProps {\n className?: string\n id?: string\n}\n\nexport function FloatingPanelTextarea({\n className,\n id,\n}: FloatingPanelTextareaProps) {\n const { note, setNote } = useFloatingPanel()\n\n return (\n setNote(e.target.value)}\n />\n )\n}\n\ninterface FloatingPanelHeaderProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FloatingPanelHeader({\n children,\n className,\n}: FloatingPanelHeaderProps) {\n return (\n \n {children}\n \n )\n}\n\ninterface FloatingPanelBodyProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FloatingPanelBody({\n children,\n className,\n}: FloatingPanelBodyProps) {\n return (\n \n {children}\n \n )\n}\n\ninterface FloatingPanelFooterProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function FloatingPanelFooter({\n children,\n className,\n}: FloatingPanelFooterProps) {\n return (\n \n {children}\n \n )\n}\n\ninterface FloatingPanelCloseButtonProps {\n className?: string\n}\n\nexport function FloatingPanelCloseButton({\n className,\n}: FloatingPanelCloseButtonProps) {\n const { closeFloatingPanel } = useFloatingPanel()\n\n return (\n \n \n \n )\n}\n\ninterface FloatingPanelSubmitButtonProps {\n className?: string\n}\n\nexport function FloatingPanelSubmitButton({\n className,\n}: FloatingPanelSubmitButtonProps) {\n return (\n \n Submit Note\n \n )\n}\n\ninterface FloatingPanelButtonProps {\n children: React.ReactNode\n onClick?: () => void\n className?: string\n}\n\nexport function FloatingPanelButton({\n children,\n onClick,\n className,\n}: FloatingPanelButtonProps) {\n return (\n \n {children}\n \n )\n}\n\nexport default {\n Root: FloatingPanelRoot,\n Trigger: FloatingPanelTrigger,\n Content: FloatingPanelContent,\n Form: FloatingPanelForm,\n Label: FloatingPanelLabel,\n Textarea: FloatingPanelTextarea,\n Header: FloatingPanelHeader,\n Body: FloatingPanelBody,\n Footer: FloatingPanelFooter,\n CloseButton: FloatingPanelCloseButton,\n SubmitButton: FloatingPanelSubmitButton,\n Button: FloatingPanelButton,\n}\n// \"use client\"\n\n// import React, {\n// createContext,\n// useContext,\n// useEffect,\n// useId,\n// useRef,\n// useState,\n// } from \"react\"\n// import { AnimatePresence, MotionConfig, motion } from \"motion/react\"\n// import { ArrowLeftIcon } from \"lucide-react\"\n\n// import { cn } from \"@/lib/utils\"\n\n// const TRANSITION = {\n// type: \"spring\",\n// bounce: 0.1,\n// duration: 0.4,\n// }\n\n// interface FloatingPanelContextType {\n// isOpen: boolean\n// openFloatingPanel: (rect: DOMRect) => void\n// closeFloatingPanel: () => void\n// uniqueId: string\n// note: string\n// setNote: (note: string) => void\n// triggerRect: DOMRect | null\n// title: string\n// setTitle: (title: string) => void\n// }\n\n// const FloatingPanelContext = createContext<\n// FloatingPanelContextType | undefined\n// >(undefined)\n\n// function useFloatingPanel() {\n// const context = useContext(FloatingPanelContext)\n// if (!context) {\n// throw new Error(\n// \"useFloatingPanel must be used within a FloatingPanelProvider\"\n// )\n// }\n// return context\n// }\n\n// function useFloatingPanelLogic() {\n// const uniqueId = useId()\n// const [isOpen, setIsOpen] = useState(false)\n// const [note, setNote] = useState(\"\")\n// const [triggerRect, setTriggerRect] = useState(null)\n// const [title, setTitle] = useState(\"\")\n\n// const openFloatingPanel = (rect: DOMRect) => {\n// setTriggerRect(rect)\n// setIsOpen(true)\n// }\n// const closeFloatingPanel = () => {\n// setIsOpen(false)\n// setNote(\"\")\n// }\n\n// return {\n// isOpen,\n// openFloatingPanel,\n// closeFloatingPanel,\n// uniqueId,\n// note,\n// setNote,\n// triggerRect,\n// title,\n// setTitle,\n// }\n// }\n\n// interface FloatingPanelRootProps {\n// children: React.ReactNode\n// className?: string\n// }\n\n// export function FloatingPanelRoot({\n// children,\n// className,\n// }: FloatingPanelRootProps) {\n// const floatingPanelLogic = useFloatingPanelLogic()\n\n// return (\n// \n// \n//
{children}
\n//
\n//
\n// )\n// }\n\n// interface FloatingPanelTriggerProps {\n// children: React.ReactNode\n// className?: string\n// title: string\n// }\n\n// export function FloatingPanelTrigger({\n// children,\n// className,\n// title,\n// }: FloatingPanelTriggerProps) {\n// const { openFloatingPanel, uniqueId, setTitle } = useFloatingPanel()\n// const triggerRef = useRef(null)\n\n// const handleClick = () => {\n// if (triggerRef.current) {\n// openFloatingPanel(triggerRef.current.getBoundingClientRect())\n// setTitle(title)\n// }\n// }\n\n// return (\n// \n// \n// {children}\n// \n// \n// )\n// }\n\n// interface FloatingPanelContentProps {\n// children: React.ReactNode\n// className?: string\n// }\n\n// export function FloatingPanelContent({\n// children,\n// className,\n// }: FloatingPanelContentProps) {\n// const { isOpen, closeFloatingPanel, uniqueId, triggerRect, title } =\n// useFloatingPanel()\n// const contentRef = useRef(null)\n\n// useEffect(() => {\n// const handleClickOutside = (event: MouseEvent) => {\n// if (\n// contentRef.current &&\n// !contentRef.current.contains(event.target as Node)\n// ) {\n// closeFloatingPanel()\n// }\n// }\n// document.addEventListener(\"mousedown\", handleClickOutside)\n// return () => document.removeEventListener(\"mousedown\", handleClickOutside)\n// }, [closeFloatingPanel])\n\n// useEffect(() => {\n// const handleKeyDown = (event: KeyboardEvent) => {\n// if (event.key === \"Escape\") closeFloatingPanel()\n// }\n// document.addEventListener(\"keydown\", handleKeyDown)\n// return () => document.removeEventListener(\"keydown\", handleKeyDown)\n// }, [closeFloatingPanel])\n\n// const variants = {\n// hidden: { opacity: 0, scale: 0.9, y: 10 },\n// visible: { opacity: 1, scale: 1, y: 0 },\n// }\n\n// return (\n// \n// {isOpen && (\n// <>\n// \n// \n// {title}\n// {children}\n// \n// \n// )}\n// \n// )\n// }\n\n// interface FloatingPanelTitleProps {\n// children: React.ReactNode\n// }\n\n// function FloatingPanelTitle({ children }: FloatingPanelTitleProps) {\n// const { uniqueId } = useFloatingPanel()\n\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelFormProps {\n// children: React.ReactNode\n// onSubmit?: (note: string) => void\n// className?: string\n// }\n\n// export function FloatingPanelForm({\n// children,\n// onSubmit,\n// className,\n// }: FloatingPanelFormProps) {\n// const { note, closeFloatingPanel } = useFloatingPanel()\n\n// const handleSubmit = (e: React.FormEvent) => {\n// e.preventDefault()\n// onSubmit?.(note)\n// closeFloatingPanel()\n// }\n\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelLabelProps {\n// children: React.ReactNode\n// htmlFor: string\n// className?: string\n// }\n\n// export function FloatingPanelLabel({\n// children,\n// htmlFor,\n// className,\n// }: FloatingPanelLabelProps) {\n// const { note } = useFloatingPanel()\n\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelTextareaProps {\n// className?: string\n// id?: string\n// }\n\n// export function FloatingPanelTextarea({\n// className,\n// id,\n// }: FloatingPanelTextareaProps) {\n// const { note, setNote } = useFloatingPanel()\n\n// return (\n// setNote(e.target.value)}\n// />\n// )\n// }\n\n// interface FloatingPanelHeaderProps {\n// children: React.ReactNode\n// className?: string\n// }\n\n// export function FloatingPanelHeader({\n// children,\n// className,\n// }: FloatingPanelHeaderProps) {\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelBodyProps {\n// children: React.ReactNode\n// className?: string\n// }\n\n// export function FloatingPanelBody({\n// children,\n// className,\n// }: FloatingPanelBodyProps) {\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelFooterProps {\n// children: React.ReactNode\n// className?: string\n// }\n\n// export function FloatingPanelFooter({\n// children,\n// className,\n// }: FloatingPanelFooterProps) {\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// interface FloatingPanelCloseButtonProps {\n// className?: string\n// }\n\n// export function FloatingPanelCloseButton({\n// className,\n// }: FloatingPanelCloseButtonProps) {\n// const { closeFloatingPanel } = useFloatingPanel()\n\n// return (\n// \n// \n// \n// )\n// }\n\n// interface FloatingPanelSubmitButtonProps {\n// className?: string\n// }\n\n// export function FloatingPanelSubmitButton({\n// className,\n// }: FloatingPanelSubmitButtonProps) {\n// return (\n// \n// Submit Note\n// \n// )\n// }\n\n// interface FloatingPanelButtonProps {\n// children: React.ReactNode\n// onClick?: () => void\n// className?: string\n// }\n\n// export function FloatingPanelButton({\n// children,\n// onClick,\n// className,\n// }: FloatingPanelButtonProps) {\n// return (\n// \n// {children}\n// \n// )\n// }\n\n// export default {\n// Root: FloatingPanelRoot,\n// Trigger: FloatingPanelTrigger,\n// Content: FloatingPanelContent,\n// Form: FloatingPanelForm,\n// Label: FloatingPanelLabel,\n// Textarea: FloatingPanelTextarea,\n// Header: FloatingPanelHeader,\n// Body: FloatingPanelBody,\n// Footer: FloatingPanelFooter,\n// CloseButton: FloatingPanelCloseButton,\n// SubmitButton: FloatingPanelSubmitButton,\n// Button: FloatingPanelButton,\n// }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/gradient-heading.json b/apps/www/public/registry/styles/default/gradient-heading.json index cb2aecb..15f72f3 100644 --- a/apps/www/public/registry/styles/default/gradient-heading.json +++ b/apps/www/public/registry/styles/default/gradient-heading.json @@ -6,7 +6,7 @@ "files": [ { "name": "gradient-heading.tsx", - "content": "import React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst headingVariants = cva(\n \"tracking-tight pb-3 bg-clip-text text-transparent\",\n {\n variants: {\n variant: {\n default:\n \"bg-gradient-to-t from-neutral-700 to-neutral-800 dark:from-stone-200 dark:to-neutral-200\",\n pink: \"bg-gradient-to-t from-accent to-accent/90 dark:from-stone-200 dark:to-neutral-200\",\n light: \"bg-gradient-to-t from-neutral-200 to-neutral-300\",\n secondary:\n \"bg-gradient-to-t from-primary-foreground to-muted-foreground\",\n },\n size: {\n default: \"text-2xl sm:text-3xl lg:text-4xl\",\n xxs: \"text-base sm:text-lg lg:text-lg\",\n xs: \"text-lg sm:text-xl lg:text-2xl\",\n sm: \"text-xl sm:text-2xl lg:text-3xl\",\n md: \"text-2xl sm:text-3xl lg:text-4xl\",\n lg: \"text-3xl sm:text-4xl lg:text-5xl\",\n xl: \"text-4xl sm:text-5xl lg:text-6xl\",\n xxl: \"text-5xl sm:text-6xl lg:text-[6rem]\",\n xxxl: \"text-5xl sm:text-6xl lg:text-[8rem]\",\n },\n weight: {\n default: \"font-bold\",\n thin: \"font-thin\",\n base: \"font-base\",\n semi: \"font-semibold\",\n bold: \"font-bold\",\n black: \"font-black\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n weight: \"default\",\n },\n }\n)\n\nexport interface HeadingProps extends VariantProps {\n asChild?: boolean\n children: React.ReactNode\n className?: string\n}\n\nconst GradientHeading = React.forwardRef(\n ({ asChild, variant, weight, size, className, children, ...props }, ref) => {\n const Comp = asChild ? Slot : \"h3\" // default to 'h3' if not a child\n return (\n \n \n {children}\n \n \n )\n }\n)\n\nGradientHeading.displayName = \"GradientHeading\"\n\n// Manually define the variant types\nexport type Variant = \"default\" | \"pink\" | \"light\" | \"secondary\"\nexport type Size =\n | \"default\"\n | \"xxs\"\n | \"xs\"\n | \"sm\"\n | \"md\"\n | \"lg\"\n | \"xl\"\n | \"xxl\"\n | \"xxxl\"\nexport type Weight = \"default\" | \"thin\" | \"base\" | \"semi\" | \"bold\" | \"black\"\n\nexport { GradientHeading, headingVariants }\n" + "content": "import React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst headingVariants = cva(\n \"tracking-tight pb-3 bg-clip-text text-transparent\",\n {\n variants: {\n variant: {\n default:\n \"bg-gradient-to-t from-neutral-700 to-neutral-800 dark:from-stone-200 dark:to-neutral-200\",\n pink: \"bg-gradient-to-t from-accent to-accent/90 dark:from-stone-200 dark:to-neutral-200\",\n light: \"bg-gradient-to-t from-neutral-200 to-neutral-300\",\n secondary:\n \"bg-gradient-to-t from-neutral-500 to-neutral-600 dark:from-stone-200 dark:to-neutral-200\",\n },\n size: {\n default: \"text-2xl sm:text-3xl lg:text-4xl\",\n xxs: \"text-base sm:text-lg lg:text-lg\",\n xs: \"text-lg sm:text-xl lg:text-2xl\",\n sm: \"text-xl sm:text-2xl lg:text-3xl\",\n md: \"text-2xl sm:text-3xl lg:text-4xl\",\n lg: \"text-3xl sm:text-4xl lg:text-5xl\",\n xl: \"text-4xl sm:text-5xl lg:text-6xl\",\n xxl: \"text-5xl sm:text-6xl lg:text-[6rem]\",\n xxxl: \"text-5xl sm:text-6xl lg:text-[8rem]\",\n },\n weight: {\n default: \"font-bold\",\n thin: \"font-thin\",\n base: \"font-base\",\n semi: \"font-semibold\",\n bold: \"font-bold\",\n black: \"font-black\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n weight: \"default\",\n },\n }\n)\n\nexport interface HeadingProps extends VariantProps {\n asChild?: boolean\n children: React.ReactNode\n className?: string\n}\n\nconst GradientHeading = React.forwardRef(\n ({ asChild, variant, weight, size, className, children, ...props }, ref) => {\n const Comp = asChild ? Slot : \"h3\" // default to 'h3' if not a child\n return (\n \n \n {children}\n \n \n )\n }\n)\n\nGradientHeading.displayName = \"GradientHeading\"\n\n// Manually define the variant types\nexport type Variant = \"default\" | \"pink\" | \"light\" | \"secondary\"\nexport type Size =\n | \"default\"\n | \"xxs\"\n | \"xs\"\n | \"sm\"\n | \"md\"\n | \"lg\"\n | \"xl\"\n | \"xxl\"\n | \"xxxl\"\nexport type Weight = \"default\" | \"thin\" | \"base\" | \"semi\" | \"bold\" | \"black\"\n\nexport { GradientHeading, headingVariants }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/hover-video-player.json b/apps/www/public/registry/styles/default/hover-video-player.json new file mode 100644 index 0000000..96f6543 --- /dev/null +++ b/apps/www/public/registry/styles/default/hover-video-player.json @@ -0,0 +1,13 @@ +{ + "name": "hover-video-player", + "dependencies": [ + "framer-motion" + ], + "files": [ + { + "name": "hover-video-player.tsx", + "content": "\"use client\"\n\n/**\n * HoverVideoPlayer Component\n *\n * A React component that plays video on hover/touch with advanced features like:\n * - Lazy loading and intersection observer support\n * - Mobile touch support\n * - Picture-in-Picture\n * - Custom overlay support\n * - Thumbnail support\n * - Playback controls\n *\n * @example\n * ```tsx\n * }\n * loadingOverlay={}\n * enableControls\n * />\n * ```\n */\nimport React, {\n ReactNode,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\"\nimport Image from \"next/image\"\nimport { AnimatePresence, motion } from \"framer-motion\"\nimport { Maximize, Minimize, Pause, Play, Volume2, VolumeX } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Slider } from \"@/components/ui/slider\"\n\n// Types\ninterface VideoPlayerState {\n isHovering: boolean\n isPlaying: boolean\n isLoading: boolean\n progress: number\n muted: boolean\n volume: number\n isPiP: boolean\n isMobile: boolean\n controlsVisible: boolean\n showThumbnail: boolean\n isInView: boolean\n}\n\ninterface HoverVideoPlayerProps {\n videoSrc: string\n thumbnailSrc?: string\n hoverOverlay?: React.ReactNode\n pausedOverlay?: React.ReactNode\n loadingOverlay?: React.ReactNode\n playbackStartDelay?: number\n restartOnPaused?: boolean\n unloadVideoOnPaused?: boolean\n playbackRangeStart?: number\n playbackRangeEnd?: number\n muted?: boolean\n loop?: boolean\n preload?: \"auto\" | \"metadata\" | \"none\"\n className?: string\n style?: React.CSSProperties\n onHoverStart?: () => void\n onHoverEnd?: () => void\n enableControls?: boolean\n cropTop?: number\n cropBottom?: number\n isVimeo?: boolean\n}\n\ninterface HoverVideoPlayerContextType {\n isPlaying: boolean\n isHovering: boolean\n isLoading: boolean\n progress: number\n volume: number\n muted: boolean\n isPiP: boolean\n isMobile: boolean\n controlsVisible: boolean\n videoRef: React.RefObject\n togglePlay: () => void\n toggleMute: () => void\n togglePiP: () => void\n setVolume: (value: number) => void\n setProgress: (value: number) => void\n cropTop: number\n cropBottom: number\n thumbnailSrc?: string\n}\n\ninterface VimeoPlayer {\n destroy: () => void\n ready: () => Promise\n setVolume: (volume: number) => Promise\n play: () => Promise\n pause: () => Promise\n on: (event: string, callback: (...args: any[]) => void) => void\n off: (event: string, callback: (...args: any[]) => void) => void\n}\n\ninterface VimeoConstructor {\n Player: {\n new (element: HTMLElement, options: any): VimeoPlayer\n }\n}\n\n// Context\nconst HoverVideoPlayerContext =\n createContext(null)\n\n/**\n * Custom hook to access HoverVideoPlayer context\n * @throws {Error} If used outside of HoverVideoPlayerContext\n */\nconst useHoverVideoPlayer = () => {\n const context = useContext(HoverVideoPlayerContext)\n if (!context) {\n throw new Error(\n \"useHoverVideoPlayer must be used within a HoverVideoPlayer\"\n )\n }\n return context\n}\n\n// Add this helper function before the HoverVideoPlayer component\nfunction debounce any>(\n func: T,\n wait: number\n): {\n (...args: Parameters): void\n cancel: () => void\n} {\n let timeoutId: NodeJS.Timeout | null = null\n\n const debouncedFn = (...args: Parameters) => {\n if (timeoutId) clearTimeout(timeoutId)\n timeoutId = setTimeout(() => func(...args), wait)\n }\n\n debouncedFn.cancel = () => {\n if (timeoutId) {\n clearTimeout(timeoutId)\n timeoutId = null\n }\n }\n\n return debouncedFn\n}\n\n// Add this helper function\nfunction isVimeoUrl(url: string): boolean {\n return url.includes(\"player.vimeo.com/video/\") || url.includes(\"vimeo.com/\")\n}\n\n// Add this helper function\nfunction loadVimeoSDK(): Promise {\n return new Promise((resolve, reject) => {\n if ((window as any).Vimeo) {\n resolve((window as any).Vimeo)\n return\n }\n\n const script = document.createElement(\"script\")\n script.src = \"https://player.vimeo.com/api/player.js\"\n script.async = true\n script.onload = () => resolve((window as any).Vimeo)\n script.onerror = reject\n document.body.appendChild(script)\n })\n}\n\n// Main Component\nconst HoverVideoPlayer: React.FC = ({\n videoSrc,\n thumbnailSrc,\n hoverOverlay,\n pausedOverlay,\n loadingOverlay,\n playbackStartDelay = 0,\n restartOnPaused = false,\n unloadVideoOnPaused = false,\n playbackRangeStart,\n playbackRangeEnd,\n muted: initialMuted = false,\n loop = true,\n preload = \"metadata\",\n className,\n style,\n onHoverStart,\n onHoverEnd,\n enableControls = false,\n cropTop = 0,\n cropBottom = 0,\n isVimeo = false,\n}) => {\n // Refs for DOM elements and timing\n const containerRef = useRef(null)\n const videoRef = useRef(null)\n const playbackTimeoutRef = useRef()\n const lastPlayAttemptRef = useRef(0)\n\n // Consolidated state management\n const [state, setState] = useState({\n isHovering: false,\n isPlaying: false,\n isLoading: false,\n progress: 0,\n muted: initialMuted,\n volume: 1,\n isPiP: false,\n isMobile: false,\n controlsVisible: false,\n showThumbnail: true,\n isInView: false,\n })\n\n // Mobile detection\n const checkMobile = useMemo(\n () =>\n debounce(() => {\n setState((prev) => ({ ...prev, isMobile: window.innerWidth <= 768 }))\n }, 200),\n []\n )\n\n // Event Handlers\n const handleTouchStart = useCallback(() => {\n if (state.isMobile) {\n setState((prev) => ({ ...prev, controlsVisible: !prev.controlsVisible }))\n }\n }, [state.isMobile])\n\n const handleHoverStart = useCallback(() => {\n if (!state.isMobile) {\n console.log(\"Hover start\")\n setState((prev) => ({ ...prev, isHovering: true }))\n onHoverStart?.()\n }\n }, [state.isMobile, onHoverStart])\n\n const handleHoverEnd = useCallback(() => {\n if (!state.isMobile) {\n console.log(\"Hover end\")\n setState((prev) => ({ ...prev, isHovering: false }))\n onHoverEnd?.()\n }\n }, [state.isMobile, onHoverEnd])\n\n // Video Playback Controls\n const playVideo = useCallback(() => {\n if (!videoRef.current || !state.isInView) {\n console.log(\"PlayVideo blocked:\", {\n hasVideo: !!videoRef.current,\n isInView: state.isInView,\n })\n return\n }\n\n const video = videoRef.current\n console.log(\"Attempting to play video:\", {\n readyState: video.readyState,\n paused: video.paused,\n currentSrc: video.currentSrc,\n })\n\n // Reset loading state when attempting to play\n setState((prev) => ({ ...prev, isLoading: true }))\n\n // Ensure video is ready to play\n const attemptPlay = () => {\n video\n .play()\n .then(() => {\n console.log(\"Video play success\")\n setState((prev) => ({\n ...prev,\n isLoading: false,\n isPlaying: true,\n showThumbnail: false,\n }))\n })\n .catch((error) => {\n console.log(\"Video play error:\", error.name)\n if (error.name === \"NotAllowedError\") {\n // User interaction required - show thumbnail\n setState((prev) => ({\n ...prev,\n isLoading: false,\n showThumbnail: true,\n }))\n } else if (error.name !== \"AbortError\") {\n console.error(\"HoverVideoPlayer: Playback error:\", error)\n setState((prev) => ({\n ...prev,\n isLoading: false,\n showThumbnail: true,\n }))\n }\n })\n }\n\n if (video.readyState >= 3) {\n console.log(\"Video ready, attempting immediate play\")\n attemptPlay()\n } else {\n console.log(\"Video not ready, waiting for canplay event\")\n const handleCanPlay = () => {\n console.log(\"Canplay event fired\")\n video.removeEventListener(\"canplay\", handleCanPlay)\n attemptPlay()\n }\n video.addEventListener(\"canplay\", handleCanPlay)\n }\n }, [state.isInView])\n\n const pauseVideo = useCallback(() => {\n if (!videoRef.current) return\n\n videoRef.current.pause()\n setState((prev) => ({\n ...prev,\n isPlaying: false,\n showThumbnail: true,\n }))\n\n if (restartOnPaused) {\n videoRef.current.currentTime = playbackRangeStart || 0\n }\n }, [restartOnPaused, playbackRangeStart])\n\n // Effects\n\n // Mobile detection effect\n useEffect(() => {\n checkMobile()\n window.addEventListener(\"resize\", checkMobile)\n return () => {\n window.removeEventListener(\"resize\", checkMobile)\n checkMobile.cancel()\n }\n }, [checkMobile])\n\n // Intersection Observer effect\n useEffect(() => {\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n const isNowInView = entry.isIntersecting\n setState((prev) => ({ ...prev, isInView: isNowInView }))\n\n if (!isNowInView) {\n pauseVideo()\n }\n })\n },\n {\n root: null,\n rootMargin: \"50px\",\n threshold: 0.1,\n }\n )\n\n if (containerRef.current) {\n observer.observe(containerRef.current)\n }\n\n return () => observer.disconnect()\n }, [pauseVideo])\n\n // Video event handlers effect\n useEffect(() => {\n const video = videoRef.current\n if (!video || !state.isInView) return\n\n const handlers = {\n loadstart: () => {\n console.log(\"Video loadstart\")\n setState((prev) => ({ ...prev, isLoading: true }))\n },\n loadeddata: () => {\n console.log(\"Video loadeddata\", {\n readyState: video.readyState,\n duration: video.duration,\n paused: video.paused,\n isHovering: state.isHovering,\n controlsVisible: state.controlsVisible,\n })\n setState((prev) => ({ ...prev, isLoading: false }))\n // Only attempt to play if we're hovering or controls are visible\n if (state.isHovering || state.controlsVisible) {\n console.log(\"Video loaded and hover/controls active, attempting play\")\n playVideo()\n }\n },\n canplay: () => {\n console.log(\"Video canplay event\")\n },\n playing: () => {\n console.log(\"Video playing\")\n setState((prev) => ({\n ...prev,\n isLoading: false,\n isPlaying: true,\n showThumbnail: false,\n }))\n },\n pause: () => {\n console.log(\"Video paused\")\n setState((prev) => ({\n ...prev,\n isPlaying: false,\n showThumbnail: true,\n }))\n },\n error: (e: Event) => {\n const videoError = (e.target as HTMLVideoElement).error\n console.error(\"Video error:\", {\n code: videoError?.code,\n message: videoError?.message,\n currentSrc: video.currentSrc,\n })\n setState((prev) => ({\n ...prev,\n isLoading: false,\n showThumbnail: true,\n }))\n },\n }\n\n // Add event listeners\n Object.entries(handlers).forEach(([event, handler]) => {\n video.addEventListener(event, handler)\n })\n\n // Set initial source and load the video\n if (!video.src) {\n console.log(\"Setting video source:\", videoSrc)\n video.src = videoSrc\n video.load()\n }\n\n return () => {\n Object.entries(handlers).forEach(([event, handler]) => {\n video.removeEventListener(event, handler)\n })\n }\n }, [\n state.isInView,\n state.isHovering,\n state.controlsVisible,\n playVideo,\n videoSrc,\n ])\n\n // Playback control effect\n useEffect(() => {\n console.log(\"Playback control effect:\", {\n isInView: state.isInView,\n isMobile: state.isMobile,\n controlsVisible: state.controlsVisible,\n isHovering: state.isHovering,\n })\n\n if (!state.isInView) {\n console.log(\"Not in view, skipping playback\")\n return\n }\n\n let playbackTimeout: NodeJS.Timeout | undefined\n\n if (state.isMobile) {\n if (state.controlsVisible) {\n console.log(\"Mobile controls visible, playing\")\n playVideo()\n } else {\n console.log(\"Mobile controls hidden, pausing\")\n pauseVideo()\n }\n } else if (state.isHovering) {\n console.log(\n \"Hovering, scheduling playback with delay:\",\n playbackStartDelay\n )\n playbackTimeout = setTimeout(() => {\n console.log(\"Playback delay completed, attempting to play\")\n playVideo()\n }, playbackStartDelay)\n } else {\n console.log(\"Not hovering, pausing\")\n pauseVideo()\n }\n\n return () => {\n if (playbackTimeout) {\n console.log(\"Clearing playback timeout\")\n clearTimeout(playbackTimeout)\n }\n }\n }, [\n state.isInView,\n state.isMobile,\n state.controlsVisible,\n state.isHovering,\n playVideo,\n pauseVideo,\n playbackStartDelay,\n ])\n\n // Volume effect\n useEffect(() => {\n const video = videoRef.current\n if (!video) return\n\n video.muted = state.muted\n video.volume = state.volume\n }, [state.muted, state.volume])\n\n // Control handlers\n const togglePlayPause = useCallback(() => {\n if (state.isPlaying) {\n pauseVideo()\n } else {\n playVideo()\n }\n }, [state.isPlaying, pauseVideo, playVideo])\n\n const toggleMute = useCallback(() => {\n setState((prev) => ({ ...prev, muted: !prev.muted }))\n }, [])\n\n const togglePiP = useCallback(async () => {\n if (!document.pictureInPictureEnabled) return\n\n try {\n if (document.pictureInPictureElement) {\n await document.exitPictureInPicture()\n setState((prev) => ({ ...prev, isPiP: false }))\n } else if (videoRef.current) {\n await videoRef.current.requestPictureInPicture()\n setState((prev) => ({ ...prev, isPiP: true }))\n }\n } catch (error) {\n console.error(\"PiP error:\", error)\n }\n }, [])\n\n // Context value\n const contextValue = useMemo(\n () => ({\n isPlaying: state.isPlaying,\n isHovering: state.isHovering,\n isLoading: state.isLoading,\n progress: state.progress,\n volume: state.volume,\n muted: state.muted,\n isPiP: state.isPiP,\n isMobile: state.isMobile,\n controlsVisible: state.controlsVisible,\n videoRef,\n togglePlay: togglePlayPause,\n toggleMute,\n togglePiP,\n setVolume: (value) => setState((prev) => ({ ...prev, volume: value })),\n setProgress: (value) =>\n setState((prev) => ({ ...prev, progress: value })),\n cropTop,\n cropBottom,\n thumbnailSrc,\n }),\n [\n state,\n togglePlayPause,\n toggleMute,\n togglePiP,\n cropTop,\n cropBottom,\n thumbnailSrc,\n ]\n )\n\n // Render\n return (\n \n \n {/* Video Element */}\n {state.isInView && (\n \n )}\n\n {/* Thumbnail */}\n {thumbnailSrc && (state.showThumbnail || !state.isInView) && (\n \n )}\n\n {/* Overlays */}\n {pausedOverlay && (\n \n {pausedOverlay}\n \n )}\n {loadingOverlay && (\n \n {loadingOverlay}\n \n )}\n {hoverOverlay && !state.isMobile && (\n \n {hoverOverlay}\n \n )}\n\n {/* Controls */}\n {enableControls && (\n \n
\n \n \n \n
\n \n
\n )}\n \n
\n )\n}\n\n/**\n * Video component that handles the actual video element\n */\nconst HoverVideoPlayerVideo: React.FC<{\n src: string\n unloadVideoOnPaused: boolean\n loop: boolean\n preload: string\n}> = ({ src, unloadVideoOnPaused, loop, preload }) => {\n const { videoRef, muted, cropTop, cropBottom, isHovering } =\n useHoverVideoPlayer()\n const isVimeoVideo = isVimeoUrl(src)\n const containerRef = useRef(null)\n const playerRef = useRef(null)\n\n useEffect(() => {\n if (!isVimeoVideo || !containerRef.current) return\n\n const videoId = src.split(\"/\").pop() || \"\"\n let player: VimeoPlayer | null = null\n\n loadVimeoSDK()\n .then((Vimeo) => {\n if (!containerRef.current) return\n\n player = new Vimeo.Player(containerRef.current, {\n id: videoId,\n autopause: false,\n muted,\n loop,\n responsive: true,\n controls: false,\n autoplay: false,\n volume: 1,\n })\n\n playerRef.current = player\n\n player.ready().then(() => {\n console.log(\"Vimeo video loaded\")\n if (isHovering) {\n player?.play()\n }\n })\n\n // Add hover effect handlers\n const handlePlay = () => {\n if (player && isHovering) {\n player.play().catch((error) => {\n console.error(\"Vimeo play error:\", error)\n })\n }\n }\n\n const handlePause = () => {\n if (player) {\n player.pause().catch((error) => {\n console.error(\"Vimeo pause error:\", error)\n })\n }\n }\n\n // Watch for hover state changes\n if (isHovering) {\n handlePlay()\n } else {\n handlePause()\n }\n\n player.on(\"play\", () => console.log(\"Vimeo video playing\"))\n player.on(\"pause\", () => console.log(\"Vimeo video paused\"))\n player.on(\"error\", (err) => {\n console.error(\"Vimeo player error:\", err)\n })\n })\n .catch((error) => {\n console.error(\"Failed to load Vimeo SDK:\", error)\n })\n\n return () => {\n if (player) {\n player.destroy()\n playerRef.current = null\n }\n }\n }, [isVimeoVideo, src, muted, loop, isHovering])\n\n if (isVimeoVideo) {\n return
\n }\n\n return (\n \n )\n}\n\n/**\n * Thumbnail component shown when video is not playing\n */\nconst HoverVideoPlayerThumbnail: React.FC<{ src: string }> = ({ src }) => {\n const { cropTop, cropBottom, isHovering, isLoading, isPlaying } =\n useHoverVideoPlayer()\n\n // Only show thumbnail when not playing and not hovering, or when loading\n if ((isPlaying || isHovering) && !isLoading) return null\n\n return (\n \n \n
\n )\n}\n\n/**\n * Controls overlay with play/pause, volume, and progress bar\n */\nconst HoverVideoPlayerControls: React.FC<{ children: React.ReactNode }> = ({\n children,\n}) => {\n const { isHovering, isMobile, controlsVisible } = useHoverVideoPlayer()\n\n const shouldShowControls = isMobile ? controlsVisible : isHovering\n\n return (\n \n {children}\n \n )\n}\n\n/**\n * Overlay shown when hovering over the video\n */\nconst HoverVideoPlayerHoverOverlay: React.FC<{ children: React.ReactNode }> = ({\n children,\n}) => {\n const { isHovering, isLoading } = useHoverVideoPlayer()\n\n return (\n \n {isHovering && !isLoading && (\n \n {children}\n \n )}\n \n )\n}\n\n/**\n * Overlay shown when video is paused\n */\nconst HoverVideoPlayerPausedOverlay: React.FC<{\n children: React.ReactNode\n}> = ({ children }) => {\n const { isPlaying, isLoading, isHovering } = useHoverVideoPlayer()\n\n return (\n \n {!isPlaying && !isLoading && !isHovering && (\n \n {children}\n \n )}\n \n )\n}\n\n/**\n * Overlay shown while video is loading\n */\nconst HoverVideoPlayerLoadingOverlay: React.FC<{\n children: React.ReactNode\n}> = ({ children }) => {\n const { isLoading } = useHoverVideoPlayer()\n\n return (\n \n {isLoading && (\n \n {children}\n \n )}\n \n )\n}\n\n/**\n * Play/Pause button component\n */\nconst HoverVideoPlayerPlayPauseButton: React.FC = () => {\n const { isPlaying, togglePlay } = useHoverVideoPlayer()\n\n return (\n \n {isPlaying ? : }\n \n )\n}\n\n/**\n * Volume control component with mute toggle and volume slider\n */\nconst HoverVideoPlayerVolumeControl: React.FC = () => {\n const { muted, toggleMute, volume, setVolume } = useHoverVideoPlayer()\n\n return (\n
\n \n {muted ? (\n \n ) : (\n \n )}\n \n setVolume(value[0] / 100)}\n aria-label=\"Volume\"\n />\n
\n )\n}\n\n/**\n * Progress bar component for video timeline\n */\nconst HoverVideoPlayerProgressBar: React.FC = () => {\n const { progress, setProgress, videoRef } = useHoverVideoPlayer()\n\n return (\n {\n if (videoRef.current) {\n const newTime = (value[0] / 100) * videoRef.current.duration\n videoRef.current.currentTime = newTime\n setProgress(value[0])\n }\n }}\n aria-label=\"Video progress\"\n />\n )\n}\n\n/**\n * Picture-in-Picture toggle button\n */\nconst HoverVideoPlayerPiPButton: React.FC = () => {\n const { isPiP, togglePiP } = useHoverVideoPlayer()\n\n return (\n \n {isPiP ? (\n \n ) : (\n \n )}\n \n )\n}\n\n/**\n * Wrapper component for maintaining aspect ratio with padding\n */\nconst HoverVideoPaddingWrapper: React.FC<{\n width: number\n height: number\n className?: string\n children?: ReactNode\n}> = ({ width, height, className, children }) => {\n return (\n \n
\n {children}\n
\n )\n}\n\n// Add display names for better debugging\nHoverVideoPlayerControls.displayName = \"HoverVideoPlayerControls\"\nHoverVideoPaddingWrapper.displayName = \"HoverVideoPaddingWrapper\"\nHoverVideoPlayerHoverOverlay.displayName = \"HoverVideoPlayerHoverOverlay\"\nHoverVideoPlayerPausedOverlay.displayName = \"HoverVideoPlayerPausedOverlay\"\nHoverVideoPlayerLoadingOverlay.displayName = \"HoverVideoPlayerLoadingOverlay\"\nHoverVideoPlayerPlayPauseButton.displayName = \"HoverVideoPlayerPlayPauseButton\"\nHoverVideoPlayerVolumeControl.displayName = \"HoverVideoPlayerVolumeControl\"\nHoverVideoPlayerProgressBar.displayName = \"HoverVideoPlayerProgressBar\"\nHoverVideoPlayerPiPButton.displayName = \"HoverVideoPlayerPiPButton\"\n\nexport {\n HoverVideoPlayer,\n HoverVideoPlayerControls,\n HoverVideoPlayerHoverOverlay,\n HoverVideoPlayerPausedOverlay,\n HoverVideoPlayerLoadingOverlay,\n HoverVideoPlayerPlayPauseButton,\n HoverVideoPlayerVolumeControl,\n HoverVideoPlayerProgressBar,\n HoverVideoPlayerPiPButton,\n HoverVideoPaddingWrapper,\n}\n\nexport default HoverVideoPlayer\n" + } + ], + "type": "components:ui" +} \ No newline at end of file diff --git a/apps/www/public/registry/styles/default/loading-carousel.json b/apps/www/public/registry/styles/default/loading-carousel.json new file mode 100644 index 0000000..ac10a0a --- /dev/null +++ b/apps/www/public/registry/styles/default/loading-carousel.json @@ -0,0 +1,13 @@ +{ + "name": "loading-carousel", + "dependencies": [ + "framer-motion" + ], + "files": [ + { + "name": "loading-carousel.tsx", + "content": "// npm i embla-carousel-autoplay framer-motion lucide-react\n// npx shadcn@latest add carousel\n\"use client\"\n\nimport React, { useCallback, useEffect, useState, type JSX } from \"react\"\nimport Image from \"next/image\"\nimport Autoplay from \"embla-carousel-autoplay\"\nimport { ChevronRight } from \"lucide-react\"\nimport {\n AnimatePresence,\n MotionProps,\n Variants,\n motion,\n useAnimation,\n} from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n type CarouselApi,\n} from \"@/components/ui/carousel\"\n\ninterface Tip {\n text: string\n image: string\n url?: string\n}\n\ninterface LoadingCarouselProps {\n tips?: Tip[]\n className?: string\n autoplayInterval?: number\n showNavigation?: boolean\n showIndicators?: boolean\n showProgress?: boolean\n aspectRatio?: \"video\" | \"square\" | \"wide\"\n textPosition?: \"top\" | \"bottom\"\n onTipChange?: (index: number) => void\n backgroundTips?: boolean\n backgroundGradient?: boolean\n shuffleTips?: boolean\n animateText?: boolean\n}\n\nconst defaultTips: Tip[] = [\n {\n text: \"Backend snippets. Shadcn style headless components.. but for your backend.\",\n image: \"/placeholders/cult-snips.png\",\n url: \"https://www.newcult.co/backend\",\n },\n {\n text: \"Create your first directory app today. AI batch scripts to process 100s of urls in seconds.\",\n image: \"/placeholders/cult-dir.png\",\n url: \"https://www.newcult.co/templates/cult-seo\",\n },\n {\n text: \"Cult landing page template. Framer motion, shadcn, and tailwind.\",\n image: \"/placeholders/cult-rune.png\",\n url: \"https://www.newcult.co/templates/cult-landing-page\",\n },\n {\n text: \"Vector embeddings, semantic search, and chat based vector retrieval on easy mode.\",\n image: \"/placeholders/cult-manifest.png\",\n url: \"https://www.newcult.co/templates/manifest\",\n },\n {\n text: \"SEO analysis app. Scraping, analysis, insights, and AI recommendations.\",\n image: \"/placeholders/cult-seo.png\",\n url: \"https://www.newcult.co/templates/cult-seo\",\n },\n]\n\nfunction shuffleArray(array: T[]): T[] {\n const shuffled = [...array]\n for (let i = shuffled.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1))\n ;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]\n }\n return shuffled\n}\n\nconst carouselVariants: Variants = {\n enter: (direction: number) => ({\n x: direction > 0 ? \"100%\" : \"-100%\",\n opacity: 0,\n }),\n center: {\n x: 0,\n opacity: 1,\n },\n exit: (direction: number) => ({\n x: direction < 0 ? \"100%\" : \"-100%\",\n opacity: 0,\n }),\n}\n\nconst textVariants: Variants = {\n hidden: { opacity: 0, y: 20 },\n visible: { opacity: 1, y: 0, transition: { delay: 0.3, duration: 0.5 } },\n}\n\nconst aspectRatioClasses = {\n video: \"aspect-video\",\n square: \"aspect-square\",\n wide: \"aspect-[2/1]\",\n}\n\nexport function LoadingCarousel({\n onTipChange,\n className,\n tips = defaultTips,\n showProgress = true,\n aspectRatio = \"video\",\n showNavigation = false,\n showIndicators = true,\n backgroundTips = false,\n textPosition = \"bottom\",\n autoplayInterval = 4500,\n backgroundGradient = false,\n shuffleTips = false,\n animateText = true,\n}: LoadingCarouselProps) {\n const [progress, setProgress] = useState(0)\n const [api, setApi] = useState()\n const [current, setCurrent] = useState(0)\n const [direction, setDirection] = useState(0)\n const controls = useAnimation()\n const [displayTips] = useState(() =>\n shuffleTips ? shuffleArray(tips) : tips\n )\n\n const autoplay = Autoplay({\n delay: autoplayInterval,\n stopOnInteraction: false,\n })\n\n useEffect(() => {\n if (!api) {\n return\n }\n\n setCurrent(api.selectedScrollSnap())\n setDirection(\n api.scrollSnapList().indexOf(api.selectedScrollSnap()) - current\n )\n\n const onSelect = () => {\n const newIndex = api.selectedScrollSnap()\n setCurrent(newIndex)\n setDirection(api.scrollSnapList().indexOf(newIndex) - current)\n onTipChange?.(newIndex)\n }\n\n api.on(\"select\", onSelect)\n\n return () => {\n api.off(\"select\", onSelect)\n }\n }, [api, current, onTipChange])\n\n useEffect(() => {\n if (!showProgress) return\n\n const timer = setInterval(() => {\n setProgress((oldProgress) => {\n if (oldProgress === 100) {\n return 0\n }\n const diff = 2 // Constant increment for smoother progress\n return Math.min(oldProgress + diff, 100)\n })\n }, autoplayInterval / 50)\n\n return () => {\n clearInterval(timer)\n }\n }, [showProgress, autoplayInterval])\n\n useEffect(() => {\n if (progress === 100) {\n controls.start({ scaleX: 0 }).then(() => {\n setProgress(0)\n controls.set({ scaleX: 1 })\n })\n } else {\n controls.start({ scaleX: progress / 100 })\n }\n }, [progress, controls])\n\n const handleSelect = useCallback(\n (index: number) => {\n api?.scrollTo(index)\n },\n [api]\n )\n\n return (\n \n
\n \n \n \n {(displayTips || []).map((tip, index) => (\n \n \n \n {backgroundGradient && (\n
\n )}\n\n {backgroundTips ? (\n \n {displayTips[current]?.url ? (\n \n

\n {tip.text}\n

\n \n ) : (\n

\n {tip.text}\n

\n )}\n \n ) : null}\n \n \n ))}\n \n \n {showNavigation && (\n <>\n \n \n \n )}\n \n \n \n {showIndicators && (\n
\n {(displayTips || []).map((_, index) => (\n handleSelect(index)}\n aria-label={`Go to tip ${index + 1}`}\n />\n ))}\n
\n )}\n
\n {backgroundTips ? (\n \n Tip {current + 1}/{displayTips?.length || 0}\n \n ) : (\n
\n {displayTips[current]?.url ? (\n \n {animateText ? (\n \n {displayTips[current]?.text}\n \n ) : (\n displayTips[current]?.text\n )}\n \n ) : (\n \n {animateText ? (\n \n {displayTips[current]?.text}\n \n ) : (\n displayTips[current]?.text\n )}\n \n )}\n
\n )}\n {backgroundTips && }\n
\n
\n {showProgress && (\n \n )}\n
\n \n \n )\n}\n\n// Credit -> https://motion-primitives.com/docs/text-scramble\n// https://x.com/Ibelick\ntype TextScrambleProps = {\n children: string\n duration?: number\n speed?: number\n characterSet?: string\n as?: React.ElementType\n className?: string\n trigger?: boolean\n onScrambleComplete?: () => void\n} & MotionProps\n\nconst defaultChars =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\"\n\nfunction TextScramble({\n children,\n duration = 0.8,\n speed = 0.04,\n characterSet = defaultChars,\n className,\n as: Component = \"p\",\n trigger = true,\n onScrambleComplete,\n ...props\n}: TextScrambleProps) {\n const MotionComponent = motion.create(\n Component as keyof JSX.IntrinsicElements\n )\n const [displayText, setDisplayText] = useState(children)\n const [isAnimating, setIsAnimating] = useState(false)\n const text = children\n\n const scramble = async () => {\n if (isAnimating) return\n setIsAnimating(true)\n\n const steps = duration / speed\n let step = 0\n\n const interval = setInterval(() => {\n let scrambled = \"\"\n const progress = step / steps\n\n for (let i = 0; i < text.length; i++) {\n if (text[i] === \" \") {\n scrambled += \" \"\n continue\n }\n\n if (progress * text.length > i) {\n scrambled += text[i]\n } else {\n scrambled +=\n characterSet[Math.floor(Math.random() * characterSet.length)]\n }\n }\n\n setDisplayText(scrambled)\n step++\n\n if (step > steps) {\n clearInterval(interval)\n setDisplayText(text)\n setIsAnimating(false)\n onScrambleComplete?.()\n }\n }, speed * 1000)\n }\n\n useEffect(() => {\n if (!trigger) return\n\n scramble()\n }, [trigger])\n\n return (\n \n {displayText}\n \n )\n}\n\nexport default LoadingCarousel\n" + } + ], + "type": "components:ui" +} \ No newline at end of file diff --git a/apps/www/public/registry/styles/default/logo-carousel.json b/apps/www/public/registry/styles/default/logo-carousel.json new file mode 100644 index 0000000..d9fcbc2 --- /dev/null +++ b/apps/www/public/registry/styles/default/logo-carousel.json @@ -0,0 +1,13 @@ +{ + "name": "logo-carousel", + "dependencies": [ + "framer-motion" + ], + "files": [ + { + "name": "logo-carousel.tsx", + "content": "\"use client\"\n\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useState,\n type SVGProps,\n} from \"react\"\nimport { AnimatePresence, motion } from \"motion/react\"\n\n// Define the structure for our logo objects\ninterface Logo {\n name: string\n id: number\n img: React.ComponentType>\n}\n\n// Utility function to randomly shuffle an array\n// This is used to mix up the order of logos for a more dynamic display\nconst shuffleArray = (array: T[]): T[] => {\n const shuffled = [...array]\n for (let i = shuffled.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1))\n ;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]\n }\n return shuffled\n}\n\n// Utility function to distribute logos across multiple columns\n// This ensures each column has a balanced number of logos\nconst distributeLogos = (allLogos: Logo[], columnCount: number): Logo[][] => {\n const shuffled = shuffleArray(allLogos)\n const columns: Logo[][] = Array.from({ length: columnCount }, () => [])\n\n // Distribute logos evenly across columns\n shuffled.forEach((logo, index) => {\n columns[index % columnCount].push(logo)\n })\n\n // Ensure all columns have the same number of logos by filling shorter columns\n const maxLength = Math.max(...columns.map((col) => col.length))\n columns.forEach((col) => {\n while (col.length < maxLength) {\n col.push(shuffled[Math.floor(Math.random() * shuffled.length)])\n }\n })\n\n return columns\n}\n\n// Props for the LogoColumn component\ninterface LogoColumnProps {\n logos: Logo[]\n index: number\n currentTime: number\n}\n\n// LogoColumn component: Displays a single column of animated logos\nconst LogoColumn: React.FC = React.memo(\n ({ logos, index, currentTime }) => {\n const cycleInterval = 2000 // Time each logo is displayed (in milliseconds)\n const columnDelay = index * 200 // Stagger the start of each column's animation\n // Calculate which logo should be displayed based on the current time\n const adjustedTime =\n (currentTime + columnDelay) % (cycleInterval * logos.length)\n const currentIndex = Math.floor(adjustedTime / cycleInterval)\n\n // Memoize the current logo to prevent unnecessary re-renders\n const CurrentLogo = useMemo(\n () => logos[currentIndex].img,\n [logos, currentIndex]\n )\n\n return (\n // Framer Motion component for the column container\n \n {/* AnimatePresence enables animation of components that are removed from the DOM */}\n \n {/* Framer Motion component for each logo */}\n \n \n \n \n \n )\n }\n)\n\n// Main LogoCarousel component\nfunction LogoCarousel({ columnCount = 2 }: { columnCount?: number }) {\n const [logoSets, setLogoSets] = useState([])\n const [currentTime, setCurrentTime] = useState(0)\n\n // Memoize the array of logos to prevent unnecessary re-renders\n const allLogos: Logo[] = useMemo(\n () => [\n { name: \"Apple\", id: 1, img: AppleIcon },\n { name: \"CEO Supabase\", id: 2, img: SupabaseIcon },\n { name: \"Vercel\", id: 3, img: VercelIcon },\n { name: \"Lowes\", id: 4, img: LowesIcon },\n { name: \"Ally\", id: 5, img: AllyLogo },\n { name: \"Pierre\", id: 6, img: PierreIcon },\n { name: \"BMW\", id: 7, img: BMWIcon },\n { name: \"Claude\", id: 8, img: ClaudeAIIcon },\n { name: \"Nextjs\", id: 9, img: NextjsIcon },\n { name: \"Tailwind\", id: 10, img: TailwindCSSIcon },\n { name: \"Upstash\", id: 11, img: UpstashIcon },\n { name: \"Typescript\", id: 12, img: TypeScriptIcon },\n { name: \"Stripe\", id: 13, img: StripeIcon },\n { name: \"OpenAI\", id: 14, img: OpenAIIconBlack },\n ],\n []\n )\n\n // Distribute logos across columns when the component mounts\n useEffect(() => {\n const distributedLogos = distributeLogos(allLogos, columnCount)\n setLogoSets(distributedLogos)\n }, [allLogos])\n\n // Function to update the current time (used for logo cycling)\n const updateTime = useCallback(() => {\n setCurrentTime((prevTime) => prevTime + 100)\n }, [])\n\n // Set up an interval to update the time every 100ms\n useEffect(() => {\n const intervalId = setInterval(updateTime, 100)\n return () => clearInterval(intervalId)\n }, [updateTime])\n\n // Render the logo columns\n return (\n
\n {logoSets.map((logos, index) => (\n \n ))}\n
\n )\n}\n\nfunction AppleIcon(props: SVGProps) {\n return (\n \n \n \n )\n}\n\nfunction PierreIcon(props: SVGProps) {\n return (\n \n \n \n )\n}\n\nfunction BMWIcon(props: SVGProps) {\n return (\n \n \n \n \n \n \n \n \n \n )\n}\n\nfunction LowesIcon(props: SVGProps) {\n return (\n \n \n \n \n \n \n \n \n \n \n \n )\n}\n\nfunction AllyLogo(props: SVGProps) {\n return (\n \n \n \n \n \n \n )\n}\n\nfunction VercelIcon(props: SVGProps) {\n return (\n \n \n \n )\n}\n\nconst StripeIcon = (props: SVGProps) => (\n \n \n \n)\n\nconst TypeScriptIcon = (props: SVGProps) => (\n \n \n \n \n)\n\nconst ClaudeAIIcon = (props: SVGProps) => (\n \n \n \n \n)\n\nfunction SupabaseIcon(props: SVGProps) {\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n )\n}\n\nfunction OpenAIIconBlack(props: SVGProps) {\n return (\n \n \n \n )\n}\n\nfunction UpstashIcon(props: SVGProps) {\n return (\n \n \n \n \n \n \n \n )\n}\n\nconst TailwindCSSIcon = (props: SVGProps) => (\n \n \n \n \n \n \n \n \n \n \n)\n\nconst NextjsIcon = (props: SVGProps) => (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n)\n\nexport { LogoCarousel }\nexport default LogoCarousel\n" + } + ], + "type": "components:ui" +} \ No newline at end of file diff --git a/apps/www/public/registry/styles/default/popover-form.json b/apps/www/public/registry/styles/default/popover-form.json index 697eee8..4e4db41 100644 --- a/apps/www/public/registry/styles/default/popover-form.json +++ b/apps/www/public/registry/styles/default/popover-form.json @@ -6,7 +6,7 @@ "files": [ { "name": "popover-form.tsx", - "content": "\"use client\"\n\nimport { ReactNode, RefObject, useEffect, useRef } from \"react\"\nimport { AnimatePresence, motion } from \"framer-motion\"\nimport { ChevronUp, Loader } from \"lucide-react\"\n\ntype PopoverFormProps = {\n open: boolean\n setOpen: (open: boolean) => void\n openChild?: ReactNode\n successChild?: ReactNode\n showSuccess: boolean\n width?: string\n height?: string\n showCloseButton?: boolean\n title: string\n}\n\nexport function PopoverForm({\n open,\n setOpen,\n openChild,\n showSuccess,\n successChild,\n width = \"364px\",\n height = \"192px\",\n title = \"Feedback\",\n showCloseButton = false,\n}: PopoverFormProps) {\n const ref = useRef(null)\n useClickOutside(ref, () => setOpen(false))\n\n return (\n \n setOpen(true)}\n style={{ borderRadius: 8 }}\n className=\"flex h-9 items-center border bg-white dark:bg-[#121212] px-3 text-sm font-medium outline-none\"\n >\n {title}\n \n \n {open && (\n \n \n {title}\n \n\n {showCloseButton && (\n
\n setOpen(false)}\n className=\"absolute z-10 -mt-1 flex items-center justify-center w-[10px] h-[6px] text-muted-foreground hover:text-foreground focus:outline-none rounded-full \"\n aria-label=\"Close\"\n >\n \n \n\n \n
\n )}\n\n \n {showSuccess ? (\n \n {successChild || }\n \n ) : (\n \n {openChild}\n \n )}\n \n \n )}\n
\n \n )\n}\n\nexport function PopoverFormButton({\n loading,\n text = \"submit\",\n}: {\n loading: boolean\n text: string\n}) {\n return (\n \n \n \n {loading ? (\n \n ) : (\n {text}\n )}\n \n \n \n )\n}\n\nconst useClickOutside = (\n ref: RefObject,\n handleOnClickOutside: (event: MouseEvent | TouchEvent) => void\n) => {\n useEffect(() => {\n const listener = (event: MouseEvent | TouchEvent) => {\n if (!ref.current || ref.current.contains(event.target as Node)) {\n return\n }\n handleOnClickOutside(event)\n }\n document.addEventListener(\"mousedown\", listener)\n document.addEventListener(\"touchstart\", listener)\n return () => {\n document.removeEventListener(\"mousedown\", listener)\n document.removeEventListener(\"touchstart\", listener)\n }\n }, [ref, handleOnClickOutside])\n}\n\nexport function PopoverFormSuccess({\n title = \"Success\",\n description = \"Thank you for your submission\",\n}) {\n return (\n <>\n \n \n \n \n

{title}

\n

\n {description}\n

\n \n )\n}\n\nexport function PopoverFormSeparator({\n width = 352,\n height = 2,\n}: {\n width?: number | string\n height?: number\n}) {\n return (\n \n \n \n )\n}\n\nfunction PopoverFormCutOutTopIcon({\n width = 44,\n height = 30,\n}: {\n width?: number\n height?: number\n}) {\n const aspectRatio = 6 / 12\n const calculatedHeight = width * aspectRatio\n const calculatedWidth = height / aspectRatio\n\n const finalWidth = Math.min(width, calculatedWidth)\n const finalHeight = Math.min(height, calculatedHeight)\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n )\n}\n\nexport function PopoverFormCutOutLeftIcon() {\n return (\n \n \n \n \n \n \n \n \n \n \n \n )\n}\n\nexport function PopoverFormCutOutRightIcon() {\n return (\n \n \n \n \n \n \n \n \n \n \n \n )\n}\n\nexport default PopoverForm\n" + "content": "\"use client\"\n\nimport { ReactNode, RefObject, useEffect, useRef } from \"react\"\nimport { ChevronUp, Loader } from \"lucide-react\"\nimport { AnimatePresence, motion } from \"motion/react\"\n\ntype PopoverFormProps = {\n open: boolean\n setOpen: (open: boolean) => void\n openChild?: ReactNode\n successChild?: ReactNode\n showSuccess: boolean\n width?: string\n height?: string\n showCloseButton?: boolean\n title: string\n}\n\nexport function PopoverForm({\n open,\n setOpen,\n openChild,\n showSuccess,\n successChild,\n width = \"364px\",\n height = \"192px\",\n title = \"Feedback\",\n showCloseButton = false,\n}: PopoverFormProps) {\n const ref = useRef(null)\n useClickOutside(ref, () => setOpen(false))\n\n return (\n \n setOpen(true)}\n style={{ borderRadius: 8 }}\n className=\"flex h-9 items-center border bg-white dark:bg-[#121212] px-3 text-sm font-medium outline-none\"\n >\n {title}\n \n \n {open && (\n \n \n {title}\n \n\n {showCloseButton && (\n
\n setOpen(false)}\n className=\"absolute z-10 -mt-1 flex items-center justify-center w-[10px] h-[6px] text-muted-foreground hover:text-foreground focus:outline-none rounded-full \"\n aria-label=\"Close\"\n >\n \n \n\n \n
\n )}\n\n \n {showSuccess ? (\n \n {successChild || }\n \n ) : (\n \n {openChild}\n \n )}\n \n \n )}\n
\n \n )\n}\n\nexport function PopoverFormButton({\n loading,\n text = \"submit\",\n}: {\n loading: boolean\n text: string\n}) {\n return (\n \n \n \n {loading ? (\n \n ) : (\n {text}\n )}\n \n \n \n )\n}\n\nconst useClickOutside = (\n ref: RefObject,\n handleOnClickOutside: (event: MouseEvent | TouchEvent) => void\n) => {\n useEffect(() => {\n const listener = (event: MouseEvent | TouchEvent) => {\n if (!ref.current || ref.current.contains(event.target as Node)) {\n return\n }\n handleOnClickOutside(event)\n }\n document.addEventListener(\"mousedown\", listener)\n document.addEventListener(\"touchstart\", listener)\n return () => {\n document.removeEventListener(\"mousedown\", listener)\n document.removeEventListener(\"touchstart\", listener)\n }\n }, [ref, handleOnClickOutside])\n}\n\nexport function PopoverFormSuccess({\n title = \"Success\",\n description = \"Thank you for your submission\",\n}) {\n return (\n <>\n \n \n \n \n

{title}

\n

\n {description}\n

\n \n )\n}\n\nexport function PopoverFormSeparator({\n width = 352,\n height = 2,\n}: {\n width?: number | string\n height?: number\n}) {\n return (\n \n \n \n )\n}\n\nfunction PopoverFormCutOutTopIcon({\n width = 44,\n height = 30,\n}: {\n width?: number\n height?: number\n}) {\n const aspectRatio = 6 / 12\n const calculatedHeight = width * aspectRatio\n const calculatedWidth = height / aspectRatio\n\n const finalWidth = Math.min(width, calculatedWidth)\n const finalHeight = Math.min(height, calculatedHeight)\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n )\n}\n\nexport function PopoverFormCutOutLeftIcon() {\n return (\n \n \n \n \n \n \n \n \n \n \n \n )\n}\n\nexport function PopoverFormCutOutRightIcon() {\n return (\n \n \n \n \n \n \n \n \n \n \n \n )\n}\n\nexport default PopoverForm\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/popover.json b/apps/www/public/registry/styles/default/popover.json index 142a36f..f1adae7 100644 --- a/apps/www/public/registry/styles/default/popover.json +++ b/apps/www/public/registry/styles/default/popover.json @@ -6,7 +6,7 @@ "files": [ { "name": "popover.tsx", - "content": "\"use client\"\n\nimport React, {\n createContext,\n useContext,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { AnimatePresence, MotionConfig, motion } from \"framer-motion\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst TRANSITION = {\n type: \"spring\",\n bounce: 0.05,\n duration: 0.3,\n}\n\nfunction useClickOutside(\n ref: React.RefObject,\n handler: () => void\n) {\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (ref.current && !ref.current.contains(event.target as Node)) {\n handler()\n }\n }\n\n document.addEventListener(\"mousedown\", handleClickOutside)\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside)\n }\n }, [ref, handler])\n}\n\ninterface PopoverContextType {\n isOpen: boolean\n openPopover: () => void\n closePopover: () => void\n uniqueId: string\n note: string\n setNote: (note: string) => void\n}\n\nconst PopoverContext = createContext(undefined)\n\nfunction usePopover() {\n const context = useContext(PopoverContext)\n if (!context) {\n throw new Error(\"usePopover must be used within a PopoverProvider\")\n }\n return context\n}\n\nfunction usePopoverLogic() {\n const uniqueId = useId()\n const [isOpen, setIsOpen] = useState(false)\n const [note, setNote] = useState(\"\")\n\n const openPopover = () => setIsOpen(true)\n const closePopover = () => {\n setIsOpen(false)\n setNote(\"\")\n }\n\n return { isOpen, openPopover, closePopover, uniqueId, note, setNote }\n}\n\ninterface PopoverRootProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function PopoverRoot({ children, className }: PopoverRootProps) {\n const popoverLogic = usePopoverLogic()\n\n return (\n \n \n \n {children}\n \n \n \n )\n}\n\ninterface PopoverTriggerProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function PopoverTrigger({ children, className }: PopoverTriggerProps) {\n const { openPopover, uniqueId } = usePopover()\n\n return (\n \n \n {children}\n \n \n )\n}\n\ninterface PopoverContentProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function PopoverContent({ children, className }: PopoverContentProps) {\n const { isOpen, closePopover, uniqueId } = usePopover()\n const formContainerRef = useRef(null)\n\n useClickOutside(formContainerRef, closePopover)\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n closePopover()\n }\n }\n\n document.addEventListener(\"keydown\", handleKeyDown)\n\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [closePopover])\n\n return (\n \n {isOpen && (\n \n {children}\n \n )}\n \n )\n}\n\ninterface PopoverFormProps {\n children: React.ReactNode\n onSubmit?: (note: string) => void\n className?: string\n}\n\nexport function PopoverForm({\n children,\n onSubmit,\n className,\n}: PopoverFormProps) {\n const { note, closePopover } = usePopover()\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault()\n onSubmit?.(note)\n closePopover()\n }\n\n return (\n \n {children}\n \n )\n}\n\ninterface PopoverLabelProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function PopoverLabel({ children, className }: PopoverLabelProps) {\n const { uniqueId, note } = usePopover()\n\n return (\n \n {children}\n \n )\n}\n\ninterface PopoverTextareaProps {\n className?: string\n}\n\nexport function PopoverTextarea({ className }: PopoverTextareaProps) {\n const { note, setNote } = usePopover()\n\n return (\n setNote(e.target.value)}\n />\n )\n}\n\ninterface PopoverFooterProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function PopoverFooter({ children, className }: PopoverFooterProps) {\n return (\n \n {children}\n \n )\n}\n\ninterface PopoverCloseButtonProps {\n className?: string\n}\n\nexport function PopoverCloseButton({ className }: PopoverCloseButtonProps) {\n const { closePopover } = usePopover()\n\n return (\n \n \n \n )\n}\n\ninterface PopoverSubmitButtonProps {\n className?: string\n}\n\nexport function PopoverSubmitButton({ className }: PopoverSubmitButtonProps) {\n return (\n \n Submit\n \n )\n}\n\nexport function PopoverHeader({\n children,\n className,\n}: {\n children: React.ReactNode\n className?: string\n}) {\n return (\n \n {children}\n \n )\n}\n\nexport function PopoverBody({\n children,\n className,\n}: {\n children: React.ReactNode\n className?: string\n}) {\n return
{children}
\n}\n\n// New component: PopoverButton\nexport function PopoverButton({\n children,\n onClick,\n className,\n}: {\n children: React.ReactNode\n onClick?: () => void\n className?: string\n}) {\n return (\n \n {children}\n \n )\n}\n" + "content": "\"use client\"\n\nimport React, {\n createContext,\n useContext,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { X } from \"lucide-react\"\nimport { AnimatePresence, MotionConfig, motion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst TRANSITION = {\n type: \"spring\",\n bounce: 0.05,\n duration: 0.3,\n}\n\nfunction useClickOutside(\n ref: React.RefObject,\n handler: () => void\n) {\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (ref.current && !ref.current.contains(event.target as Node)) {\n handler()\n }\n }\n\n document.addEventListener(\"mousedown\", handleClickOutside)\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside)\n }\n }, [ref, handler])\n}\n\ninterface PopoverContextType {\n isOpen: boolean\n openPopover: () => void\n closePopover: () => void\n uniqueId: string\n note: string\n setNote: (note: string) => void\n}\n\nconst PopoverContext = createContext(undefined)\n\nfunction usePopover() {\n const context = useContext(PopoverContext)\n if (!context) {\n throw new Error(\"usePopover must be used within a PopoverProvider\")\n }\n return context\n}\n\nfunction usePopoverLogic() {\n const uniqueId = useId()\n const [isOpen, setIsOpen] = useState(false)\n const [note, setNote] = useState(\"\")\n\n const openPopover = () => setIsOpen(true)\n const closePopover = () => {\n setIsOpen(false)\n setNote(\"\")\n }\n\n return { isOpen, openPopover, closePopover, uniqueId, note, setNote }\n}\n\ninterface PopoverRootProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function PopoverRoot({ children, className }: PopoverRootProps) {\n const popoverLogic = usePopoverLogic()\n\n return (\n \n \n \n {children}\n \n \n \n )\n}\n\ninterface PopoverTriggerProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function PopoverTrigger({ children, className }: PopoverTriggerProps) {\n const { openPopover, uniqueId } = usePopover()\n\n return (\n \n \n {children}\n \n \n )\n}\n\ninterface PopoverContentProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function PopoverContent({ children, className }: PopoverContentProps) {\n const { isOpen, closePopover, uniqueId } = usePopover()\n const formContainerRef = useRef(null)\n\n useClickOutside(formContainerRef, closePopover)\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n closePopover()\n }\n }\n\n document.addEventListener(\"keydown\", handleKeyDown)\n\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [closePopover])\n\n return (\n \n {isOpen && (\n \n {children}\n \n )}\n \n )\n}\n\ninterface PopoverFormProps {\n children: React.ReactNode\n onSubmit?: (note: string) => void\n className?: string\n}\n\nexport function PopoverForm({\n children,\n onSubmit,\n className,\n}: PopoverFormProps) {\n const { note, closePopover } = usePopover()\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault()\n onSubmit?.(note)\n closePopover()\n }\n\n return (\n \n {children}\n \n )\n}\n\ninterface PopoverLabelProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function PopoverLabel({ children, className }: PopoverLabelProps) {\n const { uniqueId, note } = usePopover()\n\n return (\n \n {children}\n \n )\n}\n\ninterface PopoverTextareaProps {\n className?: string\n}\n\nexport function PopoverTextarea({ className }: PopoverTextareaProps) {\n const { note, setNote } = usePopover()\n\n return (\n setNote(e.target.value)}\n />\n )\n}\n\ninterface PopoverFooterProps {\n children: React.ReactNode\n className?: string\n}\n\nexport function PopoverFooter({ children, className }: PopoverFooterProps) {\n return (\n \n {children}\n \n )\n}\n\ninterface PopoverCloseButtonProps {\n className?: string\n}\n\nexport function PopoverCloseButton({ className }: PopoverCloseButtonProps) {\n const { closePopover } = usePopover()\n\n return (\n \n \n \n )\n}\n\ninterface PopoverSubmitButtonProps {\n className?: string\n}\n\nexport function PopoverSubmitButton({ className }: PopoverSubmitButtonProps) {\n return (\n \n Submit\n \n )\n}\n\nexport function PopoverHeader({\n children,\n className,\n}: {\n children: React.ReactNode\n className?: string\n}) {\n return (\n \n {children}\n \n )\n}\n\nexport function PopoverBody({\n children,\n className,\n}: {\n children: React.ReactNode\n className?: string\n}) {\n return
{children}
\n}\n\n// New component: PopoverButton\nexport function PopoverButton({\n children,\n onClick,\n className,\n}: {\n children: React.ReactNode\n onClick?: () => void\n className?: string\n}) {\n return (\n \n {children}\n \n )\n}\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/shader-lens-blur.json b/apps/www/public/registry/styles/default/shader-lens-blur.json index e1603f4..054cc7a 100644 --- a/apps/www/public/registry/styles/default/shader-lens-blur.json +++ b/apps/www/public/registry/styles/default/shader-lens-blur.json @@ -8,7 +8,7 @@ "files": [ { "name": "shader-lens-blur.tsx", - "content": "\"use client\"\n\nimport React, { useCallback, useEffect, useRef, useState } from \"react\"\nimport { motion } from \"framer-motion\"\nimport { atom, useAtom } from \"jotai\"\nimport { useTheme } from \"next-themes\"\nimport * as THREE from \"three\"\n\nconst fragmentShader = `\nvarying vec2 v_texcoord;\n\nuniform vec2 u_mouse;\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\nuniform float u_time;\nuniform vec3 u_color1;\nuniform vec3 u_color2;\nuniform vec3 u_color3;\nuniform vec3 u_color4;\nuniform float u_hoverStrength;\nuniform bool u_invertMouse;\nuniform bool u_isDarkMode;\n\n#define PI 3.1415926535897932384626433832795\n#define TWO_PI 6.2831853071795864769252867665590\n\nvec2 coord(in vec2 p) {\n p = p / u_resolution.xy;\n if (u_resolution.x > u_resolution.y) {\n p.x *= u_resolution.x / u_resolution.y;\n p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n } else {\n p.y *= u_resolution.y / u_resolution.x;\n p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n }\n p -= 0.5;\n p *= vec2(-1.0, 1.0);\n return p;\n}\n\n#define st0 coord(gl_FragCoord.xy)\n#define mx coord(u_mouse)\n\nfloat sdRoundRect(vec2 p, vec2 b, float r) {\n vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n}\n\nfloat sdCircle(in vec2 st, in vec2 center) {\n return length(st - center) * 2.0;\n}\n\nfloat sdPoly(in vec2 p, in float w, in int sides) {\n float a = atan(p.x, p.y) + PI;\n float r = TWO_PI / float(sides);\n float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n return d * 2.0 - w;\n}\n\nfloat aastep(float threshold, float value) {\n float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n return smoothstep(threshold - afwidth, threshold + afwidth, value);\n}\n\nfloat fill(float x, float size, float edge) {\n return 1.0 - smoothstep(size - edge, size + edge, x);\n}\n\nfloat stroke(float x, float size, float w, float edge) {\n float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 st = st0 + 0.5;\n vec2 posMouse = mx + 0.5;\n \n float size = 1.2 + sin(u_time) * 0.1;\n float roundness = 0.4 + sin(u_time * 0.5) * 0.1;\n float borderSize = 0.05 + sin(u_time * 0.7) * 0.02;\n float circleSize = 0.3 + sin(u_time * 0.8) * 0.05;\n float circleEdge = 0.5 + sin(u_time * 0.6) * 0.1;\n \n float sdfCircle = fill(\n sdCircle(st, posMouse),\n circleSize,\n circleEdge\n );\n \n float sdf;\n if (VAR == 0) {\n sdf = sdRoundRect(st, vec2(size), roundness);\n sdf = stroke(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n } else if (VAR == 1) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n } else if (VAR == 2) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = stroke(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n } else if (VAR == 3) {\n sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n }\n \n vec3 gradient = mix(\n mix(u_color1, u_color2, 0.5 + 0.5 * cos(u_time + st.x + 0.0)),\n mix(u_color3, u_color4, 0.5 + 0.5 * cos(u_time + st.y + 2.0)),\n 0.5 + 0.5 * cos(u_time + st.x + st.y + 4.0)\n );\n \n vec3 shapeColor = sdf * gradient;\n \n float mouseEffect = u_invertMouse ? 1.0 - sdfCircle : sdfCircle;\n shapeColor = mix(shapeColor, vec3(1.0) - shapeColor, u_hoverStrength * mouseEffect);\n \n if (u_isDarkMode) {\n shapeColor = mix(shapeColor, vec3(1.0), 0.2);\n } else {\n shapeColor = mix(shapeColor, vec3(0.0), 0.1);\n }\n \n gl_FragColor = vec4(shapeColor, sdf);\n}\n`\n\ninterface ShaderConfig {\n variation: number\n color1: string\n color2: string\n color3: string\n color4: string\n enableHover: boolean\n invertMouse: boolean\n width: string\n height: string\n}\n\nconst initialState: ShaderConfig = {\n variation: 3,\n color1: \"#D5F981\",\n color2: \"#A1BBE7\",\n color3: \"#F2BAE2\",\n color4: \"#68E8FA\",\n enableHover: true,\n invertMouse: true,\n width: \"100%\",\n height: \"400px\",\n}\n\nexport const configAtom = atom(initialState)\n\nexport function ShaderLensBlur() {\n const [config] = useAtom(configAtom)\n const containerRef = useRef(null)\n const canvasRef = useRef(null)\n const rendererRef = useRef(null)\n const sceneRef = useRef(null)\n const cameraRef = useRef(null)\n const materialRef = useRef(null)\n const rafRef = useRef(null)\n const [isInteracting, setIsInteracting] = useState(false)\n const { theme } = useTheme()\n\n const updateSize = useCallback(() => {\n if (\n !containerRef.current ||\n !canvasRef.current ||\n !rendererRef.current ||\n !cameraRef.current ||\n !materialRef.current\n )\n return\n\n const { clientWidth: w, clientHeight: h } = containerRef.current\n const aspect = w / h\n\n cameraRef.current.left = -aspect\n cameraRef.current.right = aspect\n cameraRef.current.top = 1\n cameraRef.current.bottom = -1\n cameraRef.current.updateProjectionMatrix()\n\n rendererRef.current.setSize(w, h)\n materialRef.current.uniforms.u_resolution.value.set(w, h)\n }, [])\n\n const updateMousePosition = useCallback(\n (x: number, y: number) => {\n if (!containerRef.current || !materialRef.current) return\n const rect = containerRef.current.getBoundingClientRect()\n const mouseX = x - rect.left\n const mouseY = y - rect.top\n if (isInteracting || config.enableHover) {\n materialRef.current.uniforms.u_mouse.value.set(\n mouseX,\n rect.height - mouseY\n )\n }\n },\n [isInteracting, config.enableHover]\n )\n\n const animate = useCallback(\n (time: number) => {\n if (\n !rendererRef.current ||\n !sceneRef.current ||\n !cameraRef.current ||\n !materialRef.current\n )\n return\n\n materialRef.current.uniforms.u_time.value = time * 0.001\n materialRef.current.uniforms.u_hoverStrength.value =\n isInteracting || config.enableHover ? 0.3 : 0\n\n rendererRef.current.render(sceneRef.current, cameraRef.current)\n rafRef.current = requestAnimationFrame(animate)\n },\n [config.enableHover, isInteracting]\n )\n\n useEffect(() => {\n if (!containerRef.current || !canvasRef.current) return\n\n const scene = new THREE.Scene()\n sceneRef.current = scene\n\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000)\n camera.position.z = 1\n cameraRef.current = camera\n\n const renderer = new THREE.WebGLRenderer({\n canvas: canvasRef.current,\n antialias: true,\n alpha: true,\n })\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))\n renderer.setClearColor(0x000000, 0)\n rendererRef.current = renderer\n\n const geometry = new THREE.PlaneGeometry(2, 2)\n const material = new THREE.ShaderMaterial({\n vertexShader: `\n varying vec2 v_texcoord;\n void main() {\n gl_Position = vec4(position, 1.0);\n v_texcoord = uv;\n }\n `,\n fragmentShader,\n uniforms: {\n u_mouse: { value: new THREE.Vector2() },\n u_resolution: { value: new THREE.Vector2() },\n u_pixelRatio: { value: Math.min(window.devicePixelRatio, 2) },\n u_time: { value: 0 },\n u_color1: { value: new THREE.Color(config.color1) },\n u_color2: { value: new THREE.Color(config.color2) },\n u_color3: { value: new THREE.Color(config.color3) },\n u_color4: { value: new THREE.Color(config.color4) },\n u_hoverStrength: { value: 0 },\n u_invertMouse: { value: config.invertMouse },\n u_isDarkMode: { value: theme === \"dark\" },\n },\n defines: {\n VAR: config.variation,\n },\n transparent: true,\n blending: THREE.NormalBlending,\n })\n materialRef.current = material\n\n const mesh = new THREE.Mesh(geometry, material)\n scene.add(mesh)\n\n updateSize()\n rafRef.current = requestAnimationFrame(animate)\n\n const resizeObserver = new ResizeObserver(updateSize)\n resizeObserver.observe(containerRef.current)\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current)\n if (rendererRef.current) rendererRef.current.dispose()\n if (containerRef.current) resizeObserver.unobserve(containerRef.current)\n }\n }, [config, updateSize, animate, theme])\n\n useEffect(() => {\n const container = containerRef.current\n if (!container) return\n\n const handlePointerMove = (event: PointerEvent) =>\n updateMousePosition(event.clientX, event.clientY)\n const handleTouchMove = (event: TouchEvent) => {\n if (event.touches.length > 0) {\n const touch = event.touches[0]\n updateMousePosition(touch.clientX, touch.clientY)\n }\n }\n\n container.addEventListener(\"pointermove\", handlePointerMove)\n container.addEventListener(\"touchmove\", handleTouchMove)\n container.addEventListener(\"pointerdown\", () => setIsInteracting(true))\n container.addEventListener(\"pointerup\", () => setIsInteracting(false))\n container.addEventListener(\"touchstart\", () => setIsInteracting(true))\n container.addEventListener(\"touchend\", () => setIsInteracting(false))\n\n return () => {\n container.removeEventListener(\"pointermove\", handlePointerMove)\n container.removeEventListener(\"touchmove\", handleTouchMove)\n container.removeEventListener(\"pointerdown\", () => setIsInteracting(true))\n container.removeEventListener(\"pointerup\", () => setIsInteracting(false))\n container.removeEventListener(\"touchstart\", () => setIsInteracting(true))\n container.removeEventListener(\"touchend\", () => setIsInteracting(false))\n }\n }, [updateMousePosition])\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.u_color1.value.set(config.color1)\n materialRef.current.uniforms.u_color2.value.set(config.color2)\n materialRef.current.uniforms.u_color3.value.set(config.color3)\n materialRef.current.uniforms.u_color4.value.set(config.color4)\n materialRef.current.uniforms.u_invertMouse.value = config.invertMouse\n materialRef.current.uniforms.u_isDarkMode.value = theme === \"dark\"\n materialRef.current.defines.VAR = config.variation\n materialRef.current.needsUpdate = true\n }\n }, [config, theme])\n\n return (\n \n \n \n {config.enableHover\n ? `${\n config.invertMouse ? \"Inverted mouse\" : \"Normal mouse\"\n } interaction`\n : \"Interact with the animation\"}{\" \"}\n using mouse or touch\n \n \n )\n}\n\nexport default ShaderLensBlur\n// \"use client\"\n\n// // npm install jotai three\n// import React, { useCallback, useEffect, useRef, useState } from \"react\"\n// import { motion } from \"framer-motion\"\n// import { atom, useAtom } from \"jotai\"\n// import * as THREE from \"three\"\n\n// const fragmentShader = `\n// varying vec2 v_texcoord;\n\n// uniform vec2 u_mouse;\n// uniform vec2 u_resolution;\n// uniform float u_pixelRatio;\n// uniform float u_time;\n// uniform vec3 u_color1;\n// uniform vec3 u_color2;\n// uniform vec3 u_color3;\n// uniform vec3 u_color4;\n// uniform float u_hoverStrength;\n// uniform bool u_invertMouse;\n\n// #define PI 3.1415926535897932384626433832795\n// #define TWO_PI 6.2831853071795864769252867665590\n\n// vec2 coord(in vec2 p) {\n// p = p / u_resolution.xy;\n// if (u_resolution.x > u_resolution.y) {\n// p.x *= u_resolution.x / u_resolution.y;\n// p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n// } else {\n// p.y *= u_resolution.y / u_resolution.x;\n// p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n// }\n// p -= 0.5;\n// p *= vec2(-1.0, 1.0);\n// return p;\n// }\n\n// #define st0 coord(gl_FragCoord.xy)\n// #define mx coord(u_mouse)\n\n// float sdRoundRect(vec2 p, vec2 b, float r) {\n// vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n// return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n// }\n\n// float sdCircle(in vec2 st, in vec2 center) {\n// return length(st - center) * 2.0;\n// }\n\n// float sdPoly(in vec2 p, in float w, in int sides) {\n// float a = atan(p.x, p.y) + PI;\n// float r = TWO_PI / float(sides);\n// float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n// return d * 2.0 - w;\n// }\n\n// float aastep(float threshold, float value) {\n// float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n// return smoothstep(threshold - afwidth, threshold + afwidth, value);\n// }\n\n// float fill(float x, float size, float edge) {\n// return 1.0 - smoothstep(size - edge, size + edge, x);\n// }\n\n// float stroke(float x, float size, float w, float edge) {\n// float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n// return clamp(d, 0.0, 1.0);\n// }\n\n// void main() {\n// vec2 st = st0 + 0.5;\n// vec2 posMouse = mx + 0.5;\n\n// float size = 1.2 + sin(u_time) * 0.1;\n// float roundness = 0.4 + sin(u_time * 0.5) * 0.1;\n// float borderSize = 0.05 + sin(u_time * 0.7) * 0.02;\n// float circleSize = 0.3 + sin(u_time * 0.8) * 0.05;\n// float circleEdge = 0.5 + sin(u_time * 0.6) * 0.1;\n\n// float sdfCircle = fill(\n// sdCircle(st, posMouse),\n// circleSize,\n// circleEdge\n// );\n\n// float sdf;\n// if (VAR == 0) {\n// sdf = sdRoundRect(st, vec2(size), roundness);\n// sdf = stroke(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n// } else if (VAR == 1) {\n// sdf = sdCircle(st, vec2(0.5));\n// sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n// } else if (VAR == 2) {\n// sdf = sdCircle(st, vec2(0.5));\n// sdf = stroke(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n// } else if (VAR == 3) {\n// sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n// sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n// }\n\n// vec3 gradient = mix(\n// mix(u_color1, u_color2, 0.5 + 0.5 * cos(u_time + st.x + 0.0)),\n// mix(u_color3, u_color4, 0.5 + 0.5 * cos(u_time + st.y + 2.0)),\n// 0.5 + 0.5 * cos(u_time + st.x + st.y + 4.0)\n// );\n\n// vec3 shapeColor = sdf * gradient;\n\n// float mouseEffect = u_invertMouse ? 1.0 - sdfCircle : sdfCircle;\n// shapeColor = mix(shapeColor, vec3(1.0) - shapeColor, u_hoverStrength * mouseEffect);\n\n// gl_FragColor = vec4(shapeColor, sdf);\n// }\n// `\n\n// interface ShaderConfig {\n// variation: number\n// color1: string\n// color2: string\n// color3: string\n// color4: string\n// enableHover: boolean\n// invertMouse: boolean\n// width: string\n// height: string\n// }\n\n// const initialState: ShaderConfig = {\n// variation: 0,\n// color1: \"#ff0000\",\n// color2: \"#00ff00\",\n// color3: \"#0000ff\",\n// color4: \"#ffff00\",\n// enableHover: true,\n// invertMouse: true,\n// width: \"100%\",\n// height: \"400px\",\n// }\n\n// export const configAtom = atom(initialState)\n\n// export function ShaderLensBlur() {\n// const [config] = useAtom(configAtom)\n// const containerRef = useRef(null)\n// const canvasRef = useRef(null)\n// const rendererRef = useRef(null)\n// const sceneRef = useRef(null)\n// const cameraRef = useRef(null)\n// const materialRef = useRef(null)\n// const rafRef = useRef(null)\n// const [isInteracting, setIsInteracting] = useState(false)\n\n// const updateSize = useCallback(() => {\n// if (\n// !containerRef.current ||\n// !canvasRef.current ||\n// !rendererRef.current ||\n// !cameraRef.current ||\n// !materialRef.current\n// )\n// return\n\n// const { clientWidth: w, clientHeight: h } = containerRef.current\n// const aspect = w / h\n\n// cameraRef.current.left = -aspect\n// cameraRef.current.right = aspect\n// cameraRef.current.top = 1\n// cameraRef.current.bottom = -1\n// cameraRef.current.updateProjectionMatrix()\n\n// rendererRef.current.setSize(w, h)\n// materialRef.current.uniforms.u_resolution.value.set(w, h)\n// }, [])\n\n// const updateMousePosition = useCallback(\n// (x: number, y: number) => {\n// if (!containerRef.current || !materialRef.current) return\n// const rect = containerRef.current.getBoundingClientRect()\n// const mouseX = x - rect.left\n// const mouseY = y - rect.top\n// if (isInteracting || config.enableHover) {\n// materialRef.current.uniforms.u_mouse.value.set(\n// mouseX,\n// rect.height - mouseY\n// )\n// }\n// },\n// [isInteracting, config.enableHover]\n// )\n\n// const animate = useCallback(\n// (time: number) => {\n// if (\n// !rendererRef.current ||\n// !sceneRef.current ||\n// !cameraRef.current ||\n// !materialRef.current\n// )\n// return\n\n// materialRef.current.uniforms.u_time.value = time * 0.001\n// materialRef.current.uniforms.u_hoverStrength.value =\n// isInteracting || config.enableHover ? 0.3 : 0\n\n// rendererRef.current.render(sceneRef.current, cameraRef.current)\n// rafRef.current = requestAnimationFrame(animate)\n// },\n// [config.enableHover, isInteracting]\n// )\n\n// useEffect(() => {\n// if (!containerRef.current || !canvasRef.current) return\n\n// const scene = new THREE.Scene()\n// sceneRef.current = scene\n\n// const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000)\n// camera.position.z = 1\n// cameraRef.current = camera\n\n// const renderer = new THREE.WebGLRenderer({\n// canvas: canvasRef.current,\n// antialias: true,\n// alpha: true,\n// })\n// renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))\n// renderer.setClearColor(0x000000, 0)\n// rendererRef.current = renderer\n\n// const geometry = new THREE.PlaneGeometry(2, 2)\n// const material = new THREE.ShaderMaterial({\n// vertexShader: `\n// varying vec2 v_texcoord;\n// void main() {\n// gl_Position = vec4(position, 1.0);\n// v_texcoord = uv;\n// }\n// `,\n// fragmentShader,\n// uniforms: {\n// u_mouse: { value: new THREE.Vector2() },\n// u_resolution: { value: new THREE.Vector2() },\n// u_pixelRatio: { value: Math.min(window.devicePixelRatio, 2) },\n// u_time: { value: 0 },\n// u_color1: { value: new THREE.Color(config.color1) },\n// u_color2: { value: new THREE.Color(config.color2) },\n// u_color3: { value: new THREE.Color(config.color3) },\n// u_color4: { value: new THREE.Color(config.color4) },\n// u_hoverStrength: { value: 0 },\n// u_invertMouse: { value: config.invertMouse },\n// },\n// defines: {\n// VAR: config.variation,\n// },\n// transparent: true,\n// blending: THREE.NormalBlending,\n// })\n// materialRef.current = material\n\n// const mesh = new THREE.Mesh(geometry, material)\n// scene.add(mesh)\n\n// updateSize()\n// rafRef.current = requestAnimationFrame(animate)\n\n// const resizeObserver = new ResizeObserver(updateSize)\n// resizeObserver.observe(containerRef.current)\n\n// return () => {\n// if (rafRef.current) cancelAnimationFrame(rafRef.current)\n// if (rendererRef.current) rendererRef.current.dispose()\n// if (containerRef.current) resizeObserver.unobserve(containerRef.current)\n// }\n// }, [config, updateSize, animate])\n\n// useEffect(() => {\n// const container = containerRef.current\n// if (!container) return\n\n// const handlePointerMove = (event: PointerEvent) =>\n// updateMousePosition(event.clientX, event.clientY)\n// const handleTouchMove = (event: TouchEvent) => {\n// if (event.touches.length > 0) {\n// const touch = event.touches[0]\n// updateMousePosition(touch.clientX, touch.clientY)\n// }\n// }\n\n// container.addEventListener(\"pointermove\", handlePointerMove)\n// container.addEventListener(\"touchmove\", handleTouchMove)\n// container.addEventListener(\"pointerdown\", () => setIsInteracting(true))\n// container.addEventListener(\"pointerup\", () => setIsInteracting(false))\n// container.addEventListener(\"touchstart\", () => setIsInteracting(true))\n// container.addEventListener(\"touchend\", () => setIsInteracting(false))\n\n// return () => {\n// container.removeEventListener(\"pointermove\", handlePointerMove)\n// container.removeEventListener(\"touchmove\", handleTouchMove)\n// container.removeEventListener(\"pointerdown\", () => setIsInteracting(true))\n// container.removeEventListener(\"pointerup\", () => setIsInteracting(false))\n// container.removeEventListener(\"touchstart\", () => setIsInteracting(true))\n// container.removeEventListener(\"touchend\", () => setIsInteracting(false))\n// }\n// }, [updateMousePosition])\n\n// useEffect(() => {\n// if (materialRef.current) {\n// materialRef.current.uniforms.u_color1.value.set(config.color1)\n// materialRef.current.uniforms.u_color2.value.set(config.color2)\n// materialRef.current.uniforms.u_color3.value.set(config.color3)\n// materialRef.current.uniforms.u_color4.value.set(config.color4)\n// materialRef.current.uniforms.u_invertMouse.value = config.invertMouse\n// materialRef.current.defines.VAR = config.variation\n// materialRef.current.needsUpdate = true\n// }\n// }, [config])\n\n// return (\n// \n// \n//
\n// {config.enableHover\n// ? `${\n// config.invertMouse ? \"Inverted mouse\" : \"Normal mouse\"\n// } interaction`\n// : \"Interact with the animation\"}{\" \"}\n// using mouse or touch\n//
\n// \n// )\n// }\n\n// export default ShaderLensBlur\n" + "content": "\"use client\"\n\nimport React, { useCallback, useEffect, useRef, useState } from \"react\"\nimport { atom, useAtom } from \"jotai\"\nimport { motion } from \"motion/react\"\nimport { useTheme } from \"next-themes\"\nimport * as THREE from \"three\"\n\nconst fragmentShader = `\nvarying vec2 v_texcoord;\n\nuniform vec2 u_mouse;\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\nuniform float u_time;\nuniform vec3 u_color1;\nuniform vec3 u_color2;\nuniform vec3 u_color3;\nuniform vec3 u_color4;\nuniform float u_hoverStrength;\nuniform bool u_invertMouse;\nuniform bool u_isDarkMode;\n\n#define PI 3.1415926535897932384626433832795\n#define TWO_PI 6.2831853071795864769252867665590\n\nvec2 coord(in vec2 p) {\n p = p / u_resolution.xy;\n if (u_resolution.x > u_resolution.y) {\n p.x *= u_resolution.x / u_resolution.y;\n p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n } else {\n p.y *= u_resolution.y / u_resolution.x;\n p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n }\n p -= 0.5;\n p *= vec2(-1.0, 1.0);\n return p;\n}\n\n#define st0 coord(gl_FragCoord.xy)\n#define mx coord(u_mouse)\n\nfloat sdRoundRect(vec2 p, vec2 b, float r) {\n vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n}\n\nfloat sdCircle(in vec2 st, in vec2 center) {\n return length(st - center) * 2.0;\n}\n\nfloat sdPoly(in vec2 p, in float w, in int sides) {\n float a = atan(p.x, p.y) + PI;\n float r = TWO_PI / float(sides);\n float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n return d * 2.0 - w;\n}\n\nfloat aastep(float threshold, float value) {\n float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n return smoothstep(threshold - afwidth, threshold + afwidth, value);\n}\n\nfloat fill(float x, float size, float edge) {\n return 1.0 - smoothstep(size - edge, size + edge, x);\n}\n\nfloat stroke(float x, float size, float w, float edge) {\n float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 st = st0 + 0.5;\n vec2 posMouse = mx + 0.5;\n \n float size = 1.2 + sin(u_time) * 0.1;\n float roundness = 0.4 + sin(u_time * 0.5) * 0.1;\n float borderSize = 0.05 + sin(u_time * 0.7) * 0.02;\n float circleSize = 0.3 + sin(u_time * 0.8) * 0.05;\n float circleEdge = 0.5 + sin(u_time * 0.6) * 0.1;\n \n float sdfCircle = fill(\n sdCircle(st, posMouse),\n circleSize,\n circleEdge\n );\n \n float sdf;\n if (VAR == 0) {\n sdf = sdRoundRect(st, vec2(size), roundness);\n sdf = stroke(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n } else if (VAR == 1) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n } else if (VAR == 2) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = stroke(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n } else if (VAR == 3) {\n sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n }\n \n vec3 gradient = mix(\n mix(u_color1, u_color2, 0.5 + 0.5 * cos(u_time + st.x + 0.0)),\n mix(u_color3, u_color4, 0.5 + 0.5 * cos(u_time + st.y + 2.0)),\n 0.5 + 0.5 * cos(u_time + st.x + st.y + 4.0)\n );\n \n vec3 shapeColor = sdf * gradient;\n \n float mouseEffect = u_invertMouse ? 1.0 - sdfCircle : sdfCircle;\n shapeColor = mix(shapeColor, vec3(1.0) - shapeColor, u_hoverStrength * mouseEffect);\n \n if (u_isDarkMode) {\n shapeColor = mix(shapeColor, vec3(1.0), 0.2);\n } else {\n shapeColor = mix(shapeColor, vec3(0.0), 0.1);\n }\n \n gl_FragColor = vec4(shapeColor, sdf);\n}\n`\n\ninterface ShaderConfig {\n variation: number\n color1: string\n color2: string\n color3: string\n color4: string\n enableHover: boolean\n invertMouse: boolean\n width: string\n height: string\n}\n\nconst initialState: ShaderConfig = {\n variation: 3,\n color1: \"#D5F981\",\n color2: \"#A1BBE7\",\n color3: \"#F2BAE2\",\n color4: \"#68E8FA\",\n enableHover: true,\n invertMouse: true,\n width: \"100%\",\n height: \"400px\",\n}\n\nexport const configAtom = atom(initialState)\n\nexport function ShaderLensBlur() {\n const [config] = useAtom(configAtom)\n const containerRef = useRef(null)\n const canvasRef = useRef(null)\n const rendererRef = useRef(null)\n const sceneRef = useRef(null)\n const cameraRef = useRef(null)\n const materialRef = useRef(null)\n const rafRef = useRef(null)\n const [isInteracting, setIsInteracting] = useState(false)\n const { theme } = useTheme()\n\n const updateSize = useCallback(() => {\n if (\n !containerRef.current ||\n !canvasRef.current ||\n !rendererRef.current ||\n !cameraRef.current ||\n !materialRef.current\n )\n return\n\n const { clientWidth: w, clientHeight: h } = containerRef.current\n const aspect = w / h\n\n cameraRef.current.left = -aspect\n cameraRef.current.right = aspect\n cameraRef.current.top = 1\n cameraRef.current.bottom = -1\n cameraRef.current.updateProjectionMatrix()\n\n rendererRef.current.setSize(w, h)\n materialRef.current.uniforms.u_resolution.value.set(w, h)\n }, [])\n\n const updateMousePosition = useCallback(\n (x: number, y: number) => {\n if (!containerRef.current || !materialRef.current) return\n const rect = containerRef.current.getBoundingClientRect()\n const mouseX = x - rect.left\n const mouseY = y - rect.top\n if (isInteracting || config.enableHover) {\n materialRef.current.uniforms.u_mouse.value.set(\n mouseX,\n rect.height - mouseY\n )\n }\n },\n [isInteracting, config.enableHover]\n )\n\n const animate = useCallback(\n (time: number) => {\n if (\n !rendererRef.current ||\n !sceneRef.current ||\n !cameraRef.current ||\n !materialRef.current\n )\n return\n\n materialRef.current.uniforms.u_time.value = time * 0.001\n materialRef.current.uniforms.u_hoverStrength.value =\n isInteracting || config.enableHover ? 0.3 : 0\n\n rendererRef.current.render(sceneRef.current, cameraRef.current)\n rafRef.current = requestAnimationFrame(animate)\n },\n [config.enableHover, isInteracting]\n )\n\n useEffect(() => {\n if (!containerRef.current || !canvasRef.current) return\n\n const scene = new THREE.Scene()\n sceneRef.current = scene\n\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000)\n camera.position.z = 1\n cameraRef.current = camera\n\n const renderer = new THREE.WebGLRenderer({\n canvas: canvasRef.current,\n antialias: true,\n alpha: true,\n })\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))\n renderer.setClearColor(0x000000, 0)\n rendererRef.current = renderer\n\n const geometry = new THREE.PlaneGeometry(2, 2)\n const material = new THREE.ShaderMaterial({\n vertexShader: `\n varying vec2 v_texcoord;\n void main() {\n gl_Position = vec4(position, 1.0);\n v_texcoord = uv;\n }\n `,\n fragmentShader,\n uniforms: {\n u_mouse: { value: new THREE.Vector2() },\n u_resolution: { value: new THREE.Vector2() },\n u_pixelRatio: { value: Math.min(window.devicePixelRatio, 2) },\n u_time: { value: 0 },\n u_color1: { value: new THREE.Color(config.color1) },\n u_color2: { value: new THREE.Color(config.color2) },\n u_color3: { value: new THREE.Color(config.color3) },\n u_color4: { value: new THREE.Color(config.color4) },\n u_hoverStrength: { value: 0 },\n u_invertMouse: { value: config.invertMouse },\n u_isDarkMode: { value: theme === \"dark\" },\n },\n defines: {\n VAR: config.variation,\n },\n transparent: true,\n blending: THREE.NormalBlending,\n })\n materialRef.current = material\n\n const mesh = new THREE.Mesh(geometry, material)\n scene.add(mesh)\n\n updateSize()\n rafRef.current = requestAnimationFrame(animate)\n\n const resizeObserver = new ResizeObserver(updateSize)\n resizeObserver.observe(containerRef.current)\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current)\n if (rendererRef.current) rendererRef.current.dispose()\n if (containerRef.current) resizeObserver.unobserve(containerRef.current)\n }\n }, [config, updateSize, animate, theme])\n\n useEffect(() => {\n const container = containerRef.current\n if (!container) return\n\n const handlePointerMove = (event: PointerEvent) =>\n updateMousePosition(event.clientX, event.clientY)\n const handleTouchMove = (event: TouchEvent) => {\n if (event.touches.length > 0) {\n const touch = event.touches[0]\n updateMousePosition(touch.clientX, touch.clientY)\n }\n }\n\n container.addEventListener(\"pointermove\", handlePointerMove)\n container.addEventListener(\"touchmove\", handleTouchMove)\n container.addEventListener(\"pointerdown\", () => setIsInteracting(true))\n container.addEventListener(\"pointerup\", () => setIsInteracting(false))\n container.addEventListener(\"touchstart\", () => setIsInteracting(true))\n container.addEventListener(\"touchend\", () => setIsInteracting(false))\n\n return () => {\n container.removeEventListener(\"pointermove\", handlePointerMove)\n container.removeEventListener(\"touchmove\", handleTouchMove)\n container.removeEventListener(\"pointerdown\", () => setIsInteracting(true))\n container.removeEventListener(\"pointerup\", () => setIsInteracting(false))\n container.removeEventListener(\"touchstart\", () => setIsInteracting(true))\n container.removeEventListener(\"touchend\", () => setIsInteracting(false))\n }\n }, [updateMousePosition])\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.u_color1.value.set(config.color1)\n materialRef.current.uniforms.u_color2.value.set(config.color2)\n materialRef.current.uniforms.u_color3.value.set(config.color3)\n materialRef.current.uniforms.u_color4.value.set(config.color4)\n materialRef.current.uniforms.u_invertMouse.value = config.invertMouse\n materialRef.current.uniforms.u_isDarkMode.value = theme === \"dark\"\n materialRef.current.defines.VAR = config.variation\n materialRef.current.needsUpdate = true\n }\n }, [config, theme])\n\n return (\n \n \n \n {config.enableHover\n ? `${\n config.invertMouse ? \"Inverted mouse\" : \"Normal mouse\"\n } interaction`\n : \"Interact with the animation\"}{\" \"}\n using mouse or touch\n \n \n )\n}\n\nexport default ShaderLensBlur\n// \"use client\"\n\n// // npm install jotai three\n// import React, { useCallback, useEffect, useRef, useState } from \"react\"\n// import { motion } from \"motion/react\"\n// import { atom, useAtom } from \"jotai\"\n// import * as THREE from \"three\"\n\n// const fragmentShader = `\n// varying vec2 v_texcoord;\n\n// uniform vec2 u_mouse;\n// uniform vec2 u_resolution;\n// uniform float u_pixelRatio;\n// uniform float u_time;\n// uniform vec3 u_color1;\n// uniform vec3 u_color2;\n// uniform vec3 u_color3;\n// uniform vec3 u_color4;\n// uniform float u_hoverStrength;\n// uniform bool u_invertMouse;\n\n// #define PI 3.1415926535897932384626433832795\n// #define TWO_PI 6.2831853071795864769252867665590\n\n// vec2 coord(in vec2 p) {\n// p = p / u_resolution.xy;\n// if (u_resolution.x > u_resolution.y) {\n// p.x *= u_resolution.x / u_resolution.y;\n// p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n// } else {\n// p.y *= u_resolution.y / u_resolution.x;\n// p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n// }\n// p -= 0.5;\n// p *= vec2(-1.0, 1.0);\n// return p;\n// }\n\n// #define st0 coord(gl_FragCoord.xy)\n// #define mx coord(u_mouse)\n\n// float sdRoundRect(vec2 p, vec2 b, float r) {\n// vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n// return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n// }\n\n// float sdCircle(in vec2 st, in vec2 center) {\n// return length(st - center) * 2.0;\n// }\n\n// float sdPoly(in vec2 p, in float w, in int sides) {\n// float a = atan(p.x, p.y) + PI;\n// float r = TWO_PI / float(sides);\n// float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n// return d * 2.0 - w;\n// }\n\n// float aastep(float threshold, float value) {\n// float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n// return smoothstep(threshold - afwidth, threshold + afwidth, value);\n// }\n\n// float fill(float x, float size, float edge) {\n// return 1.0 - smoothstep(size - edge, size + edge, x);\n// }\n\n// float stroke(float x, float size, float w, float edge) {\n// float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n// return clamp(d, 0.0, 1.0);\n// }\n\n// void main() {\n// vec2 st = st0 + 0.5;\n// vec2 posMouse = mx + 0.5;\n\n// float size = 1.2 + sin(u_time) * 0.1;\n// float roundness = 0.4 + sin(u_time * 0.5) * 0.1;\n// float borderSize = 0.05 + sin(u_time * 0.7) * 0.02;\n// float circleSize = 0.3 + sin(u_time * 0.8) * 0.05;\n// float circleEdge = 0.5 + sin(u_time * 0.6) * 0.1;\n\n// float sdfCircle = fill(\n// sdCircle(st, posMouse),\n// circleSize,\n// circleEdge\n// );\n\n// float sdf;\n// if (VAR == 0) {\n// sdf = sdRoundRect(st, vec2(size), roundness);\n// sdf = stroke(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n// } else if (VAR == 1) {\n// sdf = sdCircle(st, vec2(0.5));\n// sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n// } else if (VAR == 2) {\n// sdf = sdCircle(st, vec2(0.5));\n// sdf = stroke(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n// } else if (VAR == 3) {\n// sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n// sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n// }\n\n// vec3 gradient = mix(\n// mix(u_color1, u_color2, 0.5 + 0.5 * cos(u_time + st.x + 0.0)),\n// mix(u_color3, u_color4, 0.5 + 0.5 * cos(u_time + st.y + 2.0)),\n// 0.5 + 0.5 * cos(u_time + st.x + st.y + 4.0)\n// );\n\n// vec3 shapeColor = sdf * gradient;\n\n// float mouseEffect = u_invertMouse ? 1.0 - sdfCircle : sdfCircle;\n// shapeColor = mix(shapeColor, vec3(1.0) - shapeColor, u_hoverStrength * mouseEffect);\n\n// gl_FragColor = vec4(shapeColor, sdf);\n// }\n// `\n\n// interface ShaderConfig {\n// variation: number\n// color1: string\n// color2: string\n// color3: string\n// color4: string\n// enableHover: boolean\n// invertMouse: boolean\n// width: string\n// height: string\n// }\n\n// const initialState: ShaderConfig = {\n// variation: 0,\n// color1: \"#ff0000\",\n// color2: \"#00ff00\",\n// color3: \"#0000ff\",\n// color4: \"#ffff00\",\n// enableHover: true,\n// invertMouse: true,\n// width: \"100%\",\n// height: \"400px\",\n// }\n\n// export const configAtom = atom(initialState)\n\n// export function ShaderLensBlur() {\n// const [config] = useAtom(configAtom)\n// const containerRef = useRef(null)\n// const canvasRef = useRef(null)\n// const rendererRef = useRef(null)\n// const sceneRef = useRef(null)\n// const cameraRef = useRef(null)\n// const materialRef = useRef(null)\n// const rafRef = useRef(null)\n// const [isInteracting, setIsInteracting] = useState(false)\n\n// const updateSize = useCallback(() => {\n// if (\n// !containerRef.current ||\n// !canvasRef.current ||\n// !rendererRef.current ||\n// !cameraRef.current ||\n// !materialRef.current\n// )\n// return\n\n// const { clientWidth: w, clientHeight: h } = containerRef.current\n// const aspect = w / h\n\n// cameraRef.current.left = -aspect\n// cameraRef.current.right = aspect\n// cameraRef.current.top = 1\n// cameraRef.current.bottom = -1\n// cameraRef.current.updateProjectionMatrix()\n\n// rendererRef.current.setSize(w, h)\n// materialRef.current.uniforms.u_resolution.value.set(w, h)\n// }, [])\n\n// const updateMousePosition = useCallback(\n// (x: number, y: number) => {\n// if (!containerRef.current || !materialRef.current) return\n// const rect = containerRef.current.getBoundingClientRect()\n// const mouseX = x - rect.left\n// const mouseY = y - rect.top\n// if (isInteracting || config.enableHover) {\n// materialRef.current.uniforms.u_mouse.value.set(\n// mouseX,\n// rect.height - mouseY\n// )\n// }\n// },\n// [isInteracting, config.enableHover]\n// )\n\n// const animate = useCallback(\n// (time: number) => {\n// if (\n// !rendererRef.current ||\n// !sceneRef.current ||\n// !cameraRef.current ||\n// !materialRef.current\n// )\n// return\n\n// materialRef.current.uniforms.u_time.value = time * 0.001\n// materialRef.current.uniforms.u_hoverStrength.value =\n// isInteracting || config.enableHover ? 0.3 : 0\n\n// rendererRef.current.render(sceneRef.current, cameraRef.current)\n// rafRef.current = requestAnimationFrame(animate)\n// },\n// [config.enableHover, isInteracting]\n// )\n\n// useEffect(() => {\n// if (!containerRef.current || !canvasRef.current) return\n\n// const scene = new THREE.Scene()\n// sceneRef.current = scene\n\n// const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000)\n// camera.position.z = 1\n// cameraRef.current = camera\n\n// const renderer = new THREE.WebGLRenderer({\n// canvas: canvasRef.current,\n// antialias: true,\n// alpha: true,\n// })\n// renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))\n// renderer.setClearColor(0x000000, 0)\n// rendererRef.current = renderer\n\n// const geometry = new THREE.PlaneGeometry(2, 2)\n// const material = new THREE.ShaderMaterial({\n// vertexShader: `\n// varying vec2 v_texcoord;\n// void main() {\n// gl_Position = vec4(position, 1.0);\n// v_texcoord = uv;\n// }\n// `,\n// fragmentShader,\n// uniforms: {\n// u_mouse: { value: new THREE.Vector2() },\n// u_resolution: { value: new THREE.Vector2() },\n// u_pixelRatio: { value: Math.min(window.devicePixelRatio, 2) },\n// u_time: { value: 0 },\n// u_color1: { value: new THREE.Color(config.color1) },\n// u_color2: { value: new THREE.Color(config.color2) },\n// u_color3: { value: new THREE.Color(config.color3) },\n// u_color4: { value: new THREE.Color(config.color4) },\n// u_hoverStrength: { value: 0 },\n// u_invertMouse: { value: config.invertMouse },\n// },\n// defines: {\n// VAR: config.variation,\n// },\n// transparent: true,\n// blending: THREE.NormalBlending,\n// })\n// materialRef.current = material\n\n// const mesh = new THREE.Mesh(geometry, material)\n// scene.add(mesh)\n\n// updateSize()\n// rafRef.current = requestAnimationFrame(animate)\n\n// const resizeObserver = new ResizeObserver(updateSize)\n// resizeObserver.observe(containerRef.current)\n\n// return () => {\n// if (rafRef.current) cancelAnimationFrame(rafRef.current)\n// if (rendererRef.current) rendererRef.current.dispose()\n// if (containerRef.current) resizeObserver.unobserve(containerRef.current)\n// }\n// }, [config, updateSize, animate])\n\n// useEffect(() => {\n// const container = containerRef.current\n// if (!container) return\n\n// const handlePointerMove = (event: PointerEvent) =>\n// updateMousePosition(event.clientX, event.clientY)\n// const handleTouchMove = (event: TouchEvent) => {\n// if (event.touches.length > 0) {\n// const touch = event.touches[0]\n// updateMousePosition(touch.clientX, touch.clientY)\n// }\n// }\n\n// container.addEventListener(\"pointermove\", handlePointerMove)\n// container.addEventListener(\"touchmove\", handleTouchMove)\n// container.addEventListener(\"pointerdown\", () => setIsInteracting(true))\n// container.addEventListener(\"pointerup\", () => setIsInteracting(false))\n// container.addEventListener(\"touchstart\", () => setIsInteracting(true))\n// container.addEventListener(\"touchend\", () => setIsInteracting(false))\n\n// return () => {\n// container.removeEventListener(\"pointermove\", handlePointerMove)\n// container.removeEventListener(\"touchmove\", handleTouchMove)\n// container.removeEventListener(\"pointerdown\", () => setIsInteracting(true))\n// container.removeEventListener(\"pointerup\", () => setIsInteracting(false))\n// container.removeEventListener(\"touchstart\", () => setIsInteracting(true))\n// container.removeEventListener(\"touchend\", () => setIsInteracting(false))\n// }\n// }, [updateMousePosition])\n\n// useEffect(() => {\n// if (materialRef.current) {\n// materialRef.current.uniforms.u_color1.value.set(config.color1)\n// materialRef.current.uniforms.u_color2.value.set(config.color2)\n// materialRef.current.uniforms.u_color3.value.set(config.color3)\n// materialRef.current.uniforms.u_color4.value.set(config.color4)\n// materialRef.current.uniforms.u_invertMouse.value = config.invertMouse\n// materialRef.current.defines.VAR = config.variation\n// materialRef.current.needsUpdate = true\n// }\n// }, [config])\n\n// return (\n// \n// \n//
\n// {config.enableHover\n// ? `${\n// config.invertMouse ? \"Inverted mouse\" : \"Normal mouse\"\n// } interaction`\n// : \"Interact with the animation\"}{\" \"}\n// using mouse or touch\n//
\n// \n// )\n// }\n\n// export default ShaderLensBlur\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/shift-card.json b/apps/www/public/registry/styles/default/shift-card.json index 4d8e6e2..4d5c7cc 100644 --- a/apps/www/public/registry/styles/default/shift-card.json +++ b/apps/www/public/registry/styles/default/shift-card.json @@ -6,7 +6,7 @@ "files": [ { "name": "shift-card.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, MotionProps, motion } from \"framer-motion\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface ShiftCardProps\n extends Omit {\n className?: string\n topContent?: React.ReactNode\n middleContent?: React.ReactNode\n topAnimateContent?: React.ReactNode\n bottomContent?: React.ReactNode\n}\n\nconst ShiftCardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ children, ...props }, ref) => (\n
\n {children}\n
\n))\nShiftCardHeader.displayName = \"ShiftCardHeader\"\n\ninterface ShiftCardContentProps extends React.HTMLAttributes {\n isHovered: boolean\n}\nconst ShiftCardContent = React.forwardRef<\n HTMLDivElement,\n ShiftCardContentProps\n>(({ isHovered, children, ...divProps }, ref) => {\n // Explicitly define motion props to avoid passing incompatible HTML attributes\n const motionProps: MotionProps = {\n initial: { opacity: 0, height: 0 },\n animate: isHovered\n ? { opacity: 1, height: 194 }\n : { opacity: 1, height: 38 },\n transition: { duration: 0.3, delay: 0.1, ease: \"circIn\" },\n }\n\n return (\n \n {children}\n \n )\n})\nShiftCardContent.displayName = \"ShiftCardContent\"\n\nconst ShiftCard = React.forwardRef(\n (\n {\n className,\n topContent,\n topAnimateContent,\n middleContent,\n bottomContent,\n ...props\n },\n ref\n ) => {\n const [isHovered, setHovered] = React.useState(false)\n const handleMouseEnter = () => setHovered(true)\n const handleMouseLeave = () => setHovered(false)\n const handleTapStart = () => setHovered(true)\n const handleTapCancel = () => setHovered(false)\n const handleTap = () => setHovered(false)\n\n return (\n \n \n
\n {topContent}\n\n \n {isHovered ? <>{topAnimateContent} : null}\n \n
\n
\n\n
\n \n {!isHovered ? <>{middleContent} : null}\n \n
\n\n \n \n {bottomContent}\n \n \n \n )\n }\n)\n\nShiftCard.displayName = \"ShiftCard\"\n\nexport { ShiftCard, ShiftCardHeader, ShiftCardContent }\nexport default ShiftCard\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, MotionProps, motion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface ShiftCardProps\n extends Omit {\n className?: string\n topContent?: React.ReactNode\n middleContent?: React.ReactNode\n topAnimateContent?: React.ReactNode\n bottomContent?: React.ReactNode\n}\n\nconst ShiftCardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ children, ...props }, ref) => (\n
\n {children}\n
\n))\nShiftCardHeader.displayName = \"ShiftCardHeader\"\n\ninterface ShiftCardContentProps extends React.HTMLAttributes {\n isHovered: boolean\n}\nconst ShiftCardContent = React.forwardRef<\n HTMLDivElement,\n ShiftCardContentProps\n>(({ isHovered, children, ...divProps }, ref) => {\n // Explicitly define motion props to avoid passing incompatible HTML attributes\n const motionProps: MotionProps = {\n initial: { opacity: 0, height: 0 },\n animate: isHovered\n ? { opacity: 1, height: 194 }\n : { opacity: 1, height: 38 },\n transition: { duration: 0.3, delay: 0.1, ease: \"circIn\" },\n }\n\n return (\n \n {children}\n \n )\n})\nShiftCardContent.displayName = \"ShiftCardContent\"\n\nconst ShiftCard = React.forwardRef(\n (\n {\n className,\n topContent,\n topAnimateContent,\n middleContent,\n bottomContent,\n ...props\n },\n ref\n ) => {\n const [isHovered, setHovered] = React.useState(false)\n const handleMouseEnter = () => setHovered(true)\n const handleMouseLeave = () => setHovered(false)\n const handleTapStart = () => setHovered(true)\n const handleTapCancel = () => setHovered(false)\n const handleTap = () => setHovered(false)\n\n return (\n \n \n
\n {topContent}\n\n \n {isHovered ? <>{topAnimateContent} : null}\n \n
\n
\n\n
\n \n {!isHovered ? <>{middleContent} : null}\n \n
\n\n \n \n {bottomContent}\n \n \n \n )\n }\n)\n\nShiftCard.displayName = \"ShiftCard\"\n\nexport { ShiftCard, ShiftCardHeader, ShiftCardContent }\nexport default ShiftCard\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/side-panel.json b/apps/www/public/registry/styles/default/side-panel.json index a84723b..c298ceb 100644 --- a/apps/www/public/registry/styles/default/side-panel.json +++ b/apps/www/public/registry/styles/default/side-panel.json @@ -6,7 +6,7 @@ "files": [ { "name": "side-panel.tsx", - "content": "\"use client\"\n\nimport React, { ReactNode, forwardRef } from \"react\"\nimport { AnimatePresence, MotionConfig, motion } from \"framer-motion\"\nimport useMeasure from \"react-use-measure\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype PanelContainerProps = {\n panelOpen: boolean\n handlePanelOpen: () => void\n className?: string\n videoUrl?: string\n renderButton?: (handleToggle: () => void) => ReactNode\n children: ReactNode\n}\n\nconst sectionVariants = {\n open: {\n width: \"97%\",\n transition: {\n duration: 0.3,\n ease: \"easeInOut\",\n delayChildren: 0.3,\n staggerChildren: 0.2,\n },\n },\n closed: {\n transition: { duration: 0.2, ease: \"easeInOut\" },\n },\n}\n\nconst sharedTransition = { duration: 0.6, ease: \"easeInOut\" }\n\nexport const SidePanel = forwardRef(\n ({ panelOpen, handlePanelOpen, className, renderButton, children }, ref) => {\n const [measureRef, bounds] = useMeasure()\n\n return (\n \n \n 0 ? bounds.height : 0.1 }}\n className=\"h-auto\"\n transition={{ type: \"spring\", bounce: 0.02, duration: 0.65 }}\n >\n
\n \n \n \n {renderButton && renderButton(handlePanelOpen)}\n
\n\n {panelOpen && (\n \n {children}\n \n )}\n \n \n \n \n \n
\n )\n }\n)\n\nSidePanel.displayName = \"SidePanel\"\n\nexport default SidePanel\n\ntype ResizablePanelProps = {\n children: React.ReactNode\n}\n\nconst ResizablePanel = React.forwardRef(\n ({ children }, ref) => {\n const transition = { type: \"ease\", ease: \"easeInOut\", duration: 0.4 }\n\n return (\n \n
\n
\n \n {children}\n
\n
\n \n
\n )\n }\n)\n\nResizablePanel.displayName = \"ResizablePanel\"\n" + "content": "\"use client\"\n\nimport React, { ReactNode, forwardRef } from \"react\"\nimport { AnimatePresence, MotionConfig, motion } from \"motion/react\"\nimport useMeasure from \"react-use-measure\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype PanelContainerProps = {\n panelOpen: boolean\n handlePanelOpen: () => void\n className?: string\n videoUrl?: string\n renderButton?: (handleToggle: () => void) => ReactNode\n children: ReactNode\n}\n\nconst sectionVariants = {\n open: {\n width: \"97%\",\n transition: {\n duration: 0.3,\n ease: \"easeInOut\",\n delayChildren: 0.3,\n staggerChildren: 0.2,\n },\n },\n closed: {\n transition: { duration: 0.2, ease: \"easeInOut\" },\n },\n}\n\nconst sharedTransition = { duration: 0.6, ease: \"easeInOut\" }\n\nexport const SidePanel = forwardRef(\n ({ panelOpen, handlePanelOpen, className, renderButton, children }, ref) => {\n const [measureRef, bounds] = useMeasure()\n\n return (\n \n \n 0 ? bounds.height : 0.1 }}\n className=\"h-auto\"\n transition={{ type: \"spring\", bounce: 0.02, duration: 0.65 }}\n >\n
\n \n \n \n {renderButton && renderButton(handlePanelOpen)}\n
\n\n {panelOpen && (\n \n {children}\n \n )}\n \n \n \n \n \n
\n )\n }\n)\n\nSidePanel.displayName = \"SidePanel\"\n\nexport default SidePanel\n\ntype ResizablePanelProps = {\n children: React.ReactNode\n}\n\nconst ResizablePanel = React.forwardRef(\n ({ children }, ref) => {\n const transition = { type: \"ease\", ease: \"easeInOut\", duration: 0.4 }\n\n return (\n \n
\n
\n \n {children}\n
\n
\n \n
\n )\n }\n)\n\nResizablePanel.displayName = \"ResizablePanel\"\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/sortable-list.json b/apps/www/public/registry/styles/default/sortable-list.json index 95f3de0..7dc1a75 100644 --- a/apps/www/public/registry/styles/default/sortable-list.json +++ b/apps/www/public/registry/styles/default/sortable-list.json @@ -6,7 +6,7 @@ "files": [ { "name": "sortable-list.tsx", - "content": "\"use client\"\n\n// npx shadcn-ui@latest add checkbox\n// npm i react-use-measure\nimport { Dispatch, ReactNode, SetStateAction, useState } from \"react\"\nimport {\n AnimatePresence,\n LayoutGroup,\n Reorder,\n motion,\n useDragControls,\n} from \"framer-motion\"\nimport { Trash } from \"lucide-react\"\nimport useMeasure from \"react-use-measure\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\n\nexport type Item = {\n text: string\n checked: boolean\n id: number\n description: string\n}\n\ninterface SortableListItemProps {\n item: Item\n order: number\n onCompleteItem: (id: number) => void\n onRemoveItem: (id: number) => void\n renderExtra?: (item: Item) => React.ReactNode\n isExpanded?: boolean\n className?: string\n handleDrag: () => void\n}\n\nfunction SortableListItem({\n item,\n order,\n onCompleteItem,\n onRemoveItem,\n renderExtra,\n handleDrag,\n isExpanded,\n className,\n}: SortableListItemProps) {\n let [ref, bounds] = useMeasure()\n const [isDragging, setIsDragging] = useState(false)\n const [isDraggable, setIsDraggable] = useState(true)\n const dragControls = useDragControls()\n\n const handleDragStart = (event: any) => {\n setIsDragging(true)\n dragControls.start(event, { snapToCursor: true })\n handleDrag()\n }\n\n const handleDragEnd = () => {\n setIsDragging(false)\n }\n\n return (\n \n
\n 0 ? bounds.height : undefined,\n transition: {\n type: \"spring\",\n bounce: 0,\n duration: 0.4,\n },\n }}\n exit={{\n opacity: 0,\n transition: {\n duration: 0.05,\n type: \"spring\",\n bounce: 0.1,\n },\n }}\n layout\n layoutId={`item-${item.id}`}\n dragListener={!item.checked}\n dragControls={dragControls}\n onDragEnd={handleDragEnd}\n style={\n isExpanded\n ? {\n zIndex: 9999,\n marginTop: 10,\n marginBottom: 10,\n position: \"relative\",\n overflow: \"hidden\",\n }\n : {\n position: \"relative\",\n overflow: \"hidden\",\n }\n }\n whileDrag={{ zIndex: 9999 }}\n >\n
\n \n \n {!isExpanded ? (\n \n {/* List Remove Actions */}\n onCompleteItem(item.id)}\n className=\" ml-3 h-5 w-5 rounded-md border-white/20 bg-black/30 data-[state=checked]:bg-black data-[state=checked]:text-red-200\"\n />\n {/* List Order */}\n

\n {order + 1}\n

\n\n {/* List Title */}\n \n \n {item.checked ? \"Delete\" : ` ${item.text}`}\n \n \n \n ) : null}\n
\n\n {/* List Item Children */}\n {renderExtra && renderExtra(item)}\n \n
\n \n \n {/* List Delete Action Animation */}\n \n {item.checked ? (\n \n ) : null}\n \n \n {item.checked ? (\n \n onRemoveItem(item.id)}\n >\n \n \n \n ) : null}\n \n
\n
\n )\n}\n\nSortableListItem.displayName = \"SortableListItem\"\n\ninterface SortableListProps {\n items: Item[]\n setItems: Dispatch>\n onCompleteItem: (id: number) => void\n renderItem: (\n item: Item,\n order: number,\n onCompleteItem: (id: number) => void,\n onRemoveItem: (id: number) => void\n ) => ReactNode\n}\n\nfunction SortableList({\n items,\n setItems,\n onCompleteItem,\n renderItem,\n}: SortableListProps) {\n if (items) {\n return (\n \n \n \n {items?.map((item, index) =>\n renderItem(item, index, onCompleteItem, (id: number) =>\n setItems((items) => items.filter((item) => item.id !== id))\n )\n )}\n \n \n \n )\n }\n return null\n}\n\nSortableList.displayName = \"SortableList\"\n\nexport { SortableList, SortableListItem }\nexport default SortableList\n" + "content": "\"use client\"\n\n// npx shadcn-ui@latest add checkbox\n// npm i react-use-measure\nimport { Dispatch, ReactNode, SetStateAction, useState } from \"react\"\nimport { Trash } from \"lucide-react\"\nimport {\n AnimatePresence,\n LayoutGroup,\n Reorder,\n motion,\n useDragControls,\n} from \"motion/react\"\nimport useMeasure from \"react-use-measure\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\n\nexport type Item = {\n text: string\n checked: boolean\n id: number\n description: string\n}\n\ninterface SortableListItemProps {\n item: Item\n order: number\n onCompleteItem: (id: number) => void\n onRemoveItem: (id: number) => void\n renderExtra?: (item: Item) => React.ReactNode\n isExpanded?: boolean\n className?: string\n handleDrag: () => void\n}\n\nfunction SortableListItem({\n item,\n order,\n onCompleteItem,\n onRemoveItem,\n renderExtra,\n handleDrag,\n isExpanded,\n className,\n}: SortableListItemProps) {\n let [ref, bounds] = useMeasure()\n const [isDragging, setIsDragging] = useState(false)\n const [isDraggable, setIsDraggable] = useState(true)\n const dragControls = useDragControls()\n\n const handleDragStart = (event: any) => {\n setIsDragging(true)\n dragControls.start(event, { snapToCursor: true })\n handleDrag()\n }\n\n const handleDragEnd = () => {\n setIsDragging(false)\n }\n\n return (\n \n
\n 0 ? bounds.height : undefined,\n transition: {\n type: \"spring\",\n bounce: 0,\n duration: 0.4,\n },\n }}\n exit={{\n opacity: 0,\n transition: {\n duration: 0.05,\n type: \"spring\",\n bounce: 0.1,\n },\n }}\n layout\n layoutId={`item-${item.id}`}\n dragListener={!item.checked}\n dragControls={dragControls}\n onDragEnd={handleDragEnd}\n style={\n isExpanded\n ? {\n zIndex: 9999,\n marginTop: 10,\n marginBottom: 10,\n position: \"relative\",\n overflow: \"hidden\",\n }\n : {\n position: \"relative\",\n overflow: \"hidden\",\n }\n }\n whileDrag={{ zIndex: 9999 }}\n >\n
\n \n \n {!isExpanded ? (\n \n {/* List Remove Actions */}\n onCompleteItem(item.id)}\n className=\" ml-3 h-5 w-5 rounded-md border-white/20 bg-black/30 data-[state=checked]:bg-black data-[state=checked]:text-red-200\"\n />\n {/* List Order */}\n

\n {order + 1}\n

\n\n {/* List Title */}\n \n \n {item.checked ? \"Delete\" : ` ${item.text}`}\n \n \n \n ) : null}\n
\n\n {/* List Item Children */}\n {renderExtra && renderExtra(item)}\n \n
\n \n \n {/* List Delete Action Animation */}\n \n {item.checked ? (\n \n ) : null}\n \n \n {item.checked ? (\n \n onRemoveItem(item.id)}\n >\n \n \n \n ) : null}\n \n
\n
\n )\n}\n\nSortableListItem.displayName = \"SortableListItem\"\n\ninterface SortableListProps {\n items: Item[]\n setItems: Dispatch>\n onCompleteItem: (id: number) => void\n renderItem: (\n item: Item,\n order: number,\n onCompleteItem: (id: number) => void,\n onRemoveItem: (id: number) => void\n ) => ReactNode\n}\n\nfunction SortableList({\n items,\n setItems,\n onCompleteItem,\n renderItem,\n}: SortableListProps) {\n if (items) {\n return (\n \n \n \n {items?.map((item, index) =>\n renderItem(item, index, onCompleteItem, (id: number) =>\n setItems((items) => items.filter((item) => item.id !== id))\n )\n )}\n \n \n \n )\n }\n return null\n}\n\nSortableList.displayName = \"SortableList\"\n\nexport { SortableList, SortableListItem }\nexport default SortableList\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/text-animate.json b/apps/www/public/registry/styles/default/text-animate.json index 3ff3538..6a753e2 100644 --- a/apps/www/public/registry/styles/default/text-animate.json +++ b/apps/www/public/registry/styles/default/text-animate.json @@ -6,7 +6,7 @@ "files": [ { "name": "text-animate.tsx", - "content": "\"use client\"\n\nimport { FC, useEffect, useRef } from \"react\"\nimport { HTMLMotionProps, motion, useAnimation, useInView } from \"framer-motion\"\n\ntype AnimationType =\n | \"fadeIn\"\n | \"fadeInUp\"\n | \"popIn\"\n | \"shiftInUp\"\n | \"rollIn\"\n | \"whipIn\"\n | \"whipInUp\"\n | \"calmInUp\"\n\ninterface Props extends HTMLMotionProps<\"div\"> {\n text: string\n type?: AnimationType\n delay?: number\n duration?: number\n}\n\nconst animationVariants = {\n fadeIn: {\n container: {\n hidden: { opacity: 0 },\n visible: (i: number = 1) => ({\n opacity: 1,\n transition: { staggerChildren: 0.05, delayChildren: i * 0.3 },\n }),\n },\n child: {\n visible: {\n opacity: 1,\n y: [0, -10, 0],\n transition: {\n type: \"spring\",\n damping: 12,\n stiffness: 100,\n },\n },\n hidden: { opacity: 0, y: 10 },\n },\n },\n fadeInUp: {\n container: {\n hidden: { opacity: 0 },\n visible: {\n opacity: 1,\n transition: { staggerChildren: 0.1, delayChildren: 0.2 },\n },\n },\n child: {\n visible: { opacity: 1, y: 0, transition: { duration: 0.5 } },\n hidden: { opacity: 0, y: 20 },\n },\n },\n popIn: {\n container: {\n hidden: { scale: 0 },\n visible: {\n scale: 1,\n transition: { staggerChildren: 0.05, delayChildren: 0.2 },\n },\n },\n child: {\n visible: {\n opacity: 1,\n scale: 1.1,\n transition: { type: \"spring\", damping: 15, stiffness: 400 },\n },\n hidden: { opacity: 0, scale: 0 },\n },\n },\n calmInUp: {\n container: {\n hidden: {},\n visible: (i: number = 1) => ({\n transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },\n }),\n },\n child: {\n hidden: {\n y: \"200%\",\n transition: { ease: [0.455, 0.03, 0.515, 0.955], duration: 0.85 },\n },\n visible: {\n y: 0,\n transition: {\n ease: [0.125, 0.92, 0.69, 0.975], // Drawing attention to dynamic content or interactive elements, where the animation needs to be engaging but not abrupt\n duration: 0.75,\n // ease: [0.455, 0.03, 0.515, 0.955], // smooth and gradual acceleration followed by a steady deceleration towards the end of the animation\n // ease: [0.115, 0.955, 0.655, 0.939], // smooth and gradual acceleration followed by a steady deceleration towards the end of the animation\n // ease: [0.09, 0.88, 0.68, 0.98], // Very Gentle Onset, Swift Mid-Section, Soft Landing\n // ease: [0.11, 0.97, 0.64, 0.945], // Minimal Start, Energetic Acceleration, Smooth Closure\n },\n },\n },\n },\n shiftInUp: {\n container: {\n hidden: {},\n visible: (i: number = 1) => ({\n transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },\n }),\n },\n child: {\n hidden: {\n y: \"100%\", // Starting from below but not too far to ensure a dramatic but manageable shift.\n transition: {\n ease: [0.75, 0, 0.25, 1], // Starting quickly\n duration: 0.6, // Shortened duration for a more dramatic start\n },\n },\n visible: {\n y: 0,\n transition: {\n duration: 0.8, // Slightly longer to accommodate the slow middle and swift end\n ease: [0.22, 1, 0.36, 1], // This easing function starts quickly (dramatic shift), slows down (slow middle), and ends quickly (clean swift end)\n },\n },\n },\n },\n\n whipInUp: {\n container: {\n hidden: {},\n visible: (i: number = 1) => ({\n transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },\n }),\n },\n child: {\n hidden: {\n y: \"200%\",\n transition: { ease: [0.455, 0.03, 0.515, 0.955], duration: 0.45 },\n },\n visible: {\n y: 0,\n transition: {\n ease: [0.5, -0.15, 0.25, 1.05],\n duration: 0.75,\n },\n },\n },\n },\n rollIn: {\n container: {\n hidden: {},\n visible: {},\n },\n child: {\n hidden: {\n opacity: 0,\n y: `0.25em`,\n },\n visible: {\n opacity: 1,\n y: `0em`,\n transition: {\n duration: 0.65,\n ease: [0.65, 0, 0.75, 1], // Great! Swift Beginning, Prolonged Ease, Quick Finish\n // ease: [0.75, 0.05, 0.85, 1], // Quick Start, Smooth Middle, Sharp End\n // ease: [0.7, -0.25, 0.9, 1.25], // Fast Acceleration, Gentle Slowdown, Sudden Snap\n // ease: [0.7, -0.5, 0.85, 1.5], // Quick Leap, Soft Glide, Snappy Closure\n },\n },\n },\n },\n whipIn: {\n container: {\n hidden: {},\n visible: {},\n },\n child: {\n hidden: {\n opacity: 0,\n y: `0.35em`,\n },\n visible: {\n opacity: 1,\n y: `0em`,\n transition: {\n duration: 0.45,\n // ease: [0.75, 0.05, 0.85, 1], // Quick Start, Smooth Middle, Sharp End\n // ease: [0.7, -0.25, 0.9, 1.25], // Fast Acceleration, Gentle Slowdown, Sudden Snap\n // ease: [0.65, 0, 0.75, 1], // Great! Swift Beginning, Prolonged Ease, Quick Finish\n ease: [0.85, 0.1, 0.9, 1.2], // Rapid Initiation, Subtle Slow, Sharp Conclusion\n },\n },\n },\n },\n}\n\nconst TextAnimate: FC = ({\n text,\n type = \"whipInUp\",\n ...props\n}: Props) => {\n // const { ref, inView } = useInView({\n // threshold: 0.5,\n // triggerOnce: true,\n // });\n\n const ref = useRef(null)\n const isInView = useInView(ref, { once: true })\n\n const letters = Array.from(text)\n const { container, child } = animationVariants[type]\n\n const ctrls = useAnimation()\n\n // useEffect(() => {\n // if (isInView) {\n // ctrls.start(\"visible\");\n // }\n // if (!isInView) {\n // ctrls.start(\"hidden\");\n // }\n // }, [ctrls, isInView]);\n\n if (type === \"rollIn\" || type === \"whipIn\") {\n return (\n

\n {text.split(\" \").map((word, index) => {\n return (\n \n {word.split(\"\").map((character, index) => {\n return (\n \n {character}\n \n )\n })}\n \n )\n })}\n

\n )\n }\n\n return (\n \n {letters.map((letter, index) => (\n \n {letter === \" \" ? \"\\u00A0\" : letter}\n \n ))}\n \n )\n}\n\nexport { TextAnimate }\nexport default TextAnimate\n" + "content": "\"use client\"\n\nimport { FC, useEffect, useRef } from \"react\"\nimport { HTMLMotionProps, motion, useAnimation, useInView } from \"motion/react\"\n\ntype AnimationType =\n | \"fadeIn\"\n | \"fadeInUp\"\n | \"popIn\"\n | \"shiftInUp\"\n | \"rollIn\"\n | \"whipIn\"\n | \"whipInUp\"\n | \"calmInUp\"\n\ninterface Props extends HTMLMotionProps<\"div\"> {\n text: string\n type?: AnimationType\n delay?: number\n duration?: number\n}\n\nconst animationVariants = {\n fadeIn: {\n container: {\n hidden: { opacity: 0 },\n visible: (i: number = 1) => ({\n opacity: 1,\n transition: { staggerChildren: 0.05, delayChildren: i * 0.3 },\n }),\n },\n child: {\n visible: {\n opacity: 1,\n y: [0, -10, 0],\n transition: {\n type: \"spring\",\n damping: 12,\n stiffness: 100,\n },\n },\n hidden: { opacity: 0, y: 10 },\n },\n },\n fadeInUp: {\n container: {\n hidden: { opacity: 0 },\n visible: {\n opacity: 1,\n transition: { staggerChildren: 0.1, delayChildren: 0.2 },\n },\n },\n child: {\n visible: { opacity: 1, y: 0, transition: { duration: 0.5 } },\n hidden: { opacity: 0, y: 20 },\n },\n },\n popIn: {\n container: {\n hidden: { scale: 0 },\n visible: {\n scale: 1,\n transition: { staggerChildren: 0.05, delayChildren: 0.2 },\n },\n },\n child: {\n visible: {\n opacity: 1,\n scale: 1.1,\n transition: { type: \"spring\", damping: 15, stiffness: 400 },\n },\n hidden: { opacity: 0, scale: 0 },\n },\n },\n calmInUp: {\n container: {\n hidden: {},\n visible: (i: number = 1) => ({\n transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },\n }),\n },\n child: {\n hidden: {\n y: \"200%\",\n transition: { ease: [0.455, 0.03, 0.515, 0.955], duration: 0.85 },\n },\n visible: {\n y: 0,\n transition: {\n ease: [0.125, 0.92, 0.69, 0.975], // Drawing attention to dynamic content or interactive elements, where the animation needs to be engaging but not abrupt\n duration: 0.75,\n // ease: [0.455, 0.03, 0.515, 0.955], // smooth and gradual acceleration followed by a steady deceleration towards the end of the animation\n // ease: [0.115, 0.955, 0.655, 0.939], // smooth and gradual acceleration followed by a steady deceleration towards the end of the animation\n // ease: [0.09, 0.88, 0.68, 0.98], // Very Gentle Onset, Swift Mid-Section, Soft Landing\n // ease: [0.11, 0.97, 0.64, 0.945], // Minimal Start, Energetic Acceleration, Smooth Closure\n },\n },\n },\n },\n shiftInUp: {\n container: {\n hidden: {},\n visible: (i: number = 1) => ({\n transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },\n }),\n },\n child: {\n hidden: {\n y: \"100%\", // Starting from below but not too far to ensure a dramatic but manageable shift.\n transition: {\n ease: [0.75, 0, 0.25, 1], // Starting quickly\n duration: 0.6, // Shortened duration for a more dramatic start\n },\n },\n visible: {\n y: 0,\n transition: {\n duration: 0.8, // Slightly longer to accommodate the slow middle and swift end\n ease: [0.22, 1, 0.36, 1], // This easing function starts quickly (dramatic shift), slows down (slow middle), and ends quickly (clean swift end)\n },\n },\n },\n },\n\n whipInUp: {\n container: {\n hidden: {},\n visible: (i: number = 1) => ({\n transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },\n }),\n },\n child: {\n hidden: {\n y: \"200%\",\n transition: { ease: [0.455, 0.03, 0.515, 0.955], duration: 0.45 },\n },\n visible: {\n y: 0,\n transition: {\n ease: [0.5, -0.15, 0.25, 1.05],\n duration: 0.75,\n },\n },\n },\n },\n rollIn: {\n container: {\n hidden: {},\n visible: {},\n },\n child: {\n hidden: {\n opacity: 0,\n y: `0.25em`,\n },\n visible: {\n opacity: 1,\n y: `0em`,\n transition: {\n duration: 0.65,\n ease: [0.65, 0, 0.75, 1], // Great! Swift Beginning, Prolonged Ease, Quick Finish\n // ease: [0.75, 0.05, 0.85, 1], // Quick Start, Smooth Middle, Sharp End\n // ease: [0.7, -0.25, 0.9, 1.25], // Fast Acceleration, Gentle Slowdown, Sudden Snap\n // ease: [0.7, -0.5, 0.85, 1.5], // Quick Leap, Soft Glide, Snappy Closure\n },\n },\n },\n },\n whipIn: {\n container: {\n hidden: {},\n visible: {},\n },\n child: {\n hidden: {\n opacity: 0,\n y: `0.35em`,\n },\n visible: {\n opacity: 1,\n y: `0em`,\n transition: {\n duration: 0.45,\n // ease: [0.75, 0.05, 0.85, 1], // Quick Start, Smooth Middle, Sharp End\n // ease: [0.7, -0.25, 0.9, 1.25], // Fast Acceleration, Gentle Slowdown, Sudden Snap\n // ease: [0.65, 0, 0.75, 1], // Great! Swift Beginning, Prolonged Ease, Quick Finish\n ease: [0.85, 0.1, 0.9, 1.2], // Rapid Initiation, Subtle Slow, Sharp Conclusion\n },\n },\n },\n },\n}\n\nconst TextAnimate: FC = ({\n text,\n type = \"whipInUp\",\n ...props\n}: Props) => {\n // const { ref, inView } = useInView({\n // threshold: 0.5,\n // triggerOnce: true,\n // });\n\n const ref = useRef(null)\n const isInView = useInView(ref, { once: true })\n\n const letters = Array.from(text)\n const { container, child } = animationVariants[type]\n\n const ctrls = useAnimation()\n\n // useEffect(() => {\n // if (isInView) {\n // ctrls.start(\"visible\");\n // }\n // if (!isInView) {\n // ctrls.start(\"hidden\");\n // }\n // }, [ctrls, isInView]);\n\n if (type === \"rollIn\" || type === \"whipIn\") {\n return (\n

\n {text.split(\" \").map((word, index) => {\n return (\n \n {word.split(\"\").map((character, index) => {\n return (\n \n {character}\n \n )\n })}\n \n )\n })}\n

\n )\n }\n\n return (\n \n {letters.map((letter, index) => (\n \n {letter === \" \" ? \"\\u00A0\" : letter}\n \n ))}\n \n )\n}\n\nexport { TextAnimate }\nexport default TextAnimate\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/three-d-carousel.json b/apps/www/public/registry/styles/default/three-d-carousel.json index 229f925..d436e30 100644 --- a/apps/www/public/registry/styles/default/three-d-carousel.json +++ b/apps/www/public/registry/styles/default/three-d-carousel.json @@ -6,7 +6,7 @@ "files": [ { "name": "three-d-carousel.tsx", - "content": "\"use client\"\n\nimport { memo, useEffect, useLayoutEffect, useMemo, useState } from \"react\"\nimport {\n AnimatePresence,\n motion,\n useAnimation,\n useMotionValue,\n useTransform,\n} from \"framer-motion\"\n\nexport const useIsomorphicLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect\n\ntype UseMediaQueryOptions = {\n defaultValue?: boolean\n initializeWithValue?: boolean\n}\n\nconst IS_SERVER = typeof window === \"undefined\"\n\nexport function useMediaQuery(\n query: string,\n {\n defaultValue = false,\n initializeWithValue = true,\n }: UseMediaQueryOptions = {}\n): boolean {\n const getMatches = (query: string): boolean => {\n if (IS_SERVER) {\n return defaultValue\n }\n return window.matchMedia(query).matches\n }\n\n const [matches, setMatches] = useState(() => {\n if (initializeWithValue) {\n return getMatches(query)\n }\n return defaultValue\n })\n\n const handleChange = () => {\n setMatches(getMatches(query))\n }\n\n useIsomorphicLayoutEffect(() => {\n const matchMedia = window.matchMedia(query)\n handleChange()\n\n matchMedia.addEventListener(\"change\", handleChange)\n\n return () => {\n matchMedia.removeEventListener(\"change\", handleChange)\n }\n }, [query])\n\n return matches\n}\n\nconst keywords = [\n \"night\",\n \"city\",\n \"sky\",\n \"sunset\",\n \"sunrise\",\n \"winter\",\n \"skyscraper\",\n \"building\",\n \"cityscape\",\n \"architecture\",\n \"street\",\n \"lights\",\n \"downtown\",\n \"bridge\",\n]\n\nconst duration = 0.15\nconst transition = { duration, ease: [0.32, 0.72, 0, 1], filter: \"blur(4px)\" }\nconst transitionOverlay = { duration: 0.5, ease: [0.32, 0.72, 0, 1] }\n\nconst Carousel = memo(\n ({\n handleClick,\n controls,\n cards,\n isCarouselActive,\n }: {\n handleClick: (imgUrl: string, index: number) => void\n controls: any\n cards: string[]\n isCarouselActive: boolean\n }) => {\n const isScreenSizeSm = useMediaQuery(\"(max-width: 640px)\")\n const cylinderWidth = isScreenSizeSm ? 1100 : 1800\n const faceCount = cards.length\n const faceWidth = cylinderWidth / faceCount\n const radius = cylinderWidth / (2 * Math.PI)\n const rotation = useMotionValue(0)\n const transform = useTransform(\n rotation,\n (value) => `rotate3d(0, 1, 0, ${value}deg)`\n )\n\n return (\n \n \n isCarouselActive &&\n rotation.set(rotation.get() + info.offset.x * 0.05)\n }\n onDragEnd={(_, info) =>\n isCarouselActive &&\n controls.start({\n rotateY: rotation.get() + info.velocity.x * 0.05,\n transition: {\n type: \"spring\",\n stiffness: 100,\n damping: 30,\n mass: 0.1,\n },\n })\n }\n animate={controls}\n >\n {cards.map((imgUrl, i) => (\n handleClick(imgUrl, i)}\n >\n \n \n ))}\n \n \n )\n }\n)\n\nconst hiddenMask = `repeating-linear-gradient(to right, rgba(0,0,0,0) 0px, rgba(0,0,0,0) 30px, rgba(0,0,0,1) 30px, rgba(0,0,0,1) 30px)`\nconst visibleMask = `repeating-linear-gradient(to right, rgba(0,0,0,0) 0px, rgba(0,0,0,0) 0px, rgba(0,0,0,1) 0px, rgba(0,0,0,1) 30px)`\nfunction ThreeDPhotoCarousel() {\n const [activeImg, setActiveImg] = useState(null)\n const [isCarouselActive, setIsCarouselActive] = useState(true)\n const controls = useAnimation()\n const cards = useMemo(\n () => keywords.map((keyword) => `https://picsum.photos/200/300?${keyword}`),\n []\n )\n\n useEffect(() => {\n console.log(\"Cards loaded:\", cards)\n }, [cards])\n\n const handleClick = (imgUrl: string) => {\n setActiveImg(imgUrl)\n setIsCarouselActive(false)\n controls.stop()\n }\n\n const handleClose = () => {\n setActiveImg(null)\n setIsCarouselActive(true)\n }\n\n return (\n \n \n {activeImg && (\n \n \n \n )}\n \n
\n \n
\n \n )\n}\n\nexport default ThreeDPhotoCarousel\n" + "content": "\"use client\"\n\nimport { memo, useEffect, useLayoutEffect, useMemo, useState } from \"react\"\nimport {\n AnimatePresence,\n motion,\n useAnimation,\n useMotionValue,\n useTransform,\n} from \"motion/react\"\n\nexport const useIsomorphicLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect\n\ntype UseMediaQueryOptions = {\n defaultValue?: boolean\n initializeWithValue?: boolean\n}\n\nconst IS_SERVER = typeof window === \"undefined\"\n\nexport function useMediaQuery(\n query: string,\n {\n defaultValue = false,\n initializeWithValue = true,\n }: UseMediaQueryOptions = {}\n): boolean {\n const getMatches = (query: string): boolean => {\n if (IS_SERVER) {\n return defaultValue\n }\n return window.matchMedia(query).matches\n }\n\n const [matches, setMatches] = useState(() => {\n if (initializeWithValue) {\n return getMatches(query)\n }\n return defaultValue\n })\n\n const handleChange = () => {\n setMatches(getMatches(query))\n }\n\n useIsomorphicLayoutEffect(() => {\n const matchMedia = window.matchMedia(query)\n handleChange()\n\n matchMedia.addEventListener(\"change\", handleChange)\n\n return () => {\n matchMedia.removeEventListener(\"change\", handleChange)\n }\n }, [query])\n\n return matches\n}\n\nconst keywords = [\n \"night\",\n \"city\",\n \"sky\",\n \"sunset\",\n \"sunrise\",\n \"winter\",\n \"skyscraper\",\n \"building\",\n \"cityscape\",\n \"architecture\",\n \"street\",\n \"lights\",\n \"downtown\",\n \"bridge\",\n]\n\nconst duration = 0.15\nconst transition = { duration, ease: [0.32, 0.72, 0, 1], filter: \"blur(4px)\" }\nconst transitionOverlay = { duration: 0.5, ease: [0.32, 0.72, 0, 1] }\n\nconst Carousel = memo(\n ({\n handleClick,\n controls,\n cards,\n isCarouselActive,\n }: {\n handleClick: (imgUrl: string, index: number) => void\n controls: any\n cards: string[]\n isCarouselActive: boolean\n }) => {\n const isScreenSizeSm = useMediaQuery(\"(max-width: 640px)\")\n const cylinderWidth = isScreenSizeSm ? 1100 : 1800\n const faceCount = cards.length\n const faceWidth = cylinderWidth / faceCount\n const radius = cylinderWidth / (2 * Math.PI)\n const rotation = useMotionValue(0)\n const transform = useTransform(\n rotation,\n (value) => `rotate3d(0, 1, 0, ${value}deg)`\n )\n\n return (\n \n \n isCarouselActive &&\n rotation.set(rotation.get() + info.offset.x * 0.05)\n }\n onDragEnd={(_, info) =>\n isCarouselActive &&\n controls.start({\n rotateY: rotation.get() + info.velocity.x * 0.05,\n transition: {\n type: \"spring\",\n stiffness: 100,\n damping: 30,\n mass: 0.1,\n },\n })\n }\n animate={controls}\n >\n {cards.map((imgUrl, i) => (\n handleClick(imgUrl, i)}\n >\n \n \n ))}\n \n \n )\n }\n)\n\nconst hiddenMask = `repeating-linear-gradient(to right, rgba(0,0,0,0) 0px, rgba(0,0,0,0) 30px, rgba(0,0,0,1) 30px, rgba(0,0,0,1) 30px)`\nconst visibleMask = `repeating-linear-gradient(to right, rgba(0,0,0,0) 0px, rgba(0,0,0,0) 0px, rgba(0,0,0,1) 0px, rgba(0,0,0,1) 30px)`\nfunction ThreeDPhotoCarousel() {\n const [activeImg, setActiveImg] = useState(null)\n const [isCarouselActive, setIsCarouselActive] = useState(true)\n const controls = useAnimation()\n const cards = useMemo(\n () => keywords.map((keyword) => `https://picsum.photos/200/300?${keyword}`),\n []\n )\n\n useEffect(() => {\n console.log(\"Cards loaded:\", cards)\n }, [cards])\n\n const handleClick = (imgUrl: string) => {\n setActiveImg(imgUrl)\n setIsCarouselActive(false)\n controls.stop()\n }\n\n const handleClose = () => {\n setActiveImg(null)\n setIsCarouselActive(true)\n }\n\n return (\n \n \n {activeImg && (\n \n \n \n )}\n \n
\n \n
\n \n )\n}\n\nexport default ThreeDPhotoCarousel\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/typewriter.json b/apps/www/public/registry/styles/default/typewriter.json index 93b532f..955117c 100644 --- a/apps/www/public/registry/styles/default/typewriter.json +++ b/apps/www/public/registry/styles/default/typewriter.json @@ -6,7 +6,7 @@ "files": [ { "name": "typewriter.tsx", - "content": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\nimport { animate, motion, useMotionValue, useTransform } from \"framer-motion\"\n\nexport interface ITypewriterProps {\n delay: number\n texts: string[]\n baseText?: string\n}\n\nexport function Typewriter({ delay, texts, baseText = \"\" }: ITypewriterProps) {\n const [animationComplete, setAnimationComplete] = useState(false)\n const count = useMotionValue(0)\n const rounded = useTransform(count, (latest) => Math.round(latest))\n const displayText = useTransform(rounded, (latest) =>\n baseText.slice(0, latest)\n )\n\n useEffect(() => {\n const controls = animate(count, baseText.length, {\n type: \"tween\",\n delay,\n duration: 1,\n ease: \"easeInOut\",\n onComplete: () => setAnimationComplete(true),\n })\n return () => {\n controls.stop && controls.stop()\n }\n }, [count, baseText.length, delay])\n\n return (\n \n {displayText}\n {animationComplete && (\n \n )}\n \n \n )\n}\n\nexport interface IRepeatedTextAnimationProps {\n delay: number\n texts: string[]\n}\n\nconst defaultTexts = [\n \"quiz page with questions and answers\",\n \"blog Article Details Page Layout\",\n \"ecommerce dashboard with a sidebar\",\n \"ui like platform.openai.com....\",\n \"buttttton\",\n \"aop that tracks non-standard split sleep cycles\",\n \"transparent card to showcase achievements of a user\",\n]\nfunction RepeatedTextAnimation({\n delay,\n texts = defaultTexts,\n}: IRepeatedTextAnimationProps) {\n const textIndex = useMotionValue(0)\n\n const baseText = useTransform(textIndex, (latest) => texts[latest] || \"\")\n const count = useMotionValue(0)\n const rounded = useTransform(count, (latest) => Math.round(latest))\n const displayText = useTransform(rounded, (latest) =>\n baseText.get().slice(0, latest)\n )\n const updatedThisRound = useMotionValue(true)\n\n useEffect(() => {\n const animation = animate(count, 60, {\n type: \"tween\",\n delay,\n duration: 1,\n ease: \"easeIn\",\n repeat: Infinity,\n repeatType: \"reverse\",\n repeatDelay: 1,\n onUpdate(latest) {\n if (updatedThisRound.get() && latest > 0) {\n updatedThisRound.set(false)\n } else if (!updatedThisRound.get() && latest === 0) {\n textIndex.set((textIndex.get() + 1) % texts.length)\n updatedThisRound.set(true)\n }\n },\n })\n return () => {\n animation.stop && animation.stop()\n }\n }, [count, delay, textIndex, texts, updatedThisRound])\n\n return {displayText}\n}\n\nconst cursorVariants = {\n blinking: {\n opacity: [0, 0, 1, 1],\n transition: {\n duration: 1,\n repeat: Infinity,\n repeatDelay: 0,\n ease: \"linear\",\n times: [0, 0.5, 0.5, 1],\n },\n },\n}\n\nfunction BlinkingCursor() {\n return (\n \n )\n}\n" + "content": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\nimport { animate, motion, useMotionValue, useTransform } from \"motion/react\"\n\nexport interface ITypewriterProps {\n delay: number\n texts: string[]\n baseText?: string\n}\n\nexport function Typewriter({ delay, texts, baseText = \"\" }: ITypewriterProps) {\n const [animationComplete, setAnimationComplete] = useState(false)\n const count = useMotionValue(0)\n const rounded = useTransform(count, (latest) => Math.round(latest))\n const displayText = useTransform(rounded, (latest) =>\n baseText.slice(0, latest)\n )\n\n useEffect(() => {\n const controls = animate(count, baseText.length, {\n type: \"tween\",\n delay,\n duration: 1,\n ease: \"easeInOut\",\n onComplete: () => setAnimationComplete(true),\n })\n return () => {\n controls.stop && controls.stop()\n }\n }, [count, baseText.length, delay])\n\n return (\n \n {displayText}\n {animationComplete && (\n \n )}\n \n \n )\n}\n\nexport interface IRepeatedTextAnimationProps {\n delay: number\n texts: string[]\n}\n\nconst defaultTexts = [\n \"quiz page with questions and answers\",\n \"blog Article Details Page Layout\",\n \"ecommerce dashboard with a sidebar\",\n \"ui like platform.openai.com....\",\n \"buttttton\",\n \"aop that tracks non-standard split sleep cycles\",\n \"transparent card to showcase achievements of a user\",\n]\nfunction RepeatedTextAnimation({\n delay,\n texts = defaultTexts,\n}: IRepeatedTextAnimationProps) {\n const textIndex = useMotionValue(0)\n\n const baseText = useTransform(textIndex, (latest) => texts[latest] || \"\")\n const count = useMotionValue(0)\n const rounded = useTransform(count, (latest) => Math.round(latest))\n const displayText = useTransform(rounded, (latest) =>\n baseText.get().slice(0, latest)\n )\n const updatedThisRound = useMotionValue(true)\n\n useEffect(() => {\n const animation = animate(count, 60, {\n type: \"tween\",\n delay,\n duration: 1,\n ease: \"easeIn\",\n repeat: Infinity,\n repeatType: \"reverse\",\n repeatDelay: 1,\n onUpdate(latest) {\n if (updatedThisRound.get() && latest > 0) {\n updatedThisRound.set(false)\n } else if (!updatedThisRound.get() && latest === 0) {\n textIndex.set((textIndex.get() + 1) % texts.length)\n updatedThisRound.set(true)\n }\n },\n })\n return () => {\n animation.stop && animation.stop()\n }\n }, [count, delay, textIndex, texts, updatedThisRound])\n\n return {displayText}\n}\n\nconst cursorVariants = {\n blinking: {\n opacity: [0, 0, 1, 1],\n transition: {\n duration: 1,\n repeat: Infinity,\n repeatDelay: 0,\n ease: \"linear\",\n times: [0, 0.5, 0.5, 1],\n },\n },\n}\n\nfunction BlinkingCursor() {\n return (\n \n )\n}\n" } ], "type": "components:ui" diff --git a/apps/www/registry/default/example/color-picker-demo.tsx b/apps/www/registry/default/example/color-picker-demo.tsx index a3ae439..8f06ff6 100644 --- a/apps/www/registry/default/example/color-picker-demo.tsx +++ b/apps/www/registry/default/example/color-picker-demo.tsx @@ -1,8 +1,8 @@ "use client" import React, { useCallback, useState } from "react" -import { motion } from "framer-motion" import { Check, Copy, Lock, LockOpen, Palette, RefreshCw } from "lucide-react" +import { motion } from "motion/react" import { Poline, positionFunctions } from "poline" import { Button } from "@/components/ui/button" diff --git a/apps/www/registry/default/example/dynamic-island-demo.tsx b/apps/www/registry/default/example/dynamic-island-demo.tsx index a0c1bb2..a2b458f 100644 --- a/apps/www/registry/default/example/dynamic-island-demo.tsx +++ b/apps/www/registry/default/example/dynamic-island-demo.tsx @@ -1,7 +1,6 @@ "use client" import { createContext, useContext } from "react" -import { motion, useReducedMotion } from "framer-motion" import { ArrowUpLeftSquareIcon, Loader, @@ -11,6 +10,7 @@ import { User, Waves, } from "lucide-react" +import { motion, useReducedMotion } from "motion/react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" diff --git a/apps/www/registry/default/example/expandable-demo.tsx b/apps/www/registry/default/example/expandable-demo.tsx index b45a4f4..29c451f 100644 --- a/apps/www/registry/default/example/expandable-demo.tsx +++ b/apps/www/registry/default/example/expandable-demo.tsx @@ -1,7 +1,6 @@ "use client" import React, { useState } from "react" -import { AnimatePresence, motion } from "framer-motion" import { Battery, Bluetooth, @@ -20,6 +19,7 @@ import { Video, Wind, } from "lucide-react" +import { AnimatePresence, motion } from "motion/react" import { toast } from "sonner" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" diff --git a/apps/www/registry/default/example/family-button-demo.tsx b/apps/www/registry/default/example/family-button-demo.tsx index 25bcaa5..68c0324 100644 --- a/apps/www/registry/default/example/family-button-demo.tsx +++ b/apps/www/registry/default/example/family-button-demo.tsx @@ -1,7 +1,7 @@ "use client" import { useMemo, useState } from "react" -import { AnimatePresence, MotionConfig, motion } from "framer-motion" +import { AnimatePresence, MotionConfig, motion } from "motion/react" import useMeasure from "react-use-measure" import FamilyButton from "../ui/family-button" diff --git a/apps/www/registry/default/example/floating-panel-demo.tsx b/apps/www/registry/default/example/floating-panel-demo.tsx index dc4a501..5c61c71 100644 --- a/apps/www/registry/default/example/floating-panel-demo.tsx +++ b/apps/www/registry/default/example/floating-panel-demo.tsx @@ -1,8 +1,8 @@ "use client" import React from "react" -import { AnimatePresence, motion } from "framer-motion" import { Image as ImageIcon, Paintbrush, Plus, X } from "lucide-react" +import { AnimatePresence, motion } from "motion/react" import { FloatingPanelBody, diff --git a/apps/www/registry/default/example/hover-video-player-demo.tsx b/apps/www/registry/default/example/hover-video-player-demo.tsx new file mode 100644 index 0000000..5d64275 --- /dev/null +++ b/apps/www/registry/default/example/hover-video-player-demo.tsx @@ -0,0 +1,47 @@ +"use client" + +import React from "react" +import { motion } from "framer-motion" + +import { cn } from "@/lib/utils" + +import { GradientHeading } from "../ui/gradient-heading" +import { HoverVideoPlayer } from "../ui/hover-video-player" + +export default function HoverVideoPlayerDemo() { + return ( +
+
+ Hover video player +
+ + + + + + newcopy.ai + +
+ ) +} diff --git a/apps/www/registry/default/example/loading-carousel-demo.tsx b/apps/www/registry/default/example/loading-carousel-demo.tsx new file mode 100644 index 0000000..92439cd --- /dev/null +++ b/apps/www/registry/default/example/loading-carousel-demo.tsx @@ -0,0 +1,83 @@ +"use client" + +import React from "react" + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" + +import { LoadingCarousel } from "../ui/loading-carousel" + +export default function LoadingCarouselDemo() { + return ( +
+
+ + Default LoadingCarousel + + + + +
+ +
+ + Wide Aspect Ratio with Top Text + + + + +
+ +
+ + Background Tips + Gradient + + + + +
+ +
+ + Custom Interval and Navigation + + + + +
+ +
+ + Shuffled Tips with Custom Interval + + + + +
+ +
+ + Square Aspect Ratio with Background Tips + + + + +
+
+ ) +} diff --git a/apps/www/registry/default/example/logo-carousel-demo.tsx b/apps/www/registry/default/example/logo-carousel-demo.tsx new file mode 100644 index 0000000..64e5173 --- /dev/null +++ b/apps/www/registry/default/example/logo-carousel-demo.tsx @@ -0,0 +1,25 @@ +"use client" + +import React from "react" + +import { GradientHeading } from "../ui/gradient-heading" +import LogoCarousel from "../ui/logo-carousel" + +export default function LogoCarouselDemo() { + return ( +
+
+
+ + The best are already here + + + Join new cult + +
+ + +
+
+ ) +} diff --git a/apps/www/registry/default/example/shader-lens-blur-demo.tsx b/apps/www/registry/default/example/shader-lens-blur-demo.tsx index a919796..e3702dd 100644 --- a/apps/www/registry/default/example/shader-lens-blur-demo.tsx +++ b/apps/www/registry/default/example/shader-lens-blur-demo.tsx @@ -1,9 +1,9 @@ "use client" import React, { useCallback } from "react" -import { motion } from "framer-motion" import { useAtom } from "jotai" import { Circle, CircleOff, Sliders, Square, Triangle } from "lucide-react" +import { motion } from "motion/react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Label } from "@/components/ui/label" diff --git a/apps/www/registry/default/example/shift-card-demo.tsx b/apps/www/registry/default/example/shift-card-demo.tsx index 80da76d..8bb9946 100644 --- a/apps/www/registry/default/example/shift-card-demo.tsx +++ b/apps/www/registry/default/example/shift-card-demo.tsx @@ -1,6 +1,6 @@ "use client" -import { motion } from "framer-motion" +import { motion } from "motion/react" import { ShiftCard } from "@/registry/default/ui/shift-card" import { TextureButton } from "@/registry/default/ui/texture-button" diff --git a/apps/www/registry/default/example/side-panel-demo.tsx b/apps/www/registry/default/example/side-panel-demo.tsx index 7168df1..513efb3 100644 --- a/apps/www/registry/default/example/side-panel-demo.tsx +++ b/apps/www/registry/default/example/side-panel-demo.tsx @@ -7,7 +7,7 @@ import React, { useContext, useState, } from "react" -import { AnimatePresence, MotionConfig, motion } from "framer-motion" +import { AnimatePresence, MotionConfig, motion } from "motion/react" import ReactPlayer from "react-player/lazy" import useMeasure from "react-use-measure" diff --git a/apps/www/registry/default/example/sortable-list-demo.tsx b/apps/www/registry/default/example/sortable-list-demo.tsx index e612612..a715656 100644 --- a/apps/www/registry/default/example/sortable-list-demo.tsx +++ b/apps/www/registry/default/example/sortable-list-demo.tsx @@ -1,8 +1,8 @@ "use client" import { useCallback, useState } from "react" -import { AnimatePresence, LayoutGroup, motion } from "framer-motion" import { Plus, RepeatIcon, Settings2Icon, XIcon } from "lucide-react" +import { AnimatePresence, LayoutGroup, motion } from "motion/react" import { toast } from "sonner" import { cn } from "@/lib/utils" diff --git a/apps/www/registry/default/example/text-animate-demo.tsx b/apps/www/registry/default/example/text-animate-demo.tsx index 797a352..f1df964 100644 --- a/apps/www/registry/default/example/text-animate-demo.tsx +++ b/apps/www/registry/default/example/text-animate-demo.tsx @@ -1,7 +1,7 @@ "use client" import { useRef, useState } from "react" -import { motion, useInView } from "framer-motion" +import { motion, useInView } from "motion/react" import { FadeIn } from "@/components/fade-in" diff --git a/apps/www/registry/default/ui/animated-number.tsx b/apps/www/registry/default/ui/animated-number.tsx index 92c8353..3f59089 100644 --- a/apps/www/registry/default/ui/animated-number.tsx +++ b/apps/www/registry/default/ui/animated-number.tsx @@ -1,7 +1,7 @@ "use client" import { useEffect } from "react" -import { MotionValue, motion, useSpring, useTransform } from "framer-motion" +import { MotionValue, motion, useSpring, useTransform } from "motion/react" interface AnimatedNumberProps { value: number diff --git a/apps/www/registry/default/ui/bg-animated-fractal-dot-grid.tsx b/apps/www/registry/default/ui/bg-animated-fractal-dot-grid.tsx index 29b5bd9..de690f0 100644 --- a/apps/www/registry/default/ui/bg-animated-fractal-dot-grid.tsx +++ b/apps/www/registry/default/ui/bg-animated-fractal-dot-grid.tsx @@ -1,7 +1,7 @@ "use client" import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" -import { AnimatePresence, motion } from "framer-motion" +import { AnimatePresence, motion } from "motion/react" interface FractalDotGridProps { /** Size of each dot in pixels */ diff --git a/apps/www/registry/default/ui/bg-animated-gradient.tsx b/apps/www/registry/default/ui/bg-animated-gradient.tsx index 8caa2cb..504e688 100644 --- a/apps/www/registry/default/ui/bg-animated-gradient.tsx +++ b/apps/www/registry/default/ui/bg-animated-gradient.tsx @@ -1,7 +1,7 @@ "use client" import React, { useEffect } from "react" -import { motion, useAnimation } from "framer-motion" +import { motion, useAnimation } from "motion/react" interface GradientStop { color: string diff --git a/apps/www/registry/default/ui/canvas-fractal-grid.tsx b/apps/www/registry/default/ui/canvas-fractal-grid.tsx index 39a7fb8..0f713e8 100644 --- a/apps/www/registry/default/ui/canvas-fractal-grid.tsx +++ b/apps/www/registry/default/ui/canvas-fractal-grid.tsx @@ -1,7 +1,7 @@ "use client" import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" -import { AnimatePresence, motion, useAnimation } from "framer-motion" +import { AnimatePresence, motion, useAnimation } from "motion/react" interface GradientStop { color: string @@ -715,7 +715,7 @@ export default React.memo(CanvasFractalGrid) // "use client" // import React, { useCallback, useEffect, useMemo, useRef } from "react" -// import { AnimatePresence, motion, useAnimation, useSpring } from "framer-motion" +// import { AnimatePresence, motion, useAnimation, useSpring } from "motion/react" // const NoiseSVG = React.memo(() => ( // @@ -1077,7 +1077,7 @@ export default React.memo(CanvasFractalGrid) // "use client" // import React, { useCallback, useEffect, useMemo, useRef } from "react" -// import { AnimatePresence, motion, useAnimation, useSpring } from "framer-motion" +// import { AnimatePresence, motion, useAnimation, useSpring } from "motion/react" // const NoiseSVG = React.memo(() => ( // diff --git a/apps/www/registry/default/ui/color-picker.tsx b/apps/www/registry/default/ui/color-picker.tsx index 9c2541f..d8577b8 100644 --- a/apps/www/registry/default/ui/color-picker.tsx +++ b/apps/www/registry/default/ui/color-picker.tsx @@ -1,8 +1,8 @@ "use client" import React, { useEffect, useState } from "react" -import { AnimatePresence, motion } from "framer-motion" import { Check, ChevronDown } from "lucide-react" +import { AnimatePresence, motion } from "motion/react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" diff --git a/apps/www/registry/default/ui/direction-aware-tabs.tsx b/apps/www/registry/default/ui/direction-aware-tabs.tsx index 247b3b2..6d47ff9 100644 --- a/apps/www/registry/default/ui/direction-aware-tabs.tsx +++ b/apps/www/registry/default/ui/direction-aware-tabs.tsx @@ -1,7 +1,7 @@ "use client" import { ReactNode, useMemo, useState } from "react" -import { AnimatePresence, MotionConfig, motion } from "framer-motion" +import { AnimatePresence, MotionConfig, motion } from "motion/react" import useMeasure from "react-use-measure" import { cn } from "@/lib/utils" diff --git a/apps/www/registry/default/ui/dock.tsx b/apps/www/registry/default/ui/dock.tsx index fc38444..4ac8185 100644 --- a/apps/www/registry/default/ui/dock.tsx +++ b/apps/www/registry/default/ui/dock.tsx @@ -19,7 +19,7 @@ import { useMotionValue, useSpring, useTransform, -} from "framer-motion" +} from "motion/react" import { cn } from "@/lib/utils" diff --git a/apps/www/registry/default/ui/dynamic-island.tsx b/apps/www/registry/default/ui/dynamic-island.tsx index 7aa2179..b864c9c 100644 --- a/apps/www/registry/default/ui/dynamic-island.tsx +++ b/apps/www/registry/default/ui/dynamic-island.tsx @@ -10,7 +10,7 @@ import React, { useRef, useState, } from "react" -import { AnimatePresence, motion, useWillChange } from "framer-motion" +import { AnimatePresence, motion, useWillChange } from "motion/react" const stiffness = 400 const damping = 30 diff --git a/apps/www/registry/default/ui/expandable.tsx b/apps/www/registry/default/ui/expandable.tsx index 66e254e..f83bd7b 100644 --- a/apps/www/registry/default/ui/expandable.tsx +++ b/apps/www/registry/default/ui/expandable.tsx @@ -13,7 +13,7 @@ import { motion, useMotionValue, useSpring, -} from "framer-motion" +} from "motion/react" import useMeasure from "react-use-measure" import { cn } from "@/lib/utils" diff --git a/apps/www/registry/default/ui/family-button.tsx b/apps/www/registry/default/ui/family-button.tsx index aa88472..288043f 100644 --- a/apps/www/registry/default/ui/family-button.tsx +++ b/apps/www/registry/default/ui/family-button.tsx @@ -1,8 +1,8 @@ "use client" import { FC, ReactNode, useState } from "react" -import { motion } from "framer-motion" import { PlusIcon, XIcon } from "lucide-react" +import { motion } from "motion/react" import { cn } from "@/lib/utils" diff --git a/apps/www/registry/default/ui/floating-panel.tsx b/apps/www/registry/default/ui/floating-panel.tsx index 76ee521..9fe3c46 100644 --- a/apps/www/registry/default/ui/floating-panel.tsx +++ b/apps/www/registry/default/ui/floating-panel.tsx @@ -8,8 +8,8 @@ import React, { useRef, useState, } from "react" -import { AnimatePresence, MotionConfig, Variants, motion } from "framer-motion" import { ArrowLeftIcon } from "lucide-react" +import { AnimatePresence, MotionConfig, Variants, motion } from "motion/react" import { cn } from "@/lib/utils" @@ -490,7 +490,7 @@ export default { // useRef, // useState, // } from "react" -// import { AnimatePresence, MotionConfig, motion } from "framer-motion" +// import { AnimatePresence, MotionConfig, motion } from "motion/react" // import { ArrowLeftIcon } from "lucide-react" // import { cn } from "@/lib/utils" diff --git a/apps/www/registry/default/ui/gradient-heading.tsx b/apps/www/registry/default/ui/gradient-heading.tsx index 68aaea0..fd683dd 100644 --- a/apps/www/registry/default/ui/gradient-heading.tsx +++ b/apps/www/registry/default/ui/gradient-heading.tsx @@ -14,7 +14,7 @@ const headingVariants = cva( pink: "bg-gradient-to-t from-accent to-accent/90 dark:from-stone-200 dark:to-neutral-200", light: "bg-gradient-to-t from-neutral-200 to-neutral-300", secondary: - "bg-gradient-to-t from-primary-foreground to-muted-foreground", + "bg-gradient-to-t from-neutral-500 to-neutral-600 dark:from-stone-200 dark:to-neutral-200", }, size: { default: "text-2xl sm:text-3xl lg:text-4xl", @@ -24,6 +24,7 @@ const headingVariants = cva( md: "text-2xl sm:text-3xl lg:text-4xl", lg: "text-3xl sm:text-4xl lg:text-5xl", xl: "text-4xl sm:text-5xl lg:text-6xl", + xll: "text-5xl sm:text-6xl lg:text-[5.4rem] leading-[0.5rem] lg:leading-[0.5rem] ", xxl: "text-5xl sm:text-6xl lg:text-[6rem]", xxxl: "text-5xl sm:text-6xl lg:text-[8rem]", }, diff --git a/apps/www/registry/default/ui/hover-video-player.tsx b/apps/www/registry/default/ui/hover-video-player.tsx new file mode 100644 index 0000000..ac5b468 --- /dev/null +++ b/apps/www/registry/default/ui/hover-video-player.tsx @@ -0,0 +1,1055 @@ +"use client" + +/** + * HoverVideoPlayer Component + * + * A React component that plays video on hover/touch with advanced features like: + * - Lazy loading and intersection observer support + * - Mobile touch support + * - Picture-in-Picture + * - Custom overlay support + * - Thumbnail support + * - Playback controls + * + * @example + * ```tsx + * } + * loadingOverlay={} + * enableControls + * /> + * ``` + */ +import React, { + ReactNode, + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react" +import Image from "next/image" +import { AnimatePresence, motion } from "framer-motion" +import { Maximize, Minimize, Pause, Play, Volume2, VolumeX } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Slider } from "@/components/ui/slider" + +// Types +interface VideoPlayerState { + isHovering: boolean + isPlaying: boolean + isLoading: boolean + progress: number + muted: boolean + volume: number + isPiP: boolean + isMobile: boolean + controlsVisible: boolean + showThumbnail: boolean + isInView: boolean +} + +interface HoverVideoPlayerProps { + videoSrc: string + thumbnailSrc?: string + hoverOverlay?: React.ReactNode + pausedOverlay?: React.ReactNode + loadingOverlay?: React.ReactNode + playbackStartDelay?: number + restartOnPaused?: boolean + unloadVideoOnPaused?: boolean + playbackRangeStart?: number + playbackRangeEnd?: number + muted?: boolean + loop?: boolean + preload?: "auto" | "metadata" | "none" + className?: string + style?: React.CSSProperties + onHoverStart?: () => void + onHoverEnd?: () => void + enableControls?: boolean + cropTop?: number + cropBottom?: number + isVimeo?: boolean +} + +interface HoverVideoPlayerContextType { + isPlaying: boolean + isHovering: boolean + isLoading: boolean + progress: number + volume: number + muted: boolean + isPiP: boolean + isMobile: boolean + controlsVisible: boolean + videoRef: React.RefObject + togglePlay: () => void + toggleMute: () => void + togglePiP: () => void + setVolume: (value: number) => void + setProgress: (value: number) => void + cropTop: number + cropBottom: number + thumbnailSrc?: string +} + +interface VimeoPlayer { + destroy: () => void + ready: () => Promise + setVolume: (volume: number) => Promise + play: () => Promise + pause: () => Promise + on: (event: string, callback: (...args: any[]) => void) => void + off: (event: string, callback: (...args: any[]) => void) => void +} + +interface VimeoConstructor { + Player: { + new (element: HTMLElement, options: any): VimeoPlayer + } +} + +// Context +const HoverVideoPlayerContext = + createContext(null) + +/** + * Custom hook to access HoverVideoPlayer context + * @throws {Error} If used outside of HoverVideoPlayerContext + */ +const useHoverVideoPlayer = () => { + const context = useContext(HoverVideoPlayerContext) + if (!context) { + throw new Error( + "useHoverVideoPlayer must be used within a HoverVideoPlayer" + ) + } + return context +} + +// Add this helper function before the HoverVideoPlayer component +function debounce any>( + func: T, + wait: number +): { + (...args: Parameters): void + cancel: () => void +} { + let timeoutId: NodeJS.Timeout | null = null + + const debouncedFn = (...args: Parameters) => { + if (timeoutId) clearTimeout(timeoutId) + timeoutId = setTimeout(() => func(...args), wait) + } + + debouncedFn.cancel = () => { + if (timeoutId) { + clearTimeout(timeoutId) + timeoutId = null + } + } + + return debouncedFn +} + +// Add this helper function +function isVimeoUrl(url: string): boolean { + return url.includes("player.vimeo.com/video/") || url.includes("vimeo.com/") +} + +// Add this helper function +function loadVimeoSDK(): Promise { + return new Promise((resolve, reject) => { + if ((window as any).Vimeo) { + resolve((window as any).Vimeo) + return + } + + const script = document.createElement("script") + script.src = "https://player.vimeo.com/api/player.js" + script.async = true + script.onload = () => resolve((window as any).Vimeo) + script.onerror = reject + document.body.appendChild(script) + }) +} + +// Main Component +const HoverVideoPlayer: React.FC = ({ + videoSrc, + thumbnailSrc, + hoverOverlay, + pausedOverlay, + loadingOverlay, + playbackStartDelay = 0, + restartOnPaused = false, + unloadVideoOnPaused = false, + playbackRangeStart, + playbackRangeEnd, + muted: initialMuted = false, + loop = true, + preload = "metadata", + className, + style, + onHoverStart, + onHoverEnd, + enableControls = false, + cropTop = 0, + cropBottom = 0, + isVimeo = false, +}) => { + // Refs for DOM elements and timing + const containerRef = useRef(null) + const videoRef = useRef(null) + const playbackTimeoutRef = useRef() + const lastPlayAttemptRef = useRef(0) + + // Consolidated state management + const [state, setState] = useState({ + isHovering: false, + isPlaying: false, + isLoading: false, + progress: 0, + muted: initialMuted, + volume: 1, + isPiP: false, + isMobile: false, + controlsVisible: false, + showThumbnail: true, + isInView: false, + }) + + // Mobile detection + const checkMobile = useMemo( + () => + debounce(() => { + setState((prev) => ({ ...prev, isMobile: window.innerWidth <= 768 })) + }, 200), + [] + ) + + // Event Handlers + const handleTouchStart = useCallback(() => { + if (state.isMobile) { + setState((prev) => ({ ...prev, controlsVisible: !prev.controlsVisible })) + } + }, [state.isMobile]) + + const handleHoverStart = useCallback(() => { + if (!state.isMobile) { + console.log("Hover start") + setState((prev) => ({ ...prev, isHovering: true })) + onHoverStart?.() + } + }, [state.isMobile, onHoverStart]) + + const handleHoverEnd = useCallback(() => { + if (!state.isMobile) { + console.log("Hover end") + setState((prev) => ({ ...prev, isHovering: false })) + onHoverEnd?.() + } + }, [state.isMobile, onHoverEnd]) + + // Video Playback Controls + const playVideo = useCallback(() => { + if (!videoRef.current || !state.isInView) { + console.log("PlayVideo blocked:", { + hasVideo: !!videoRef.current, + isInView: state.isInView, + }) + return + } + + const video = videoRef.current + console.log("Attempting to play video:", { + readyState: video.readyState, + paused: video.paused, + currentSrc: video.currentSrc, + }) + + // Reset loading state when attempting to play + setState((prev) => ({ ...prev, isLoading: true })) + + // Ensure video is ready to play + const attemptPlay = () => { + video + .play() + .then(() => { + console.log("Video play success") + setState((prev) => ({ + ...prev, + isLoading: false, + isPlaying: true, + showThumbnail: false, + })) + }) + .catch((error) => { + console.log("Video play error:", error.name) + if (error.name === "NotAllowedError") { + // User interaction required - show thumbnail + setState((prev) => ({ + ...prev, + isLoading: false, + showThumbnail: true, + })) + } else if (error.name !== "AbortError") { + console.error("HoverVideoPlayer: Playback error:", error) + setState((prev) => ({ + ...prev, + isLoading: false, + showThumbnail: true, + })) + } + }) + } + + if (video.readyState >= 3) { + console.log("Video ready, attempting immediate play") + attemptPlay() + } else { + console.log("Video not ready, waiting for canplay event") + const handleCanPlay = () => { + console.log("Canplay event fired") + video.removeEventListener("canplay", handleCanPlay) + attemptPlay() + } + video.addEventListener("canplay", handleCanPlay) + } + }, [state.isInView]) + + const pauseVideo = useCallback(() => { + if (!videoRef.current) return + + videoRef.current.pause() + setState((prev) => ({ + ...prev, + isPlaying: false, + showThumbnail: true, + })) + + if (restartOnPaused) { + videoRef.current.currentTime = playbackRangeStart || 0 + } + }, [restartOnPaused, playbackRangeStart]) + + // Effects + + // Mobile detection effect + useEffect(() => { + checkMobile() + window.addEventListener("resize", checkMobile) + return () => { + window.removeEventListener("resize", checkMobile) + checkMobile.cancel() + } + }, [checkMobile]) + + // Intersection Observer effect + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + const isNowInView = entry.isIntersecting + setState((prev) => ({ ...prev, isInView: isNowInView })) + + if (!isNowInView) { + pauseVideo() + } + }) + }, + { + root: null, + rootMargin: "50px", + threshold: 0.1, + } + ) + + if (containerRef.current) { + observer.observe(containerRef.current) + } + + return () => observer.disconnect() + }, [pauseVideo]) + + // Video event handlers effect + useEffect(() => { + const video = videoRef.current + if (!video || !state.isInView) return + + const handlers = { + loadstart: () => { + console.log("Video loadstart") + setState((prev) => ({ ...prev, isLoading: true })) + }, + loadeddata: () => { + console.log("Video loadeddata", { + readyState: video.readyState, + duration: video.duration, + paused: video.paused, + isHovering: state.isHovering, + controlsVisible: state.controlsVisible, + }) + setState((prev) => ({ ...prev, isLoading: false })) + // Only attempt to play if we're hovering or controls are visible + if (state.isHovering || state.controlsVisible) { + console.log("Video loaded and hover/controls active, attempting play") + playVideo() + } + }, + canplay: () => { + console.log("Video canplay event") + }, + playing: () => { + console.log("Video playing") + setState((prev) => ({ + ...prev, + isLoading: false, + isPlaying: true, + showThumbnail: false, + })) + }, + pause: () => { + console.log("Video paused") + setState((prev) => ({ + ...prev, + isPlaying: false, + showThumbnail: true, + })) + }, + error: (e: Event) => { + const videoError = (e.target as HTMLVideoElement).error + console.error("Video error:", { + code: videoError?.code, + message: videoError?.message, + currentSrc: video.currentSrc, + }) + setState((prev) => ({ + ...prev, + isLoading: false, + showThumbnail: true, + })) + }, + } + + // Add event listeners + Object.entries(handlers).forEach(([event, handler]) => { + video.addEventListener(event, handler) + }) + + // Set initial source and load the video + if (!video.src) { + console.log("Setting video source:", videoSrc) + video.src = videoSrc + video.load() + } + + return () => { + Object.entries(handlers).forEach(([event, handler]) => { + video.removeEventListener(event, handler) + }) + } + }, [ + state.isInView, + state.isHovering, + state.controlsVisible, + playVideo, + videoSrc, + ]) + + // Playback control effect + useEffect(() => { + console.log("Playback control effect:", { + isInView: state.isInView, + isMobile: state.isMobile, + controlsVisible: state.controlsVisible, + isHovering: state.isHovering, + }) + + if (!state.isInView) { + console.log("Not in view, skipping playback") + return + } + + let playbackTimeout: NodeJS.Timeout | undefined + + if (state.isMobile) { + if (state.controlsVisible) { + console.log("Mobile controls visible, playing") + playVideo() + } else { + console.log("Mobile controls hidden, pausing") + pauseVideo() + } + } else if (state.isHovering) { + console.log( + "Hovering, scheduling playback with delay:", + playbackStartDelay + ) + playbackTimeout = setTimeout(() => { + console.log("Playback delay completed, attempting to play") + playVideo() + }, playbackStartDelay) + } else { + console.log("Not hovering, pausing") + pauseVideo() + } + + return () => { + if (playbackTimeout) { + console.log("Clearing playback timeout") + clearTimeout(playbackTimeout) + } + } + }, [ + state.isInView, + state.isMobile, + state.controlsVisible, + state.isHovering, + playVideo, + pauseVideo, + playbackStartDelay, + ]) + + // Volume effect + useEffect(() => { + const video = videoRef.current + if (!video) return + + video.muted = state.muted + video.volume = state.volume + }, [state.muted, state.volume]) + + // Control handlers + const togglePlayPause = useCallback(() => { + if (state.isPlaying) { + pauseVideo() + } else { + playVideo() + } + }, [state.isPlaying, pauseVideo, playVideo]) + + const toggleMute = useCallback(() => { + setState((prev) => ({ ...prev, muted: !prev.muted })) + }, []) + + const togglePiP = useCallback(async () => { + if (!document.pictureInPictureEnabled) return + + try { + if (document.pictureInPictureElement) { + await document.exitPictureInPicture() + setState((prev) => ({ ...prev, isPiP: false })) + } else if (videoRef.current) { + await videoRef.current.requestPictureInPicture() + setState((prev) => ({ ...prev, isPiP: true })) + } + } catch (error) { + console.error("PiP error:", error) + } + }, []) + + // Context value + const contextValue = useMemo( + () => ({ + isPlaying: state.isPlaying, + isHovering: state.isHovering, + isLoading: state.isLoading, + progress: state.progress, + volume: state.volume, + muted: state.muted, + isPiP: state.isPiP, + isMobile: state.isMobile, + controlsVisible: state.controlsVisible, + videoRef, + togglePlay: togglePlayPause, + toggleMute, + togglePiP, + setVolume: (value) => setState((prev) => ({ ...prev, volume: value })), + setProgress: (value) => + setState((prev) => ({ ...prev, progress: value })), + cropTop, + cropBottom, + thumbnailSrc, + }), + [ + state, + togglePlayPause, + toggleMute, + togglePiP, + cropTop, + cropBottom, + thumbnailSrc, + ] + ) + + // Render + return ( + + + {/* Video Element */} + {state.isInView && ( + + )} + + {/* Thumbnail */} + {thumbnailSrc && (state.showThumbnail || !state.isInView) && ( + + )} + + {/* Overlays */} + {pausedOverlay && ( + + {pausedOverlay} + + )} + {loadingOverlay && ( + + {loadingOverlay} + + )} + {hoverOverlay && !state.isMobile && ( + + {hoverOverlay} + + )} + + {/* Controls */} + {enableControls && ( + +
+ + + +
+ + + )} + + + ) +} + +/** + * Video component that handles the actual video element + */ +const HoverVideoPlayerVideo: React.FC<{ + src: string + unloadVideoOnPaused: boolean + loop: boolean + preload: string +}> = ({ src, unloadVideoOnPaused, loop, preload }) => { + const { videoRef, muted, cropTop, cropBottom, isHovering } = + useHoverVideoPlayer() + const isVimeoVideo = isVimeoUrl(src) + const containerRef = useRef(null) + const playerRef = useRef(null) + + useEffect(() => { + if (!isVimeoVideo || !containerRef.current) return + + const videoId = src.split("/").pop() || "" + let player: VimeoPlayer | null = null + + loadVimeoSDK() + .then((Vimeo) => { + if (!containerRef.current) return + + player = new Vimeo.Player(containerRef.current, { + id: videoId, + autopause: false, + muted, + loop, + responsive: true, + controls: false, + autoplay: false, + volume: 1, + }) + + playerRef.current = player + + player.ready().then(() => { + console.log("Vimeo video loaded") + if (isHovering) { + player?.play() + } + }) + + // Add hover effect handlers + const handlePlay = () => { + if (player && isHovering) { + player.play().catch((error) => { + console.error("Vimeo play error:", error) + }) + } + } + + const handlePause = () => { + if (player) { + player.pause().catch((error) => { + console.error("Vimeo pause error:", error) + }) + } + } + + // Watch for hover state changes + if (isHovering) { + handlePlay() + } else { + handlePause() + } + + player.on("play", () => console.log("Vimeo video playing")) + player.on("pause", () => console.log("Vimeo video paused")) + player.on("error", (err) => { + console.error("Vimeo player error:", err) + }) + }) + .catch((error) => { + console.error("Failed to load Vimeo SDK:", error) + }) + + return () => { + if (player) { + player.destroy() + playerRef.current = null + } + } + }, [isVimeoVideo, src, muted, loop, isHovering]) + + if (isVimeoVideo) { + return
+ } + + return ( +