Skip to content

Commit

Permalink
DropdownMenuV2: update animation (#64868)
Browse files Browse the repository at this point in the history
* DropdownMenuV2: update animation specs

* Swap inner and outer wrappers so that role="menu" is direct parent of menuitems

* CHANGELOG

* Using currentPlacement instead of placement

* Move z-index styles to inner wrapper

* Consolidate animation styles

* Tweak outer wrapper scale factor

* Remove will-change as it seems to cause glitchiness

* Add exit animation

- Refactor from CSS animation to transition
- Use `[data-enter]` and `[data-leave]` ariakit attributes
- Move CSS comments inline

* Remove unuesed import

* Update packages/components/src/dropdown-menu-v2/styles.ts

Co-authored-by: Marin Atanasov <[email protected]>

* Change out animation duration to 200ms

---------

Unlinked contributors: nick-a8c.

Co-authored-by: ciampo <[email protected]>
Co-authored-by: tyxla <[email protected]>
Co-authored-by: jameskoster <[email protected]>
Co-authored-by: mtias <[email protected]>
  • Loading branch information
5 people authored Sep 2, 2024
1 parent 52832f5 commit c0c870c
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 69 deletions.
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
- `DropdownMenu` v2: fix flashing menu item styles when using keyboard ([#64873](https://github.com/WordPress/gutenberg/pull/64873), [#64942](https://github.com/WordPress/gutenberg/pull/64942)).
- `DropdownMenu` v2: refactor to overloaded naming convention ([#64654](https://github.com/WordPress/gutenberg/pull/64654)).
- `DropdownMenu` v2: add `GroupLabel` subcomponent ([#64854](https://github.com/WordPress/gutenberg/pull/64854)).
- `DropdownMenuV2`: update animation ([#64868](https://github.com/WordPress/gutenberg/pull/64868)).
- `Composite` V2: fix Storybook docgen ([#64682](https://github.com/WordPress/gutenberg/pull/64682)).
- `Composite` V2: accept store props on top-level component ([#64832](https://github.com/WordPress/gutenberg/pull/64832)).

Expand Down
18 changes: 14 additions & 4 deletions packages/components/src/dropdown-menu-v2/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,11 @@ const UnconnectedDropdownMenu = (
);

// Extract the side from the applied placement — useful for animations.
// Using `currentPlacement` instead of `placement` to make sure that we
// use the final computed placement (including "flips" etc).
const appliedPlacementSide = useStoreState(
dropdownMenuStore,
'placement'
'currentPlacement'
).split( '-' )[ 0 ];

if (
Expand Down Expand Up @@ -173,7 +175,7 @@ const UnconnectedDropdownMenu = (
/>

{ /* Menu popover */ }
<Styled.DropdownMenu
<Ariakit.Menu
{ ...otherProps }
modal={ modal }
store={ dropdownMenuStore }
Expand All @@ -185,15 +187,23 @@ const UnconnectedDropdownMenu = (
shift={ shift ?? ( dropdownMenuStore.parent ? -4 : 0 ) }
hideOnHoverOutside={ false }
data-side={ appliedPlacementSide }
variant={ variant }
wrapperProps={ wrapperProps }
hideOnEscape={ hideOnEscape }
unmountOnHide
render={ ( renderProps ) => (
// Two wrappers are needed for the entry animation, where the menu
// container scales with a different factor than its contents.
// The {...renderProps} are passed to the inner wrapper, so that the
// menu element is the direct parent of the menu item elements.
<Styled.MenuPopoverOuterWrapper variant={ variant }>
<Styled.MenuPopoverInnerWrapper { ...renderProps } />
</Styled.MenuPopoverOuterWrapper>
) }
>
<DropdownMenuContext.Provider value={ contextValue }>
{ children }
</DropdownMenuContext.Provider>
</Styled.DropdownMenu>
</Ariakit.Menu>
</>
);
};
Expand Down
157 changes: 92 additions & 65 deletions packages/components/src/dropdown-menu-v2/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import * as Ariakit from '@ariakit/react';
import { css, keyframes } from '@emotion/react';
import { css } from '@emotion/react';
import styled from '@emotion/styled';

/**
Expand All @@ -15,9 +15,13 @@ import { Truncate } from '../truncate';
import type { DropdownMenuContext } from './types';

const ANIMATION_PARAMS = {
SLIDE_AMOUNT: '2px',
DURATION: '400ms',
EASING: 'cubic-bezier( 0.16, 1, 0.3, 1 )',
SCALE_AMOUNT_OUTER: 0.82,
SCALE_AMOUNT_CONTENT: 0.9,
DURATION: {
IN: '400ms',
OUT: '200ms',
},
EASING: 'cubic-bezier(0.33, 0, 0, 1)',
};

const CONTENT_WRAPPER_PADDING = space( 1 );
Expand All @@ -38,41 +42,60 @@ const TOOLBAR_VARIANT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ TOOLBAR_VAR

const GRID_TEMPLATE_COLS = 'minmax( 0, max-content ) 1fr';

const slideUpAndFade = keyframes( {
'0%': {
opacity: 0,
transform: `translateY(${ ANIMATION_PARAMS.SLIDE_AMOUNT })`,
},
'100%': { opacity: 1, transform: 'translateY(0)' },
} );
export const MenuPopoverOuterWrapper = styled.div<
Pick< DropdownMenuContext, 'variant' >
>`
position: relative;
const slideRightAndFade = keyframes( {
'0%': {
opacity: 0,
transform: `translateX(-${ ANIMATION_PARAMS.SLIDE_AMOUNT })`,
},
'100%': { opacity: 1, transform: 'translateX(0)' },
} );
background-color: ${ COLORS.ui.background };
border-radius: ${ CONFIG.radiusMedium };
${ ( props ) => css`
box-shadow: ${ props.variant === 'toolbar'
? TOOLBAR_VARIANT_BOX_SHADOW
: DEFAULT_BOX_SHADOW };
` }
const slideDownAndFade = keyframes( {
'0%': {
opacity: 0,
transform: `translateY(-${ ANIMATION_PARAMS.SLIDE_AMOUNT })`,
},
'100%': { opacity: 1, transform: 'translateY(0)' },
} );
overflow: hidden;
const slideLeftAndFade = keyframes( {
'0%': {
opacity: 0,
transform: `translateX(${ ANIMATION_PARAMS.SLIDE_AMOUNT })`,
},
'100%': { opacity: 1, transform: 'translateX(0)' },
} );
/* Open/close animation (outer wrapper) */
@media not ( prefers-reduced-motion ) {
transition-property: transform, opacity;
transition-timing-function: ${ ANIMATION_PARAMS.EASING };
transition-duration: ${ ANIMATION_PARAMS.DURATION.IN };
will-change: transform, opacity;
export const DropdownMenu = styled( Ariakit.Menu )<
Pick< DropdownMenuContext, 'variant' >
>`
/* Regardless of the side, fade in and out. */
opacity: 0;
&:has( [data-enter] ) {
opacity: 1;
}
&:has( [data-leave] ) {
transition-duration: ${ ANIMATION_PARAMS.DURATION.OUT };
}
/* For menus opening on top and bottom side, animate the scale Y too. */
&:has( [data-side='bottom'] ),
&:has( [data-side='top'] ) {
transform: scaleY( ${ ANIMATION_PARAMS.SCALE_AMOUNT_OUTER } );
}
&:has( [data-side='bottom'] ) {
transform-origin: top;
}
&:has( [data-side='top'] ) {
transform-origin: bottom;
}
&:has( [data-enter][data-side='bottom'] ),
&:has( [data-enter][data-side='top'] ),
/* Do not animate the scaleY when closing the menu */
&:has( [data-leave][data-side='bottom'] ),
&:has( [data-leave][data-side='top'] ) {
transform: scaleY( 1 );
}
}
`;

export const MenuPopoverInnerWrapper = styled.div`
position: relative;
/* Same as popover component */
/* TODO: is there a way to read the sass variable? */
Expand All @@ -86,39 +109,41 @@ export const DropdownMenu = styled( Ariakit.Menu )<
min-width: 160px;
max-width: 320px;
max-height: var( --popover-available-height );
padding: ${ CONTENT_WRAPPER_PADDING };
background-color: ${ COLORS.ui.background };
border-radius: ${ CONFIG.radiusMedium };
${ ( props ) => css`
box-shadow: ${ props.variant === 'toolbar'
? TOOLBAR_VARIANT_BOX_SHADOW
: DEFAULT_BOX_SHADOW };
` }
padding: ${ CONTENT_WRAPPER_PADDING };
overscroll-behavior: contain;
overflow: auto;
/* Only visible in Windows High Contrast mode */
outline: 2px solid transparent !important;
/* Animation */
&[data-open] {
@media not ( prefers-reduced-motion ) {
animation-duration: ${ ANIMATION_PARAMS.DURATION };
animation-timing-function: ${ ANIMATION_PARAMS.EASING };
will-change: transform, opacity;
/* Default animation.*/
animation-name: ${ slideDownAndFade };
&[data-side='left'] {
animation-name: ${ slideLeftAndFade };
}
&[data-side='up'] {
animation-name: ${ slideUpAndFade };
}
&[data-side='right'] {
animation-name: ${ slideRightAndFade };
}
/* Open/close animation (inner content wrapper) */
@media not ( prefers-reduced-motion ) {
transition: inherit;
transform-origin: inherit;
/*
* For menus opening on top and bottom side, animate the scale Y too.
* The content scales at a different rate than the outer container:
* - first, counter the outer scale factor by doing "1 / scaleAmountOuter"
* - then, apply the content scale factor.
*/
&[data-side='bottom'],
&[data-side='top'] {
transform: scaleY(
calc(
1 / ${ ANIMATION_PARAMS.SCALE_AMOUNT_OUTER } *
${ ANIMATION_PARAMS.SCALE_AMOUNT_CONTENT }
)
);
}
&[data-enter][data-side='bottom'],
&[data-enter][data-side='top'],
/* Do not animate the scaleY when closing the menu */
&[data-leave][data-side='bottom'],
&[data-leave][data-side='top'] {
transform: scaleY( 1 );
}
}
`;
Expand Down Expand Up @@ -194,7 +219,7 @@ const baseItem = css`
}
/* When the item is the trigger of an open submenu */
${ DropdownMenu }:not(:focus) &:not(:focus)[aria-expanded="true"] {
${ MenuPopoverInnerWrapper }:not(:focus) &:not(:focus)[aria-expanded="true"] {
background-color: ${ LIGHT_BACKGROUND_COLOR };
color: ${ COLORS.theme.foreground };
}
Expand Down Expand Up @@ -292,9 +317,9 @@ export const ItemSuffixWrapper = styled.span`
* When the parent menu item is active, except when it's a non-focused/hovered
* submenu trigger (in that case, color should not be inherited)
*/
[data-active-item]:not( [data-focus-visible] ) *:not(${ DropdownMenu }) &,
[data-active-item]:not( [data-focus-visible] ) *:not(${ MenuPopoverInnerWrapper }) &,
/* When the parent menu item is disabled */
[aria-disabled='true'] *:not(${ DropdownMenu }) & {
[aria-disabled='true'] *:not(${ MenuPopoverInnerWrapper }) & {
color: inherit;
}
`;
Expand Down Expand Up @@ -357,8 +382,10 @@ export const DropdownMenuItemHelpText = styled( Truncate )`
color: ${ LIGHTER_TEXT_COLOR };
word-break: break-all;
[data-active-item]:not( [data-focus-visible] ) *:not( ${ DropdownMenu } ) &,
[aria-disabled='true'] *:not( ${ DropdownMenu } ) & {
[data-active-item]:not( [data-focus-visible] )
*:not( ${ MenuPopoverInnerWrapper } )
&,
[aria-disabled='true'] *:not( ${ MenuPopoverInnerWrapper } ) & {
color: inherit;
}
`;

0 comments on commit c0c870c

Please sign in to comment.