import React from 'react' import { isWeb } from '@tamagui/constants' import type { GetProps, RadiusTokens, SizeTokens, ViewProps, ThemeValueFallback, } from '@tamagui/web' import { styled, usePropsAndStyle } from '@tamagui/web' import type { FC } from 'react' import { Image as RNImage } from 'react-native' const StyledImage = styled(RNImage, { name: 'Image', }) type StyledImageProps = Omit, 'borderRadius'> & { borderRadius?: RadiusTokens } type BaseProps = Omit< StyledImageProps, 'width' | 'height' | 'style' | 'onLayout' | 'resizeMode' > & { width?: string | number | SizeTokens | ThemeValueFallback height?: string | number | SizeTokens | ThemeValueFallback /** * @deprecated use `source` instead to disambiguate width/height style from width/height of the actual image */ src?: string | StyledImageProps['source'] /** @deprecated use objectFit instead */ resizeMode?: StyledImageProps['resizeMode'] objectFit?: React.CSSProperties['objectFit'] } export type ImageProps = BaseProps & Omit type RNImageType = typeof RNImage type ImageType = FC & { getSize: RNImageType['getSize'] getSizeWithHeaders: RNImageType['getSizeWithHeaders'] prefetch: RNImageType['prefetch'] prefetchWithMetadata: RNImageType['prefetchWithMetadata'] abortPrefetch: RNImageType['abortPrefetch'] queryCache: RNImageType['queryCache'] } let hasWarned = false /** * @summary An image is a component that displays an image. * @see — Docs https://tamagui.dev/ui/image */ export const Image = StyledImage.styleable((inProps, ref) => { const [props, style] = usePropsAndStyle(inProps) const { src, source, objectFit, ...rest } = props if (process.env.NODE_ENV === 'development') { if (typeof src === 'string') { if ( (typeof props.width === 'string' && props.width[0] !== '$') || (typeof props.height === 'string' && props.height[0] !== '$') ) { if (!hasWarned) { hasWarned = true console.warn( `React Native expects a numerical width/height. If you want to use a percent you must define the "source" prop with width, height, and uri.` ) } } } } let finalSource = typeof src === 'string' ? { uri: src, ...(isWeb && { width: props.width || style?.width, height: props.height || style?.height, }), } : (source ?? src) if (finalSource && typeof finalSource === 'object') { if (process.env.TAMAGUI_TARGET === 'native') { // fix: normalize import style images if (!Array.isArray(finalSource)) { if (typeof finalSource.uri === 'number') { finalSource = finalSource.uri if (source && typeof source === 'object' && !Array.isArray(source)) { // @ts-ignore style.width ??= source.width // @ts-ignore style.height ??= source.height } } } } if (process.env.NODE_ENV === 'development' && process.env.TAMAGUI_IMAGE_CHECK_ERROR) { // lets add an onload timeout and warning if it fails to load in console React.useEffect(() => { async function run() { if (typeof src === 'string') { try { await fetch(src).then((res) => res.text()) } catch (err) { console.error(`Error loading image: ${src}`, { props }) } } } run() }, [src]) } // require compat across native/web if (finalSource['default']) { finalSource = finalSource['default'] } } // must set defaultSource to allow SSR, default it to the same as src return ( ) }) as any as ImageType Image.getSize = RNImage.getSize Image.getSizeWithHeaders = RNImage.getSizeWithHeaders Image.prefetch = RNImage.prefetch Image.prefetchWithMetadata = RNImage.prefetchWithMetadata Image.abortPrefetch = RNImage.abortPrefetch Image.queryCache = RNImage.queryCache