import { noop } from 'lodash' import React, { useState } from 'react' import { AnimatableNumericValue, DimensionValue, ImageStyle, Image as ReactNativeImage, ImageProps as ReactNativeImageProps, StyleSheet, View, ViewStyle, } from 'react-native' import Animated, { AnimatedStyle } from 'react-native-reanimated' import { useTheme } from '../../hooks' import { Spinner } from './spinner' // ================================= // ====== Component ================ // ================================= export interface ImageProps extends ReactNativeImageProps { /** * Describes the image to screen-readers and marks the image as `accessible`. * Passing an empty string will hide the image from screen-readers. */ alt: string /** * Shows the image's loading spinner right away. Enabled by default. */ defaultLoading?: boolean /** * Externally controls the loading state of the image. */ loading?: boolean /** * Hide the loading spinner, regardless of the loading state. */ hideLoader?: boolean /** * Size of the loading spinner. */ loaderSize?: number /** * Style object for the preload background. */ loadingBackgroundStyles?: ViewStyle /** * Style the outer View of the image. */ wrapperStyle?: ViewStyle /** * Style the image if animated. */ animatedImageStyle?: AnimatedStyle } export function Image({ source, onLoad = noop, defaultLoading = true, loading = false, hideLoader = false, loaderSize = 24, loadingBackgroundStyles, style = {}, wrapperStyle = {}, alt, animatedImageStyle = {}, ...props }: ImageProps) { const [isImageLoading, setIsImageLoading] = useState(defaultLoading) const imageStyles = StyleSheet.flatten(style) const { width = '100%', height = '100%', borderRadius = 0 } = imageStyles || {} const styles = useStyles({ width, height, borderRadius }) const handleOnLoad = (event: any) => { setIsImageLoading(false) onLoad?.(event) } const handleOnError = () => { setIsImageLoading(false) } const isLoading = isImageLoading || loading const ImageComponent = animatedImageStyle ? Animated.Image : ReactNativeImage return ( {!hideLoader && isLoading && ( )} ) } // ================================= // ====== Styles =================== // ================================= interface Styles { width: DimensionValue height: DimensionValue borderRadius: AnimatableNumericValue | string } const useStyles = ({ width, height, borderRadius }: Styles) => { const { colors } = useTheme() return StyleSheet.create({ loadingBackground: { position: 'absolute', top: 0, left: 0, backgroundColor: colors.fillColorNeutral070, borderRadius, width, height, }, image: { backgroundColor: colors.fillColorNeutral070, width, height, }, spinner: { width: '100%', height: '100%', justifyContent: 'center', alignItems: 'center', position: 'absolute', }, }) }