Merge pull request #6 from movie-web/nativewind

Nativewind
This commit is contained in:
Jorrin
2024-01-30 19:12:01 +01:00
committed by GitHub
44 changed files with 1735 additions and 1539 deletions

View File

@@ -17,7 +17,7 @@ module.exports = {
settings: { settings: {
'import/resolver': { 'import/resolver': {
typescript: { typescript: {
project: './tsconfig.json', project: ['./tsconfig.base.json', './apps/*/tsconfig.json'],
}, },
}, },
}, },

View File

@@ -1,4 +1,5 @@
{ {
"singleQuote": true, "singleQuote": true,
"endOfLine": "auto" "endOfLine": "auto",
"plugins": ["prettier-plugin-tailwindcss"]
} }

11
.vscode/settings.json vendored
View File

@@ -7,5 +7,14 @@
}, },
"[typescriptreact]": { "[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
} },
"typescript.tsdk": "node_modules\\typescript\\lib",
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
],
"typescript.preferences.autoImportFileExcludePatterns": [
// Should import Text from UI components instead
"react-native/Libraries/Text/Text.d.ts"
]
} }

1
apps/mobile/app.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="nativewind/types" />

View File

@@ -4,20 +4,19 @@
"slug": "mw-mobile", "slug": "mw-mobile",
"version": "1.0.0", "version": "1.0.0",
"orientation": "portrait", "orientation": "portrait",
"scheme":"dev.movieweb.app", "scheme": "dev.movieweb.app",
"userInterfaceStyle": "automatic",
"icon": "./assets/images/icon.png", "icon": "./assets/images/icon.png",
"splash": { "splash": {
"image": "./assets/images/splash.png", "image": "./assets/images/splash.png",
"resizeMode": "contain", "resizeMode": "contain",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
"jsEngine": "jsc", "jsEngine": "jsc",
"updates": { "updates": {
"fallbackToCacheTimeout": 0 "fallbackToCacheTimeout": 0
}, },
"assetBundlePatterns": [ "assetBundlePatterns": ["**/*"],
"**/*"
],
"ios": { "ios": {
"supportsTablet": true, "supportsTablet": true,
"bundleIdentifier": "dev.movieweb.app" "bundleIdentifier": "dev.movieweb.app"
@@ -34,20 +33,17 @@
"bundler": "metro" "bundler": "metro"
}, },
"plugins": [ "plugins": [
"expo-router", "expo-router",
[ [
"@config-plugins/detox", "@config-plugins/detox",
{ {
"skipProguard": false, "skipProguard": false,
"subdomains": [ "subdomains": ["10.0.2.2", "localhost"]
"10.0.2.2",
"localhost"
]
} }
] ]
], ],
"experiments": { "experiments": {
"typedRoutes": true "typedRoutes": true
} }
} }
} }

View File

