@ramonclaudio/create-vexpo 0.1.0

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 (174) hide show
  1. package/README.md +50 -0
  2. package/dist/index.js +183 -0
  3. package/dist/templates/default/.eas/workflows/asc-events.yml +84 -0
  4. package/dist/templates/default/.eas/workflows/deploy-production.yml +129 -0
  5. package/dist/templates/default/.eas/workflows/development-builds.yml +19 -0
  6. package/dist/templates/default/.eas/workflows/e2e-tests.yml +42 -0
  7. package/dist/templates/default/.eas/workflows/pr-preview.yml +98 -0
  8. package/dist/templates/default/.eas/workflows/release.yml +44 -0
  9. package/dist/templates/default/.eas/workflows/rollback.yml +86 -0
  10. package/dist/templates/default/.eas/workflows/rollout.yml +84 -0
  11. package/dist/templates/default/.eas/workflows/rotate-apple-jwt.yml +42 -0
  12. package/dist/templates/default/.eas/workflows/testflight.yml +57 -0
  13. package/dist/templates/default/.github/workflows/check.yml +28 -0
  14. package/dist/templates/default/.maestro/launch.yaml +18 -0
  15. package/dist/templates/default/AGENTS.md +79 -0
  16. package/dist/templates/default/DESIGN.md +331 -0
  17. package/dist/templates/default/LICENSE +21 -0
  18. package/dist/templates/default/README.md +153 -0
  19. package/dist/templates/default/SETUP.md +618 -0
  20. package/dist/templates/default/__tests__/convex/constants.test.ts +49 -0
  21. package/dist/templates/default/__tests__/convex/validators.test.ts +23 -0
  22. package/dist/templates/default/__tests__/convex/webhook.test.ts +343 -0
  23. package/dist/templates/default/__tests__/lib/deep-link.test.ts +67 -0
  24. package/dist/templates/default/_easignore +22 -0
  25. package/dist/templates/default/_editorconfig +9 -0
  26. package/dist/templates/default/_env.example +34 -0
  27. package/dist/templates/default/_fingerprintignore +24 -0
  28. package/dist/templates/default/_gitattributes +7 -0
  29. package/dist/templates/default/_gitignore +69 -0
  30. package/dist/templates/default/_oxfmtrc.json +3 -0
  31. package/dist/templates/default/_oxlintrc.json +34 -0
  32. package/dist/templates/default/app/(app)/(tabs)/(home)/index.tsx +50 -0
  33. package/dist/templates/default/app/(app)/(tabs)/(home,search)/_layout.tsx +44 -0
  34. package/dist/templates/default/app/(app)/(tabs)/(search)/index.tsx +247 -0
  35. package/dist/templates/default/app/(app)/(tabs)/_layout.tsx +77 -0
  36. package/dist/templates/default/app/(app)/(tabs)/settings/_layout.tsx +37 -0
  37. package/dist/templates/default/app/(app)/(tabs)/settings/index.tsx +362 -0
  38. package/dist/templates/default/app/(app)/(tabs)/settings/preferences.tsx +184 -0
  39. package/dist/templates/default/app/(app)/_layout.tsx +73 -0
  40. package/dist/templates/default/app/(app)/debug.tsx +389 -0
  41. package/dist/templates/default/app/(app)/help.tsx +254 -0
  42. package/dist/templates/default/app/(app)/linked.tsx +116 -0
  43. package/dist/templates/default/app/(app)/privacy.tsx +159 -0
  44. package/dist/templates/default/app/(app)/profile.tsx +915 -0
  45. package/dist/templates/default/app/(app)/sessions.tsx +191 -0
  46. package/dist/templates/default/app/(app)/welcome.tsx +140 -0
  47. package/dist/templates/default/app/(auth)/_layout.tsx +31 -0
  48. package/dist/templates/default/app/(auth)/forgot-password.tsx +168 -0
  49. package/dist/templates/default/app/(auth)/reset-password.tsx +314 -0
  50. package/dist/templates/default/app/(auth)/sign-in.tsx +453 -0
  51. package/dist/templates/default/app/(auth)/sign-up.tsx +563 -0
  52. package/dist/templates/default/app/+native-intent.tsx +14 -0
  53. package/dist/templates/default/app/+not-found.tsx +51 -0
  54. package/dist/templates/default/app/_layout.tsx +102 -0
  55. package/dist/templates/default/app-store/screenshots/.gitkeep +0 -0
  56. package/dist/templates/default/app-store/screenshots/README.md +13 -0
  57. package/dist/templates/default/app.config.ts +201 -0
  58. package/dist/templates/default/app.json +11 -0
  59. package/dist/templates/default/assets/brand-icon-dark.png +0 -0
  60. package/dist/templates/default/assets/brand-icon-light.png +0 -0
  61. package/dist/templates/default/assets/fonts/Geist-Black.ttf +0 -0
  62. package/dist/templates/default/assets/fonts/Geist-BlackItalic.ttf +0 -0
  63. package/dist/templates/default/assets/fonts/Geist-Bold.ttf +0 -0
  64. package/dist/templates/default/assets/fonts/Geist-BoldItalic.ttf +0 -0
  65. package/dist/templates/default/assets/fonts/Geist-ExtraBold.ttf +0 -0
  66. package/dist/templates/default/assets/fonts/Geist-ExtraBoldItalic.ttf +0 -0
  67. package/dist/templates/default/assets/fonts/Geist-ExtraLight.ttf +0 -0
  68. package/dist/templates/default/assets/fonts/Geist-ExtraLightItalic.ttf +0 -0
  69. package/dist/templates/default/assets/fonts/Geist-Italic.ttf +0 -0
  70. package/dist/templates/default/assets/fonts/Geist-Light.ttf +0 -0
  71. package/dist/templates/default/assets/fonts/Geist-LightItalic.ttf +0 -0
  72. package/dist/templates/default/assets/fonts/Geist-Medium.ttf +0 -0
  73. package/dist/templates/default/assets/fonts/Geist-MediumItalic.ttf +0 -0
  74. package/dist/templates/default/assets/fonts/Geist-Regular.ttf +0 -0
  75. package/dist/templates/default/assets/fonts/Geist-SemiBold.ttf +0 -0
  76. package/dist/templates/default/assets/fonts/Geist-SemiBoldItalic.ttf +0 -0
  77. package/dist/templates/default/assets/fonts/Geist-Thin.ttf +0 -0
  78. package/dist/templates/default/assets/fonts/Geist-ThinItalic.ttf +0 -0
  79. package/dist/templates/default/assets/fonts/Geist-Variable-Italic.ttf +0 -0
  80. package/dist/templates/default/assets/fonts/Geist-Variable.ttf +0 -0
  81. package/dist/templates/default/assets/fonts/GeistMono-Bold.ttf +0 -0
  82. package/dist/templates/default/assets/fonts/GeistMono-BoldItalic.ttf +0 -0
  83. package/dist/templates/default/assets/fonts/GeistMono-Italic.ttf +0 -0
  84. package/dist/templates/default/assets/fonts/GeistMono-Medium.ttf +0 -0
  85. package/dist/templates/default/assets/fonts/GeistMono-MediumItalic.ttf +0 -0
  86. package/dist/templates/default/assets/fonts/GeistMono-Regular.ttf +0 -0
  87. package/dist/templates/default/assets/fonts/GeistPixel-Square.ttf +0 -0
  88. package/dist/templates/default/assets/icon.png +0 -0
  89. package/dist/templates/default/assets/sounds/notification.wav +0 -0
  90. package/dist/templates/default/assets/splash-image-dark.png +0 -0
  91. package/dist/templates/default/assets/splash-image-light.png +0 -0
  92. package/dist/templates/default/bun.lock +1860 -0
  93. package/dist/templates/default/components/auth/otp-verification.tsx +255 -0
  94. package/dist/templates/default/components/auth/password-field.tsx +121 -0
  95. package/dist/templates/default/components/auth/segmented-toggle.tsx +47 -0
  96. package/dist/templates/default/components/ui/convex-error.tsx +32 -0
  97. package/dist/templates/default/components/ui/error-boundary.tsx +57 -0
  98. package/dist/templates/default/components/ui/loading-screen.tsx +31 -0
  99. package/dist/templates/default/components/ui/material.tsx +94 -0
  100. package/dist/templates/default/components/ui/offline-banner.tsx +58 -0
  101. package/dist/templates/default/components/ui/prominent-button.tsx +71 -0
  102. package/dist/templates/default/components/ui/skeleton.tsx +107 -0
  103. package/dist/templates/default/components/ui/status-text.tsx +49 -0
  104. package/dist/templates/default/components/ui/update-banner.tsx +82 -0
  105. package/dist/templates/default/constants/layout.ts +102 -0
  106. package/dist/templates/default/constants/theme.ts +401 -0
  107. package/dist/templates/default/constants/ui.ts +77 -0
  108. package/dist/templates/default/convex/_generated/api.d.ts +77 -0
  109. package/dist/templates/default/convex/_generated/api.js +23 -0
  110. package/dist/templates/default/convex/_generated/dataModel.d.ts +60 -0
  111. package/dist/templates/default/convex/_generated/server.d.ts +143 -0
  112. package/dist/templates/default/convex/_generated/server.js +93 -0
  113. package/dist/templates/default/convex/admin.ts +102 -0
  114. package/dist/templates/default/convex/auth.config.ts +6 -0
  115. package/dist/templates/default/convex/auth.ts +335 -0
  116. package/dist/templates/default/convex/constants.ts +46 -0
  117. package/dist/templates/default/convex/convex.config.ts +11 -0
  118. package/dist/templates/default/convex/crons.ts +42 -0
  119. package/dist/templates/default/convex/email.ts +109 -0
  120. package/dist/templates/default/convex/env.ts +31 -0
  121. package/dist/templates/default/convex/errors.ts +33 -0
  122. package/dist/templates/default/convex/functions.ts +54 -0
  123. package/dist/templates/default/convex/http.ts +176 -0
  124. package/dist/templates/default/convex/log.ts +81 -0
  125. package/dist/templates/default/convex/pushTokens.ts +114 -0
  126. package/dist/templates/default/convex/rateLimit.ts +92 -0
  127. package/dist/templates/default/convex/schema.ts +28 -0
  128. package/dist/templates/default/convex/tsconfig.json +18 -0
  129. package/dist/templates/default/convex/users.ts +279 -0
  130. package/dist/templates/default/convex/validators.ts +74 -0
  131. package/dist/templates/default/convex/webhook.ts +193 -0
  132. package/dist/templates/default/convex.json +6 -0
  133. package/dist/templates/default/eas.json +56 -0
  134. package/dist/templates/default/fingerprint.config.js +9 -0
  135. package/dist/templates/default/hooks/use-debounce.ts +20 -0
  136. package/dist/templates/default/hooks/use-deep-link.ts +43 -0
  137. package/dist/templates/default/hooks/use-navigation-tracking.ts +15 -0
  138. package/dist/templates/default/hooks/use-network.ts +11 -0
  139. package/dist/templates/default/hooks/use-notifications.ts +107 -0
  140. package/dist/templates/default/hooks/use-onboarding.ts +15 -0
  141. package/dist/templates/default/hooks/use-reduced-motion.ts +11 -0
  142. package/dist/templates/default/hooks/use-theme.ts +53 -0
  143. package/dist/templates/default/hooks/use-updates.ts +86 -0
  144. package/dist/templates/default/lib/a11y.ts +5 -0
  145. package/dist/templates/default/lib/app.ts +14 -0
  146. package/dist/templates/default/lib/assets.ts +17 -0
  147. package/dist/templates/default/lib/auth-client.ts +21 -0
  148. package/dist/templates/default/lib/convex-auth.tsx +79 -0
  149. package/dist/templates/default/lib/deep-link.ts +71 -0
  150. package/dist/templates/default/lib/dev-menu.ts +119 -0
  151. package/dist/templates/default/lib/device.ts +40 -0
  152. package/dist/templates/default/lib/dynamic-font.ts +49 -0
  153. package/dist/templates/default/lib/env.ts +10 -0
  154. package/dist/templates/default/lib/haptics.ts +24 -0
  155. package/dist/templates/default/lib/notifications.ts +276 -0
  156. package/dist/templates/default/lib/preferences.ts +45 -0
  157. package/dist/templates/default/lib/schemas.ts +137 -0
  158. package/dist/templates/default/lib/storage.ts +47 -0
  159. package/dist/templates/default/lib/updates.ts +107 -0
  160. package/dist/templates/default/metro.config.js +14 -0
  161. package/dist/templates/default/package.json +129 -0
  162. package/dist/templates/default/patches/PR-368.patch +91 -0
  163. package/dist/templates/default/patches/convex-dev-better-auth-0.12.2.tgz +0 -0
  164. package/dist/templates/default/plugins/README.md +9 -0
  165. package/dist/templates/default/plugins/with-auto-signing.js +45 -0
  166. package/dist/templates/default/plugins/with-pod-deployment-target.js +35 -0
  167. package/dist/templates/default/scripts/README.md +36 -0
  168. package/dist/templates/default/scripts/_run.mjs +77 -0
  169. package/dist/templates/default/scripts/clean.ts +543 -0
  170. package/dist/templates/default/scripts/rotate-apple-jwt.mjs +80 -0
  171. package/dist/templates/default/store.config.json +58 -0
  172. package/dist/templates/default/tsconfig.json +13 -0
  173. package/dist/templates/default/vitest.config.ts +21 -0
  174. package/package.json +69 -0
