mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add ai and todo example templates for native frontends (#293)
This commit is contained in:
@@ -1,27 +1,44 @@
|
||||
import { TabBarIcon } from "@/components/tabbar-icon";
|
||||
import { useColorScheme } from "@/lib/use-color-scheme";
|
||||
import { Tabs } from "expo-router";
|
||||
|
||||
import { TabBarIcon } from "@/components/tabbar-icon";
|
||||
|
||||
export default function TabLayout() {
|
||||
const { isDarkColorScheme } = useColorScheme();
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
tabBarActiveTintColor: "black",
|
||||
tabBarActiveTintColor: isDarkColorScheme
|
||||
? "hsl(217.2 91.2% 59.8%)"
|
||||
: "hsl(221.2 83.2% 53.3%)",
|
||||
tabBarInactiveTintColor: isDarkColorScheme
|
||||
? "hsl(215 20.2% 65.1%)"
|
||||
: "hsl(215.4 16.3% 46.9%)",
|
||||
tabBarStyle: {
|
||||
backgroundColor: isDarkColorScheme
|
||||
? "hsl(222.2 84% 4.9%)"
|
||||
: "hsl(0 0% 100%)",
|
||||
borderTopColor: isDarkColorScheme
|
||||
? "hsl(217.2 32.6% 17.5%)"
|
||||
: "hsl(214.3 31.8% 91.4%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Tab One",
|
||||
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
|
||||
title: "Home",
|
||||
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="two"
|
||||
options={{
|
||||
title: "Tab Two",
|
||||
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
|
||||
title: "Explore",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<TabBarIcon name="compass" color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { Container } from "@/components/container";
|
||||
import { Text, View } from "react-native";
|
||||
import { ScrollView, Text, View } from "react-native";
|
||||
|
||||
export default function TabOne() {
|
||||
return (
|
||||
<Container>
|
||||
<View className="p-6 flex-1 justify-center">
|
||||
<Text className="text-2xl font-bold text-foreground text-center mb-4">
|
||||
Tab One
|
||||
</Text>
|
||||
<Text className="text-foreground text-center">
|
||||
This is the first tab of the application.
|
||||
</Text>
|
||||
</View>
|
||||
</Container>
|
||||
);
|
||||
return (
|
||||
<Container>
|
||||
<ScrollView className="flex-1 p-6">
|
||||
<View className="py-8">
|
||||
<Text className="text-3xl font-bold text-foreground mb-2">
|
||||
Tab One
|
||||
</Text>
|
||||
<Text className="text-lg text-muted-foreground">
|
||||
Explore the first section of your app
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { Container } from "@/components/container";
|
||||
import { Text, View } from "react-native";
|
||||
import { ScrollView, Text, View } from "react-native";
|
||||
|
||||
export default function TabTwo() {
|
||||
return (
|
||||
<Container>
|
||||
<View className="p-6 flex-1 justify-center">
|
||||
<Text className="text-2xl font-bold text-foreground text-center mb-4">
|
||||
Tab Two
|
||||
</Text>
|
||||
<Text className="text-foreground text-center">
|
||||
This is the second tab of the application.
|
||||
</Text>
|
||||
</View>
|
||||
</Container>
|
||||
);
|
||||
return (
|
||||
<Container>
|
||||
<ScrollView className="flex-1 p-6">
|
||||
<View className="py-8">
|
||||
<Text className="text-3xl font-bold text-foreground mb-2">
|
||||
Tab Two
|
||||
</Text>
|
||||
<Text className="text-lg text-muted-foreground">
|
||||
Discover more features and content
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,18 +17,6 @@ const DrawerLayout = () => {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{{#if (includes examples "todo")}}
|
||||
<Drawer.Screen
|
||||
name="todos"
|
||||
options=\{{
|
||||
headerTitle: "Todos",
|
||||
drawerLabel: "Todos",
|
||||
drawerIcon: ({ size, color }) => (
|
||||
<Ionicons name="checkbox-outline" size={size} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{{/if}}
|
||||
<Drawer.Screen
|
||||
name="(tabs)"
|
||||
options=\{{
|
||||
@@ -44,6 +32,34 @@ const DrawerLayout = () => {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{{#if (includes examples "todo")}}
|
||||
<Drawer.Screen
|
||||
name="todos"
|
||||
options=\{{
|
||||
headerTitle: "Todos",
|
||||
drawerLabel: "Todos",
|
||||
drawerIcon: ({ size, color }) => (
|
||||
<Ionicons name="checkbox-outline" size={size} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (includes examples "ai")}}
|
||||
<Drawer.Screen
|
||||
name="ai"
|
||||
options=\{{
|
||||
headerTitle: "AI",
|
||||
drawerLabel: "AI",
|
||||
drawerIcon: ({ size, color }) => (
|
||||
<Ionicons
|
||||
name="chatbubble-ellipses-outline"
|
||||
size={size}
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{{/if}}
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,52 +26,65 @@ export default function Home() {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ScrollView className="py-4 flex-1">
|
||||
<Text className="font-mono text-foreground text-2xl font-bold mb-6">
|
||||
BETTER T STACK
|
||||
</Text>
|
||||
|
||||
<View className="rounded-lg border border-foreground p-4">
|
||||
<Text className="mb-2 font-medium text-foreground">API Status</Text>
|
||||
<ScrollView showsVerticalScrollIndicator={false} className="flex-1">
|
||||
<Text className="font-mono text-foreground text-3xl font-bold mb-4">
|
||||
BETTER T STACK
|
||||
</Text>
|
||||
<View className="bg-card border border-border rounded-xl p-6 mb-6 shadow-sm">
|
||||
{{#if (eq backend "convex")}}
|
||||
<View className="flex-row items-center gap-2">
|
||||
<View className="flex-row items-center gap-3">
|
||||
<View
|
||||
className={`h-2.5 w-2.5 rounded-full ${
|
||||
healthCheck ? "bg-green-500" : "bg-red-500"
|
||||
className={`h-3 w-3 rounded-full ${
|
||||
healthCheck ? "bg-green-500" : "bg-orange-500"
|
||||
}`}
|
||||
/>
|
||||
<Text className="text-sm text-foreground">
|
||||
<View className="flex-1">
|
||||
<Text className="text-sm font-medium text-card-foreground">
|
||||
Convex
|
||||
</Text>
|
||||
<Text className="text-xs text-muted-foreground">
|
||||
{healthCheck === undefined
|
||||
? "Checking..."
|
||||
? "Checking connection..."
|
||||
: healthCheck === "OK"
|
||||
? "Connected"
|
||||
: "Error"}
|
||||
</Text>
|
||||
? "All systems operational"
|
||||
: "Service unavailable"}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
{{else}}
|
||||
{{#unless (eq api "none")}}
|
||||
<View className="flex-row items-center gap-2">
|
||||
<View className="flex-row items-center gap-3">
|
||||
<View
|
||||
className={`h-2.5 w-2.5 rounded-full ${
|
||||
healthCheck.data ? "bg-green-500" : "bg-red-500"
|
||||
className={`h-3 w-3 rounded-full ${
|
||||
healthCheck.data ? "bg-green-500" : "bg-orange-500"
|
||||
}`}
|
||||
/>
|
||||
<Text className="text-sm text-foreground">
|
||||
{{#if (eq api "orpc")}}
|
||||
<View className="flex-1">
|
||||
<Text className="text-sm font-medium text-card-foreground">
|
||||
{{#if (eq api "orpc")}}
|
||||
ORPC
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
TRPC
|
||||
{{/if}}
|
||||
</Text>
|
||||
<Text className="text-xs text-muted-foreground">
|
||||
{{#if (eq api "orpc")}}
|
||||
{healthCheck.isLoading
|
||||
? "Checking..."
|
||||
? "Checking connection..."
|
||||
: healthCheck.data
|
||||
? "Connected"
|
||||
: "Disconnected"}
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
? "All systems operational"
|
||||
: "Service unavailable"}
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
{healthCheck.isLoading
|
||||
? "Checking..."
|
||||
? "Checking connection..."
|
||||
: healthCheck.data
|
||||
? "Connected"
|
||||
: "Disconnected"}
|
||||
{{/if}}
|
||||
</Text>
|
||||
? "All systems operational"
|
||||
: "Service unavailable"}
|
||||
{{/if}}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
import { Link, Stack } from 'expo-router';
|
||||
import { Text } from 'react-native';
|
||||
|
||||
import { Container } from '@/components/container';
|
||||
import { Container } from "@/components/container";
|
||||
import { Link, Stack } from "expo-router";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: 'Oops!' }} />
|
||||
<Stack.Screen options={{ title: "Oops!" }} />
|
||||
<Container>
|
||||
<Text className="text-xl font-bold">This screen doesn't exist.</Text>
|
||||
<Link href="/" className="mt-4 pt-4">
|
||||
<Text className="text-base text-[#2e78b7]">Go to home screen!</Text>
|
||||
</Link>
|
||||
<View className="flex-1 justify-center items-center p-6">
|
||||
<View className="items-center">
|
||||
<Text className="text-6xl mb-4">🤔</Text>
|
||||
<Text className="text-2xl font-bold text-foreground mb-2 text-center">
|
||||
Page Not Found
|
||||
</Text>
|
||||
<Text className="text-muted-foreground text-center mb-8 max-w-sm">
|
||||
Sorry, the page you're looking for doesn't exist.
|
||||
</Text>
|
||||
<Link href="/" asChild>
|
||||
<Text className="text-primary font-medium bg-primary/10 px-6 py-3 rounded-lg">
|
||||
Go to Home
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
{{#if (includes examples "ai")}}
|
||||
import "@/polyfills";
|
||||
{{/if}}
|
||||
{{#if (eq backend "convex")}}
|
||||
import { ConvexProvider, ConvexReactClient } from "convex/react";
|
||||
{{else}}
|
||||
|
||||
@@ -2,11 +2,13 @@ import { Container } from "@/components/container";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function Modal() {
|
||||
return (
|
||||
<Container>
|
||||
<View className="flex-1 justify-center items-center">
|
||||
<Text className="text-xl font-bold text-foreground">Modal View</Text>
|
||||
</View>
|
||||
</Container>
|
||||
);
|
||||
return (
|
||||
<Container>
|
||||
<View className="flex-1 p-6">
|
||||
<View className="flex-row items-center justify-between mb-8">
|
||||
<Text className="text-2xl font-bold text-foreground">Modal</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React from "react";
|
||||
import { SafeAreaView } from "react-native";
|
||||
|
||||
export const Container = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<SafeAreaView className="flex flex-1 p-4 bg-background">
|
||||
{children}
|
||||
</SafeAreaView>
|
||||
<SafeAreaView className="flex-1 bg-background">{children}</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
import FontAwesome from '@expo/vector-icons/FontAwesome';
|
||||
import { forwardRef } from 'react';
|
||||
import { Pressable, StyleSheet } from 'react-native';
|
||||
import FontAwesome from "@expo/vector-icons/FontAwesome";
|
||||
import { forwardRef } from "react";
|
||||
import { Pressable } from "react-native";
|
||||
|
||||
export const HeaderButton = forwardRef<typeof Pressable, { onPress?: () => void }>(
|
||||
({ onPress }, ref) => {
|
||||
return (
|
||||
<Pressable onPress={onPress}>
|
||||
{({ pressed }) => (
|
||||
<FontAwesome
|
||||
name="info-circle"
|
||||
size={25}
|
||||
color="gray"
|
||||
style={[
|
||||
styles.headerRight,
|
||||
{
|
||||
opacity: pressed ? 0.5 : 1,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
headerRight: {
|
||||
marginRight: 15,
|
||||
},
|
||||
export const HeaderButton = forwardRef<
|
||||
typeof Pressable,
|
||||
{ onPress?: () => void }
|
||||
>(({ onPress }, ref) => {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
className="p-2 mr-2 rounded-lg bg-secondary/50 active:bg-secondary"
|
||||
>
|
||||
{({ pressed }) => (
|
||||
<FontAwesome
|
||||
name="info-circle"
|
||||
size={20}
|
||||
className="text-secondary-foreground"
|
||||
style={{
|
||||
opacity: pressed ? 0.7 : 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import FontAwesome from '@expo/vector-icons/FontAwesome';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import FontAwesome from "@expo/vector-icons/FontAwesome";
|
||||
|
||||
export const TabBarIcon = (props: {
|
||||
name: React.ComponentProps<typeof FontAwesome>['name'];
|
||||
name: React.ComponentProps<typeof FontAwesome>["name"];
|
||||
color: string;
|
||||
}) => {
|
||||
return <FontAwesome size={28} style={styles.tabBarIcon} {...props} />;
|
||||
return <FontAwesome size={24} style={{ marginBottom: -3 }} {...props} />;
|
||||
};
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
tabBarIcon: {
|
||||
marginBottom: -3,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,21 +5,46 @@
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96%;
|
||||
--secondary-foreground: 222.2 84% 4.9%;
|
||||
--muted: 210 40% 96%;
|
||||
--muted-foreground: 215.4 16.3% 40%;
|
||||
--accent: 210 40% 96%;
|
||||
--accent-foreground: 222.2 84% 4.9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 221.2 83.2% 53.3%;
|
||||
--radius: 8px;
|
||||
}
|
||||
|
||||
.dark:root {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 84% 4.9%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 70%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 72% 51%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 224.3 76.3% 94.1%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
export const NAV_THEME = {
|
||||
light: {
|
||||
background: "hsl(0 0% 100%)",
|
||||
border: "hsl(240 5.9% 90%)",
|
||||
border: "hsl(220 13% 91%)",
|
||||
card: "hsl(0 0% 100%)",
|
||||
notification: "hsl(0 84.2% 60.2%)",
|
||||
primary: "hsl(240 5.9% 10%)",
|
||||
text: "hsl(240 10% 3.9%)",
|
||||
primary: "hsl(221.2 83.2% 53.3%)",
|
||||
text: "hsl(222.2 84% 4.9%)",
|
||||
},
|
||||
dark: {
|
||||
background: "hsl(240 10% 3.9%)",
|
||||
border: "hsl(240 3.7% 15.9%)",
|
||||
card: "hsl(240 10% 3.9%)",
|
||||
background: "hsl(222.2 84% 4.9%)",
|
||||
border: "hsl(217.2 32.6% 17.5%)",
|
||||
card: "hsl(222.2 84% 4.9%)",
|
||||
notification: "hsl(0 72% 51%)",
|
||||
primary: "hsl(0 0% 98%)",
|
||||
text: "hsl(0 0% 98%)",
|
||||
primary: "hsl(217.2 91.2% 59.8%)",
|
||||
text: "hsl(210 40% 98%)",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
"@react-navigation/native": "^7.0.14",
|
||||
"@tanstack/react-form": "^1.0.5",
|
||||
"@tanstack/react-query": "^5.69.2",
|
||||
{{#if (includes examples "ai")}}
|
||||
"@stardazed/streams-text-encoding": "^1.0.2",
|
||||
"@ungap/structured-clone": "^1.3.0",
|
||||
{{/if}}
|
||||
"expo": "^53.0.4",
|
||||
"expo-constants": "~17.1.4",
|
||||
"expo-linking": "~7.1.4",
|
||||
@@ -11,6 +11,14 @@ module.exports = {
|
||||
colors: {
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
@@ -19,9 +27,28 @@ module.exports = {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
radius: "var(--radius)",
|
||||
},
|
||||
borderRadius: {
|
||||
xl: "calc(var(--radius) + 4px)",
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
borderWidth: {
|
||||
hairline: hairlineWidth(),
|
||||
|
||||
@@ -10,23 +10,28 @@ export default function TabLayout() {
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
tabBarActiveTintColor: theme.colors.primary,
|
||||
tabBarInactiveTintColor: theme.colors.mutedForeground,
|
||||
tabBarStyle: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderTopColor: theme.colors.border,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Tab One",
|
||||
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
|
||||
title: "Home",
|
||||
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="two"
|
||||
options={{
|
||||
title: "Tab Two",
|
||||
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
|
||||
title: "Explore",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<TabBarIcon name="compass" color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
import { Stack } from "expo-router";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { Container } from "@/components/container";
|
||||
import { Text, View } from "react-native";
|
||||
import { ScrollView, Text, View } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: "Tab One" }} />
|
||||
<Container>
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text}>Tab One</Text>
|
||||
<Container>
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
<View style={styles.headerSection}>
|
||||
<Text style={styles.title}>Tab One</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
Explore the first section of your app
|
||||
</Text>
|
||||
</View>
|
||||
</Container>
|
||||
</>
|
||||
</ScrollView>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
text: {
|
||||
color: theme.colors.typography,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingBottom: 100,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: theme.spacing.lg,
|
||||
},
|
||||
headerSection: {
|
||||
paddingVertical: theme.spacing.xl,
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.fontSize["3xl"],
|
||||
fontWeight: "bold",
|
||||
color: theme.colors.foreground,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: theme.fontSize.lg,
|
||||
color: theme.colors.mutedForeground,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
import { Stack } from "expo-router";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { Container } from "@/components/container";
|
||||
import { Text, View } from "react-native";
|
||||
import { ScrollView, Text, View } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
|
||||
export default function Home() {
|
||||
export default function TabTwo() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: "Tab Two" }} />
|
||||
<Container>
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text}>Tab Two</Text>
|
||||
<Container>
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
<View style={styles.headerSection}>
|
||||
<Text style={styles.title}>Tab Two</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
Discover more features and content
|
||||
</Text>
|
||||
</View>
|
||||
</Container>
|
||||
</>
|
||||
</ScrollView>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
text: {
|
||||
color: theme.colors.typography,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingBottom: 100,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: theme.spacing.lg,
|
||||
},
|
||||
headerSection: {
|
||||
paddingVertical: theme.spacing.xl,
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.fontSize["3xl"],
|
||||
fontWeight: "bold",
|
||||
color: theme.colors.foreground,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: theme.fontSize.lg,
|
||||
color: theme.colors.mutedForeground,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -10,26 +10,26 @@ const DrawerLayout = () => {
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
screenOptions={{
|
||||
screenOptions=\{{
|
||||
headerStyle: {
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: theme.colors.typography,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
headerTintColor: theme.colors.typography,
|
||||
headerTintColor: theme.colors.foreground,
|
||||
drawerStyle: {
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
drawerLabelStyle: {
|
||||
color: theme.colors.typography,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
drawerInactiveTintColor: theme.colors.typography,
|
||||
drawerInactiveTintColor: theme.colors.mutedForeground,
|
||||
}}
|
||||
>
|
||||
<Drawer.Screen
|
||||
name="index"
|
||||
options={{
|
||||
options=\{{
|
||||
headerTitle: "Home",
|
||||
drawerLabel: "Home",
|
||||
drawerIcon: ({ size, color }) => (
|
||||
@@ -39,7 +39,7 @@ const DrawerLayout = () => {
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="(tabs)"
|
||||
options={{
|
||||
options=\{{
|
||||
headerTitle: "Tabs",
|
||||
drawerLabel: "Tabs",
|
||||
drawerIcon: ({ size, color }) => (
|
||||
@@ -52,6 +52,34 @@ const DrawerLayout = () => {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{{#if (includes examples "todo")}}
|
||||
<Drawer.Screen
|
||||
name="todos"
|
||||
options=\{{
|
||||
headerTitle: "Todos",
|
||||
drawerLabel: "Todos",
|
||||
drawerIcon: ({ size, color }) => (
|
||||
<Ionicons name="checkbox-outline" size={size} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (includes examples "ai")}}
|
||||
<Drawer.Screen
|
||||
name="ai"
|
||||
options=\{{
|
||||
headerTitle: "AI",
|
||||
drawerLabel: "AI",
|
||||
drawerIcon: ({ size, color }) => (
|
||||
<Ionicons
|
||||
name="chatbubble-ellipses-outline"
|
||||
size={size}
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{{/if}}
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
@@ -28,56 +28,79 @@ export default function Home() {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ScrollView contentContainerStyle={styles.pageContainer}>
|
||||
<Text style={styles.headerTitle}>BETTER T STACK</Text>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.container}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<Text className="font-mono text-foreground text-3xl font-bold mb-4">
|
||||
BETTER T STACK
|
||||
</Text>
|
||||
<View style={styles.statusCard}>
|
||||
<View style={styles.statusHeader}>
|
||||
<Text style={styles.statusTitle}>System Status</Text>
|
||||
<View style={styles.statusBadge}>
|
||||
<Text style={styles.statusBadgeText}>LIVE</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.apiStatusCard}>
|
||||
<Text style={styles.cardTitle}>API Status</Text>
|
||||
{{#if (eq backend "convex")}}
|
||||
<View style={styles.apiStatusRow}>
|
||||
<View style={styles.statusRow}>
|
||||
<View
|
||||
style={[
|
||||
styles.statusIndicatorDot,
|
||||
styles.statusDot,
|
||||
healthCheck === "OK"
|
||||
? styles.statusIndicatorGreen
|
||||
: styles.statusIndicatorRed,
|
||||
? styles.statusDotSuccess
|
||||
: styles.statusDotWarning,
|
||||
]}
|
||||
/>
|
||||
<Text style={styles.statusText}>
|
||||
{healthCheck === undefined
|
||||
? "Checking..."
|
||||
: healthCheck === "OK"
|
||||
? "Connected"
|
||||
: "Error"}
|
||||
</Text>
|
||||
<View style={styles.statusContent}>
|
||||
<Text style={styles.statusLabel}>Convex</Text>
|
||||
<Text style={styles.statusDescription}>
|
||||
{healthCheck === undefined
|
||||
? "Checking connection..."
|
||||
: healthCheck === "OK"
|
||||
? "All systems operational"
|
||||
: "Service unavailable"}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
{{else}}
|
||||
{{#unless (eq api "none")}}
|
||||
<View style={styles.apiStatusRow}>
|
||||
<View style={styles.statusRow}>
|
||||
<View
|
||||
style={[
|
||||
styles.statusIndicatorDot,
|
||||
styles.statusDot,
|
||||
healthCheck.data
|
||||
? styles.statusIndicatorGreen
|
||||
: styles.statusIndicatorRed,
|
||||
? styles.statusDotSuccess
|
||||
: styles.statusDotWarning,
|
||||
]}
|
||||
/>
|
||||
<Text style={styles.statusText}>
|
||||
{{#if (eq api "orpc")}}
|
||||
{healthCheck.isLoading
|
||||
? "Checking..."
|
||||
: healthCheck.data
|
||||
? "Connected"
|
||||
: "Disconnected"}
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
{healthCheck.isLoading
|
||||
? "Checking..."
|
||||
: healthCheck.data
|
||||
? "Connected"
|
||||
: "Disconnected"}
|
||||
{{/if}}
|
||||
</Text>
|
||||
<View style={styles.statusContent}>
|
||||
<Text style={styles.statusLabel}>
|
||||
{{#if (eq api "orpc")}}
|
||||
ORPC
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
TRPC
|
||||
{{/if}}
|
||||
</Text>
|
||||
<Text style={styles.statusDescription}>
|
||||
{{#if (eq api "orpc")}}
|
||||
{healthCheck.isLoading
|
||||
? "Checking connection..."
|
||||
: healthCheck.data
|
||||
? "All systems operational"
|
||||
: "Service unavailable"}
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
{healthCheck.isLoading
|
||||
? "Checking connection..."
|
||||
: healthCheck.data
|
||||
? "All systems operational"
|
||||
: "Service unavailable"}
|
||||
{{/if}}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
@@ -88,44 +111,84 @@ export default function Home() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
pageContainer: {
|
||||
paddingHorizontal: 8,
|
||||
container: {
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
},
|
||||
headerTitle: {
|
||||
color: theme?.colors?.typography,
|
||||
fontSize: 30,
|
||||
heroSection: {
|
||||
paddingVertical: theme.spacing.xl,
|
||||
},
|
||||
heroTitle: {
|
||||
fontSize: theme.fontSize["4xl"],
|
||||
fontWeight: "bold",
|
||||
marginBottom: 16,
|
||||
color: theme.colors.foreground,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
apiStatusCard: {
|
||||
marginBottom: 24,
|
||||
borderRadius: 8,
|
||||
heroSubtitle: {
|
||||
fontSize: theme.fontSize.lg,
|
||||
color: theme.colors.mutedForeground,
|
||||
lineHeight: 28,
|
||||
},
|
||||
statusCard: {
|
||||
backgroundColor: theme.colors.card,
|
||||
borderWidth: 1,
|
||||
borderColor: theme?.colors?.border,
|
||||
padding: 16,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
padding: theme.spacing.lg,
|
||||
marginBottom: theme.spacing.lg,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 3,
|
||||
elevation: 2,
|
||||
},
|
||||
cardTitle: {
|
||||
marginBottom: 12,
|
||||
fontWeight: "500",
|
||||
color: theme?.colors?.typography,
|
||||
},
|
||||
apiStatusRow: {
|
||||
statusHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
justifyContent: "space-between",
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
statusIndicatorDot: {
|
||||
height: 12,
|
||||
width: 12,
|
||||
statusTitle: {
|
||||
fontSize: theme.fontSize.lg,
|
||||
fontWeight: "600",
|
||||
color: theme.colors.cardForeground,
|
||||
},
|
||||
statusBadge: {
|
||||
backgroundColor: theme.colors.secondary,
|
||||
paddingHorizontal: theme.spacing.sm + 4,
|
||||
paddingVertical: theme.spacing.xs,
|
||||
borderRadius: 9999,
|
||||
},
|
||||
statusIndicatorGreen: {
|
||||
statusBadgeText: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: "500",
|
||||
color: theme.colors.secondaryForeground,
|
||||
},
|
||||
statusRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing.sm + 4,
|
||||
},
|
||||
statusDot: {
|
||||
height: 12,
|
||||
width: 12,
|
||||
borderRadius: 6,
|
||||
},
|
||||
statusDotSuccess: {
|
||||
backgroundColor: theme.colors.success,
|
||||
},
|
||||
statusIndicatorRed: {
|
||||
backgroundColor: theme.colors.destructive,
|
||||
statusDotWarning: {
|
||||
backgroundColor: "#F59E0B",
|
||||
},
|
||||
statusText: {
|
||||
color: theme?.colors?.typography,
|
||||
statusContent: {
|
||||
flex: 1,
|
||||
},
|
||||
statusLabel: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: "500",
|
||||
color: theme.colors.cardForeground,
|
||||
},
|
||||
statusDescription: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.mutedForeground,
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -1,34 +1,65 @@
|
||||
import { Link, Stack } from "expo-router";
|
||||
import { Text } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
|
||||
import { Container } from "@/components/container";
|
||||
import { Link, Stack } from "expo-router";
|
||||
import { Text, View } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: "Oops!" }} />
|
||||
<Container>
|
||||
<Text style={styles.title}>This screen doesn't exist.</Text>
|
||||
<Link href="/" style={styles.link}>
|
||||
<Text style={styles.linkText}>Go to home screen!</Text>
|
||||
</Link>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.emoji}>🤔</Text>
|
||||
<Text style={styles.title}>Page Not Found</Text>
|
||||
<Text style={styles.description}>
|
||||
Sorry, the page you're looking for doesn't exist.
|
||||
</Text>
|
||||
<Link href="/" style={styles.button}>
|
||||
<Text style={styles.buttonText}>Go to Home</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: theme.spacing.lg,
|
||||
},
|
||||
content: {
|
||||
alignItems: "center",
|
||||
},
|
||||
emoji: {
|
||||
fontSize: 64,
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
fontSize: theme.fontSize["2xl"],
|
||||
fontWeight: "bold",
|
||||
color: theme.colors.typography,
|
||||
color: theme.colors.foreground,
|
||||
marginBottom: theme.spacing.sm,
|
||||
textAlign: "center",
|
||||
},
|
||||
link: {
|
||||
marginTop: 16,
|
||||
paddingVertical: 16,
|
||||
description: {
|
||||
color: theme.colors.mutedForeground,
|
||||
textAlign: "center",
|
||||
marginBottom: theme.spacing.xl,
|
||||
maxWidth: 280,
|
||||
},
|
||||
linkText: {
|
||||
fontSize: 14,
|
||||
button: {
|
||||
backgroundColor: `${theme.colors.primary}1A`, // 10% opacity
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
paddingVertical: theme.spacing.sm + 4,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
},
|
||||
buttonText: {
|
||||
color: theme.colors.primary,
|
||||
fontWeight: "500",
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
{{#if (includes examples "ai")}}
|
||||
import "@/polyfills";
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
import { queryClient } from "@/utils/trpc";
|
||||
{{/if}}
|
||||
@@ -39,9 +42,9 @@ export default function RootLayout() {
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: theme.colors.typography,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
headerTintColor: theme.colors.typography,
|
||||
headerTintColor: theme.colors.foreground,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="(drawer)" options=\{{ headerShown: false }} />
|
||||
@@ -62,9 +65,9 @@ export default function RootLayout() {
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: theme.colors.typography,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
headerTintColor: theme.colors.typography,
|
||||
headerTintColor: theme.colors.foreground,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="(drawer)" options=\{{ headerShown: false }} />
|
||||
@@ -83,9 +86,9 @@ export default function RootLayout() {
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: theme.colors.typography,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
headerTintColor: theme.colors.typography,
|
||||
headerTintColor: theme.colors.foreground,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="(drawer)" options=\{{ headerShown: false }} />
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
import { Container } from "@/components/container";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import { Platform, Text, View } from "react-native";
|
||||
import { Text, View } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
|
||||
export default function Modal() {
|
||||
return (
|
||||
<>
|
||||
<StatusBar style={Platform.OS === "ios" ? "light" : "auto"} />
|
||||
<Container>
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text}>Model</Text>
|
||||
<Container>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Modal</Text>
|
||||
</View>
|
||||
</Container>
|
||||
</>
|
||||
</View>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
text: {
|
||||
color: theme.colors.typography,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingBottom: 100,
|
||||
justifyContent: "center",
|
||||
padding: theme.spacing.lg,
|
||||
},
|
||||
header: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.fontSize["2xl"],
|
||||
fontWeight: "bold",
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { SafeAreaView } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
|
||||
export const Container = ({ children }: { children: React.ReactNode }) => {
|
||||
return <View style={styles.container}>{children}</View>;
|
||||
return <SafeAreaView style={styles.container}>{children}</SafeAreaView>;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create((theme, rt) => ({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingBottom: rt.insets.bottom,
|
||||
backgroundColor: theme.colors.background,
|
||||
transform: [
|
||||
{
|
||||
translateY: rt.insets.ime * -1,
|
||||
},
|
||||
],
|
||||
paddingBottom: rt.insets.bottom,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
import FontAwesome from '@expo/vector-icons/FontAwesome';
|
||||
import { forwardRef } from 'react';
|
||||
import { Pressable, StyleSheet } from 'react-native';
|
||||
import FontAwesome from "@expo/vector-icons/FontAwesome";
|
||||
import { forwardRef } from "react";
|
||||
import { Pressable } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
|
||||
export const HeaderButton = forwardRef<typeof Pressable, { onPress?: () => void }>(
|
||||
({ onPress }, ref) => {
|
||||
return (
|
||||
<Pressable onPress={onPress}>
|
||||
{({ pressed }) => (
|
||||
<FontAwesome
|
||||
name="info-circle"
|
||||
size={25}
|
||||
color="gray"
|
||||
style={[
|
||||
styles.headerRight,
|
||||
{
|
||||
opacity: pressed ? 0.5 : 1,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
headerRight: {
|
||||
marginRight: 15,
|
||||
},
|
||||
export const HeaderButton = forwardRef<
|
||||
typeof Pressable,
|
||||
{ onPress?: () => void }
|
||||
>(({ onPress }, ref) => {
|
||||
return (
|
||||
<Pressable onPress={onPress} style={styles.button}>
|
||||
{({ pressed }) => (
|
||||
<FontAwesome
|
||||
name="info-circle"
|
||||
size={20}
|
||||
color={styles.icon.color}
|
||||
style={{
|
||||
opacity: pressed ? 0.7 : 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
button: {
|
||||
padding: theme.spacing.sm,
|
||||
marginRight: theme.spacing.sm,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
backgroundColor: `${theme.colors.secondary}80`, // 50% opacity
|
||||
},
|
||||
icon: {
|
||||
color: theme.colors.secondaryForeground,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import FontAwesome from '@expo/vector-icons/FontAwesome';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import FontAwesome from "@expo/vector-icons/FontAwesome";
|
||||
|
||||
export const TabBarIcon = (props: {
|
||||
name: React.ComponentProps<typeof FontAwesome>['name'];
|
||||
name: React.ComponentProps<typeof FontAwesome>["name"];
|
||||
color: string;
|
||||
}) => {
|
||||
return <FontAwesome size={28} style={styles.tabBarIcon} {...props} />;
|
||||
return <FontAwesome size={24} style={{ marginBottom: -3 }} {...props} />;
|
||||
};
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
tabBarIcon: {
|
||||
marginBottom: -3,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
"@react-navigation/bottom-tabs": "^7.0.5",
|
||||
"@react-navigation/drawer": "^7.0.0",
|
||||
"@react-navigation/native": "^7.0.3",
|
||||
{{#if (includes examples "ai")}}
|
||||
"@stardazed/streams-text-encoding": "^1.0.2",
|
||||
"@ungap/structured-clone": "^1.3.0",
|
||||
{{/if}}
|
||||
"@tanstack/react-form": "^1.0.5",
|
||||
"babel-plugin-react-compiler": "^19.0.0-beta-af1b7da-20250417",
|
||||
"expo": "^53.0.8",
|
||||
@@ -34,7 +38,7 @@
|
||||
"react-native-reanimated": "~3.17.4",
|
||||
"react-native-safe-area-context": "5.4.0",
|
||||
"react-native-screens": "~4.10.0",
|
||||
"react-native-unistyles": "3.0.0-rc.3",
|
||||
"react-native-unistyles": "^3.0.0-rc.4",
|
||||
"react-native-web": "^0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -1,35 +1,98 @@
|
||||
const sharedColors = {
|
||||
success: "#22C55E",
|
||||
destructive: "#DC2626",
|
||||
border: "#D1D5DB",
|
||||
destructive: "#EF4444",
|
||||
warning: "#F59E0B",
|
||||
info: "#3B82F6",
|
||||
} as const;
|
||||
|
||||
export const lightTheme = {
|
||||
colors: {
|
||||
...sharedColors,
|
||||
typography: "#000000",
|
||||
background: "#ffffff",
|
||||
primary: "#3B82F6",
|
||||
typography: "hsl(222.2 84% 4.9%)",
|
||||
background: "hsl(0 0% 100%)",
|
||||
foreground: "hsl(222.2 84% 4.9%)",
|
||||
card: "hsl(0 0% 100%)",
|
||||
cardForeground: "hsl(222.2 84% 4.9%)",
|
||||
primary: "hsl(221.2 83.2% 53.3%)",
|
||||
primaryForeground: "hsl(210 40% 98%)",
|
||||
secondary: "hsl(210 40% 96%)",
|
||||
secondaryForeground: "hsl(222.2 84% 4.9%)",
|
||||
muted: "hsl(210 40% 96%)",
|
||||
mutedForeground: "hsl(215.4 16.3% 46.9%)",
|
||||
accent: "hsl(210 40% 96%)",
|
||||
accentForeground: "hsl(222.2 84% 4.9%)",
|
||||
border: "hsl(214.3 31.8% 91.4%)",
|
||||
input: "hsl(214.3 31.8% 91.4%)",
|
||||
ring: "hsl(221.2 83.2% 53.3%)",
|
||||
},
|
||||
margins: {
|
||||
sm: 2,
|
||||
md: 4,
|
||||
lg: 8,
|
||||
xl: 12,
|
||||
spacing: {
|
||||
xs: 4,
|
||||
sm: 8,
|
||||
md: 16,
|
||||
lg: 24,
|
||||
xl: 32,
|
||||
xxl: 48,
|
||||
},
|
||||
borderRadius: {
|
||||
sm: 6,
|
||||
md: 8,
|
||||
lg: 12,
|
||||
xl: 16,
|
||||
},
|
||||
fontSize: {
|
||||
xs: 12,
|
||||
sm: 14,
|
||||
base: 16,
|
||||
lg: 18,
|
||||
xl: 20,
|
||||
"2xl": 24,
|
||||
"3xl": 30,
|
||||
"4xl": 36,
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const darkTheme = {
|
||||
colors: {
|
||||
...sharedColors,
|
||||
typography: "#ffffff",
|
||||
background: "#000000",
|
||||
primary: "#60A5FA",
|
||||
typography: "hsl(210 40% 98%)",
|
||||
background: "hsl(222.2 84% 4.9%)",
|
||||
foreground: "hsl(210 40% 98%)",
|
||||
card: "hsl(222.2 84% 4.9%)",
|
||||
cardForeground: "hsl(210 40% 98%)",
|
||||
primary: "hsl(217.2 91.2% 59.8%)",
|
||||
primaryForeground: "hsl(222.2 84% 4.9%)",
|
||||
secondary: "hsl(217.2 32.6% 17.5%)",
|
||||
secondaryForeground: "hsl(210 40% 98%)",
|
||||
muted: "hsl(217.2 32.6% 17.5%)",
|
||||
mutedForeground: "hsl(215 20.2% 65.1%)",
|
||||
accent: "hsl(217.2 32.6% 17.5%)",
|
||||
accentForeground: "hsl(210 40% 98%)",
|
||||
border: "hsl(217.2 32.6% 17.5%)",
|
||||
input: "hsl(217.2 32.6% 17.5%)",
|
||||
ring: "hsl(224.3 76.3% 94.1%)",
|
||||
},
|
||||
margins: {
|
||||
sm: 2,
|
||||
md: 4,
|
||||
lg: 8,
|
||||
xl: 12,
|
||||
spacing: {
|
||||
xs: 4,
|
||||
sm: 8,
|
||||
md: 16,
|
||||
lg: 24,
|
||||
xl: 32,
|
||||
xxl: 48,
|
||||
},
|
||||
borderRadius: {
|
||||
sm: 6,
|
||||
md: 8,
|
||||
lg: 12,
|
||||
xl: 16,
|
||||
},
|
||||
fontSize: {
|
||||
xs: 12,
|
||||
sm: 14,
|
||||
base: 16,
|
||||
lg: 18,
|
||||
xl: 20,
|
||||
"2xl": 24,
|
||||
"3xl": 30,
|
||||
"4xl": 36,
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { StyleSheet } from 'react-native-unistyles';
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
|
||||
import { breakpoints } from './breakpoints';
|
||||
import { lightTheme, darkTheme } from './theme';
|
||||
import { breakpoints } from "./breakpoints";
|
||||
import { darkTheme, lightTheme } from "./theme";
|
||||
|
||||
type AppBreakpoints = typeof breakpoints;
|
||||
|
||||
@@ -10,7 +10,7 @@ type AppThemes = {
|
||||
dark: typeof darkTheme;
|
||||
};
|
||||
|
||||
declare module 'react-native-unistyles' {
|
||||
declare module "react-native-unistyles" {
|
||||
export interface UnistylesBreakpoints extends AppBreakpoints {}
|
||||
export interface UnistylesThemes extends AppThemes {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user