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

Try menu appear animation & start work on handbook #13617

Merged
merged 26 commits into from
Feb 7, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dacfd32
Try menu appear animation & start work on handbook
Jan 21, 2019
1d1f164
Faster animation & commit docs.
Feb 1, 2019
5f17198
Try an animate component
youknowriad Feb 1, 2019
b028d51
Fix popover initial position
youknowriad Feb 1, 2019
bf10dff
Minor documentation spelling + grammar fixes.
kjellr Feb 1, 2019
ce7d7f1
Fix typo and clarify doc.
jasmussen Feb 4, 2019
a883d30
Fix failing unit tests by updating snapshots
gziolo Feb 4, 2019
df62716
Add template for the component README
gziolo Feb 4, 2019
6842659
Add template for the component README
gziolo Feb 4, 2019
e26b700
Add template for the component README
gziolo Feb 4, 2019
e62c739
update docs manifest
Feb 4, 2019
5415930
Fix block inserter and transformation e2e tests
youknowriad Feb 7, 2019
4ad8653
Fix tests relying on the more menu block
youknowriad Feb 7, 2019
491aab5
Fix block settings menu test and nux
youknowriad Feb 7, 2019
0e5038e
Fix editor modes and invalid block tests
youknowriad Feb 7, 2019
1e52573
Enhance the README of the animate component
youknowriad Feb 7, 2019
520503b
Fix opening the block settings menu in e2e tests
youknowriad Feb 7, 2019
88a57f9
Remove the animation doc
youknowriad Feb 7, 2019
a2de8fc
Bring back the principles section of the animation doc
youknowriad Feb 7, 2019
d9e3601
Fixed typo in link
gziolo Feb 7, 2019
1e9976a
Fixed origin option description
gziolo Feb 7, 2019
58470a3
Fix description of options once again
gziolo Feb 7, 2019
432c0e1
More test stabilization
youknowriad Feb 7, 2019
91b7144
Add the animation doc to the designer handbook
youknowriad Feb 7, 2019
8f9ef37
Update the components package changelog
youknowriad Feb 7, 2019
328b652
More e2e tests stability
youknowriad Feb 7, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions docs/designers-developers/designers/animation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Animation
mkaz marked this conversation as resolved.
Show resolved Hide resolved

Animation can help reinforce a sense of hierarchy and spatial orientation. This document goes into principles you should follow when you add animation.

## Principles

### Point of Origin

- Animation can help anchor an interface element. For example a menu can scale up from the button that opened it.
- Animation can help give a sense of place; for example a sidebar can animate in from the side, implying it was always hidden off-screen.
- Design your animations as if you're working with real-world materials. Imagine your user interface elements are made of real materials — when not on screen, where are they? Use animation to help express that.

### Speed

- Animations should never block a user interaction. They should be fast, almost always complete in less than 0.2 seconds.
- A user should not have to wait for an animation to finish before they can interact.
- Animations should be performant. Use `transform` CSS properties when you can, these render elements on the GPU, making them smooth.
- If an animation can't be made fast & performant, leave it out.

### Simple

- Don't bounce if the material isn't made of rubber.
- Don't rotate, fold, or animate on a curve. Keep it simple.
aduth marked this conversation as resolved.
Show resolved Hide resolved

### Consistency

In creating consistent animations, we have to establish physical rules for how elements behave when animated. When all animations follow these rules, they feel consistent, related, and predictable. An animation should match user expectations, if it doesn't, it's probably not the right animation for the job.

Reuse animations if one already exists for your task.

## Inventory of Reused Animations

The following is a running list of existing animations, and how they fit with the above principles.

### `edit-post__loading-fade-animation`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we never promote those class names but rather convert them to components and keep them as an implementation detail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly agree. In fact I feel that documenting these like this is the first step to retiring them in favor of something better/simpler.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a plan :)


A "pulsing fade" animation that is used when an element is _transient_. For example when an image is being uploaded. It is simple, and indicates background work.

### `edit-post__fade-in-animation`

The most basic fade-in animation. It simply fades from transparent to opaque.

### `components-button__busy-animation`

When a button is working, this animation adds a set of diagonal stripes that animate from left to right.

### `components-modal__appear-animation`

Modal windows are cards that live below the viewport. When invoked, they animate in front of the button and upwards. Because of their relative size, this is a very brief fade animation.

### `components-spinner__animation`

A simple rotation used for a loading-state spinner.

### `edit-post-fullscreen-mode__slide-in-animation`

When you enter fullscreen mode, the editor bar animates downwards from the top.

### `edit-post-layout__slide-in-animation`

The Publish sidebar lives to the right of the viewport. When you press the "Publish..." button, it slides in from the right.

### `nux-pulse`

This animation is used for the dot indicators that appear in the out of box experience. They create a "pulsing" effect behind the dot indicators.
25 changes: 25 additions & 0 deletions packages/components/src/animate/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* External dependencies
*/
import classnames from 'classnames';

function Animate( { type, options = {}, children } ) {
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it exit early when children isn't a function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as I was saying above, we don't guard about the other props. I'm hesitant to add a special case for children.

if ( type === 'appear' ) {
const { origin = 'top' } = options;
const [ yAxis, xAxis = 'center' ] = origin.split( ' ' );

return children( {
className: classnames(
'components-animate__appear',
{
[ 'is-from-' + xAxis ]: xAxis !== 'center',
[ 'is-from-' + yAxis ]: yAxis !== 'middle',
},
),
} );
}

return children( {} );
}

