Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

chore(Animation): move to function component, styles in theme are not required #2258

Merged
merged 8 commits into from
Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### BREAKING CHANGES
- Restricted prop set in the `Checkbox`, `Icon`, `Label`, `Slider`, `Status`, `Text` @layershifter ([#2307](https://github.com/microsoft/fluent-ui-react/pull/2307))
- Styles caching when no inline overrides are defined is enabled by default; use the `performance` prop on the `Provider` to opt out of this if needed @mnajdova ([#2309](https://github.com/microsoft/fluent-ui-react/pull/2309))
- Styles for the `Animation` component are removed from Teams theme @layershifter ([#2258](https://github.com/microsoft/fluent-ui-react/pull/2258))

### Fixes
- Remove dependency on Lodash in TypeScript typings @layershifter ([#2323](https://github.com/microsoft/fluent-ui-react/pull/2323))
Expand All @@ -33,6 +34,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Performance
- Add styles caching when there aren't inline overrides defined @mnajdova ([#2309](https://github.com/microsoft/fluent-ui-react/pull/2309))
- Styles for `Animation` component are computed again only on prop changes @layershifter ([#2258](https://github.com/microsoft/fluent-ui-react/pull/2258))

<!--------------------------------[ v0.44.0 ]------------------------------- -->
## [v0.44.0](https://github.com/microsoft/fluent-ui-react/tree/v0.44.0) (2020-02-05)
Expand Down
251 changes: 159 additions & 92 deletions packages/react/src/components/Animation/Animation.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import * as PropTypes from 'prop-types'
import * as React from 'react'
import {
ComponentAnimationProp,
getUnhandledProps,
unstable_createAnimationStyles as createAnimationStyles,
unstable_getStyles as getStyles,
useTelemetry,
} from '@fluentui/react-bindings'
import cx from 'classnames'
import * as _ from 'lodash'
import * as PropTypes from 'prop-types'
import * as React from 'react'
// @ts-ignore
import { ThemeContext } from 'react-fela'
import { Transition } from 'react-transition-group'

import {
UIComponent,
childrenExist,
StyledComponentProps,
commonPropTypes,
ShorthandFactory,
} from '../../utils'
import { ComponentEventHandler } from '../../types'
import { childrenExist, StyledComponentProps, commonPropTypes } from '../../utils'
import { ComponentEventHandler, ProviderContextPrepared } from '../../types'

export type AnimationChildrenProp = (props: { classes: string }) => React.ReactNode

Expand Down Expand Up @@ -139,92 +142,156 @@ export interface AnimationProps extends StyledComponentProps {
/**
* An Animation provides animation effects to rendered elements.
*/
class Animation extends UIComponent<AnimationProps, any> {
static create: ShorthandFactory<AnimationProps>

static className = 'ui-animation'

static displayName = 'Animation'

static propTypes = {
...commonPropTypes.createCommon({
accessibility: false,
as: false,
content: false,
children: false,
}),
children: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
name: PropTypes.string,
delay: PropTypes.string,
direction: PropTypes.string,
duration: PropTypes.string,
fillMode: PropTypes.string,
iterationCount: PropTypes.string,
keyframeParams: PropTypes.object,
playState: PropTypes.string,
timingFunction: PropTypes.string,
visible: PropTypes.bool,
appear: PropTypes.bool,
mountOnEnter: PropTypes.bool,
unmountOnExit: PropTypes.bool,
timeout: PropTypes.oneOfType([
PropTypes.number,
PropTypes.shape({
appear: PropTypes.number,
enter: PropTypes.number,
exit: PropTypes.number,
}),
]),
onEnter: PropTypes.func,
onEntering: PropTypes.func,
onEntered: PropTypes.func,
onExit: PropTypes.func,
onExiting: PropTypes.func,
onExited: PropTypes.func,
}

static defaultProps = {
timeout: 0,
}

handleAnimationEvent = (
const Animation: React.FC<AnimationProps> & {
className: string
handledProps: (keyof AnimationProps)[]
} = props => {
const context: ProviderContextPrepared = React.useContext(ThemeContext)
const { setStart, setEnd } = useTelemetry(Animation.displayName, context.telemetry)
setStart()

const {
appear,
children,
className,
delay,
direction,
duration,
fillMode,
iterationCount,
keyframeParams,
mountOnEnter,
name,
playState,
timeout,
timingFunction,
visible,
unmountOnExit,
} = props

const handleAnimationEvent = (
event: 'onEnter' | 'onEntering' | 'onEntered' | 'onExit' | 'onExiting' | 'onExited',
) => () => {
_.invoke(this.props, event, null, this.props)
_.invoke(props, event, null, props)
}

renderComponent({ ElementType, classes, unhandledProps }) {
const { children, mountOnEnter, unmountOnExit, timeout, appear, visible } = this.props

const isChildrenFunction = typeof children === 'function'

const child =
childrenExist(children) &&
!isChildrenFunction &&
(React.Children.only(children) as React.ReactElement<any>)

return (
<Transition
in={visible}
appear={appear}
mountOnEnter={mountOnEnter}
unmountOnExit={unmountOnExit}
timeout={timeout}
onEnter={this.handleAnimationEvent('onEnter')}
onEntering={this.handleAnimationEvent('onEntering')}
onEntered={this.handleAnimationEvent('onEntered')}
onExit={this.handleAnimationEvent('onExit')}
onExiting={this.handleAnimationEvent('onExiting')}
onExited={this.handleAnimationEvent('onExited')}
{...unhandledProps}
className={!isChildrenFunction ? cx(classes.root, (child as any).props.className) : ''}
>
{isChildrenFunction
? () => (children as AnimationChildrenProp)({ classes: classes.root })
: child}
</Transition>
)
}
const { classes } = React.useMemo(() => {
const animation: ComponentAnimationProp = {
name,
keyframeParams,
duration,
delay,
iterationCount,
direction,
fillMode,
playState,
timingFunction,
}

return getStyles({
className: Animation.className,
displayName: Animation.displayName,
props: {
className,
styles: createAnimationStyles(animation, context.theme),
},

disableAnimations: context.disableAnimations,
renderer: context.renderer,
rtl: context.rtl,
performance: {},
saveDebug: _.noop,
Copy link
Member Author

Choose a reason for hiding this comment

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

Should we have debug for Animation component?

Copy link
Contributor

Choose a reason for hiding this comment

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

No 👍

theme: context.theme,
})
}, [
className,
context,
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we using everything from the context? Should we maybe list the things used from the context

Copy link
Member Author

Choose a reason for hiding this comment

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

     disableAnimations: context.disableAnimations,
      renderer: context.renderer,
      rtl: context.rtl,
      theme: context.theme,

Almost everything... So I don't see any benefits in destructing it...

name,
delay,
direction,
duration,
fillMode,
iterationCount,
keyframeParams,
playState,
timingFunction,
])
const unhandledProps = getUnhandledProps(Animation.handledProps, props)

const isChildrenFunction = typeof children === 'function'
const child =
childrenExist(children) &&
!isChildrenFunction &&
(React.Children.only(children) as React.ReactElement)

const element = (
<Transition
in={visible}
appear={appear}
mountOnEnter={mountOnEnter}
unmountOnExit={unmountOnExit}
timeout={timeout}
onEnter={handleAnimationEvent('onEnter')}
onEntering={handleAnimationEvent('onEntering')}
onEntered={handleAnimationEvent('onEntered')}
onExit={handleAnimationEvent('onExit')}
onExiting={handleAnimationEvent('onExiting')}
onExited={handleAnimationEvent('onExited')}
{...unhandledProps}
className={!isChildrenFunction ? cx(classes.root, (child as any).props.className) : ''}
>
{isChildrenFunction
? () => (children as AnimationChildrenProp)({ classes: classes.root })
: child}
</Transition>
)
setEnd()

return element
}

Animation.className = 'ui-animation'
Animation.displayName = 'Animation'

Animation.defaultProps = {
timeout: 0,
}
Animation.propTypes = {
...commonPropTypes.createCommon({
accessibility: false,
as: false,
content: false,
children: false,
}),
children: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
name: PropTypes.string,
delay: PropTypes.string,
direction: PropTypes.string,
duration: PropTypes.string,
fillMode: PropTypes.string,
iterationCount: PropTypes.string,
keyframeParams: PropTypes.object,
playState: PropTypes.string,
timingFunction: PropTypes.string,
visible: PropTypes.bool,
appear: PropTypes.bool,
mountOnEnter: PropTypes.bool,
unmountOnExit: PropTypes.bool,
timeout: PropTypes.oneOfType([
PropTypes.number,
PropTypes.shape({
appear: PropTypes.number,
enter: PropTypes.number,
exit: PropTypes.number,
}),
]),
onEnter: PropTypes.func,
onEntering: PropTypes.func,
onEntered: PropTypes.func,
onExit: PropTypes.func,
onExiting: PropTypes.func,
onExited: PropTypes.func,
}
Animation.handledProps = Object.keys(Animation.propTypes) as any

export default Animation
2 changes: 0 additions & 2 deletions packages/react/src/themes/teams/componentStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ export { default as Tree } from './components/Tree/treeStyles'
export { default as TreeItem } from './components/Tree/treeItemStyles'
export { default as TreeTitle } from './components/Tree/treeTitleStyles'

export { default as Animation } from './components/Animation/animationStyles'

export { default as Video } from './components/Video/videoStyles'

export { default as Tooltip } from './components/Tooltip/tooltipStyles'
Expand Down
2 changes: 0 additions & 2 deletions packages/react/src/themes/teams/componentVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ export { default as ToolbarMenuRadioGroup } from './components/Toolbar/toolbarMe

export { default as TreeTitle } from './components/Tree/treeTitleVariables'

export { default as Animation } from './components/Animation/animationVariables'

export { default as Video } from './components/Video/videoVariables'

export { default as Tooltip } from './components/Tooltip/tooltipVariables'
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Animation from 'src/components/Animation/Animation'

describe('Animation', () => {
isConformant(Animation, {
constructorName: 'Animation',
hasAccessibilityProp: false,
requiredProps: { children: <div /> },
handlesAsProp: false,
Expand Down
28 changes: 0 additions & 28 deletions packages/react/test/specs/utils/felaRenderer-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,6 @@ import Animation from 'src/components/Animation/Animation'
import Provider from 'src/components/Provider/Provider'
import Text from 'src/components/Text/Text'
import { felaRenderer } from 'src/utils'
import {
ComponentAnimationProp,
unstable_createAnimationStyles as createAnimationStyles,
} from '@fluentui/react-bindings'

// Animation component depends on theme styles 💣
// Issue: https://github.com/microsoft/fluent-ui-react/issues/2247
// This adds required styles when needed.
const AnimationComponentStyles = {
root: ({ props: p, theme }) => {
const animation: ComponentAnimationProp = {
name: p.name,
keyframeParams: p.keyframeParams,
duration: p.duration,
delay: p.delay,
iterationCount: p.iterationCount,
direction: p.direction,
fillMode: p.fillMode,
playState: p.playState,
timingFunction: p.timingFunction,
}

return createAnimationStyles(animation, theme)
},
}

describe('felaRenderer', () => {
test('basic styles are rendered', () => {
Expand Down Expand Up @@ -75,7 +50,6 @@ describe('felaRenderer', () => {
const snapshot = createSnapshot(
<Provider
theme={{
componentStyles: { Animation: AnimationComponentStyles },
animations: { colorChanger },
}}
>
Expand Down Expand Up @@ -106,7 +80,6 @@ describe('felaRenderer', () => {
const snapshot = createSnapshot(
<Provider
theme={{
componentStyles: { Animation: AnimationComponentStyles },
animations: { colorChanger },
}}
>
Expand Down Expand Up @@ -138,7 +111,6 @@ describe('felaRenderer', () => {
<Provider
disableAnimations
theme={{
componentStyles: { Animation: AnimationComponentStyles },
animations: { spinner },
}}
>
Expand Down