@nexub/react-native 0.0.0-stage → 2.2.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 (134) hide show
  1. package/README.md +113 -2
  2. package/lib/module/components/actions.js +222 -0
  3. package/lib/module/components/actions.js.map +1 -0
  4. package/lib/module/components/containers.js +300 -0
  5. package/lib/module/components/containers.js.map +1 -0
  6. package/lib/module/components/display.js +299 -0
  7. package/lib/module/components/display.js.map +1 -0
  8. package/lib/module/components/feedback.js +484 -0
  9. package/lib/module/components/feedback.js.map +1 -0
  10. package/lib/module/components/inputs.js +792 -0
  11. package/lib/module/components/inputs.js.map +1 -0
  12. package/lib/module/components/mobile.js +818 -0
  13. package/lib/module/components/mobile.js.map +1 -0
  14. package/lib/module/components/overlays.js +421 -0
  15. package/lib/module/components/overlays.js.map +1 -0
  16. package/lib/module/components/shared.js +57 -0
  17. package/lib/module/components/shared.js.map +1 -0
  18. package/lib/module/hooks/useControllableState.js +23 -0
  19. package/lib/module/hooks/useControllableState.js.map +1 -0
  20. package/lib/module/index.js +31 -0
  21. package/lib/module/index.js.map +1 -0
  22. package/lib/module/package.json +1 -0
  23. package/lib/module/patterns/app.js +709 -0
  24. package/lib/module/patterns/app.js.map +1 -0
  25. package/lib/module/patterns/auth.js +224 -0
  26. package/lib/module/patterns/auth.js.map +1 -0
  27. package/lib/module/patterns/home.js +201 -0
  28. package/lib/module/patterns/home.js.map +1 -0
  29. package/lib/module/patterns/index.js +8 -0
  30. package/lib/module/patterns/index.js.map +1 -0
  31. package/lib/module/patterns/money.js +419 -0
  32. package/lib/module/patterns/money.js.map +1 -0
  33. package/lib/module/primitives/Icon.js +17 -0
  34. package/lib/module/primitives/Icon.js.map +1 -0
  35. package/lib/module/primitives/Layout.js +79 -0
  36. package/lib/module/primitives/Layout.js.map +1 -0
  37. package/lib/module/primitives/Pressable.js +79 -0
  38. package/lib/module/primitives/Pressable.js.map +1 -0
  39. package/lib/module/primitives/ScreenContainer.js +58 -0
  40. package/lib/module/primitives/ScreenContainer.js.map +1 -0
  41. package/lib/module/primitives/Surface.js +57 -0
  42. package/lib/module/primitives/Surface.js.map +1 -0
  43. package/lib/module/primitives/Text.js +70 -0
  44. package/lib/module/primitives/Text.js.map +1 -0
  45. package/lib/module/theme/ThemeProvider.js +127 -0
  46. package/lib/module/theme/ThemeProvider.js.map +1 -0
  47. package/lib/module/theme/fonts.js +56 -0
  48. package/lib/module/theme/fonts.js.map +1 -0
  49. package/lib/module/theme/haptics.js +56 -0
  50. package/lib/module/theme/haptics.js.map +1 -0
  51. package/lib/module/theme/motion.js +89 -0
  52. package/lib/module/theme/motion.js.map +1 -0
  53. package/lib/module/theme/navigation.js +99 -0
  54. package/lib/module/theme/navigation.js.map +1 -0
  55. package/lib/typescript/components/actions.d.ts +53 -0
  56. package/lib/typescript/components/actions.d.ts.map +1 -0
  57. package/lib/typescript/components/containers.d.ts +59 -0
  58. package/lib/typescript/components/containers.d.ts.map +1 -0
  59. package/lib/typescript/components/display.d.ts +68 -0
  60. package/lib/typescript/components/display.d.ts.map +1 -0
  61. package/lib/typescript/components/feedback.d.ts +96 -0
  62. package/lib/typescript/components/feedback.d.ts.map +1 -0
  63. package/lib/typescript/components/inputs.d.ts +141 -0
  64. package/lib/typescript/components/inputs.d.ts.map +1 -0
  65. package/lib/typescript/components/mobile.d.ts +173 -0
  66. package/lib/typescript/components/mobile.d.ts.map +1 -0
  67. package/lib/typescript/components/overlays.d.ts +74 -0
  68. package/lib/typescript/components/overlays.d.ts.map +1 -0
  69. package/lib/typescript/components/shared.d.ts +20 -0
  70. package/lib/typescript/components/shared.d.ts.map +1 -0
  71. package/lib/typescript/hooks/useControllableState.d.ts +6 -0
  72. package/lib/typescript/hooks/useControllableState.d.ts.map +1 -0
  73. package/lib/typescript/index.d.ts +39 -0
  74. package/lib/typescript/index.d.ts.map +1 -0
  75. package/lib/typescript/package.json +1 -0
  76. package/lib/typescript/patterns/app.d.ts +139 -0
  77. package/lib/typescript/patterns/app.d.ts.map +1 -0
  78. package/lib/typescript/patterns/auth.d.ts +54 -0
  79. package/lib/typescript/patterns/auth.d.ts.map +1 -0
  80. package/lib/typescript/patterns/home.d.ts +50 -0
  81. package/lib/typescript/patterns/home.d.ts.map +1 -0
  82. package/lib/typescript/patterns/index.d.ts +9 -0
  83. package/lib/typescript/patterns/index.d.ts.map +1 -0
  84. package/lib/typescript/patterns/money.d.ts +106 -0
  85. package/lib/typescript/patterns/money.d.ts.map +1 -0
  86. package/lib/typescript/primitives/Icon.d.ts +12 -0
  87. package/lib/typescript/primitives/Icon.d.ts.map +1 -0
  88. package/lib/typescript/primitives/Layout.d.ts +26 -0
  89. package/lib/typescript/primitives/Layout.d.ts.map +1 -0
  90. package/lib/typescript/primitives/Pressable.d.ts +21 -0
  91. package/lib/typescript/primitives/Pressable.d.ts.map +1 -0
  92. package/lib/typescript/primitives/ScreenContainer.d.ts +25 -0
  93. package/lib/typescript/primitives/ScreenContainer.d.ts.map +1 -0
  94. package/lib/typescript/primitives/Surface.d.ts +23 -0
  95. package/lib/typescript/primitives/Surface.d.ts.map +1 -0
  96. package/lib/typescript/primitives/Text.d.ts +21 -0
  97. package/lib/typescript/primitives/Text.d.ts.map +1 -0
  98. package/lib/typescript/theme/ThemeProvider.d.ts +69 -0
  99. package/lib/typescript/theme/ThemeProvider.d.ts.map +1 -0
  100. package/lib/typescript/theme/fonts.d.ts +25 -0
  101. package/lib/typescript/theme/fonts.d.ts.map +1 -0
  102. package/lib/typescript/theme/haptics.d.ts +3 -0
  103. package/lib/typescript/theme/haptics.d.ts.map +1 -0
  104. package/lib/typescript/theme/motion.d.ts +79 -0
  105. package/lib/typescript/theme/motion.d.ts.map +1 -0
  106. package/lib/typescript/theme/navigation.d.ts +158 -0
  107. package/lib/typescript/theme/navigation.d.ts.map +1 -0
  108. package/package.json +138 -3
  109. package/src/components/actions.tsx +259 -0
  110. package/src/components/containers.tsx +352 -0
  111. package/src/components/display.tsx +345 -0
  112. package/src/components/feedback.tsx +541 -0
  113. package/src/components/inputs.tsx +899 -0
  114. package/src/components/mobile.tsx +883 -0
  115. package/src/components/overlays.tsx +488 -0
  116. package/src/components/shared.ts +54 -0
  117. package/src/hooks/useControllableState.ts +27 -0
  118. package/src/index.ts +177 -0
  119. package/src/patterns/app.tsx +715 -0
  120. package/src/patterns/auth.tsx +266 -0
  121. package/src/patterns/home.tsx +211 -0
  122. package/src/patterns/index.ts +59 -0
  123. package/src/patterns/money.tsx +458 -0
  124. package/src/primitives/Icon.tsx +15 -0
  125. package/src/primitives/Layout.tsx +71 -0
  126. package/src/primitives/Pressable.tsx +97 -0
  127. package/src/primitives/ScreenContainer.tsx +65 -0
  128. package/src/primitives/Surface.tsx +75 -0
  129. package/src/primitives/Text.tsx +79 -0
  130. package/src/theme/ThemeProvider.tsx +210 -0
  131. package/src/theme/fonts.ts +52 -0
  132. package/src/theme/haptics.ts +57 -0
  133. package/src/theme/motion.ts +105 -0
  134. package/src/theme/navigation.ts +68 -0
