{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "reveal-text",
  "title": "Reveal Text",
  "description": "Reveals text with a directional slide-and-fade when it enters the viewport.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/reveal-text/reveal-text.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/** Slide-in origin for the reveal. */\nexport type RevealDirection = \"down\" | \"left\" | \"right\" | \"up\";\n\n/** Props for {@link RevealText}. */\nexport type RevealTextProps = React.ComponentPropsWithoutRef<\"div\"> & {\n  /** Content slid into view behind a clipping mask. */\n  children: React.ReactNode;\n  /** Milliseconds before the reveal starts. Defaults to `0`. */\n  delay?: number;\n  /** Origin the content slides from. Defaults to `\"up\"`. */\n  direction?: RevealDirection;\n};\n\nfunction usePrefersReducedMotion(): boolean {\n  const [reduced, setReduced] = React.useState(false);\n\n  React.useEffect(() => {\n    if (\n      typeof window === \"undefined\" ||\n      typeof window.matchMedia !== \"function\"\n    ) {\n      return;\n    }\n\n    const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    const onChange = (): void => {\n      setReduced(query.matches);\n    };\n\n    onChange();\n    query.addEventListener(\"change\", onChange);\n\n    return () => {\n      query.removeEventListener(\"change\", onChange);\n    };\n  }, []);\n\n  return reduced;\n}\n\nfunction useInView(nodeRef: React.RefObject<HTMLDivElement | null>): boolean {\n  const [inView, setInView] = React.useState(\n    () => typeof IntersectionObserver !== \"function\",\n  );\n\n  React.useEffect(() => {\n    const node = nodeRef.current;\n    if (!node || typeof IntersectionObserver !== \"function\") {\n      return;\n    }\n    const observer = new IntersectionObserver((entries) => {\n      if (entries.some((entry) => entry.isIntersecting)) {\n        setInView(true);\n        observer.disconnect();\n      }\n    });\n    observer.observe(node);\n    return () => {\n      observer.disconnect();\n    };\n  }, [nodeRef]);\n\n  return inView;\n}\n\nconst hiddenTransforms: Record<RevealDirection, string> = {\n  down: \"-translate-y-full\",\n  left: \"translate-x-full\",\n  right: \"-translate-x-full\",\n  up: \"translate-y-full\",\n};\n\n/**\n * Slides content into view from one edge behind a clipping mask, once.\n *\n * Respects `prefers-reduced-motion`: the content shows in place.\n *\n * @example\n * ```tsx\n * <RevealText direction=\"up\">Headline</RevealText>\n * ```\n */\nexport const RevealText = ({\n  children,\n  className,\n  delay = 0,\n  direction = \"up\",\n  ref,\n  ...props\n}: RevealTextProps & { ref?: React.Ref<HTMLDivElement> }) => {\n  const reduced = usePrefersReducedMotion();\n  const nodeRef = React.useRef<HTMLDivElement>(null);\n  const inView = useInView(nodeRef);\n  const visible = reduced || inView;\n\n  return (\n    <div\n      className={cn(\"overflow-hidden\", className)}\n      ref={(node) => {\n        nodeRef.current = node;\n        if (typeof ref === \"function\") {\n          ref(node);\n        } else if (ref) {\n          ref.current = node;\n        }\n      }}\n      {...props}\n    >\n      <div\n        className={cn(\n          \"transition-[transform,opacity] duration-700 ease-out motion-reduce:transition-none\",\n          visible\n            ? \"translate-x-0 translate-y-0 opacity-100\"\n            : cn(\"opacity-0\", hiddenTransforms[direction]),\n        )}\n        style={{ transitionDelay: `${delay}ms` }}\n      >\n        {children}\n      </div>\n    </div>\n  );\n};\nRevealText.displayName = \"RevealText\";\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.2.1",
  "stability": "stable"
}