@@ -1,20 +1,19 @@
import { Tabs } from 'expo-router'; import { Tabs } from 'expo-router';
import Colors from '../../constants/Colors'; import TabBarIcon from '@/components/TabBarIcon';
import TabBarIcon from '../../components/TabBarIcon'; import Colors from '@/constants/Colors';
import { globalStyles } from '../../styles/global';
export default function TabLayout() { export default function TabLayout() {
return ( return (
<Tabs <Tabs
sceneContainerStyle={{ sceneContainerStyle={{
backgroundColor: '#000', backgroundColor: Colors.background,
}} }}
screenOptions={{ screenOptions={{
headerShown: false, headerShown: false,
tabBarActiveTintColor: Colors.dark.purple100, tabBarActiveTintColor: Colors.primary[100],
tabBarStyle: { tabBarStyle: {
backgroundColor: Colors.dark.shade700, backgroundColor: Colors.secondary[700],
borderTopColor: 'transparent', borderTopColor: 'transparent',
borderTopRightRadius: 20, borderTopRightRadius: 20,
borderTopLeftRadius: 20, borderTopLeftRadius: 20,
@@ -28,7 +27,6 @@ export default function TabLayout() {
{ {
marginTop: 2, marginTop: 2,
}, },
globalStyles.fOpenSansMedium,
], ],
}} }}
> >
@@ -55,26 +53,9 @@ export default function TabLayout() {
options={{ options={{
title: 'Search', title: 'Search',
tabBarLabel: '', tabBarLabel: '',
tabBarShowLabel: false,
tabBarLabelStyle: {
display: 'none',
},
tabBarIconStyle: {},
tabBarIcon: () => ( tabBarIcon: () => (
<TabBarIcon <TabBarIcon
style={{ className=" bg-primary-400 flex aspect-[1/1] h-14 items-center justify-center rounded-full text-center text-2xl text-white"
position: 'relative',
top: -1,
backgroundColor: Colors.dark.purple400,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
aspectRatio: 1,
borderRadius: 100,
textAlign: 'center',
textAlignVertical: 'center',
height: 56,
}}
name="search" name="search"
color="#FFF" color="#FFF"
/> />

View File

@@ -1,8 +1,5 @@
import { StyleSheet } from 'react-native'; import ScreenLayout from '@/components/layout/ScreenLayout';
import { Text } from '@/components/ui/Text';
import ScreenLayout from '../../components/layout/screenLayout';
import { globalStyles } from '../../styles/global';
import { RegularText } from '../../components/Styled';
export default function AboutScreen() { export default function AboutScreen() {
return ( return (
@@ -10,27 +7,10 @@ export default function AboutScreen() {
title="About" title="About"
subtitle="What is movie-web and how content is served?" subtitle="What is movie-web and how content is served?"
> >
<RegularText style={globalStyles.textWhite}> <Text>
No content is served from movie-web directly and movie web does not host No content is served from movie-web directly and movie web does not host
anything. anything.
</RegularText> </Text>
</ScreenLayout> </ScreenLayout>
); );
} }
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

View File

@@ -1,8 +1,5 @@
import { StyleSheet, Text } from 'react-native'; import ScreenLayout from '@/components/layout/ScreenLayout';
import { Text } from '@/components/ui/Text';
import { globalStyles } from '../../styles/global';
import ScreenLayout from '../../components/layout/screenLayout';
import { RegularText } from '../../components/Styled';
export default function AccountScreen() { export default function AccountScreen() {
return ( return (
@@ -10,9 +7,7 @@ export default function AccountScreen() {
title="Account" title="Account"
subtitle="Manage your movie web account from here" subtitle="Manage your movie web account from here"
> >
<RegularText style={globalStyles.textWhite}> <Text>Hey Bro! what are you up to?</Text>
Hey Bro! what are you up to?
</RegularText>
</ScreenLayout> </ScreenLayout>
); );
} }

View File

@@ -1,13 +1,10 @@
import { RegularText } from '../../components/Styled'; import ScreenLayout from '@/components/layout/ScreenLayout';
import ScreenLayout from '../../components/layout/screenLayout'; import { Text } from '@/components/ui/Text';
import { globalStyles } from '../../styles/global';
export default function HomeScreen() { export default function HomeScreen() {
return ( return (
<ScreenLayout title="Home" subtitle="This is where all magic happens"> <ScreenLayout title="Home" subtitle="This is where all magic happens">
<RegularText style={globalStyles.textWhite}> <Text>Movies will be listed here</Text>
Movies will be listed here
</RegularText>
</ScreenLayout> </ScreenLayout>
); );
} }

View File

@@ -1,10 +1,10 @@
import { FontAwesome5 } from '@expo/vector-icons'; import { FontAwesome5 } from '@expo/vector-icons';
import { useFocusEffect } from 'expo-router';
import { useCallback, useRef, useState } from 'react'; import { useCallback, useRef, useState } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { TextInput } from 'react-native-gesture-handler'; import { TextInput } from 'react-native-gesture-handler';
import { globalStyles } from '../../../styles/global';
import Colors from '../../../constants/Colors'; import Colors from '@/constants/Colors';
import { useFocusEffect } from 'expo-router';
export default function Searchbar() { export default function Searchbar() {
const [keyword, setKeyword] = useState(''); const [keyword, setKeyword] = useState('');
@@ -24,41 +24,19 @@ export default function Searchbar() {
); );
return ( return (
<View <View className="border-primary-400 focus-within:border-primary-300 mb-6 mt-4 flex-row items-center rounded-full border">
style={{ <View className="ml-1 w-12 items-center justify-center">
...globalStyles.flexRow, <FontAwesome5 name="search" size={18} color={Colors.secondary[200]} />
...globalStyles.itemsCenter,
...globalStyles.border,
...globalStyles.roundedFull,
marginTop: 14,
marginBottom: 24,
}}
>
<View
style={{
...globalStyles.justifyCenter,
...globalStyles.itemsCenter,
width: 48,
marginLeft: 4,
}}
>
<FontAwesome5 name="search" size={18} color={Colors.dark.shade200} />
</View> </View>
<TextInput <TextInput
value={keyword} value={keyword}
autoFocus={true} autoFocus
onChangeText={(text) => setKeyword(text)} onChangeText={(text) => setKeyword(text)}
ref={inputRef} ref={inputRef}
placeholder="What are you looking for?" placeholder="What are you looking for?"
placeholderTextColor={Colors.dark.shade200} placeholderTextColor={Colors.secondary[200]}
style={[ className="w-full rounded-3xl py-3 pr-5 text-white focus-visible:outline-none"
globalStyles.input, />
globalStyles.fOpenSansRegular,
{
width: '100%',
},
]}
></TextInput>
</View> </View>
); );
} }

View File

@@ -1,13 +1,9 @@
import { Dimensions, ScrollView, View } from 'react-native'; import { ScrollView, View } from 'react-native';
import Item from '@/components/item/item';
import ScreenLayout from '@/components/layout/ScreenLayout';
import { Text } from '@/components/ui/Text';
import { globalStyles } from '../../../styles/global';
import ScreenLayout from '../../../components/layout/screenLayout';
import { TextInput } from 'react-native-gesture-handler';
import styles from './styles';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useFocusEffect } from 'expo-router';
import { BoldText } from '../../../components/Styled';
import Item from '../../../components/item/item';
import Searchbar from './Searchbar'; import Searchbar from './Searchbar';
export default function SearchScreen() { export default function SearchScreen() {
@@ -15,23 +11,21 @@ export default function SearchScreen() {
<ScrollView> <ScrollView>
<ScreenLayout <ScreenLayout
title={ title={
<View <View className="flex-row items-center">
style={{ ...globalStyles.flexRow, ...globalStyles.itemsCenter }} <Text className="text-2xl font-bold">Search</Text>
>
<BoldText style={globalStyles.sectionTitle}>Search</BoldText>
</View> </View>
} }
subtitle="Looking for something?" subtitle="Looking for something?"
> >
<Searchbar /> <Searchbar />
<View style={styles.items}> <View className="flex w-full flex-1 flex-row flex-wrap justify-start">
<View style={styles.itemOuter}> <View className="basis-1/2 px-3 pb-3">
<Item /> <Item />
</View> </View>
<View style={styles.itemOuter}> <View className="basis-1/2 px-3 pb-3">
<Item /> <Item />
</View> </View>
<View style={styles.itemOuter}> <View className="basis-1/2 px-3 pb-3">
<Item /> <Item />
</View> </View>
</View> </View>

View File

@@ -1,19 +0,0 @@
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
items: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'flex-start',
width: '100%',
flexDirection: 'row',
flex: 1,
},
itemOuter: {
flexBasis: '50%',
paddingHorizontal: 12,
paddingBottom: 12,
},
});
export default styles;

View File

@@ -1,14 +1,10 @@
import { RegularText } from '../../components/Styled'; import ScreenLayout from '@/components/layout/ScreenLayout';
import ScreenLayout from '../../components/layout/screenLayout'; import { Text } from '@/components/ui/Text';
import { globalStyles } from '../../styles/global';
import { StyleSheet, Text } from 'react-native';
export default function SettingsScreen() { export default function SettingsScreen() {
return ( return (
<ScreenLayout title="Settings" subtitle="Need to change something?"> <ScreenLayout title="Settings" subtitle="Need to change something?">
<RegularText style={globalStyles.textWhite}> <Text>Settings would be listed in here. Coming soon</Text>
Settings would be listed in here. Coming soon
</RegularText>
</ScreenLayout> </ScreenLayout>
); );
} }

View File

@@ -1,41 +1,21 @@
import { Link, Stack } from 'expo-router'; import { Link, Stack } from 'expo-router';
import { StyleSheet, View } from 'react-native'; import { View } from 'react-native';
import { BoldText, RegularText } from '../components/Styled';
import { Text } from '@/components/ui/Text';
export default function NotFoundScreen() { export default function NotFoundScreen() {
return ( return (
<> <>
<Stack.Screen options={{ title: 'Oops!' }} /> <Stack.Screen options={{ title: 'Oops!' }} />
<View style={styles.container}> <View className="flex-1 items-center justify-center p-5">
<BoldText style={styles.title}> <Text className="text-lg font-bold">
This screen doesn&apos;t exist. This screen doesn&apos;t exist.
</BoldText> </Text>
<Link href="/" style={styles.link}> <Link href="/" className="mt-4 py-4">
<RegularText style={styles.linkText}>Go to home screen!</RegularText> <Text className="text-sm text-sky-500">Go to home screen!</Text>
</Link> </Link>
</View> </View>
</> </>
); );
} }
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
link: {
marginTop: 15,
paddingVertical: 15,
},
linkText: {
fontSize: 14,
color: '#2e78b7',
},
});

View File

@@ -1,3 +1,4 @@
/* eslint-disable global-require */
import FontAwesome from '@expo/vector-icons/FontAwesome'; import FontAwesome from '@expo/vector-icons/FontAwesome';
import { import {
DarkTheme, DarkTheme,
@@ -9,7 +10,9 @@ import { SplashScreen, Stack } from 'expo-router';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useColorScheme } from 'react-native'; import { useColorScheme } from 'react-native';
import Colors from '../constants/Colors'; import Colors from '@/constants/Colors';
import './styles/global.css';
export { export {
// Catch any errors thrown by the Layout component. // Catch any errors thrown by the Layout component.
@@ -27,7 +30,6 @@ SplashScreen.preventAutoHideAsync();
export default function RootLayout() { export default function RootLayout() {
const [loaded, error] = useFonts({ const [loaded, error] = useFonts({
// eslint-disable-next-line global-require
OpenSansRegular: require('../assets/fonts/OpenSans-Regular.ttf'), OpenSansRegular: require('../assets/fonts/OpenSans-Regular.ttf'),
OpenSansLight: require('../assets/fonts/OpenSans-Light.ttf'), OpenSansLight: require('../assets/fonts/OpenSans-Light.ttf'),
OpenSansMedium: require('../assets/fonts/OpenSans-Medium.ttf'), OpenSansMedium: require('../assets/fonts/OpenSans-Medium.ttf'),
@@ -65,12 +67,11 @@ function RootLayoutNav() {
gestureEnabled: true, gestureEnabled: true,
headerShown: false, headerShown: false,
contentStyle: { contentStyle: {
backgroundColor: Colors.dark.shade900, backgroundColor: Colors.background,
}, },
}} }}
> >
<Stack.Screen name="(tabs)" options={{ headerShown: false }} /> <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack> </Stack>
</ThemeProvider> </ThemeProvider>
); );

View File

@@ -13,7 +13,6 @@ export function ExternalLink(
target: '_blank', target: '_blank',
}} }}
{...props} {...props}
// @ts-expect-error: External URLs are not typed.
href={props.href} href={props.href}
onPress={(e) => { onPress={(e) => {
if (Platform.OS !== 'web') { if (Platform.OS !== 'web') {

View File

@@ -0,0 +1,17 @@
import { FontAwesome } from '@expo/vector-icons';
import Colors from '@/constants/Colors';
type Props = {
focused?: boolean;
} & React.ComponentProps<typeof FontAwesome>;
export default function TabBarIcon({ focused, ...rest }: Props) {
return (
<FontAwesome
color={focused ? Colors.primary[300] : Colors.secondary[300]}
size={24}
{...rest}
/>
);
}

View File

@@ -0,0 +1,26 @@
import { Image, View } from 'react-native';
import { Text } from '@/components/ui/Text';
import { TMDB_POSTER_PATH } from '@/constants/General';
export default function Item() {
return (
<View className="w-full">
<View className="mb-2 aspect-[9/14] w-full overflow-hidden rounded-2xl">
<Image
source={{
uri: `${TMDB_POSTER_PATH}/w342//gdIrmf2DdY5mgN6ycVP0XlzKzbE.jpg`,
width: 200,
}}
className="h-full w-full object-cover"
/>
</View>
<Text className="font-bold">Hamilton</Text>
<View className="flex-row items-center gap-3">
<Text className="text-xs text-gray-600">Movie</Text>
<View className="h-1 w-1 rounded-3xl bg-gray-600" />
<Text className="text-sm text-gray-600">2023</Text>
</View>
</View>
);
}

View File

@@ -0,0 +1,22 @@
import { View } from 'react-native';
import { Text } from '@/components/ui/Text';
type Props = {
title?: React.ReactNode | string;
subtitle?: string;
children?: React.ReactNode;
};
export default function ScreenLayout({ title, subtitle, children }: Props) {
return (
<View className="bg-shade-900 flex-1 p-12">
{typeof title === 'string' && (
<Text className="text-2xl font-bold">{title}</Text>
)}
{typeof title !== 'string' && title}
<Text className="mt-1 text-sm font-bold">{subtitle}</Text>
<View className="py-3">{children}</View>
</View>
);
}

View File

@@ -0,0 +1,17 @@
import { cva } from 'class-variance-authority';
import { Text as RNText, TextProps } from 'react-native';
import { cn } from '@/lib/utils';
const textVariants = cva('text-white');
export function Text({ className, ...props }: TextProps) {
return (
<RNText
className={cn(className, textVariants(), {
'font-sans': !className?.includes('font-'),
})}
{...props}
/>
);
}

View File

@@ -0,0 +1,14 @@
export default {
primary: {
100: '#C082FF',
300: '#8D44D6',
400: '#7831BF',
},
secondary: {
50: '#676790',
200: '#3F3F60',
300: '#32324F',
700: '#131322',
},
background: '#0a0a12',
};

View File

@@ -1 +1 @@
export const TMDB_POSTER_PATH = `https://image.tmdb.org/t/p`; export const TMDB_POSTER_PATH = `https://image.tmdb.org/t/p`;

View File

@@ -0,0 +1,6 @@
import { ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -1,33 +0,0 @@
import { StatusBar } from 'expo-status-bar';
import { Platform, StyleSheet, View } from 'react-native';
import { BoldText, RegularText } from '../components/Styled';
export default function ModalScreen() {
return (
<View style={styles.container}>
<BoldText style={styles.title}>Modal</BoldText>
<View style={styles.separator} />
<RegularText>Modal?!</RegularText>
{/* Use a light status bar on iOS to account for the black space above the modal */}
<StatusBar style={Platform.OS === 'ios' ? 'light' : 'auto'} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,10 +1,14 @@
module.exports = function (api) { module.exports = function (api) {
api.cache(true); api.cache(true);
return { return {
presets: ['babel-preset-expo'], presets: [
plugins: [ ['babel-preset-expo', { jsxImportSource: 'nativewind' }],
// Required for expo-router 'nativewind/babel',
'expo-router/babel', ],
], plugins: [
// Required for expo-router
'expo-router/babel',
'react-native-reanimated/plugin',
],
}; };
}; };

View File

@@ -1,44 +0,0 @@
import { Text } from 'react-native';
export const RegularText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansRegular' }, style]} {...rest}>
{children}
</Text>
);
};
export const BoldText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansBold' }, style]} {...rest}>
{children}
</Text>
);
};
export const SemiBoldText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansSemiBold' }, style]} {...rest}>
{children}
</Text>
);
};
export const MediumText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansMedium' }, style]} {...rest}>
{children}
</Text>
);
};
export const ExtraBoldText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansExtraBold' }, style]} {...rest}>
{children}
</Text>
);
};
export const LightText = ({ style, children, ...rest }: Text['props']) => {
return (
<Text style={[{ fontFamily: 'OpenSansLight' }, style]} {...rest}>
{children}
</Text>
);
};

View File

@@ -1,21 +0,0 @@
import { FontAwesome } from '@expo/vector-icons';
import Colors from '../constants/Colors';
type Props = {
focused?: boolean;
color?: string;
} & React.ComponentProps<typeof FontAwesome>;
export default function TabBarIcon({
color = Colors.dark.shade300,
focused,
...rest
}: Props) {
return (
<FontAwesome
color={color || (focused ? Colors.dark.purple300 : Colors.dark.shade300)}
size={24}
{...rest}
/>
);
}

View File

@@ -1,31 +0,0 @@
import { globalStyles } from '../../styles/global';
import { Image, Text, View } from 'react-native';
import styles from './styles';
import { BoldText, RegularText } from '../Styled';
import { TMDB_POSTER_PATH } from '../../constants/General';
export default function Item() {
return (
<View style={styles.wrapper}>
<View style={styles.imageWrapper}>
<Image
source={{
uri: `${TMDB_POSTER_PATH}/w342//gdIrmf2DdY5mgN6ycVP0XlzKzbE.jpg`,
width: 200,
}}
style={styles.image}
/>
</View>
<BoldText style={globalStyles.textWhite}>Hamilton</BoldText>
<View style={styles.meta}>
<RegularText style={[globalStyles.textMuted, styles.smallText]}>
Movie
</RegularText>
<View style={[globalStyles.dotSeperator]}></View>
<RegularText style={[globalStyles.textMuted, styles.smallText]}>
2023
</RegularText>
</View>
</View>
);
}

View File

@@ -1,31 +0,0 @@
import Colors from '../../constants/Colors';
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
wrapper: {
width: '100%',
},
imageWrapper: {
borderRadius: 16,
aspectRatio: 9 / 14,
width: '100%',
overflow: 'hidden',
position: 'relative',
marginBottom: 6,
},
image: {
width: '100%',
height: '100%',
resizeMode: 'cover',
},
meta: {
flexDirection: 'row',
gap: 6,
alignItems: 'center',
},
smallText: {
fontSize: 12,
},
});
export default styles;

View File

@@ -1,31 +0,0 @@
import { View } from 'react-native';
import { globalStyles } from '../../styles/global';
import { styles } from './styles';
import { BoldText, RegularText } from '../Styled';
type Props = {
title?: React.ReactNode | string;
subtitle?: string;
children?: React.ReactNode;
};
export default function ScreenLayout({ title, subtitle, children }: Props) {
return (
<View
style={[
globalStyles.pageContainer,
globalStyles.container,
styles.container,
]}
>
{typeof title === 'string' && (
<BoldText style={globalStyles.sectionTitle}>{title}</BoldText>
)}
{typeof title !== 'string' && title}
<RegularText style={[{ marginTop: 4 }, globalStyles.sectionSubtitle]}>
{subtitle}
</RegularText>
<View style={styles.children}>{children}</View>
</View>
);
}

View File

@@ -1,10 +0,0 @@
import { Dimensions, StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
container: {
minHeight: Dimensions.get('window').height,
},
children: {
paddingVertical: 12,
},
});

View File

@@ -1,27 +0,0 @@
const tintColorLight = '#2f95dc';
const tintColorDark = '#fff';
export default {
light: {
text: '#000',
background: '#fff',
tint: tintColorLight,
tabIconDefault: '#ccc',
tabIconSelected: tintColorLight,
},
dark: {
text: '#fff',
background: '#000',
tint: tintColorDark,
tabIconDefault: '#ccc',
tabIconSelected: tintColorDark,
purple100: '#C082FF',
purple300: '#8D44D6',
purple400: '#7831BF',
shade50: '#676790',
shade200: '#3F3F60',
shade300: '#32324F',
shade700: '#131322',
shade900: '#0A0A12',
},
};

View File

@@ -1,9 +1,12 @@
const { withNxMetro } = require('@nx/expo'); const { withNxMetro } = require('@nx/expo');
const { getDefaultConfig } = require('@expo/metro-config'); const { getDefaultConfig } = require('@expo/metro-config');
const { mergeConfig } = require('metro-config'); const { mergeConfig } = require('metro-config');
const { withNativeWind } = require('nativewind/metro');
const exclusionList = require('metro-config/src/defaults/exclusionList'); const exclusionList = require('metro-config/src/defaults/exclusionList');
const defaultConfig = getDefaultConfig(__dirname); const defaultConfig = getDefaultConfig(__dirname, {
isCSSEnabled: true,
});
const { assetExts, sourceExts } = defaultConfig.resolver; const { assetExts, sourceExts } = defaultConfig.resolver;
/** /**
@@ -18,14 +21,22 @@ const customConfig = {
}, },
resolver: { resolver: {
assetExts: assetExts.filter((ext) => ext !== 'svg'), assetExts: assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg'], sourceExts: [...sourceExts, 'svg', 'mjs'],
blockList: exclusionList([/^(?!.*node_modules).*\/dist\/.*/]), blockList: exclusionList([/^(?!.*node_modules).*\/dist\/.*/]),
// unstable_enableSymlinks: true, // unstable_enableSymlinks: true,
// unstable_enablePackageExports: true, // unstable_enablePackageExports: true,
}, },
}; };
module.exports = withNxMetro(mergeConfig(defaultConfig, customConfig), { const nativeWindConfig = withNativeWind(
mergeConfig(defaultConfig, customConfig),
{
input: './app/styles/global.css',
configPath: './tailwind.config.ts',
},
);
const nxConfig = withNxMetro(nativeWindConfig, {
// Change this to true to see debugging info. // Change this to true to see debugging info.
// Useful if you have issues resolving modules // Useful if you have issues resolving modules
debug: false, debug: false,
@@ -34,3 +45,4 @@ module.exports = withNxMetro(mergeConfig(defaultConfig, customConfig), {
// Specify folders to watch, in addition to Nx defaults (workspace libraries and node_modules) // Specify folders to watch, in addition to Nx defaults (workspace libraries and node_modules)
watchFolders: [], watchFolders: [],
}); });
module.exports = nxConfig;

View File

@@ -12,8 +12,15 @@
"@expo/vector-icons": "^13.0.0", "@expo/vector-icons": "^13.0.0",
"@nx/expo": "*", "@nx/expo": "*",
"@react-navigation/native": "^6.1.9", "@react-navigation/native": "^6.1.9",
"@rnx-kit/metro-config": "*",
"@rnx-kit/metro-resolver-symlinks": "*",
"@testing-library/jest-native": "*", "@testing-library/jest-native": "*",
"@testing-library/react-native": "*", "@testing-library/react-native": "*",
"@types/react": "*",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"eslint-plugin-react": "*",
"eslint-plugin-react-hooks": "*",
"expo": "*", "expo": "*",
"expo-font": "^11.4.0", "expo-font": "^11.4.0",
"expo-linking": "^5.0.2", "expo-linking": "^5.0.2",
@@ -22,26 +29,27 @@
"expo-status-bar": "*", "expo-status-bar": "*",
"expo-system-ui": "^2.6.0", "expo-system-ui": "^2.6.0",
"expo-web-browser": "^12.5.0", "expo-web-browser": "^12.5.0",
"jest": "*",
"jest-expo": "*",
"metro": "*",
"metro-config": "*", "metro-config": "*",
"nativewind": "^4.0.23",
"pod-install": "*", "pod-install": "*",
"react": "*", "react": "*",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-native": "*", "react-native": "*",
"react-native-gesture-handler": "^2.14.1", "react-native-gesture-handler": "^2.14.1",
"react-native-reanimated": "~3.3.0",
"react-native-safe-area-context": "^4.8.2", "react-native-safe-area-context": "^4.8.2",
"react-native-screens": "^3.29.0", "react-native-screens": "^3.29.0",
"react-native-svg": "*", "react-native-svg": "*",
"react-native-svg-transformer": "*", "react-native-svg-transformer": "*",
"react-native-web": "^0.19.10", "react-native-web": "^0.19.10",
"@rnx-kit/metro-config": "*",
"@rnx-kit/metro-resolver-symlinks": "*",
"@types/react": "*",
"eslint-plugin-react": "*",
"eslint-plugin-react-hooks": "*",
"jest": "*",
"jest-expo": "*",
"react-test-renderer": "*", "react-test-renderer": "*",
"typescript": "*" "tailwind-merge": "^2.2.1",
"tailwindcss": "3.3.2",
"typescript": "*",
"prettier-plugin-tailwindcss": "*"
}, },
"scripts": { "scripts": {
"eas-build-pre-install": "cd ../../ && node tools/scripts/eas-build-pre-install.mjs . apps/mobile && cp pnpm-lock.yaml apps/mobile", "eas-build-pre-install": "cd ../../ && node tools/scripts/eas-build-pre-install.mjs . apps/mobile && cp pnpm-lock.yaml apps/mobile",
@@ -62,13 +70,8 @@
"jest": "^29.4.1", "jest": "^29.4.1",
"jest-expo": "~49.0.0", "jest-expo": "~49.0.0",
"pod-install": "^0.1.39", "pod-install": "^0.1.39",
"prettier-plugin-tailwindcss": "^0.5.11",
"react-test-renderer": "18.2.0", "react-test-renderer": "18.2.0",
"typescript": "~5.2.2" "typescript": "~5.2.2"
},
"overrides": {
"react-refresh": "~0.14.0"
},
"resolutions": {
"react-refresh": "~0.14.0"
} }
} }

