Created
August 8, 2018 00:10
-
-
Save wldcordeiro/53cc307596f9c4f198e79593a652a76c to your computer and use it in GitHub Desktop.
Transitions and Stuffz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react' | |
import { bool, func, number, oneOf, oneOfType, shape, string } from 'prop-types' | |
import { Transition } from 'react-transition-group' | |
import stringify from 'json-stable-stringify' | |
function parseAmount(amount) { | |
if (typeof amount === 'object') { | |
return { | |
in: amount.in != null ? amount.in : 100, | |
out: amount.out != null ? amount.out : 0, | |
} | |
} | |
return { in: amount, out: 0 } | |
} | |
// 25ms is a decent enough amout of time to wait after the CSS transition for | |
// minor latency. | |
const BUFFER = 25 | |
// Transition uses four states, for this kind of transition we essentially have two. | |
const isEnd = state => ['entering', 'exited'].includes(state) | |
const isHorizontal = dir => ['left', 'right'].includes(dir) | |
const adjustForAxis = (dir, amount) => | |
['left', 'top'].includes(dir) ? -amount : amount | |
/* | |
* The idea is that this function is called for prop changes the user controls | |
* and we can avoid calling it by using component state. The rest of the styling | |
* relies upon the state provided by `Transition` and will update with that value. | |
*/ | |
function getStaticStyle(amount, curve, dir, duration) { | |
const transform = isHorizontal(dir) ? 'translateX' : 'translateY' | |
const { in: inAmount, out: outAmount } = parseAmount(amount) | |
const adjustedAmount = adjustForAxis(dir, inAmount) | |
return { | |
transition: `all ${duration}ms ${curve}`, | |
transformOrigin: `${dir} center`, | |
transform: { | |
end: `${transform}(${adjustedAmount}%)`, | |
start: `${transform}(${outAmount}%)`, | |
}, | |
} | |
} | |
export class WithSlide extends Component { | |
static propTypes = { | |
// Function that receives `{ style, state }` where `style` is the transition | |
// style for the start or end state, and `state` is the string state `start` | |
// or `end` to allow consumers to add additional styles for states. | |
children: func.isRequired, | |
// The boolean value to run the transition | |
in: bool.isRequired, | |
// Amount can be provided in one of two ways. If you provide a number you | |
// adjust only the `in` amount of the transition. Providing an object with | |
// the `in` and `out` values gives you more control. | |
amount: oneOfType([number, shape({ in: number, out: number })]), | |
// the easing curve function for the transition to use. | |
curve: string, | |
// Direction of exit, this just follows box model. | |
dir: oneOf(['top', 'left', 'bottom', 'right']), | |
// Duration in milliseconds of animation. | |
duration: number, | |
} | |
static defaultProps = { | |
amount: 100, | |
dir: 'left', | |
duration: 250, | |
curve: 'cubic-bezier(0.07, 0.95, 0, 1)', | |
} | |
state = { baseStyles: null } | |
static getDerivedStateFromProps({ amount, curve, dir, duration }, prevState) { | |
const newStyle = getStaticStyle(amount, curve, dir, duration) | |
const newStyleStr = stringify(newStyle) | |
const oldStyleStr = stringify(prevState.baseStyles) | |
if (newStyleStr !== oldStyleStr) { | |
return { baseStyles: newStyle } | |
} | |
return null | |
} | |
getTransitionStyle = transitionState => { | |
const { | |
baseStyles: { transform, ...baseStyles }, | |
} = this.state | |
return { | |
...baseStyles, | |
transform: isEnd(transitionState) ? transform.end : transform.start, | |
} | |
} | |
render() { | |
const { children, duration, in: inProp } = this.props | |
return ( | |
<Transition timeout={duration + BUFFER} in={inProp}> | |
{transitionState => | |
children({ | |
style: this.getTransitionStyle(transitionState), | |
state: isEnd(transitionState) ? 'end' : 'start', | |
}) | |
} | |
</Transition> | |
) | |
} | |
} | |
export default WithSlide |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import { css } from 'glamor' | |
import { storiesOf } from '@storybook/react' | |
import { | |
withKnobs, | |
boolean, | |
number, | |
object, | |
select, | |
text, | |
} from '@storybook/addon-knobs/react' | |
import WithSlide from './with-slide' | |
const containerStyle = css({ | |
width: 600, | |
height: 600, | |
display: 'flex', | |
flexWrap: 'nowrap', | |
border: '2px solid grey', | |
overflow: 'hidden', | |
}) | |
const blockStyle = css({ | |
width: 600, | |
height: 600, | |
background: 'yellow', | |
overflow: 'auto', | |
fontSize: '3rem', | |
display: 'flex', | |
alignItems: 'center', | |
justifyContent: 'center', | |
}) | |
const defaultTransitionAmounts = { in: 100, out: 0 } | |
const transitionDirections = ['top', 'right', 'bottom', 'left'] | |
const defaultUserStyles = { | |
start: { background: 'red' }, | |
end: { height: '20%' }, | |
} | |
storiesOf('WithSlide', module) | |
.addDecorator(withKnobs) | |
.add('Kitchen Sink', () => { | |
const inProp = boolean('Toggle Transition', true) | |
const duration = number('Transition Duration', 250) | |
const dir = select('Transition Direction', transitionDirections, 'left') | |
const amount = object('Transition Amounts', defaultTransitionAmounts) | |
const curve = text( | |
'Transition easing function', | |
'cubic-bezier(0.07, 0.95, 0, 1)' | |
) | |
const useStyle = boolean('Use Custom state style', false) | |
const userStyle = object('Custom User State Style', defaultUserStyles) | |
return ( | |
<div {...containerStyle}> | |
<WithSlide | |
in={inProp} | |
curve={curve} | |
duration={duration} | |
dir={dir} | |
amount={amount} | |
> | |
{({ style, state }) => ( | |
<div {...css(blockStyle, style, useStyle && userStyle[state])}> | |
Stuff | |
</div> | |
)} | |
</WithSlide> | |
</div> | |
) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment