Skip to content

Commit

Permalink
feat: Add Universal Image component
Browse files Browse the repository at this point in the history
  • Loading branch information
codinsonn committed Apr 7, 2024
1 parent b76bce0 commit 4a3a2e4
Show file tree
Hide file tree
Showing 11 changed files with 486 additions and 2 deletions.
3 changes: 3 additions & 0 deletions apps/expo/app/(public)/images/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ImagesScreen from '@app/core/screens/ImagesScreen'

export default ImagesScreen
3 changes: 2 additions & 1 deletion apps/expo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"react-native": "0.73.2",
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
"react-native-web": "~0.19.6"
"react-native-web": "~0.19.6",
"expo-image": "~1.10.6"
},
"devDependencies": {
"@babel/core": "^7.19.3",
Expand Down
4 changes: 4 additions & 0 deletions apps/next/app/(public)/images/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
'use client'
import ImagesScreen from '@app/core/screens/ImagesScreen'

export default ImagesScreen
8 changes: 8 additions & 0 deletions apps/next/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ const nextConfig = withExpo({
experimental: {
forceSwcTransforms: true,
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "codinsonn.dev",
}
]
}
});

module.exports = nextConfig;
Binary file added features/app-core/assets/aetherspaceLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 90 additions & 0 deletions features/app-core/components/Image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Image as ExpoImage } from 'expo-image'
import { UniversalImageProps, UniversalImageMethods } from './Image.types'

/* --- <Image/> -------------------------------------------------------------------------------- */

const Image = (props: UniversalImageProps): JSX.Element => {
// Props
const {
/* - Universal - */
src,
alt,
width,
height,
style,
priority,
onError,
onLoadEnd,
/* - Split - */
expoPlaceholder,
/* - Next.js - */
onLoad,
fill,
/* - Expo - */
accessibilityLabel,
accessible,
allowDownscaling,
autoplay,
blurRadius,
cachePolicy,
contentFit,
contentPosition,
enableLiveTextInteraction,
focusable,
onLoadStart,
onProgress,
placeholderContentFit,
recyclingKey,
responsivePolicy,
} = props

// -- Overrides --

// @ts-ignore
const finalStyle = { width, height, ...style }
if (fill) finalStyle.height = '100%'
if (fill) finalStyle.width = '100%'

// -- Render --

return (
<ExpoImage
/* - Universal - */
source={src as any}
alt={alt || accessibilityLabel} // @ts-ignore
style={finalStyle}
priority={priority}
onError={onError}
onLoadEnd={onLoadEnd || onLoad as any}
/* - Split - */
placeholder={expoPlaceholder}
/* - Expo - */
accessibilityLabel={alt || accessibilityLabel}
accessible={accessible}
allowDownscaling={allowDownscaling}
autoplay={autoplay}
blurRadius={blurRadius}
cachePolicy={cachePolicy}
contentFit={contentFit}
contentPosition={contentPosition}
enableLiveTextInteraction={enableLiveTextInteraction}
focusable={focusable}
onLoadStart={onLoadStart}
onProgress={onProgress}
placeholderContentFit={placeholderContentFit}
recyclingKey={recyclingKey}
responsivePolicy={responsivePolicy}
/>
)
}

/* --- Static Methods -------------------------------------------------------------------------- */

Image.clearDiskCache = ExpoImage.clearDiskCache as UniversalImageMethods['clearDiskCache']
Image.clearMemoryCache = ExpoImage.clearMemoryCache as UniversalImageMethods['clearMemoryCache']
Image.getCachePathAsync = ExpoImage.getCachePathAsync as UniversalImageMethods['getCachePathAsync']
Image.prefetch = ExpoImage.prefetch as UniversalImageMethods['prefetch']

/* --- Exports --------------------------------------------------------------------------------- */

export { Image }
218 changes: 218 additions & 0 deletions features/app-core/components/Image.types.tsx

Large diffs are not rendered by default.

78 changes: 78 additions & 0 deletions features/app-core/components/Image.web.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import NextImage from 'next/image'
import { UniversalImageProps, UniversalImageMethods } from './Image.types'

/* --- <Image/> -------------------------------------------------------------------------------- */

