Skip to content

Commit 4a3a2e4

Browse files
committed
feat: Add Universal Image component
1 parent b76bce0 commit 4a3a2e4

File tree

11 files changed

+486
-2
lines changed

11 files changed

+486
-2
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import ImagesScreen from '@app/core/screens/ImagesScreen'
2+
3+
export default ImagesScreen

apps/expo/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"react-native": "0.73.2",
1616
"react-native-safe-area-context": "4.8.2",
1717
"react-native-screens": "~3.29.0",
18-
"react-native-web": "~0.19.6"
18+
"react-native-web": "~0.19.6",
19+
"expo-image": "~1.10.6"
1920
},
2021
"devDependencies": {
2122
"@babel/core": "^7.19.3",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
'use client'
2+
import ImagesScreen from '@app/core/screens/ImagesScreen'
3+
4+
export default ImagesScreen

apps/next/next.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ const nextConfig = withExpo({
1313
experimental: {
1414
forceSwcTransforms: true,
1515
},
16+
images: {
17+
remotePatterns: [
18+
{
19+
protocol: "https",
20+
hostname: "codinsonn.dev",
21+
}
22+
]
23+
}
1624
});
1725

1826
module.exports = nextConfig;
53.1 KB
Loading
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Image as ExpoImage } from 'expo-image'
2+
import { UniversalImageProps, UniversalImageMethods } from './Image.types'
3+
4+
/* --- <Image/> -------------------------------------------------------------------------------- */
5+
6+
const Image = (props: UniversalImageProps): JSX.Element => {
7+
// Props
8+
const {
9+
/* - Universal - */
10+
src,
11+
alt,
12+
width,
13+
height,
14+
style,
15+
priority,
16+
onError,
17+
onLoadEnd,
18+
/* - Split - */
19+
expoPlaceholder,
20+
/* - Next.js - */
21+
onLoad,
22+
fill,
23+
/* - Expo - */
24+
accessibilityLabel,
25+
accessible,
26+
allowDownscaling,
27+
autoplay,
28+
blurRadius,
29+
cachePolicy,
30+
contentFit,
31+
contentPosition,
32+
enableLiveTextInteraction,
33+
focusable,
34+
onLoadStart,
35+
onProgress,
36+
placeholderContentFit,
37+
recyclingKey,
38+
responsivePolicy,
39+
} = props
40+
41+
// -- Overrides --
42+
43+
// @ts-ignore
44+
const finalStyle = { width, height, ...style }
45+
if (fill) finalStyle.height = '100%'
46+
if (fill) finalStyle.width = '100%'
47+
48+
// -- Render --
49+
50+
return (
51+
<ExpoImage
52+
/* - Universal - */
53+
source={src as any}
54+
alt={alt || accessibilityLabel} // @ts-ignore
55+
style={finalStyle}
56+
priority={priority}
57+
onError={onError}
58+
onLoadEnd={onLoadEnd || onLoad as any}
59+
/* - Split - */
60+
placeholder={expoPlaceholder}
61+
/* - Expo - */
62+
accessibilityLabel={alt || accessibilityLabel}
63+
accessible={accessible}
64+
allowDownscaling={allowDownscaling}
65+
autoplay={autoplay}
66+
blurRadius={blurRadius}
67+
cachePolicy={cachePolicy}
68+
contentFit={contentFit}
69+
contentPosition={contentPosition}
70+
enableLiveTextInteraction={enableLiveTextInteraction}
71+
focusable={focusable}
72+
onLoadStart={onLoadStart}
73+
onProgress={onProgress}
74+
placeholderContentFit={placeholderContentFit}
75+
recyclingKey={recyclingKey}
76+
responsivePolicy={responsivePolicy}
77+
/>
78+
)
79+
}
80+
81+
/* --- Static Methods -------------------------------------------------------------------------- */
82+
83+
Image.clearDiskCache = ExpoImage.clearDiskCache as UniversalImageMethods['clearDiskCache']
84+
Image.clearMemoryCache = ExpoImage.clearMemoryCache as UniversalImageMethods['clearMemoryCache']
85+
Image.getCachePathAsync = ExpoImage.getCachePathAsync as UniversalImageMethods['getCachePathAsync']
86+
Image.prefetch = ExpoImage.prefetch as UniversalImageMethods['prefetch']
87+
88+
/* --- Exports --------------------------------------------------------------------------------- */
89+
90+
export { Image }

features/app-core/components/Image.types.tsx

