From 6e58e34efbddea723fa85d072f8e71b08e6ca5d3 Mon Sep 17 00:00:00 2001 From: Alberto Chamorro Date: Mon, 1 Apr 2019 00:00:31 +0200 Subject: [PATCH] Adds the flipping card and the loading list stories --- storybook/stories/FlipCreditCard/CardView.tsx | 84 +++++++ .../stories/FlipCreditCard/CardViewBack.tsx | 54 ++++ .../stories/FlipCreditCard/CardViewFront.tsx | 126 ++++++++++ .../stories/FlipCreditCard/FlipOnGesture.tsx | 113 +++++++++ .../stories/FlipCreditCard/FlipOnTouch.tsx | 78 ++++++ .../FlipCreditCard/assets/card-chip.png | Bin 0 -> 15073 bytes .../assets/gray-world-map-md.png | Bin 0 -> 15082 bytes .../FlipCreditCard/assets/visa-icon.png | Bin 0 -> 27915 bytes storybook/stories/FlipCreditCard/common.ts | 64 +++++ storybook/stories/FlipCreditCard/style.ts | 117 +++++++++ .../stories/LoadingList/TransitionStrategy.ts | 196 +++++++++++++++ storybook/stories/LoadingList/index.tsx | 235 ++++++++++++++++++ storybook/stories/LoadingList/styles.ts | 59 +++++ storybook/stories/index.tsx | 40 ++- 14 files changed, 1158 insertions(+), 8 deletions(-) create mode 100644 storybook/stories/FlipCreditCard/CardView.tsx create mode 100644 storybook/stories/FlipCreditCard/CardViewBack.tsx create mode 100644 storybook/stories/FlipCreditCard/CardViewFront.tsx create mode 100644 storybook/stories/FlipCreditCard/FlipOnGesture.tsx create mode 100644 storybook/stories/FlipCreditCard/FlipOnTouch.tsx create mode 100644 storybook/stories/FlipCreditCard/assets/card-chip.png create mode 100644 storybook/stories/FlipCreditCard/assets/gray-world-map-md.png create mode 100644 storybook/stories/FlipCreditCard/assets/visa-icon.png create mode 100644 storybook/stories/FlipCreditCard/common.ts create mode 100644 storybook/stories/FlipCreditCard/style.ts create mode 100644 storybook/stories/LoadingList/TransitionStrategy.ts create mode 100644 storybook/stories/LoadingList/index.tsx create mode 100644 storybook/stories/LoadingList/styles.ts diff --git a/storybook/stories/FlipCreditCard/CardView.tsx b/storybook/stories/FlipCreditCard/CardView.tsx new file mode 100644 index 0000000..8638912 --- /dev/null +++ b/storybook/stories/FlipCreditCard/CardView.tsx @@ -0,0 +1,84 @@ +import React from "react"; +import { StyleProp, ViewStyle } from "react-native"; +import { + defaultCVCPlaceholder, + defaultExpiryPlaceholder, + defaultCardHolderNamePlaceholder, + defaultCreditCardNumberPlaceholder +} from "./common"; +import CreditCardViewFront from "./CardViewFront"; +import CreditCardViewBack from "./CardViewBack"; + +export interface CreditCardLabels { + expiryDate: string; +} + +export interface CreditCardFields { + creditCardName: string; + creditCardNumber: string; + expiryDate: string; + cvc: string; +} + +export interface Props { + style?: StyleProp; + scale?: number; + creditCardName?: string; + creditCardNumber?: string; + creditCardNumberLength?: number; + expiryDate?: string; + cvc?: string; + placeholders?: Partial; + localizedLabels?: Partial; + visibleFace: "front" | "back"; +} + +export default function CardView(props: Props) { + const { + creditCardName, + creditCardNumber, + cvc, + placeholders: optionalPlaceholders, + localizedLabels: optionalLabels, + expiryDate, + visibleFace, + style: externalStyle + } = props; + + const placeholders = { + cvc: defaultCVCPlaceholder, + expiryDate: defaultExpiryPlaceholder, + creditCardName: defaultCardHolderNamePlaceholder, + creditCardNumber: defaultCreditCardNumberPlaceholder, + ...optionalPlaceholders + }; + + const labels = { + expiryDate: "MM/YY", + ...optionalLabels + }; + + if (visibleFace === "front") { + return ( + + ); + } else { + return ( + + ); + } +} diff --git a/storybook/stories/FlipCreditCard/CardViewBack.tsx b/storybook/stories/FlipCreditCard/CardViewBack.tsx new file mode 100644 index 0000000..c6cf6b0 --- /dev/null +++ b/storybook/stories/FlipCreditCard/CardViewBack.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { View, Text, StyleProp, ViewStyle } from "react-native"; +import style, { + CardBackgroundColorStart, + CardBackgroundColorEnd +} from "./style"; +import { CardInformationContainer } from "./CardViewFront"; +import { LinearGradient } from "expo"; +import { addPlaceholderStyleIfNeeded } from "./common"; +import { CreditCardFields, CreditCardLabels } from "./CardView"; + +interface CreditCardViewBackProps { + cvc: string; + cardHolderName: string; + expiryDate: string; + style?: StyleProp; + placeholders: CreditCardFields; + labels: CreditCardLabels; +} + +export default function CreditCardViewBack(props: CreditCardViewBackProps) { + const { + cvc, + cardHolderName, + expiryDate, + style: externalStyle, + placeholders, + labels + } = props; + + return ( + + + + + {cvc || placeholders.cvc} + + + + + ); +} diff --git a/storybook/stories/FlipCreditCard/CardViewFront.tsx b/storybook/stories/FlipCreditCard/CardViewFront.tsx new file mode 100644 index 0000000..4c7f2a5 --- /dev/null +++ b/storybook/stories/FlipCreditCard/CardViewFront.tsx @@ -0,0 +1,126 @@ +import React from "react"; +import { + View, + Text, + Image, + StyleProp, + ViewStyle, + TextStyle +} from "react-native"; +import { LinearGradient } from "expo"; +import style, { + CardBackgroundColorEnd, + CardBackgroundColorStart +} from "./style"; +import { addPlaceholderStyleIfNeeded } from "./common"; +import { CreditCardFields, CreditCardLabels } from "./CardView"; + +interface CreditCardViewFrontProps { + cardHolderName: string; + number: string; + expiryDate: string; + style?: StyleProp; + placeholders: CreditCardFields; + labels: CreditCardLabels; +} + +interface CreditCardInfoContainerProps { + cardHolderName: string; + expiryDate: string; + textStyle?: StyleProp; + style?: StyleProp; + placeholders: CreditCardFields; + labels: CreditCardLabels; +} + +export function CardInformationContainer(props: CreditCardInfoContainerProps) { + const { + cardHolderName, + expiryDate, + textStyle, + style: externalStyle, + placeholders, + labels + } = props; + + return ( + + + {(cardHolderName || placeholders.creditCardName).toUpperCase()} + + + + {labels.expiryDate} + + + {expiryDate || placeholders.expiryDate} + + + + ); +} + +export default function CreditCardViewFront(props: CreditCardViewFrontProps) { + const { + cardHolderName, + number, + expiryDate, + placeholders, + style: externalStyle, + labels + } = props; + + return ( + + + + + + + {number || placeholders.creditCardNumber} + + + ); +} diff --git a/storybook/stories/FlipCreditCard/FlipOnGesture.tsx b/storybook/stories/FlipCreditCard/FlipOnGesture.tsx new file mode 100644 index 0000000..e2600e4 --- /dev/null +++ b/storybook/stories/FlipCreditCard/FlipOnGesture.tsx @@ -0,0 +1,113 @@ +import React from "react"; +import CardView, { Props as CardViewProps } from "./CardView"; +import { + Animated, + TouchableOpacity, + View, + PanResponder, + Dimensions +} from "react-native"; +import style from "./style"; + +interface Props extends CardViewProps {} + +type Face = "front" | "back"; + +interface State { + visibleFace: Face; +} + +const screenWidth = Dimensions.get("window").width; + +export default class FlipCreditCard extends React.PureComponent { + state = { visibleFace: "front" as Face }; + prevX = 0; + rotation = new Animated.Value(0); + currentRotation = 0; + + _panResponder = PanResponder.create({ + onStartShouldSetPanResponder: (evt, gestureState) => true, + onStartShouldSetPanResponderCapture: (evt, gestureState) => true, + onMoveShouldSetPanResponder: (evt, gestureState) => true, + onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, + + onPanResponderGrant: (evt, gestureState) => { + // The gesture has started. Show visual feedback so the user knows + // what is happening! + // gestureState.d{x,y} will be set to zero now + this.prevX = 0; + }, + onPanResponderMove: (evt, gestureState) => { + // The most recent move distance is gestureState.move{X,Y} + // The accumulated gesture distance since becoming responder is + // gestureState.d{x,y} + this.currentRotation = Math.max( + 0, + Math.min( + 1, + this.currentRotation + (gestureState.dx - this.prevX) / screenWidth + ) + ); + + this.prevX = gestureState.dx; + + if (this.currentRotation >= 0.5) { + this.setState({ visibleFace: "back" }); + } else { + this.setState({ visibleFace: "front" }); + } + + this.rotation.setValue(this.currentRotation); + }, + onShouldBlockNativeResponder: (evt, gestureState) => { + // Returns whether this component should block native components from becoming the JS + // responder. Returns true by default. Is currently only supported on android. + return true; + }, + onPanResponderEnd: (evt, gestureState) => { + this.prevX = 0; + Animated.spring(this.rotation, { + friction: 4, + toValue: this.currentRotation >= 0.5 ? 1 : 0 + }).start(); + } + }); + + nextSide: (face: Face) => Face = face => { + switch (face) { + case "front": + return "back"; + case "back": + return "front"; + } + }; + + render() { + const { scale = 1 } = this.props; + const { visibleFace } = this.state; + + const rotation = this.rotation.interpolate({ + inputRange: [0, 1], + outputRange: ["0deg", "180deg"] + }); + + return ( + + + + + + ); + } +} diff --git a/storybook/stories/FlipCreditCard/FlipOnTouch.tsx b/storybook/stories/FlipCreditCard/FlipOnTouch.tsx new file mode 100644 index 0000000..1dca6b1 --- /dev/null +++ b/storybook/stories/FlipCreditCard/FlipOnTouch.tsx @@ -0,0 +1,78 @@ +import React from "react"; +import CardView, { Props as CardViewProps } from "./CardView"; +import { Animated, TouchableOpacity } from "react-native"; +import style from "./style"; + +interface Props extends CardViewProps {} + +type Face = "front" | "back"; + +interface State { + visibleFace: Face; +} + +const animationDuration = 200; + +export default class FlipCreditCard extends React.PureComponent { + state = { visibleFace: "front" as Face }; + rotation = new Animated.Value(0); + + nextSide: (face: Face) => Face = face => { + switch (face) { + case "front": + return "back"; + case "back": + return "front"; + } + }; + + startFlipping = () => { + Animated.timing(this.rotation, { + toValue: 1, + duration: animationDuration / 2, + useNativeDriver: true + }).start(() => { + this.setState( + prevState => ({ + visibleFace: this.nextSide(prevState.visibleFace) + }), + () => this.endFlipping() + ); + }); + }; + + endFlipping = () => { + Animated.timing(this.rotation, { + toValue: 0, + duration: animationDuration / 2, + useNativeDriver: true + }).start(); + }; + + flipSides = () => { + this.startFlipping(); + }; + + render() { + const { scale = 1 } = this.props; + const { visibleFace } = this.state; + + const rotation = this.rotation.interpolate({ + inputRange: [0, 1], + outputRange: ["0deg", "90deg"] + }); + + return ( + + + + + + ); + } +} diff --git a/storybook/stories/FlipCreditCard/assets/card-chip.png b/storybook/stories/FlipCreditCard/assets/card-chip.png new file mode 100644 index 0000000000000000000000000000000000000000..ff5dac05708c566a86b6d9d804755aad1b2abe3c GIT binary patch literal 15073 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 M07*qoM6N<$g4cv}@c;k- literal 0 HcmV?d00001 diff --git a/storybook/stories/FlipCreditCard/assets/visa-icon.png b/storybook/stories/FlipCreditCard/assets/visa-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3be1120bec9bc82b17989db64c292b3a7fdfa24a GIT binary patch literal 27915 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 literal 0 HcmV?d00001 diff --git a/storybook/stories/FlipCreditCard/common.ts b/storybook/stories/FlipCreditCard/common.ts new file mode 100644 index 0000000..71cf625 --- /dev/null +++ b/storybook/stories/FlipCreditCard/common.ts @@ -0,0 +1,64 @@ +import style from "./style"; + +export const defaultCreditCardNumberLength = 16; +export const defaultCVCPlaceholder = "---"; +export const defaultExpiryPlaceholder = "--/--"; +export const defaultCardHolderNamePlaceholder = "Card holder's name"; +export const defaultCreditCardNumberPlaceholder = "---- ---- ---- ----"; + +export function addPlaceholderStyleIfNeeded(value: any) { + if (value) { + return; + } + + return style.placeholderTextStyle; +} + +const placeholder = "---- ---- ---- ----"; +const digitsGroupLength = 4; + +function formatCreditCardString(number: string) { + return number + .split("") + .reduce( + (prev, curr, index) => + `${prev}${(index % digitsGroupLength === 0 && " ") || ""}${curr}`, + "" + ); +} + +function paddedNumber(value: string | number, length = 2) { + return `0${value}`.substr(-length); +} + +function twoDigitsDateString(date: string) { + if (!date) { + return "--"; + } + + return `${paddedNumber(date)}`.substr(0, 2); +} + +function assertValidDateFormat(expiryDate: string) { + if (expiryDate.length > 2 && expiryDate.indexOf("/") < 0) { + throw new Error("The date must have a format of mm/yy"); + } +} + +export function formatExpiryDateString(expiryDate: string) { + assertValidDateFormat(expiryDate); + + const [month, year] = expiryDate.split("/"); + const monthString = twoDigitsDateString(month); + const yearString = twoDigitsDateString(year); + + return `${monthString}/${yearString}`; +} + +export function creditCardStringWithNumber(number: string) { + const normalizedNumber = `${number.replace(/\s/g, "")}`; + + return `${normalizedNumber}${placeholder.substring( + 16 - normalizedNumber.length + )}`; +} diff --git a/storybook/stories/FlipCreditCard/style.ts b/storybook/stories/FlipCreditCard/style.ts new file mode 100644 index 0000000..fc5b049 --- /dev/null +++ b/storybook/stories/FlipCreditCard/style.ts @@ -0,0 +1,117 @@ +import { StyleSheet, Platform } from "react-native"; + +const CardSize = { width: 400, height: 253.33 }; +export const CardBackgroundColorStart = "#6833d2"; +export const CardBackgroundColorEnd = "#3f1f7e"; + +export default StyleSheet.create({ + container: { + width: "100%", + alignItems: "center", + backfaceVisibility: "visible" + }, + cardView: { + ...CardSize, + borderRadius: 15, + backgroundColor: CardBackgroundColorStart, + marginBottom: 27, + position: "relative" + }, + placeholderTextStyle: { + opacity: 0.7 + }, + cardViewFrontBackground: { + width: "100%", + height: "100%", + opacity: 0.1 + }, + cardViewFrontChip: { + position: "absolute", + width: 60, + height: 60, + top: 33, + left: 23 + }, + cardViewFrontBrandIcon: { + position: "absolute", + width: 80, + height: 53, + resizeMode: "contain", + right: 23, + top: 23 + }, + cardInformationContainer: { + position: "absolute", + bottom: 3, + flexDirection: "row", + alignItems: "center", + padding: 23 + }, + cardInformationContainerReverse: { + transform: [{ scaleX: -1 }], + opacity: 0.3 + }, + cardInformationContainerReverseText: { + color: "rgb(0, 0, 0)", + shadowRadius: 0, + fontWeight: "100" + }, + cardViewText: { + color: "rgba(255, 255, 255, 0.8)", + fontWeight: "bold", + fontFamily: Platform.select({ ios: "Courier", android: "monospace" }), + fontSize: 23, + shadowColor: "black", + textShadowColor: "black", + textShadowRadius: 1, + textShadowOffset: { width: 1, height: 1 } + }, + cardViewFrontCardNameText: { + flex: 1 + }, + cardViewFrontNumberText: { + position: "absolute", + top: 113, + left: 0, + fontSize: 30, + width: "100%", + textAlign: "center", + letterSpacing: 1.5 + }, + cardViewFrontExpiration: { + alignItems: "center", + position: "relative", + fontSize: 28 + }, + cardViewFrontExpiryLabel: { + fontSize: 16, + opacity: 0.7, + marginBottom: 5, + textShadowRadius: 0, + textShadowOffset: { width: 0, height: 0 } + }, + cardViewBackStrip: { + top: 37, + height: 48, + width: "100%", + backgroundColor: "black" + }, + cardViewBackCVCContainer: { + position: "absolute", + top: 100, + alignItems: "flex-end", + justifyContent: "center", + paddingRight: 16, + marginLeft: 16, + backgroundColor: "white", + width: "70%", + height: 29 + }, + cardViewBackCVCText: { + fontSize: 21, + fontStyle: "italic", + fontFamily: "Courier", + textAlign: "right", + fontWeight: "bold" + } +}); diff --git a/storybook/stories/LoadingList/TransitionStrategy.ts b/storybook/stories/LoadingList/TransitionStrategy.ts new file mode 100644 index 0000000..bbad8ca --- /dev/null +++ b/storybook/stories/LoadingList/TransitionStrategy.ts @@ -0,0 +1,196 @@ +import { Dimensions, Animated } from "react-native"; + +export interface TransitionStrategy { + getStyles(animatedProperty: Animated.Value): { [key: string]: any }; + configuredAnimatedValue(): Animated.Value; + targetValue(): number; + getName(): string; +} + +export const numberOfTransitions = 3; +export enum Transition { + SlideFromRight, + SlideFromBottom, + FallDown, + SlideFromLeft, + ScalingDown, + Flip +} + +export const AllTransitions = [ + Transition.SlideFromRight, + Transition.SlideFromBottom, + Transition.FallDown, + Transition.SlideFromLeft, + Transition.ScalingDown, + Transition.Flip +]; + +const screenDimensions = Dimensions.get("window"); + +class SlideFromRightTransitionStrategy implements TransitionStrategy { + getName(): string { + return "Slide from Right"; + } + + targetValue(): number { + return 0; + } + + configuredAnimatedValue(): Animated.Value { + return new Animated.Value(screenDimensions.width); + } + + getStyles(animatedProperty: Animated.Value): { [key: string]: any } { + const opacity = animatedProperty.interpolate({ + inputRange: [0, screenDimensions.width], + outputRange: [1, 0] + }); + + return { + transform: [{ translateX: animatedProperty }], + opacity + }; + } +} + +class SlideFromLeftTransitionStrategy implements TransitionStrategy { + getName(): string { + return "Slide from Left"; + } + + targetValue(): number { + return 0; + } + + configuredAnimatedValue(): Animated.Value { + return new Animated.Value(-screenDimensions.width); + } + + getStyles(animatedProperty: Animated.Value): { [key: string]: any } { + const opacity = animatedProperty.interpolate({ + inputRange: [-screenDimensions.width, 0], + outputRange: [0, 1] + }); + + return { + transform: [{ translateX: animatedProperty }], + opacity + }; + } +} + +class ScalingDownTransitionStrategy implements TransitionStrategy { + getName(): string { + return "Scaling"; + } + + targetValue(): number { + return 1; + } + + configuredAnimatedValue(): Animated.Value { + return new Animated.Value(5); + } + + getStyles(animatedProperty: Animated.Value): { [key: string]: any } { + const opacity = animatedProperty.interpolate({ + inputRange: [1, 5], + outputRange: [1, 0] + }); + + return { + transform: [{ scale: animatedProperty }], + opacity + }; + } +} + +class FlipTransitionStrategy implements TransitionStrategy { + getName(): string { + return "Flip"; + } + + targetValue(): number { + return 0; + } + + configuredAnimatedValue(): Animated.Value { + return new Animated.Value(1); + } + + getStyles(animatedProperty: Animated.Value): { [key: string]: any } { + const spin = animatedProperty.interpolate({ + inputRange: [0, 1], + outputRange: ["0deg", "270deg"] + }); + + return { + transform: [{ rotateY: spin }] + }; + } +} + +class FallDownTransitionStrategy implements TransitionStrategy { + getName(): string { + return "Fall Down"; + } + targetValue(): number { + return 1; + } + + configuredAnimatedValue(): Animated.Value { + return new Animated.Value(0); + } + + getStyles(animatedProperty: Animated.Value): { [key: string]: any } { + return { + opacity: animatedProperty + }; + } +} + +class SlideFromBottomTransitionStrategy implements TransitionStrategy { + getName(): string { + return "Slide from Bottom"; + } + + targetValue(): number { + return 0; + } + + configuredAnimatedValue(): Animated.Value { + return new Animated.Value(screenDimensions.height); + } + + getStyles(animatedProperty: Animated.Value): { [key: string]: any } { + const opacity = animatedProperty.interpolate({ + inputRange: [0, screenDimensions.width], + outputRange: [1, 0] + }); + + return { + transform: [{ translateY: animatedProperty }], + opacity + }; + } +} + +export default function getTransitionStrategy( + transition: Transition +): TransitionStrategy { + switch (transition) { + case Transition.FallDown: + return new FallDownTransitionStrategy(); + case Transition.SlideFromBottom: + return new SlideFromBottomTransitionStrategy(); + case Transition.SlideFromRight: + return new SlideFromRightTransitionStrategy(); + case Transition.SlideFromLeft: + return new SlideFromLeftTransitionStrategy(); + case Transition.ScalingDown: + return new ScalingDownTransitionStrategy(); + case Transition.Flip: + return new FlipTransitionStrategy(); + } +} diff --git a/storybook/stories/LoadingList/index.tsx b/storybook/stories/LoadingList/index.tsx new file mode 100644 index 0000000..5b25cfb --- /dev/null +++ b/storybook/stories/LoadingList/index.tsx @@ -0,0 +1,235 @@ +import React from "react"; +import { Animated, FlatList, View, TouchableOpacity, Text } from "react-native"; +import getTransitionStrategy, { + Transition, + TransitionStrategy, + AllTransitions +} from "./TransitionStrategy"; +import styles from "./styles"; + +/** + * Genearates a random string. + * + * The generated string has high probabilites of being unique, + * even though uniqueness is not ensured. + * + * It is used to generate a random key so a flat list + * can differentiate between Views and work its recycling magic. + * + * @returns {string} An _unique_ random key + */ +function generateRandomKey(): string { + return Math.random() + .toString(36) + .substring(7); +} + +interface LoadingListProps {} +interface LoadingListState { + items: ItemData[]; + selectedTransition: Transition; +} + +interface ItemData { + key: string; +} + +const defaultTransition = Transition.SlideFromRight; +const animationDelay = 150; + +/** + * Returns an array of elements + * + * It is used as a building block for objects. This function + * is very usueful when using in conjunction with `map`. + * + * @example + * sequence(10).map(() => ) + * + * @param {number} numberOfElements The number of elements contained in the array. + * @returns {string[]} An array of strings. + */ +function sequence(numberOfElements: number): string[] { + return "a".repeat(numberOfElements).split(""); +} + +/** + * React component that renders a list of items, with a transition effect. + * + * @export + * @class LoadingList + * @extends {React.PureComponent} + */ +export default class LoadingList extends React.PureComponent< + LoadingListProps, + LoadingListState +> { + state = { items: [], selectedTransition: defaultTransition }; + initialNumberOfItems = 10; + + constructor(props: LoadingListProps) { + super(props); + + this.transitionStrategy = getTransitionStrategy(defaultTransition); + this.resetAnimation(); + + this.state = { + items: sequence(this.initialNumberOfItems).map(() => ({ + key: generateRandomKey() + })), + selectedTransition: defaultTransition + }; + } + + animatedProperty: Animated.Value[]; + transitionStrategy: TransitionStrategy; + + componentDidMount() { + this.resetAnimation(); + } + + /** + * Run the current animation. + * + * @memberof LoadingList + */ + reload = () => { + this.resetAnimation(); + this.forceUpdate(); + }; + + /** + * Configures the animation to be run. + * + * @memberof LoadingList + */ + resetAnimation = () => { + this.resetAnimatedValues(); + this.configureAnimation(); + }; + + /** + * As part of the animation configuration, the animated values are set to their initial values. + * + * @memberof LoadingList + */ + resetAnimatedValues = () => { + this.animatedProperty = sequence(this.initialNumberOfItems).map(() => + this.transitionStrategy.configuredAnimatedValue() + ); + }; + + /** + * Configures an starts the animation. + * + * @memberof LoadingList + */ + configureAnimation = () => { + Animated.stagger( + animationDelay, + this.animatedProperty.map(value => + Animated.spring(value, { + toValue: this.transitionStrategy.targetValue(), + useNativeDriver: true + }) + ) + ).start(); + }; + + /** + * Forces the FlatList to be re-initialized. + * + * This method is needed becuase of the view recycling mechanism + * of the FlatList. + * + * @memberof LoadingList + */ + forceUpdate() { + const { items } = this.state; + + this.setState(() => ({ items: [] })); + + setTimeout(() => { + this.setState({ items }); + }); + } + + /** + * Selects the next transition on the list. + * + * @param {Transition} currentTransition + * @returns + * @memberof LoadingList + */ + getNextTransition(currentTransition: Transition) { + const currentIndex = AllTransitions.findIndex( + value => value === currentTransition + ); + + const nextIndex = (currentIndex + 1) % AllTransitions.length; + return AllTransitions[nextIndex]; + } + + showNextTransition = () => { + const { selectedTransition } = this.state; + + const nextTransition = this.getNextTransition(selectedTransition); + this.transitionStrategy = getTransitionStrategy(nextTransition); + + this.setState( + { + selectedTransition: nextTransition + }, + () => this.reload() + ); + }; + + keyExtractor(item: ItemData) { + return item.key; + } + + renderItem = ({ item, index }: { item: ItemData; index: number }) => { + if (index < this.initialNumberOfItems) { + return ( + + ); + } + + return ; + }; + + render() { + const { items } = this.state; + + return ( + + + + + {this.transitionStrategy.getName()} + + + + + RELOAD + + + + + ); + } +} diff --git a/storybook/stories/LoadingList/styles.ts b/storybook/stories/LoadingList/styles.ts new file mode 100644 index 0000000..ec80ede --- /dev/null +++ b/storybook/stories/LoadingList/styles.ts @@ -0,0 +1,59 @@ +import { StyleSheet } from "react-native"; + +export default StyleSheet.create({ + box: { + backgroundColor: "white", + height: 70, + marginBottom: 7 + }, + container: { + backgroundColor: "rgb(207, 215, 219)", + }, + header: { + backgroundColor: "rgb(93, 122, 137)", + flexDirection: "row", + padding: 20, + marginBottom: 10, + alignItems: "center", + shadowColor: "black", + shadowOffset: { width: 0, height: 3 }, + shadowRadius: 5, + shadowOpacity: 0.5 + }, + layoutPicker: { + flexDirection: "row", + alignItems: "center", + flex: 1, + marginRight: 20, + padding: 20 + }, + layoutPickerText: { + color: "#ccc", + fontSize: 18, + fontWeight: "bold", + flex: 1 + }, + reloadButton: { + backgroundColor: "rgb(255, 188, 0)", + paddingHorizontal: 20, + paddingVertical: 15 + }, + reloadButtonText: { + color: "white", + fontWeight: "bold", + fontSize: 15 + }, + itemsContainer: { + paddingHorizontal: 10 + }, + dropDownArrow: { + width: 0, + height: 0, + borderLeftWidth: 6, + borderLeftColor: "transparent", + borderRightWidth: 6, + borderRightColor: "transparent", + borderTopWidth: 6, + borderTopColor: "#ccc" + } +}); diff --git a/storybook/stories/index.tsx b/storybook/stories/index.tsx index f9d41ac..26c0fc6 100644 --- a/storybook/stories/index.tsx +++ b/storybook/stories/index.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { View, Dimensions, Text } from "react-native"; +import { View, Dimensions, Text, SafeAreaView } from "react-native"; import { storiesOf } from "@storybook/react-native"; import TopToBottomRowAnimation from "./TopToBottomRowAnimation"; import LeftToRightRowAnimation from "./LeftToRightRowAnimation"; @@ -9,14 +9,13 @@ import AnimatedBar from "./AnimatedBar"; import { seq } from "../../components/utils"; import GrowAndDisappear from "./GrowAndDisappear"; import Banner from "./Banner"; +import GestureCardView from './FlipCreditCard/FlipOnGesture'; +import FlipOnTapCardView from './FlipCreditCard/FlipOnTouch'; +import LoadingList from './LoadingList'; -storiesOf("Row Animation", module). - add("Appear from the left", () => ( - - )) - .add("Appear from the top", () => ( - - )); +storiesOf("Row Animation", module) + .add("Appear from the left", () => ) + .add("Appear from the top", () => ); const dimensions = Dimensions.get("window"); @@ -50,4 +49,29 @@ storiesOf("Simple animations", module) Some text + )) + .add("Flipping credit card (with gestures)", () => ( + + + + )).add("Flipping credit card (flip on tap)", () => ( + + + + )).add("Loading list transitions", () => ( + ));