Blueprint

Motion

Motion presets are available for both CSS transitions and animations and JavaScript-driven animations in the form of Framer Motion spring presets. Motion can be used simply for delight or to add semantic value to an interface, though ideally, it adds a bit of both. Substantial motion should always respect useIsMotionReduced.

Framer

Framer Motion is a JavaScript animation library. It's ideal for more substantial interface motion as its physics-based movement creates much more realistic, fluid effects. It can also be applied in cases that are difficult or impossible to reproduce effectively with CSS (i.e: when a component is unmounted).

When should I use CSS instead?

Because Framer Motion animates using JavaScript it can be more performance intensive than CSS animations and transitions. When possible, always use transform instead of properties that update element dimensions and can cause a DOM reflow. If you're animating many elements or are running into performance issues, you may want to try using CSS instead.

Usage

A modified version of Framer's motion() export is available from @hover/blueprint that integrates motion and system functionality. See below and the Framer Motion documentation for more details.


import { motion, Center, Loader, Motion } from '@hover/blueprint';
import { AnimatePresence } from 'framer-motion';
// The system `motion()` provides the correct props when wrapping system
// components and custom components that compose system components
const MotionLoader = motion(Loader);
export const MyComponent = ({ isVisible }) => (
// Elements created with `motion.[element]` support system
// style props in addition to the Framer Motion APIs
<motion.div
animate={{ x: isVisible ? '0%' : '100%' }}
background="neutral.100"
borderRadius="400"
>
{/* System motion presets are available on the `Motion` component */}
<Motion.Firm>
<AnimatePresence>
{isVisible && (
<MotionLoader
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
</Motion.Firm>
</motion.div>
);

Motion Factory

Using the modified motion() factory from @hover/blueprint instead of that from framer-motion ensures that system components wrapped with motion() have the correct prop typings for both style props and Framer's animation props.

It also automatically augments JSX elements created using the <motion.div /> shorthand with system style props. See the system factory page for more information.


import { motion as framerMotion } from 'framer-motion';
import { motion as systemMotion, system } from '@hover/blueprint';
// This
const MotionDiv = framerMotion(system.div);
<MotionDiv animate={{ x: 100 }} bg="primary.100" />
// Is equivalent to this
<systemMotion.div animate={{ x: 100 }} bg="primary.100" />

Presets

Framer Motion presets are applied via React context with the Motion helper. Simply wrap any any Framer-driven animations with a Motion preset.

<Motion.Snappy>
</Motion.Snappy>
<Motion.Tight>
🏐
</Motion.Tight>
<Motion.Firm>
🏀
</Motion.Firm>
<Motion.Soft>
</Motion.Soft>

Gestures

Swipe

Use the onSwipe helper handler for invoking actions in response to swipe gestures.

Given a swipe axis and actions to invoke when swiping towards the start or end of said axis, it determines whether to invoke one of the provided actions depending on whether the swipe in the respective direction passes the provided threshold.

PropertyTypeDescription
axis'x' | 'y'Direction of swipe
start?SwipeActionAction to invoke when swiping towards start of axis
end?SwipeActionAction to invoke when swiping towards end of axis
none?SwipeActionAction to invoke when no swipe threshold is reached
end?SwipeActionAction to invoke when swiping towards end of axis
none?SwipeActionAction to invoke when no swipe threshold is reached (defaults to 10000)
distance?numberOptionally, a distance threshold that will trigger a swipe. If defined and distance is reached, it will trigger a swipe regardless of the provided force threshold.

import { motion, Image, onSwipe } from '@hover/blueprint';
const MotionImage = motion(Image);
<MotionImage
drag="x"
onDragEnd={onSwipe({
axis: 'x',
start: () => console.log('swiped left'),
end: () => console.log('swiped right'),
})}
/>;

CSS

CSS transitions are ideal for short state changes, such as the :hover state of a button and the :focus state of an input. For more substantial movement, you might consider Framer Motion for a more realistic effect.

Usage

The easiest way to apply CSS presets is via the transition helper. Simply provide the property or properties and spread the return value into a component's props or a style object.

Props
Style Object

import { transition, Center, Loader } from '@hover/blueprint';
export const MyComponent = ({ isVisible }) => (
<Center>
<Loader {...transition.easing250('opacity')} opacity={isVisible ? 1 : 0} />
</Center>
);

They are also available as strings from the motion tokens export for interpolating into more complex transitions.


import { Button } from '@hover/blueprint';
import { motion } from '@hover/blueprint/foundation';
export const PartyButton = () => (
<Button
_hover={{ opacity: 1, filter: 'grayscale(0%)' }}
filter="grayscale(100%)"
label="party"
opacity="0.5"
shape="circle"
transition={`opacity ${motion.easings[150]}, filter ${motion.easings[500]}`}
>
🥳
</Button>
);

Presets

CSS transition presets are available on the transition helper as functions (e.g: transition.easing150(...properties)) and from the motion.easings tokens as strings including the duration and easing function (e.g: motion.easings[150]).

transition.easing150(...)
transition.easing250(...)
🏐
transition.easing500(...)
🏀

Copyright © 2024 Hover Inc. All Rights Reserved.