Today’s post is actually somewhat of a fun one! No frustrating broken things, no shenanigans with a black box to worry about. Just some simple Typescript fun.

Mr. Rose is as happy as I am.

So let’s take this common hero component. It has some gorgeous animations courtesy of Framer Motion. Highly recommend this library for how simple it is to get going and the sheer flexibility of the animations. Take your div and turn it into a motion.div. Apply the right attributes. Done. I did mention it was easy, right? Anyway, check out this component:

import { JSX } from 'react';
import { motion } from 'framer-motion';
import styles from './PageHero.module.css';

import {
  ComponentParams,
  ImageField,
  Text,
  TextField,
  withDatasourceCheck,
} from '@sitecore-jss/sitecore-jss-nextjs';

interface HeroProps {
  rendering: {
    componentName: string;
    dataSource: string;
  };
  params: ComponentParams;
  fields: {
    PersonaImage: ImageField;
    Title: TextField;
    Description: TextField;
  };
}

const PageHero = (props: HeroProps): JSX.Element => {
  

  return (
    <div>
      <div className={styles['page-hero']}>
        <motion.div
          className={styles['persona-image-container']}
          initial={{ opacity: 0, x: -250 }}
          whileInView={{ opacity: 1, x: 0 }}
          viewport={{ once: true }}
          transition={{ duration: 0.7, ease: 'easeInOut' }}
        ></motion.div>
        <div className={styles['content-container']}>
          <motion.h1
            initial={{ opacity: 0, y: 50 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true }}
            transition={{ duration: 0.7, ease: 'easeInOut', delay: 0.7 }}
          >
            {props?.fields?.Title?.value && (
              <Text field={props?.fields?.Title} />
            )}
          </motion.h1>
          <motion.p
            initial={{ opacity: 0, y: 50 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true }}
            transition={{ duration: 0.7, ease: 'easeInOut', delay: 1.4 }}
          >
            {props?.fields?.Description?.value && (
              <Text field={props?.fields?.Description} />
            )}
          </motion.p>
        </div>
      </div>
    </div>
  );
};

export default withDatasourceCheck()<HeroProps>(PageHero);

Essentially there’s three motions here. One for the background (first div), one for the h1, one for the paragraph tag with the description in it. The delay attribute gives it a bit of a pizazz. But content authors aren’t really interested in pizazz. Plus, we don’t want any random animations to break the hydration in Experience Editor. Now, I could do a whole “If in editing, show this markup (div), else show the animation markup (motion.div). This is going to almost double the size of our files though. So what do we do? We’ll use something called the spread syntax.

Spread Syntax was my nickname in college, y’all…

But for serious, what does the Spread Syntax do? It takes an iterable (not to be confused with an edible) and expands it into elements. How does THIS help us? Well, let’s take a look at our first motion.div:

<motion.div
  className={styles['persona-image-container']}
  initial={{ opacity: 0, x: -250 }}
  whileInView={{ opacity: 1, x: 0 }}
  viewport={{ once: true }}
  transition={{ duration: 0.7, ease: 'easeInOut' }}
></motion.div>

I can actually extract all those animation attributes out into an object:

 const bgAnimationAttributes = {
    initial: { opacity: 0, x: -250 },
    whileInView: { opacity: 1, x: 0 },
    viewport: { once: true },
    transition: { duration: 0.7, ease: 'easeInOut' },
  };

And then I’d need to update my div to the spread syntax:

<motion.div
  className={styles['persona-image-container']}
  {...bgAnimationAttributes}
></motion.div>

When this executes, those attributes will get expanded into the motion.div. But, we’re not done yet. We’re still need to disable this in Experience Editor. We’ll be using our trusty useSitecoreContext (to check what mode we’re in) coupled with a ternary operator.

 const bgAnimationAttributes = !useSitecoreContext().sitecoreContext.pageEditing
    ? {
        initial: { opacity: 0, x: -250 },
        whileInView: { opacity: 1, x: 0 },
        viewport: { once: true },
        transition: { duration: 0.7, ease: 'easeInOut' },
      }
    : {};

That basically says “If I’m in not Page Editing mode, use these attributes, else no attributes” which would effectively disable our animation in EE. Here’s the final file:

import { JSX } from 'react';
import { motion } from 'framer-motion';
import styles from './PageHero.module.css';

import {
  ComponentParams,
  ImageField,
  Text,
  TextField,
  useSitecoreContext,
  withDatasourceCheck,
} from '@sitecore-jss/sitecore-jss-nextjs';

interface HeroProps {
  rendering: {
    componentName: string;
    dataSource: string;
  };
  params: ComponentParams;
  fields: {
    PersonaImage: ImageField;
    Title: TextField;
    Description: TextField;
  };
}

const PageHero = (props: HeroProps): JSX.Element => {
  const isEditing = !useSitecoreContext().sitecoreContext.pageEditing;

  const bgAnimationAttributes = !isEditing
    ? {
        initial: { opacity: 0, x: -250 },
        whileInView: { opacity: 1, x: 0 },
        viewport: { once: true },
        transition: { duration: 0.7, ease: 'easeInOut' },
      }
    : {};

  const h1AnimationAttributes = !isEditing
    ? {
        initial: { opacity: 0, y: 50 },
        whileInView: { opacity: 1, y: 0 },
        viewport: { once: true },
        transition: { duration: 0.7, ease: 'easeInOut', delay: 0.7 },
      }
    : {};

  const descriptionAnimationAttributes = !isEditing
    ? {
        initial: { opacity: 0, y: 50 },
        whileInView: { opacity: 1, y: 0 },
        viewport: { once: true },
        transition: { duration: 0.7, ease: 'easeInOut', delay: 1.4 },
      }
    : {};

  return (
    <div>
      <div className={styles['page-hero']}>
        <motion.div
          className={styles['persona-image-container']}
          {...bgAnimationAttributes}
        ></motion.div>
        <div className={styles['content-container']}>
          <motion.h1 {...h1AnimationAttributes}>
            {props?.fields?.Title?.value && (
              <Text field={props?.fields?.Title} />
            )}
          </motion.h1>
          <motion.p {...descriptionAnimationAttributes}>
            {props?.fields?.Description?.value && (
              <Text field={props?.fields?.Description} />
            )}
          </motion.p>
        </div>
      </div>
    </div>
  );
};

export default withDatasourceCheck()<HeroProps>(PageHero);

I love the simplicity of this. You’re not messing with the actual markup, nor introducing any duplication.

I’m due a Snoop meme, I know.