View File

@@ -8,7 +8,8 @@
"executor": "@nx/expo:start", "executor": "@nx/expo:start",
"dependsOn": ["ensure-symlink", "sync-deps"], "dependsOn": ["ensure-symlink", "sync-deps"],
"options": { "options": {
"port": 8081 "port": 8081,
"clear": true
} }
}, },
"serve": { "serve": {

View File

@@ -1,75 +0,0 @@
import { StyleSheet } from 'react-native';
import Colors from '../constants/Colors';
export const globalStyles = StyleSheet.create({
pageContainer: {
flex: 1,
backgroundColor: Colors.dark.shade900,
paddingVertical: 48,
},
container: {
padding: 24,
},
sectionTitle: {
color: '#FFF',
letterSpacing: -1.5,
fontWeight: 'bold',
fontSize: 28,
},
sectionSubtitle: {
color: Colors.dark.shade200,
fontSize: 14,
},
textWhite: {
color: '#FFF',
},
flexRow: {
flexDirection: 'row',
},
itemsCenter: {
alignItems: 'center',
},
justifyCenter: {
justifyContent: 'center',
},
input: {
borderRadius: 24,
paddingRight: 18,
paddingVertical: 12,
color: '#FFF',
},
border: {
borderWidth: 1,
borderColor: 'rgba(255,255,255,.1)',
},
roundedFull: {
borderRadius: 34,
},
fOpenSansLight: {
fontFamily: 'OpenSansLight',
},
fOpenSansBold: {
fontFamily: 'OpenSansBold',
},
fOpenSansSemiBold: {
fontFamily: 'OpenSansSemiBold',
},
fOpenSansMedium: {
fontFamily: 'OpenSansMedium',
},
fOpenSansExtraBold: {
fontFamily: 'OpenSansExtraBold',
},
fOpenSansRegular: {
fontFamily: 'OpenSansRegular',
},
textMuted: {
color: '#5F5F7A',
},
dotSeperator: {
width: 4,
height: 4,
backgroundColor: '#5F5F7A',
borderRadius: 50,
},
});

View File

@@ -0,0 +1,27 @@
// @ts-expect-error - no types
import nativewind from 'nativewind/preset';
import type { Config } from 'tailwindcss';
import colors from './app/constants/Colors';
export default {
content: ['./app/**/*.{js,jsx,ts,tsx}'],
presets: [
nativewind,
{
theme: {
extend: {
colors,
fontFamily: {
sans: ['OpenSansRegular'],
thin: ['OpenSansLight'],
normal: ['OpenSansRegular'],
medium: ['OpenSansMedium'],
semibold: ['OpenSansSemiBold'],
bold: ['OpenSansBold'],
extrabold: ['OpenSansExtra'],
},
},
},
},
],
} satisfies Config;

View File

@@ -1,8 +1,9 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"composite": true,
"outDir": "../../dist/out-tsc", "outDir": "../../dist/out-tsc",
"types": ["node"] "types": ["node"],
}, },
"files": ["../../node_modules/@nx/expo/typings/svg.d.ts"], "files": ["../../node_modules/@nx/expo/typings/svg.d.ts"],
"exclude": [ "exclude": [
@@ -11,5 +12,11 @@
"**/*.spec.tsx", "**/*.spec.tsx",
"test-setup.ts" "test-setup.ts"
], ],
"include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"] "include": [
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx",
"tailwind.config.ts"
]
} }