@@ -0,0 +1,266 @@
1
+ import * as React from 'react';
2
+ import { View } from 'react-native';
3
+ import { Lock, ShieldCheck } from '@nexub/icons/native';
4
+ import { Button } from '../components/actions';
5
+ import { PasswordInput, TextField } from '../components/inputs';
6
+ import { Banner } from '../components/feedback';
7
+ import { OTPInput, StickyFooter } from '../components/mobile';
8
+ import { ScreenContainer } from '../primitives/ScreenContainer';
9
+ import { Text, Heading } from '../primitives/Text';
10
+ import { useTheme } from '../theme/ThemeProvider';
11
+
12
+ export interface AuthScreenProps {
13
+ /** Brand mark above the title. */
14
+ logo?: React.ReactNode;
15
+ title: string;
16
+ description?: string;
17
+ children: React.ReactNode;
18
+ /** Primary action(s), kept above the keyboard. */
19
+ footer: React.ReactNode;
20
+ /** Error from the last attempt (announced). */
21
+ error?: string;
22
+ }
23
+
24
+ /** Frame for sign-in, sign-up and verification screens (web AuthCard, full screen). */
25
+ export function AuthScreen({ logo, title, description, children, footer, error }: AuthScreenProps) {
26
+ const { tokens } = useTheme();
27
+ return (
28
+ <StickyFooter footer={footer}>
29
+ <ScreenContainer
30
+ edges={['top', 'left', 'right']}
31
+ gap="6"
32
+ contentStyle={{ paddingTop: tokens.spacing[10] }}
33
+ >
34
+ <View style={{ gap: tokens.spacing[2] }}>
35
+ {logo}
36
+ <Heading variant="headline-md">{title}</Heading>
37
+ {description && (
38
+ <Text variant="body-md" color="on-surface-variant">
39
+ {description}
40
+ </Text>
41
+ )}
42
+ </View>
43
+ {error && <Banner tone="danger">{error}</Banner>}
44
+ <View style={{ gap: tokens.spacing[4] }}>{children}</View>
45
+ </ScreenContainer>
46
+ </StickyFooter>
47
+ );
48
+ }
49
+
50
+ export interface SignInScreenProps {
51
+ onSubmit: (email: string, password: string) => void;
52
+ /** Offer passkey/biometric sign-in (name the method: "Sign in with Face ID"). */
53
+ biometricLabel?: string;
54
+ onBiometric?: () => void;
55
+ onForgotPassword?: () => void;
56
+ onCreateAccount?: () => void;
57
+ loading?: boolean;
58
+ error?: string;
59
+ logo?: React.ReactNode;
60
+ /** Copy overrides. */
61
+ title?: string;
62
+ submitLabel?: string;
63
+ }
64
+
65
+ /** Email + password sign-in with platform autofill, plus an optional biometric/passkey path. */
66
+ export function SignInScreen({
67
+ onSubmit,
68
+ biometricLabel,
69
+ onBiometric,
70
+ onForgotPassword,
71
+ onCreateAccount,
72
+ loading,
73
+ error,
74
+ logo,
75
+ title = 'Sign in',
76
+ submitLabel = 'Sign in',
77
+ }: SignInScreenProps) {
78
+ const [email, setEmail] = React.useState('');
79
+ const [password, setPassword] = React.useState('');
80
+ return (
81
+ <AuthScreen
82
+ logo={logo}
83
+ title={title}
84
+ error={error}
85
+ footer={
86
+ <>
87
+ <Button
88
+ size="lg"
89
+ fullWidth
90
+ loading={loading}
91
+ disabled={!email || !password}
92
+ onPress={() => onSubmit(email, password)}
93
+ >
94
+ {submitLabel}
95
+ </Button>
96
+ {onBiometric && biometricLabel && (
97
+ <Button
98
+ size="lg"
99
+ fullWidth
100
+ variant="tonal"
101
+ leadingIcon={ShieldCheck}
102
+ onPress={onBiometric}
103
+ >
104
+ {biometricLabel}
105
+ </Button>
106
+ )}
107
+ {onCreateAccount && (
108
+ <Button size="lg" fullWidth variant="text" onPress={onCreateAccount}>
109
+ Create an account
110
+ </Button>
111
+ )}
112
+ </>
113
+ }
114
+ >
115
+ <TextField
116
+ label="Email"
117
+ value={email}
118
+ onChangeText={setEmail}
119
+ keyboardType="email-address"
120
+ autoCapitalize="none"
121
+ autoCorrect={false}
122
+ autoComplete="email"
123
+ textContentType="username"
124
+ returnKeyType="next"
125
+ />
126
+ <PasswordInput
127
+ label="Password"
128
+ value={password}
129
+ onChangeText={setPassword}
130
+ returnKeyType="go"
131
+ onSubmitEditing={() => email && password && onSubmit(email, password)}
132
+ />
133
+ {onForgotPassword && (
134
+ <Button
135
+ variant="text"
136
+ size="sm"
137
+ onPress={onForgotPassword}
138
+ style={{ alignSelf: 'flex-end' }}
139
+ >
140
+ Forgot password?
141
+ </Button>
142
+ )}
143
+ </AuthScreen>
144
+ );
145
+ }
146
+
147
+ export interface VerifyCodeScreenProps {
148
+ /** Where the code went, e.g. "+65 •••• 4821". */
149
+ destination: string;
150
+ onComplete: (code: string) => void;
151
+ onResend: () => void;
152
+ /** Seconds before Resend is allowed again. @default 30 */
153
+ resendAfter?: number;
154
+ length?: number;
155
+ error?: string;
156
+ }
157
+
158
+ /** One-time code entry (SMS autofill ready) with a resend countdown. */
159
+ export function VerifyCodeScreen({
160
+ destination,
161
+ onComplete,
162
+ onResend,
163
+ resendAfter = 30,
164
+ length = 6,
165
+ error,
166
+ }: VerifyCodeScreenProps) {
167
+ const [left, setLeft] = React.useState(resendAfter);
168
+ React.useEffect(() => {
169
+ if (left <= 0) return;
170
+ const t = setTimeout(() => setLeft(left - 1), 1000);
171
+ return () => clearTimeout(t);
172
+ }, [left]);
173
+ return (
174
+ <AuthScreen
175
+ title="Enter the code"
176
+ description={`We sent a ${length}-digit code to ${destination}.`}
177
+ footer={
178
+ <Button
179
+ size="lg"
180
+ fullWidth
181
+ variant="text"
182
+ disabled={left > 0}
183
+ onPress={() => {
184
+ setLeft(resendAfter);
185
+ onResend();
186
+ }}
187
+ >
188
+ {left > 0 ? `Resend code in ${left}s` : 'Resend code'}
189
+ </Button>
190
+ }
191
+ >
192
+ <OTPInput
193
+ label="Verification code"
194
+ length={length}
195
+ onComplete={onComplete}
196
+ error={error}
197
+ autoFocus
198
+ />
199
+ </AuthScreen>
200
+ );
201
+ }
202
+
203
+ export interface LockScreenProps {
204
+ /** "Unlock with Face ID" / "Unlock with fingerprint" (name the method). */
205
+ unlockLabel: string;
206
+ onUnlock: () => void;
207
+ /** Fallback to the app passcode or password. */
208
+ onUsePasscode?: () => void;
209
+ logo?: React.ReactNode;
210
+ appName?: string;
211
+ }
212
+
213
+ /** Biometric unlock on app open/resume, with a passcode fallback. */
214
+ export function LockScreen({
215
+ unlockLabel,
216
+ onUnlock,
217
+ onUsePasscode,
218
+ logo,
219
+ appName,
220
+ }: LockScreenProps) {
221
+ const { roles, tokens } = useTheme();
222
+ return (
223
+ <StickyFooter
224
+ footer={
225
+ <>
226
+ <Button size="lg" fullWidth leadingIcon={ShieldCheck} onPress={onUnlock}>
227
+ {unlockLabel}
228
+ </Button>
229
+ {onUsePasscode && (
230
+ <Button size="lg" fullWidth variant="text" onPress={onUsePasscode}>
231
+ Use passcode
232
+ </Button>
233
+ )}
234
+ </>
235
+ }
236
+ >
237
+ <View
238
+ style={{
239
+ flex: 1,
240
+ alignItems: 'center',
241
+ justifyContent: 'center',
242
+ gap: tokens.spacing[4],
243
+ padding: tokens.spacing[6],
244
+ }}
245
+ >
246
+ {logo ?? (
247
+ <View
248
+ style={{
249
+ width: 72,
250
+ height: 72,
251
+ borderRadius: 36,
252
+ alignItems: 'center',
253
+ justifyContent: 'center',
254
+ backgroundColor: roles['primary-container'],
255
+ }}
256
+ >
257
+ <Lock size="xl" color={roles['on-primary-container']} />
258
+ </View>
259
+ )}
260
+ <Heading variant="headline-sm" style={{ textAlign: 'center' }}>
261
+ {appName ? `${appName} is locked` : 'Locked'}
262
+ </Heading>
263
+ </View>
264
+ </StickyFooter>
265
+ );
266
+ }
@@ -0,0 +1,211 @@
1
+ import * as React from 'react';
2
+ import { View } from 'react-native';
3
+ import { moneyFormatter } from '@nexub/core';
4
+ import type { NativeIconProps } from '@nexub/icons/native';
5
+ import { Button } from '../components/actions';
6
+ import { Card, ListItem, ListSection } from '../components/containers';
7
+ import { Amount, Avatar, Badge } from '../components/display';
8
+ import { RefreshControl } from '../components/mobile';
9
+ import { Pressable } from '../primitives/Pressable';
10
+ import { ScreenContainer } from '../primitives/ScreenContainer';
11
+ import { Heading, Text } from '../primitives/Text';
12
+ import { useTheme } from '../theme/ThemeProvider';
13
+ import type { Transaction } from './money';
14
+
15
+ export interface QuickAction {
16
+ id: string;
17
+ label: string;
18
+ icon?: React.ComponentType<NativeIconProps>;
19
+ onPress: () => void;
20
+ }
21
+
22
+ export interface Insight {
23
+ id: string;
24
+ title: string;
25
+ body: string;
26
+ action?: { label: string; onPress: () => void };
27
+ }
28
+
29
+ export interface HomeScreenProps {
30
+ /** e.g. "Good morning, Mei". Leave out when the native header shows the title. */
31
+ greeting?: string;
32
+ balance: { value: number; currency: string; label?: string };
33
+ /** Send, Add money, Request… Up to four fit one row. */
34
+ quickActions?: QuickAction[];
35
+ /** The latest few transactions. */
36
+ recent?: Transaction[];
37
+ onTransactionPress?: (t: Transaction) => void;
38
+ /** Shows "See all" beside Recent activity. */
39
+ onSeeAllActivity?: () => void;
40
+ insights?: Insight[];
41
+ onRefresh?: () => void;
42
+ refreshing?: boolean;
43
+ /** Copy overrides. */
44
+ labels?: Partial<{ totalBalance: string; recentActivity: string; seeAll: string; insights: string }>;
45
+ }
46
+
47
+ /**
48
+ * Money-app home (Payub): balance hero with quick actions, recent activity,
49
+ * insights and pull-to-refresh. Web twin: `DashboardPage` in @nexub/patterns.
50
+ */
51
+ export function HomeScreen({
52
+ greeting,
53
+ balance,
54
+ quickActions = [],
55
+ recent = [],
56
+ onTransactionPress,
57
+ onSeeAllActivity,
58
+ insights = [],
59
+ onRefresh,
60
+ refreshing = false,
61
+ labels,
62
+ }: HomeScreenProps) {
63
+ const { roles, tokens, locale } = useTheme();
64
+ const l = {
65
+ totalBalance: 'Total balance',
66
+ recentActivity: 'Recent activity',
67
+ seeAll: 'See all',
68
+ insights: 'Insights',
69
+ ...labels,
70
+ };
71
+ const balanceLabel = balance.label ?? l.totalBalance;
72
+
73
+ return (
74
+ <ScreenContainer
75
+ gap="6"
76
+ refreshControl={
77
+ onRefresh ? <RefreshControl refreshing={refreshing} onRefresh={onRefresh} /> : undefined
78
+ }
79
+ >
80
+ {greeting && <Heading variant="headline-sm">{greeting}</Heading>}
81
+
82
+ <Card tone="surface-container" style={{ padding: tokens.spacing[5], gap: tokens.spacing[5] }}>
83
+ <View
84
+ accessible
85
+ accessibilityLabel={`${balanceLabel}, ${moneyFormatter(balance.currency, locale).format(balance.value)}`}
86
+ style={{ gap: tokens.spacing[1] }}
87
+ >
88
+ <Text variant="label-lg" color="on-surface-variant">
89
+ {balanceLabel}
90
+ </Text>
91
+ <Amount value={balance.value} currency={balance.currency} locale={locale} size="display" />
92
+ </View>
93
+ {quickActions.length > 0 && (
94
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', gap: tokens.spacing[2] }}>
95
+ {quickActions.slice(0, 4).map((a) => {
96
+ const Icon = a.icon;
97
+ return (
98
+ <Pressable
99
+ key={a.id}
100
+ accessibilityRole="button"
101
+ accessibilityLabel={a.label}
102
+ haptic="selection"
103
+ onPress={a.onPress}
104
+ style={{
105
+ flex: 1,
106
+ alignItems: 'center',
107
+ gap: tokens.spacing[2],
108
+ paddingVertical: tokens.spacing[2],
109
+ borderRadius: tokens.shape.container,
110
+ }}
111
+ >
112
+ <View
113
+ style={{
114
+ width: 48,
115
+ height: 48,
116
+ borderRadius: 24,
117
+ alignItems: 'center',
118
+ justifyContent: 'center',
119
+ backgroundColor: roles['primary-container'],
120
+ }}
121
+ >
122
+ {Icon && <Icon size="md" color={roles['on-primary-container']} />}
123
+ </View>
124
+ <Text variant="label-md" numberOfLines={1}>
125
+ {a.label}
126
+ </Text>
127
+ </Pressable>
128
+ );
129
+ })}
130
+ </View>
131
+ )}
132
+ </Card>
133
+
134
+ {recent.length > 0 && (
135
+ <View style={{ gap: tokens.spacing[2] }}>
136
+ <View
137
+ style={{
138
+ flexDirection: 'row',
139
+ alignItems: 'center',
140
+ justifyContent: 'space-between',
141
+ paddingStart: tokens.spacing[4],
142
+ }}
143
+ >
144
+ <Text variant="title-sm" color="on-surface-variant" accessibilityRole="header">
145
+ {l.recentActivity}
146
+ </Text>
147
+ {onSeeAllActivity && (
148
+ <Button variant="text" size="sm" onPress={onSeeAllActivity}>
149
+ {l.seeAll}
150
+ </Button>
151
+ )}
152
+ </View>
153
+ <ListSection inset={68}>
154
+ {recent.map((t) => (
155
+ <ListItem
156
+ key={t.id}
157
+ title={t.title}
158
+ subtitle={t.subtitle}
159
+ leading={<Avatar name={t.title} src={t.image} />}
160
+ trailing={
161
+ <View style={{ alignItems: 'flex-end', gap: 2 }}>
162
+ <Amount
163
+ value={t.amount}
164
+ currency={t.currency}
165
+ locale={locale}
166
+ tone="auto"
167
+ signDisplay="exceptZero"
168
+ />
169
+ {t.status && (
170
+ <Badge tone={t.status === 'failed' ? 'danger' : 'warning'} size="sm">
171
+ {t.status === 'failed' ? 'Failed' : 'Pending'}
172
+ </Badge>
173
+ )}
174
+ </View>
175
+ }
176
+ variant={onTransactionPress ? 'navigation' : 'static'}
177
+ onPress={onTransactionPress && (() => onTransactionPress(t))}
178
+ />
179
+ ))}
180
+ </ListSection>
181
+ </View>
182
+ )}
183
+
184
+ {insights.length > 0 && (
185
+ <View style={{ gap: tokens.spacing[3] }}>
186
+ <Text
187
+ variant="title-sm"
188
+ color="on-surface-variant"
189
+ accessibilityRole="header"
190
+ style={{ paddingHorizontal: tokens.spacing[4] }}
191
+ >
192
+ {l.insights}
193
+ </Text>
194
+ {insights.map((i) => (
195
+ <Card key={i.id}>
196
+ <Text variant="title-sm">{i.title}</Text>
197
+ <Text variant="body-sm" color="on-surface-variant">
198
+ {i.body}
199
+ </Text>
200
+ {i.action && (
201
+ <Button variant="text" size="sm" onPress={i.action.onPress} style={{ alignSelf: 'flex-start' }}>
202
+ {i.action.label}
203
+ </Button>
204
+ )}
205
+ </Card>
206
+ ))}
207
+ </View>
208
+ )}
209
+ </ScreenContainer>
210
+ );
211
+ }
@@ -0,0 +1,59 @@
1
+ // @nexub/react-native/patterns — screen templates built only from public components.
2
+ export { HomeScreen } from './home';
3
+ export type { HomeScreenProps, QuickAction, Insight } from './home';
4
+ export { AuthScreen, SignInScreen, VerifyCodeScreen, LockScreen } from './auth';
5
+ export type {
6
+ AuthScreenProps,
7
+ SignInScreenProps,
8
+ VerifyCodeScreenProps,
9
+ LockScreenProps,
10
+ } from './auth';
11
+ export {
12
+ FlowStep,
13
+ AmountStep,
14
+ ReviewStep,
15
+ ActivityList,
16
+ TransactionDetail,
17
+ ResultScreen,
18
+ } from './money';
19
+ export type {
20
+ FlowStepProps,
21
+ Quote,
22
+ AmountStepProps,
23
+ ReviewRow,
24
+ ReviewStepProps,
25
+ Transaction,
26
+ ActivityListProps,
27
+ TransactionDetailProps,
28
+ ResultScreenProps,
29
+ } from './money';
30
+ export {
31
+ OnboardingScreen,
32
+ PermissionPrimer,
33
+ SettingsScreen,
34
+ AppearanceSection,
35
+ ProfileHeader,
36
+ NotificationsInbox,
37
+ SearchScreen,
38
+ ChatScreen,
39
+ PaywallScreen,
40
+ AppSwitcherSheet,
41
+ } from './app';
42
+ export type {
43
+ OnboardingSlide,
44
+ OnboardingScreenProps,
45
+ PermissionPrimerProps,
46
+ SettingsGroup,
47
+ SettingsScreenProps,
48
+ AppearanceSectionProps,
49
+ ProfileHeaderProps,
50
+ NotificationEntry,
51
+ NotificationsInboxProps,
52
+ SearchScreenProps,
53
+ ChatMessage,
54
+ ChatScreenProps,
55
+ Plan,
56
+ PaywallScreenProps,
57
+ NexubApp,
58
+ AppSwitcherSheetProps,
59
+ } from './app';