[instant] Viewport specific errors (#1228)
feat(instant): Shows different error animation based on viewport
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Keyframes } from 'styled-components';
|
||||
import { InterpolationValue } from 'styled-components';
|
||||
|
||||
import { media, OptionallyScreenSpecific, stylesForMedia } from '../../style/media';
|
||||
import { css, keyframes, styled } from '../../style/theme';
|
||||
|
||||
export interface TransitionInfo {
|
||||
@@ -51,30 +52,57 @@ export interface PositionAnimationSettings {
|
||||
right?: TransitionInfo;
|
||||
timingFunction: string;
|
||||
duration?: string;
|
||||
position?: string;
|
||||
}
|
||||
|
||||
export interface PositionAnimationProps extends PositionAnimationSettings {
|
||||
position: string;
|
||||
const generatePositionAnimationCss = (positionSettings: PositionAnimationSettings) => {
|
||||
return css`
|
||||
animation-name: ${slideKeyframeGenerator(
|
||||
positionSettings.position || 'relative',
|
||||
positionSettings.top,
|
||||
positionSettings.bottom,
|
||||
positionSettings.left,
|
||||
positionSettings.right,
|
||||
)};
|
||||
animation-duration: ${positionSettings.duration || '0.3s'};
|
||||
animation-timing-function: ${positionSettings.timingFunction};
|
||||
animation-delay: 0s;
|
||||
animation-iteration-count: 1;
|
||||
animation-fill-mode: forwards;
|
||||
position: ${positionSettings.position || 'relative'};
|
||||
width: 100%;
|
||||
`;
|
||||
};
|
||||
|
||||
export interface PositionAnimationProps {
|
||||
positionSettings: OptionallyScreenSpecific<PositionAnimationSettings>;
|
||||
zIndex?: OptionallyScreenSpecific<number>;
|
||||
}
|
||||
|
||||
const defaultAnimation = (positionSettings: OptionallyScreenSpecific<PositionAnimationSettings>) => {
|
||||
const bestDefault = 'default' in positionSettings ? positionSettings.default : positionSettings;
|
||||
return generatePositionAnimationCss(bestDefault);
|
||||
};
|
||||
const animationForSize = (
|
||||
positionSettings: OptionallyScreenSpecific<PositionAnimationSettings>,
|
||||
sizeKey: 'sm' | 'md' | 'lg',
|
||||
mediaFn: (...args: any[]) => InterpolationValue[],
|
||||
) => {
|
||||
// checking default makes sure we have a PositionAnimationSettings object
|
||||
// and then we check to see if we have a setting for the specific `sizeKey`
|
||||
const animationSettingsForSize = 'default' in positionSettings && positionSettings[sizeKey];
|
||||
return animationSettingsForSize && mediaFn`${generatePositionAnimationCss(animationSettingsForSize)}`;
|
||||
};
|
||||
|
||||
export const PositionAnimation =
|
||||
styled.div <
|
||||
PositionAnimationProps >
|
||||
`
|
||||
animation-name: ${props =>
|
||||
css`
|
||||
${slideKeyframeGenerator(props.position, props.top, props.bottom, props.left, props.right)};
|
||||
`};
|
||||
animation-duration: ${props => props.duration || '0.3s'};
|
||||
animation-timing-function: ${props => props.timingFunction};
|
||||
animation-delay: 0s;
|
||||
animation-iteration-count: 1;
|
||||
animation-fill-mode: forwards;
|
||||
position: ${props => props.position};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
&& {
|
||||
${props => props.zIndex && stylesForMedia<number>('z-index', props.zIndex)}
|
||||
${props => defaultAnimation(props.positionSettings)}
|
||||
${props => animationForSize(props.positionSettings, 'sm', media.small)}
|
||||
${props => animationForSize(props.positionSettings, 'md', media.medium)}
|
||||
${props => animationForSize(props.positionSettings, 'lg', media.large)}
|
||||
}
|
||||
`;
|
||||
|
||||
PositionAnimation.defaultProps = {
|
||||
position: 'relative',
|
||||
};
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { OptionallyScreenSpecific } from '../../style/media';
|
||||
|
||||
import { PositionAnimation, PositionAnimationSettings } from './position_animation';
|
||||
|
||||
export type SlideAnimationState = 'slidIn' | 'slidOut' | 'none';
|
||||
export interface SlideAnimationProps {
|
||||
position: string;
|
||||
animationState: SlideAnimationState;
|
||||
slideInSettings: PositionAnimationSettings;
|
||||
slideOutSettings: PositionAnimationSettings;
|
||||
slideInSettings: OptionallyScreenSpecific<PositionAnimationSettings>;
|
||||
slideOutSettings: OptionallyScreenSpecific<PositionAnimationSettings>;
|
||||
zIndex?: OptionallyScreenSpecific<number>;
|
||||
}
|
||||
|
||||
export const SlideAnimation: React.StatelessComponent<SlideAnimationProps> = props => {
|
||||
if (props.animationState === 'none') {
|
||||
return <React.Fragment>{props.children}</React.Fragment>;
|
||||
}
|
||||
const propsToUse = props.animationState === 'slidIn' ? props.slideInSettings : props.slideOutSettings;
|
||||
const positionSettings = props.animationState === 'slidIn' ? props.slideInSettings : props.slideOutSettings;
|
||||
return (
|
||||
<PositionAnimation position={props.position} {...propsToUse}>
|
||||
<PositionAnimation positionSettings={positionSettings} zIndex={props.zIndex}>
|
||||
{props.children}
|
||||
</PositionAnimation>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { ScreenSpecification } from '../style/media';
|
||||
import { ColorOption } from '../style/theme';
|
||||
import { zIndex } from '../style/z_index';
|
||||
|
||||
import { PositionAnimationSettings } from './animations/position_animation';
|
||||
import { SlideAnimation, SlideAnimationState } from './animations/slide_animation';
|
||||
@@ -21,6 +23,7 @@ export const Error: React.StatelessComponent<ErrorProps> = props => (
|
||||
backgroundColor={ColorOption.lightOrange}
|
||||
width="100%"
|
||||
borderRadius="6px"
|
||||
marginTop="10px"
|
||||
marginBottom="10px"
|
||||
>
|
||||
<Flex justify="flex-start">
|
||||
@@ -39,25 +42,51 @@ export interface SlidingErrorProps extends ErrorProps {
|
||||
}
|
||||
export const SlidingError: React.StatelessComponent<SlidingErrorProps> = props => {
|
||||
const slideAmount = '120px';
|
||||
const slideUpSettings: PositionAnimationSettings = {
|
||||
|
||||
const desktopSlideIn: PositionAnimationSettings = {
|
||||
timingFunction: 'ease-in',
|
||||
top: {
|
||||
from: slideAmount,
|
||||
to: '0px',
|
||||
},
|
||||
position: 'relative',
|
||||
};
|
||||
const slideDownSettings: PositionAnimationSettings = {
|
||||
const desktopSlideOut: PositionAnimationSettings = {
|
||||
timingFunction: 'cubic-bezier(0.25, 0.1, 0.25, 1)',
|
||||
top: {
|
||||
from: '0px',
|
||||
to: slideAmount,
|
||||
},
|
||||
position: 'relative',
|
||||
};
|
||||
|
||||
const mobileSlideIn: PositionAnimationSettings = {
|
||||
duration: '0.5s',
|
||||
timingFunction: 'ease-in',
|
||||
top: { from: '-120px', to: '0px' },
|
||||
position: 'fixed',
|
||||
};
|
||||
const moblieSlideOut: PositionAnimationSettings = {
|
||||
duration: '0.5s',
|
||||
timingFunction: 'ease-in',
|
||||
top: { from: '0px', to: '-120px' },
|
||||
position: 'fixed',
|
||||
};
|
||||
|
||||
const slideUpSettings: ScreenSpecification<PositionAnimationSettings> = {
|
||||
default: desktopSlideIn,
|
||||
sm: mobileSlideIn,
|
||||
};
|
||||
const slideOutSettings: ScreenSpecification<PositionAnimationSettings> = {
|
||||
default: desktopSlideOut,
|
||||
sm: moblieSlideOut,
|
||||
};
|
||||
|
||||
return (
|
||||
<SlideAnimation
|
||||
position="relative"
|
||||
slideInSettings={slideUpSettings}
|
||||
slideOutSettings={slideDownSettings}
|
||||
slideOutSettings={slideOutSettings}
|
||||
zIndex={{ sm: zIndex.errorPopUp, default: zIndex.errorPopBehind }}
|
||||
animationState={props.animationState}
|
||||
>
|
||||
<Error icon={props.icon} message={props.message} />
|
||||
|
||||
@@ -51,6 +51,7 @@ export const SlidingPanel: React.StatelessComponent<SlidingPanelProps> = props =
|
||||
from: slideAmount,
|
||||
to: '0px',
|
||||
},
|
||||
position: 'absolute',
|
||||
};
|
||||
const slideDownSettings: PositionAnimationSettings = {
|
||||
duration: '0.3s',
|
||||
@@ -59,10 +60,10 @@ export const SlidingPanel: React.StatelessComponent<SlidingPanelProps> = props =
|
||||
from: '0px',
|
||||
to: slideAmount,
|
||||
},
|
||||
position: 'absolute',
|
||||
};
|
||||
return (
|
||||
<SlideAnimation
|
||||
position="absolute"
|
||||
slideInSettings={slideUpSettings}
|
||||
slideOutSettings={slideDownSettings}
|
||||
animationState={animationState}
|
||||
|
||||
@@ -67,9 +67,9 @@ export const Container =
|
||||
${props => cssRuleIfExists(props, 'cursor')}
|
||||
${props => cssRuleIfExists(props, 'overflow')}
|
||||
${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')};
|
||||
${props => props.display && stylesForMedia('display', props.display)}
|
||||
${props => (props.width ? stylesForMedia('width', props.width) : '')}
|
||||
${props => (props.height ? stylesForMedia('height', props.height) : '')}
|
||||
${props => props.display && stylesForMedia<string>('display', props.display)}
|
||||
${props => props.width && stylesForMedia<string>('width', props.width)}
|
||||
${props => props.height && stylesForMedia<string>('height', props.height)}
|
||||
background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
|
||||
border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')};
|
||||
&:hover {
|
||||
|
||||
@@ -35,7 +35,7 @@ export class ZeroExInstantContainer extends React.Component<ZeroExInstantContain
|
||||
height={{ default: 'auto', sm: '100%' }}
|
||||
position="relative"
|
||||
>
|
||||
<Container zIndex={zIndex.errorPopup} position="relative">
|
||||
<Container position="relative">
|
||||
<LatestError />
|
||||
</Container>
|
||||
<Container
|
||||
|
||||
@@ -14,30 +14,38 @@ const generateMediaWrapper = (screenWidth: ScreenWidths) => (...args: any[]) =>
|
||||
}
|
||||
`;
|
||||
|
||||
const media = {
|
||||
export const media = {
|
||||
small: generateMediaWrapper(ScreenWidths.Sm),
|
||||
medium: generateMediaWrapper(ScreenWidths.Md),
|
||||
large: generateMediaWrapper(ScreenWidths.Lg),
|
||||
};
|
||||
|
||||
export interface ScreenSpecifications {
|
||||
default: string;
|
||||
sm?: string;
|
||||
md?: string;
|
||||
lg?: string;
|
||||
export interface ScreenSpecification<T> {
|
||||
default: T;
|
||||
sm?: T;
|
||||
md?: T;
|
||||
lg?: T;
|
||||
}
|
||||
export type MediaChoice = string | ScreenSpecifications;
|
||||
export const stylesForMedia = (cssPropertyName: string, choice: MediaChoice): InterpolationValue[] => {
|
||||
if (typeof choice === 'string') {
|
||||
export type OptionallyScreenSpecific<T> = T | ScreenSpecification<T>;
|
||||
export type MediaChoice = OptionallyScreenSpecific<string>;
|
||||
/**
|
||||
* Given a css property name and a OptionallyScreenSpecific value,
|
||||
* generates css properties with screen-specific viewport styling
|
||||
*/
|
||||
export function stylesForMedia<T extends string | number>(
|
||||
cssPropertyName: string,
|
||||
choice: OptionallyScreenSpecific<T>,
|
||||
): InterpolationValue[] {
|
||||
if (typeof choice === 'object') {
|
||||
return css`
|
||||
${cssPropertyName}: ${choice};
|
||||
`;
|
||||
}
|
||||
|
||||
return css`
|
||||
${cssPropertyName}: ${choice.default};
|
||||
${choice.lg && media.large`${cssPropertyName}: ${choice.lg}`}
|
||||
${choice.md && media.medium`${cssPropertyName}: ${choice.md}`}
|
||||
${choice.sm && media.small`${cssPropertyName}: ${choice.sm}`}
|
||||
`;
|
||||
};
|
||||
} else {
|
||||
return css`
|
||||
${cssPropertyName}: ${choice};
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const zIndex = {
|
||||
errorPopup: 1,
|
||||
errorPopBehind: 1,
|
||||
mainContainer: 2,
|
||||
panel: 3,
|
||||
errorPopUp: 4,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user