Skip to content

Commit 9efca2f

Browse files
committed
feat: Parse NativeWind styles in custom Image & Link
1 parent ca782e9 commit 9efca2f

File tree

11 files changed

+93
-57
lines changed

11 files changed

+93
-57
lines changed

features/app-core/components/Image.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Image as ExpoImage } from 'expo-image'
22
import { UniversalImageProps, UniversalImageMethods } from './Image.types'
3+
import { parseNativeWindStyles } from '../utils/parseNativeWindStyles'
34

45
/* --- <Image/> -------------------------------------------------------------------------------- */
56

@@ -38,10 +39,13 @@ const Image = (props: UniversalImageProps): JSX.Element => {
3839
responsivePolicy,
3940
} = props
4041

42+
// -- Nativewind --
43+
44+
const { nativeWindStyles, restStyle } = parseNativeWindStyles(style)
45+
const finalStyle = { width, height, ...nativeWindStyles, ...restStyle }
46+
4147
// -- Overrides --
4248

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

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export type UniversalImageProps = {
3838
* - Remember that the required width and height props can interact with your styling. If you use styling to modify an image's width, you should also style its height to auto to preserve its intrinsic aspect ratio, or your image will be distorted. */
3939
style?: ExpoImageProps['style']
4040

41+
/** Universal, will affect both Expo & Next.js
42+
* - Remember that the required width and height props can interact with your styling. If you use styling to modify an image's width, you should also style its height to auto to preserve its intrinsic aspect ratio, or your image will be distorted. */
43+
className?: string
44+
4145
/** Universal, will affect both Expo & Next.js - Called on an image fetching error. */
4246
onError?: ExpoImageProps['onError']
4347

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import NextImage from 'next/image'
22
import { UniversalImageProps, UniversalImageMethods } from './Image.types'
3+
import { parseNativeWindStyles } from '../utils/parseNativeWindStyles'
34

45
/* --- <Image/> -------------------------------------------------------------------------------- */
56

@@ -11,6 +12,7 @@ const Image = (props: UniversalImageProps): JSX.Element => {
1112
alt,
1213
width,
1314
height,
15+
className,
1416
style = {},
1517
priority = 'normal',
1618
onError,
@@ -31,10 +33,13 @@ const Image = (props: UniversalImageProps): JSX.Element => {
3133
contentFit,
3234
} = props
3335

36+
// -- Nativewind --
37+
38+
const { nativeWindStyles, nativeWindClassName, restStyle } = parseNativeWindStyles(style)
39+
const finalStyle = { width, height, ...nativeWindStyles, ...restStyle } as React.CSSProperties
40+
3441
// -- Overrides --
3542

36-
// @ts-ignore
37-
const finalStyle = { width, height, ...style }
3843
if (fill) finalStyle.height = '100%'
3944
if (fill) finalStyle.width = '100%'
4045
if (fill) finalStyle.objectFit = contentFit || 'cover'
@@ -47,7 +52,8 @@ const Image = (props: UniversalImageProps): JSX.Element => {
4752
src={src as any}
4853
alt={alt || accessibilityLabel}
4954
width={width}
50-
height={height} // @ts-ignore
55+
height={height}
56+
className={[className, nativeWindClassName].filter(Boolean).join(' ')}
5157
style={finalStyle}
5258
priority={priority === 'high'}
5359
onError={onError as any}

features/app-core/components/styled.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { styled } from 'nativewind'
22
import { Text as RNText, View as RNView } from 'react-native'
33
import { Link as UniversalLink } from '../navigation/Link'
4+
import { Image as UniversalImage } from './Image'
45

56
/* --- Primitives ------------------------------------------------------------------------------ */
67

78
export const View = styled(RNView, '')
89
export const Text = styled(RNText, '')
10+
export const Image = styled(UniversalImage, '')
911

1012
/* --- Typography ------------------------------------------------------------------------------ */
1113

@@ -17,6 +19,7 @@ export const P = styled(RNText, 'text-base')
1719

1820
/* --- Fix for Next Link ----------------------------------------------------------------------- */
1921

22+
export const Link = styled(UniversalLink, 'text-blue-500 underline')
2023
export const LinkText = styled(RNText, 'text-blue-500 underline')
2124
export const TextLink = (props: Omit<React.ComponentProps<typeof UniversalLink>, 'className'> & { className?: string }) => {
2225
const { className, style, children, ...universalLinkProps } = props

features/app-core/navigation/Link.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Link as ExpoLink } from 'expo-router'
22
import type { UniversalLinkProps } from './Link.types'
3+
import { parseNativeWindStyles } from '../utils/parseNativeWindStyles'
34

45
/* --- <Link/> --------------------------------------------------------------------------------- */
56

@@ -21,12 +22,17 @@ export const Link = (props: UniversalLinkProps) => {
2122
maxFontSizeMultiplier
2223
} = props
2324

25+
// -- Nativewind --
26+
27+
const { nativeWindStyles, restStyle } = parseNativeWindStyles(style)
28+
const finalStyle = { ...nativeWindStyles, ...restStyle }
29+
2430
// -- Render --
2531

2632
return (
2733
<ExpoLink
2834
href={href}
29-
style={style}
35+
style={finalStyle}
3036
onPress={onPress}
3137
target={target}
3238
asChild={asChild}

features/app-core/navigation/Link.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export type UniversalLinkProps = {
1414
style?: ExpoLinkProps['style'];
1515

1616
/** -!- Nativewind classNames should be applied to either the parent or children of Link. Ideally, create or use a TextLink component instead */
17-
className?: never;
17+
className?: string; // never;
1818

1919
/** Universal - Should replace the current route without adding to the history - Default: false. */
2020
replace?: boolean;

features/app-core/navigation/Link.web.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import NextLink from 'next/link'
22
import type { ComponentProps } from 'react'
33
import type { UniversalLinkProps } from './Link.types'
4+
import { parseNativeWindStyles } from '../utils/parseNativeWindStyles'
45

56
/* --- <Link/> --------------------------------------------------------------------------------- */
67

@@ -9,6 +10,7 @@ export const Link = (props: UniversalLinkProps) => {
910
const {
1011
children,
1112
href,
13+
className,
1214
style,
1315
replace,
1416
onPress,
@@ -21,12 +23,18 @@ export const Link = (props: UniversalLinkProps) => {
2123
as,
2224
} = props
2325

26+
// -- Nativewind --
27+
28+
const { nativeWindStyles, nativeWindClassName, restStyle } = parseNativeWindStyles(style)
29+
const finalStyle = { ...nativeWindStyles, ...restStyle } as React.CSSProperties
30+
2431
// -- Render --
2532

2633
return (
2734
<NextLink
2835
href={href}
29-
style={style as unknown as ComponentProps<typeof NextLink>['style']}
36+
className={[className, nativeWindClassName].filter(Boolean).join(' ')}
37+
style={finalStyle as unknown as ComponentProps<typeof NextLink>['style']}
3038
onClick={onPress}
3139
target={target}
3240
replace={replace}

features/app-core/screens/HomeScreen.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
import React from 'react'
2-
import { Image } from '../components/Image'
3-
import { View, H3, P, TextLink } from '../components/styled'
2+
import { View, Image, H3, P, Link } from '../components/styled'
43

54
/* --- <HomeScreen/> --------------------------------------------------------------------------- */
65

76
const HomeScreen = () => {
87
return (
98
<View className="flex flex-1 justify-center items-center">
10-
<Image src={require('../assets/aetherspaceLogo.png')} width={60} height={60} style={{ marginBottom: 12 }} />
11-
<H3 className="text-center">Expo + Next.js app routing 👋</H3>
9+
<Image src={require('../assets/aetherspaceLogo.png')} width={60} height={60} className="mb-3" />
10+
<H3 className="text-center">Expo + Next.js app routing 🚀</H3>
1211
<P className="mt-2 text-center">Open HomeScreen.tsx in features/app-core/screens to start working on your app</P>
13-
<TextLink className="mt-4 text-center" href="/subpages/aetherspace">
12+
<Link className="mt-4 text-center" href="/subpages/aetherspace">
1413
Test navigation
15-
</TextLink>
16-
<Link href="https://universal-base-starter-docs.vercel.app/" target="_blank" style={styles.link}>Docs</Link>
14+
</Link>
15+
<Link className="mt-4 text-center" href="/images">
16+
Test images
17+
</Link>
18+
<Link className="mt-4 text-center" href="https://universal-base-starter-docs.vercel.app/" target="_blank">
19+
Docs
20+
</Link>
1721
</View>
1822
)
1923
}
Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,37 @@
11
import React from 'react'
2-
import { StyleSheet, Text, View } from 'react-native'
3-
import { Link } from '../navigation/Link'
4-
import { Image } from '../components/Image'
2+
import { View, Text, Image, Link } from '../components/styled'
53

64
/* --- <ImagesScreen/> --------------------------------------------------------------------------- */
75

86
const ImagesScreen = () => {
97
return (
10-
<View style={styles.container}>
8+
<View className="flex flex-1 justify-center items-center">
119
<Link
1210
href="/"
13-
style={{ ...styles.backButton, ...styles.link, textDecorationLine: 'none' }}
11+
className="text-blue-500 absolute top-8 web:top-0 left-0 p-4"
1412
>
1513
{`< Back`}
1614
</Link>
1715
{/* - 1 - */}
1816
<Image src={require('../assets/aetherspaceLogo.png')} width={60} height={60} />
19-
<Text style={styles.subtitle}>src=static-require | width: 60 | height: 60</Text>
17+
<Text className="mt-2 mb-4 text-center text-base">src=static-require | width: 60 | height: 60</Text>
2018
{/* - 2 - */}
2119
<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>
20+
<Text className="mt-2 mb-4 text-center text-base">src=external-url | width: 60 | height: 60</Text>
2321
{/* - 3 - */}
24-
<View style={{ width: 60, height: 80, position: 'relative', borderColor: 'black', borderStyle: 'dashed', borderWidth: 1 }}>
22+
<View className="w-[60px] h-[80px] relative border-[1px] border-dashed border-black">
2523
<Image src={require('../assets/aetherspaceLogo.png')} fill />
2624
</View>
27-
<Text style={styles.subtitle}>wrapper=50x80, relative | fill=true</Text>
25+
<Text className="mt-2 mb-4 text-center text-base">wrapper=50x80, relative | fill=true</Text>
2826
{/* - 4 - */}
29-
<View style={{ width: 80, height: 60, position: 'relative', borderColor: 'black', borderStyle: 'dashed', borderWidth: 1 }}>
27+
<View className="w-[80px] h-[60px] relative border-[1px] border-dashed border-black">
3028
<Image src={require('../assets/aetherspaceLogo.png')} fill contentFit="contain" />
3129
</View>
32-
<Text style={styles.subtitle}>wrapper=80x60, relative | fill | contentFit=contain</Text>
30+
<Text className="mt-2 mb-4 text-center text-base">wrapper=80x60, relative | fill | contentFit=contain</Text>
3331
</View>
3432
)
3533
}
3634

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-
6535
/* --- Exports --------------------------------------------------------------------------------- */
6636

6737
export default ImagesScreen

features/app-core/screens/SlugScreen.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22
import { useRouteParams } from '@app/core/navigation/useRouteParams'
3-
import { View, Text, H3, TextLink } from '../components/styled'
3+
import { View, Text, H3, Link } from '../components/styled'
44
import { useRouter } from '../navigation/useRouter'
55

66
/* --- <SlugScreen/> --------------------------------------------------------------------------- */
@@ -32,13 +32,13 @@ const SlugScreen = (props) => {
3232
<Text className="mt-2 text-base text-center">
3333
Need a more robust, Fully-Stacked, Full-Product, Universal App Setup?
3434
</Text>
35-
<TextLink
35+
<Link
3636
href="https://github.com/Aetherspace/green-stack-starter-demo#readme"
3737
className="mt-4 text-center"
3838
target="_blank"
3939
>
4040
Check out the GREEN Stack Starter
41-
</TextLink>
41+
</Link>
4242
<Text className="mt-4 text-center text-blue-500 underline" onPress={() => push('/subpages/push')}>
4343
{`router.push()`}
4444
</Text>

0 commit comments

Comments
 (0)