Skip to content

Commit

Permalink
feat(Transition): make duration prop more advanced (#1967)
Browse files Browse the repository at this point in the history
* feat(Transition): make `duration` prop more advanced

* feat(Transition): support string duration propType
  • Loading branch information
layershifter authored and levithomason committed Aug 20, 2017
1 parent e350886 commit 6b66da1
Show file tree
Hide file tree
Showing 16 changed files with 180 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const transitions = ['jiggle', 'flash', 'shake', 'pulse', 'tada', 'bounce']

const options = transitions.map(name => ({ key: name, text: name, value: name }))

export default class TransitionExampleStaticExplorer extends Component {
export default class TransitionExampleTransitionExplorer extends Component {
state = { animation: transitions[0], duration: 500, visible: true }

handleChange = (e, { name, value }) => this.setState({ [name]: value })
Expand Down
7 changes: 3 additions & 4 deletions docs/app/Examples/modules/Transition/Explorers/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React from 'react'
import { Message } from 'semantic-ui-react'

import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'

import { Message } from 'semantic-ui-react'

const TransitionTypesExamples = () => (
const TransitionExplorersExamples = () => (
<ExampleSection title='Explorers'>
<ComponentExample
title='Directional Animations'
Expand All @@ -32,4 +31,4 @@ const TransitionTypesExamples = () => (
</ExampleSection>
)

export default TransitionTypesExamples
export default TransitionExplorersExamples
2 changes: 1 addition & 1 deletion docs/app/Examples/modules/Transition/Types/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react'
import { Message } from 'semantic-ui-react'

import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'
import { Message } from 'semantic-ui-react'

const TransitionTypesExamples = () => (
<ExampleSection title='Types'>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { Component } from 'react'
import { Form, Grid, Image, Transition } from 'semantic-ui-react'

export default class TransitionExampleDuration extends Component {
state = { hide: 500, show: 500, visible: true }

handleChange = (e, { name, value }) => this.setState({ [name]: value })

toggleVisibility = () => this.setState({ visible: !this.state.visible })

render() {
const { hide, show, visible } = this.state

return (
<Grid columns={2}>
<Grid.Column as={Form}>
<Form.Input
label={`Hide duration: ${hide}ms `}
min={100}
max={5000}
name='hide'
onChange={this.handleChange}
step={100}
type='range'
value={hide}
/>
<Form.Input
label={`Show duration: ${show}ms `}
min={100}
max={5000}
name='show'
onChange={this.handleChange}
step={100}
type='range'
value={show}
/>
<Form.Button content='Run' onClick={this.toggleVisibility} />
</Grid.Column>

<Grid.Column>
<Transition duration={{ hide, show }} visible={visible}>
<Image centered size='small' src='/assets/images/leaves/3.png' />
</Transition>
</Grid.Column>
</Grid>
)
}
}
21 changes: 21 additions & 0 deletions docs/app/Examples/modules/Transition/Usage/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'

import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'

const TransitionUsageExamples = () => (
<ExampleSection title='Usage'>
<ComponentExample
title='Duration'
description={(
<span>
Duration of the CSS transition animation can be defined separately for <code>hide</code> and <code>show</code>
animations.
</span>
)}
examplePath='modules/Transition/Usage/TransitionExampleDuration'
/>
</ExampleSection>
)

export default TransitionUsageExamples
2 changes: 2 additions & 0 deletions docs/app/Examples/modules/Transition/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import React from 'react'

import Explorers from './Explorers'
import Types from './Types'
import Usage from './Usage'

const TransitionExamples = () => (
<div>
<Types />
<Explorers />
<Usage />
</div>
)

Expand Down
Binary file added docs/app/assets/images/leaves/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,12 @@ export { default as Sticky, StickyProps } from './dist/commonjs/modules/Sticky';
export { default as Tab, TabProps } from './dist/commonjs/modules/Tab';
export { default as TabPane, TabPaneProps } from './dist/commonjs/modules/Tab/TabPane';

export { default as Transition, TransitionProps, TRANSITION_STATUSES } from './dist/commonjs/modules/Transition';
export {
default as Transition,
TransitionProps,
TransitionPropDuration,
TRANSITION_STATUSES
} from './dist/commonjs/modules/Transition';
export { default as TransitionGroup, TransitionGroupProps } from './dist/commonjs/modules/Transition/TransitionGroup';

// Views
Expand Down
1 change: 1 addition & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ export * as SUI from './SUI'

export { default as keyboardKey } from './keyboardKey'
export { numberToWordMap, numberToWord } from './numberToWord'
export normalizeTransitionDuration from './normalizeTransitionDuration'
export { default as objectDiff } from './objectDiff'
9 changes: 9 additions & 0 deletions src/lib/normalizeTransitionDuration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Normalizes the duration of a transition.
* @param {number|object} duration The value to normalize.
* @param {'hide'|'show'} type The type of transition.
* @returns {number}
*/
export default (duration, type) => (
(typeof duration === 'number' || typeof duration === 'string') ? duration : duration[type]
)
7 changes: 6 additions & 1 deletion src/modules/Transition/Transition.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface TransitionProps {
children?: React.ReactNode;

/** Duration of the CSS transition animation in milliseconds. */
duration?: number;
duration?: number | string | TransitionPropDuration;

/** Show the component; triggers the enter or exit animation. */
visible?: boolean;
Expand Down Expand Up @@ -69,6 +69,11 @@ export interface TransitionEventData extends TransitionProps {
status: TRANSITION_STATUSES;
}

export interface TransitionPropDuration {
hide: number;
show: number;
}

interface TransitionComponent extends React.ComponentClass<TransitionProps> {
Group: typeof TransitionGroup;

Expand Down
23 changes: 20 additions & 3 deletions src/modules/Transition/Transition.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ import { cloneElement, Component } from 'react'
import {
makeDebugger,
META,
normalizeTransitionDuration,
SUI,
useKeyOnly,
} from '../../lib'
import TransitionGroup from './TransitionGroup'

const debug = makeDebugger('transition')

const TRANSITION_TYPE = {
ENTERING: 'show',
EXITING: 'hide',
}

/**
* A transition is an animation usually used to move content in or out of view.
*/
Expand All @@ -25,7 +31,14 @@ export default class Transition extends Component {
children: PropTypes.element.isRequired,

/** Duration of the CSS transition animation in milliseconds. */
duration: PropTypes.number,
duration: PropTypes.oneOfType([
PropTypes.number,
PropTypes.shape({
hide: PropTypes.number,
show: PropTypes.number,
}),
PropTypes.string,
]),

/** Show the component; triggers the enter or exit animation. */
visible: PropTypes.bool,
Expand Down Expand Up @@ -145,7 +158,7 @@ export default class Transition extends Component {
this.nextStatus = null
this.setState({ status, animating: true }, () => {
_.invoke(this.props, 'onStart', null, { ...this.props, status })
setTimeout(this.handleComplete, duration)
setTimeout(this.handleComplete, normalizeTransitionDuration(duration, 'show'))
})
}

Expand Down Expand Up @@ -262,9 +275,13 @@ export default class Transition extends Component {

computeStyle = () => {
const { children, duration } = this.props
const { status } = this.state

const childStyle = _.get(children, 'props.style')
const type = TRANSITION_TYPE[status]
const animationDuration = type && `${normalizeTransitionDuration(duration, type)}ms`

return { ...childStyle, animationDuration: `${duration}ms` }
return { ...childStyle, animationDuration }
}

// ----------------------------------------
Expand Down
4 changes: 3 additions & 1 deletion src/modules/Transition/TransitionGroup.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as React from 'react';

import { SemanticTRANSITIONS } from '../../';
import { TransitionPropDuration } from './Transition';

export interface TransitionGroupProps {
[key: string]: any;
Expand All @@ -14,7 +16,7 @@ export interface TransitionGroupProps {
children?: React.ReactNode;

/** Duration of the CSS transition animation in milliseconds. */
duration?: number;
duration?: number | string | TransitionPropDuration;
}

interface TransitionGroupComponent extends React.ComponentClass<TransitionGroupProps> {
Expand Down
9 changes: 8 additions & 1 deletion src/modules/Transition/TransitionGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ export default class TransitionGroup extends React.Component {
children: PropTypes.node,

/** Duration of the CSS transition animation in milliseconds. */
duration: PropTypes.number,
duration: PropTypes.oneOfType([
PropTypes.number,
PropTypes.shape({
hide: PropTypes.number.isRequired,
show: PropTypes.number.isRequired,
}),
PropTypes.string,
]),
}

static defaultProps = {
Expand Down
2 changes: 1 addition & 1 deletion src/modules/Transition/index.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default, TransitionProps, TRANSITION_STATUSES } from './Transition';
export { default, TransitionProps, TransitionPropDuration, TRANSITION_STATUSES } from './Transition';
62 changes: 50 additions & 12 deletions test/specs/modules/Transition/Transition-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,20 +160,58 @@ describe('Transition', () => {
})

describe('duration', () => {
it('applies default value to style', () => {
wrapperShallow(
<Transition>
<p />
</Transition>,
).should.have.style('animation-duration', '500ms')
it('does not apply to style when ENTERED', () => {
wrapperShallow(<Transition transitionOnMount={false}><p /></Transition>)
.should.not.have.style('animation-duration')
})

it('applies value to style', () => {
wrapperShallow(
<Transition duration={1000}>
<p />
</Transition>,
).should.have.style('animation-duration', '1000ms')
it('applies default value to style when ENTERING', () => {
wrapperShallow(<Transition><p /></Transition>)

wrapper.setState({ status: Transition.ENTERING })
wrapper.should.have.style('animation-duration', '500ms')
})

it('applies numeric value to style when ENTERING', () => {
wrapperShallow(<Transition duration={1000}><p /></Transition>)

wrapper.setState({ status: Transition.ENTERING })
wrapper.should.have.style('animation-duration', '1000ms')
})

it('applies object value to style when ENTERING', () => {
wrapperShallow(<Transition duration={{ hide: 1000, show: 2000 }}><p /></Transition>)

wrapper.setState({ status: Transition.ENTERING })
wrapper.should.have.style('animation-duration', '2000ms')
})

it('does not apply to style when EXITED', () => {
wrapperShallow(<Transition><p /></Transition>)

wrapper.setState({ status: Transition.EXITED })
wrapper.should.not.have.style('animation-duration')
})

it('applies default value to style when EXITING', () => {
wrapperShallow(<Transition><p /></Transition>)

wrapper.setState({ animating: true, status: Transition.EXITING })
wrapper.should.have.style('animation-duration')
})

it('applies numeric value to style when EXITING', () => {
wrapperShallow(<Transition duration={1000}><p /></Transition>)
wrapper.setState({ status: Transition.ENTERING })

wrapper.should.have.style('animation-duration', '1000ms')
})

it('applies object value to style when EXITING', () => {
wrapperShallow(<Transition duration={{ hide: 1000, show: 2000 }}><p /></Transition>)

wrapper.setState({ status: Transition.EXITING })
wrapper.should.have.style('animation-duration', '1000ms')
})
})

Expand Down

0 comments on commit 6b66da1

Please sign in to comment.