@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.
- package/README.md +50 -0
- package/dist/index.js +183 -0
- package/dist/templates/default/.eas/workflows/asc-events.yml +84 -0
- package/dist/templates/default/.eas/workflows/deploy-production.yml +129 -0
- package/dist/templates/default/.eas/workflows/development-builds.yml +19 -0
- package/dist/templates/default/.eas/workflows/e2e-tests.yml +42 -0
- package/dist/templates/default/.eas/workflows/pr-preview.yml +98 -0
- package/dist/templates/default/.eas/workflows/release.yml +44 -0
- package/dist/templates/default/.eas/workflows/rollback.yml +86 -0
- package/dist/templates/default/.eas/workflows/rollout.yml +84 -0
- package/dist/templates/default/.eas/workflows/rotate-apple-jwt.yml +42 -0
- package/dist/templates/default/.eas/workflows/testflight.yml +57 -0
- package/dist/templates/default/.github/workflows/check.yml +28 -0
- package/dist/templates/default/.maestro/launch.yaml +18 -0
- package/dist/templates/default/AGENTS.md +79 -0
- package/dist/templates/default/DESIGN.md +331 -0
- package/dist/templates/default/LICENSE +21 -0
- package/dist/templates/default/README.md +153 -0
- package/dist/templates/default/SETUP.md +618 -0
- package/dist/templates/default/__tests__/convex/constants.test.ts +49 -0
- package/dist/templates/default/__tests__/convex/validators.test.ts +23 -0
- package/dist/templates/default/__tests__/convex/webhook.test.ts +343 -0
- package/dist/templates/default/__tests__/lib/deep-link.test.ts +67 -0
- package/dist/templates/default/_easignore +22 -0
- package/dist/templates/default/_editorconfig +9 -0
- package/dist/templates/default/_env.example +34 -0
- package/dist/templates/default/_fingerprintignore +24 -0
- package/dist/templates/default/_gitattributes +7 -0
- package/dist/templates/default/_gitignore +69 -0
- package/dist/templates/default/_oxfmtrc.json +3 -0
- package/dist/templates/default/_oxlintrc.json +34 -0
- package/dist/templates/default/app/(app)/(tabs)/(home)/index.tsx +50 -0
- package/dist/templates/default/app/(app)/(tabs)/(home,search)/_layout.tsx +44 -0
- package/dist/templates/default/app/(app)/(tabs)/(search)/index.tsx +247 -0
- package/dist/templates/default/app/(app)/(tabs)/_layout.tsx +77 -0
- package/dist/templates/default/app/(app)/(tabs)/settings/_layout.tsx +37 -0
- package/dist/templates/default/app/(app)/(tabs)/settings/index.tsx +362 -0
- package/dist/templates/default/app/(app)/(tabs)/settings/preferences.tsx +184 -0
- package/dist/templates/default/app/(app)/_layout.tsx +73 -0
- package/dist/templates/default/app/(app)/debug.tsx +389 -0
- package/dist/templates/default/app/(app)/help.tsx +254 -0
- package/dist/templates/default/app/(app)/linked.tsx +116 -0
- package/dist/templates/default/app/(app)/privacy.tsx +159 -0
- package/dist/templates/default/app/(app)/profile.tsx +915 -0
- package/dist/templates/default/app/(app)/sessions.tsx +191 -0
- package/dist/templates/default/app/(app)/welcome.tsx +140 -0
- package/dist/templates/default/app/(auth)/_layout.tsx +31 -0
- package/dist/templates/default/app/(auth)/forgot-password.tsx +168 -0
- package/dist/templates/default/app/(auth)/reset-password.tsx +314 -0
- package/dist/templates/default/app/(auth)/sign-in.tsx +453 -0
- package/dist/templates/default/app/(auth)/sign-up.tsx +563 -0
- package/dist/templates/default/app/+native-intent.tsx +14 -0
- package/dist/templates/default/app/+not-found.tsx +51 -0
- package/dist/templates/default/app/_layout.tsx +102 -0
- package/dist/templates/default/app-store/screenshots/.gitkeep +0 -0
- package/dist/templates/default/app-store/screenshots/README.md +13 -0
- package/dist/templates/default/app.config.ts +201 -0
- package/dist/templates/default/app.json +11 -0
- package/dist/templates/default/assets/brand-icon-dark.png +0 -0
- package/dist/templates/default/assets/brand-icon-light.png +0 -0
- package/dist/templates/default/assets/fonts/Geist-Black.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-BlackItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Bold.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-BoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraBold.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraBoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraLight.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ExtraLightItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Italic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Light.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-LightItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Medium.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-MediumItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Regular.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-SemiBold.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-SemiBoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Thin.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-ThinItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Variable-Italic.ttf +0 -0
- package/dist/templates/default/assets/fonts/Geist-Variable.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Bold.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-BoldItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Italic.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Medium.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-MediumItalic.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistMono-Regular.ttf +0 -0
- package/dist/templates/default/assets/fonts/GeistPixel-Square.ttf +0 -0
- package/dist/templates/default/assets/icon.png +0 -0
- package/dist/templates/default/assets/sounds/notification.wav +0 -0
- package/dist/templates/default/assets/splash-image-dark.png +0 -0
- package/dist/templates/default/assets/splash-image-light.png +0 -0
- package/dist/templates/default/bun.lock +1860 -0
- package/dist/templates/default/components/auth/otp-verification.tsx +255 -0
- package/dist/templates/default/components/auth/password-field.tsx +121 -0
- package/dist/templates/default/components/auth/segmented-toggle.tsx +47 -0
- package/dist/templates/default/components/ui/convex-error.tsx +32 -0
- package/dist/templates/default/components/ui/error-boundary.tsx +57 -0
- package/dist/templates/default/components/ui/loading-screen.tsx +31 -0
- package/dist/templates/default/components/ui/material.tsx +94 -0
- package/dist/templates/default/components/ui/offline-banner.tsx +58 -0
- package/dist/templates/default/components/ui/prominent-button.tsx +71 -0
- package/dist/templates/default/components/ui/skeleton.tsx +107 -0
- package/dist/templates/default/components/ui/status-text.tsx +49 -0
- package/dist/templates/default/components/ui/update-banner.tsx +82 -0
- package/dist/templates/default/constants/layout.ts +102 -0
- package/dist/templates/default/constants/theme.ts +401 -0
- package/dist/templates/default/constants/ui.ts +77 -0
- package/dist/templates/default/convex/_generated/api.d.ts +77 -0
- package/dist/templates/default/convex/_generated/api.js +23 -0
- package/dist/templates/default/convex/_generated/dataModel.d.ts +60 -0
- package/dist/templates/default/convex/_generated/server.d.ts +143 -0
- package/dist/templates/default/convex/_generated/server.js +93 -0
- package/dist/templates/default/convex/admin.ts +102 -0
- package/dist/templates/default/convex/auth.config.ts +6 -0
- package/dist/templates/default/convex/auth.ts +335 -0
- package/dist/templates/default/convex/constants.ts +46 -0
- package/dist/templates/default/convex/convex.config.ts +11 -0
- package/dist/templates/default/convex/crons.ts +42 -0
- package/dist/templates/default/convex/email.ts +109 -0
- package/dist/templates/default/convex/env.ts +31 -0
- package/dist/templates/default/convex/errors.ts +33 -0
- package/dist/templates/default/convex/functions.ts +54 -0
- package/dist/templates/default/convex/http.ts +176 -0
- package/dist/templates/default/convex/log.ts +81 -0
- package/dist/templates/default/convex/pushTokens.ts +114 -0
- package/dist/templates/default/convex/rateLimit.ts +92 -0
- package/dist/templates/default/convex/schema.ts +28 -0
- package/dist/templates/default/convex/tsconfig.json +18 -0
- package/dist/templates/default/convex/users.ts +279 -0
- package/dist/templates/default/convex/validators.ts +74 -0
- package/dist/templates/default/convex/webhook.ts +193 -0
- package/dist/templates/default/convex.json +6 -0
- package/dist/templates/default/eas.json +56 -0
- package/dist/templates/default/fingerprint.config.js +9 -0
- package/dist/templates/default/hooks/use-debounce.ts +20 -0
- package/dist/templates/default/hooks/use-deep-link.ts +43 -0
- package/dist/templates/default/hooks/use-navigation-tracking.ts +15 -0
- package/dist/templates/default/hooks/use-network.ts +11 -0
- package/dist/templates/default/hooks/use-notifications.ts +107 -0
- package/dist/templates/default/hooks/use-onboarding.ts +15 -0
- package/dist/templates/default/hooks/use-reduced-motion.ts +11 -0
- package/dist/templates/default/hooks/use-theme.ts +53 -0
- package/dist/templates/default/hooks/use-updates.ts +86 -0
- package/dist/templates/default/lib/a11y.ts +5 -0
- package/dist/templates/default/lib/app.ts +14 -0
- package/dist/templates/default/lib/assets.ts +17 -0
- package/dist/templates/default/lib/auth-client.ts +21 -0
- package/dist/templates/default/lib/convex-auth.tsx +79 -0
- package/dist/templates/default/lib/deep-link.ts +71 -0
- package/dist/templates/default/lib/dev-menu.ts +119 -0
- package/dist/templates/default/lib/device.ts +40 -0
- package/dist/templates/default/lib/dynamic-font.ts +49 -0
- package/dist/templates/default/lib/env.ts +10 -0
- package/dist/templates/default/lib/haptics.ts +24 -0
- package/dist/templates/default/lib/notifications.ts +276 -0
- package/dist/templates/default/lib/preferences.ts +45 -0
- package/dist/templates/default/lib/schemas.ts +137 -0
- package/dist/templates/default/lib/storage.ts +47 -0
- package/dist/templates/default/lib/updates.ts +107 -0
- package/dist/templates/default/metro.config.js +14 -0
- package/dist/templates/default/package.json +129 -0
- package/dist/templates/default/patches/PR-368.patch +91 -0
- package/dist/templates/default/patches/convex-dev-better-auth-0.12.2.tgz +0 -0
- package/dist/templates/default/plugins/README.md +9 -0
- package/dist/templates/default/plugins/with-auto-signing.js +45 -0
- package/dist/templates/default/plugins/with-pod-deployment-target.js +35 -0
- package/dist/templates/default/scripts/README.md +36 -0
- package/dist/templates/default/scripts/_run.mjs +77 -0
- package/dist/templates/default/scripts/clean.ts +543 -0
- package/dist/templates/default/scripts/rotate-apple-jwt.mjs +80 -0
- package/dist/templates/default/store.config.json +58 -0
- package/dist/templates/default/tsconfig.json +13 -0
- package/dist/templates/default/vitest.config.ts +21 -0
- 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;
|