feat: refactor animation code
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import * as React from 'react';
|
||||
import { Keyframes } from 'styled-components';
|
||||
|
||||
import { css, keyframes, styled } from '../../style/theme';
|
||||
|
||||
const generateTransitionInfoCss = (
|
||||
key: keyof TransitionInfo,
|
||||
top?: TransitionInfo,
|
||||
bottom?: TransitionInfo,
|
||||
left?: TransitionInfo,
|
||||
right?: TransitionInfo,
|
||||
): string => {
|
||||
const topStringIfExists = top ? `top: ${top[key]};` : '';
|
||||
const bottomStringIfExists = bottom ? `bottom: ${bottom[key]};` : '';
|
||||
const leftStringIfExists = left ? `left: ${left[key]};` : '';
|
||||
const rightStringIfExists = right ? `right: ${right[key]};` : '';
|
||||
return `
|
||||
${topStringIfExists}
|
||||
${bottomStringIfExists}
|
||||
${leftStringIfExists}
|
||||
${rightStringIfExists}
|
||||
`;
|
||||
};
|
||||
|
||||
const slideKeyframeGenerator = (
|
||||
top?: TransitionInfo,
|
||||
bottom?: TransitionInfo,
|
||||
left?: TransitionInfo,
|
||||
right?: TransitionInfo,
|
||||
) => keyframes`
|
||||
from {
|
||||
position: relative;
|
||||
${generateTransitionInfoCss('from', top, bottom, left, right)}
|
||||
}
|
||||
|
||||
to {
|
||||
position: relative;
|
||||
${generateTransitionInfoCss('to', top, bottom, left, right)}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface TransitionInfo {
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
export interface PositionAnimationProps {
|
||||
top?: TransitionInfo;
|
||||
bottom?: TransitionInfo;
|
||||
left?: TransitionInfo;
|
||||
right?: TransitionInfo;
|
||||
timingFunction: string;
|
||||
direction?: string;
|
||||
}
|
||||
|
||||
export const PositionAnimation =
|
||||
styled.div <
|
||||
PositionAnimationProps >
|
||||
`
|
||||
animation-name: ${props =>
|
||||
css`
|
||||
${slideKeyframeGenerator(props.top, props.bottom, props.left, props.right)};
|
||||
`};
|
||||
animation-duration: 0.3s;
|
||||
animation-timing-function: ${props => props.timingFunction};
|
||||
animation-delay: 0s;
|
||||
animation-iteration-count: 1;
|
||||
animation-fill-mode: ${props => props.direction || 'none'};
|
||||
position: relative;
|
||||
`;
|
||||
@@ -3,56 +3,16 @@ import { Keyframes } from 'styled-components';
|
||||
|
||||
import { css, keyframes, styled } from '../../style/theme';
|
||||
|
||||
const slideKeyframeGenerator = (fromY: string, toY: string) => keyframes`
|
||||
from {
|
||||
position: relative;
|
||||
top: ${fromY};
|
||||
}
|
||||
|
||||
to {
|
||||
position: relative;
|
||||
top: ${toY};
|
||||
}
|
||||
`;
|
||||
import { PositionAnimation, PositionAnimationProps } from './position_animation';
|
||||
|
||||
export type SlideAnimationPhase = 'slideIn' | 'slideOut';
|
||||
export interface SlideAnimationProps {
|
||||
keyframes: Keyframes;
|
||||
animationType: string;
|
||||
animationDirection?: string;
|
||||
phase: SlideAnimationPhase;
|
||||
slideIn: PositionAnimationProps;
|
||||
slideOut: PositionAnimationProps;
|
||||
}
|
||||
|
||||
export const SlideAnimation =
|
||||
styled.div <
|
||||
SlideAnimationProps >
|
||||
`
|
||||
animation-name: ${props =>
|
||||
css`
|
||||
${props.keyframes};
|
||||
`};
|
||||
animation-duration: 0.3s;
|
||||
animation-timing-function: ${props => props.animationType};
|
||||
animation-delay: 0s;
|
||||
animation-iteration-count: 1;
|
||||
animation-fill-mode: ${props => props.animationDirection || 'none'};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export interface SlideAnimationComponentProps {
|
||||
downY: string;
|
||||
}
|
||||
|
||||
export const SlideUpAnimation: React.StatelessComponent<SlideAnimationComponentProps> = props => (
|
||||
<SlideAnimation animationType="ease-in" keyframes={slideKeyframeGenerator(props.downY, '0px')}>
|
||||
{props.children}
|
||||
</SlideAnimation>
|
||||
);
|
||||
|
||||
export const SlideDownAnimation: React.StatelessComponent<SlideAnimationComponentProps> = props => (
|
||||
<SlideAnimation
|
||||
animationDirection="forwards"
|
||||
animationType="cubic-bezier(0.25, 0.1, 0.25, 1)"
|
||||
keyframes={slideKeyframeGenerator('0px', props.downY)}
|
||||
>
|
||||
{props.children}
|
||||
</SlideAnimation>
|
||||
);
|
||||
export const SlideAnimation: React.StatelessComponent<SlideAnimationProps> = props => {
|
||||
const propsToUse = props.phase === 'slideIn' ? props.slideIn : props.slideOut;
|
||||
return <PositionAnimation {...propsToUse}>{props.children}</PositionAnimation>;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface ERC20AssetAmountInputProps {
|
||||
asset?: ERC20Asset;
|
||||
value?: BigNumberInput;
|
||||
onChange: (value?: BigNumberInput, asset?: ERC20Asset) => void;
|
||||
onSymbolClick: (asset?: ERC20Asset) => void;
|
||||
onSymbolClick?: (asset?: ERC20Asset) => void;
|
||||
startingFontSizePx: number;
|
||||
fontColor?: ColorOption;
|
||||
isDisabled: boolean;
|
||||
@@ -28,7 +28,6 @@ export interface ERC20AssetAmountInputState {
|
||||
export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInputProps, ERC20AssetAmountInputState> {
|
||||
public static defaultProps = {
|
||||
onChange: util.boundNoop,
|
||||
onSymbolClick: util.boundNoop,
|
||||
isDisabled: false,
|
||||
};
|
||||
constructor(props: ERC20AssetAmountInputProps) {
|
||||
@@ -112,7 +111,9 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput
|
||||
});
|
||||
};
|
||||
private readonly _handleSymbolClick = () => {
|
||||
this.props.onSymbolClick(this.props.asset);
|
||||
if (this.props.onSymbolClick) {
|
||||
this.props.onSymbolClick(this.props.asset);
|
||||
}
|
||||
};
|
||||
// For assets with symbols of different length,
|
||||
// start scaling the input at different character lengths
|
||||
|
||||
@@ -60,8 +60,8 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
|
||||
private _renderAmountsSection(): React.ReactNode {
|
||||
return (
|
||||
<Container>
|
||||
<Container marginBottom="5px">{this._placeholderOrAmount(this._ethAmount)}</Container>
|
||||
<Container opacity={0.7}>{this._placeholderOrAmount(this._dollarAmount)}</Container>
|
||||
<Container marginBottom="5px">{this._renderPlaceholderOrAmount(this._renderEthAmount)}</Container>
|
||||
<Container opacity={0.7}>{this._renderPlaceholderOrAmount(this._renderDollarAmount)}</Container>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -92,7 +92,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
|
||||
return 'I want to buy';
|
||||
}
|
||||
|
||||
private _placeholderOrAmount(amountFunction: () => React.ReactNode): React.ReactNode {
|
||||
private _renderPlaceholderOrAmount(amountFunction: () => React.ReactNode): React.ReactNode {
|
||||
if (this.props.quoteRequestState === AsyncProcessState.PENDING) {
|
||||
return <AmountPlaceholder isPulsating={true} color={PLACEHOLDER_COLOR} />;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
|
||||
return amountFunction();
|
||||
}
|
||||
|
||||
private readonly _ethAmount = (): React.ReactNode => {
|
||||
private readonly _renderEthAmount = (): React.ReactNode => {
|
||||
return (
|
||||
<Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}>
|
||||
{format.ethBaseAmount(
|
||||
@@ -114,7 +114,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
|
||||
);
|
||||
};
|
||||
|
||||
private readonly _dollarAmount = (): React.ReactNode => {
|
||||
private readonly _renderDollarAmount = (): React.ReactNode => {
|
||||
return (
|
||||
<Text fontSize="16px" fontColor={ColorOption.white}>
|
||||
{format.ethBaseAmountInUsd(
|
||||
|
||||
@@ -12,10 +12,11 @@ export interface PanelProps {
|
||||
export const Panel: React.StatelessComponent<PanelProps> = ({ children, onClose }) => (
|
||||
<Container
|
||||
backgroundColor={ColorOption.white}
|
||||
position="absolute"
|
||||
top="0px"
|
||||
left="0px"
|
||||
width="100%"
|
||||
// position="absolute"
|
||||
// left="0px"
|
||||
// bottom="0px"
|
||||
// width="100%"
|
||||
// height="100%"
|
||||
height="100%"
|
||||
zIndex={zIndex.panel}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,8 @@ import * as React from 'react';
|
||||
|
||||
import { ColorOption } from '../style/theme';
|
||||
|
||||
import { SlideDownAnimation, SlideUpAnimation } from './animations/slide_animations';
|
||||
import { PositionAnimationProps } from './animations/position_animation';
|
||||
import { SlideAnimation, SlideAnimationPhase } from './animations/slide_animations';
|
||||
|
||||
import { Container, Flex, Text } from './ui';
|
||||
|
||||
@@ -31,16 +32,29 @@ export const Error: React.StatelessComponent<ErrorProps> = props => (
|
||||
</Container>
|
||||
);
|
||||
|
||||
export type SlidingDirection = 'up' | 'down';
|
||||
export interface SlidingErrorProps extends ErrorProps {
|
||||
direction: SlidingDirection;
|
||||
phase: SlideAnimationPhase;
|
||||
}
|
||||
export const SlidingError: React.StatelessComponent<SlidingErrorProps> = props => {
|
||||
const AnimationComponent = props.direction === 'up' ? SlideUpAnimation : SlideDownAnimation;
|
||||
|
||||
const slideAmount = '120px';
|
||||
const slideUp: PositionAnimationProps = {
|
||||
timingFunction: 'ease-in',
|
||||
top: {
|
||||
from: slideAmount,
|
||||
to: '0px',
|
||||
},
|
||||
};
|
||||
const slideDown: PositionAnimationProps = {
|
||||
timingFunction: 'cubic-bezier(0.25, 0.1, 0.25, 1)',
|
||||
top: {
|
||||
from: '0px',
|
||||
to: slideAmount,
|
||||
},
|
||||
direction: 'forwards',
|
||||
};
|
||||
return (
|
||||
<AnimationComponent downY="120px">
|
||||
<SlideAnimation slideIn={slideUp} slideOut={slideDown} phase={props.phase}>
|
||||
<Error icon={props.icon} message={props.message} />
|
||||
</AnimationComponent>
|
||||
</SlideAnimation>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface ContainerProps {
|
||||
whiteSpace?: string;
|
||||
opacity?: number;
|
||||
cursor?: string;
|
||||
overflow?: string;
|
||||
}
|
||||
|
||||
export const Container =
|
||||
@@ -59,6 +60,7 @@ export const Container =
|
||||
${props => cssRuleIfExists(props, 'white-space')}
|
||||
${props => cssRuleIfExists(props, 'opacity')}
|
||||
${props => cssRuleIfExists(props, 'cursor')}
|
||||
${props => cssRuleIfExists(props, 'overflow')}
|
||||
${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')};
|
||||
background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
|
||||
border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')};
|
||||
|
||||
@@ -4,16 +4,16 @@ import { LatestBuyQuoteOrderDetails } from '../containers/latest_buy_quote_order
|
||||
import { LatestError } from '../containers/latest_error';
|
||||
import { SelectedAssetBuyOrderStateButtons } from '../containers/selected_asset_buy_order_state_buttons';
|
||||
import { SelectedAssetInstantHeading } from '../containers/selected_asset_instant_heading';
|
||||
|
||||
import { ColorOption } from '../style/theme';
|
||||
import { zIndex } from '../style/z_index';
|
||||
|
||||
import { Panel } from './panel';
|
||||
import { Container, Flex } from './ui';
|
||||
|
||||
export interface ZeroExInstantContainerProps {}
|
||||
|
||||
export const ZeroExInstantContainer: React.StatelessComponent<ZeroExInstantContainerProps> = props => (
|
||||
<Container width="350px">
|
||||
<Container width="350px" position="relative">
|
||||
<Container zIndex={zIndex.errorPopup} position="relative">
|
||||
<LatestError />
|
||||
</Container>
|
||||
@@ -23,6 +23,7 @@ export const ZeroExInstantContainer: React.StatelessComponent<ZeroExInstantConta
|
||||
backgroundColor={ColorOption.white}
|
||||
borderRadius="3px"
|
||||
hasBoxShadow={true}
|
||||
overflow="hidden"
|
||||
>
|
||||
<Flex direction="column" justify="flex-start">
|
||||
<SelectedAssetInstantHeading />
|
||||
@@ -31,6 +32,11 @@ export const ZeroExInstantContainer: React.StatelessComponent<ZeroExInstantConta
|
||||
<SelectedAssetBuyOrderStateButtons />
|
||||
</Container>
|
||||
</Flex>
|
||||
{/* <Container position="absolute" left="0px" bottom="0px" width="100%" height="100%">
|
||||
<SlideAnimationHelper direction="up" downY="200px">
|
||||
<Panel> Hey </Panel>
|
||||
</SlideAnimationHelper>
|
||||
</Container> */}
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { SlideAnimationPhase } from '../components/animations/slide_animations';
|
||||
import { SlidingError } from '../components/sliding_error';
|
||||
import { State } from '../redux/reducer';
|
||||
import { Asset, DisplayStatus } from '../types';
|
||||
@@ -9,26 +10,26 @@ import { Asset, DisplayStatus } from '../types';
|
||||
export interface LatestErrorComponentProps {
|
||||
asset?: Asset;
|
||||
latestErrorMessage?: string;
|
||||
slidingDirection: 'down' | 'up';
|
||||
slidingPhase: SlideAnimationPhase;
|
||||
}
|
||||
|
||||
export const LatestErrorComponent: React.StatelessComponent<LatestErrorComponentProps> = props => {
|
||||
if (!props.latestErrorMessage) {
|
||||
return <div />;
|
||||
}
|
||||
return <SlidingError direction={props.slidingDirection} icon="😢" message={props.latestErrorMessage} />;
|
||||
return <SlidingError phase={props.slidingPhase} icon="😢" message={props.latestErrorMessage} />;
|
||||
};
|
||||
|
||||
interface ConnectedState {
|
||||
asset?: Asset;
|
||||
latestErrorMessage?: string;
|
||||
slidingDirection: 'down' | 'up';
|
||||
slidingPhase: SlideAnimationPhase;
|
||||
}
|
||||
export interface LatestErrorProps {}
|
||||
const mapStateToProps = (state: State, _ownProps: LatestErrorProps): ConnectedState => ({
|
||||
asset: state.selectedAsset,
|
||||
latestErrorMessage: state.latestErrorMessage,
|
||||
slidingDirection: state.latestErrorDisplayStatus === DisplayStatus.Present ? 'up' : 'down',
|
||||
slidingPhase: state.latestErrorDisplayStatus === DisplayStatus.Present ? 'slideIn' : 'slideOut',
|
||||
});
|
||||
|
||||
export const LatestError = connect(mapStateToProps)(LatestErrorComponent);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { errorFlasher } from '../util/error_flasher';
|
||||
export interface SelectedERC20AssetAmountInputProps {
|
||||
fontColor?: ColorOption;
|
||||
startingFontSizePx: number;
|
||||
onSymbolClick?: (asset?: ERC20Asset) => void;
|
||||
}
|
||||
|
||||
interface ConnectedState {
|
||||
|
||||
Reference in New Issue
Block a user