export default Animate;
28 changes: 28 additions & 0 deletions packages/components/src/animate/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.components-animate__appear {
animation: components-animate__appear-animation 0.1s cubic-bezier(0, 0, 0.2, 1) 0s;
animation-fill-mode: forwards;

&.is-from-top,
&.is-from-top.is-from-left {
transform-origin: top left;
}
&.is-from-top.is-from-right {
transform-origin: top right;
}
&.is-from-bottom,
&.is-from-bottom.is-from-left {
transform-origin: bottom left;
}
&.is-from-bottom.is-from-right {
transform-origin: bottom right;
}
}

@keyframes components-animate__appear-animation {
from {
transform: translateY(-2em) scaleY(0) scaleX(0);
}
to {
transform: translateY(0%) scaleY(1) scaleX(1);
}
}
1 change: 1 addition & 0 deletions packages/components/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Components
export * from './primitives';
// eslint-disable-next-line camelcase
export { default as Animate } from './animate';
export { default as Autocomplete } from './autocomplete';
export { default as BaseControl } from './base-control';
export { default as Button } from './button';
Expand Down
85 changes: 56 additions & 29 deletions packages/components/src/popover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import IconButton from '../icon-button';
import ScrollLock from '../scroll-lock';
import IsolatedEventContainer from '../isolated-event-container';
import { Slot, Fill, Consumer } from '../slot-fill';
import Animate from '../animate';

const FocusManaged = withConstrainedTabbing( withFocusReturn( ( { children } ) => children ) );

Expand Down Expand Up @@ -55,6 +56,11 @@ class Popover extends Component {
contentWidth: null,
isMobile: false,
popoverSize: null,

// Delay the animation after the initial render
// because the animation have impact on the height of the popover
// causing the computed position to be wrong.
isReadyToAnimate: false,
};

// Property used keep track of the previous anchor rect
Expand Down Expand Up @@ -150,7 +156,7 @@ class Popover extends Component {
popoverSize.height !== this.state.popoverSize.height
);
if ( didPopoverSizeChange ) {
this.setState( { popoverSize } );
this.setState( { popoverSize, isReadyToAnimate: true } );
}
this.anchorRect = anchorRect;
this.computePopoverPosition( popoverSize, anchorRect );
Expand Down Expand Up @@ -258,6 +264,7 @@ class Popover extends Component {
focusOnMount,
getAnchorRect,
expandOnMobile,
animate = true,
/* eslint-enable no-unused-vars */
...contentProps
} = this.props;
Expand All @@ -270,8 +277,21 @@ class Popover extends Component {
contentWidth,
popoverSize,
isMobile,
isReadyToAnimate,
} = this.state;

// Compute the animation position
const yAxisMapping = {
top: 'bottom',
bottom: 'top',
};
const xAxisMapping = {
left: 'right',
right: 'left',
};
const animateYAxis = yAxisMapping[ yAxis ] || 'middle';
const animateXAxis = xAxisMapping[ xAxis ] || 'center';

const classes = classnames(
'components-popover',
className,
Expand All @@ -289,36 +309,43 @@ class Popover extends Component {
/* eslint-disable jsx-a11y/no-static-element-interactions */
let content = (
<PopoverDetectOutside onClickOutside={ onClickOutside }>
<IsolatedEventContainer
className={ classes }
style={ {
top: ! isMobile && popoverTop ? popoverTop + 'px' : undefined,
left: ! isMobile && popoverLeft ? popoverLeft + 'px' : undefined,
visibility: popoverSize ? undefined : 'hidden',
} }
{ ...contentProps }
onKeyDown={ this.maybeClose }
<Animate
type={ animate && isReadyToAnimate ? 'appear' : null }
options={ { origin: animateYAxis + ' ' + animateXAxis } }
>
{ isMobile && (
<div className="components-popover__header">
<span className="components-popover__header-title">
{ headerTitle }
</span>
<IconButton className="components-popover__close" icon="no-alt" onClick={ onClose } />
</div>
{ ( { className: animateClassName } ) => (
<IsolatedEventContainer
className={ classnames( classes, animateClassName ) }
style={ {
top: ! isMobile && popoverTop ? popoverTop + 'px' : undefined,
left: ! isMobile && popoverLeft ? popoverLeft + 'px' : undefined,
visibility: popoverSize ? undefined : 'hidden',
} }
{ ...contentProps }
onKeyDown={ this.maybeClose }
>
{ isMobile && (
<div className="components-popover__header">
<span className="components-popover__header-title">
{ headerTitle }
</span>
<IconButton className="components-popover__close" icon="no-alt" onClick={ onClose } />
</div>
) }
<div
ref={ this.contentNode }
className="components-popover__content"
style={ {
maxHeight: ! isMobile && contentHeight ? contentHeight + 'px' : undefined,
maxWidth: ! isMobile && contentWidth ? contentWidth + 'px' : undefined,
} }
tabIndex="-1"
>
{ children }
</div>
</IsolatedEventContainer>
) }
<div
ref={ this.contentNode }
className="components-popover__content"
style={ {
maxHeight: ! isMobile && contentHeight ? contentHeight + 'px' : undefined,
maxWidth: ! isMobile && contentWidth ? contentWidth + 'px' : undefined,
} }
tabIndex="-1"
>
{ children }
</div>
</IsolatedEventContainer>
</Animate>
</PopoverDetectOutside>
);
/* eslint-enable jsx-a11y/no-static-element-interactions */
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/style.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@import "./animate//style.scss";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a typo?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(The double slash, to be clear!)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep :) typo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this typo!

@import "./autocomplete/style.scss";
@import "./base-control/style.scss";
@import "./button-group/style.scss";
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/tooltip/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class Tooltip extends Component {
position={ position }
className="components-tooltip"
aria-hidden="true"
animate={ false }
>
{ text }
<Shortcut className="components-tooltip__shortcut" shortcut={ shortcut } />
Expand Down