Lines changed: 218 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import NextImage from 'next/image'
2+
import { UniversalImageProps, UniversalImageMethods } from './Image.types'
3+
4+
/* --- <Image/> -------------------------------------------------------------------------------- */
5+
6+
const Image = (props: UniversalImageProps): JSX.Element => {
7+
// Props
8+
const {
9+
/* - Universal - */
10+
src,
11+
alt,
12+
width,
13+
height,
14+
style = {},
15+
priority = 'normal',
16+
onError,
17+
onLoadEnd,
18+
/* - Split - */
19+
nextPlaceholder,
20+
/* - Next.js - */
21+
loader,
22+
fill,
23+
sizes,
24+
quality,
25+
onLoad,
26+
loading,
27+
blurDataURL,
28+
unoptimized,
29+
/* - Expo - */
30+
accessibilityLabel,
31+
contentFit,
32+
} = props
33+
34+
// -- Overrides --
35+
36+
// @ts-ignore
37+
const finalStyle = { width, height, ...style }
38+
if (fill) finalStyle.height = '100%'
39+
if (fill) finalStyle.width = '100%'
40+
if (fill) finalStyle.objectFit = contentFit || 'cover'
41+
42+
// -- Render --
43+
44+
return (
45+
<NextImage
46+
/* - Universal - */
47+
src={src as any}
48+
alt={alt || accessibilityLabel}
49+
width={width}
50+
height={height} // @ts-ignore
51+
style={finalStyle}
52+
priority={priority === 'high'}
53+
onError={onError as any}
54+
onLoad={(onLoad || onLoadEnd) as any}
55+
/* - Split - */
56+
placeholder={nextPlaceholder}
57+
/* - Next.js - */
58+
loader={loader}
59+
fill={fill}
60+
sizes={sizes}
61+
quality={quality}
62+
loading={loading}
63+
blurDataURL={blurDataURL}
64+
unoptimized={unoptimized}
65+
/>
66+
)
67+
}
68+
69+
/* --- Static Methods -------------------------------------------------------------------------- */
70+
71+
Image.clearDiskCache = (() => {}) as UniversalImageMethods['clearDiskCache']
72+
Image.clearMemoryCache = (() => {}) as UniversalImageMethods['clearMemoryCache']
73+
Image.getCachePathAsync = ((cacheKey: string) => {}) as UniversalImageMethods['getCachePathAsync'] // prettier-ignore
74+
Image.prefetch = ((urls: string | string[], cachePolicy?: "memory" | "memory-disk") => {}) as UniversalImageMethods['prefetch'] // prettier-ignore
75+
76+
/* --- Exports --------------------------------------------------------------------------------- */
77+
78+
export { Image }

features/app-core/screens/HomeScreen.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import React from 'react'
22
import { StyleSheet, Text, View } from 'react-native'
33
import { Link } from '../navigation/Link'
4+
import { Image } from '../components/Image'
45

56
/* --- <HomeScreen/> --------------------------------------------------------------------------- */
67

78
const HomeScreen = () => {
89
return (
910
<View style={styles.container}>
10-
<Text style={styles.title}>Expo + Next.js app routing 👋</Text>
11+
<Image src={require('../assets/aetherspaceLogo.png')} width={60} height={60} style={{ marginBottom: 12 }} />
12+
<Text style={styles.title}>Expo + Next.js app routing 🚀</Text>
1113
<Text style={styles.subtitle}>Open HomeScreen.tsx in features/app-core/screens to start working on your app</Text>
1214
<Link href="/subpages/aetherspace" style={styles.link}>Test navigation</Link>
15+
<Link href="/images" style={styles.link}>Test images</Link>
1316
</View>
1417
)
1518
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react'
2+
import { StyleSheet, Text, View } from 'react-native'
3+
import { Link } from '../navigation/Link'
4+
import { Image } from '../components/Image'
5+
6+
/* --- <ImagesScreen/> --------------------------------------------------------------------------- */
7+
8+
const ImagesScreen = () => {
9+
return (
10+
<View style={styles.container}>
11+
<Link
12+
href="/"
13+
style={{ ...styles.backButton, ...styles.link, textDecorationLine: 'none' }}
14+
>
15+
{`< Back`}
16+
</Link>
17+
{/* - 1 - */}
18+
<Image src={require('../assets/aetherspaceLogo.png')} width={60} height={60} />
19+
<Text style={styles.subtitle}>src=static-require | width: 60 | height: 60</Text>
20+
{/* - 2 - */}
21+
<Image src="https://codinsonn.dev/_next/image?url=%2Fimg%2FCodelyFansLogoPic160x160.jpeg&w=256&q=75" width={60} height={60} />
22+
<Text style={styles.subtitle}>src=external-url | width: 60 | height: 60</Text>
23+
{/* - 3 - */}
24+
<View style={{ width: 60, height: 80, position: 'relative', borderColor: 'black', borderStyle: 'dashed', borderWidth: 1 }}>
25+
<Image src={require('../assets/aetherspaceLogo.png')} fill />
26+
</View>
27+
<Text style={styles.subtitle}>wrapper=50x80, relative | fill=true</Text>
28+
{/* - 4 - */}
29+
<View style={{ width: 80, height: 60, position: 'relative', borderColor: 'black', borderStyle: 'dashed', borderWidth: 1 }}>
30+
<Image src={require('../assets/aetherspaceLogo.png')} fill contentFit="contain" />
31+
</View>
32+
<Text style={styles.subtitle}>wrapper=80x60, relative | fill | contentFit=contain</Text>
33+
</View>
34+
)
35+
}
36+
37+
/* --- Styles ---------------------------------------------------------------------------------- */
38+
39+
const styles = StyleSheet.create({
40+
container: {
41+
flex: 1,
42+
justifyContent: 'center',
43+
alignItems: 'center',
44+
},
45+
backButton: {
46+
position: 'absolute',
47+
top: 16,
48+
left: 16,
49+
},
50+
subtitle: {
51+
marginTop: 8,
52+
marginBottom: 16,
53+
fontSize: 16,
54+
textAlign: 'center',
55+
},
56+
link: {
57+
marginTop: 16,
58+
fontSize: 16,
59+
color: 'blue',
60+
textAlign: 'center',
61+
textDecorationLine: 'underline',
62+
},
63+
})
64+
65+
/* --- Exports --------------------------------------------------------------------------------- */
66+
67+
export default ImagesScreen

0 commit comments

Comments
 (0)