create-better-t-stack 3.2.22 → 3.2.23-canary.b8d62867

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +1 -1
  3. package/dist/index.d.ts +4 -2
  4. package/dist/index.js +1 -1
  5. package/dist/{src-WIwtBCHf.js → src-VoUvj8ZF.js} +106 -66
  6. package/package.json +1 -1
  7. package/templates/addons/biome/biome.json.hbs +5 -0
  8. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +5 -5
  9. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +1 -1
  10. package/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs +127 -0
  11. package/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs +138 -0
  12. package/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs +91 -0
  13. package/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs +102 -0
  14. package/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs +186 -0
  15. package/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs +131 -0
  16. package/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs +150 -0
  17. package/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs +9 -1
  18. package/templates/auth/better-auth/native/unistyles/components/sign-in.tsx.hbs +5 -0
  19. package/templates/auth/better-auth/native/unistyles/components/sign-up.tsx.hbs +5 -0
  20. package/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs +123 -0
  21. package/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs +90 -0
  22. package/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs +116 -0
  23. package/templates/auth/better-auth/server/base/src/index.ts.hbs +5 -5
  24. package/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +287 -0
  25. package/templates/examples/ai/native/{nativewind → bare}/polyfills.js +1 -0
  26. package/templates/examples/ai/native/{nativewind → uniwind}/app/(drawer)/ai.tsx.hbs +52 -51
  27. package/templates/examples/ai/native/uniwind/polyfills.js +26 -0
  28. package/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs +521 -0
  29. package/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs +295 -0
  30. package/templates/extras/bunfig.toml.hbs +3 -3
  31. package/templates/frontend/native/bare/_gitignore +18 -0
  32. package/templates/frontend/native/{nativewind → bare}/app/(drawer)/(tabs)/_layout.tsx.hbs +7 -12
  33. package/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs +43 -0
  34. package/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs +43 -0
  35. package/templates/frontend/native/{nativewind → bare}/app/(drawer)/_layout.tsx.hbs +24 -1
  36. package/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs +234 -0
  37. package/templates/frontend/native/bare/app/+not-found.tsx.hbs +65 -0
  38. package/templates/frontend/native/bare/app/_layout.tsx.hbs +163 -0
  39. package/templates/frontend/native/bare/app/modal.tsx.hbs +34 -0
  40. package/templates/frontend/native/{nativewind → bare}/app.json.hbs +1 -0
  41. package/templates/frontend/native/bare/components/container.tsx.hbs +25 -0
  42. package/templates/frontend/native/bare/components/header-button.tsx.hbs +47 -0
  43. package/templates/frontend/native/{nativewind → bare}/components/tabbar-icon.tsx.hbs +1 -0
  44. package/templates/frontend/native/{nativewind → bare}/lib/android-navigation-bar.tsx.hbs +1 -0
  45. package/templates/frontend/native/{nativewind → bare}/lib/constants.ts.hbs +1 -0
  46. package/templates/frontend/native/bare/lib/use-color-scheme.ts.hbs +20 -0
  47. package/templates/frontend/native/bare/metro.config.js.hbs +9 -0
  48. package/templates/frontend/native/{nativewind → bare}/package.json.hbs +1 -2
  49. package/templates/frontend/native/bare/tsconfig.json.hbs +11 -0
  50. package/templates/frontend/native/{nativewind → uniwind}/_gitignore +4 -8
  51. package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/_layout.tsx.hbs +46 -0
  52. package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/index.tsx.hbs +15 -0
  53. package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/two.tsx.hbs +15 -0
  54. package/templates/frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs +83 -0
  55. package/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +151 -0
  56. package/templates/frontend/native/uniwind/app/+not-found.tsx.hbs +32 -0
  57. package/templates/frontend/native/uniwind/app/_layout.tsx.hbs +131 -0
  58. package/templates/frontend/native/uniwind/app/modal.tsx.hbs +53 -0
  59. package/templates/frontend/native/uniwind/app.json.hbs +19 -0
  60. package/templates/frontend/native/uniwind/components/container.tsx.hbs +33 -0
  61. package/templates/frontend/native/uniwind/components/theme-toggle.tsx.hbs +35 -0
  62. package/templates/frontend/native/uniwind/contexts/app-theme-context.tsx.hbs +62 -0
  63. package/templates/frontend/native/uniwind/global.css +5 -0
  64. package/templates/frontend/native/uniwind/metro.config.js.hbs +13 -0
  65. package/templates/frontend/native/uniwind/package.json.hbs +54 -0
  66. package/templates/frontend/native/{nativewind → uniwind}/tsconfig.json.hbs +4 -8
  67. package/templates/auth/better-auth/convex/native/nativewind/components/sign-in.tsx.hbs +0 -86
  68. package/templates/auth/better-auth/convex/native/nativewind/components/sign-up.tsx.hbs +0 -97
  69. package/templates/auth/better-auth/native/nativewind/app/(drawer)/index.tsx.hbs +0 -95
  70. package/templates/auth/better-auth/native/nativewind/components/sign-in.tsx.hbs +0 -93
  71. package/templates/auth/better-auth/native/nativewind/components/sign-up.tsx.hbs +0 -104
  72. package/templates/examples/todo/native/nativewind/app/(drawer)/todos.tsx.hbs +0 -295
  73. package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx.hbs +0 -19
  74. package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx.hbs +0 -19
  75. package/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +0 -178
  76. package/templates/frontend/native/nativewind/app/+not-found.tsx.hbs +0 -29
  77. package/templates/frontend/native/nativewind/app/_layout.tsx.hbs +0 -175
  78. package/templates/frontend/native/nativewind/app/modal.tsx.hbs +0 -14
  79. package/templates/frontend/native/nativewind/babel.config.js.hbs +0 -14
  80. package/templates/frontend/native/nativewind/components/container.tsx.hbs +0 -8
  81. package/templates/frontend/native/nativewind/components/header-button.tsx.hbs +0 -26
  82. package/templates/frontend/native/nativewind/global.css +0 -50
  83. package/templates/frontend/native/nativewind/lib/use-color-scheme.ts.hbs +0 -12
  84. package/templates/frontend/native/nativewind/metro.config.js.hbs +0 -12
  85. package/templates/frontend/native/nativewind/tailwind.config.js.hbs +0 -59
  86. /package/templates/auth/clerk/convex/native/base/app/(auth)/{sign-out.tsx.hbs → sign-up.tsx.hbs} +0 -0
