internhub-v2-mobile-ui-kit 0.0.1

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/.gitkeep ADDED
File without changes
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "internhub-v2-mobile-ui-kit",
3
+ "version": "0.0.1",
4
+ "description": "Premium UI Kit for InternHub Mobile App",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "scripts": {
8
+ "lint": "eslint .",
9
+ "test": "jest"
10
+ },
11
+ "keywords": [
12
+ "react-native",
13
+ "ui-kit",
14
+ "internhub"
15
+ ],
16
+ "author": "Nguyen Thanh Nhan",
17
+ "license": "MIT",
18
+ "peerDependencies": {
19
+ "react": "*",
20
+ "react-native": "*"
21
+ },
22
+ "dependencies": {
23
+ "react-native-linear-gradient": "^2.8.3",
24
+ "react-native-svg": "^14.1.0"
25
+ }
26
+ }
package/src/.gitkeep ADDED
File without changes
@@ -0,0 +1,129 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet } from 'react-native';
3
+ import { Colors, Spacing, FontSize, BorderRadius } from '../tokens';
4
+
5
+ interface AlertProps {
6
+ type: 'success' | 'error' | 'warning' | 'info';
7
+ message: string;
8
+ subMessage?: string;
9
+ }
10
+
11
+ export const Alert = ({ type, message, subMessage }: AlertProps) => {
12
+ const getStyles = () => {
13
+ switch (type) {
14
+ case 'success':
15
+ return {
16
+ container: styles.successContainer,
17
+ icon: '✓',
18
+ iconStyle: styles.successIcon,
19
+ };
20
+ case 'error':
21
+ return {
22
+ container: styles.errorContainer,
23
+ icon: '✕',
24
+ iconStyle: styles.errorIcon,
25
+ };
26
+ case 'warning':
27
+ return {
28
+ container: styles.warningContainer,
29
+ icon: '!',
30
+ iconStyle: styles.warningIcon,
31
+ };
32
+ case 'info':
33
+ default:
34
+ return {
35
+ container: styles.infoContainer,
36
+ icon: 'i',
37
+ iconStyle: styles.infoIcon,
38
+ };
39
+ }
40
+ };
41
+
42
+ const alertStyles = getStyles();
43
+
44
+ return (
45
+ <View style={[styles.container, alertStyles.container]}>
46
+ <View style={[styles.iconContainer, alertStyles.iconStyle]}>
47
+ <Text style={styles.iconText}>{alertStyles.icon}</Text>
48
+ </View>
49
+ <View style={styles.textContainer}>
50
+ <Text style={styles.message}>{message}</Text>
51
+ {subMessage && <Text style={styles.subMessage}>{subMessage}</Text>}
52
+ </View>
53
+ </View>
54
+ );
55
+ };
56
+
57
+ const styles = StyleSheet.create({
58
+ container: {
59
+ flexDirection: 'row',
60
+ alignItems: 'flex-start',
61
+ padding: Spacing.md,
62
+ borderRadius: BorderRadius.md,
63
+ marginBottom: Spacing.md,
64
+ marginTop: Spacing.sm,
65
+ },
66
+ iconContainer: {
67
+ width: 20,
68
+ height: 20,
69
+ borderRadius: 10,
70
+ justifyContent: 'center',
71
+ alignItems: 'center',
72
+ marginRight: Spacing.sm,
73
+ marginTop: 2,
74
+ },
75
+ iconText: {
76
+ color: '#FFF',
77
+ fontSize: 12,
78
+ fontWeight: 'bold',
79
+ },
80
+ textContainer: {
81
+ flex: 1,
82
+ },
83
+ message: {
84
+ fontSize: FontSize.md,
85
+ color: '#D32F2F',
86
+ fontWeight: '500',
87
+ },
88
+ subMessage: {
89
+ fontSize: FontSize.sm,
90
+ color: '#D32F2F',
91
+ marginTop: 4,
92
+ },
93
+ // Error - Pink/Red background like Figma
94
+ errorContainer: {
95
+ backgroundColor: '#FFEBEE',
96
+ borderWidth: 1,
97
+ borderColor: '#FFCDD2',
98
+ },
99
+ errorIcon: {
100
+ backgroundColor: '#D32F2F',
101
+ },
102
+ // Success
103
+ successContainer: {
104
+ backgroundColor: '#E8F5E9',
105
+ borderWidth: 1,
106
+ borderColor: '#C8E6C9',
107
+ },
108
+ successIcon: {
109
+ backgroundColor: '#4CAF50',
110
+ },
111
+ // Warning
112
+ warningContainer: {
113
+ backgroundColor: '#FFF8E1',
114
+ borderWidth: 1,
115
+ borderColor: '#FFECB3',
116
+ },
117
+ warningIcon: {
118
+ backgroundColor: '#FF9800',
119
+ },
120
+ // Info
121
+ infoContainer: {
122
+ backgroundColor: '#E3F2FD',
123
+ borderWidth: 1,
124
+ borderColor: '#BBDEFB',
125
+ },
126
+ infoIcon: {
127
+ backgroundColor: '#2196F3',
128
+ },
129
+ });
File without changes
File without changes
File without changes
@@ -0,0 +1,136 @@
1
+ import React from 'react';
2
+ import {
3
+ TouchableOpacity,
4
+ Text,
5
+ StyleSheet,
6
+ ActivityIndicator,
7
+ ViewStyle,
8
+ } from 'react-native';
9
+ import { Colors, Spacing, FontSize } from '../tokens';
10
+
11
+ interface ButtonProps {
12
+ title: string;
13
+ onPress: () => void;
14
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
15
+ disabled?: boolean;
16
+ loading?: boolean;
17
+ style?: ViewStyle;
18
+ }
19
+
20
+ export const Button = ({
21
+ title,
22
+ onPress,
23
+ variant = 'primary',
24
+ disabled = false,
25
+ loading = false,
26
+ style,
27
+ }: ButtonProps) => {
28
+ const getButtonStyle = () => {
29
+ if (disabled) {
30
+ return styles.buttonDisabled;
31
+ }
32
+ switch (variant) {
33
+ case 'primary':
34
+ return styles.buttonPrimary;
35
+ case 'secondary':
36
+ return styles.buttonSecondary;
37
+ case 'outline':
38
+ return styles.buttonOutline;
39
+ case 'ghost':
40
+ return styles.buttonGhost;
41
+ default:
42
+ return styles.buttonPrimary;
43
+ }
44
+ };
45
+
46
+ const getTextStyle = () => {
47
+ if (disabled) {
48
+ return styles.textDisabled;
49
+ }
50
+ switch (variant) {
51
+ case 'primary':
52
+ return styles.textPrimary;
53
+ case 'secondary':
54
+ return styles.textSecondary;
55
+ case 'outline':
56
+ return styles.textOutline;
57
+ case 'ghost':
58
+ return styles.textGhost;
59
+ default:
60
+ return styles.textPrimary;
61
+ }
62
+ };
63
+
64
+ return (
65
+ <TouchableOpacity
66
+ style={[styles.button, getButtonStyle(), style]}
67
+ onPress={onPress}
68
+ disabled={disabled || loading}
69
+ activeOpacity={0.85}>
70
+ {loading ? (
71
+ <ActivityIndicator
72
+ color={variant === 'outline' ? '#FF9800' : '#FFF'}
73
+ />
74
+ ) : (
75
+ <Text style={[styles.text, getTextStyle()]}>{title}</Text>
76
+ )}
77
+ </TouchableOpacity>
78
+ );
79
+ };
80
+
81
+ const styles = StyleSheet.create({
82
+ button: {
83
+ paddingVertical: Spacing.md + 2,
84
+ paddingHorizontal: Spacing.xl,
85
+ borderRadius: 14,
86
+ alignItems: 'center',
87
+ justifyContent: 'center',
88
+ minHeight: 52,
89
+ },
90
+ buttonPrimary: {
91
+ backgroundColor: '#FF9800',
92
+ shadowColor: '#FF9800',
93
+ shadowOffset: { width: 0, height: 4 },
94
+ shadowOpacity: 0.3,
95
+ shadowRadius: 8,
96
+ elevation: 5,
97
+ },
98
+ buttonSecondary: {
99
+ backgroundColor: '#4CAF50',
100
+ shadowColor: '#4CAF50',
101
+ shadowOffset: { width: 0, height: 4 },
102
+ shadowOpacity: 0.3,
103
+ shadowRadius: 8,
104
+ elevation: 5,
105
+ },
106
+ buttonOutline: {
107
+ backgroundColor: '#F5F5F5',
108
+ borderWidth: 0,
109
+ },
110
+ buttonGhost: {
111
+ backgroundColor: 'transparent',
112
+ },
113
+ buttonDisabled: {
114
+ backgroundColor: '#E0E0E0',
115
+ },
116
+ text: {
117
+ fontSize: FontSize.lg,
118
+ fontWeight: '600',
119
+ letterSpacing: 0.5,
120
+ },
121
+ textPrimary: {
122
+ color: '#FFF',
123
+ },
124
+ textSecondary: {
125
+ color: '#FFF',
126
+ },
127
+ textOutline: {
128
+ color: '#757575',
129
+ },
130
+ textGhost: {
131
+ color: '#FF9800',
132
+ },
133
+ textDisabled: {
134
+ color: '#9E9E9E',
135
+ },
136
+ });
File without changes
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet, ViewStyle } from 'react-native';
3
+ import { Colors, BorderRadius, Spacing } from '../tokens';
4
+
5
+ interface CardProps {
6
+ children: React.ReactNode;
7
+ style?: ViewStyle;
8
+ }
9
+
10
+ export const Card = ({ children, style }: CardProps) => {
11
+ return <View style={[styles.card, style]}>{children}</View>;
12
+ };
13
+
14
+ const styles = StyleSheet.create({
15
+ card: {
16
+ backgroundColor: Colors.cardBackground,
17
+ borderRadius: 30,
18
+ padding: Spacing.xl,
19
+ marginHorizontal: Spacing.lg,
20
+ // Premium shadow
21
+ shadowColor: '#000',
22
+ shadowOffset: { width: 0, height: 10 },
23
+ shadowOpacity: 0.15,
24
+ shadowRadius: 20,
25
+ elevation: 10,
26
+ },
27
+ });
File without changes
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import { StyleSheet, View } from 'react-native';
3
+ import LinearGradient from 'react-native-linear-gradient';
4
+
5
+ interface GradientBackgroundProps {
6
+ children: React.ReactNode;
7
+ }
8
+
9
+ export const GradientBackground = ({ children }: GradientBackgroundProps) => {
10
+ return (
11
+ <LinearGradient
12
+ colors={['#FFE4C4', '#FFECD2', '#FFF5EB', '#FFFFFF', '#FFFFFF']}
13
+ locations={[0, 0.25, 0.45, 0.7, 1]}
14
+ start={{ x: 0.5, y: 0 }}
15
+ end={{ x: 0.5, y: 1 }}
16
+ style={styles.container}
17
+ >
18
+ <View style={styles.content}>{children}</View>
19
+ </LinearGradient>
20
+ );
21
+ };
22
+
23
+ const styles = StyleSheet.create({
24
+ container: {
25
+ flex: 1,
26
+ },
27
+ content: {
28
+ flex: 1,
29
+ zIndex: 10,
30
+ },
31
+ });
File without changes
@@ -0,0 +1,180 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TextInput,
6
+ StyleSheet,
7
+ TouchableOpacity,
8
+ TextInputProps,
9
+ } from 'react-native';
10
+ import { Colors, Spacing, FontSize, BorderRadius } from '../tokens';
11
+
12
+ interface InputProps extends TextInputProps {
13
+ label?: string;
14
+ required?: boolean;
15
+ error?: string;
16
+ isPassword?: boolean;
17
+ }
18
+
19
+ export const Input = ({
20
+ label,
21
+ required,
22
+ error,
23
+ isPassword,
24
+ style,
25
+ ...props
26
+ }: InputProps) => {
27
+ const [isFocused, setIsFocused] = useState(false);
28
+ const [showPassword, setShowPassword] = useState(false);
29
+
30
+ return (
31
+ <View style={styles.container}>
32
+ {label && (
33
+ <Text style={styles.label}>
34
+ {label}
35
+ {required && <Text style={styles.required}> *</Text>}
36
+ </Text>
37
+ )}
38
+
39
+ <View
40
+ style={[
41
+ styles.inputContainer,
42
+ isFocused && styles.inputFocused,
43
+ error && styles.inputError,
44
+ ]}>
45
+ <TextInput
46
+ style={[styles.input, style]}
47
+ placeholderTextColor="#BDBDBD"
48
+ secureTextEntry={isPassword && !showPassword}
49
+ onFocus={() => setIsFocused(true)}
50
+ onBlur={() => setIsFocused(false)}
51
+ {...props}
52
+ />
53
+
54
+ {isPassword && (
55
+ <TouchableOpacity
56
+ onPress={() => setShowPassword(!showPassword)}
57
+ style={styles.eyeButton}>
58
+ {showPassword ? (
59
+ // Eye open icon
60
+ <View style={styles.eyeIconContainer}>
61
+ <View style={styles.eyeShape} />
62
+ <View style={styles.eyePupil} />
63
+ </View>
64
+ ) : (
65
+ // Lock icon
66
+ <View style={styles.lockIconContainer}>
67
+ <View style={styles.lockShackle} />
68
+ <View style={styles.lockBody}>
69
+ <View style={styles.lockKeyhole} />
70
+ </View>
71
+ </View>
72
+ )}
73
+ </TouchableOpacity>
74
+ )}
75
+ </View>
76
+
77
+ {error && <Text style={styles.errorText}>{error}</Text>}
78
+ </View>
79
+ );
80
+ };
81
+
82
+ const styles = StyleSheet.create({
83
+ container: {
84
+ marginBottom: Spacing.lg,
85
+ },
86
+ label: {
87
+ fontSize: FontSize.md,
88
+ color: '#424242',
89
+ marginBottom: Spacing.sm,
90
+ fontWeight: '500',
91
+ },
92
+ required: {
93
+ color: '#FF5252',
94
+ },
95
+ inputContainer: {
96
+ flexDirection: 'row',
97
+ alignItems: 'center',
98
+ borderWidth: 1.5,
99
+ borderColor: '#E0E0E0',
100
+ borderRadius: 12,
101
+ backgroundColor: '#FAFAFA',
102
+ paddingHorizontal: Spacing.md,
103
+ },
104
+ inputFocused: {
105
+ borderColor: '#FF9800',
106
+ backgroundColor: '#FFF',
107
+ shadowColor: '#FF9800',
108
+ shadowOffset: { width: 0, height: 0 },
109
+ shadowOpacity: 0.2,
110
+ shadowRadius: 8,
111
+ elevation: 3,
112
+ },
113
+ inputError: {
114
+ borderColor: '#FF5252',
115
+ },
116
+ input: {
117
+ flex: 1,
118
+ fontSize: FontSize.md,
119
+ color: '#212121',
120
+ paddingVertical: Spacing.md,
121
+ },
122
+ eyeButton: {
123
+ padding: Spacing.xs,
124
+ },
125
+ // Lock icon
126
+ lockIconContainer: {
127
+ width: 20,
128
+ height: 22,
129
+ alignItems: 'center',
130
+ },
131
+ lockShackle: {
132
+ width: 12,
133
+ height: 8,
134
+ borderWidth: 2,
135
+ borderColor: '#FF9800',
136
+ borderBottomWidth: 0,
137
+ borderTopLeftRadius: 6,
138
+ borderTopRightRadius: 6,
139
+ },
140
+ lockBody: {
141
+ width: 16,
142
+ height: 12,
143
+ backgroundColor: '#FF9800',
144
+ borderRadius: 3,
145
+ alignItems: 'center',
146
+ justifyContent: 'center',
147
+ },
148
+ lockKeyhole: {
149
+ width: 4,
150
+ height: 4,
151
+ backgroundColor: '#FFF',
152
+ borderRadius: 2,
153
+ },
154
+ // Eye icon
155
+ eyeIconContainer: {
156
+ width: 22,
157
+ height: 14,
158
+ justifyContent: 'center',
159
+ alignItems: 'center',
160
+ },
161
+ eyeShape: {
162
+ width: 20,
163
+ height: 12,
164
+ borderWidth: 2,
165
+ borderColor: '#FF9800',
166
+ borderRadius: 10,
167
+ },
168
+ eyePupil: {
169
+ position: 'absolute',
170
+ width: 6,
171
+ height: 6,
172
+ backgroundColor: '#FF9800',
173
+ borderRadius: 3,
174
+ },
175
+ errorText: {
176
+ fontSize: FontSize.sm,
177
+ color: '#FF5252',
178
+ marginTop: Spacing.xs,
179
+ },
180
+ });
File without changes
@@ -0,0 +1,304 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ Modal as RNModal,
7
+ TouchableOpacity,
8
+ Dimensions,
9
+ } from 'react-native';
10
+ import { Spacing, FontSize, BorderRadius } from '../tokens';
11
+
12
+ const { width } = Dimensions.get('window');
13
+
14
+ interface ModalProps {
15
+ visible: boolean;
16
+ type: 'success' | 'error' | 'warning' | 'info';
17
+ title: string;
18
+ message: string;
19
+ buttonText: string;
20
+ onPress: () => void;
21
+ secondaryButtonText?: string;
22
+ onSecondaryPress?: () => void;
23
+ }
24
+
25
+ // Custom drawn icon components
26
+ const ErrorIcon = () => (
27
+ <View style={iconStyles.container}>
28
+ {/* Sparkles */}
29
+ <View style={[iconStyles.sparkle, { top: 5, left: 15 }]}>
30
+ <Text style={iconStyles.sparkleText}>✦</Text>
31
+ </View>
32
+ <View style={[iconStyles.sparkle, { top: 10, right: 10 }]}>
33
+ <Text style={iconStyles.sparkleText}>✦</Text>
34
+ </View>
35
+ <View style={[iconStyles.sparkle, { bottom: 20, left: 5 }]}>
36
+ <Text style={[iconStyles.sparkleText, { fontSize: 10 }]}>✕</Text>
37
+ </View>
38
+ <View style={[iconStyles.sparkle, { bottom: 10, right: 15 }]}>
39
+ <Text style={iconStyles.sparkleText}>✦</Text>
40
+ </View>
41
+
42
+ {/* Face */}
43
+ <View style={iconStyles.errorFace}>
44
+ {/* X eyes */}
45
+ <View style={iconStyles.eyeRow}>
46
+ <Text style={iconStyles.xEye}>✕</Text>
47
+ <Text style={iconStyles.xEye}>✕</Text>
48
+ </View>
49
+ {/* Sad mouth */}
50
+ <View style={iconStyles.sadMouth} />
51
+ </View>
52
+
53
+ {/* Badge */}
54
+ <View style={iconStyles.errorBadge}>
55
+ <Text style={iconStyles.badgeX}>✕</Text>
56
+ </View>
57
+ </View>
58
+ );
59
+
60
+ const SuccessIcon = () => (
61
+ <View style={iconStyles.container}>
62
+ {/* Sparkles */}
63
+ <View style={[iconStyles.sparkle, { top: 5, left: 15 }]}>
64
+ <Text style={[iconStyles.sparkleText, { color: '#81C784' }]}>✦</Text>
65
+ </View>
66
+ <View style={[iconStyles.sparkle, { top: 10, right: 10 }]}>
67
+ <Text style={[iconStyles.sparkleText, { color: '#81C784' }]}>✦</Text>
68
+ </View>
69
+ <View style={[iconStyles.sparkle, { bottom: 20, left: 5 }]}>
70
+ <Text style={[iconStyles.sparkleText, { color: '#81C784', fontSize: 10 }]}>★</Text>
71
+ </View>
72
+ <View style={[iconStyles.sparkle, { bottom: 10, right: 15 }]}>
73
+ <Text style={[iconStyles.sparkleText, { color: '#81C784' }]}>✦</Text>
74
+ </View>
75
+
76
+ {/* Face */}
77
+ <View style={iconStyles.successFace}>
78
+ {/* Happy eyes */}
79
+ <View style={iconStyles.eyeRow}>
80
+ <View style={iconStyles.happyEye} />
81
+ <View style={iconStyles.happyEye} />
82
+ </View>
83
+ {/* Happy mouth */}
84
+ <View style={iconStyles.happyMouth} />
85
+ </View>
86
+
87
+ {/* Badge */}
88
+ <View style={iconStyles.successBadge}>
89
+ <Text style={iconStyles.badgeCheck}>✓</Text>
90
+ </View>
91
+ </View>
92
+ );
93
+
94
+ export const Modal = ({
95
+ visible,
96
+ type,
97
+ title,
98
+ message,
99
+ buttonText,
100
+ onPress,
101
+ secondaryButtonText,
102
+ onSecondaryPress,
103
+ }: ModalProps) => {
104
+ const getTitleColor = () => {
105
+ switch (type) {
106
+ case 'success':
107
+ return '#4CAF50';
108
+ case 'error':
109
+ case 'warning':
110
+ return '#E57373';
111
+ case 'info':
112
+ default:
113
+ return '#64B5F6';
114
+ }
115
+ };
116
+
117
+ return (
118
+ <RNModal
119
+ visible={visible}
120
+ transparent
121
+ animationType="fade"
122
+ statusBarTranslucent>
123
+ <View style={styles.overlay}>
124
+ <View style={styles.modalContainer}>
125
+ {/* Icon */}
126
+ {type === 'error' || type === 'warning' ? <ErrorIcon /> : <SuccessIcon />}
127
+
128
+ {/* Title */}
129
+ <Text style={[styles.title, { color: getTitleColor() }]}>{title}</Text>
130
+
131
+ {/* Message with link */}
132
+ <Text style={styles.message}>
133
+ {message.split('Quên mật khẩu')[0]}
134
+ {message.includes('Quên mật khẩu') && onSecondaryPress && (
135
+ <Text style={styles.link} onPress={onSecondaryPress}>
136
+ Quên mật khẩu
137
+ </Text>
138
+ )}
139
+ </Text>
140
+
141
+ {/* Primary Button */}
142
+ <TouchableOpacity
143
+ style={styles.primaryButton}
144
+ onPress={onPress}
145
+ activeOpacity={0.8}>
146
+ <Text style={styles.primaryButtonText}>{buttonText}</Text>
147
+ </TouchableOpacity>
148
+ </View>
149
+ </View>
150
+ </RNModal>
151
+ );
152
+ };
153
+
154
+ const iconStyles = StyleSheet.create({
155
+ container: {
156
+ width: 100,
157
+ height: 100,
158
+ justifyContent: 'center',
159
+ alignItems: 'center',
160
+ marginBottom: Spacing.md,
161
+ },
162
+ sparkle: {
163
+ position: 'absolute',
164
+ },
165
+ sparkleText: {
166
+ fontSize: 14,
167
+ color: '#F8A5A5',
168
+ },
169
+ // Error face
170
+ errorFace: {
171
+ width: 70,
172
+ height: 70,
173
+ borderRadius: 35,
174
+ backgroundColor: '#F8A5A5',
175
+ justifyContent: 'center',
176
+ alignItems: 'center',
177
+ },
178
+ eyeRow: {
179
+ flexDirection: 'row',
180
+ justifyContent: 'space-between',
181
+ width: 40,
182
+ marginBottom: 5,
183
+ },
184
+ xEye: {
185
+ fontSize: 16,
186
+ fontWeight: 'bold',
187
+ color: '#C75050',
188
+ },
189
+ sadMouth: {
190
+ width: 25,
191
+ height: 12,
192
+ borderTopWidth: 3,
193
+ borderTopColor: '#C75050',
194
+ borderRadius: 20,
195
+ marginTop: 5,
196
+ },
197
+ errorBadge: {
198
+ position: 'absolute',
199
+ bottom: 10,
200
+ right: 15,
201
+ width: 24,
202
+ height: 24,
203
+ borderRadius: 12,
204
+ backgroundColor: '#EF5350',
205
+ justifyContent: 'center',
206
+ alignItems: 'center',
207
+ },
208
+ badgeX: {
209
+ color: '#FFF',
210
+ fontSize: 14,
211
+ fontWeight: 'bold',
212
+ },
213
+ // Success face
214
+ successFace: {
215
+ width: 70,
216
+ height: 70,
217
+ borderRadius: 35,
218
+ backgroundColor: '#81C784',
219
+ justifyContent: 'center',
220
+ alignItems: 'center',
221
+ },
222
+ happyEye: {
223
+ width: 8,
224
+ height: 4,
225
+ borderBottomWidth: 3,
226
+ borderBottomColor: '#2E7D32',
227
+ borderRadius: 10,
228
+ },
229
+ happyMouth: {
230
+ width: 25,
231
+ height: 12,
232
+ borderBottomWidth: 3,
233
+ borderBottomColor: '#2E7D32',
234
+ borderRadius: 20,
235
+ marginTop: 8,
236
+ },
237
+ successBadge: {
238
+ position: 'absolute',
239
+ bottom: 10,
240
+ right: 15,
241
+ width: 24,
242
+ height: 24,
243
+ borderRadius: 12,
244
+ backgroundColor: '#4CAF50',
245
+ justifyContent: 'center',
246
+ alignItems: 'center',
247
+ },
248
+ badgeCheck: {
249
+ color: '#FFF',
250
+ fontSize: 14,
251
+ fontWeight: 'bold',
252
+ },
253
+ });
254
+
255
+ const styles = StyleSheet.create({
256
+ overlay: {
257
+ flex: 1,
258
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
259
+ justifyContent: 'center',
260
+ alignItems: 'center',
261
+ padding: Spacing.lg,
262
+ },
263
+ modalContainer: {
264
+ width: width - Spacing.xl * 2,
265
+ backgroundColor: '#FFF',
266
+ borderRadius: BorderRadius.xl,
267
+ padding: Spacing.xl,
268
+ alignItems: 'center',
269
+ shadowColor: '#000',
270
+ shadowOffset: { width: 0, height: 10 },
271
+ shadowOpacity: 0.2,
272
+ shadowRadius: 20,
273
+ elevation: 10,
274
+ },
275
+ title: {
276
+ fontSize: FontSize.xl,
277
+ fontWeight: '700',
278
+ textAlign: 'center',
279
+ marginBottom: Spacing.md,
280
+ },
281
+ message: {
282
+ fontSize: FontSize.md,
283
+ color: '#666',
284
+ textAlign: 'center',
285
+ lineHeight: 22,
286
+ marginBottom: Spacing.xl,
287
+ },
288
+ link: {
289
+ color: '#FF9800',
290
+ fontWeight: '600',
291
+ },
292
+ primaryButton: {
293
+ width: '100%',
294
+ backgroundColor: '#FF9800',
295
+ paddingVertical: Spacing.md,
296
+ borderRadius: BorderRadius.lg,
297
+ alignItems: 'center',
298
+ },
299
+ primaryButtonText: {
300
+ color: '#FFF',
301
+ fontSize: FontSize.lg,
302
+ fontWeight: '600',
303
+ },
304
+ });
@@ -0,0 +1,84 @@
1
+ import React, { useRef, useState } from 'react';
2
+ import { View, TextInput, StyleSheet } from 'react-native';
3
+ import { Colors, Spacing, BorderRadius, FontSize } from '../tokens';
4
+
5
+ interface OTPInputProps {
6
+ length?: number;
7
+ value: string;
8
+ onChange: (value: string) => void;
9
+ error?: boolean;
10
+ }
11
+
12
+ export const OTPInput = ({
13
+ length = 6,
14
+ value,
15
+ onChange,
16
+ error = false,
17
+ }: OTPInputProps) => {
18
+ const inputRefs = useRef<(TextInput | null)[]>([]);
19
+ const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
20
+
21
+ const handleChange = (text: string, index: number) => {
22
+ const newValue = value.split('');
23
+ newValue[index] = text;
24
+
25
+ // Move to next input
26
+ if (text && index < length - 1) {
27
+ inputRefs.current[index + 1]?.focus();
28
+ }
29
+
30
+ onChange(newValue.join(''));
31
+ };
32
+
33
+ const handleKeyPress = (key: string, index: number) => {
34
+ if (key === 'Backspace' && !value[index] && index > 0) {
35
+ inputRefs.current[index - 1]?.focus();
36
+ }
37
+ };
38
+
39
+ const getBorderColor = (index: number) => {
40
+ if (error) return '#FF5252'; // Error red
41
+ if (focusedIndex === index) return '#FF9800'; // Focus orange
42
+ if (value[index]) return Colors.success;
43
+ return Colors.border;
44
+ };
45
+
46
+ return (
47
+ <View style={styles.container}>
48
+ {Array.from({ length }, (_, index) => (
49
+ <TextInput
50
+ key={index}
51
+ ref={ref => (inputRefs.current[index] = ref)}
52
+ style={[styles.input, { borderColor: getBorderColor(index) }]}
53
+ value={value[index] || ''}
54
+ onChangeText={text => handleChange(text.slice(-1), index)}
55
+ onKeyPress={({ nativeEvent }) => handleKeyPress(nativeEvent.key, index)}
56
+ onFocus={() => setFocusedIndex(index)}
57
+ onBlur={() => setFocusedIndex(null)}
58
+ keyboardType="number-pad"
59
+ maxLength={1}
60
+ textAlign="center"
61
+ />
62
+ ))}
63
+ </View>
64
+ );
65
+ };
66
+
67
+ const styles = StyleSheet.create({
68
+ container: {
69
+ flexDirection: 'row',
70
+ justifyContent: 'center',
71
+ alignItems: 'center',
72
+ gap: Spacing.sm,
73
+ },
74
+ input: {
75
+ width: 45,
76
+ height: 52,
77
+ borderWidth: 1,
78
+ borderRadius: BorderRadius.sm,
79
+ fontSize: FontSize.xxl,
80
+ fontWeight: '600',
81
+ color: Colors.textPrimary,
82
+ backgroundColor: Colors.white,
83
+ },
84
+ });
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet } from 'react-native';
3
+ import { Colors, Spacing, FontSize } from '../tokens';
4
+
5
+ interface PasswordRequirement {
6
+ label: string;
7
+ isValid: boolean;
8
+ }
9
+
10
+ interface PasswordCheckerProps {
11
+ requirements: PasswordRequirement[];
12
+ }
13
+
14
+ export const PasswordChecker = ({ requirements }: PasswordCheckerProps) => {
15
+ return (
16
+ <View style={styles.container}>
17
+ {requirements.map((req, index) => (
18
+ <View key={index} style={styles.row}>
19
+ <View
20
+ style={[
21
+ styles.checkbox,
22
+ req.isValid ? styles.checkboxValid : styles.checkboxInvalid,
23
+ ]}>
24
+ {req.isValid && <Text style={styles.checkmark}>✓</Text>}
25
+ </View>
26
+ <Text
27
+ style={[
28
+ styles.label,
29
+ req.isValid ? styles.labelValid : styles.labelInvalid,
30
+ ]}>
31
+ {req.label}
32
+ </Text>
33
+ </View>
34
+ ))}
35
+ </View>
36
+ );
37
+ };
38
+
39
+ const styles = StyleSheet.create({
40
+ container: {
41
+ backgroundColor: Colors.successLight,
42
+ borderRadius: 8,
43
+ padding: Spacing.md,
44
+ marginBottom: Spacing.md,
45
+ },
46
+ row: {
47
+ flexDirection: 'row',
48
+ alignItems: 'center',
49
+ marginBottom: Spacing.xs,
50
+ },
51
+ checkbox: {
52
+ width: 18,
53
+ height: 18,
54
+ borderRadius: 9,
55
+ justifyContent: 'center',
56
+ alignItems: 'center',
57
+ marginRight: Spacing.sm,
58
+ },
59
+ checkboxValid: {
60
+ backgroundColor: Colors.success,
61
+ },
62
+ checkboxInvalid: {
63
+ backgroundColor: Colors.textDisabled,
64
+ },
65
+ checkmark: {
66
+ color: Colors.white,
67
+ fontSize: 12,
68
+ fontWeight: 'bold',
69
+ },
70
+ label: {
71
+ fontSize: FontSize.sm,
72
+ flex: 1,
73
+ },
74
+ labelValid: {
75
+ color: Colors.success,
76
+ },
77
+ labelInvalid: {
78
+ color: Colors.textSecondary,
79
+ },
80
+ });
File without changes
File without changes
File without changes
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ // Design Tokens
2
+ export * from './tokens';
3
+
4
+ // Components
5
+ export { Button } from './Button/Button';
6
+ export { Input } from './Input/Input';
7
+ export { Card } from './Card/Card';
8
+ export { OTPInput } from './OTPInput/OTPInput';
9
+ export { Alert } from './Alert/Alert';
10
+ export { GradientBackground } from './GradientBackground/GradientBackground';
11
+ export { Modal } from './Modal/Modal';
12
+ export { PasswordChecker } from './PasswordChecker/PasswordChecker';
package/src/tokens.ts ADDED
@@ -0,0 +1,104 @@
1
+ // Design Tokens - Extracted from Figma
2
+ export const Colors = {
3
+ // Brand - Premium Orange
4
+ primary: '#E18308', // Main Brand Color
5
+ primaryDark: '#B36605',
6
+ primaryLight: '#FF9800',
7
+ secondary: '#FF9800', // Brighter Orange for accents
8
+
9
+ // Backgrounds - Soft Cream & Glass
10
+ background: '#FFF9F0', // Cream Background
11
+ surface: '#FFFFFF', // Pure White
12
+ surfaceAlt: '#FFFBF5', // Soft Cream Surface
13
+
14
+ // Text
15
+ textPrimary: '#333333',
16
+ textSecondary: '#666666',
17
+ textTertiary: '#888888',
18
+ textAccent: '#E18308',
19
+ textDark: '#5D4037',
20
+ textLight: '#FFFFFF',
21
+ textMuted: '#8A9499',
22
+ textDisabled: '#BDBDBD',
23
+
24
+ // Borders
25
+ border: '#FFE0B2',
26
+ borderAlt: 'rgba(0, 0, 0, 0.08)',
27
+ inputBorder: '#E0E0E0',
28
+
29
+ // Functionals
30
+ success: '#4CAF50',
31
+ successLight: '#E8F5E9',
32
+ error: '#F44336',
33
+ errorLight: '#FFEBEE',
34
+ warning: '#FFC107',
35
+ warningLight: '#FFF8E1',
36
+ info: '#2196F3',
37
+ infoLight: '#E3F2FD',
38
+
39
+ // Special Effects (Glassmorphism)
40
+ shadow: '#E18308',
41
+ glassWhite: 'rgba(255, 255, 255, 0.9)',
42
+ glassOrange: 'rgba(255, 237, 213, 0.6)', // Orange 100 with opacity
43
+ glassBorder: 'rgba(255, 255, 255, 0.8)',
44
+ avatarBg: '#FFF3E0',
45
+
46
+ // Legacy/Utilities
47
+ white: '#FFFFFF',
48
+ black: '#000000',
49
+ inputBackground: '#FFFFFF',
50
+ cardBackground: '#FFFFFF',
51
+ transparent: 'transparent',
52
+ };
53
+
54
+ export const Spacing = {
55
+ xs: 4,
56
+ sm: 8,
57
+ md: 16,
58
+ lg: 24,
59
+ xl: 32,
60
+ xxl: 48,
61
+ };
62
+
63
+ export const BorderRadius = {
64
+ sm: 4,
65
+ md: 8,
66
+ lg: 12,
67
+ xl: 16,
68
+ xxl: 24,
69
+ full: 9999,
70
+ };
71
+
72
+ export const FontSize = {
73
+ xs: 10,
74
+ sm: 12,
75
+ md: 14,
76
+ lg: 16,
77
+ xl: 18,
78
+ xxl: 24,
79
+ xxxl: 32,
80
+ };
81
+
82
+ export const FontWeight = {
83
+ regular: '400' as const,
84
+ medium: '500' as const,
85
+ semibold: '600' as const,
86
+ bold: '700' as const,
87
+ };
88
+
89
+ export const Shadow = {
90
+ card: {
91
+ shadowColor: '#000',
92
+ shadowOffset: { width: 0, height: 2 },
93
+ shadowOpacity: 0.1,
94
+ shadowRadius: 8,
95
+ elevation: 4,
96
+ },
97
+ modal: {
98
+ shadowColor: '#000',
99
+ shadowOffset: { width: 0, height: 4 },
100
+ shadowOpacity: 0.15,
101
+ shadowRadius: 12,
102
+ elevation: 8,
103
+ },
104
+ };