Skip to content

Commit

Permalink
Popover: fix and improve opening animation, use framer motion (#43186)
Browse files Browse the repository at this point in the history
* Popover: use motion for animation, adjust animation origin

* Add more text to popover in storybook examples

* Move positionToPlacement to utils file

* Do not animate when expanded

* Add translation and opacity to the animation

* Memoize the computation of motion animation props

* Add support for reduced motion

* Force popover position in "AllPlacements" storybook example for more clarity around poopover positioning

* Wrap component unit tests inside a "Component" describe call

* Update snapshot

* Add positionToPlacement and placementToMotionAnimationProps unit tests

* CHANGELOG

* More accessible comment

* Cleanup comment
  • Loading branch information
ciampo authored Aug 17, 2022
1 parent c621055 commit 0c1705e
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 102 deletions.
4 changes: 4 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- `CustomSelectControl`: Deprecate constrained width style. Add a `__nextUnconstrainedWidth` prop to start opting into the unconstrained width that will become the default in a future version, currently scheduled to be WordPress 6.4 ([#43230](https://github.com/WordPress/gutenberg/pull/43230)).

### Bug Fix

- `Popover`: fix and improve opening animation ([#43186](https://github.com/WordPress/gutenberg/pull/43186)).

### Enhancements

- `ToggleGroupControl`: Improve TypeScript documentation ([#43265](https://github.com/WordPress/gutenberg/pull/43265)).
Expand Down
107 changes: 48 additions & 59 deletions packages/components/src/popover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
limitShift,
size,
} from '@floating-ui/react-dom';
// eslint-disable-next-line no-restricted-imports
import { motion, useReducedMotion } from 'framer-motion';

/**
* WordPress dependencies
Expand Down Expand Up @@ -40,7 +42,7 @@ import { Path, SVG } from '@wordpress/primitives';
import Button from '../button';
import ScrollLock from '../scroll-lock';
import { Slot, Fill, useSlot } from '../slot-fill';
import { getAnimateClassName } from '../animate';
import { positionToPlacement, placementToMotionAnimationProps } from './utils';

/**
* Name of slot in which popover should fill.
Expand Down Expand Up @@ -73,50 +75,48 @@ const ArrowTriangle = ( props ) => (
</SVG>
);

const slotNameContext = createContext();

const positionToPlacement = ( position ) => {
const [ x, y, z ] = position.split( ' ' );
const MaybeAnimatedWrapper = forwardRef(
(
{
style: receivedInlineStyles,
placement,
shouldAnimate = false,
...props
},
forwardedRef
) => {
const shouldReduceMotion = useReducedMotion();

const { style: motionInlineStyles, ...otherMotionProps } = useMemo(
() => placementToMotionAnimationProps( placement ),
[ placement ]
);

if ( [ 'top', 'bottom' ].includes( x ) ) {
let suffix = '';
if ( ( !! z && z === 'left' ) || y === 'right' ) {
suffix = '-start';
} else if ( ( !! z && z === 'right' ) || y === 'left' ) {
suffix = '-end';
if ( shouldAnimate && ! shouldReduceMotion ) {
return (
<motion.div
style={ {
...motionInlineStyles,
...receivedInlineStyles,
} }
{ ...otherMotionProps }
{ ...props }
ref={ forwardedRef }
/>
);
}
return x + suffix;
}

return y;
};

const placementToAnimationOrigin = ( placement ) => {
const [ a, b ] = placement.split( '-' );

let x, y;
if ( a === 'top' || a === 'bottom' ) {
x = a === 'top' ? 'bottom' : 'top';
y = 'middle';
if ( b === 'start' ) {
y = 'left';
} else if ( b === 'end' ) {
y = 'right';
}
}

if ( a === 'left' || a === 'right' ) {
x = 'center';
y = a === 'left' ? 'right' : 'left';
if ( b === 'start' ) {
x = 'top';
} else if ( b === 'end' ) {
x = 'bottom';
}
return (
<div
style={ receivedInlineStyles }
{ ...props }
ref={ forwardedRef }
/>
);
}
);

return x + ' ' + y;
};
const slotNameContext = createContext();

const Popover = (
{
Expand Down Expand Up @@ -411,14 +411,6 @@ const Popover = (
};
}, [ ownerDocument ] );

/** @type {false | string} */
const animateClassName =
!! animate &&
getAnimateClassName( {
type: 'appear',
origin: placementToAnimationOrigin( computedPlacement ),
} );

const mergedFloatingRef = useMergeRefs( [
floating,
dialogRef,
Expand All @@ -431,16 +423,13 @@ const Popover = (
let content = (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
className={ classnames(
'components-popover',
className,
animateClassName,
{
'is-expanded': isExpanded,
'is-alternate': isAlternate,
}
) }
<MaybeAnimatedWrapper
shouldAnimate={ animate && ! isExpanded }
placement={ computedPlacement }
className={ classnames( 'components-popover', className, {
'is-expanded': isExpanded,
'is-alternate': isAlternate,
} ) }
{ ...contentProps }
ref={ mergedFloatingRef }
{ ...dialogProps }
Expand Down Expand Up @@ -489,7 +478,7 @@ const Popover = (
<ArrowTriangle />
</div>
) }
</div>
</MaybeAnimatedWrapper>
);

if ( slot.ref ) {
Expand Down
16 changes: 15 additions & 1 deletion packages/components/src/popover/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,14 @@ export const Default = ( args ) => {
);
};
Default.args = {
children: <>Popover&apos;s&nbsp;content</>,
children: (
<div style={ { width: '280px', whiteSpace: 'normal' } }>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat.
</div>
),
};

/**
Expand Down Expand Up @@ -166,8 +173,15 @@ AllPlacements.parameters = {
};
AllPlacements.args = {
...Default.args,
children: (
<div style={ { width: '280px', whiteSpace: 'normal' } }>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
</div>
),
noArrow: false,
offset: 10,
__unstableForcePosition: true,
};

export const DynamicHeight = ( { children, ...args } ) => {
Expand Down
12 changes: 6 additions & 6 deletions packages/components/src/popover/test/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Popover should pass additional props to portaled element 1`] = `
exports[`Popover Component should pass additional props to portaled element 1`] = `
<span>
<div
class="components-popover components-animate__appear is-from-left is-from-top"
class="components-popover"
role="tooltip"
style="position: absolute;"
style="position: absolute; opacity: 0; transform: translateY(-2em) scale(0) translateZ(0); transform-origin: 0% 0% 0;"
tabindex="-1"
>
<div
Expand All @@ -17,11 +17,11 @@ exports[`Popover should pass additional props to portaled element 1`] = `
</span>
`;

exports[`Popover should render content 1`] = `
exports[`Popover Component should render content 1`] = `
<span>
<div
class="components-popover components-animate__appear is-from-left is-from-top"
style="position: absolute;"
class="components-popover"
style="position: absolute; opacity: 0; transform: translateY(-2em) scale(0) translateZ(0); transform-origin: 0% 0% 0;"
tabindex="-1"
>
<div
Expand Down
Loading

0 comments on commit 0c1705e

Please sign in to comment.