@umituz/react-native-design-system 2.6.88 → 2.6.89
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/package.json +1 -1
- package/src/molecules/FormField.README.md +424 -273
- package/src/molecules/List/README.md +568 -0
- package/src/molecules/StepProgress/README.md +522 -0
- package/src/molecules/emoji/README.md +580 -0
- package/src/molecules/splash/types/index.ts +5 -8
- package/src/molecules/swipe-actions/README.md +672 -0
- package/src/theme/core/colors/DarkColors.ts +0 -3
- package/src/theme/core/colors/LightColors.ts +0 -3
- package/src/theme/core/themes.ts +1 -8
- package/src/theme/infrastructure/providers/DesignSystemProvider.tsx +1 -1
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
# FormField
|
|
2
2
|
|
|
3
|
-
FormField,
|
|
3
|
+
FormField is a molecule component that combines a label, input field, and validation messages into a complete form input unit. It wraps `AtomicInput` with `AtomicText` for labels and error/helper messages.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
7
|
+
- 📝 **Complete Form Input**: Label, input, and messages in one component
|
|
8
|
+
- ✅ **Validation**: Error and helper text support
|
|
9
|
+
- 🔴 **Required Indicator**: Visual required field marker
|
|
10
|
+
- 🎨 **Theme-Aware**: Design token integration
|
|
11
|
+
- ♿ **Accessible**: Full accessibility support
|
|
12
|
+
- 🎯 **Simple API**: Easy to use with minimal props
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Installation
|
|
15
15
|
|
|
16
16
|
```tsx
|
|
17
17
|
import { FormField } from 'react-native-design-system';
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Basic Usage
|
|
21
21
|
|
|
22
22
|
```tsx
|
|
23
23
|
import React, { useState } from 'react';
|
|
@@ -30,316 +30,516 @@ export const BasicExample = () => {
|
|
|
30
30
|
return (
|
|
31
31
|
<View style={{ padding: 16 }}>
|
|
32
32
|
<FormField
|
|
33
|
-
label="
|
|
33
|
+
label="Email"
|
|
34
34
|
value={email}
|
|
35
35
|
onChangeText={setEmail}
|
|
36
|
-
placeholder="
|
|
37
|
-
keyboardType="email-address"
|
|
36
|
+
placeholder="Enter your email"
|
|
38
37
|
/>
|
|
39
38
|
</View>
|
|
40
39
|
);
|
|
41
40
|
};
|
|
42
41
|
```
|
|
43
42
|
|
|
44
|
-
##
|
|
43
|
+
## Basic Input
|
|
45
44
|
|
|
46
45
|
```tsx
|
|
47
46
|
<FormField
|
|
48
|
-
label="
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
placeholder="Adınız ve soyadınız"
|
|
47
|
+
label="Username"
|
|
48
|
+
value={username}
|
|
49
|
+
onChangeText={setUsername}
|
|
50
|
+
placeholder="Enter username"
|
|
53
51
|
/>
|
|
54
52
|
```
|
|
55
53
|
|
|
56
|
-
##
|
|
54
|
+
## With Error
|
|
57
55
|
|
|
58
56
|
```tsx
|
|
59
57
|
<FormField
|
|
60
|
-
label="
|
|
58
|
+
label="Email"
|
|
61
59
|
value={email}
|
|
62
60
|
onChangeText={setEmail}
|
|
63
|
-
placeholder="
|
|
64
|
-
error="
|
|
65
|
-
state="error"
|
|
61
|
+
placeholder="Enter your email"
|
|
62
|
+
error="Please enter a valid email address"
|
|
66
63
|
/>
|
|
67
64
|
```
|
|
68
65
|
|
|
69
|
-
##
|
|
66
|
+
## Required Field
|
|
70
67
|
|
|
71
68
|
```tsx
|
|
72
69
|
<FormField
|
|
73
|
-
label="
|
|
70
|
+
label="Password"
|
|
74
71
|
value={password}
|
|
75
72
|
onChangeText={setPassword}
|
|
76
|
-
placeholder="
|
|
73
|
+
placeholder="Enter password"
|
|
77
74
|
secureTextEntry
|
|
78
|
-
|
|
75
|
+
required
|
|
79
76
|
/>
|
|
80
77
|
```
|
|
81
78
|
|
|
82
|
-
##
|
|
79
|
+
## Helper Text
|
|
83
80
|
|
|
84
81
|
```tsx
|
|
85
82
|
<FormField
|
|
86
|
-
label="
|
|
83
|
+
label="Username"
|
|
87
84
|
value={username}
|
|
88
85
|
onChangeText={setUsername}
|
|
89
|
-
placeholder="
|
|
90
|
-
|
|
86
|
+
placeholder="Choose a username"
|
|
87
|
+
helperText="Must be at least 3 characters long"
|
|
91
88
|
/>
|
|
92
89
|
```
|
|
93
90
|
|
|
94
|
-
##
|
|
91
|
+
## Custom Required Indicator
|
|
95
92
|
|
|
96
93
|
```tsx
|
|
97
94
|
<FormField
|
|
98
|
-
label="
|
|
99
|
-
value={
|
|
100
|
-
onChangeText={
|
|
101
|
-
placeholder="
|
|
102
|
-
secureTextEntry
|
|
103
|
-
showPasswordToggle
|
|
104
|
-
helperText="En az 8 karakter, 1 büyük harf ve 1 rakam"
|
|
95
|
+
label="Email"
|
|
96
|
+
value={email}
|
|
97
|
+
onChangeText={setEmail}
|
|
98
|
+
placeholder="your@email.com"
|
|
105
99
|
required
|
|
100
|
+
requiredIndicator=" (required)"
|
|
106
101
|
/>
|
|
107
102
|
```
|
|
108
103
|
|
|
109
|
-
##
|
|
104
|
+
## With Icons
|
|
110
105
|
|
|
111
106
|
```tsx
|
|
112
107
|
<FormField
|
|
113
|
-
label="
|
|
114
|
-
value={
|
|
115
|
-
onChangeText={
|
|
116
|
-
placeholder="
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
keyboardType="phone-pad"
|
|
108
|
+
label="Email"
|
|
109
|
+
value={email}
|
|
110
|
+
onChangeText={setEmail}
|
|
111
|
+
placeholder="your@email.com"
|
|
112
|
+
leftIcon="mail-outline"
|
|
113
|
+
rightIcon="checkmark-circle-outline"
|
|
120
114
|
/>
|
|
121
115
|
```
|
|
122
116
|
|
|
123
|
-
##
|
|
117
|
+
## Example Usages
|
|
124
118
|
|
|
125
|
-
###
|
|
119
|
+
### Login Form
|
|
126
120
|
|
|
127
121
|
```tsx
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
export const LoginForm = () => {
|
|
123
|
+
const [email, setEmail] = useState('');
|
|
124
|
+
const [password, setPassword] = useState('');
|
|
125
|
+
const [errors, setErrors] = useState({});
|
|
126
|
+
|
|
127
|
+
const validate = () => {
|
|
128
|
+
const newErrors = {};
|
|
129
|
+
|
|
130
|
+
if (!email) {
|
|
131
|
+
newErrors.email = 'Email is required';
|
|
132
|
+
} else if (!isValidEmail(email)) {
|
|
133
|
+
newErrors.email = 'Please enter a valid email';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!password) {
|
|
137
|
+
newErrors.password = 'Password is required';
|
|
138
|
+
} else if (password.length < 6) {
|
|
139
|
+
newErrors.password = 'Password must be at least 6 characters';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setErrors(newErrors);
|
|
143
|
+
return Object.keys(newErrors).length === 0;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const handleSubmit = () => {
|
|
147
|
+
if (validate()) {
|
|
148
|
+
login({ email, password });
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<View style={{ padding: 16 }}>
|
|
154
|
+
<FormField
|
|
155
|
+
label="Email"
|
|
156
|
+
value={email}
|
|
157
|
+
onChangeText={setEmail}
|
|
158
|
+
placeholder="your@email.com"
|
|
159
|
+
keyboardType="email-address"
|
|
160
|
+
autoCapitalize="none"
|
|
161
|
+
error={errors.email}
|
|
162
|
+
required
|
|
163
|
+
/>
|
|
164
|
+
|
|
165
|
+
<FormField
|
|
166
|
+
label="Password"
|
|
167
|
+
value={password}
|
|
168
|
+
onChangeText={setPassword}
|
|
169
|
+
placeholder="Enter password"
|
|
170
|
+
secureTextEntry
|
|
171
|
+
error={errors.password}
|
|
172
|
+
required
|
|
173
|
+
/>
|
|
174
|
+
|
|
175
|
+
<Button title="Login" onPress={handleSubmit} />
|
|
176
|
+
</View>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
```
|
|
131
180
|
|
|
132
|
-
|
|
181
|
+
### Registration Form
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
export const RegistrationForm = () => {
|
|
133
185
|
const [formData, setFormData] = useState({
|
|
134
|
-
|
|
186
|
+
firstName: '',
|
|
187
|
+
lastName: '',
|
|
135
188
|
email: '',
|
|
136
189
|
password: '',
|
|
137
190
|
confirmPassword: '',
|
|
138
191
|
});
|
|
192
|
+
|
|
139
193
|
const [errors, setErrors] = useState({});
|
|
140
194
|
|
|
141
|
-
const
|
|
142
|
-
setFormData(
|
|
143
|
-
// Clear error when user
|
|
195
|
+
const handleChange = (field, value) => {
|
|
196
|
+
setFormData({ ...formData, [field]: value });
|
|
197
|
+
// Clear error when user starts typing
|
|
144
198
|
if (errors[field]) {
|
|
145
|
-
setErrors(
|
|
199
|
+
setErrors({ ...errors, [field]: null });
|
|
146
200
|
}
|
|
147
201
|
};
|
|
148
202
|
|
|
149
203
|
const validate = () => {
|
|
150
204
|
const newErrors = {};
|
|
151
205
|
|
|
152
|
-
if (!formData.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (!formData.email) {
|
|
157
|
-
newErrors.email = 'E-posta zorunludur';
|
|
158
|
-
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
|
|
159
|
-
newErrors.email = 'Geçerli bir e-posta adresi girin';
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (!formData.password) {
|
|
163
|
-
newErrors.password = 'Şifre zorunludur';
|
|
164
|
-
} else if (formData.password.length < 8) {
|
|
165
|
-
newErrors.password = 'Şifre en az 8 karakter olmalıdır';
|
|
166
|
-
}
|
|
167
|
-
|
|
206
|
+
if (!formData.firstName) newErrors.firstName = 'First name is required';
|
|
207
|
+
if (!formData.lastName) newErrors.lastName = 'Last name is required';
|
|
208
|
+
if (!formData.email) newErrors.email = 'Email is required';
|
|
209
|
+
if (!formData.password) newErrors.password = 'Password is required';
|
|
168
210
|
if (formData.password !== formData.confirmPassword) {
|
|
169
|
-
newErrors.confirmPassword = '
|
|
211
|
+
newErrors.confirmPassword = 'Passwords do not match';
|
|
170
212
|
}
|
|
171
213
|
|
|
172
214
|
setErrors(newErrors);
|
|
173
215
|
return Object.keys(newErrors).length === 0;
|
|
174
216
|
};
|
|
175
217
|
|
|
176
|
-
const handleSubmit = () => {
|
|
177
|
-
if (validate()) {
|
|
178
|
-
console.log('Form submitted:', formData);
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
218
|
return (
|
|
183
219
|
<ScrollView style={{ padding: 16 }}>
|
|
184
220
|
<FormField
|
|
185
|
-
label="
|
|
186
|
-
value={formData.
|
|
187
|
-
onChangeText={
|
|
188
|
-
placeholder="
|
|
189
|
-
error={errors.
|
|
221
|
+
label="First Name"
|
|
222
|
+
value={formData.firstName}
|
|
223
|
+
onChangeText={(value) => handleChange('firstName', value)}
|
|
224
|
+
placeholder="John"
|
|
225
|
+
error={errors.firstName}
|
|
226
|
+
required
|
|
227
|
+
/>
|
|
228
|
+
|
|
229
|
+
<FormField
|
|
230
|
+
label="Last Name"
|
|
231
|
+
value={formData.lastName}
|
|
232
|
+
onChangeText={(value) => handleChange('lastName', value)}
|
|
233
|
+
placeholder="Doe"
|
|
234
|
+
error={errors.lastName}
|
|
190
235
|
required
|
|
191
236
|
/>
|
|
192
237
|
|
|
193
238
|
<FormField
|
|
194
|
-
label="
|
|
239
|
+
label="Email"
|
|
195
240
|
value={formData.email}
|
|
196
|
-
onChangeText={
|
|
197
|
-
placeholder="
|
|
241
|
+
onChangeText={(value) => handleChange('email', value)}
|
|
242
|
+
placeholder="john.doe@example.com"
|
|
198
243
|
keyboardType="email-address"
|
|
199
244
|
autoCapitalize="none"
|
|
200
245
|
error={errors.email}
|
|
201
|
-
helperText="Size ulaşmak için kullanacağız"
|
|
202
246
|
required
|
|
203
247
|
/>
|
|
204
248
|
|
|
205
249
|
<FormField
|
|
206
|
-
label="
|
|
250
|
+
label="Password"
|
|
207
251
|
value={formData.password}
|
|
208
|
-
onChangeText={
|
|
209
|
-
placeholder="
|
|
252
|
+
onChangeText={(value) => handleChange('password', value)}
|
|
253
|
+
placeholder="Create a password"
|
|
210
254
|
secureTextEntry
|
|
211
|
-
showPasswordToggle
|
|
212
255
|
error={errors.password}
|
|
213
|
-
helperText="
|
|
256
|
+
helperText="Must be at least 8 characters"
|
|
214
257
|
required
|
|
215
258
|
/>
|
|
216
259
|
|
|
217
260
|
<FormField
|
|
218
|
-
label="
|
|
261
|
+
label="Confirm Password"
|
|
219
262
|
value={formData.confirmPassword}
|
|
220
|
-
onChangeText={
|
|
221
|
-
placeholder="
|
|
263
|
+
onChangeText={(value) => handleChange('confirmPassword', value)}
|
|
264
|
+
placeholder="Confirm your password"
|
|
222
265
|
secureTextEntry
|
|
223
|
-
showPasswordToggle
|
|
224
266
|
error={errors.confirmPassword}
|
|
225
267
|
required
|
|
226
268
|
/>
|
|
227
269
|
|
|
228
|
-
<Button title="
|
|
270
|
+
<Button title="Create Account" onPress={validate} />
|
|
229
271
|
</ScrollView>
|
|
230
272
|
);
|
|
231
273
|
};
|
|
232
274
|
```
|
|
233
275
|
|
|
234
|
-
###
|
|
276
|
+
### Profile Settings
|
|
235
277
|
|
|
236
278
|
```tsx
|
|
237
|
-
export const
|
|
238
|
-
const [
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
// Login logic
|
|
248
|
-
};
|
|
279
|
+
export const ProfileSettings = () => {
|
|
280
|
+
const [profile, setProfile] = useState({
|
|
281
|
+
displayName: '',
|
|
282
|
+
username: '',
|
|
283
|
+
bio: '',
|
|
284
|
+
location: '',
|
|
285
|
+
website: '',
|
|
286
|
+
});
|
|
249
287
|
|
|
250
288
|
return (
|
|
251
|
-
<
|
|
289
|
+
<ScrollView style={{ padding: 16 }}>
|
|
252
290
|
<FormField
|
|
253
|
-
label="
|
|
254
|
-
value={
|
|
255
|
-
onChangeText={
|
|
256
|
-
placeholder="
|
|
257
|
-
|
|
258
|
-
autoCapitalize="none"
|
|
291
|
+
label="Display Name"
|
|
292
|
+
value={profile.displayName}
|
|
293
|
+
onChangeText={(value) => setProfile({ ...profile, displayName: value })}
|
|
294
|
+
placeholder="John Doe"
|
|
295
|
+
helperText="This is how you'll appear on your profile"
|
|
259
296
|
/>
|
|
260
297
|
|
|
261
298
|
<FormField
|
|
262
|
-
label="
|
|
263
|
-
value={
|
|
264
|
-
onChangeText={
|
|
265
|
-
placeholder="
|
|
266
|
-
|
|
267
|
-
|
|
299
|
+
label="Username"
|
|
300
|
+
value={profile.username}
|
|
301
|
+
onChangeText={(value) => setProfile({ ...profile, username: value })}
|
|
302
|
+
placeholder="johndoe"
|
|
303
|
+
helperText="https://example.com/username"
|
|
304
|
+
leftIcon="at-outline"
|
|
305
|
+
/>
|
|
306
|
+
|
|
307
|
+
<FormField
|
|
308
|
+
label="Bio"
|
|
309
|
+
value={profile.bio}
|
|
310
|
+
onChangeText={(value) => setProfile({ ...profile, bio: value })}
|
|
311
|
+
placeholder="Tell us about yourself"
|
|
312
|
+
multiline
|
|
313
|
+
numberOfLines={4}
|
|
314
|
+
helperText="Maximum 150 characters"
|
|
268
315
|
/>
|
|
269
316
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
317
|
+
<FormField
|
|
318
|
+
label="Location"
|
|
319
|
+
value={profile.location}
|
|
320
|
+
onChangeText={(value) => setProfile({ ...profile, location: value })}
|
|
321
|
+
placeholder="New York, NY"
|
|
322
|
+
leftIcon="location-outline"
|
|
323
|
+
/>
|
|
275
324
|
|
|
276
|
-
<
|
|
277
|
-
|
|
325
|
+
<FormField
|
|
326
|
+
label="Website"
|
|
327
|
+
value={profile.website}
|
|
328
|
+
onChangeText={(value) => setProfile({ ...profile, website: value })}
|
|
329
|
+
placeholder="https://yourwebsite.com"
|
|
330
|
+
keyboardType="url"
|
|
331
|
+
autoCapitalize="none"
|
|
332
|
+
leftIcon="link-outline"
|
|
333
|
+
/>
|
|
334
|
+
|
|
335
|
+
<Button title="Save Changes" onPress={handleSave} />
|
|
336
|
+
</ScrollView>
|
|
278
337
|
);
|
|
279
338
|
};
|
|
280
339
|
```
|
|
281
340
|
|
|
282
|
-
###
|
|
341
|
+
### Address Form
|
|
283
342
|
|
|
284
343
|
```tsx
|
|
285
|
-
export const
|
|
286
|
-
const [
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
344
|
+
export const AddressForm = () => {
|
|
345
|
+
const [address, setAddress] = useState({
|
|
346
|
+
street: '',
|
|
347
|
+
apartment: '',
|
|
348
|
+
city: '',
|
|
349
|
+
state: '',
|
|
350
|
+
zipCode: '',
|
|
351
|
+
country: '',
|
|
292
352
|
});
|
|
293
353
|
|
|
294
354
|
return (
|
|
295
|
-
<
|
|
355
|
+
<View style={{ padding: 16 }}>
|
|
296
356
|
<FormField
|
|
297
|
-
label="
|
|
298
|
-
value={
|
|
299
|
-
onChangeText={(
|
|
300
|
-
placeholder="
|
|
357
|
+
label="Street Address"
|
|
358
|
+
value={address.street}
|
|
359
|
+
onChangeText={(value) => setAddress({ ...address, street: value })}
|
|
360
|
+
placeholder="123 Main St"
|
|
301
361
|
required
|
|
302
362
|
/>
|
|
303
363
|
|
|
304
364
|
<FormField
|
|
305
|
-
label="
|
|
306
|
-
value={
|
|
307
|
-
onChangeText={(
|
|
308
|
-
placeholder="
|
|
365
|
+
label="Apartment/Suite (optional)"
|
|
366
|
+
value={address.apartment}
|
|
367
|
+
onChangeText={(value) => setAddress({ ...address, apartment: value })}
|
|
368
|
+
placeholder="Apt 4B"
|
|
369
|
+
/>
|
|
370
|
+
|
|
371
|
+
<FormField
|
|
372
|
+
label="City"
|
|
373
|
+
value={address.city}
|
|
374
|
+
onChangeText={(value) => setAddress({ ...address, city: value })}
|
|
375
|
+
placeholder="New York"
|
|
309
376
|
required
|
|
310
377
|
/>
|
|
311
378
|
|
|
379
|
+
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
380
|
+
<View style={{ flex: 1 }}>
|
|
381
|
+
<FormField
|
|
382
|
+
label="State"
|
|
383
|
+
value={address.state}
|
|
384
|
+
onChangeText={(value) => setAddress({ ...address, state: value })}
|
|
385
|
+
placeholder="NY"
|
|
386
|
+
required
|
|
387
|
+
/>
|
|
388
|
+
</View>
|
|
389
|
+
|
|
390
|
+
<View style={{ flex: 1 }}>
|
|
391
|
+
<FormField
|
|
392
|
+
label="ZIP Code"
|
|
393
|
+
value={address.zipCode}
|
|
394
|
+
onChangeText={(value) => setAddress({ ...address, zipCode: value })}
|
|
395
|
+
placeholder="10001"
|
|
396
|
+
keyboardType="number-pad"
|
|
397
|
+
required
|
|
398
|
+
/>
|
|
399
|
+
</View>
|
|
400
|
+
</View>
|
|
401
|
+
|
|
312
402
|
<FormField
|
|
313
|
-
label="
|
|
314
|
-
value={
|
|
315
|
-
onChangeText={(
|
|
316
|
-
placeholder="
|
|
317
|
-
keyboardType="email-address"
|
|
318
|
-
autoCapitalize="none"
|
|
319
|
-
leadingIcon="mail-outline"
|
|
403
|
+
label="Country"
|
|
404
|
+
value={address.country}
|
|
405
|
+
onChangeText={(value) => setAddress({ ...address, country: value })}
|
|
406
|
+
placeholder="United States"
|
|
320
407
|
required
|
|
321
408
|
/>
|
|
409
|
+
</View>
|
|
410
|
+
);
|
|
411
|
+
};
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Credit Card Form
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
export const CreditCardForm = () => {
|
|
418
|
+
const [card, setCard] = useState({
|
|
419
|
+
cardNumber: '',
|
|
420
|
+
cardHolder: '',
|
|
421
|
+
expiryDate: '',
|
|
422
|
+
cvv: '',
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const formatCardNumber = (text) => {
|
|
426
|
+
return text.replace(/\s/g, '').replace(/(.{4})/g, '$1 ').trim();
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const formatExpiry = (text) => {
|
|
430
|
+
if (text.length === 2 && !text.includes('/')) {
|
|
431
|
+
return text + '/';
|
|
432
|
+
}
|
|
433
|
+
return text;
|
|
434
|
+
};
|
|
322
435
|
|
|
436
|
+
return (
|
|
437
|
+
<View style={{ padding: 16 }}>
|
|
323
438
|
<FormField
|
|
324
|
-
label="
|
|
325
|
-
value={
|
|
326
|
-
onChangeText={(
|
|
327
|
-
placeholder="
|
|
328
|
-
keyboardType="
|
|
329
|
-
|
|
439
|
+
label="Card Number"
|
|
440
|
+
value={card.cardNumber}
|
|
441
|
+
onChangeText={(value) => setCard({ ...card, cardNumber: formatCardNumber(value) })}
|
|
442
|
+
placeholder="1234 5678 9012 3456"
|
|
443
|
+
keyboardType="number-pad"
|
|
444
|
+
maxLength={19}
|
|
445
|
+
leftIcon="card-outline"
|
|
446
|
+
required
|
|
330
447
|
/>
|
|
331
448
|
|
|
332
449
|
<FormField
|
|
333
|
-
label="
|
|
334
|
-
value={
|
|
335
|
-
onChangeText={(
|
|
336
|
-
placeholder="
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
maxLength={200}
|
|
340
|
-
showCharacterCount
|
|
450
|
+
label="Cardholder Name"
|
|
451
|
+
value={card.cardHolder}
|
|
452
|
+
onChangeText={(value) => setCard({ ...card, cardHolder: value })}
|
|
453
|
+
placeholder="JOHN DOE"
|
|
454
|
+
autoCapitalize="characters"
|
|
455
|
+
required
|
|
341
456
|
/>
|
|
342
|
-
|
|
457
|
+
|
|
458
|
+
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
459
|
+
<View style={{ flex: 1 }}>
|
|
460
|
+
<FormField
|
|
461
|
+
label="Expiry Date"
|
|
462
|
+
value={card.expiryDate}
|
|
463
|
+
onChangeText={(value) => setCard({ ...card, expiryDate: formatExpiry(value) })}
|
|
464
|
+
placeholder="MM/YY"
|
|
465
|
+
keyboardType="number-pad"
|
|
466
|
+
maxLength={5}
|
|
467
|
+
required
|
|
468
|
+
/>
|
|
469
|
+
</View>
|
|
470
|
+
|
|
471
|
+
<View style={{ flex: 1 }}>
|
|
472
|
+
<FormField
|
|
473
|
+
label="CVV"
|
|
474
|
+
value={card.cvv}
|
|
475
|
+
onChangeText={(value) => setCard({ ...card, cvv: value })}
|
|
476
|
+
placeholder="123"
|
|
477
|
+
keyboardType="number-pad"
|
|
478
|
+
maxLength={4}
|
|
479
|
+
secureTextEntry
|
|
480
|
+
helperText="3 or 4 digits on back of card"
|
|
481
|
+
required
|
|
482
|
+
/>
|
|
483
|
+
</View>
|
|
484
|
+
</View>
|
|
485
|
+
</View>
|
|
486
|
+
);
|
|
487
|
+
};
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Search Form
|
|
491
|
+
|
|
492
|
+
```tsx
|
|
493
|
+
export const SearchForm = () => {
|
|
494
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
495
|
+
const [filters, setFilters] = useState({
|
|
496
|
+
category: '',
|
|
497
|
+
minPrice: '',
|
|
498
|
+
maxPrice: '',
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
return (
|
|
502
|
+
<View style={{ padding: 16 }}>
|
|
503
|
+
<FormField
|
|
504
|
+
label="Search"
|
|
505
|
+
value={searchTerm}
|
|
506
|
+
onChangeText={setSearchTerm}
|
|
507
|
+
placeholder="What are you looking for?"
|
|
508
|
+
leftIcon="search-outline"
|
|
509
|
+
/>
|
|
510
|
+
|
|
511
|
+
<FormField
|
|
512
|
+
label="Category"
|
|
513
|
+
value={filters.category}
|
|
514
|
+
onChangeText={(value) => setFilters({ ...filters, category: value })}
|
|
515
|
+
placeholder="Select a category"
|
|
516
|
+
rightIcon="chevron-down-outline"
|
|
517
|
+
/>
|
|
518
|
+
|
|
519
|
+
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
520
|
+
<View style={{ flex: 1 }}>
|
|
521
|
+
<FormField
|
|
522
|
+
label="Min Price"
|
|
523
|
+
value={filters.minPrice}
|
|
524
|
+
onChangeText={(value) => setFilters({ ...filters, minPrice: value })}
|
|
525
|
+
placeholder="$0"
|
|
526
|
+
keyboardType="number-pad"
|
|
527
|
+
/>
|
|
528
|
+
</View>
|
|
529
|
+
|
|
530
|
+
<View style={{ flex: 1 }}>
|
|
531
|
+
<FormField
|
|
532
|
+
label="Max Price"
|
|
533
|
+
value={filters.maxPrice}
|
|
534
|
+
onChangeText={(value) => setFilters({ ...filters, maxPrice: value })}
|
|
535
|
+
placeholder="$1000"
|
|
536
|
+
keyboardType="number-pad"
|
|
537
|
+
/>
|
|
538
|
+
</View>
|
|
539
|
+
</View>
|
|
540
|
+
|
|
541
|
+
<Button title="Search" onPress={handleSearch} />
|
|
542
|
+
</View>
|
|
343
543
|
);
|
|
344
544
|
};
|
|
345
545
|
```
|
|
@@ -348,139 +548,90 @@ export const ProfileForm = () => {
|
|
|
348
548
|
|
|
349
549
|
### FormFieldProps
|
|
350
550
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
| Prop | Tip | Varsayılan | Açıklama |
|
|
354
|
-
|------|-----|------------|----------|
|
|
355
|
-
| `label` | `string` | - | Alan etiketi |
|
|
356
|
-
| `error` | `string` | - | Hata mesajı |
|
|
357
|
-
| `helperText` | `string` | - | Yardımcı metin |
|
|
358
|
-
| `required` | `boolean` | `false` | Zorunlu alan |
|
|
359
|
-
| `requiredIndicator` | `string` | `' *'` | Zorunlu alan işareti |
|
|
360
|
-
| `containerStyle` | `ViewStyle` | - | Container stili |
|
|
361
|
-
| `style` | `ViewStyle` | - | Container stili (alias) |
|
|
551
|
+
Extends `Omit<AtomicInputProps, 'state' | 'label'>`
|
|
362
552
|
|
|
363
|
-
|
|
553
|
+
| Prop | Type | Default | Description |
|
|
554
|
+
|------|------|---------|-------------|
|
|
555
|
+
| `label` | `string` | - | Field label |
|
|
556
|
+
| `error` | `string` | - | Error message |
|
|
557
|
+
| `helperText` | `string` | - | Helper text |
|
|
558
|
+
| `required` | `boolean` | `false` | Show required indicator |
|
|
559
|
+
| `containerStyle` | `ViewStyle` | - | Container style |
|
|
560
|
+
| `style` | `ViewStyle` | - | Alias for containerStyle |
|
|
561
|
+
| `requiredIndicator` | `string` | `' *'` | Required indicator text |
|
|
364
562
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
label="Özel Alan"
|
|
368
|
-
value={value}
|
|
369
|
-
onChangeText={setValue}
|
|
370
|
-
containerStyle={{
|
|
371
|
-
marginBottom: 24,
|
|
372
|
-
backgroundColor: '#f9fafb',
|
|
373
|
-
padding: 16,
|
|
374
|
-
borderRadius: 8,
|
|
375
|
-
}}
|
|
376
|
-
/>
|
|
377
|
-
```
|
|
563
|
+
Plus all AtomicInput props:
|
|
564
|
+
- `value`, `onChangeText`, `placeholder`, `secureTextEntry`, `keyboardType`, etc.
|
|
378
565
|
|
|
379
566
|
## Best Practices
|
|
380
567
|
|
|
381
568
|
### 1. Error Handling
|
|
382
569
|
|
|
383
570
|
```tsx
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const validate = () => {
|
|
389
|
-
const newErrors = {};
|
|
390
|
-
if (!email) newErrors.email = 'Bu alan zorunludur';
|
|
391
|
-
setErrors(newErrors);
|
|
392
|
-
return Object.keys(newErrors).length === 0;
|
|
393
|
-
};
|
|
571
|
+
// ✅ Good: Clear specific errors
|
|
572
|
+
<FormField
|
|
573
|
+
error="Email must be in format: user@example.com"
|
|
574
|
+
/>
|
|
394
575
|
|
|
395
|
-
//
|
|
576
|
+
// ❌ Bad: Vague errors
|
|
396
577
|
<FormField
|
|
397
|
-
error=
|
|
398
|
-
// ...
|
|
578
|
+
error="Invalid"
|
|
399
579
|
/>
|
|
400
580
|
```
|
|
401
581
|
|
|
402
|
-
### 2. Helper Text
|
|
582
|
+
### 2. Helper Text
|
|
403
583
|
|
|
404
584
|
```tsx
|
|
405
|
-
//
|
|
585
|
+
// ✅ Good: Helpful guidance
|
|
586
|
+
<FormField
|
|
587
|
+
helperText="Must be at least 8 characters with 1 number"
|
|
588
|
+
/>
|
|
589
|
+
|
|
590
|
+
// ❌ Bad: Obvious info
|
|
406
591
|
<FormField
|
|
407
|
-
|
|
408
|
-
helperText="En az 8 karakter, 1 büyük harf ve 1 rakam içermelidir"
|
|
409
|
-
// ...
|
|
592
|
+
helperText="Enter text here"
|
|
410
593
|
/>
|
|
411
594
|
```
|
|
412
595
|
|
|
413
596
|
### 3. Required Fields
|
|
414
597
|
|
|
415
598
|
```tsx
|
|
416
|
-
//
|
|
599
|
+
// ✅ Good: Use sparingly
|
|
417
600
|
<FormField
|
|
418
|
-
label="
|
|
601
|
+
label="Email"
|
|
419
602
|
required
|
|
420
|
-
// ...
|
|
421
603
|
/>
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
## Erişilebilirlik
|
|
425
|
-
|
|
426
|
-
FormField, tam erişilebilirlik desteği sunar:
|
|
427
|
-
|
|
428
|
-
- ✅ Label ilişkilendirmesi
|
|
429
|
-
- ✅ Error state anonsu
|
|
430
|
-
- ✅ Required field göstergesi
|
|
431
|
-
- ✅ Screen reader desteği
|
|
432
|
-
|
|
433
|
-
## Form Validasyon Örneği
|
|
434
|
-
|
|
435
|
-
```tsx
|
|
436
|
-
const useFormValidation = (schema) => {
|
|
437
|
-
const [errors, setErrors] = useState({});
|
|
438
604
|
|
|
439
|
-
|
|
440
|
-
|
|
605
|
+
// ❌ Bad: Overuse
|
|
606
|
+
<FormField
|
|
607
|
+
label="Optional Field"
|
|
608
|
+
required
|
|
609
|
+
/>
|
|
610
|
+
```
|
|
441
611
|
|
|
442
|
-
|
|
443
|
-
const rules = schema[key];
|
|
444
|
-
const value = data[key];
|
|
612
|
+
## Accessibility
|
|
445
613
|
|
|
446
|
-
|
|
447
|
-
newErrors[key] = `${rules.label} zorunludur`;
|
|
448
|
-
} else if (rules.pattern && !rules.pattern.test(value)) {
|
|
449
|
-
newErrors[key] = rules.message || 'Geçersiz değer';
|
|
450
|
-
} else if (rules.minLength && value.length < rules.minLength) {
|
|
451
|
-
newErrors[key] = `${rules.label} en az ${rules.minLength} karakter olmalıdır`;
|
|
452
|
-
}
|
|
453
|
-
});
|
|
614
|
+
FormField provides full accessibility support:
|
|
454
615
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
616
|
+
- ✅ Screen reader labels
|
|
617
|
+
- ✅ Error announcements
|
|
618
|
+
- ✅ Required field indicators
|
|
619
|
+
- ✅ Helper text
|
|
620
|
+
- ✅ Auto-focus handling
|
|
458
621
|
|
|
459
|
-
|
|
460
|
-
};
|
|
622
|
+
## Performance Tips
|
|
461
623
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
required: true,
|
|
466
|
-
label: 'E-posta',
|
|
467
|
-
pattern: /\S+@\S+\.\S+/,
|
|
468
|
-
message: 'Geçerli bir e-posta adresi girin',
|
|
469
|
-
},
|
|
470
|
-
password: {
|
|
471
|
-
required: true,
|
|
472
|
-
label: 'Şifre',
|
|
473
|
-
minLength: 8,
|
|
474
|
-
},
|
|
475
|
-
});
|
|
476
|
-
```
|
|
624
|
+
1. **Controlled Inputs**: Always use controlled inputs with state
|
|
625
|
+
2. **Validation Debounce**: Debounce validation for better UX
|
|
626
|
+
3. **Memoization**: Memo validation functions
|
|
477
627
|
|
|
478
|
-
##
|
|
628
|
+
## Related Components
|
|
479
629
|
|
|
480
|
-
- [`AtomicInput`](../atoms/input/README.md) -
|
|
481
|
-
- [`
|
|
482
|
-
- [`
|
|
630
|
+
- [`AtomicInput`](../atoms/input/README.md) - Base input component
|
|
631
|
+
- [`AtomicText`](../atoms/AtomicText/README.md) - Text component
|
|
632
|
+
- [`Button`](../atoms/button/README.md) - Button component
|
|
633
|
+
- [`AlertInline`](../alerts/README.md) - Inline alert component
|
|
483
634
|
|
|
484
|
-
##
|
|
635
|
+
## License
|
|
485
636
|
|
|
486
637
|
MIT
|