@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,71 @@
1
+ import { Button, Text } from "@expo/ui/swift-ui";
2
+ import {
3
+ background,
4
+ buttonStyle,
5
+ clipShape,
6
+ disabled as disabledModifier,
7
+ foregroundStyle,
8
+ frame,
9
+ multilineTextAlignment,
10
+ } from "@expo/ui/swift-ui/modifiers";
11
+
12
+ import { useDynamicFont } from "@/lib/dynamic-font";
13
+ import { Button as ButtonTokens } from "@/constants/layout";
14
+ import { useColors } from "@/hooks/use-theme";
15
+
16
+ // Full-width prominent action button. Capsule shape, shadcn `primary` fill,
17
+ // `primaryForeground` Geist bold label.
18
+ //
19
+ // Why this isn't `buttonStyle("borderedProminent")`:
20
+ // SwiftUI's borderedProminent paints the bg with the tint color but hardcodes
21
+ // the label foreground to `.white`. Our shadcn `primary` is near-white in dark
22
+ // mode (n200) and near-black in light mode (n900). Pairing borderedProminent
23
+ // with that tint gives white-on-white in dark mode. To honor the shadcn
24
+ // neutral palette in both schemes we paint the bg ourselves and set the label
25
+ // to `primaryForeground` (the true contrast color).
26
+ //
27
+ // Why frame is on the Text, not the Button:
28
+ // SwiftUI's Button label is content-sized. `frame(maxWidth:.infinity)` on the
29
+ // Button itself wraps the styled button in an invisible flex frame without
30
+ // expanding it. Putting the frame on the LABEL inside the button is the fix.
31
+ //
32
+ // Why 10000 instead of Infinity:
33
+ // `Infinity` serialized through the @expo/ui modifier bridge gets ignored by
34
+ // the SwiftUI button's content-sizing logic, leaving the button content-sized.
35
+ // A large finite number behaves as effectively infinite (capped by the parent
36
+ // VStack's available width) and is honored by the bridge.
37
+ export function ProminentButton({
38
+ label,
39
+ onPress,
40
+ disabled,
41
+ }: {
42
+ label: string;
43
+ onPress: () => void;
44
+ disabled?: boolean;
45
+ }) {
46
+ const dfont = useDynamicFont();
47
+ const colors = useColors();
48
+ return (
49
+ <Button
50
+ modifiers={[
51
+ buttonStyle("plain"),
52
+ frame({ maxWidth: 10000 }),
53
+ background(colors.primary as string),
54
+ clipShape("capsule"),
55
+ disabledModifier(disabled ?? false),
56
+ ]}
57
+ onPress={onPress}
58
+ >
59
+ <Text
60
+ modifiers={[
61
+ frame({ maxWidth: 10000, height: ButtonTokens.height }),
62
+ multilineTextAlignment("center"),
63
+ dfont({ size: ButtonTokens.fontSize, weight: ButtonTokens.fontWeight }),
64
+ foregroundStyle(colors.primaryForeground as string),
65
+ ]}
66
+ >
67
+ {label}
68
+ </Text>
69
+ </Button>
70
+ );
71
+ }
@@ -0,0 +1,107 @@
1
+ import { VStack, HStack, Spacer, Text } from "@expo/ui/swift-ui";
2
+ import { background, clipShape, cornerRadius, frame, padding } from "@expo/ui/swift-ui/modifiers";
3
+
4
+ import { Spacing } from "@/constants/layout";
5
+ import { useColors } from "@/hooks/use-theme";
6
+
7
+ // Skeleton placeholders for initial query loads. SwiftUI-native: filled
8
+ // muted-color boxes laid out in the shape of the screen they're standing
9
+ // in for. No animation. SwiftUI's `Host` doesn't ergonomically support
10
+ // per-tick opacity tweens, and static skeletons satisfy the
11
+ // Reduce Motion accessibility setting automatically.
12
+
13
+ type BarProps = {
14
+ width: number | "fill";
15
+ height: number;
16
+ radius?: number;
17
+ };
18
+
19
+ function Bar({ width, height, radius = 6 }: BarProps): React.ReactNode {
20
+ const colors = useColors();
21
+ return (
22
+ <VStack
23
+ modifiers={[
24
+ frame(width === "fill" ? { maxWidth: Infinity, height } : { width, height }),
25
+ background(colors.muted as string),
26
+ cornerRadius(radius),
27
+ ]}
28
+ >
29
+ <Text> </Text>
30
+ </VStack>
31
+ );
32
+ }
33
+
34
+ function Circle({ size }: { size: number }): React.ReactNode {
35
+ const colors = useColors();
36
+ return (
37
+ <VStack
38
+ modifiers={[
39
+ frame({ width: size, height: size }),
40
+ background(colors.muted as string),
41
+ clipShape("circle"),
42
+ ]}
43
+ >
44
+ <Text> </Text>
45
+ </VStack>
46
+ );
47
+ }
48
+
49
+ // Profile screen skeleton. Mirrors the layout of `app/(app)/profile.tsx`:
50
+ // avatar row + display-name row + email row + sign-in-method row.
51
+ export function SkeletonProfile(): React.ReactNode {
52
+ return (
53
+ <VStack alignment="leading" spacing={Spacing.xl} modifiers={[padding({ all: 24 })]}>
54
+ <HStack spacing={Spacing.lg}>
55
+ <Circle size={72} />
56
+ <VStack alignment="leading" spacing={Spacing.sm}>
57
+ <Bar width={160} height={20} />
58
+ <Bar width={120} height={14} />
59
+ </VStack>
60
+ <Spacer />
61
+ </HStack>
62
+ <VStack alignment="leading" spacing={Spacing.md}>
63
+ <Bar width={80} height={14} />
64
+ <Bar width="fill" height={44} radius={22} />
65
+ </VStack>
66
+ <VStack alignment="leading" spacing={Spacing.md}>
67
+ <Bar width={80} height={14} />
68
+ <Bar width="fill" height={44} radius={22} />
69
+ </VStack>
70
+ <VStack alignment="leading" spacing={Spacing.md}>
71
+ <Bar width={120} height={14} />
72
+ <Bar width="fill" height={44} radius={22} />
73
+ </VStack>
74
+ </VStack>
75
+ );
76
+ }
77
+
78
+ // Sessions screen skeleton. Three placeholder rows mirroring the
79
+ // device-by-device shape in `app/(app)/sessions.tsx`.
80
+ export function SkeletonSessions(): React.ReactNode {
81
+ return (
82
+ <VStack alignment="leading" spacing={Spacing.md} modifiers={[padding({ all: 24 })]}>
83
+ <SkeletonSessionRow />
84
+ <SkeletonSessionRow />
85
+ <SkeletonSessionRow />
86
+ </VStack>
87
+ );
88
+ }
89
+
90
+ function SkeletonSessionRow(): React.ReactNode {
91
+ const colors = useColors();
92
+ return (
93
+ <VStack
94
+ alignment="leading"
95
+ spacing={Spacing.md}
96
+ modifiers={[padding({ all: 16 }), background(colors.card as string), cornerRadius(12)]}
97
+ >
98
+ <HStack spacing={Spacing.md}>
99
+ <Bar width={140} height={18} />
100
+ <Spacer />
101
+ <Bar width={60} height={14} />
102
+ </HStack>
103
+ <Bar width={200} height={14} />
104
+ <Bar width={180} height={14} />
105
+ </VStack>
106
+ );
107
+ }
@@ -0,0 +1,49 @@
1
+ import { useEffect } from "react";
2
+ import { AccessibilityInfo } from "react-native";
3
+ import { HStack, Image, Text } from "@expo/ui/swift-ui";
4
+ import { foregroundStyle } from "@expo/ui/swift-ui/modifiers";
5
+
6
+ import { useDynamicFont } from "@/lib/dynamic-font";
7
+ import { Colors } from "@/constants/theme";
8
+
9
+ type Props = { children: string; size?: number };
10
+
11
+ function announce(prefix: string, message: string) {
12
+ AccessibilityInfo.announceForAccessibility(`${prefix}: ${message}`);
13
+ }
14
+
15
+ export function ErrorText({ children, size = 14 }: Props) {
16
+ const dfont = useDynamicFont();
17
+ useEffect(() => {
18
+ announce("Error", children);
19
+ }, [children]);
20
+
21
+ return (
22
+ <HStack spacing={6} alignment="center">
23
+ <Image
24
+ systemName="exclamationmark.triangle.fill"
25
+ size={size}
26
+ color={Colors.destructive as string}
27
+ />
28
+ <Text modifiers={[dfont({ size }), foregroundStyle(Colors.destructive as string)]}>
29
+ {children}
30
+ </Text>
31
+ </HStack>
32
+ );
33
+ }
34
+
35
+ export function SuccessText({ children, size = 14 }: Props) {
36
+ const dfont = useDynamicFont();
37
+ useEffect(() => {
38
+ announce("Success", children);
39
+ }, [children]);
40
+
41
+ return (
42
+ <HStack spacing={6} alignment="center">
43
+ <Image systemName="checkmark.circle.fill" size={size} color={Colors.success as string} />
44
+ <Text modifiers={[dfont({ size }), foregroundStyle(Colors.success as string)]}>
45
+ {children}
46
+ </Text>
47
+ </HStack>
48
+ );
49
+ }
@@ -0,0 +1,82 @@
1
+ import { Pressable, Text, View } from "react-native";
2
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
3
+
4
+ import { Material } from "@/components/ui/material";
5
+ import { useAppUpdates } from "@/hooks/use-updates";
6
+ import { Spacing, FontSize, FontFamily } from "@/constants/layout";
7
+ import { Radius } from "@/constants/theme";
8
+ import { ZIndex } from "@/constants/ui";
9
+ import { useColors } from "@/hooks/use-theme";
10
+
11
+ // In-app surface for the EAS Update lifecycle. Mirrors the OfflineBanner
12
+ // pattern (translucent material overlaying the nav layer per HIG) and is
13
+ // only visible while the update state machine is doing something the user
14
+ // would want to see:
15
+ //
16
+ // - downloading progress %, no tap target. auto-applies on finish
17
+ // - download failed tap to retry
18
+ // - check failed silent unless the user previously asked for an update
19
+ //
20
+ // `isUpdatePending` (downloaded, awaiting reload) is handled by the
21
+ // `useAppUpdates` hook (auto-reload with reload screen), so we don't
22
+ // surface it here. the splash-screen overlay does the visual work.
23
+ export function UpdateBanner() {
24
+ const updates = useAppUpdates();
25
+ const insets = useSafeAreaInsets();
26
+ const colors = useColors();
27
+
28
+ const showProgress = updates.isDownloading;
29
+ const showError = !!updates.downloadError;
30
+ if (!showProgress && !showError) return null;
31
+
32
+ const tint = showError ? (colors.destructive as string) : (colors.primary as string);
33
+ const fg = showError
34
+ ? (colors.destructiveForeground as string)
35
+ : (colors.primaryForeground as string);
36
+ const pct =
37
+ showProgress && updates.downloadProgress != null
38
+ ? ` ${Math.round(updates.downloadProgress * 100)}%`
39
+ : "";
40
+ const label = showError ? "Update failed. Tap to retry." : `Updating${pct}`;
41
+
42
+ return (
43
+ <View
44
+ accessibilityLiveRegion="polite"
45
+ accessibilityRole="alert"
46
+ style={{
47
+ position: "absolute",
48
+ bottom: 0,
49
+ left: 0,
50
+ right: 0,
51
+ zIndex: ZIndex.updateBanner,
52
+ paddingBottom: insets.bottom,
53
+ }}
54
+ >
55
+ <Pressable
56
+ accessibilityRole="button"
57
+ accessibilityLabel={label}
58
+ accessibilityHint={showError ? "Re-attempts the update download" : undefined}
59
+ disabled={!showError}
60
+ onPress={showError ? () => updates.downloadAndApply() : undefined}
61
+ >
62
+ <Material
63
+ variant="chrome"
64
+ tintColor={tint}
65
+ style={{
66
+ marginHorizontal: Spacing.md,
67
+ marginBottom: Spacing.xs,
68
+ borderRadius: Radius.full,
69
+ overflow: "hidden",
70
+ paddingVertical: Spacing.sm,
71
+ paddingHorizontal: Spacing.lg,
72
+ alignItems: "center",
73
+ }}
74
+ >
75
+ <Text style={{ fontSize: FontSize.md, fontFamily: FontFamily.semiBold, color: fg }}>
76
+ {label}
77
+ </Text>
78
+ </Material>
79
+ </Pressable>
80
+ </View>
81
+ );
82
+ }
@@ -0,0 +1,102 @@
1
+ export const Spacing = {
2
+ xxs: 2,
3
+ xs: 4,
4
+ sm: 8,
5
+ md: 12,
6
+ lg: 16,
7
+ xl: 20,
8
+ "2xl": 24,
9
+ "3xl": 32,
10
+ "4xl": 40,
11
+ } as const;
12
+
13
+ export const TouchTarget = {
14
+ min: 44,
15
+ } as const;
16
+
17
+ export const HitSlop = {
18
+ sm: 8,
19
+ md: 10,
20
+ lg: 12,
21
+ } as const;
22
+
23
+ export const FontSize = {
24
+ xs: 11,
25
+ sm: 12,
26
+ md: 13,
27
+ base: 14,
28
+ lg: 15,
29
+ xl: 16,
30
+ "2xl": 17,
31
+ "3xl": 18,
32
+ "4xl": 20,
33
+ "5xl": 24,
34
+ "6xl": 28,
35
+ "7xl": 30,
36
+ } as const;
37
+
38
+ export const LineHeight = {
39
+ tight: 18,
40
+ base: 20,
41
+ relaxed: 22,
42
+ loose: 24,
43
+ "2xl": 26,
44
+ "3xl": 34,
45
+ "4xl": 38,
46
+ } as const;
47
+
48
+ export const MaxWidth = {
49
+ form: 440,
50
+ content: 600,
51
+ wide: 800,
52
+ } as const;
53
+
54
+ export const Breakpoint = {
55
+ phone: 428,
56
+ tablet: 768,
57
+ desktop: 1024,
58
+ } as const;
59
+
60
+ export const TAB_BAR_HEIGHT = 80;
61
+ export const TAB_BAR_CLEARANCE = TAB_BAR_HEIGHT + Spacing.lg;
62
+
63
+ // Single source of truth for prominent action buttons across the auth flow,
64
+ // onboarding, error states, and OTP. Keeps Sign In, Sign Up, Send Reset
65
+ // Code, Reset Password, Verify, Try Again, and Sign in with Apple visually
66
+ // identical: same height, same capsule corner radius, same Geist label
67
+ // size and weight. Color comes from the shadcn palette (`primary` /
68
+ // `primaryForeground`), never hardcoded.
69
+ export const Button = {
70
+ height: 50,
71
+ cornerRadius: 25,
72
+ fontSize: 17,
73
+ fontWeight: "semibold",
74
+ secondaryFontWeight: "medium",
75
+ } as const;
76
+
77
+ export const IconSize = {
78
+ sm: 14,
79
+ md: 16,
80
+ lg: 18,
81
+ xl: 20,
82
+ "2xl": 22,
83
+ "3xl": 24,
84
+ "4xl": 32,
85
+ "5xl": 48,
86
+ "6xl": 64,
87
+ } as const;
88
+
89
+ export const FontFamily = {
90
+ thin: "Geist-Thin",
91
+ extraLight: "Geist-ExtraLight",
92
+ light: "Geist-Light",
93
+ regular: "Geist-Regular",
94
+ medium: "Geist-Medium",
95
+ semiBold: "Geist-SemiBold",
96
+ bold: "Geist-Bold",
97
+ extraBold: "Geist-ExtraBold",
98
+ black: "Geist-Black",
99
+ mono: "GeistMono-Regular",
100
+ monoMedium: "GeistMono-Medium",
101
+ monoBold: "GeistMono-Bold",
102
+ } as const;