Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Popover: fix and improve opening animation, use framer motion #43186

Merged
merged 14 commits into from
Aug 17, 2022
Merged
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
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