diff --git a/src/components/Image/Image.tsx b/src/components/Image/Image.tsx new file mode 100644 index 0000000..2303fbe --- /dev/null +++ b/src/components/Image/Image.tsx @@ -0,0 +1,88 @@ +import React, { useCallback } from "react"; +import { + Animated, + Image as RNImage, + ImageLoadEventData, + StyleSheet, + View, + Text, + NativeSyntheticEvent +} from "react-native"; + +import { ImageProps } from "./Image.types"; + +const Image: React.FC = ({ + PlaceholderContent, + ImageComponent = RNImage, + transition, + transitionDuration, + onLoad, + ...props +}) => { + const placeholderOpacity = React.useRef(new Animated.Value(1)); + + const onLoadHandler = useCallback( + (event: NativeSyntheticEvent) => { + if (transition) { + Animated.timing(placeholderOpacity.current, { + toValue: 0, + duration: transitionDuration, + useNativeDriver: true + }).start(); + } else { + placeholderOpacity.current.setValue(0); + } + onLoad?.(event); + }, + [transition, transitionDuration, onLoad] + ); + + const hasImage = Boolean(props.source); + + return ( + + + + + {React.isValidElement(PlaceholderContent) + ? PlaceholderContent + : PlaceholderContent && {PlaceholderContent}} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: "transparent", + height: "100%", + width: "100%", + position: "relative", + overflow: "hidden" + }, + placeholder: { + backgroundColor: "white", + alignItems: "center", + width: "100%", + height: "100%", + justifyContent: "center" + } +}); + +export default Image; diff --git a/src/components/Image/Image.types.ts b/src/components/Image/Image.types.ts new file mode 100644 index 0000000..9f640af --- /dev/null +++ b/src/components/Image/Image.types.ts @@ -0,0 +1,9 @@ +import React from "react"; +import { ImageProps as RNImageProps } from "react-native"; + +export interface ImageProps extends RNImageProps { + PlaceholderContent?: React.ReactElement; + ImageComponent?: typeof React.Component; + transition?: boolean; + transitionDuration?: number; +} diff --git a/src/components/Image/index.ts b/src/components/Image/index.ts new file mode 100644 index 0000000..c6cd0de --- /dev/null +++ b/src/components/Image/index.ts @@ -0,0 +1 @@ +export { default } from "./Image"; diff --git a/storybook/stories/Image/Image.stories.tsx b/storybook/stories/Image/Image.stories.tsx new file mode 100644 index 0000000..e21efb4 --- /dev/null +++ b/storybook/stories/Image/Image.stories.tsx @@ -0,0 +1,34 @@ +// TextInput.stories.js + +import React from "react"; + +import { storiesOf } from "@storybook/react-native"; + +import { ActivityIndicator, View } from "react-native"; + +import Image from "../../../src/components/Image/Image"; + +storiesOf("Image", module) + .addDecorator((getStory) => ( + + {getStory()} + + )) + .add("Standard", () => ( + + } + /> + + )) + .add("With Fade Transition", () => ( + + } + transition={true} + transitionDuration={500} + /> + + )); diff --git a/storybook/stories/index.js b/storybook/stories/index.js index 6aea5e8..4e26318 100644 --- a/storybook/stories/index.js +++ b/storybook/stories/index.js @@ -5,3 +5,4 @@ import "./TextInput/TextInput.stories"; import "./Card/Card.stories"; import "./Text/Text.stories"; import "./Avatar/Avatar.stories"; +import "./Image/Image.stories";