# Smooth Reveal

The SmoothReveal component creates a smooth reveal animation for its children when they enter the viewport.

- **Category:** Animation
- **Docs:** https://uibeats.com/docs/animation/smooth-reveal
- **Registry:** https://uibeats.com/r/smooth-reveal.json
- **License:** MIT
- **npm dependencies:** `motion`

## Installation

```bash
npx shadcn@latest add https://uibeats.com/r/smooth-reveal.json
```

Or install the dependencies and copy the source below:

```bash
npm install motion
```

## When to use

The workhorse for long scrolling pages. Match the direction to reading order: up for stacked sections, left or right for side-by-side columns. Keep distance under about 60px. Past that the movement reads as a jump instead of a reveal.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `React.ReactNode` | `-` | The content to be revealed (required) |
| `direction` | `'up' \| 'down' \| 'left' \| 'right'` | `'up'` | The direction of the reveal animation |
| `delay` | `number` | `0` | Delay before the animation starts (in seconds) |
| `duration` | `number` | `0.6` | Duration of the animation (in seconds) |
| `distance` | `number` | `50` | Distance of the reveal animation (in pixels) |
| `className` | `string` | `''` | Additional CSS classes for styling |
| `once` | `boolean` | `true` | Whether to trigger the animation only once |

## Example usage

```tsx
import React from "react";
import SmoothReveal from "@/components/demo/animation/smooth-reveal";

const SmoothRevealUsage = () => {
  return (
    <SmoothReveal delay={1.2}>
      <p className="text-md mb-4 md:text-lg">
        This content will smoothly reveal on scroll
      </p>
    </SmoothReveal>
  );
};

export default SmoothRevealUsage;
```

## Source

Save as `components/ui/smooth-reveal.tsx`.

```tsx
"use client";
import React from "react";
import { cubicBezier, motion, useInView, useReducedMotion } from "motion/react";

type SmoothRevealProps = {
  children: React.ReactNode;
  direction?: "up" | "down" | "left" | "right";
  delay?: number;
  duration?: number;
  distance?: number;
  className?: string;
  once?: boolean;
};

const SmoothReveal: React.FC<SmoothRevealProps> = ({
  children,
  direction = "up",
  delay = 0,
  duration = 0.8,
  distance = 100,
  className = "",
  once = true,
}) => {
  const ref = React.useRef<HTMLDivElement>(null);
  const isInView = useInView(ref, { once, amount: 0.1 });
  const prefersReducedMotion = useReducedMotion();

  const getDirectionalProps = () => {
    switch (direction) {
      case "down":
        return { y: -distance };
      case "left":
        return { x: distance };
      case "right":
        return { x: -distance };
      case "up":
      default:
        return { y: distance };
    }
  };

  const customEasing = cubicBezier(0.2, 0.0, 0.0, 1.0);

  // 100px of travel by default: the largest displacement of any entrance
  // here, and the one most worth removing when asked to.
  const variants = prefersReducedMotion
    ? {
        hidden: { opacity: 1, x: 0, y: 0 },
        visible: { opacity: 1, x: 0, y: 0 },
      }
    : {
        hidden: {
          opacity: 0,
          ...getDirectionalProps(),
        },
        visible: {
          opacity: 1,
          y: 0,
          x: 0,
          transition: {
            duration: duration,
            ease: customEasing,
            delay: delay,
          },
        },
      };

  return (
    <motion.div
      ref={ref}
      initial="hidden"
      animate={isInView ? "visible" : "hidden"}
      variants={variants}
      className={className}
    >
      {children}
    </motion.div>
  );
};

export default SmoothReveal;
```
