@retray-dev/ui-kit 5.1.0 → 5.4.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/COMPONENTS.md +257 -17
- package/EXAMPLES.md +666 -0
- package/README.md +12 -9
- package/dist/index.d.mts +117 -12
- package/dist/index.d.ts +117 -12
- package/dist/index.js +589 -364
- package/dist/index.mjs +527 -305
- package/package.json +3 -2
- package/src/components/Accordion/Accordion.tsx +25 -2
- package/src/components/Avatar/Avatar.tsx +21 -7
- package/src/components/Button/Button.tsx +16 -7
- package/src/components/ButtonGroup/ButtonGroup.tsx +60 -0
- package/src/components/ButtonGroup/index.ts +1 -0
- package/src/components/Chip/Chip.tsx +8 -1
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +7 -7
- package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +25 -3
- package/src/components/DetailRow/DetailRow.tsx +140 -0
- package/src/components/DetailRow/index.ts +1 -0
- package/src/components/LabelValue/LabelValue.tsx +25 -4
- package/src/components/MonthPicker/MonthPicker.tsx +18 -6
- package/src/components/Sheet/Sheet.tsx +52 -16
- package/src/components/Textarea/Textarea.tsx +66 -29
- package/src/index.ts +5 -0
- package/src/tokens.ts +1 -1
- package/src/utils/typography.ts +24 -0
package/EXAMPLES.md
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
# @retray-dev/ui-kit — Composition Examples
|
|
2
|
+
|
|
3
|
+
This file contains full-code examples of complete app screens built by composing multiple UI kit components. These are working examples from the `example/` app, demonstrating real-world component patterns, state management, and navigation.
|
|
4
|
+
|
|
5
|
+
**For AI agents:** Use these examples as reference patterns when building screens with this UI kit. They show proper imports, state management, navigation, haptics integration, and component composition.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Example 1: Finance Dashboard
|
|
10
|
+
|
|
11
|
+
**Use case:** Personal finance app home screen with budget tracking, transaction list, and expense breakdown.
|
|
12
|
+
|
|
13
|
+
**Components used:** MonthPicker, CurrencyDisplay, Progress, CategoryStrip, ListItem, DetailRow, Card, Button, Text, Icon
|
|
14
|
+
|
|
15
|
+
**Features:**
|
|
16
|
+
- Month selection with locale support
|
|
17
|
+
- Hero balance display with auto-scaling
|
|
18
|
+
- Budget progress tracking with visual indicator
|
|
19
|
+
- Category filtering with horizontal scrollable tabs
|
|
20
|
+
- Transaction list with custom icons and color-coded amounts
|
|
21
|
+
- Expense breakdown with separator hierarchy
|
|
22
|
+
- Export action with success toast
|
|
23
|
+
|
|
24
|
+
### State Setup
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
const [financeMonth, setFinanceMonth] = useState({ month: 5, year: 2026 })
|
|
28
|
+
const [financeCategory, setFinanceCategory] = useState('all')
|
|
29
|
+
|
|
30
|
+
const FINANCE_CATEGORIES = [
|
|
31
|
+
{ label: 'Todo', value: 'all', icon: 'layers' },
|
|
32
|
+
{ label: 'Comida', value: 'food', icon: 'coffee' },
|
|
33
|
+
{ label: 'Transporte', value: 'transport', icon: 'truck' },
|
|
34
|
+
{ label: 'Salud', value: 'health', icon: 'heart' },
|
|
35
|
+
]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Implementation
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { View, ScrollView, StyleSheet } from 'react-native'
|
|
42
|
+
import {
|
|
43
|
+
Card,
|
|
44
|
+
CardContent,
|
|
45
|
+
MonthPicker,
|
|
46
|
+
CurrencyDisplay,
|
|
47
|
+
Progress,
|
|
48
|
+
CategoryStrip,
|
|
49
|
+
ListItem,
|
|
50
|
+
DetailRow,
|
|
51
|
+
Separator,
|
|
52
|
+
Button,
|
|
53
|
+
Text,
|
|
54
|
+
useTheme,
|
|
55
|
+
useToast,
|
|
56
|
+
} from '@retray-dev/ui-kit'
|
|
57
|
+
|
|
58
|
+
function FinanceDashboard() {
|
|
59
|
+
const { colors } = useTheme()
|
|
60
|
+
const { toast } = useToast()
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<View style={[styles.fullScreen, { backgroundColor: colors.background }]}>
|
|
64
|
+
{/* Header */}
|
|
65
|
+
<View style={[styles.screenHeader, { backgroundColor: colors.card, borderBottomColor: colors.border }]}>
|
|
66
|
+
<Button
|
|
67
|
+
label="Volver"
|
|
68
|
+
variant="text"
|
|
69
|
+
size="sm"
|
|
70
|
+
iconName="arrow-left"
|
|
71
|
+
onPress={() => {/* navigate back */}}
|
|
72
|
+
/>
|
|
73
|
+
<Text variant="title-sm">Resumen financiero</Text>
|
|
74
|
+
<View style={{ width: 70 }} />
|
|
75
|
+
</View>
|
|
76
|
+
|
|
77
|
+
{/* Content */}
|
|
78
|
+
<ScrollView contentContainerStyle={styles.screenContent}>
|
|
79
|
+
{/* Month Selector */}
|
|
80
|
+
<MonthPicker value={financeMonth} onChange={setFinanceMonth} locale="es" />
|
|
81
|
+
<View style={{ height: 16 }} />
|
|
82
|
+
|
|
83
|
+
{/* Balance Card */}
|
|
84
|
+
<Card>
|
|
85
|
+
<CardContent>
|
|
86
|
+
<Text variant="caption" color={colors.foregroundMuted}>Gastos del mes</Text>
|
|
87
|
+
<CurrencyDisplay value={1250000} variant="hero" autoScale />
|
|
88
|
+
<View style={{ height: 8 }} />
|
|
89
|
+
<Progress value={78} variant="destructive" />
|
|
90
|
+
<Text variant="caption-sm" color={colors.foregroundMuted}>
|
|
91
|
+
78% del presupuesto · $1.600.000
|
|
92
|
+
</Text>
|
|
93
|
+
</CardContent>
|
|
94
|
+
</Card>
|
|
95
|
+
<View style={{ height: 12 }} />
|
|
96
|
+
|
|
97
|
+
{/* Category Filter */}
|
|
98
|
+
<CategoryStrip
|
|
99
|
+
categories={FINANCE_CATEGORIES}
|
|
100
|
+
value={financeCategory}
|
|
101
|
+
onValueChange={(v) => setFinanceCategory(Array.isArray(v) ? v[0] ?? 'all' : v)}
|
|
102
|
+
/>
|
|
103
|
+
<View style={{ height: 16 }} />
|
|
104
|
+
|
|
105
|
+
{/* Transaction List */}
|
|
106
|
+
<Text variant="caption" style={{ marginBottom: 8 }}>Movimientos recientes</Text>
|
|
107
|
+
<ListItem
|
|
108
|
+
title="Rappi"
|
|
109
|
+
subtitle="Comida · 12 may"
|
|
110
|
+
leftRender={
|
|
111
|
+
<View style={[styles.txIcon, { backgroundColor: '#fff3f0' }]}>
|
|
112
|
+
<Text>🛵</Text>
|
|
113
|
+
</View>
|
|
114
|
+
}
|
|
115
|
+
rightRender={<Text variant="title-sm" color={colors.destructive}>−$45.000</Text>}
|
|
116
|
+
showSeparator
|
|
117
|
+
onPress={() => {}}
|
|
118
|
+
/>
|
|
119
|
+
<ListItem
|
|
120
|
+
title="Metro"
|
|
121
|
+
subtitle="Transporte · 11 may"
|
|
122
|
+
leftRender={
|
|
123
|
+
<View style={[styles.txIcon, { backgroundColor: '#f0f4ff' }]}>
|
|
124
|
+
<Text>🚇</Text>
|
|
125
|
+
</View>
|
|
126
|
+
}
|
|
127
|
+
rightRender={<Text variant="title-sm" color={colors.destructive}>−$5.200</Text>}
|
|
128
|
+
showSeparator
|
|
129
|
+
onPress={() => {}}
|
|
130
|
+
/>
|
|
131
|
+
<ListItem
|
|
132
|
+
title="Nómina"
|
|
133
|
+
subtitle="Ingreso · 1 may"
|
|
134
|
+
leftRender={
|
|
135
|
+
<View style={[styles.txIcon, { backgroundColor: '#f0fdf4' }]}>
|
|
136
|
+
<Text>💼</Text>
|
|
137
|
+
</View>
|
|
138
|
+
}
|
|
139
|
+
rightRender={<Text variant="title-sm" color={colors.success}>+$3.500.000</Text>}
|
|
140
|
+
onPress={() => {}}
|
|
141
|
+
/>
|
|
142
|
+
<View style={{ height: 16 }} />
|
|
143
|
+
|
|
144
|
+
{/* Expense Breakdown */}
|
|
145
|
+
<Text variant="caption" style={{ marginBottom: 8 }}>Desglose</Text>
|
|
146
|
+
<View style={{ gap: 8 }}>
|
|
147
|
+
<DetailRow label="Comida & bebida" value="$380.000" leftIconName="coffee" />
|
|
148
|
+
<DetailRow label="Transporte" value="$95.000" leftIconName="truck" />
|
|
149
|
+
<DetailRow label="Entretenimiento" value="$210.000" leftIconName="music" />
|
|
150
|
+
<DetailRow label="Salud" value="$85.000" leftIconName="heart" />
|
|
151
|
+
<DetailRow label="Otros" value="$480.000" leftIconName="more-horizontal" />
|
|
152
|
+
<Separator />
|
|
153
|
+
<DetailRow label="Total" value="$1.250.000" labelWeight="bold" separator="none" rightIconName="trending-up" />
|
|
154
|
+
</View>
|
|
155
|
+
<View style={{ height: 24 }} />
|
|
156
|
+
|
|
157
|
+
{/* Export Action */}
|
|
158
|
+
<Button
|
|
159
|
+
label="Exportar reporte"
|
|
160
|
+
fullWidth
|
|
161
|
+
iconName="download"
|
|
162
|
+
onPress={() => {
|
|
163
|
+
toast({ title: 'Reporte exportado', variant: 'success' })
|
|
164
|
+
}}
|
|
165
|
+
/>
|
|
166
|
+
<View style={{ height: 32 }} />
|
|
167
|
+
</ScrollView>
|
|
168
|
+
</View>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const styles = StyleSheet.create({
|
|
173
|
+
fullScreen: {
|
|
174
|
+
flex: 1,
|
|
175
|
+
},
|
|
176
|
+
screenHeader: {
|
|
177
|
+
flexDirection: 'row',
|
|
178
|
+
alignItems: 'center',
|
|
179
|
+
justifyContent: 'space-between',
|
|
180
|
+
paddingHorizontal: 8,
|
|
181
|
+
paddingVertical: 8,
|
|
182
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
183
|
+
},
|
|
184
|
+
screenContent: {
|
|
185
|
+
padding: 16,
|
|
186
|
+
paddingBottom: 48,
|
|
187
|
+
},
|
|
188
|
+
txIcon: {
|
|
189
|
+
width: 40,
|
|
190
|
+
height: 40,
|
|
191
|
+
borderRadius: 12,
|
|
192
|
+
alignItems: 'center',
|
|
193
|
+
justifyContent: 'center',
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Example 2: Edit Profile
|
|
201
|
+
|
|
202
|
+
**Use case:** User profile settings editor with avatar, form fields, toggles, and verification status.
|
|
203
|
+
|
|
204
|
+
**Components used:** Avatar, Badge, Input, Select, Switch, Card, AlertBanner, Button, Text, Separator
|
|
205
|
+
|
|
206
|
+
**Features:**
|
|
207
|
+
- Avatar with online status indicator
|
|
208
|
+
- Membership tier badge
|
|
209
|
+
- Form fields with prefix icons and proper keyboard types
|
|
210
|
+
- Country selection dropdown
|
|
211
|
+
- Toggle switches for preferences
|
|
212
|
+
- Success banner for verification status
|
|
213
|
+
- Save/cancel navigation with toast feedback
|
|
214
|
+
|
|
215
|
+
### State Setup
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
const [profileName, setProfileName] = useState('Julian Cruz')
|
|
219
|
+
const [profileEmail, setProfileEmail] = useState('julian@email.com')
|
|
220
|
+
const [switchOn, setSwitchOn] = useState(false)
|
|
221
|
+
const [darkMode, setDarkMode] = useState(false)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Implementation
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { View, ScrollView, StyleSheet } from 'react-native'
|
|
228
|
+
import {
|
|
229
|
+
Avatar,
|
|
230
|
+
Badge,
|
|
231
|
+
Input,
|
|
232
|
+
Select,
|
|
233
|
+
Switch,
|
|
234
|
+
Card,
|
|
235
|
+
CardContent,
|
|
236
|
+
Separator,
|
|
237
|
+
AlertBanner,
|
|
238
|
+
Button,
|
|
239
|
+
Text,
|
|
240
|
+
useTheme,
|
|
241
|
+
useToast,
|
|
242
|
+
} from '@retray-dev/ui-kit'
|
|
243
|
+
|
|
244
|
+
function EditProfile() {
|
|
245
|
+
const { colors } = useTheme()
|
|
246
|
+
const { toast } = useToast()
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<View style={[styles.fullScreen, { backgroundColor: colors.background }]}>
|
|
250
|
+
{/* Header */}
|
|
251
|
+
<View style={[styles.screenHeader, { backgroundColor: colors.card, borderBottomColor: colors.border }]}>
|
|
252
|
+
<Button
|
|
253
|
+
label="Cancelar"
|
|
254
|
+
variant="text"
|
|
255
|
+
size="sm"
|
|
256
|
+
onPress={() => {/* navigate back */}}
|
|
257
|
+
/>
|
|
258
|
+
<Text variant="title-sm">Editar perfil</Text>
|
|
259
|
+
<Button
|
|
260
|
+
label="Guardar"
|
|
261
|
+
variant="text"
|
|
262
|
+
size="sm"
|
|
263
|
+
onPress={() => {
|
|
264
|
+
toast({ title: 'Perfil actualizado', variant: 'success' })
|
|
265
|
+
}}
|
|
266
|
+
/>
|
|
267
|
+
</View>
|
|
268
|
+
|
|
269
|
+
{/* Content */}
|
|
270
|
+
<ScrollView contentContainerStyle={styles.screenContent} keyboardShouldPersistTaps="handled">
|
|
271
|
+
{/* Avatar Section */}
|
|
272
|
+
<View style={{ alignItems: 'center', marginBottom: 24 }}>
|
|
273
|
+
<Avatar fallbackText={profileName || 'Usuario'} size={80} status="online" />
|
|
274
|
+
<View style={{ height: 12 }} />
|
|
275
|
+
<Badge label="Pro Member" variant="success" iconName="check-circle" />
|
|
276
|
+
</View>
|
|
277
|
+
|
|
278
|
+
{/* Form Fields */}
|
|
279
|
+
<Input
|
|
280
|
+
label="Nombre completo"
|
|
281
|
+
value={profileName}
|
|
282
|
+
onChangeText={setProfileName}
|
|
283
|
+
prefixIcon="user"
|
|
284
|
+
placeholder="Tu nombre"
|
|
285
|
+
/>
|
|
286
|
+
<View style={{ height: 12 }} />
|
|
287
|
+
<Input
|
|
288
|
+
label="Email"
|
|
289
|
+
value={profileEmail}
|
|
290
|
+
onChangeText={setProfileEmail}
|
|
291
|
+
prefixIcon="mail"
|
|
292
|
+
placeholder="tu@email.com"
|
|
293
|
+
keyboardType="email-address"
|
|
294
|
+
autoCapitalize="none"
|
|
295
|
+
/>
|
|
296
|
+
<View style={{ height: 12 }} />
|
|
297
|
+
<Select
|
|
298
|
+
label="País"
|
|
299
|
+
value="CO"
|
|
300
|
+
onValueChange={() => {}}
|
|
301
|
+
options={[
|
|
302
|
+
{ label: 'Colombia', value: 'CO' },
|
|
303
|
+
{ label: 'México', value: 'MX' },
|
|
304
|
+
{ label: 'Argentina', value: 'AR' },
|
|
305
|
+
]}
|
|
306
|
+
/>
|
|
307
|
+
<View style={{ height: 20 }} />
|
|
308
|
+
|
|
309
|
+
{/* Preferences Card */}
|
|
310
|
+
<Text variant="caption" style={{ marginBottom: 8 }}>Preferencias</Text>
|
|
311
|
+
<Card>
|
|
312
|
+
<CardContent>
|
|
313
|
+
<View style={{ gap: 12 }}>
|
|
314
|
+
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
315
|
+
<Text variant="body-sm">Notificaciones</Text>
|
|
316
|
+
<Switch checked={switchOn} onCheckedChange={setSwitchOn} />
|
|
317
|
+
</View>
|
|
318
|
+
<Separator />
|
|
319
|
+
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
320
|
+
<Text variant="body-sm">Modo oscuro</Text>
|
|
321
|
+
<Switch checked={darkMode} onCheckedChange={setDarkMode} />
|
|
322
|
+
</View>
|
|
323
|
+
</View>
|
|
324
|
+
</CardContent>
|
|
325
|
+
</Card>
|
|
326
|
+
<View style={{ height: 16 }} />
|
|
327
|
+
|
|
328
|
+
{/* Verification Banner */}
|
|
329
|
+
<AlertBanner
|
|
330
|
+
variant="success"
|
|
331
|
+
title="Cuenta verificada"
|
|
332
|
+
description="Tu email ha sido verificado correctamente."
|
|
333
|
+
iconName="shield"
|
|
334
|
+
/>
|
|
335
|
+
<View style={{ height: 32 }} />
|
|
336
|
+
</ScrollView>
|
|
337
|
+
</View>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const styles = StyleSheet.create({
|
|
342
|
+
fullScreen: {
|
|
343
|
+
flex: 1,
|
|
344
|
+
},
|
|
345
|
+
screenHeader: {
|
|
346
|
+
flexDirection: 'row',
|
|
347
|
+
alignItems: 'center',
|
|
348
|
+
justifyContent: 'space-between',
|
|
349
|
+
paddingHorizontal: 8,
|
|
350
|
+
paddingVertical: 8,
|
|
351
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
352
|
+
},
|
|
353
|
+
screenContent: {
|
|
354
|
+
padding: 16,
|
|
355
|
+
paddingBottom: 48,
|
|
356
|
+
},
|
|
357
|
+
})
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Example 3: Onboarding Flow
|
|
363
|
+
|
|
364
|
+
**Use case:** Multi-step wizard for user onboarding with progress tracking and conditional rendering.
|
|
365
|
+
|
|
366
|
+
**Components used:** Badge, Progress, Input, RadioGroup, EmptyState, Button, Text
|
|
367
|
+
|
|
368
|
+
**Features:**
|
|
369
|
+
- 3-step wizard with step indicator and progress bar
|
|
370
|
+
- Conditional rendering per step
|
|
371
|
+
- Name input with icon
|
|
372
|
+
- Goal selection with radio options
|
|
373
|
+
- Success state with illustration
|
|
374
|
+
- Contextual navigation (back/continue/start)
|
|
375
|
+
- Completion toast with dynamic message
|
|
376
|
+
|
|
377
|
+
### State Setup
|
|
378
|
+
|
|
379
|
+
```tsx
|
|
380
|
+
const [onboardingStep, setOnboardingStep] = useState(0)
|
|
381
|
+
const [onboardingName, setOnboardingName] = useState('')
|
|
382
|
+
const [onboardingGoal, setOnboardingGoal] = useState('save')
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Implementation
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
import { View, ScrollView, StyleSheet } from 'react-native'
|
|
389
|
+
import {
|
|
390
|
+
Badge,
|
|
391
|
+
Progress,
|
|
392
|
+
Input,
|
|
393
|
+
RadioGroup,
|
|
394
|
+
EmptyState,
|
|
395
|
+
Button,
|
|
396
|
+
ButtonGroup,
|
|
397
|
+
Text,
|
|
398
|
+
useTheme,
|
|
399
|
+
useToast,
|
|
400
|
+
} from '@retray-dev/ui-kit'
|
|
401
|
+
|
|
402
|
+
function OnboardingFlow() {
|
|
403
|
+
const { colors } = useTheme()
|
|
404
|
+
const { toast } = useToast()
|
|
405
|
+
|
|
406
|
+
return (
|
|
407
|
+
<View style={[styles.fullScreen, { backgroundColor: colors.background }]}>
|
|
408
|
+
{/* Header */}
|
|
409
|
+
<View style={[styles.screenHeader, { backgroundColor: colors.card, borderBottomColor: colors.border }]}>
|
|
410
|
+
<Button
|
|
411
|
+
label="Cerrar"
|
|
412
|
+
variant="text"
|
|
413
|
+
size="sm"
|
|
414
|
+
onPress={() => {/* close onboarding */}}
|
|
415
|
+
/>
|
|
416
|
+
<Text variant="title-sm">Bienvenido</Text>
|
|
417
|
+
<View style={{ width: 70 }} />
|
|
418
|
+
</View>
|
|
419
|
+
|
|
420
|
+
{/* Content */}
|
|
421
|
+
<ScrollView contentContainerStyle={styles.screenContent} keyboardShouldPersistTaps="handled">
|
|
422
|
+
{/* Progress Indicator */}
|
|
423
|
+
<View style={{ alignItems: 'center', marginBottom: 16 }}>
|
|
424
|
+
<Badge label={`Paso ${onboardingStep + 1} de 3`} variant="secondary" />
|
|
425
|
+
<View style={{ height: 12 }} />
|
|
426
|
+
<Progress value={(onboardingStep + 1) * 33.3} style={{ width: '100%' }} />
|
|
427
|
+
</View>
|
|
428
|
+
|
|
429
|
+
{/* Step 1: Name */}
|
|
430
|
+
{onboardingStep === 0 && (
|
|
431
|
+
<View style={{ gap: 16 }}>
|
|
432
|
+
<Text variant="display-md" style={{ textAlign: 'center' }}>¡Hola! 👋</Text>
|
|
433
|
+
<Text variant="body-sm" color={colors.foregroundMuted} style={{ textAlign: 'center' }}>
|
|
434
|
+
Cuéntanos un poco sobre ti para personalizar tu experiencia.
|
|
435
|
+
</Text>
|
|
436
|
+
<Input
|
|
437
|
+
label="¿Cómo te llamas?"
|
|
438
|
+
value={onboardingName}
|
|
439
|
+
onChangeText={setOnboardingName}
|
|
440
|
+
placeholder="Tu nombre"
|
|
441
|
+
prefixIcon="user"
|
|
442
|
+
/>
|
|
443
|
+
</View>
|
|
444
|
+
)}
|
|
445
|
+
|
|
446
|
+
{/* Step 2: Goal Selection */}
|
|
447
|
+
{onboardingStep === 1 && (
|
|
448
|
+
<View style={{ gap: 16 }}>
|
|
449
|
+
<Text variant="display-md" style={{ textAlign: 'center' }}>¿Cuál es tu meta?</Text>
|
|
450
|
+
<Text variant="body-sm" color={colors.foregroundMuted} style={{ textAlign: 'center' }}>
|
|
451
|
+
Te ayudaremos a alcanzarla.
|
|
452
|
+
</Text>
|
|
453
|
+
<RadioGroup
|
|
454
|
+
value={onboardingGoal}
|
|
455
|
+
onValueChange={setOnboardingGoal}
|
|
456
|
+
options={[
|
|
457
|
+
{ label: 'Ahorrar dinero', value: 'save' },
|
|
458
|
+
{ label: 'Pagar deudas', value: 'debt' },
|
|
459
|
+
{ label: 'Invertir', value: 'invest' },
|
|
460
|
+
{ label: 'Control de gastos', value: 'control' },
|
|
461
|
+
]}
|
|
462
|
+
/>
|
|
463
|
+
</View>
|
|
464
|
+
)}
|
|
465
|
+
|
|
466
|
+
{/* Step 3: Completion */}
|
|
467
|
+
{onboardingStep === 2 && (
|
|
468
|
+
<View style={{ gap: 16 }}>
|
|
469
|
+
<Text variant="display-md" style={{ textAlign: 'center' }}>
|
|
470
|
+
¡Listo, {onboardingName || 'amigo'}!
|
|
471
|
+
</Text>
|
|
472
|
+
<Text variant="body-sm" color={colors.foregroundMuted} style={{ textAlign: 'center' }}>
|
|
473
|
+
Tu cuenta está configurada. Empieza a explorar.
|
|
474
|
+
</Text>
|
|
475
|
+
<EmptyState
|
|
476
|
+
iconName="check-circle"
|
|
477
|
+
title="Todo configurado"
|
|
478
|
+
description="Toca continuar para empezar."
|
|
479
|
+
/>
|
|
480
|
+
</View>
|
|
481
|
+
)}
|
|
482
|
+
|
|
483
|
+
<View style={{ height: 32 }} />
|
|
484
|
+
|
|
485
|
+
{/* Navigation Buttons */}
|
|
486
|
+
{onboardingStep === 0 ? (
|
|
487
|
+
<Button
|
|
488
|
+
label="Continuar"
|
|
489
|
+
fullWidth
|
|
490
|
+
iconName="arrow-right"
|
|
491
|
+
onPress={() => setOnboardingStep((s) => s + 1)}
|
|
492
|
+
/>
|
|
493
|
+
) : (
|
|
494
|
+
<ButtonGroup gap={8}>
|
|
495
|
+
<Button
|
|
496
|
+
label="Atrás"
|
|
497
|
+
variant="secondary"
|
|
498
|
+
onPress={() => setOnboardingStep((s) => s - 1)}
|
|
499
|
+
/>
|
|
500
|
+
<Button
|
|
501
|
+
label={onboardingStep === 2 ? 'Empezar' : 'Continuar'}
|
|
502
|
+
iconName={onboardingStep === 2 ? 'check' : 'arrow-right'}
|
|
503
|
+
onPress={() => {
|
|
504
|
+
if (onboardingStep < 2) {
|
|
505
|
+
setOnboardingStep((s) => s + 1)
|
|
506
|
+
} else {
|
|
507
|
+
setOnboardingStep(0)
|
|
508
|
+
toast({
|
|
509
|
+
title: `¡Bienvenido, ${onboardingName || 'amigo'}!`,
|
|
510
|
+
variant: 'success'
|
|
511
|
+
})
|
|
512
|
+
}
|
|
513
|
+
}}
|
|
514
|
+
/>
|
|
515
|
+
</ButtonGroup>
|
|
516
|
+
)}
|
|
517
|
+
<View style={{ height: 32 }} />
|
|
518
|
+
</ScrollView>
|
|
519
|
+
</View>
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const styles = StyleSheet.create({
|
|
524
|
+
fullScreen: {
|
|
525
|
+
flex: 1,
|
|
526
|
+
},
|
|
527
|
+
screenHeader: {
|
|
528
|
+
flexDirection: 'row',
|
|
529
|
+
alignItems: 'center',
|
|
530
|
+
justifyContent: 'space-between',
|
|
531
|
+
paddingHorizontal: 8,
|
|
532
|
+
paddingVertical: 8,
|
|
533
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
534
|
+
},
|
|
535
|
+
screenContent: {
|
|
536
|
+
padding: 16,
|
|
537
|
+
paddingBottom: 48,
|
|
538
|
+
},
|
|
539
|
+
})
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
## Common Patterns
|
|
545
|
+
|
|
546
|
+
### Full-Screen Overlay Pattern
|
|
547
|
+
|
|
548
|
+
All examples use a full-screen overlay with consistent header structure:
|
|
549
|
+
|
|
550
|
+
```tsx
|
|
551
|
+
<View style={styles.fullScreen}>
|
|
552
|
+
{/* Header with navigation */}
|
|
553
|
+
<View style={styles.screenHeader}>
|
|
554
|
+
<Button label="Back" variant="text" size="sm" onPress={onBack} />
|
|
555
|
+
<Text variant="title-sm">Screen Title</Text>
|
|
556
|
+
<View style={{ width: 70 }} /> {/* Spacer for center alignment */}
|
|
557
|
+
</View>
|
|
558
|
+
|
|
559
|
+
{/* Scrollable content */}
|
|
560
|
+
<ScrollView contentContainerStyle={styles.screenContent}>
|
|
561
|
+
{/* content */}
|
|
562
|
+
</ScrollView>
|
|
563
|
+
</View>
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Spacing Pattern
|
|
567
|
+
|
|
568
|
+
Consistent vertical rhythm with explicit `<View style={{ height: X }} />` separators:
|
|
569
|
+
|
|
570
|
+
```tsx
|
|
571
|
+
<ComponentA />
|
|
572
|
+
<View style={{ height: 16 }} />
|
|
573
|
+
<ComponentB />
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Color Usage
|
|
577
|
+
|
|
578
|
+
Always pull colors from theme via `useTheme()`:
|
|
579
|
+
|
|
580
|
+
```tsx
|
|
581
|
+
const { colors } = useTheme()
|
|
582
|
+
|
|
583
|
+
<View style={{ backgroundColor: colors.card, borderColor: colors.border }}>
|
|
584
|
+
<Text color={colors.foregroundMuted}>Caption text</Text>
|
|
585
|
+
</View>
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### Form Field Spacing
|
|
589
|
+
|
|
590
|
+
Standard 12px gap between fields:
|
|
591
|
+
|
|
592
|
+
```tsx
|
|
593
|
+
<Input label="Field 1" />
|
|
594
|
+
<View style={{ height: 12 }} />
|
|
595
|
+
<Input label="Field 2" />
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Toast Feedback
|
|
599
|
+
|
|
600
|
+
Use `useToast()` for success/error feedback after actions:
|
|
601
|
+
|
|
602
|
+
```tsx
|
|
603
|
+
const { toast } = useToast()
|
|
604
|
+
|
|
605
|
+
toast({ title: 'Action completed', variant: 'success' })
|
|
606
|
+
toast({ title: 'Error occurred', variant: 'destructive' })
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### Button Pairs & Groups
|
|
610
|
+
|
|
611
|
+
Use `ButtonGroup` for equally-spaced button layouts (50%/50% split):
|
|
612
|
+
|
|
613
|
+
```tsx
|
|
614
|
+
// Horizontal pair (Cancel/Confirm pattern)
|
|
615
|
+
// Use size="sm" when icons are present to prevent text clipping on mobile
|
|
616
|
+
<ButtonGroup>
|
|
617
|
+
<Button label="Cancel" variant="secondary" size="sm" iconName="x" onPress={handleCancel} />
|
|
618
|
+
<Button label="Confirm" size="sm" iconName="check" onPress={handleConfirm} />
|
|
619
|
+
</ButtonGroup>
|
|
620
|
+
|
|
621
|
+
// Vertical stack
|
|
622
|
+
<ButtonGroup vertical gap={8}>
|
|
623
|
+
<Button label="Primary Action" size="sm" iconName="zap" onPress={handlePrimary} />
|
|
624
|
+
<Button label="Secondary Action" variant="secondary" size="sm" iconName="settings" onPress={handleSecondary} />
|
|
625
|
+
<Button label="Delete" variant="destructive" size="sm" iconName="trash-2" onPress={handleDelete} />
|
|
626
|
+
</ButtonGroup>
|
|
627
|
+
|
|
628
|
+
// Conditional rendering (single vs pair)
|
|
629
|
+
{isFirstStep ? (
|
|
630
|
+
<Button label="Continue" fullWidth iconName="arrow-right" onPress={handleNext} />
|
|
631
|
+
) : (
|
|
632
|
+
<ButtonGroup>
|
|
633
|
+
<Button label="Back" variant="secondary" onPress={handleBack} />
|
|
634
|
+
<Button label="Continue" iconName="arrow-right" onPress={handleNext} />
|
|
635
|
+
</ButtonGroup>
|
|
636
|
+
)}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## Running the Examples
|
|
642
|
+
|
|
643
|
+
These examples are live in the `example/` app included with the source. To run them:
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
# Clone the repo
|
|
647
|
+
git clone https://github.com/YOUR_USERNAME/retray-ui-kit
|
|
648
|
+
cd retray-ui-kit
|
|
649
|
+
|
|
650
|
+
# Install dependencies
|
|
651
|
+
pnpm install
|
|
652
|
+
|
|
653
|
+
# Build the library
|
|
654
|
+
pnpm build
|
|
655
|
+
|
|
656
|
+
# Run the example app
|
|
657
|
+
cd example
|
|
658
|
+
pnpm start
|
|
659
|
+
# Or: pnpm ios / pnpm android
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
Inside the example app, navigate to the "🖥 App Screen Compositions" accordion to access all three screens.
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
**For AI agents:** When generating screens, follow these patterns for consistency with the library's design philosophy. Use the theme colors, typography variants, spacing tokens, and component props shown in these examples.
|