{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "wavy-block",
  "type": "registry:block",
  "title": "Wavy block",
  "description": "Wavy block",
  "files": [
    {
      "path": "components/usages/wavyblockusage.tsx",
      "content": "import { WavyBlock, WavyBlockItem } from \"@/registry/open-source/wavy-block\";\n\nexport default function Usage() {\n\n    const titles = [\n        'Flexible',\n        'Animated',\n        'Customizable',\n        'Optimized',\n        'Lightweight',\n        'Responsive',\n        'UI Blocks',\n    ];\n    return (\n        <div className=\"relative w-full flex items-center justify-center\">\n            <WavyBlock className=\"flex flex-col justify-start items-start gap-6\">\n                {titles.map((title, index) => (\n                    <WavyBlockItem key={title} index={index}>\n                        <h2 className=\" text-[7.3vw] font-bold leading-none tracking-tighter uppercase whitespace-nowrap\">\n                            {title}\n                        </h2>\n                    </WavyBlockItem>\n                ))}\n            </WavyBlock>\n        </div>\n    );\n}",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/wavyblockusage.tsx",
      "content": "import { WavyBlock, WavyBlockItem } from \"@/registry/open-source/wavy-block\";\n\nexport default function Usage() {\n\n    const titles = [\n        'Flexible',\n        'Animated',\n        'Customizable',\n        'Optimized',\n        'Lightweight',\n        'Responsive',\n        'UI Blocks',\n    ];\n    return (\n        <div className=\"relative w-full flex items-center justify-center\">\n            <WavyBlock className=\"flex flex-col justify-start items-start gap-6\">\n                {titles.map((title, index) => (\n                    <WavyBlockItem key={title} index={index}>\n                        <h2 className=\" text-[7.3vw] font-bold leading-none tracking-tighter uppercase whitespace-nowrap\">\n                            {title}\n                        </h2>\n                    </WavyBlockItem>\n                ))}\n            </WavyBlock>\n        </div>\n    );\n}",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/wavy-block.tsx",
      "content": "'use client';\n\nimport {\n    HTMLMotionProps,\n    motion,\n    MotionValue,\n    SpringOptions,\n    useMotionValue,\n    useReducedMotion,\n    useScroll,\n    UseScrollOptions,\n    useSpring,\n    useTransform,\n} from 'motion/react';\nimport React from 'react';\n\n// Credit:\n// https://systaliko-ui.vercel.app/docs/scroll-animations/wavy-block\n\ninterface WavyTextsConfig {\n    baseOffsetFactor: number;\n    baseExtra: number;\n    baseAmplitude: number;\n    lengthEffect: number;\n    frequency: number;\n    progressScale: number;\n    phaseShiftDeg: number;\n    spring: SpringOptions;\n}\ninterface WavyBlockItemProps extends HTMLMotionProps<'div'> {\n    index: number;\n    config?: Partial<WavyTextsConfig>;\n    axis?: 'x' | 'y';\n}\ninterface WavyBlockContextValue {\n    scrollYProgress: MotionValue<number>;\n    maxLen: number;\n}\n\nconst WavyBlockContext = React.createContext<WavyBlockContextValue | undefined>(\n    undefined,\n);\n\nfunction useWavyBlockContext() {\n    const context = React.useContext(WavyBlockContext);\n    if (context === undefined) {\n        throw new Error('useWavyBlockContext must be used within a WavyBlock');\n    }\n    return context;\n}\nconst toRadian = (deg: number) => (deg * Math.PI) / 180;\nconst TAU = Math.PI * 2;\nconst DEFAULT_X_CONFIG: WavyTextsConfig = {\n    baseOffsetFactor: 0.1,\n    baseExtra: 0,\n    baseAmplitude: 160,\n    lengthEffect: 0.6,\n    frequency: 35,\n    progressScale: 1,\n    phaseShiftDeg: -180,\n    spring: { damping: 22, stiffness: 300 },\n};\nconst DEFAULT_Y_CONFIG: WavyTextsConfig = {\n    ...DEFAULT_X_CONFIG,\n    baseOffsetFactor: 0,\n    baseAmplitude: 120,\n    progressScale: 2,\n    phaseShiftDeg: 0,\n};\n\nexport function WavyBlockItem({\n    index,\n    axis = 'x',\n    config,\n    style,\n    ...props\n}: WavyBlockItemProps) {\n    const { scrollYProgress, maxLen } = useWavyBlockContext();\n    const reducedMotion = useReducedMotion();\n    const lengthFactor = Math.min(1, Math.max(0, maxLen / (maxLen || 1)));\n    const resolvedConfig = React.useMemo(() => {\n        const defaults = axis === 'y' ? DEFAULT_Y_CONFIG : DEFAULT_X_CONFIG;\n        return {\n            ...defaults,\n            ...config,\n            spring: {\n                ...defaults.spring,\n                ...config?.spring,\n            },\n        };\n    }, [axis, config]);\n\n    const [isMounted, setIsMounted] = React.useState<boolean>(false);\n\n    const calculateAxisOffset = React.useCallback(\n        (p: number, viewportSize?: number) => {\n            const phase = resolvedConfig.progressScale * p * TAU;\n\n            const size =\n                viewportSize ??\n                (typeof window !== 'undefined'\n                    ? axis === 'y'\n                        ? window.innerHeight\n                        : window.innerWidth\n                    : axis === 'y'\n                        ? 900\n                        : 1200);\n            const hasManualBaseOffset =\n                config?.baseOffsetFactor !== undefined ||\n                config?.baseExtra !== undefined;\n            const baseOffset =\n                axis === 'y' && !hasManualBaseOffset\n                    ? 0\n                    : resolvedConfig.baseOffsetFactor * size + resolvedConfig.baseExtra;\n\n            const amplitudeScale = 1 - resolvedConfig.lengthEffect * lengthFactor;\n            const amplitude = resolvedConfig.baseAmplitude * amplitudeScale;\n\n            const waveOffset =\n                axis === 'y'\n                    ? amplitude *\n                    Math.sin(\n                        phase +\n                        toRadian(resolvedConfig.frequency * index) +\n                        toRadian(resolvedConfig.phaseShiftDeg),\n                    )\n                    : amplitude *\n                    Math.cos(\n                        phase +\n                        toRadian(resolvedConfig.frequency * index) +\n                        toRadian(resolvedConfig.phaseShiftDeg),\n                    );\n\n            return baseOffset + waveOffset;\n        },\n        [\n            resolvedConfig,\n            lengthFactor,\n            index,\n            axis,\n            config?.baseOffsetFactor,\n            config?.baseExtra,\n        ],\n    );\n\n    const initialOffset = calculateAxisOffset(0);\n    const rawOffset = useMotionValue(initialOffset);\n    const springOffset = useSpring(rawOffset, resolvedConfig.spring);\n    const offset = reducedMotion ? rawOffset : springOffset;\n\n    React.useLayoutEffect(() => {\n        setIsMounted(true);\n    }, []);\n\n    React.useEffect(() => {\n        if (!scrollYProgress || !isMounted) return;\n\n        const unsub = scrollYProgress.onChange((p) => {\n            const viewportSize =\n                typeof window !== 'undefined'\n                    ? axis === 'y'\n                        ? window.innerHeight\n                        : window.innerWidth\n                    : axis === 'y'\n                        ? 900\n                        : 1200;\n            const newOffset = calculateAxisOffset(p, viewportSize);\n            rawOffset.set(newOffset);\n        });\n\n        return () => {\n            if (unsub) unsub();\n        };\n    }, [scrollYProgress, rawOffset, calculateAxisOffset, isMounted, axis]);\n\n    const motionAxisStyle = axis === 'y' ? { y: offset } : { x: offset };\n\n    return (\n        <motion.div\n            style={{ ...motionAxisStyle, ...style }}\n            suppressHydrationWarning\n            {...props}\n        />\n    );\n}\n\nexport function WavyBlock({\n    offset = ['start end', 'end start'],\n    ...props\n}: React.ComponentPropsWithRef<'div'> & {\n    offset?: UseScrollOptions['offset'];\n}) {\n    const containerRef = React.useRef<HTMLDivElement>(null);\n    const { current } = containerRef;\n\n    const maxLen = React.useMemo(() => {\n        if (!current?.children || current.children.length === 0) return 1;\n        const childrenArray = Array.from(current.children);\n        return Math.max(\n            ...childrenArray.map((child) => (child ? String(child).length : 0)),\n        );\n    }, [current?.children]);\n\n    const { scrollYProgress } = useScroll({\n        target: containerRef,\n        offset: offset,\n    });\n    return (\n        <WavyBlockContext.Provider value={{ scrollYProgress, maxLen }}>\n            <div ref={containerRef} {...props} />\n        </WavyBlockContext.Provider>\n    );\n}\n\nexport function WavyBlockSticky({\n    yRange = [0, 300],\n    yInput = [0, 1],\n    style,\n    ...props\n}: HTMLMotionProps<'div'> & {\n    yRange?: [number, number];\n    yInput?: [number, number];\n}) {\n    const { scrollYProgress } = useWavyBlockContext();\n\n    const y = useTransform(scrollYProgress, yInput, yRange);\n    return <motion.div style={{ y, ...style }} {...props} />;\n}",
      "type": "registry:ui"
    }
  ]
}