@@ -0,0 +1,46 @@
1
+ import { Tabs } from "expo-router";
2
+ import { Ionicons } from "@expo/vector-icons";
3
+ import { useThemeColor } from "heroui-native";
4
+
5
+ export default function TabLayout() {
6
+ const themeColorForeground = useThemeColor("foreground");
7
+ const themeColorBackground = useThemeColor("background");
8
+
9
+ return (
10
+ <Tabs
11
+ screenOptions=\{{
12
+ headerShown: false,
13
+ headerStyle: {
14
+ backgroundColor: themeColorBackground,
15
+ },
16
+ headerTintColor: themeColorForeground,
17
+ headerTitleStyle: {
18
+ color: themeColorForeground,
19
+ fontWeight: "600",
20
+ },
21
+ tabBarStyle: {
22
+ backgroundColor: themeColorBackground,
23
+ },
24
+ }}
25
+ >
26
+ <Tabs.Screen
27
+ name="index"
28
+ options=\{{
29
+ title: "Home",
30
+ tabBarIcon: ({ color, size }: { color: string; size: number }) => (
31
+ <Ionicons name="home" size={size} color={color} />
32
+ ),
33
+ }}
34
+ />
35
+ <Tabs.Screen
36
+ name="two"
37
+ options=\{{
38
+ title: "Explore",
39
+ tabBarIcon: ({ color, size }: { color: string; size: number }) => (
40
+ <Ionicons name="compass" size={size} color={color} />
41
+ ),
42
+ }}
43
+ />
44
+ </Tabs>
45
+ );
46
+ }
@@ -0,0 +1,15 @@
1
+ import { Container } from "@/components/container";
2
+ import { Text, View } from "react-native";
3
+ import { Card } from "heroui-native";
4
+
5
+ export default function Home() {
6
+ return (
7
+ <Container className="p-6">
8
+ <View className="flex-1 justify-center items-center">
9
+ <Card variant="secondary" className="p-8 items-center">
10
+ <Card.Title className="text-3xl mb-2">Tab One</Card.Title>
11
+ </Card>
12
+ </View>
13
+ </Container>
14
+ );
15
+ }
@@ -0,0 +1,15 @@
1
+ import { Container } from "@/components/container";
2
+ import { Text, View } from "react-native";
3
+ import { Card } from "heroui-native";
4
+
5
+ export default function TabTwo() {
6
+ return (
7
+ <Container className="p-6">
8
+ <View className="flex-1 justify-center items-center">
9
+ <Card variant="secondary" className="p-8 items-center">
10
+ <Card.Title className="text-3xl mb-2">TabTwo</Card.Title>
11
+ </Card>
12
+ </View>
13
+ </Container>
14
+ );
15
+ }
@@ -0,0 +1,83 @@
1
+ import React, { useCallback } from "react";
2
+ import { Ionicons, MaterialIcons } from "@expo/vector-icons";
3
+ import { Link } from "expo-router";
4
+ import { Drawer } from "expo-router/drawer";
5
+ import { useThemeColor } from "heroui-native";
6
+ import { Pressable } from "react-native";
7
+ import { ThemeToggle } from "@/components/theme-toggle";
8
+
9
+ function DrawerLayout() {
10
+ const themeColorForeground = useThemeColor("foreground");
11
+ const themeColorBackground = useThemeColor("background");
12
+
13
+ const renderThemeToggle = useCallback(() => <ThemeToggle />, []);
14
+
15
+ return (
16
+ <Drawer
17
+ screenOptions=\{{
18
+ headerTintColor: themeColorForeground,
19
+ headerStyle: { backgroundColor: themeColorBackground },
20
+ headerTitleStyle: {
21
+ fontWeight: "600",
22
+ color: themeColorForeground,
23
+ },
24
+ headerRight: renderThemeToggle,
25
+ drawerStyle: { backgroundColor: themeColorBackground },
26
+ }}
27
+ >
28
+ <Drawer.Screen
29
+ name="index"
30
+ options=\{{
31
+ headerTitle: "Home",
32
+ drawerLabel: "Home",
33
+ drawerIcon: ({ size, color }) => (
34
+ <Ionicons name="home-outline" size={size} color={color} />
35
+ ),
36
+ }}
37
+ />
38
+ <Drawer.Screen
39
+ name="(tabs)"
40
+ options=\{{
41
+ headerTitle: "Tabs",
42
+ drawerLabel: "Tabs",
43
+ drawerIcon: ({ size, color }) => (
44
+ <MaterialIcons name="border-bottom" size={size} color={color} />
45
+ ),
46
+ headerRight: () => (
47
+ <Link href="/modal" asChild>
48
+ <Pressable className="mr-4">
49
+ <Ionicons name="add-outline" size={24} color={themeColorForeground} />
50
+ </Pressable>
51
+ </Link>
52
+ ),
53
+ }}
54
+ />
55
+ {{#if (includes examples "todo")}}
56
+ <Drawer.Screen
57
+ name="todos"
58
+ options=\{{
59
+ headerTitle: "Todos",
60
+ drawerLabel: "Todos",
61
+ drawerIcon: ({ size, color }) => (
62
+ <Ionicons name="checkbox-outline" size={size} color={color} />
63
+ ),
64
+ }}
65
+ />
66
+ {{/if}}
67
+ {{#if (includes examples "ai")}}
68
+ <Drawer.Screen
69
+ name="ai"
70
+ options=\{{
71
+ headerTitle: "AI",
72
+ drawerLabel: "AI",
73
+ drawerIcon: ({ size, color }) => (
74
+ <Ionicons name="chatbubble-ellipses-outline" size={size} color={color} />
75
+ ),
76
+ }}
77
+ />
78
+ {{/if}}
79
+ </Drawer>
80
+ );
81
+ }
82
+
83
+ export default DrawerLayout;
@@ -0,0 +1,151 @@
1
+ import { Text, View } from "react-native";
2
+ import { Container } from "@/components/container";
3
+ {{#if (eq api "orpc")}}
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { orpc } from "@/utils/orpc";
6
+ {{/if}}
7
+ {{#if (eq api "trpc")}}
8
+ import { useQuery } from "@tanstack/react-query";
9
+ import { trpc } from "@/utils/trpc";
10
+ {{/if}}
11
+ {{#if (and (eq backend "convex") (eq auth "clerk"))}}
12
+ import { Link } from "expo-router";
13
+ import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react";
14
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
15
+ import { useUser } from "@clerk/clerk-expo";
16
+ import { SignOutButton } from "@/components/sign-out-button";
17
+ {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
18
+ import { useConvexAuth, useQuery } from "convex/react";
19
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
20
+ import { authClient } from "@/lib/auth-client";
21
+ import { SignIn } from "@/components/sign-in";
22
+ import { SignUp } from "@/components/sign-up";
23
+ {{else if (eq backend "convex")}}
24
+ import { useQuery } from "convex/react";
25
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
26
+ {{/if}}
27
+ import { Ionicons } from "@expo/vector-icons";
28
+ import { Card, Chip, useThemeColor } from "heroui-native";
29
+
30
+ export default function Home() {
31
+ {{#if (eq api "orpc")}}
32
+ const healthCheck = useQuery(orpc.healthCheck.queryOptions());
33
+ {{/if}}
34
+ {{#if (eq api "trpc")}}
35
+ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
36
+ {{/if}}
37
+ {{#if (and (eq backend "convex") (eq auth "clerk"))}}
38
+ const { user } = useUser();
39
+ const healthCheck = useQuery(api.healthCheck.get);
40
+ const privateData = useQuery(api.privateData.get);
41
+ {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
42
+ const healthCheck = useQuery(api.healthCheck.get);
43
+ const { isAuthenticated } = useConvexAuth();
44
+ const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : "skip");
45
+ {{else if (eq backend "convex")}}
46
+ const healthCheck = useQuery(api.healthCheck.get);
47
+ {{/if}}
48
+ const mutedColor = useThemeColor("muted");
49
+ const successColor = useThemeColor("success");
50
+ const dangerColor = useThemeColor("danger");
51
+
52
+ {{#if (eq backend "convex")}}
53
+ const isConnected = healthCheck === "OK";
54
+ const isLoading = healthCheck === undefined;
55
+ {{else}}
56
+ {{#unless (eq api "none")}}
57
+ const isConnected = healthCheck?.data === "OK";
58
+ const isLoading = healthCheck?.isLoading;
59
+ {{/unless}}
60
+ {{/if}}
61
+
62
+ return (
63
+ <Container className="p-6">
64
+ <View className="py-4 mb-6">
65
+ <Text className="text-4xl font-bold text-foreground mb-2">
66
+ BETTER T STACK
67
+ </Text>
68
+ </View>
69
+
70
+ {{#unless (and (eq backend "convex") (eq auth "better-auth"))}}
71
+ <Card variant="secondary" className="p-6">
72
+ <View className="flex-row items-center justify-between mb-4">
73
+ <Card.Title>System Status</Card.Title>
74
+ <Chip
75
+ variant="secondary"
76
+ color={isConnected ? "success" : "danger"}
77
+ size="sm"
78
+ >
79
+ <Chip.Label>
80
+ {isConnected ? "LIVE" : "OFFLINE"}
81
+ </Chip.Label>
82
+ </Chip>
83
+ </View>
84
+ <Card className="p-4">
85
+ <View className="flex-row items-center">
86
+ <View
87
+ className={`w-3 h-3 rounded-full mr-3 ${
88
+ isConnected ? "bg-success" : "bg-muted"
89
+ }`}
90
+ />
91
+ <View className="flex-1">
92
+ <Text className="text-foreground font-medium mb-1">
93
+ {{#if (eq backend "convex")}}
94
+ Convex Backend
95
+ {{else}}
96
+ {{#unless (eq api "none")}}
97
+ {{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} Backend
98
+ {{/unless}}
99
+ {{/if}}
100
+ </Text>
101
+ <Card.Description>
102
+ {isLoading
103
+ ? "Checking connection..."
104
+ : isConnected
105
+ ? "Connected to API"
106
+ : "API Disconnected"}
107
+ </Card.Description>
108
+ </View>
109
+ {isLoading && (
110
+ <Ionicons name="hourglass-outline" size={20} color={mutedColor} />
111
+ )}
112
+ {!isLoading && isConnected && (
113
+ <Ionicons name="checkmark-circle" size={20} color={successColor} />
114
+ )}
115
+ {!isLoading && !isConnected && (
116
+ <Ionicons name="close-circle" size={20} color={dangerColor} />
117
+ )}
118
+ </View>
119
+ </Card>
120
+ </Card>
121
+ {{/unless}}
122
+
123
+ {{#if (and (eq backend "convex") (eq auth "clerk"))}}
124
+ <Authenticated>
125
+ <Card variant="secondary" className="mt-6 p-4">
126
+ <Text className="text-foreground text-base mb-2">
127
+ Hello {user?.emailAddresses[0].emailAddress}
128
+ </Text>
129
+ <Text className="text-muted text-sm mb-4">
130
+ Private Data: {privateData?.message}
131
+ </Text>
132
+ <SignOutButton />
133
+ </Card>
134
+ </Authenticated>
135
+ <Unauthenticated>
136
+ <View className="mt-6 gap-4">
137
+ <Link href="/(auth)/sign-in" asChild>
138
+ <Text className="text-primary font-semibold">Sign in</Text>
139
+ </Link>
140
+ <Link href="/(auth)/sign-up" asChild>
141
+ <Text className="text-primary font-semibold">Sign up</Text>
142
+ </Link>
143
+ </View>
144
+ </Unauthenticated>
145
+ <AuthLoading>
146
+ <Text className="text-muted">Loading...</Text>
147
+ </AuthLoading>
148
+ {{/if}}
149
+ </Container>
150
+ );
151
+ }
@@ -0,0 +1,32 @@
1
+ import { Container } from "@/components/container";
2
+ import { Link, Stack } from "expo-router";
3
+ import { Text, View, Pressable } from "react-native";
4
+ import { Card } from "heroui-native";
5
+
6
+ export default function NotFoundScreen() {
7
+ return (
8
+ <>
9
+ <Stack.Screen options=\{{ title: "Oops!" }} />
10
+ <Container>
11
+ <View className="flex-1 justify-center items-center p-6">
12
+ <Card variant="secondary" className="items-center p-8 max-w-md">
13
+ <Text className="text-6xl mb-4">🤔</Text>
14
+ <Card.Title className="text-2xl text-center mb-2">
15
+ Page Not Found
16
+ </Card.Title>
17
+ <Card.Description className="text-center mb-6">
18
+ Sorry, the page you're looking for doesn't exist.
19
+ </Card.Description>
20
+ <Link href="/" asChild>
21
+ <Pressable className="bg-accent px-6 py-3 rounded-lg active:opacity-70">
22
+ <Text className="text-accent-foreground font-medium text-base">
23
+ Go to Home
24
+ </Text>
25
+ </Pressable>
26
+ </Link>
27
+ </Card>
28
+ </View>
29
+ </Container>
30
+ </>
31
+ );
32
+ }
@@ -0,0 +1,131 @@
1
+ {{#if (includes examples "ai")}}
2
+ import "@/polyfills";
3
+ {{/if}}
4
+
5
+ import "@/global.css";
6
+
7
+ {{#if (eq backend "convex")}}
8
+ {{#if (eq auth "better-auth")}}
9
+ import { ConvexReactClient } from "convex/react";
10
+ import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
11
+ import { authClient } from "@/lib/auth-client";
12
+ {{else}}
13
+ import { ConvexProvider, ConvexReactClient } from "convex/react";
14
+ {{/if}}
15
+
16
+ {{#if (eq auth "clerk")}}
17
+ import { ClerkProvider, useAuth } from "@clerk/clerk-expo";
18
+ import { ConvexProviderWithClerk } from "convex/react-clerk";
19
+ import { tokenCache } from "@clerk/clerk-expo/token-cache";
20
+ {{/if}}
21
+ {{else}}
22
+ {{#unless (eq api "none")}}
23
+ import { QueryClientProvider } from "@tanstack/react-query";
24
+ {{/unless}}
25
+ {{/if}}
26
+
27
+ import { Stack } from "expo-router";
28
+ import { HeroUINativeProvider } from "heroui-native";
29
+ import { GestureHandlerRootView } from "react-native-gesture-handler";
30
+ import { KeyboardProvider } from "react-native-keyboard-controller";
31
+ import { AppThemeProvider } from "@/contexts/app-theme-context";
32
+
33
+ {{#if (eq api "trpc")}}
34
+ import { queryClient } from "@/utils/trpc";
35
+ {{/if}}
36
+ {{#if (eq api "orpc")}}
37
+ import { queryClient } from "@/utils/orpc";
38
+ {{/if}}
39
+
40
+ export const unstable_settings = {
41
+ initialRouteName: "(drawer)",
42
+ };
43
+
44
+ {{#if (eq backend "convex")}}
45
+ const convexUrl = process.env.EXPO_PUBLIC_CONVEX_URL || "";
46
+ const convex = new ConvexReactClient(convexUrl, {
47
+ unsavedChangesWarning: false,
48
+ });
49
+ {{/if}}
50
+
51
+ function StackLayout() {
52
+ return (
53
+ <Stack screenOptions=\{{}}>
54
+ <Stack.Screen name="(drawer)" options=\{{ headerShown: false }} />
55
+ {{#if (eq auth "clerk")}}
56
+ <Stack.Screen name="(auth)" options=\{{ headerShown: false }} />
57
+ {{/if}}
58
+ <Stack.Screen name="modal" options=\{{ title: "Modal", presentation: "modal" }} />
59
+ </Stack>
60
+ );
61
+ }
62
+
63
+ export default function Layout() {
64
+ return (
65
+ {{#if (eq backend "convex")}}
66
+ {{#if (eq auth "clerk")}}
67
+ <ClerkProvider tokenCache={tokenCache} publishableKey={process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>
68
+ <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
69
+ <GestureHandlerRootView style=\{{ flex: 1 }}>
70
+ <KeyboardProvider>
71
+ <AppThemeProvider>
72
+ <HeroUINativeProvider>
73
+ <StackLayout />
74
+ </HeroUINativeProvider>
75
+ </AppThemeProvider>
76
+ </KeyboardProvider>
77
+ </GestureHandlerRootView>
78
+ </ConvexProviderWithClerk>
79
+ </ClerkProvider>
80
+ {{else if (eq auth "better-auth")}}
81
+ <ConvexBetterAuthProvider client={convex} authClient={authClient}>
82
+ <GestureHandlerRootView style=\{{ flex: 1 }}>
83
+ <KeyboardProvider>
84
+ <AppThemeProvider>
85
+ <HeroUINativeProvider>
86
+ <StackLayout />
87
+ </HeroUINativeProvider>
88
+ </AppThemeProvider>
89
+ </KeyboardProvider>
90
+ </GestureHandlerRootView>
91
+ </ConvexBetterAuthProvider>
92
+ {{else}}
93
+ <ConvexProvider client={convex}>
94
+ <GestureHandlerRootView style=\{{ flex: 1 }}>
95
+ <KeyboardProvider>
96
+ <AppThemeProvider>
97
+ <HeroUINativeProvider>
98
+ <StackLayout />
99
+ </HeroUINativeProvider>
100
+ </AppThemeProvider>
101
+ </KeyboardProvider>
102
+ </GestureHandlerRootView>
103
+ </ConvexProvider>
104
+ {{/if}}
105
+ {{else}}
106
+ {{#unless (eq api "none")}}
107
+ <QueryClientProvider client={queryClient}>
108
+ <GestureHandlerRootView style=\{{ flex: 1 }}>
109
+ <KeyboardProvider>
110
+ <AppThemeProvider>
111
+ <HeroUINativeProvider>
112
+ <StackLayout />
113
+ </HeroUINativeProvider>
114
+ </AppThemeProvider>
115
+ </KeyboardProvider>
116
+ </GestureHandlerRootView>
117
+ </QueryClientProvider>
118
+ {{else}}
119
+ <GestureHandlerRootView style=\{{ flex: 1 }}>
120
+ <KeyboardProvider>
121
+ <AppThemeProvider>
122
+ <HeroUINativeProvider>
123
+ <StackLayout />
124
+ </HeroUINativeProvider>
125
+ </AppThemeProvider>
126
+ </KeyboardProvider>
127
+ </GestureHandlerRootView>
128
+ {{/unless}}
129
+ {{/if}}
130
+ );
131
+ }
@@ -0,0 +1,53 @@
1
+ import { Container } from "@/components/container";
2
+ import { Text, View, Pressable } from "react-native";
3
+ import { Ionicons } from "@expo/vector-icons";
4
+ import { router } from "expo-router";
5
+ import { Card, useThemeColor } from "heroui-native";
6
+
7
+ function Modal() {
8
+ const accentForegroundColor = useThemeColor("accent-foreground");
9
+
10
+ function handleClose() {
11
+ router.back();
12
+ }
13
+
14
+ return (
15
+ <Container>
16
+ <View className="flex-1 justify-center items-center p-6">
17
+ <Card variant="secondary" className="p-6 w-full max-w-sm">
18
+ <Card.Body className="gap-4 items-center">
19
+ <View className="w-16 h-16 bg-accent rounded-full items-center justify-center mb-2">
20
+ <Ionicons name="checkmark" size={32} color={accentForegroundColor} />
21
+ </View>
22
+ <Card.Title className="text-center text-xl">
23
+ Modal Screen
24
+ </Card.Title>
25
+ <Card.Description className="text-center">
26
+ This is an example modal screen. You can use this pattern for
27
+ dialogs, confirmations, or any overlay content.
28
+ </Card.Description>
29
+ </Card.Body>
30
+ <Card.Footer className="mt-4">
31
+ <Pressable
32
+ onPress={handleClose}
33
+ className="bg-accent p-4 rounded-lg w-full active:opacity-70"
34
+ >
35
+ <View className="flex-row items-center justify-center">
36
+ <Text className="text-accent-foreground font-semibold text-base mr-2">
37
+ Close Modal
38
+ </Text>
39
+ <Ionicons
40
+ name="close-circle"
41
+ size={20}
42
+ color={accentForegroundColor}
43
+ />
44
+ </View>
45
+ </Pressable>
46
+ </Card.Footer>
47
+ </Card>
48
+ </View>
49
+ </Container>
50
+ );
51
+ }
52
+
53
+ export default Modal;
@@ -0,0 +1,19 @@
1
+ {
2
+ "expo": {
3
+ "scheme": "{{projectName}}",
4
+ "userInterfaceStyle": "automatic",
5
+ "orientation": "default",
6
+ "web": {
7
+ "bundler": "metro"
8
+ },
9
+ "name": "{{projectName}}",
10
+ "slug": "{{projectName}}",
11
+ "plugins": [
12
+ "expo-font"
13
+ ],
14
+ "experiments": {
15
+ "typedRoutes": true,
16
+ "reactCompiler": true
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,33 @@
1
+ import { cn } from "heroui-native";
2
+ import { type PropsWithChildren } from "react";
3
+ import { ScrollView, View, type ViewProps } from "react-native";
4
+ import Animated, { type AnimatedProps } from "react-native-reanimated";
5
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
6
+
7
+ const AnimatedView = Animated.createAnimatedComponent(View);
8
+
9
+ type Props = AnimatedProps<ViewProps> & {
10
+ className?: string;
11
+ };
12
+
13
+ export function Container({
14
+ children,
15
+ className,
16
+ ...props
17
+ }: PropsWithChildren<Props>) {
18
+ const insets = useSafeAreaInsets();
19
+
20
+ return (
21
+ <AnimatedView
22
+ className={cn("flex-1 bg-background", className)}
23
+ style=\{{
24
+ paddingBottom: insets.bottom,
25
+ }}
26
+ {...props}
27
+ >
28
+ <ScrollView contentContainerStyle=\{{ flexGrow: 1 }}>
29
+ {children}
30
+ </ScrollView>
31
+ </AnimatedView>
32
+ );
33
+ }
@@ -0,0 +1,35 @@
1
+ import { Ionicons } from '@expo/vector-icons';
2
+ import * as Haptics from 'expo-haptics';
3
+ import { Platform, Pressable } from 'react-native';
4
+ import Animated, { FadeOut, ZoomIn } from 'react-native-reanimated';
5
+ import { withUniwind } from 'uniwind';
6
+ import { useAppTheme } from '@/contexts/app-theme-context';
7
+
8
+ const StyledIonicons = withUniwind(Ionicons);
9
+
10
+ export function ThemeToggle() {
11
+ const { toggleTheme, isLight } = useAppTheme();
12
+
13
+ return (
14
+ <Pressable
15
+ onPress={() => {
16
+ if (Platform.OS === 'ios') {
17
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
18
+ }
19
+ toggleTheme();
20
+ }}
21
+ className="px-2.5"
22
+ >
23
+ {isLight ? (
24
+ <Animated.View key="moon" entering={ZoomIn} exiting={FadeOut}>
25
+ <StyledIonicons name="moon" size={20} className="text-foreground" />
26
+ </Animated.View>
27
+ ) : (
28
+ <Animated.View key="sun" entering={ZoomIn} exiting={FadeOut}>
29
+ <StyledIonicons name="sunny" size={20} className="text-foreground" />
30
+ </Animated.View>
31
+ )}
32
+ </Pressable>
33
+ );
34
+ }
35
+
@@ -0,0 +1,62 @@
1
+ import React, { createContext, useCallback, useContext, useMemo } from 'react';
2
+ import { Uniwind, useUniwind } from 'uniwind';
3
+
4
+ type ThemeName = 'light' | 'dark';
5
+
6
+ type AppThemeContextType = {
7
+ currentTheme: string;
8
+ isLight: boolean;
9
+ isDark: boolean;
10
+ setTheme: (theme: ThemeName) => void;
11
+ toggleTheme: () => void;
12
+ }
13
+
14
+ const AppThemeContext = createContext<AppThemeContextType | undefined>(
15
+ undefined
16
+ );
17
+
18
+ export const AppThemeProvider = ({ children }: { children: React.ReactNode }) => {
19
+ const { theme } = useUniwind();
20
+
21
+ const isLight = useMemo(() => {
22
+ return theme === 'light';
23
+ }, [theme]);
24
+
25
+ const isDark = useMemo(() => {
26
+ return theme === 'dark';
27
+ }, [theme]);
28
+
29
+ const setTheme = useCallback((newTheme: ThemeName) => {
30
+ Uniwind.setTheme(newTheme);
31
+ }, []);
32
+
33
+ const toggleTheme = useCallback(() => {
34
+ Uniwind.setTheme(theme === 'light' ? 'dark' : 'light');
35
+ }, [theme]);
36
+
37
+ const value = useMemo(
38
+ () => ({
39
+ currentTheme: theme,
40
+ isLight,
41
+ isDark,
42
+ setTheme,
43
+ toggleTheme,
44
+ }),
45
+ [theme, isLight, isDark, setTheme, toggleTheme]
46
+ );
47
+
48
+ return (
49
+ <AppThemeContext.Provider value={value}>
50
+ {children}
51
+ </AppThemeContext.Provider>
52
+ );
53
+ };
54
+
55
+ export function useAppTheme() {
56
+ const context = useContext(AppThemeContext);
57
+ if (!context) {
58
+ throw new Error('useAppTheme must be used within AppThemeProvider');
59
+ }
60
+ return context;
61
+ }
62
+