const Image = (props: UniversalImageProps): JSX.Element => {
// Props
const {
/* - Universal - */
src,
alt,
width,
height,
style = {},
priority = 'normal',
onError,
onLoadEnd,
/* - Split - */
nextPlaceholder,
/* - Next.js - */
loader,
fill,
sizes,
quality,
onLoad,
loading,
blurDataURL,
unoptimized,
/* - Expo - */
accessibilityLabel,
contentFit,
} = props

// -- Overrides --

// @ts-ignore
const finalStyle = { width, height, ...style }
if (fill) finalStyle.height = '100%'
if (fill) finalStyle.width = '100%'
if (fill) finalStyle.objectFit = contentFit || 'cover'

// -- Render --

return (
<NextImage
/* - Universal - */
src={src as any}
alt={alt || accessibilityLabel}
width={width}
height={height} // @ts-ignore
style={finalStyle}
priority={priority === 'high'}
onError={onError as any}
onLoad={(onLoad || onLoadEnd) as any}
/* - Split - */
placeholder={nextPlaceholder}
/* - Next.js - */
loader={loader}
fill={fill}
sizes={sizes}
quality={quality}
loading={loading}
blurDataURL={blurDataURL}
unoptimized={unoptimized}
/>
)
}

/* --- Static Methods -------------------------------------------------------------------------- */

Image.clearDiskCache = (() => {}) as UniversalImageMethods['clearDiskCache']
Image.clearMemoryCache = (() => {}) as UniversalImageMethods['clearMemoryCache']
Image.getCachePathAsync = ((cacheKey: string) => {}) as UniversalImageMethods['getCachePathAsync'] // prettier-ignore
Image.prefetch = ((urls: string | string[], cachePolicy?: "memory" | "memory-disk") => {}) as UniversalImageMethods['prefetch'] // prettier-ignore

/* --- Exports --------------------------------------------------------------------------------- */

export { Image }
5 changes: 4 additions & 1 deletion features/app-core/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { Link } from '../navigation/Link'
import { Image } from '../components/Image'

/* --- <HomeScreen/> --------------------------------------------------------------------------- */

const HomeScreen = () => {
return (
<View style={styles.container}>
<Text style={styles.title}>Expo + Next.js app routing 👋</Text>
<Image src={require('../assets/aetherspaceLogo.png')} width={60} height={60} style={{ marginBottom: 12 }} />
<Text style={styles.title}>Expo + Next.js app routing 🚀</Text>
<Text style={styles.subtitle}>Open HomeScreen.tsx in features/app-core/screens to start working on your app</Text>
<Link href="/subpages/aetherspace" style={styles.link}>Test navigation</Link>
<Link href="/images" style={styles.link}>Test images</Link>
</View>
)
}
Expand Down
67 changes: 67 additions & 0 deletions features/app-core/screens/ImagesScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { Link } from '../navigation/Link'
import { Image } from '../components/Image'

/* --- <ImagesScreen/> --------------------------------------------------------------------------- */

const ImagesScreen = () => {
return (
<View style={styles.container}>
<Link
href="/"
style={{ ...styles.backButton, ...styles.link, textDecorationLine: 'none' }}
>
{`< Back`}
</Link>
{/* - 1 - */}
<Image src={require('../assets/aetherspaceLogo.png')} width={60} height={60} />
<Text style={styles.subtitle}>src=static-require | width: 60 | height: 60</Text>
{/* - 2 - */}
<Image src="https://codinsonn.dev/_next/image?url=%2Fimg%2FCodelyFansLogoPic160x160.jpeg&w=256&q=75" width={60} height={60} />
<Text style={styles.subtitle}>src=external-url | width: 60 | height: 60</Text>
{/* - 3 - */}
<View style={{ width: 60, height: 80, position: 'relative', borderColor: 'black', borderStyle: 'dashed', borderWidth: 1 }}>
<Image src={require('../assets/aetherspaceLogo.png')} fill />
</View>
<Text style={styles.subtitle}>wrapper=50x80, relative | fill=true</Text>
{/* - 4 - */}
<View style={{ width: 80, height: 60, position: 'relative', borderColor: 'black', borderStyle: 'dashed', borderWidth: 1 }}>
<Image src={require('../assets/aetherspaceLogo.png')} fill contentFit="contain" />
</View>
<Text style={styles.subtitle}>wrapper=80x60, relative | fill | contentFit=contain</Text>
</View>
)
}

/* --- Styles ---------------------------------------------------------------------------------- */

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
backButton: {
position: 'absolute',
top: 16,
left: 16,
},
subtitle: {
marginTop: 8,
marginBottom: 16,
fontSize: 16,
textAlign: 'center',
},
link: {
marginTop: 16,
fontSize: 16,
color: 'blue',
textAlign: 'center',
textDecorationLine: 'underline',
},
})

/* --- Exports --------------------------------------------------------------------------------- */

export default ImagesScreen
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4a3a2e4

Please sign in to comment.