@beyondcorp/beyond-ui-native 0.1.2 → 0.1.4

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 (71) hide show
  1. package/dist/index.js +78 -61
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +78 -61
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +11 -5
  6. package/src/components/accordion/accordion.tsx +245 -0
  7. package/src/components/accordion/index.ts +1 -0
  8. package/src/components/alert/alert.tsx +118 -0
  9. package/src/components/alert/index.ts +1 -0
  10. package/src/components/avatar/avatar.tsx +125 -0
  11. package/src/components/avatar/index.ts +1 -0
  12. package/src/components/badge/badge.tsx +89 -0
  13. package/src/components/badge/index.ts +1 -0
  14. package/src/components/bottom-sheet/bottom-sheet.tsx +336 -0
  15. package/src/components/bottom-sheet/index.ts +1 -0
  16. package/src/components/button/button.tsx +172 -0
  17. package/src/components/button/index.ts +1 -0
  18. package/src/components/card/card.tsx +121 -0
  19. package/src/components/card/index.ts +1 -0
  20. package/src/components/checkbox/checkbox.tsx +100 -0
  21. package/src/components/checkbox/index.ts +1 -0
  22. package/src/components/data-table/data-table.tsx +276 -0
  23. package/src/components/data-table/index.ts +1 -0
  24. package/src/components/dialog/dialog.tsx +278 -0
  25. package/src/components/dialog/index.ts +1 -0
  26. package/src/components/input/index.ts +1 -0
  27. package/src/components/input/input.tsx +130 -0
  28. package/src/components/radio/index.ts +1 -0
  29. package/src/components/radio/radio.tsx +117 -0
  30. package/src/components/select/index.ts +1 -0
  31. package/src/components/select/select.tsx +377 -0
  32. package/src/components/separator/index.ts +1 -0
  33. package/src/components/separator/separator.tsx +28 -0
  34. package/src/components/skeleton/index.ts +1 -0
  35. package/src/components/skeleton/skeleton.tsx +43 -0
  36. package/src/components/spinner/index.ts +1 -0
  37. package/src/components/spinner/spinner.tsx +46 -0
  38. package/src/components/stats-card/index.ts +1 -0
  39. package/src/components/stats-card/stats-card.tsx +92 -0
  40. package/src/components/switch/index.ts +1 -0
  41. package/src/components/switch/switch.tsx +80 -0
  42. package/src/components/tabs/index.ts +1 -0
  43. package/src/components/tabs/tabs.tsx +172 -0
  44. package/src/components/text/heading.tsx +50 -0
  45. package/src/components/text/index.ts +2 -0
  46. package/src/components/text/text.tsx +62 -0
  47. package/src/components/textarea/index.ts +1 -0
  48. package/src/components/textarea/textarea.tsx +87 -0
  49. package/src/components/toast/index.ts +2 -0
  50. package/src/components/toast/toast.tsx +136 -0
  51. package/src/components/toast/types.ts +32 -0
  52. package/src/index.ts +42 -0
  53. package/src/primitives/box.tsx +25 -0
  54. package/src/primitives/index.ts +2 -0
  55. package/src/primitives/pressable.tsx +49 -0
  56. package/src/providers/BeyondUIProvider.tsx +119 -0
  57. package/src/templates/auth/ForgotPasswordTemplate.tsx +132 -0
  58. package/src/templates/auth/SignInTemplate.tsx +213 -0
  59. package/src/templates/auth/SignUpTemplate.tsx +233 -0
  60. package/src/templates/auth/index.ts +3 -0
  61. package/src/templates/dashboard/DashboardTemplate.tsx +204 -0
  62. package/src/templates/dashboard/index.ts +1 -0
  63. package/src/templates/index.ts +3 -0
  64. package/src/templates/settings/SettingsTemplate.tsx +226 -0
  65. package/src/templates/settings/index.ts +1 -0
  66. package/src/theme/preset.ts +45 -0
  67. package/src/theme/tokens.ts +144 -0
  68. package/src/types/nativewind-env.d.ts +26 -0
  69. package/src/utilities/accessibility.ts +43 -0
  70. package/src/utilities/cn.ts +9 -0
  71. package/src/utilities/index.ts +2 -0