@@ -0,0 +1,50 @@
1
+ import { Host, ScrollView, VStack, Text, ContentUnavailableView } from "@expo/ui/swift-ui";
2
+ import { foregroundStyle, kerning, padding, frame, tint } from "@expo/ui/swift-ui/modifiers";
3
+
4
+ import { authClient } from "@/lib/auth-client";
5
+ import { useDynamicFont } from "@/lib/dynamic-font";
6
+ import { useColors } from "@/hooks/use-theme";
7
+
8
+ export default function HomeScreen() {
9
+ const dfont = useDynamicFont();
10
+ const colors = useColors();
11
+ const { data: session } = authClient.useSession();
12
+
13
+ const name = session?.user?.name?.split(" ")[0] ?? "there";
14
+ const now = new Date();
15
+
16
+ return (
17
+ <Host style={{ flex: 1 }}>
18
+ <ScrollView modifiers={[tint(colors.primary as string)]}>
19
+ <VStack
20
+ spacing={24}
21
+ alignment="leading"
22
+ modifiers={[padding({ horizontal: 20, top: 16, bottom: 40 })]}
23
+ >
24
+ <VStack
25
+ spacing={4}
26
+ alignment="leading"
27
+ modifiers={[frame({ maxWidth: Infinity, alignment: "leading" })]}
28
+ >
29
+ <Text
30
+ modifiers={[dfont({ size: 14 }), foregroundStyle(colors.mutedForeground as string)]}
31
+ >
32
+ <Text date={now} dateStyle="date" />
33
+ </Text>
34
+ <Text
35
+ modifiers={[dfont({ size: 32, weight: "bold", design: "rounded" }), kerning(-0.5)]}
36
+ >
37
+ Hey, {name}
38
+ </Text>
39
+ </VStack>
40
+
41
+ <ContentUnavailableView
42
+ title="Nothing here yet"
43
+ systemImage="square.dashed"
44
+ description="Home screen is ready to build."
45
+ />
46
+ </VStack>
47
+ </ScrollView>
48
+ </Host>
49
+ );
50
+ }
@@ -0,0 +1,44 @@
1
+ import { Stack, useSegments } from "expo-router";
2
+
3
+ import { useColors } from "@/hooks/use-theme";
4
+ import { useReducedMotion } from "@/hooks/use-reduced-motion";
5
+ import { HeaderTint } from "@/constants/theme";
6
+ import { FontFamily } from "@/constants/layout";
7
+
8
+ export const unstable_settings = {
9
+ home: { anchor: "index" },
10
+ search: { anchor: "index" },
11
+ };
12
+
13
+ export default function SharedLayout() {
14
+ const colors = useColors();
15
+ const reduceMotion = useReducedMotion();
16
+ const segments = useSegments() as string[];
17
+ const isSearch = segments[2] === "(search)";
18
+
19
+ return (
20
+ <Stack
21
+ screenOptions={{
22
+ headerTintColor: HeaderTint as string,
23
+ headerBlurEffect: "none",
24
+ headerShadowVisible: false,
25
+ headerLargeTitleShadowVisible: false,
26
+ headerLargeStyle: { backgroundColor: "transparent" },
27
+ headerTitleStyle: { fontFamily: FontFamily.semiBold },
28
+ headerLargeTitleStyle: { fontFamily: FontFamily.bold },
29
+ animation: reduceMotion ? "fade" : "default",
30
+ animationDuration: reduceMotion ? 150 : undefined,
31
+ }}
32
+ >
33
+ <Stack.Screen
34
+ name="index"
35
+ options={{
36
+ title: isSearch ? "Search" : "Home",
37
+ headerShown: isSearch,
38
+ headerLargeTitle: false,
39
+ contentStyle: { backgroundColor: colors.background as string },
40
+ }}
41
+ />
42
+ </Stack>
43
+ );
44
+ }
@@ -0,0 +1,247 @@
1
+ import { useMemo, useState } from "react";
2
+ import { router, Stack } from "expo-router";
3
+ import {
4
+ Host,
5
+ ScrollView,
6
+ Button,
7
+ Text,
8
+ VStack,
9
+ HStack,
10
+ Spacer,
11
+ Image,
12
+ ContentUnavailableView,
13
+ } from "@expo/ui/swift-ui";
14
+ import {
15
+ background,
16
+ buttonStyle,
17
+ clipShape,
18
+ foregroundStyle,
19
+ frame,
20
+ padding,
21
+ scrollDismissesKeyboard,
22
+ tint,
23
+ } from "@expo/ui/swift-ui/modifiers";
24
+ import type { SFSymbol } from "sf-symbols-typescript";
25
+
26
+ import { useDynamicFont } from "@/lib/dynamic-font";
27
+ import { useColors } from "@/hooks/use-theme";
28
+ import { useDebounce } from "@/hooks/use-debounce";
29
+ import { useDebugEnabled } from "@/lib/preferences";
30
+ import { haptics } from "@/lib/haptics";
31
+
32
+ type Destination = {
33
+ title: string;
34
+ subtitle: string;
35
+ icon: SFSymbol;
36
+ href: Parameters<typeof router.push>[0];
37
+ keywords: string;
38
+ };
39
+
40
+ const DESTINATIONS: readonly Destination[] = [
41
+ {
42
+ title: "Home",
43
+ subtitle: "Recent activity and updates",
44
+ icon: "house.fill",
45
+ href: "/(app)/(tabs)/(home)",
46
+ keywords: "home dashboard activity feed",
47
+ },
48
+ {
49
+ title: "Settings",
50
+ subtitle: "Account, preferences, and devices",
51
+ icon: "gearshape.fill",
52
+ href: "/(app)/(tabs)/settings",
53
+ keywords: "settings options config",
54
+ },
55
+ {
56
+ title: "Preferences",
57
+ subtitle: "Theme, motion, haptics, dynamic type",
58
+ icon: "slider.horizontal.3",
59
+ href: "/(app)/(tabs)/settings/preferences",
60
+ keywords: "preferences theme dark light motion reduced haptics accessibility",
61
+ },
62
+ {
63
+ title: "Profile",
64
+ subtitle: "Name, username, email, bio, avatar",
65
+ icon: "person.crop.circle.fill",
66
+ href: "/(app)/profile",
67
+ keywords: "profile name username email bio avatar account",
68
+ },
69
+ {
70
+ title: "Active sessions",
71
+ subtitle: "Devices currently signed in",
72
+ icon: "list.bullet.rectangle.portrait.fill",
73
+ href: "/(app)/sessions",
74
+ keywords: "sessions devices logout signed in revoke",
75
+ },
76
+ {
77
+ title: "Linked accounts",
78
+ subtitle: "Apple, email, social providers",
79
+ icon: "link.circle.fill",
80
+ href: "/(app)/linked",
81
+ keywords: "linked apple sign in social providers oauth",
82
+ },
83
+ {
84
+ title: "Help",
85
+ subtitle: "FAQ and support",
86
+ icon: "questionmark.circle.fill",
87
+ href: "/(app)/help",
88
+ keywords: "help faq support contact email issue",
89
+ },
90
+ {
91
+ title: "Privacy",
92
+ subtitle: "How your data is handled",
93
+ icon: "lock.shield.fill",
94
+ href: "/(app)/privacy",
95
+ keywords: "privacy data tracking apple labels",
96
+ },
97
+ ];
98
+
99
+ const DEBUG_DESTINATION: Destination = {
100
+ title: "Debug",
101
+ subtitle: "Version, build, OTA update, device, push diagnostics",
102
+ icon: "ant.circle.fill",
103
+ href: "/(app)/debug" as Destination["href"],
104
+ keywords: "debug diagnostics version build sdk runtime release update vendor push session",
105
+ };
106
+
107
+ const DEBOUNCE_MS = 120;
108
+
109
+ function score(d: Destination, query: string): number {
110
+ if (query.length === 0) return 0;
111
+ const q = query.toLowerCase();
112
+ const title = d.title.toLowerCase();
113
+ if (title === q) return 100;
114
+ if (title.startsWith(q)) return 80;
115
+ if (title.includes(q)) return 60;
116
+ if (d.subtitle.toLowerCase().includes(q)) return 40;
117
+ if (d.keywords.includes(q)) return 20;
118
+ return 0;
119
+ }
120
+
121
+ export default function SearchScreen() {
122
+ const dfont = useDynamicFont();
123
+ const colors = useColors();
124
+ const [raw, setRaw] = useState("");
125
+ const query = useDebounce(raw, DEBOUNCE_MS);
126
+ const [debugOn] = useDebugEnabled();
127
+
128
+ const destinations = useMemo<readonly Destination[]>(
129
+ () => (debugOn ? [...DESTINATIONS, DEBUG_DESTINATION] : DESTINATIONS),
130
+ [debugOn],
131
+ );
132
+
133
+ const results = useMemo(() => {
134
+ if (query.trim().length === 0) return destinations;
135
+ const trimmed = query.trim();
136
+ return destinations
137
+ .map((d) => ({ d, s: score(d, trimmed) }))
138
+ .filter(({ s }) => s > 0)
139
+ .toSorted((a, b) => b.s - a.s)
140
+ .map(({ d }) => d);
141
+ }, [query, destinations]);
142
+
143
+ const open = (href: Destination["href"]) => {
144
+ haptics.light();
145
+ router.push(href);
146
+ };
147
+
148
+ const sectionLabelModifiers = [
149
+ dfont({ size: 13, weight: "semibold" }),
150
+ foregroundStyle(colors.mutedForeground as string),
151
+ padding({ horizontal: 8, top: 4 }),
152
+ ];
153
+
154
+ return (
155
+ <>
156
+ <Stack.SearchBar
157
+ placement="automatic"
158
+ placeholder="Search the app"
159
+ onChangeText={(e) => setRaw(e.nativeEvent.text)}
160
+ />
161
+ <Host style={{ flex: 1, backgroundColor: colors.background }}>
162
+ <ScrollView
163
+ modifiers={[scrollDismissesKeyboard("interactively"), tint(colors.primary as string)]}
164
+ >
165
+ <VStack
166
+ spacing={12}
167
+ alignment="leading"
168
+ modifiers={[padding({ horizontal: 24, top: 16, bottom: 40 })]}
169
+ >
170
+ {results.length === 0 ? (
171
+ <ContentUnavailableView
172
+ title="No results"
173
+ systemImage="magnifyingglass"
174
+ description={`Nothing matches "${query.trim()}"`}
175
+ />
176
+ ) : (
177
+ <VStack spacing={8} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
178
+ <Text modifiers={sectionLabelModifiers}>
179
+ {query.trim() ? "RESULTS" : "JUMP TO"}
180
+ </Text>
181
+ {results.map((d) => (
182
+ <Button
183
+ key={d.href as string}
184
+ modifiers={[
185
+ buttonStyle("plain"),
186
+ frame({ maxWidth: 10000 }),
187
+ background(colors.muted as string),
188
+ clipShape("capsule"),
189
+ ]}
190
+ onPress={() => open(d.href)}
191
+ >
192
+ <HStack
193
+ spacing={14}
194
+ alignment="center"
195
+ modifiers={[
196
+ frame({ maxWidth: 10000 }),
197
+ padding({ horizontal: 16, vertical: 12 }),
198
+ ]}
199
+ >
200
+ <Image systemName={d.icon} size={20} color={colors.foreground as string} />
201
+ <VStack alignment="leading" spacing={2}>
202
+ <Text
203
+ modifiers={[
204
+ dfont({ size: 16, weight: "medium" }),
205
+ foregroundStyle(colors.foreground as string),
206
+ ]}
207
+ >
208
+ {d.title}
209
+ </Text>
210
+ <Text
211
+ modifiers={[
212
+ dfont({ size: 13 }),
213
+ foregroundStyle(colors.mutedForeground as string),
214
+ ]}
215
+ >
216
+ {d.subtitle}
217
+ </Text>
218
+ </VStack>
219
+ <Spacer />
220
+ <Image
221
+ systemName="chevron.right"
222
+ size={13}
223
+ color={colors.mutedForeground as string}
224
+ />
225
+ </HStack>
226
+ </Button>
227
+ ))}
228
+ </VStack>
229
+ )}
230
+
231
+ {query.trim() === "" ? (
232
+ <Text
233
+ modifiers={[
234
+ dfont({ size: 13 }),
235
+ foregroundStyle(colors.mutedForeground as string),
236
+ padding({ horizontal: 8, top: 4 }),
237
+ ]}
238
+ >
239
+ Type to find any screen.
240
+ </Text>
241
+ ) : null}
242
+ </VStack>
243
+ </ScrollView>
244
+ </Host>
245
+ </>
246
+ );
247
+ }
@@ -0,0 +1,77 @@
1
+ import { NativeTabs } from "expo-router/unstable-native-tabs";
2
+
3
+ import { FontFamily, FontSize } from "@/constants/layout";
4
+ import { useColors } from "@/hooks/use-theme";
5
+ import { haptics } from "@/lib/haptics";
6
+ import { LoadingScreen } from "@/components/ui/loading-screen";
7
+
8
+ export function SuspenseFallback() {
9
+ return <LoadingScreen />;
10
+ }
11
+
12
+ export default function TabLayout() {
13
+ const colors = useColors();
14
+ return (
15
+ <NativeTabs
16
+ backgroundColor={colors.background}
17
+ blurEffect="systemDefault"
18
+ tintColor={colors.tabIconSelected}
19
+ iconColor={{
20
+ default: colors.tabIconDefault,
21
+ selected: colors.tabIconSelected,
22
+ }}
23
+ labelStyle={{
24
+ fontFamily: FontFamily.medium,
25
+ fontSize: FontSize.xs,
26
+ }}
27
+ sidebarAdaptable
28
+ minimizeBehavior="automatic"
29
+ shadowColor={colors.separator}
30
+ badgeBackgroundColor={colors.destructive}
31
+ screenListeners={
32
+ __DEV__
33
+ ? {
34
+ tabPress: (e) => {
35
+ console.log("[Tab]", e.target);
36
+ },
37
+ }
38
+ : undefined
39
+ }
40
+ >
41
+ <NativeTabs.Trigger
42
+ name="(home)"
43
+ contentStyle={{ backgroundColor: colors.background }}
44
+ listeners={{
45
+ tabPress: () => haptics.light(),
46
+ }}
47
+ >
48
+ <NativeTabs.Trigger.Icon sf={{ default: "house", selected: "house.fill" }} />
49
+ <NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
50
+ <NativeTabs.Trigger.Badge hidden />
51
+ </NativeTabs.Trigger>
52
+
53
+ <NativeTabs.Trigger
54
+ name="settings"
55
+ contentStyle={{ backgroundColor: colors.background as string }}
56
+ listeners={{
57
+ tabPress: () => haptics.light(),
58
+ }}
59
+ >
60
+ <NativeTabs.Trigger.Icon sf={{ default: "gearshape", selected: "gearshape.fill" }} />
61
+ <NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>
62
+ </NativeTabs.Trigger>
63
+
64
+ <NativeTabs.Trigger
65
+ name="(search)"
66
+ role="search"
67
+ contentStyle={{ backgroundColor: colors.background as string }}
68
+ listeners={{
69
+ tabPress: () => haptics.light(),
70
+ }}
71
+ >
72
+ <NativeTabs.Trigger.Icon sf="magnifyingglass" />
73
+ <NativeTabs.Trigger.Label>Search</NativeTabs.Trigger.Label>
74
+ </NativeTabs.Trigger>
75
+ </NativeTabs>
76
+ );
77
+ }
@@ -0,0 +1,37 @@
1
+ import { Stack } from "expo-router";
2
+
3
+ import { useColors } from "@/hooks/use-theme";
4
+ import { useReducedMotion } from "@/hooks/use-reduced-motion";
5
+ import { HeaderTint } from "@/constants/theme";
6
+ import { LoadingScreen } from "@/components/ui/loading-screen";
7
+
8
+ export const unstable_settings = {
9
+ initialRouteName: "index",
10
+ };
11
+
12
+ export function SuspenseFallback() {
13
+ return <LoadingScreen />;
14
+ }
15
+
16
+ export default function SettingsLayout() {
17
+ const colors = useColors();
18
+ const reduceMotion = useReducedMotion();
19
+
20
+ return (
21
+ <Stack
22
+ screenOptions={{
23
+ headerShown: false,
24
+ contentStyle: { backgroundColor: colors.background as string },
25
+ animation: reduceMotion ? "fade" : "default",
26
+ animationDuration: reduceMotion ? 150 : undefined,
27
+ }}
28
+ >
29
+ <Stack.Screen name="index" />
30
+ <Stack.Screen name="preferences" options={{ headerShown: true }}>
31
+ <Stack.Header transparent />
32
+ <Stack.Screen.Title style={{ color: HeaderTint as string }}>Preferences</Stack.Screen.Title>
33
+ <Stack.Screen.BackButton>Settings</Stack.Screen.BackButton>
34
+ </Stack.Screen>
35
+ </Stack>
36
+ );
37
+ }