From 485d25953f6f9bd85388501720d76c363969479f Mon Sep 17 00:00:00 2001 From: madou Date: Thu, 2 May 2019 23:38:46 +1000 Subject: [PATCH 1/5] feat: adds reshaping container composite component --- packages/yubaba/src/Baba/index.tsx | 7 +- packages/yubaba/src/Collector/index.tsx | 24 +- .../animations/ReshapingContainer/index.tsx | 211 ++++++++++++++++++ .../animations/ReshapingContainer/stories.tsx | 100 +++++++++ .../src/animations/SimpleReveal/index.tsx | 105 +++++++++ .../src/animations/SimpleReveal/stories.tsx | 46 ++++ packages/yubaba/src/index.tsx | 2 + 7 files changed, 479 insertions(+), 16 deletions(-) create mode 100644 packages/yubaba/src/animations/ReshapingContainer/index.tsx create mode 100644 packages/yubaba/src/animations/ReshapingContainer/stories.tsx create mode 100644 packages/yubaba/src/animations/SimpleReveal/index.tsx create mode 100644 packages/yubaba/src/animations/SimpleReveal/stories.tsx diff --git a/packages/yubaba/src/Baba/index.tsx b/packages/yubaba/src/Baba/index.tsx index 561a949..87c4d49 100644 --- a/packages/yubaba/src/Baba/index.tsx +++ b/packages/yubaba/src/Baba/index.tsx @@ -21,10 +21,8 @@ import noop from '../lib/noop'; import * as babaStore from '../lib/babaStore'; import { InjectedProps, withBabaManagerContext } from '../BabaManager'; - export type AnimationFunc = () => Promise; - export interface MappedAnimation { animate: AnimationFunc; beforeAnimate: AnimationFunc; @@ -32,16 +30,13 @@ export interface MappedAnimation { cleanup: () => void; } - export type AnimationBlock = MappedAnimation[]; - export interface ChildProps { style?: InlineStyles; className?: string; } - export interface State { shown: boolean; childProps: ChildProps; @@ -481,6 +476,7 @@ If it's an image, try and have the image loaded before mounting, or set a static return ( { receiveRenderChildren, receiveRef, receiveData, + topMostCollector, } = this.props; if (typeof children !== 'function') { @@ -126,7 +128,7 @@ export default class Collector extends React.Component { receiveRef(ref); } - if (collect) { + if (!topMostCollector && collect) { collect.ref(ref); } }, @@ -136,13 +138,13 @@ export default class Collector extends React.Component { receiveFocalTargetRef(ref); } - if (collect) { + if (!topMostCollector && collect) { collect.focalTargetRef(ref); } }, data: childData => { const collectedData = data ? [data].concat(childData) : childData; - if (collect) { + if (!topMostCollector && collect) { collect.data(collectedData); } @@ -151,7 +153,7 @@ export default class Collector extends React.Component { } }, renderChildren: node => { - if (collect) { + if (!topMostCollector && collect) { collect.renderChildren(node); } @@ -161,9 +163,10 @@ export default class Collector extends React.Component { }, style: { ...style, - ...(collect ? collect.style : {}), + ...(collect && !topMostCollector ? collect.style : {}), }, - className: className || (collect ? collect.className : undefined), + className: + className || (collect && !topMostCollector ? collect.className : undefined), }} > {children} @@ -177,7 +180,7 @@ export default class Collector extends React.Component { {collect => { if (typeof children === 'function') { - if (collect) { + if (!topMostCollector && collect) { const collectedData = data ? [data] : []; collect.renderChildren(children); collect.data(collectedData); @@ -189,9 +192,10 @@ export default class Collector extends React.Component { return React.Children.only( children({ - className: className || (collect ? collect.className : undefined), + className: + className || (collect && !topMostCollector ? collect.className : undefined), ref: (ref: HTMLElement) => { - if (collect) { + if (!topMostCollector && collect) { collect.ref(ref); } @@ -199,7 +203,7 @@ export default class Collector extends React.Component { receiveRef(ref); } }, - style: collect ? { ...style, ...collect.style } : style || {}, + style: collect && !topMostCollector ? { ...style, ...collect.style } : style || {}, }) ); } diff --git a/packages/yubaba/src/animations/ReshapingContainer/index.tsx b/packages/yubaba/src/animations/ReshapingContainer/index.tsx new file mode 100644 index 0000000..6034c64 --- /dev/null +++ b/packages/yubaba/src/animations/ReshapingContainer/index.tsx @@ -0,0 +1,211 @@ +import * as React from 'react'; +import Baba from '../../Baba'; +import Move from '../Move'; +import { CollectorChildrenAsFunction } from '../../Collector'; +import SimpleReveal from '../SimpleReveal'; +import { Duration } from '../types'; + +interface ReshapingContainerProps { + /** + * This should be a unique identifier across your whole app. + */ + id: string; + + children: CollectorChildrenAsFunction; + + /** + * Defaults to "div". + * Any valid HTML tag allowed. + */ + as: keyof JSX.IntrinsicElements; + + /** + * Used the same as the CSS property. + */ + background?: string; + + /** + * Used the same as the CSS property. + */ + boxShadow?: string; + + /** + * Padding. + * Use only px values, otherwise same as the CSS property. + */ + padding?: string; + + /** + * Used the same as the CSS property. + */ + maxWidth?: string; + + /** + * Used the same as the CSS property. + */ + maxHeight?: string; + + /** + * Used the same as the CSS property. + */ + minWidth?: string; + + /** + * Used the same as the CSS property. + */ + minHeight?: string; + + /** + * Used the same as the CSS property. + */ + margin?: string; + + /** + * Takes either "dynamic" or a number in ms. + * How long the animation should take over {duration}ms. + * Defaults to "dynamic". + */ + duration?: Duration; + + /** + * Used the same as the CSS property. + */ + display?: string; + + /** + * Timing function to be used in the transition. + */ + timingFunction?: string; +} + +interface ReshapingContainerState { + renderCount: number; +} + +export default class ReshapingContainer extends React.PureComponent< + ReshapingContainerProps, + ReshapingContainerState +> { + static defaultProps = { + as: 'div', + }; + + state: ReshapingContainerState = { + renderCount: 0, + }; + + /** + * Incremeent render count every time a render occurs. + * We're abusing react "key" to trigger animations for now. + */ + static getDerivedStateFromProps(_, state) { + return { + renderCount: state.renderCount + 1, + }; + } + + componentDidMount() { + if (this.props.padding.indexOf('em') >= 0 || this.props.padding.indexOf('%') >= 0) { + throw new Error(`Only px values are supported for props.padding in ${this.displayName}`); + } + } + + /** + * We're using this to increase the clip-path box of the Reveal animation so the children contents + * line up with the parent container in this component. + */ + getInversePaddingParts() { + const parts = this.props.padding.split(' ').map(p => -Number(p.replace('px', ''))); + + switch (parts.length) { + case 1: + parts.push(parts[0]); + parts.push(parts[0]); + parts.push(parts[0]); + break; + + case 2: + parts.push(parts[0]); + parts.push(parts[1]); + break; + + case 3: + parts.push(parts[1]); + break; + + case 4: + default: + break; + } + + return parts; + } + + render() { + const { + children, + background, + maxWidth, + maxHeight, + minWidth, + minHeight, + boxShadow, + margin, + padding, + duration, + id, + display, + timingFunction, + ...rest + } = this.props; + + return ( + + + {baba => ( + +