View File

@@ -3,27 +3,28 @@
"compilerOptions": { "compilerOptions": {
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"jsx": "react-native", "jsx": "react-native",
"lib": [ "lib": ["dom", "esnext"],
"dom",
"esnext"
],
"moduleResolution": "node", "moduleResolution": "node",
"skipLibCheck": true, "skipLibCheck": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"strict": true, "strict": true,
"declaration": true "declaration": true,
"allowJs": true,
"baseUrl": "./",
"paths": {
"@/*": [
"./app/*"
],
}
}, },
"files": [], "files": [],
"include": [ "include": [".expo/types/**/*.ts", "expo-env.d.ts"],
".expo/types/**/*.ts",
"expo-env.d.ts"
],
"references": [ "references": [
{ {
"path": "./tsconfig.app.json" "path": "./tsconfig.app.json",
}, },
{ {
"path": "./tsconfig.spec.json" "path": "./tsconfig.spec.json",
} },
] ],
} }

View File

@@ -1,6 +1,7 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"composite": true,
"outDir": "../../dist/out-tsc", "outDir": "../../dist/out-tsc",
"module": "commonjs", "module": "commonjs",
"types": ["jest", "node"] "types": ["jest", "node"]

View File

@@ -60,5 +60,16 @@
"react-native-svg-transformer": "1.3.0", "react-native-svg-transformer": "1.3.0",
"react-native-web": "~0.19.9", "react-native-web": "~0.19.9",
"tslib": "^2.3.0" "tslib": "^2.3.0"
},
"pnpm": {
"patchedDependencies": {
"nativewind@4.0.23": "patches/nativewind@4.0.23.patch"
}
},
"overrides": {
"react-refresh": "~0.14.0"
},
"resolutions": {
"react-refresh": "~0.14.0"
} }
} }

View File

@@ -0,0 +1,13 @@
diff --git a/dist/metro/transformer.js b/dist/metro/transformer.js
index 1bda43b116d02834db01a42e64dd302e3d3fe785..8ff7f8a324cd9a8531df915a704d604828959e78 100644
--- a/dist/metro/transformer.js
+++ b/dist/metro/transformer.js
@@ -16,7 +16,7 @@ new globalThis.WebSocket(\`\${url}:${config.nativewind.fastRefreshPort}\`).addEv
StyleSheet.registerCompiled(JSON.parse('${config.nativewind.parsedOutput}'));`, "utf8"), options);
}
else if (options.platform === "web") {
- return metro_transform_worker_1.default.transform(config, projectRoot, filename, Buffer.from(`require('${config.nativewind.outputPath}');`, "utf8"), options);
+ return metro_transform_worker_1.default.transform(config, projectRoot, filename, Buffer.from(`require('${config.nativewind.outputPath.replace(/\\/g, '\\\\')}');`, "utf8"), options);
}
else {
data = Buffer.from(config.nativewind.rawOutput, "utf8");

2445
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff