Page Animations

SmartCommon uses Framer Motion to animate transitions between pages. Animations are configurable per route.

Basic Configuration

In the Provider configuration:

const config = {
  pages: {
    "*": "fade" // Default animation for all pages
  }
};

Available Animations

Animation Description Effect
fade Crossfade Opacity 0 -> 1
slideLeft Slide left Enters from the right
slideRight Slide right Enters from the left
zoom Zoom effect Scale 0.9 -> 1

Configuration by Route

You can define different animations based on the source and destination routes:

const config = {
  pages: {
    // From home page
    "/": {
      "/dashboard": "slideLeft",  // To dashboard: slides left
      "/settings": "slideLeft",   // To settings: slides left
      "*": "fade"                 // To others: fade
    },

    // From dashboard
    "/dashboard": {
      "/": "slideRight",          // Back to home: slides right
      "/details": "slideLeft",    // To details: slides left
      "*": "fade"
    },

    // From settings
    "/settings": {
      "/": "slideRight",
      "*": "fade"
    },

    // For all other pages
    "*": "fade"
  }
};

Animations are chosen according to this scheme:

  1. Look for a config for the current page
  2. If found, look for an animation for the previous page
  3. If not found, use * for the current page
  4. If no config for the current page, use global *

Example

Navigation from /dashboard to /:

pages: {
  "/": {
    "/dashboard": "slideRight", // <- This animation will be used
    "*": "fade"
  },
  "/dashboard": {
    "/": "slideRight",
    "*": "fade"
  }
}

The page / displays with slideRight because we came from /dashboard.

Disable Slide Animations on Desktop

By default, slide animations are replaced by fade on desktop for better UX:

// In the Page component (internal behavior)
if (device?.type === "desktop") {
  return "fade";
}

Using the Page Component

The Page component automatically manages animations:

import { Page, Block } from '@cap-rel/smartcommon';
import { useLocation } from 'react-router-dom';

const Dashboard = () => {
  const location = useLocation();

  return (
    <Page location={location}>
      <Block>
        Dashboard content
      </Block>
    </Page>
  );
};

Important

The location prop is required for animations to work correctly.

Customizing Animations

You can create custom animations by modifying Framer Motion variants:

// Default animations in SmartCommon
const animations = {
  slideRight: {
    initial: { x: "50%", opacity: 0 },
    animate: { x: 0, opacity: 1, transition: { duration: 0.15, ease: "easeInOut" } },
    exit: { x: "50%", opacity: 0, transition: { duration: 0.15, ease: "easeInOut" } }
  },
  slideLeft: {
    initial: { x: "-50%", opacity: 0 },
    animate: { x: 0, opacity: 1, transition: { duration: 0.15, ease: "easeInOut" } },
    exit: { x: "-50%", opacity: 0, transition: { duration: 0.15, ease: "easeInOut" } }
  },
  fade: {
    initial: { opacity: 0 },
    animate: { opacity: 1, transition: { duration: 0.15, ease: "easeOut" } },
    exit: { opacity: 0, transition: { duration: 0.15, ease: "easeOut" } }
  },
  zoom: {
    initial: { scale: 0.9, opacity: 0 },
    animate: { scale: 1, opacity: 1 },
    exit: { scale: 0.9, opacity: 0 },
    transition: { duration: 0.2, ease: "easeOut" }
  }
};

useAnimation Hook

For custom animations in your components:

import { useAnimation } from '@cap-rel/smartcommon';

const MyComponent = () => {
  const { start, animations, setAnimations } = useAnimation({
    fadeIn: { value: false, state: null },
    slideIn: { value: false, state: null }
  });

  useEffect(() => {
    if (start) {
      // Trigger animation after first render
      setAnimations(prev => ({
        ...prev,
        fadeIn: { ...prev.fadeIn, state: 'visible' }
      }));
    }
  }, [start]);

  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={animations.fadeIn.value ? { opacity: 1 } : {}}
    >
      Animated content
    </motion.div>
  );
};

Animations with Framer Motion

For more complex animations, use Framer Motion directly:

import { motion, AnimatePresence } from 'framer-motion';

const MyList = ({ items }) => (
  <AnimatePresence>
    {items.map(item => (
      <motion.div
        key={item.id}
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        exit={{ opacity: 0, y: -20 }}
        transition={{ duration: 0.2 }}
      >
        {item.name}
      </motion.div>
    ))}
  </AnimatePresence>
);

Configuration Examples

Classic Mobile Application

Hierarchical navigation (list -> detail -> sub-detail):

pages: {
  "/": {
    "*": "slideLeft"
  },
  "/items": {
    "/": "slideRight",
    "/items/*": "slideLeft",
    "*": "fade"
  },
  "/items/*": {
    "/items": "slideRight",
    "*": "fade"
  },
  "*": "fade"
}

Tab-based Application

Navigation between tabs without slide animations:

pages: {
  "/home": { "*": "fade" },
  "/search": { "*": "fade" },
  "/profile": { "*": "fade" },
  "/settings": {
    "*": "slideLeft" // Only settings has a different animation
  },
  "*": "fade"
}

Disable All Animations

pages: {
  "*": "fade" // Use only fade (most subtle)
}

// Or set duration to 0
// (requires modifying animations in code)

See Also