@@ -0,0 +1,213 @@
1
+ import * as React from "react";
2
+ import {
3
+ View,
4
+ ScrollView,
5
+ KeyboardAvoidingView,
6
+ Platform,
7
+ Pressable,
8
+ } from "react-native";
9
+ import { cn } from "../../utilities/cn";
10
+ import { Text } from "../../components/text/text";
11
+ import { Heading } from "../../components/text/heading";
12
+ import { Input } from "../../components/input/input";
13
+ import { Button } from "../../components/button/button";
14
+ import { Checkbox } from "../../components/checkbox/checkbox";
15
+ import { Alert, AlertDescription } from "../../components/alert/alert";
16
+ import { Separator } from "../../components/separator/separator";
17
+
18
+ export interface SignInTemplateProps {
19
+ title?: string;
20
+ subtitle?: string;
21
+ logo?: React.ReactNode;
22
+ onSubmit?: (data: { email: string; password: string; rememberMe: boolean }) => void;
23
+ onForgotPassword?: () => void;
24
+ onSignUpPress?: () => void;
25
+ onGooglePress?: () => void;
26
+ onApplePress?: () => void;
27
+ isLoading?: boolean;
28
+ error?: string | null;
29
+ className?: string;
30
+ }
31
+
32
+ export const SignInTemplate: React.FC<SignInTemplateProps> = ({
33
+ title = "Welcome Back",
34
+ subtitle = "Sign in to your Beyond account to continue",
35
+ logo,
36
+ onSubmit,
37
+ onForgotPassword,
38
+ onSignUpPress,
39
+ onGooglePress,
40
+ onApplePress,
41
+ isLoading = false,
42
+ error,
43
+ className,
44
+ }) => {
45
+ const [email, setEmail] = React.useState("");
46
+ const [password, setPassword] = React.useState("");
47
+ const [showPassword, setShowPassword] = React.useState(false);
48
+ const [rememberMe, setRememberMe] = React.useState(false);
49
+ const [emailError, setEmailError] = React.useState<string | undefined>();
50
+ const [passwordError, setPasswordError] = React.useState<string | undefined>();
51
+
52
+ const handleSubmit = () => {
53
+ let valid = true;
54
+ setEmailError(undefined);
55
+ setPasswordError(undefined);
56
+
57
+ if (!email || !email.includes("@")) {
58
+ setEmailError("Please enter a valid email address");
59
+ valid = false;
60
+ }
61
+ if (!password || password.length < 6) {
62
+ setPasswordError("Password must be at least 6 characters");
63
+ valid = false;
64
+ }
65
+
66
+ if (valid) {
67
+ onSubmit?.({ email, password, rememberMe });
68
+ }
69
+ };
70
+
71
+ return (
72
+ <KeyboardAvoidingView
73
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
74
+ style={{ flex: 1 }}
75
+ className={cn("flex-1 bg-background", className)}
76
+ >
77
+ <ScrollView
78
+ style={{ flex: 1 }}
79
+ contentContainerStyle={{ flexGrow: 1, justifyContent: "center" }}
80
+ className="px-6 py-8"
81
+ keyboardShouldPersistTaps="handled"
82
+ showsVerticalScrollIndicator={false}
83
+ >
84
+ <View className="w-full max-w-md mx-auto gap-6">
85
+ {/* Logo & Header */}
86
+ <View className="items-center gap-2">
87
+ {logo && <View className="mb-2">{logo}</View>}
88
+ <Heading level={2} className="text-3xl font-extrabold text-foreground text-center">
89
+ {title}
90
+ </Heading>
91
+ <Text className="text-muted text-center text-base">
92
+ {subtitle}
93
+ </Text>
94
+ </View>
95
+
96
+ {/* Error Banner */}
97
+ {error && (
98
+ <Alert variant="danger">
99
+ <AlertDescription>{error}</AlertDescription>
100
+ </Alert>
101
+ )}
102
+
103
+ {/* Fields */}
104
+ <View className="gap-4">
105
+ <Input
106
+ label="Email Address"
107
+ placeholder="you@company.com"
108
+ keyboardType="email-address"
109
+ autoCapitalize="none"
110
+ autoCorrect={false}
111
+ value={email}
112
+ onChangeText={(text) => {
113
+ setEmail(text);
114
+ if (emailError) setEmailError(undefined);
115
+ }}
116
+ error={emailError}
117
+ />
118
+
119
+ <View className="gap-1">
120
+ <Input
121
+ label="Password"
122
+ placeholder="••••••••"
123
+ secureTextEntry={!showPassword}
124
+ value={password}
125
+ onChangeText={(text) => {
126
+ setPassword(text);
127
+ if (passwordError) setPasswordError(undefined);
128
+ }}
129
+ error={passwordError}
130
+ rightElement={
131
+ <Pressable onPress={() => setShowPassword((p) => !p)} className="p-1">
132
+ <Text className="text-xs text-muted font-semibold">
133
+ {showPassword ? "HIDE" : "SHOW"}
134
+ </Text>
135
+ </Pressable>
136
+ }
137
+ />
138
+ </View>
139
+
140
+ {/* Remember & Forgot */}
141
+ <View className="flex-row items-center justify-between py-1">
142
+ <View className="flex-row items-center gap-2">
143
+ <Checkbox checked={rememberMe} onCheckedChange={setRememberMe} />
144
+ <Text className="text-sm text-foreground">Remember me</Text>
145
+ </View>
146
+
147
+ {onForgotPassword && (
148
+ <Pressable onPress={onForgotPassword}>
149
+ <Text className="text-sm font-semibold text-primary">
150
+ Forgot password?
151
+ </Text>
152
+ </Pressable>
153
+ )}
154
+ </View>
155
+
156
+ {/* Submit */}
157
+ <Button
158
+ variant="primary"
159
+ size="lg"
160
+ loading={isLoading}
161
+ onPress={handleSubmit}
162
+ className="mt-2"
163
+ >
164
+ Sign In
165
+ </Button>
166
+ </View>
167
+
168
+ {/* Social Logins */}
169
+ {(onGooglePress || onApplePress) && (
170
+ <View className="gap-4">
171
+ <View className="flex-row items-center gap-3">
172
+ <Separator className="flex-1" />
173
+ <Text className="text-xs text-muted uppercase">or continue with</Text>
174
+ <Separator className="flex-1" />
175
+ </View>
176
+
177
+ <View className="flex-row gap-3">
178
+ {onGooglePress && (
179
+ <Button
180
+ variant="outline"
181
+ className="flex-1"
182
+ onPress={onGooglePress}
183
+ >
184
+ Google
185
+ </Button>
186
+ )}
187
+ {onApplePress && (
188
+ <Button
189
+ variant="outline"
190
+ className="flex-1"
191
+ onPress={onApplePress}
192
+ >
193
+ Apple
194
+ </Button>
195
+ )}
196
+ </View>
197
+ </View>
198
+ )}
199
+
200
+ {/* Switch to SignUp */}
201
+ {onSignUpPress && (
202
+ <View className="flex-row justify-center items-center gap-1.5 pt-2">
203
+ <Text className="text-sm text-muted">Don't have an account?</Text>
204
+ <Pressable onPress={onSignUpPress}>
205
+ <Text className="text-sm font-bold text-primary">Sign Up</Text>
206
+ </Pressable>
207
+ </View>
208
+ )}
209
+ </View>
210
+ </ScrollView>
211
+ </KeyboardAvoidingView>
212
+ );
213
+ };
@@ -0,0 +1,233 @@
1
+ import * as React from "react";
2
+ import {
3
+ View,
4
+ ScrollView,
5
+ KeyboardAvoidingView,
6
+ Platform,
7
+ Pressable,
8
+ } from "react-native";
9
+ import { cn } from "../../utilities/cn";
10
+ import { Text } from "../../components/text/text";
11
+ import { Heading } from "../../components/text/heading";
12
+ import { Input } from "../../components/input/input";
13
+ import { Button } from "../../components/button/button";
14
+ import { Checkbox } from "../../components/checkbox/checkbox";
15
+ import { Alert, AlertDescription } from "../../components/alert/alert";
16
+ import { Separator } from "../../components/separator/separator";
17
+
18
+ export interface SignUpTemplateProps {
19
+ title?: string;
20
+ subtitle?: string;
21
+ logo?: React.ReactNode;
22
+ onSubmit?: (data: {
23
+ fullName: string;
24
+ email: string;
25
+ password: string;
26
+ agreeToTerms: boolean;
27
+ }) => void;
28
+ onSignInPress?: () => void;
29
+ onGooglePress?: () => void;
30
+ onApplePress?: () => void;
31
+ isLoading?: boolean;
32
+ error?: string | null;
33
+ className?: string;
34
+ }
35
+
36
+ export const SignUpTemplate: React.FC<SignUpTemplateProps> = ({
37
+ title = "Create Account",
38
+ subtitle = "Get started with your free Beyond account",
39
+ logo,
40
+ onSubmit,
41
+ onSignInPress,
42
+ onGooglePress,
43
+ onApplePress,
44
+ isLoading = false,
45
+ error,
46
+ className,
47
+ }) => {
48
+ const [fullName, setFullName] = React.useState("");
49
+ const [email, setEmail] = React.useState("");
50
+ const [password, setPassword] = React.useState("");
51
+ const [agreeToTerms, setAgreeToTerms] = React.useState(false);
52
+
53
+ const [nameError, setNameError] = React.useState<string | undefined>();
54
+ const [emailError, setEmailError] = React.useState<string | undefined>();
55
+ const [passwordError, setPasswordError] = React.useState<string | undefined>();
56
+ const [termsError, setTermsError] = React.useState<string | undefined>();
57
+
58
+ const handleSubmit = () => {
59
+ let valid = true;
60
+ setNameError(undefined);
61
+ setEmailError(undefined);
62
+ setPasswordError(undefined);
63
+ setTermsError(undefined);
64
+
65
+ if (!fullName.trim()) {
66
+ setNameError("Full name is required");
67
+ valid = false;
68
+ }
69
+ if (!email || !email.includes("@")) {
70
+ setEmailError("Please enter a valid email address");
71
+ valid = false;
72
+ }
73
+ if (!password || password.length < 8) {
74
+ setPasswordError("Password must be at least 8 characters");
75
+ valid = false;
76
+ }
77
+ if (!agreeToTerms) {
78
+ setTermsError("You must agree to the Terms of Service");
79
+ valid = false;
80
+ }
81
+
82
+ if (valid) {
83
+ onSubmit?.({ fullName, email, password, agreeToTerms });
84
+ }
85
+ };
86
+
87
+ return (
88
+ <KeyboardAvoidingView
89
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
90
+ style={{ flex: 1 }}
91
+ className={cn("flex-1 bg-background", className)}
92
+ >
93
+ <ScrollView
94
+ style={{ flex: 1 }}
95
+ contentContainerStyle={{ flexGrow: 1, justifyContent: "center" }}
96
+ className="px-6 py-8"
97
+ keyboardShouldPersistTaps="handled"
98
+ showsVerticalScrollIndicator={false}
99
+ >
100
+ <View className="w-full max-w-md mx-auto gap-6">
101
+ {/* Header */}
102
+ <View className="items-center gap-2">
103
+ {logo && <View className="mb-2">{logo}</View>}
104
+ <Heading level={2} className="text-3xl font-extrabold text-foreground text-center">
105
+ {title}
106
+ </Heading>
107
+ <Text className="text-muted text-center text-base">
108
+ {subtitle}
109
+ </Text>
110
+ </View>
111
+
112
+ {/* Error Alert */}
113
+ {error && (
114
+ <Alert variant="danger">
115
+ <AlertDescription>{error}</AlertDescription>
116
+ </Alert>
117
+ )}
118
+
119
+ {/* Form */}
120
+ <View className="gap-4">
121
+ <Input
122
+ label="Full Name"
123
+ placeholder="Mark Otto"
124
+ value={fullName}
125
+ onChangeText={(text) => {
126
+ setFullName(text);
127
+ if (nameError) setNameError(undefined);
128
+ }}
129
+ error={nameError}
130
+ />
131
+
132
+ <Input
133
+ label="Email Address"
134
+ placeholder="you@company.com"
135
+ keyboardType="email-address"
136
+ autoCapitalize="none"
137
+ value={email}
138
+ onChangeText={(text) => {
139
+ setEmail(text);
140
+ if (emailError) setEmailError(undefined);
141
+ }}
142
+ error={emailError}
143
+ />
144
+
145
+ <Input
146
+ label="Password"
147
+ placeholder="At least 8 characters"
148
+ secureTextEntry
149
+ value={password}
150
+ onChangeText={(text) => {
151
+ setPassword(text);
152
+ if (passwordError) setPasswordError(undefined);
153
+ }}
154
+ error={passwordError}
155
+ />
156
+
157
+ {/* Terms checkbox */}
158
+ <View className="gap-1">
159
+ <View className="flex-row items-center gap-2 pt-1">
160
+ <Checkbox
161
+ checked={agreeToTerms}
162
+ onCheckedChange={(checked) => {
163
+ setAgreeToTerms(checked);
164
+ if (termsError) setTermsError(undefined);
165
+ }}
166
+ />
167
+ <Text className="text-sm text-foreground flex-1">
168
+ I agree to the Terms of Service & Privacy Policy
169
+ </Text>
170
+ </View>
171
+ {termsError && (
172
+ <Text className="text-xs text-danger font-medium">{termsError}</Text>
173
+ )}
174
+ </View>
175
+
176
+ {/* Submit */}
177
+ <Button
178
+ variant="primary"
179
+ size="lg"
180
+ loading={isLoading}
181
+ onPress={handleSubmit}
182
+ className="mt-2"
183
+ >
184
+ Create Account
185
+ </Button>
186
+ </View>
187
+
188
+ {/* Social Logins */}
189
+ {(onGooglePress || onApplePress) && (
190
+ <View className="gap-4">
191
+ <View className="flex-row items-center gap-3">
192
+ <Separator className="flex-1" />
193
+ <Text className="text-xs text-muted uppercase">or continue with</Text>
194
+ <Separator className="flex-1" />
195
+ </View>
196
+
197
+ <View className="flex-row gap-3">
198
+ {onGooglePress && (
199
+ <Button
200
+ variant="outline"
201
+ className="flex-1"
202
+ onPress={onGooglePress}
203
+ >
204
+ Google
205
+ </Button>
206
+ )}
207
+ {onApplePress && (
208
+ <Button
209
+ variant="outline"
210
+ className="flex-1"
211
+ onPress={onApplePress}
212
+ >
213
+ Apple
214
+ </Button>
215
+ )}
216
+ </View>
217
+ </View>
218
+ )}
219
+
220
+ {/* Back to SignIn */}
221
+ {onSignInPress && (
222
+ <View className="flex-row justify-center items-center gap-1.5 pt-2">
223
+ <Text className="text-sm text-muted">Already have an account?</Text>
224
+ <Pressable onPress={onSignInPress}>
225
+ <Text className="text-sm font-bold text-primary">Sign In</Text>
226
+ </Pressable>
227
+ </View>
228
+ )}
229
+ </View>
230
+ </ScrollView>
231
+ </KeyboardAvoidingView>
232
+ );
233
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./SignInTemplate";
2
+ export * from "./SignUpTemplate";
3
+ export * from "./ForgotPasswordTemplate";
@@ -0,0 +1,204 @@
1
+ import * as React from "react";
2
+ import {
3
+ View,
4
+ ScrollView,
5
+ Pressable,
6
+ type ViewProps,
7
+ } from "react-native";
8
+ import { cn } from "../../utilities/cn";
9
+ import { Text } from "../../components/text/text";
10
+ import { Heading } from "../../components/text/heading";
11
+ import { Avatar, AvatarFallback, AvatarImage } from "../../components/avatar/avatar";
12
+ import { Badge } from "../../components/badge/badge";
13
+ import { Card, CardHeader, CardTitle, CardContent } from "../../components/card/card";
14
+ import { StatsCard, type StatsCardProps } from "../../components/stats-card/stats-card";
15
+ import { Button } from "../../components/button/button";
16
+
17
+ export interface DashboardMetric extends StatsCardProps {
18
+ id: string;
19
+ }
20
+
21
+ export interface DashboardActivity {
22
+ id: string;
23
+ title: string;
24
+ subtitle: string;
25
+ timestamp: string;
26
+ amount?: string;
27
+ status?: {
28
+ label: string;
29
+ variant: "primary" | "secondary" | "success" | "warning" | "danger" | "outline";
30
+ };
31
+ avatarUrl?: string;
32
+ avatarInitials?: string;
33
+ }
34
+
35
+ export interface DashboardQuickAction {
36
+ id: string;
37
+ label: string;
38
+ icon?: string;
39
+ onPress: () => void;
40
+ }
41
+
42
+ export interface DashboardTemplateProps extends ViewProps {
43
+ userName: string;
44
+ userRole?: string;
45
+ userAvatarUrl?: string;
46
+ metrics: DashboardMetric[];
47
+ quickActions?: DashboardQuickAction[];
48
+ recentActivities?: DashboardActivity[];
49
+ onViewAllActivities?: () => void;
50
+ onNotificationPress?: () => void;
51
+ unreadNotificationsCount?: number;
52
+ className?: string;
53
+ }
54
+
55
+ export const DashboardTemplate: React.FC<DashboardTemplateProps> = ({
56
+ userName,
57
+ userRole = "Administrator",
58
+ userAvatarUrl,
59
+ metrics,
60
+ quickActions,
61
+ recentActivities,
62
+ onViewAllActivities,
63
+ onNotificationPress,
64
+ unreadNotificationsCount = 0,
65
+ className,
66
+ ...props
67
+ }) => {
68
+ return (
69
+ <ScrollView
70
+ {...props}
71
+ style={[{ flex: 1 }, (props as any).style]}
72
+ className={cn("flex-1 bg-background", className)}
73
+ contentContainerStyle={{ paddingBottom: 40 }}
74
+ showsVerticalScrollIndicator={false}
75
+ >
76
+ {/* Top Greeting Header */}
77
+ <View className="px-6 pt-6 pb-4 flex-row items-center justify-between border-b border-border/40">
78
+ <View className="flex-row items-center gap-3">
79
+ <Avatar size="lg">
80
+ {userAvatarUrl && <AvatarImage source={{ uri: userAvatarUrl }} />}
81
+ <AvatarFallback text={userName.slice(0, 2).toUpperCase()} />
82
+ </Avatar>
83
+ <View>
84
+ <Text className="text-xs text-neutral-500 dark:text-neutral-400 font-medium uppercase tracking-wider">
85
+ {userRole}
86
+ </Text>
87
+ <Heading level={3} className="text-xl font-bold text-neutral-900 dark:text-neutral-50">
88
+ Hello, {userName} 👋
89
+ </Heading>
90
+ </View>
91
+ </View>
92
+
93
+ {onNotificationPress && (
94
+ <Pressable
95
+ onPress={onNotificationPress}
96
+ className="w-10 h-10 rounded-full border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 items-center justify-center relative active:opacity-70"
97
+ >
98
+ <Text className="text-base text-neutral-900 dark:text-neutral-100">🔔</Text>
99
+ {unreadNotificationsCount > 0 && (
100
+ <View className="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-danger items-center justify-center">
101
+ <Text className="text-[10px] font-bold text-white">
102
+ {unreadNotificationsCount > 9 ? "9+" : unreadNotificationsCount}
103
+ </Text>
104
+ </View>
105
+ )}
106
+ </Pressable>
107
+ )}
108
+ </View>
109
+
110
+ <View className="px-6 pt-6 gap-6">
111
+ {/* Quick Actions */}
112
+ {quickActions && quickActions.length > 0 && (
113
+ <View className="gap-2">
114
+ <Text className="text-xs font-bold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">
115
+ Quick Actions
116
+ </Text>
117
+ <ScrollView
118
+ horizontal
119
+ showsHorizontalScrollIndicator={false}
120
+ className="-mx-6 px-6"
121
+ >
122
+ <View className="flex-row gap-3">
123
+ {quickActions.map((action) => (
124
+ <Button
125
+ key={action.id}
126
+ variant="outline"
127
+ size="sm"
128
+ onPress={action.onPress}
129
+ >
130
+ {action.icon ? `${action.icon} ${action.label}` : action.label}
131
+ </Button>
132
+ ))}
133
+ </View>
134
+ </ScrollView>
135
+ </View>
136
+ )}
137
+
138
+ {/* Metrics Grid */}
139
+ <View className="gap-3">
140
+ <Text className="text-xs font-bold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">
141
+ Overview Performance
142
+ </Text>
143
+ <View className="gap-3">
144
+ {metrics.map((metric) => (
145
+ <StatsCard key={metric.id} {...metric} />
146
+ ))}
147
+ </View>
148
+ </View>
149
+
150
+ {/* Recent Activities */}
151
+ {recentActivities && recentActivities.length > 0 && (
152
+ <Card className="gap-2">
153
+ <CardHeader className="flex-row items-center justify-between pb-2">
154
+ <CardTitle className="text-base font-bold">Recent Activity</CardTitle>
155
+ {onViewAllActivities && (
156
+ <Pressable onPress={onViewAllActivities}>
157
+ <Text className="text-xs font-semibold text-primary-600 dark:text-primary-400">View all</Text>
158
+ </Pressable>
159
+ )}
160
+ </CardHeader>
161
+ <CardContent className="gap-3 pt-0">
162
+ {recentActivities.map((act) => (
163
+ <View
164
+ key={act.id}
165
+ className="flex-row items-center justify-between py-2 border-b border-neutral-200 dark:border-neutral-800 last:border-b-0"
166
+ >
167
+ <View className="flex-row items-center gap-3 flex-1 pr-2">
168
+ <Avatar size="sm">
169
+ {act.avatarUrl && <AvatarImage source={{ uri: act.avatarUrl }} />}
170
+ <AvatarFallback
171
+ text={act.avatarInitials || act.title.slice(0, 2).toUpperCase()}
172
+ />
173
+ </Avatar>
174
+ <View className="flex-1">
175
+ <Text className="text-sm font-semibold text-neutral-900 dark:text-neutral-100" numberOfLines={1}>
176
+ {act.title}
177
+ </Text>
178
+ <Text className="text-xs text-neutral-500 dark:text-neutral-400" numberOfLines={1}>
179
+ {act.subtitle} • {act.timestamp}
180
+ </Text>
181
+ </View>
182
+ </View>
183
+
184
+ <View className="items-end gap-1">
185
+ {act.amount && (
186
+ <Text className="text-sm font-bold text-neutral-900 dark:text-neutral-100">
187
+ {act.amount}
188
+ </Text>
189
+ )}
190
+ {act.status && (
191
+ <Badge variant={act.status.variant} size="sm">
192
+ {act.status.label}
193
+ </Badge>
194
+ )}
195
+ </View>
196
+ </View>
197
+ ))}
198
+ </CardContent>
199
+ </Card>
200
+ )}
201
+ </View>
202
+ </ScrollView>
203
+ );
204
+ };
@@ -0,0 +1 @@
1
+ export * from "./DashboardTemplate";
@@ -0,0 +1,3 @@
1
+ export * from "./auth";
2
+ export * from "./dashboard";
3
+ export * from "./settings";