@umituz/react-native-ai-generation-content 1.61.39 → 1.61.40
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/domains/creations/infrastructure/repositories/CreationsWriter.ts +16 -213
- package/src/domains/creations/infrastructure/repositories/creations-operations.ts +128 -0
- package/src/domains/creations/infrastructure/repositories/creations-state-operations.ts +86 -0
- package/src/domains/creations/presentation/components/GalleryScreenHeader.tsx +54 -0
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +23 -56
- package/src/domains/creations/presentation/utils/filter-buttons.util.ts +75 -0
- package/src/domains/generation/wizard/presentation/screens/GeneratingScreen.tsx +36 -43
- package/src/domains/generation/wizard/presentation/screens/TextInputScreen.tsx +6 -39
- package/src/domains/scenarios/presentation/screens/ScenarioPreviewScreen.tsx +7 -25
- package/src/features/image-to-video/presentation/screens/ImageToVideoWizardFlow.tsx +26 -46
- package/src/features/shared/index.ts +6 -0
- package/src/features/shared/presentation/components/AutoSkipPreview.tsx +24 -0
- package/src/features/shared/presentation/components/index.ts +6 -0
- package/src/features/shared/presentation/utils/index.ts +14 -0
- package/src/features/shared/presentation/utils/wizard-flow.utils.ts +84 -0
- package/src/features/text-to-image/presentation/screens/TextToImageWizardFlow.tsx +26 -46
- package/src/features/text-to-video/presentation/screens/TextToVideoWizardFlow.tsx +26 -46
- package/src/infrastructure/logging/debug.util.ts +1 -1
- package/src/infrastructure/logging/index.ts +1 -1
- package/src/infrastructure/validation/advanced-validator.ts +97 -0
- package/src/infrastructure/validation/ai-validator.ts +77 -0
- package/src/infrastructure/validation/base-validator.ts +149 -0
- package/src/infrastructure/validation/entity-validator.ts +64 -0
- package/src/infrastructure/validation/input-validator.ts +37 -409
- package/src/infrastructure/validation/sanitizer.ts +43 -0
- package/src/presentation/components/buttons/ContinueButton.tsx +72 -0
- package/src/presentation/components/buttons/index.ts +1 -0
|
@@ -1,411 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Input Validation Utilities
|
|
3
|
-
*
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Sanitizes user input to prevent XSS and injection attacks
|
|
43
|
-
*/
|
|
44
|
-
export function sanitizeString(input: unknown): string {
|
|
45
|
-
if (typeof input !== "string") {
|
|
46
|
-
return "";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return input
|
|
50
|
-
.trim()
|
|
51
|
-
.replace(/[<>]/g, "") // Remove potential HTML tags
|
|
52
|
-
.replace(/javascript:/gi, "") // Remove javascript: protocol
|
|
53
|
-
.replace(/data:/gi, "") // Remove data: protocol
|
|
54
|
-
.replace(/vbscript:/gi, "") // Remove vbscript: protocol
|
|
55
|
-
.replace(/on\w+\s*=/gi, "") // Remove event handlers
|
|
56
|
-
.replace(/--/g, "") // Remove SQL comment sequences
|
|
57
|
-
.replace(/;\s*drop\s+/gi, "") // Remove SQL injection attempts
|
|
58
|
-
.replace(/['"\\]/g, "") // Remove quotes and backslashes
|
|
59
|
-
.slice(0, 10000); // Limit length
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Validates a string input against provided rules
|
|
64
|
-
*/
|
|
65
|
-
export function validateString(
|
|
66
|
-
input: unknown,
|
|
67
|
-
options: StringValidationOptions = {}
|
|
68
|
-
): ValidationResult {
|
|
69
|
-
const errors: string[] = [];
|
|
70
|
-
|
|
71
|
-
// Check if input is a string
|
|
72
|
-
if (typeof input !== "string") {
|
|
73
|
-
return {
|
|
74
|
-
isValid: false,
|
|
75
|
-
errors: ["Input must be a string"],
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let value = options.trim !== false ? input.trim() : input;
|
|
80
|
-
|
|
81
|
-
// Check min length
|
|
82
|
-
if (options.minLength !== undefined && value.length < options.minLength) {
|
|
83
|
-
errors.push(`Input must be at least ${options.minLength} characters`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Check max length
|
|
87
|
-
if (options.maxLength !== undefined && value.length > options.maxLength) {
|
|
88
|
-
errors.push(`Input must be at most ${options.maxLength} characters`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Check pattern
|
|
92
|
-
if (options.pattern && !options.pattern.test(value)) {
|
|
93
|
-
errors.push("Input format is invalid");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Check allowed characters
|
|
97
|
-
if (options.allowedCharacters && !options.allowedCharacters.test(value)) {
|
|
98
|
-
errors.push("Input contains invalid characters");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
isValid: errors.length === 0,
|
|
103
|
-
errors,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Validates a numeric input
|
|
109
|
-
*/
|
|
110
|
-
export function validateNumber(
|
|
111
|
-
input: unknown,
|
|
112
|
-
options: NumericValidationOptions = {}
|
|
113
|
-
): ValidationResult {
|
|
114
|
-
const errors: string[] = [];
|
|
115
|
-
|
|
116
|
-
// Check if input is a number
|
|
117
|
-
if (typeof input !== "number" || isNaN(input)) {
|
|
118
|
-
return {
|
|
119
|
-
isValid: false,
|
|
120
|
-
errors: ["Input must be a number"],
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Check if integer
|
|
125
|
-
if (options.integer && !Number.isInteger(input)) {
|
|
126
|
-
errors.push("Input must be an integer");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Check min value
|
|
130
|
-
if (options.min !== undefined && input < options.min) {
|
|
131
|
-
errors.push(`Input must be at least ${options.min}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Check max value
|
|
135
|
-
if (options.max !== undefined && input > options.max) {
|
|
136
|
-
errors.push(`Input must be at most ${options.max}`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
isValid: errors.length === 0,
|
|
141
|
-
errors,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Validates URL format
|
|
147
|
-
*/
|
|
148
|
-
export function validateURL(input: unknown): ValidationResult {
|
|
149
|
-
if (typeof input !== "string") {
|
|
150
|
-
return {
|
|
151
|
-
isValid: false,
|
|
152
|
-
errors: ["URL must be a string"],
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
const url = new URL(input);
|
|
158
|
-
|
|
159
|
-
// Only allow http and https protocols
|
|
160
|
-
if (!["http:", "https:"].includes(url.protocol)) {
|
|
161
|
-
return {
|
|
162
|
-
isValid: false,
|
|
163
|
-
errors: ["Only HTTP and HTTPS protocols are allowed"],
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return { isValid: true, errors: [] };
|
|
168
|
-
} catch {
|
|
169
|
-
return {
|
|
170
|
-
isValid: false,
|
|
171
|
-
errors: ["Invalid URL format"],
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Validates email format
|
|
178
|
-
*/
|
|
179
|
-
export function validateEmail(input: unknown): ValidationResult {
|
|
180
|
-
if (typeof input !== "string") {
|
|
181
|
-
return {
|
|
182
|
-
isValid: false,
|
|
183
|
-
errors: ["Email must be a string"],
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Basic email validation regex
|
|
188
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
189
|
-
|
|
190
|
-
if (!emailRegex.test(input)) {
|
|
191
|
-
return {
|
|
192
|
-
isValid: false,
|
|
193
|
-
errors: ["Invalid email format"],
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return { isValid: true, errors: [] };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Validates base64 string
|
|
202
|
-
*/
|
|
203
|
-
export function validateBase64(input: unknown): ValidationResult {
|
|
204
|
-
if (typeof input !== "string") {
|
|
205
|
-
return {
|
|
206
|
-
isValid: false,
|
|
207
|
-
errors: ["Input must be a string"],
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Check if it's a valid base64 string
|
|
212
|
-
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
213
|
-
|
|
214
|
-
if (!base64Regex.test(input)) {
|
|
215
|
-
return {
|
|
216
|
-
isValid: false,
|
|
217
|
-
errors: ["Invalid base64 format"],
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Check if length is valid (must be multiple of 4)
|
|
222
|
-
if (input.length % 4 !== 0) {
|
|
223
|
-
return {
|
|
224
|
-
isValid: false,
|
|
225
|
-
errors: ["Base64 string length must be a multiple of 4"],
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return { isValid: true, errors: [] };
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Validates object structure
|
|
234
|
-
*/
|
|
235
|
-
export function validateObject(
|
|
236
|
-
input: unknown,
|
|
237
|
-
requiredFields: readonly string[] = []
|
|
238
|
-
): ValidationResult {
|
|
239
|
-
const errors: string[] = [];
|
|
240
|
-
|
|
241
|
-
if (typeof input !== "object" || input === null) {
|
|
242
|
-
return {
|
|
243
|
-
isValid: false,
|
|
244
|
-
errors: ["Input must be an object"],
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Check required fields
|
|
249
|
-
for (const field of requiredFields) {
|
|
250
|
-
if (!(field in input)) {
|
|
251
|
-
errors.push(`Missing required field: ${field}`);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return {
|
|
256
|
-
isValid: errors.length === 0,
|
|
257
|
-
errors,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Validates array input
|
|
263
|
-
*/
|
|
264
|
-
export function validateArray(
|
|
265
|
-
input: unknown,
|
|
266
|
-
options: {
|
|
267
|
-
readonly minLength?: number;
|
|
268
|
-
readonly maxLength?: number;
|
|
269
|
-
readonly itemType?: "string" | "number" | "object";
|
|
270
|
-
} = {}
|
|
271
|
-
): ValidationResult {
|
|
272
|
-
const errors: string[] = [];
|
|
273
|
-
|
|
274
|
-
if (!Array.isArray(input)) {
|
|
275
|
-
return {
|
|
276
|
-
isValid: false,
|
|
277
|
-
errors: ["Input must be an array"],
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Check min length
|
|
282
|
-
if (options.minLength !== undefined && input.length < options.minLength) {
|
|
283
|
-
errors.push(`Array must have at least ${options.minLength} items`);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Check max length
|
|
287
|
-
if (options.maxLength !== undefined && input.length > options.maxLength) {
|
|
288
|
-
errors.push(`Array must have at most ${options.maxLength} items`);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Check item types
|
|
292
|
-
if (options.itemType) {
|
|
293
|
-
for (let i = 0; i < input.length; i++) {
|
|
294
|
-
const item = input[i];
|
|
295
|
-
const isValidType =
|
|
296
|
-
options.itemType === "string"
|
|
297
|
-
? typeof item === "string"
|
|
298
|
-
: options.itemType === "number"
|
|
299
|
-
? typeof item === "number"
|
|
300
|
-
: typeof item === "object" && item !== null;
|
|
301
|
-
|
|
302
|
-
if (!isValidType) {
|
|
303
|
-
errors.push(`Item at index ${i} is not a ${options.itemType}`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
isValid: errors.length === 0,
|
|
310
|
-
errors,
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Combines multiple validation results
|
|
316
|
-
*/
|
|
317
|
-
export function combineValidationResults(
|
|
318
|
-
results: readonly ValidationResult[]
|
|
319
|
-
): ValidationResult {
|
|
320
|
-
const allErrors = results.flatMap((r) => r.errors);
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
isValid: allErrors.length === 0,
|
|
324
|
-
errors: allErrors,
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Sanitizes and validates user input in one step
|
|
330
|
-
*/
|
|
331
|
-
export function sanitizeAndValidate(
|
|
332
|
-
input: unknown,
|
|
333
|
-
options: StringValidationOptions = {}
|
|
334
|
-
): { readonly sanitized: string; readonly validation: ValidationResult } {
|
|
335
|
-
const sanitized = sanitizeString(input);
|
|
336
|
-
const validation = validateString(sanitized, options);
|
|
337
|
-
|
|
338
|
-
return { sanitized, validation };
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Validates prompt/input text for AI generation
|
|
343
|
-
*/
|
|
344
|
-
export function validateAIPrompt(input: unknown): ValidationResult {
|
|
345
|
-
const options: StringValidationOptions = {
|
|
346
|
-
minLength: MIN_PROMPT_LENGTH,
|
|
347
|
-
maxLength: MAX_PROMPT_LENGTH,
|
|
348
|
-
trim: true,
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
return validateString(input, options);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Validates image data (base64 or URL)
|
|
356
|
-
*/
|
|
357
|
-
export function validateImageData(input: unknown): ValidationResult {
|
|
358
|
-
if (typeof input !== "string") {
|
|
359
|
-
return {
|
|
360
|
-
isValid: false,
|
|
361
|
-
errors: ["Image data must be a string"],
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Check if it's a URL
|
|
366
|
-
if (input.startsWith("http://") || input.startsWith("https://")) {
|
|
367
|
-
return validateURL(input);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Check if it's base64
|
|
371
|
-
if (input.startsWith("data:image/")) {
|
|
372
|
-
const base64Part = input.split(",")[1];
|
|
373
|
-
if (!base64Part) {
|
|
374
|
-
return {
|
|
375
|
-
isValid: false,
|
|
376
|
-
errors: ["Invalid data URI format"],
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
return validateBase64(base64Part);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
return {
|
|
383
|
-
isValid: false,
|
|
384
|
-
errors: ["Image data must be a URL or base64 data URI"],
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Validates user ID
|
|
390
|
-
*/
|
|
391
|
-
export function validateUserId(input: unknown): ValidationResult {
|
|
392
|
-
const options: StringValidationOptions = {
|
|
393
|
-
minLength: 1,
|
|
394
|
-
maxLength: MAX_USER_ID_LENGTH,
|
|
395
|
-
pattern: /^[a-zA-Z0-9_-]+$/,
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
return validateString(input, options);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Validates creation ID
|
|
403
|
-
*/
|
|
404
|
-
export function validateCreationId(input: unknown): ValidationResult {
|
|
405
|
-
const options: StringValidationOptions = {
|
|
406
|
-
minLength: 1,
|
|
407
|
-
maxLength: MAX_CREATION_ID_LENGTH,
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
return validateString(input, options);
|
|
411
|
-
}
|
|
3
|
+
* Main export module for all validation functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Base validators
|
|
7
|
+
export {
|
|
8
|
+
validateString,
|
|
9
|
+
validateNumber,
|
|
10
|
+
validateURL,
|
|
11
|
+
validateEmail,
|
|
12
|
+
validateBase64,
|
|
13
|
+
type ValidationResult,
|
|
14
|
+
type StringValidationOptions,
|
|
15
|
+
type NumericValidationOptions,
|
|
16
|
+
} from "./base-validator";
|
|
17
|
+
|
|
18
|
+
// Sanitizers
|
|
19
|
+
export { sanitizeString, sanitizeObject } from "./sanitizer";
|
|
20
|
+
|
|
21
|
+
// Advanced validators
|
|
22
|
+
export {
|
|
23
|
+
validateObject,
|
|
24
|
+
validateArray,
|
|
25
|
+
combineValidationResults,
|
|
26
|
+
sanitizeAndValidate,
|
|
27
|
+
} from "./advanced-validator";
|
|
28
|
+
|
|
29
|
+
// AI validators
|
|
30
|
+
export { validateAIPrompt, validateImageData, validateVideoUrl } from "./ai-validator";
|
|
31
|
+
|
|
32
|
+
// Entity validators
|
|
33
|
+
export {
|
|
34
|
+
validateUserId,
|
|
35
|
+
validateCreationId,
|
|
36
|
+
validateScenarioId,
|
|
37
|
+
validateModelName,
|
|
38
|
+
validateProviderName,
|
|
39
|
+
} from "./entity-validator";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Sanitization Utilities
|
|
3
|
+
* Sanitizes user input to prevent XSS and injection attacks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sanitizes user input to prevent XSS and injection attacks
|
|
8
|
+
*/
|
|
9
|
+
export function sanitizeString(input: unknown): string {
|
|
10
|
+
if (typeof input !== "string") {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return input
|
|
15
|
+
.trim()
|
|
16
|
+
.replace(/[<>]/g, "")
|
|
17
|
+
.replace(/javascript:/gi, "")
|
|
18
|
+
.replace(/data:/gi, "")
|
|
19
|
+
.replace(/vbscript:/gi, "")
|
|
20
|
+
.replace(/on\w+\s*=/gi, "")
|
|
21
|
+
.replace(/--/g, "")
|
|
22
|
+
.replace(/;\s*drop\s+/gi, "")
|
|
23
|
+
.replace(/['"\\]/g, "")
|
|
24
|
+
.slice(0, 10000);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sanitizes object by removing dangerous properties
|
|
29
|
+
*/
|
|
30
|
+
export function sanitizeObject<T extends Record<string, unknown>>(input: T): T {
|
|
31
|
+
if (!input || typeof input !== "object") {
|
|
32
|
+
return input;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const sanitized = { ...input } as T;
|
|
36
|
+
const dangerousKeys = ["__proto__", "constructor", "prototype"];
|
|
37
|
+
|
|
38
|
+
for (const key of dangerousKeys) {
|
|
39
|
+
delete (sanitized as Record<string, unknown>)[key];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return sanitized;
|
|
43
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContinueButton Component
|
|
3
|
+
* Reusable continue button for wizard flows and forms
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
|
+
import { TouchableOpacity, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens, AtomicText, AtomicIcon } from "@umituz/react-native-design-system";
|
|
9
|
+
|
|
10
|
+
export interface ContinueButtonProps {
|
|
11
|
+
readonly label: string;
|
|
12
|
+
readonly canContinue: boolean;
|
|
13
|
+
readonly onPress: () => void;
|
|
14
|
+
readonly iconName?: string;
|
|
15
|
+
readonly disabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const ContinueButton: React.FC<ContinueButtonProps> = ({
|
|
19
|
+
label,
|
|
20
|
+
canContinue,
|
|
21
|
+
onPress,
|
|
22
|
+
iconName = "arrow-forward",
|
|
23
|
+
disabled = false,
|
|
24
|
+
}) => {
|
|
25
|
+
const tokens = useAppDesignTokens();
|
|
26
|
+
const isEnabled = canContinue && !disabled;
|
|
27
|
+
|
|
28
|
+
const buttonStyle = useMemo(() => [
|
|
29
|
+
styles.button,
|
|
30
|
+
{
|
|
31
|
+
backgroundColor: isEnabled ? tokens.colors.primary : tokens.colors.surfaceVariant,
|
|
32
|
+
opacity: isEnabled ? 1 : 0.5,
|
|
33
|
+
},
|
|
34
|
+
], [isEnabled, tokens.colors.primary, tokens.colors.surfaceVariant]);
|
|
35
|
+
|
|
36
|
+
const textStyle = useMemo(() => [
|
|
37
|
+
styles.text,
|
|
38
|
+
{ color: isEnabled ? tokens.colors.onPrimary : tokens.colors.textSecondary },
|
|
39
|
+
], [isEnabled, tokens.colors.onPrimary, tokens.colors.textSecondary]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<TouchableOpacity
|
|
43
|
+
onPress={onPress}
|
|
44
|
+
activeOpacity={0.7}
|
|
45
|
+
disabled={!isEnabled}
|
|
46
|
+
style={buttonStyle}
|
|
47
|
+
>
|
|
48
|
+
<AtomicText type="bodyMedium" style={textStyle}>
|
|
49
|
+
{label}
|
|
50
|
+
</AtomicText>
|
|
51
|
+
<AtomicIcon
|
|
52
|
+
name={iconName}
|
|
53
|
+
size="sm"
|
|
54
|
+
color={isEnabled ? "onPrimary" : "textSecondary"}
|
|
55
|
+
/>
|
|
56
|
+
</TouchableOpacity>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const styles = StyleSheet.create({
|
|
61
|
+
button: {
|
|
62
|
+
flexDirection: "row",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
paddingHorizontal: 16,
|
|
65
|
+
paddingVertical: 8,
|
|
66
|
+
borderRadius: 999,
|
|
67
|
+
},
|
|
68
|
+
text: {
|
|
69
|
+
fontWeight: "800",
|
|
70
|
+
marginRight: 4,
|
|
71
|
+
},
|
|
72
|
+
});
|