diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index 0063071..db364e6 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -1,21 +1,23 @@
import { Tabs } from 'expo-router';
import React from 'react';
import { Platform } from 'react-native';
-
-import { HapticTab } from '@/components/HapticTab';
+import { HapticTab } from '@/components/default/HapticTab';
import { IconSymbol } from '@/components/ui/IconSymbol';
import TabBarBackground from '@/components/ui/TabBarBackground';
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
-export default function TabLayout() {
- const colorScheme = useColorScheme();
+const TabLayout = () => {
+ const scheme = useColorScheme() ?? 'dark';
return (
@@ -35,12 +41,13 @@ export default function TabLayout() {
}}
/>
,
+ title: 'Settings',
+ tabBarIcon: ({ color }) => ,
}}
/>
);
-}
+};
+export default TabLayout;
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index f0a1f3e..11ace04 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -1,14 +1,12 @@
import { Image, StyleSheet, Platform } from 'react-native';
+import ParallaxScrollView from '@/components/default/ParallaxScrollView';
+import { ThemedText, ThemedView } from '@/components/theme/Theme';
-import { HelloWave } from '@/components/HelloWave';
-import ParallaxScrollView from '@/components/ParallaxScrollView';
-import { ThemedText } from '@/components/ThemedText';
-import { ThemedView } from '@/components/ThemedView';
-
-export default function HomeScreen() {
+const HomeScreen = () => {
return (
Welcome!
-
Step 1: Try it
@@ -53,7 +50,8 @@ export default function HomeScreen() {
);
-}
+};
+export default HomeScreen;
const styles = StyleSheet.create({
titleContainer: {
diff --git a/app/(tabs)/explore.tsx b/app/(tabs)/settings.tsx
similarity index 87%
rename from app/(tabs)/explore.tsx
rename to app/(tabs)/settings.tsx
index 016867b..406376e 100644
--- a/app/(tabs)/explore.tsx
+++ b/app/(tabs)/settings.tsx
@@ -1,27 +1,21 @@
import { StyleSheet, Image, Platform } from 'react-native';
-
-import { Collapsible } from '@/components/Collapsible';
-import { ExternalLink } from '@/components/ExternalLink';
-import ParallaxScrollView from '@/components/ParallaxScrollView';
-import { ThemedText } from '@/components/ThemedText';
-import { ThemedView } from '@/components/ThemedView';
+import { Collapsible } from '@/components/default/Collapsible';
+import { ExternalLink } from '@/components/default/ExternalLink';
+import ParallaxScrollView from '@/components/default/ParallaxScrollView';
+import { ThemedText, ThemedView } from '@/components/theme/Theme';
import { IconSymbol } from '@/components/ui/IconSymbol';
-export default function TabTwoScreen() {
+const TabTwoScreen = () => {
return (
+
}
>
- Explore
+ Settings
This app includes example code to help you get started.
@@ -94,7 +88,8 @@ export default function TabTwoScreen() {
);
-}
+};
+export default TabTwoScreen;
const styles = StyleSheet.create({
headerImage: {
diff --git a/app/+not-found.tsx b/app/+not-found.tsx
index ba91e7f..59a6497 100644
--- a/app/+not-found.tsx
+++ b/app/+not-found.tsx
@@ -1,22 +1,22 @@
import { Link, Stack } from 'expo-router';
import { StyleSheet } from 'react-native';
+import { ThemedText, ThemedView } from '@/components/theme/Theme';
+import TextButton from '@/components/theme/buttons/TextButton';
-import { ThemedText } from '@/components/ThemedText';
-import { ThemedView } from '@/components/ThemedView';
-
-export default function NotFoundScreen() {
+const NotFoundScreen = () => {
return (
<>
-
+
This screen doesn't exist.
-
- Go to home screen!
+
+
>
);
-}
+};
+export default NotFoundScreen;
const styles = StyleSheet.create({
container: {
@@ -25,8 +25,4 @@ const styles = StyleSheet.create({
justifyContent: 'center',
padding: 20,
},
- link: {
- marginTop: 15,
- paddingVertical: 15,
- },
});
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 13ca20f..efe8a45 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -5,14 +5,13 @@ import * as SplashScreen from 'expo-splash-screen';
import { StatusBar } from 'expo-status-bar';
import { useEffect } from 'react';
import 'react-native-reanimated';
-
import { useColorScheme } from '@/hooks/useColorScheme';
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
-export default function RootLayout() {
- const colorScheme = useColorScheme();
+const RootLayout = () => {
+ const scheme = useColorScheme() ?? 'dark';
const [loaded] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
});
@@ -28,7 +27,7 @@ export default function RootLayout() {
}
return (
-
+
@@ -36,4 +35,5 @@ export default function RootLayout() {
);
-}
+};
+export default RootLayout;
diff --git a/components/HelloWave.tsx b/components/HelloWave.tsx
deleted file mode 100644
index 7e4fd88..0000000
--- a/components/HelloWave.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useEffect } from 'react';
-import { StyleSheet } from 'react-native';
-import Animated, {
- useSharedValue,
- useAnimatedStyle,
- withTiming,
- withRepeat,
- withSequence,
-} from 'react-native-reanimated';
-
-import { ThemedText } from '@/components/ThemedText';
-
-export function HelloWave() {
- const rotationAnimation = useSharedValue(0);
-
- useEffect(() => {
- rotationAnimation.value = withRepeat(
- withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })),
- 4, // Run the animation 4 times
- );
- }, []);
-
- const animatedStyle = useAnimatedStyle(() => ({
- transform: [{ rotate: `${rotationAnimation.value}deg` }],
- }));
-
- return (
-
- 👋
-
- );
-}
-
-const styles = StyleSheet.create({
- text: {
- fontSize: 28,
- lineHeight: 32,
- marginTop: -6,
- },
-});
diff --git a/components/ThemedView.tsx b/components/ThemedView.tsx
deleted file mode 100644
index 4d2cb09..0000000
--- a/components/ThemedView.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { View, type ViewProps } from 'react-native';
-
-import { useThemeColor } from '@/hooks/useThemeColor';
-
-export type ThemedViewProps = ViewProps & {
- lightColor?: string;
- darkColor?: string;
-};
-
-export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
- const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
-
- return ;
-}
diff --git a/components/__tests__/ThemedText-test.tsx b/components/__tests__/ThemedText-test.tsx
deleted file mode 100644
index 1ac3225..0000000
--- a/components/__tests__/ThemedText-test.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import * as React from 'react';
-import renderer from 'react-test-renderer';
-
-import { ThemedText } from '../ThemedText';
-
-it(`renders correctly`, () => {
- const tree = renderer.create(Snapshot test!).toJSON();
-
- expect(tree).toMatchSnapshot();
-});
diff --git a/components/__tests__/__snapshots__/ThemedText-test.tsx.snap b/components/__tests__/__snapshots__/ThemedText-test.tsx.snap
deleted file mode 100644
index b68e53e..0000000
--- a/components/__tests__/__snapshots__/ThemedText-test.tsx.snap
+++ /dev/null
@@ -1,24 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-
- Snapshot test!
-
-`;
diff --git a/components/Collapsible.tsx b/components/default/Collapsible.tsx
similarity index 76%
rename from components/Collapsible.tsx
rename to components/default/Collapsible.tsx
index e8942c8..90219fb 100644
--- a/components/Collapsible.tsx
+++ b/components/default/Collapsible.tsx
@@ -1,15 +1,13 @@
import { PropsWithChildren, useState } from 'react';
import { StyleSheet, TouchableOpacity } from 'react-native';
-
-import { ThemedText } from '@/components/ThemedText';
-import { ThemedView } from '@/components/ThemedView';
+import { ThemedText, ThemedView } from '@/components/theme/Theme';
import { IconSymbol } from '@/components/ui/IconSymbol';
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
-export function Collapsible({ children, title }: PropsWithChildren & { title: string }) {
+export const Collapsible = ({ children, title }: PropsWithChildren & { title: string }) => {
const [isOpen, setIsOpen] = useState(false);
- const theme = useColorScheme() ?? 'light';
+ const scheme = useColorScheme() ?? 'dark';
return (
@@ -22,7 +20,7 @@ export function Collapsible({ children, title }: PropsWithChildren & { title: st
name='chevron.right'
size={18}
weight='medium'
- color={theme === 'light' ? Colors.light.icon : Colors.dark.icon}
+ color={scheme === 'light' ? Colors.light.icon : Colors.dark.icon}
style={{ transform: [{ rotate: isOpen ? '90deg' : '0deg' }] }}
/>
@@ -31,7 +29,7 @@ export function Collapsible({ children, title }: PropsWithChildren & { title: st
{isOpen && {children}}
);
-}
+};
const styles = StyleSheet.create({
heading: {
diff --git a/components/ExternalLink.tsx b/components/default/ExternalLink.tsx
similarity index 78%
rename from components/ExternalLink.tsx
rename to components/default/ExternalLink.tsx
index 18f611f..1c0e962 100644
--- a/components/ExternalLink.tsx
+++ b/components/default/ExternalLink.tsx
@@ -1,16 +1,16 @@
-import { Link } from 'expo-router';
+import { Link, RelativePathString } from 'expo-router';
import { openBrowserAsync } from 'expo-web-browser';
import { type ComponentProps } from 'react';
import { Platform } from 'react-native';
type Props = Omit, 'href'> & { href: string };
-export function ExternalLink({ href, ...rest }: Props) {
+export const ExternalLink = ({ href, ...rest }: Props) => {
return (
{
if (Platform.OS !== 'web') {
// Prevent the default behavior of linking to the default browser on native.
@@ -21,4 +21,4 @@ export function ExternalLink({ href, ...rest }: Props) {
}}
/>
);
-}
+};
diff --git a/components/HapticTab.tsx b/components/default/HapticTab.tsx
similarity index 88%
rename from components/HapticTab.tsx
rename to components/default/HapticTab.tsx
index 7f3981c..8579166 100644
--- a/components/HapticTab.tsx
+++ b/components/default/HapticTab.tsx
@@ -2,7 +2,7 @@ import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs';
import { PlatformPressable } from '@react-navigation/elements';
import * as Haptics from 'expo-haptics';
-export function HapticTab(props: BottomTabBarButtonProps) {
+export const HapticTab = (props: BottomTabBarButtonProps) => {
return (
);
-}
+};
diff --git a/components/ParallaxScrollView.tsx b/components/default/ParallaxScrollView.tsx
similarity index 75%
rename from components/ParallaxScrollView.tsx
rename to components/default/ParallaxScrollView.tsx
index 8b2aa7e..2af4f0a 100644
--- a/components/ParallaxScrollView.tsx
+++ b/components/default/ParallaxScrollView.tsx
@@ -6,8 +6,7 @@ import Animated, {
useAnimatedStyle,
useScrollViewOffset,
} from 'react-native-reanimated';
-
-import { ThemedView } from '@/components/ThemedView';
+import { ThemedView } from '@/components/theme/Theme';
import { useBottomTabOverflow } from '@/components/ui/TabBarBackground';
import { useColorScheme } from '@/hooks/useColorScheme';
@@ -16,14 +15,16 @@ const HEADER_HEIGHT = 250;
type Props = PropsWithChildren<{
headerImage: ReactElement;
headerBackgroundColor: { dark: string; light: string };
+ headerHeight?: number;
}>;
-export default function ParallaxScrollView({
+const ParallaxScrollView = ({
children,
headerImage,
headerBackgroundColor,
-}: Props) {
- const colorScheme = useColorScheme() ?? 'light';
+ headerHeight = HEADER_HEIGHT,
+}: Props) => {
+ const scheme = useColorScheme() ?? 'dark';
const scrollRef = useAnimatedRef();
const scrollOffset = useScrollViewOffset(scrollRef);
const bottom = useBottomTabOverflow();
@@ -33,12 +34,12 @@ export default function ParallaxScrollView({
{
translateY: interpolate(
scrollOffset.value,
- [-HEADER_HEIGHT, 0, HEADER_HEIGHT],
- [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75],
+ [-headerHeight, 0, headerHeight],
+ [-headerHeight / 2, 0, headerHeight * 0.75],
),
},
{
- scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]),
+ scale: interpolate(scrollOffset.value, [-headerHeight, 0, headerHeight], [2, 1, 1]),
},
],
};
@@ -55,7 +56,10 @@ export default function ParallaxScrollView({
@@ -65,14 +69,14 @@ export default function ParallaxScrollView({
);
-}
+};
+export default ParallaxScrollView;
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
- height: HEADER_HEIGHT,
overflow: 'hidden',
},
content: {
diff --git a/components/ThemedText.tsx b/components/theme/Theme.tsx
similarity index 72%
rename from components/ThemedText.tsx
rename to components/theme/Theme.tsx
index c0e1a78..0defec1 100644
--- a/components/ThemedText.tsx
+++ b/components/theme/Theme.tsx
@@ -1,22 +1,30 @@
+import { View, type ViewProps } from 'react-native';
import { Text, type TextProps, StyleSheet } from 'react-native';
-
import { useThemeColor } from '@/hooks/useThemeColor';
+export type ThemedViewProps = ViewProps & {
+ lightColor?: string;
+ darkColor?: string;
+};
export type ThemedTextProps = TextProps & {
lightColor?: string;
darkColor?: string;
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
};
-export function ThemedText({
+export const ThemedView = ({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) => {
+ const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
+ return ;
+};
+
+export const ThemedText = ({
style,
lightColor,
darkColor,
type = 'default',
...rest
-}: ThemedTextProps) {
+}: ThemedTextProps) => {
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
-
return (
);
-}
+};
const styles = StyleSheet.create({
default: {
diff --git a/components/theme/buttons/Button.tsx b/components/theme/buttons/Button.tsx
new file mode 100644
index 0000000..46bf696
--- /dev/null
+++ b/components/theme/buttons/Button.tsx
@@ -0,0 +1,58 @@
+import { StyleSheet, Pressable } from 'react-native';
+import { ThemedView } from '@/components/theme/Theme';
+import { Colors } from '@/constants/Colors';
+import { useColorScheme } from '@/hooks/useColorScheme';
+import { StyleProp, ViewStyle } from 'react-native';
+
+const DEFAULT_WIDTH = 320;
+const DEFAULT_HEIGHT = 68;
+
+type Props = {
+ width?: number;
+ height?: number;
+ onPress?: () => void;
+};
+
+const Button = ({
+ width,
+ height,
+ children,
+ onPress,
+}: Props & React.ComponentProps) => {
+ const scheme = useColorScheme() ?? 'dark';
+ return (
+
+
+ {children}
+
+
+ );
+};
+export default Button;
+
+const styles = StyleSheet.create({
+ buttonContainer: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 3,
+ },
+ button: {
+ borderRadius: 10,
+ width: '100%',
+ height: '100%',
+ alignItems: 'center',
+ justifyContent: 'center',
+ flexDirection: 'row',
+ },
+});
diff --git a/components/theme/buttons/TextButton.tsx b/components/theme/buttons/TextButton.tsx
new file mode 100644
index 0000000..65490f5
--- /dev/null
+++ b/components/theme/buttons/TextButton.tsx
@@ -0,0 +1,33 @@
+import Button from '@/components/theme/buttons/Button';
+import { ThemedText } from '@/components/theme/Theme';
+import { Colors } from '@/constants/Colors';
+import { useColorScheme } from '@/hooks/useColorScheme';
+
+const DEFAULT_FONT_SIZE = 16;
+
+type Props = {
+ width?: number;
+ height?: number;
+ text: string;
+ fontSize?: number;
+ onPress?: () => void;
+};
+
+const TextButton = ({ width, height, text, fontSize, onPress }: Props) => {
+ const scheme = useColorScheme() ?? 'dark';
+ return (
+
+ );
+};
+export default TextButton;
diff --git a/components/ui/IconSymbol.ios.tsx b/components/ui/IconSymbol.ios.tsx
index d8501ae..d0bd772 100644
--- a/components/ui/IconSymbol.ios.tsx
+++ b/components/ui/IconSymbol.ios.tsx
@@ -1,7 +1,7 @@
import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols';
import { StyleProp, ViewStyle } from 'react-native';
-export function IconSymbol({
+export const IconSymbol = ({
name,
size = 24,
color,
@@ -13,7 +13,7 @@ export function IconSymbol({
color: string;
style?: StyleProp;
weight?: SymbolWeight;
-}) {
+}) => {
return (
);
-}
+};
diff --git a/components/ui/IconSymbol.tsx b/components/ui/IconSymbol.tsx
index f1fabd4..3864699 100644
--- a/components/ui/IconSymbol.tsx
+++ b/components/ui/IconSymbol.tsx
@@ -3,7 +3,7 @@
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { SymbolWeight } from 'expo-symbols';
import React from 'react';
-import { OpaqueColorValue, StyleProp, ViewStyle } from 'react-native';
+import { OpaqueColorValue, StyleProp, TextStyle } from 'react-native';
// Add your SFSymbol to MaterialIcons mappings here.
const MAPPING = {
@@ -27,7 +27,7 @@ export type IconSymbolName = keyof typeof MAPPING;
*
* Icon `name`s are based on SFSymbols and require manual mapping to MaterialIcons.
*/
-export function IconSymbol({
+export const IconSymbol = ({
name,
size = 24,
color,
@@ -36,8 +36,8 @@ export function IconSymbol({
name: IconSymbolName;
size?: number;
color: string | OpaqueColorValue;
- style?: StyleProp;
+ style?: StyleProp;
weight?: SymbolWeight;
-}) {
+}) => {
return ;
-}
+};
diff --git a/components/ui/TabBarBackground.ios.tsx b/components/ui/TabBarBackground.ios.tsx
index b7fc921..d220d4a 100644
--- a/components/ui/TabBarBackground.ios.tsx
+++ b/components/ui/TabBarBackground.ios.tsx
@@ -3,7 +3,7 @@ import { BlurView } from 'expo-blur';
import { StyleSheet } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
-export default function BlurTabBarBackground() {
+const BlurTabBarBackground = () => {
return (
);
-}
+};
+export default BlurTabBarBackground;
-export function useBottomTabOverflow() {
+export const useBottomTabOverflow = () => {
const tabHeight = useBottomTabBarHeight();
const { bottom } = useSafeAreaInsets();
return tabHeight - bottom;
-}
+};
diff --git a/components/ui/TabBarBackground.tsx b/components/ui/TabBarBackground.tsx
index 70d1c3c..9f14010 100644
--- a/components/ui/TabBarBackground.tsx
+++ b/components/ui/TabBarBackground.tsx
@@ -1,6 +1,5 @@
// This is a shim for web and Android where the tab bar is generally opaque.
export default undefined;
-
-export function useBottomTabOverflow() {
+export const useBottomTabOverflow = () => {
return 0;
-}
+};