funuicss 3.8.9 → 3.8.11
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/css/fun.css +0 -1
- package/package.json +1 -1
- package/ui/carousel/Carousel.d.ts +3 -0
- package/ui/carousel/Carousel.js +140 -16
- package/ui/feature/Feature.d.ts +17 -1
- package/ui/feature/Feature.js +25 -1
- package/ui/form/Form.d.ts +2 -2
- package/ui/form/Form.js +1140 -202
- package/ui/input/Input.js +23 -988
- package/ui/notification/Notification.js +6 -2
package/ui/form/Form.js
CHANGED
|
@@ -100,7 +100,6 @@ var Flex_1 = __importDefault(require("../flex/Flex"));
|
|
|
100
100
|
var Text_1 = __importDefault(require("../text/Text"));
|
|
101
101
|
var pi_1 = require("react-icons/pi");
|
|
102
102
|
var componentUtils_1 = require("../../utils/componentUtils");
|
|
103
|
-
var theme_1 = require("../theme/theme");
|
|
104
103
|
// Helper function to parse JSON input
|
|
105
104
|
var parseJsonInput = function (input, defaultValue) {
|
|
106
105
|
if (input === undefined || input === null) {
|
|
@@ -225,7 +224,7 @@ var FormRadio = function (_a) {
|
|
|
225
224
|
label,
|
|
226
225
|
required && ' *'))));
|
|
227
226
|
};
|
|
228
|
-
// Function to format WhatsApp message
|
|
227
|
+
// Function to format WhatsApp message - UPDATED with proper formatting
|
|
229
228
|
var formatWhatsAppMessage = function (values, fields, header, footer) {
|
|
230
229
|
// Build message lines
|
|
231
230
|
var message = '';
|
|
@@ -233,15 +232,25 @@ var formatWhatsAppMessage = function (values, fields, header, footer) {
|
|
|
233
232
|
if (header) {
|
|
234
233
|
message += "".concat(header, "\n\n");
|
|
235
234
|
}
|
|
236
|
-
//
|
|
237
|
-
var
|
|
238
|
-
.filter(function (field) {
|
|
235
|
+
// Filter out empty/null/undefined values
|
|
236
|
+
var nonEmptyFields = fields.filter(function (field) {
|
|
239
237
|
var value = values[field.name];
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
238
|
+
// Skip if value is undefined, null, or empty string
|
|
239
|
+
if (value === undefined || value === null || value === '') {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
// Skip if array is empty
|
|
243
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
// Skip if checkbox is false
|
|
247
|
+
if (field.type === 'checkbox' && !field.multiple && value === false) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
});
|
|
252
|
+
// Format each field
|
|
253
|
+
var fieldLines = nonEmptyFields.map(function (field) {
|
|
245
254
|
var value = values[field.name];
|
|
246
255
|
var displayValue = value;
|
|
247
256
|
// Format array values (for multiple checkboxes)
|
|
@@ -257,71 +266,98 @@ var formatWhatsAppMessage = function (values, fields, header, footer) {
|
|
|
257
266
|
var option = field.options.find(function (opt) { return opt.value === value; });
|
|
258
267
|
displayValue = option ? option.label : value;
|
|
259
268
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
269
|
+
// Ensure displayValue is a string and preserve spaces/newlines
|
|
270
|
+
displayValue = String(displayValue);
|
|
271
|
+
// WhatsApp formatting:
|
|
272
|
+
// - Field label on its own line
|
|
273
|
+
// - Value on next line wrapped in backticks
|
|
274
|
+
// - Double newline between fields for readability
|
|
275
|
+
return "".concat(field.label || field.name, "\n`").concat(displayValue, "`");
|
|
276
|
+
});
|
|
277
|
+
// Join with double newline for spacing
|
|
278
|
+
message += fieldLines.join('\n\n');
|
|
264
279
|
// Add footer if provided
|
|
265
280
|
if (footer) {
|
|
266
281
|
message += "\n\n".concat(footer);
|
|
267
282
|
}
|
|
268
283
|
return encodeURIComponent(message);
|
|
269
284
|
};
|
|
270
|
-
// Main Form Component
|
|
271
|
-
var Form = function (
|
|
272
|
-
var
|
|
273
|
-
// Use theme variant
|
|
274
|
-
var themeVariant = (0, theme_1.useVariant)().variant;
|
|
285
|
+
// Main Form Component - MODIFIED for submit-only validation
|
|
286
|
+
var Form = function (props) {
|
|
287
|
+
var fieldsProp = props.fields, onSubmitProp = props.onSubmit, _a = props.defaultValues, defaultValuesProp = _a === void 0 ? {} : _a, _b = props.submitText, submitTextProp = _b === void 0 ? 'Submit' : _b, _c = props.submitBg, submitBgProp = _c === void 0 ? 'primary' : _c, submitPrefixProp = props.submitPrefix, submitSuffixProp = props.submitSuffix, _d = props.resetText, resetTextProp = _d === void 0 ? 'Reset' : _d, _e = props.showReset, showResetProp = _e === void 0 ? true : _e, _f = props.isLoading, isLoadingProp = _f === void 0 ? false : _f, _g = props.className, classNameProp = _g === void 0 ? '' : _g, _h = props.layout, layoutProp = _h === void 0 ? 'vertical' : _h, _j = props.gap, gapProp = _j === void 0 ? '1.5rem' : _j, titleProp = props.title, titleSizeProp = props.titleSize, titleColorProp = props.titleColor, descriptionProp = props.description, descriptionSizeProp = props.descriptionSize, descriptionColorProp = props.descriptionColor, whatsappContactProp = props.whatsappContact, widthProp = props.width, centeredProp = props.centered, whatsappHeaderProp = props.whatsappHeader, whatsappFooterProp = props.whatsappFooter, _k = props.fullWidth, fullWidthProp = _k === void 0 ? true : _k, _l = props.variant, variant = _l === void 0 ? '' : _l;
|
|
275
288
|
// Use component configuration with variant
|
|
276
289
|
var mergeWithLocal = (0, componentUtils_1.useComponentConfiguration)('Form', variant).mergeWithLocal;
|
|
277
|
-
//
|
|
278
|
-
var parsedFields = (0, react_1.useMemo)(function () { return parseJsonInput(fields, []); }, [fields]);
|
|
279
|
-
var parsedDefaultValues = (0, react_1.useMemo)(function () { return parseJsonInput(defaultValues, {}); }, [defaultValues]);
|
|
280
|
-
// Create local props for configuration
|
|
290
|
+
// Create local props object
|
|
281
291
|
var localProps = {
|
|
282
|
-
fields:
|
|
283
|
-
onSubmit:
|
|
284
|
-
defaultValues:
|
|
285
|
-
submitText:
|
|
286
|
-
submitBg:
|
|
287
|
-
submitPrefix:
|
|
288
|
-
submitSuffix:
|
|
289
|
-
resetText:
|
|
290
|
-
showReset:
|
|
291
|
-
isLoading:
|
|
292
|
-
className:
|
|
293
|
-
layout:
|
|
294
|
-
gap:
|
|
295
|
-
title:
|
|
296
|
-
titleSize:
|
|
297
|
-
titleColor:
|
|
298
|
-
description:
|
|
299
|
-
descriptionSize:
|
|
300
|
-
descriptionColor:
|
|
301
|
-
whatsappContact:
|
|
302
|
-
width:
|
|
303
|
-
centered:
|
|
304
|
-
whatsappHeader:
|
|
305
|
-
whatsappFooter:
|
|
306
|
-
fullWidth:
|
|
292
|
+
fields: fieldsProp,
|
|
293
|
+
onSubmit: onSubmitProp,
|
|
294
|
+
defaultValues: defaultValuesProp,
|
|
295
|
+
submitText: submitTextProp,
|
|
296
|
+
submitBg: submitBgProp,
|
|
297
|
+
submitPrefix: submitPrefixProp,
|
|
298
|
+
submitSuffix: submitSuffixProp,
|
|
299
|
+
resetText: resetTextProp,
|
|
300
|
+
showReset: showResetProp,
|
|
301
|
+
isLoading: isLoadingProp,
|
|
302
|
+
className: classNameProp,
|
|
303
|
+
layout: layoutProp,
|
|
304
|
+
gap: gapProp,
|
|
305
|
+
title: titleProp,
|
|
306
|
+
titleSize: titleSizeProp,
|
|
307
|
+
titleColor: titleColorProp,
|
|
308
|
+
description: descriptionProp,
|
|
309
|
+
descriptionSize: descriptionSizeProp,
|
|
310
|
+
descriptionColor: descriptionColorProp,
|
|
311
|
+
whatsappContact: whatsappContactProp,
|
|
312
|
+
width: widthProp,
|
|
313
|
+
centered: centeredProp,
|
|
314
|
+
whatsappHeader: whatsappHeaderProp,
|
|
315
|
+
whatsappFooter: whatsappFooterProp,
|
|
316
|
+
fullWidth: fullWidthProp,
|
|
307
317
|
variant: variant,
|
|
308
318
|
};
|
|
309
319
|
// Merge with theme configuration
|
|
310
320
|
var mergedProps = mergeWithLocal(localProps).props;
|
|
311
|
-
// Destructure
|
|
312
|
-
var
|
|
321
|
+
// Destructure with proper priority: local props override config props
|
|
322
|
+
var fields = fieldsProp !== undefined ? fieldsProp : mergedProps.fields;
|
|
323
|
+
var onSubmit = onSubmitProp !== undefined ? onSubmitProp : mergedProps.onSubmit;
|
|
324
|
+
var defaultValues = defaultValuesProp !== undefined ? defaultValuesProp : mergedProps.defaultValues;
|
|
325
|
+
var submitText = submitTextProp !== undefined ? submitTextProp : mergedProps.submitText;
|
|
326
|
+
var submitBg = submitBgProp !== undefined ? submitBgProp : mergedProps.submitBg;
|
|
327
|
+
var submitPrefix = submitPrefixProp !== undefined ? submitPrefixProp : mergedProps.submitPrefix;
|
|
328
|
+
var submitSuffix = submitSuffixProp !== undefined ? submitSuffixProp : mergedProps.submitSuffix;
|
|
329
|
+
var resetText = resetTextProp !== undefined ? resetTextProp : mergedProps.resetText;
|
|
330
|
+
var showReset = showResetProp !== undefined ? showResetProp : mergedProps.showReset;
|
|
331
|
+
var isLoading = isLoadingProp !== undefined ? isLoadingProp : mergedProps.isLoading;
|
|
332
|
+
var className = classNameProp !== undefined ? classNameProp : mergedProps.className;
|
|
333
|
+
var layout = layoutProp !== undefined ? layoutProp : mergedProps.layout;
|
|
334
|
+
var gap = gapProp !== undefined ? gapProp : mergedProps.gap;
|
|
335
|
+
var title = titleProp !== undefined ? titleProp : mergedProps.title;
|
|
336
|
+
var titleSize = titleSizeProp !== undefined ? titleSizeProp : mergedProps.titleSize;
|
|
337
|
+
var titleColor = titleColorProp !== undefined ? titleColorProp : mergedProps.titleColor;
|
|
338
|
+
var description = descriptionProp !== undefined ? descriptionProp : mergedProps.description;
|
|
339
|
+
var descriptionSize = descriptionSizeProp !== undefined ? descriptionSizeProp : mergedProps.descriptionSize;
|
|
340
|
+
var descriptionColor = descriptionColorProp !== undefined ? descriptionColorProp : mergedProps.descriptionColor;
|
|
341
|
+
var whatsappContact = whatsappContactProp !== undefined ? whatsappContactProp : mergedProps.whatsappContact;
|
|
342
|
+
var width = widthProp !== undefined ? widthProp : mergedProps.width;
|
|
343
|
+
var centered = centeredProp !== undefined ? centeredProp : mergedProps.centered;
|
|
344
|
+
var whatsappHeader = whatsappHeaderProp !== undefined ? whatsappHeaderProp : mergedProps.whatsappHeader;
|
|
345
|
+
var whatsappFooter = whatsappFooterProp !== undefined ? whatsappFooterProp : mergedProps.whatsappFooter;
|
|
346
|
+
var fullWidth = fullWidthProp !== undefined ? fullWidthProp : mergedProps.fullWidth;
|
|
347
|
+
// Parse JSON inputs
|
|
348
|
+
var parsedFields = (0, react_1.useMemo)(function () { return parseJsonInput(fields, []); }, [fields]);
|
|
349
|
+
var parsedDefaultValues = (0, react_1.useMemo)(function () { return parseJsonInput(defaultValues, {}); }, [defaultValues]);
|
|
313
350
|
// State management
|
|
314
|
-
var
|
|
315
|
-
var
|
|
316
|
-
var _15 = (0, react_1.useState)(function () {
|
|
351
|
+
var _m = (0, react_1.useState)({}), errors = _m[0], setErrors = _m[1];
|
|
352
|
+
var _o = (0, react_1.useState)(function () {
|
|
317
353
|
// Initialize form values from defaultValues and field values
|
|
318
354
|
var initialValues = {};
|
|
319
|
-
|
|
355
|
+
parsedFields.forEach(function (field) {
|
|
320
356
|
if (field.value !== undefined) {
|
|
321
357
|
initialValues[field.name] = field.value;
|
|
322
358
|
}
|
|
323
|
-
else if (
|
|
324
|
-
initialValues[field.name] =
|
|
359
|
+
else if (parsedDefaultValues[field.name] !== undefined) {
|
|
360
|
+
initialValues[field.name] = parsedDefaultValues[field.name];
|
|
325
361
|
}
|
|
326
362
|
else {
|
|
327
363
|
// Set default empty values
|
|
@@ -337,15 +373,16 @@ var Form = function (_a) {
|
|
|
337
373
|
}
|
|
338
374
|
});
|
|
339
375
|
return initialValues;
|
|
340
|
-
}), formValues =
|
|
341
|
-
var
|
|
376
|
+
}), formValues = _o[0], setFormValues = _o[1];
|
|
377
|
+
var _p = (0, react_1.useState)(false), isSubmitting = _p[0], setIsSubmitting = _p[1];
|
|
378
|
+
var _q = (0, react_1.useState)(false), hasBeenSubmitted = _q[0], setHasBeenSubmitted = _q[1];
|
|
342
379
|
// Update form values when defaultValues prop changes
|
|
343
380
|
(0, react_1.useEffect)(function () {
|
|
344
|
-
if (Object.keys(
|
|
345
|
-
setFormValues(function (prev) { return (__assign(__assign({}, prev),
|
|
381
|
+
if (Object.keys(parsedDefaultValues).length > 0) {
|
|
382
|
+
setFormValues(function (prev) { return (__assign(__assign({}, prev), parsedDefaultValues)); });
|
|
346
383
|
}
|
|
347
|
-
}, [
|
|
348
|
-
// Validate a single field
|
|
384
|
+
}, [parsedDefaultValues]);
|
|
385
|
+
// Validate a single field
|
|
349
386
|
var validateField = (0, react_1.useCallback)(function (field, value) {
|
|
350
387
|
var _a, _b, _c, _d, _e;
|
|
351
388
|
// Required validation
|
|
@@ -426,11 +463,11 @@ var Form = function (_a) {
|
|
|
426
463
|
}
|
|
427
464
|
return null;
|
|
428
465
|
}, []);
|
|
429
|
-
// Validate form
|
|
466
|
+
// Validate form
|
|
430
467
|
var validateForm = (0, react_1.useCallback)(function () {
|
|
431
468
|
var newErrors = {};
|
|
432
469
|
var hasErrors = false;
|
|
433
|
-
|
|
470
|
+
parsedFields.forEach(function (field) {
|
|
434
471
|
var value = formValues[field.name];
|
|
435
472
|
var error = validateField(field, value);
|
|
436
473
|
if (error) {
|
|
@@ -439,102 +476,46 @@ var Form = function (_a) {
|
|
|
439
476
|
}
|
|
440
477
|
});
|
|
441
478
|
setErrors(newErrors);
|
|
479
|
+
setHasBeenSubmitted(true);
|
|
442
480
|
return !hasErrors;
|
|
443
|
-
}, [
|
|
444
|
-
// Handle field change for checkboxes and radios
|
|
481
|
+
}, [parsedFields, validateField, formValues]);
|
|
482
|
+
// Handle field change for checkboxes and radios
|
|
445
483
|
var handleFieldChange = (0, react_1.useCallback)(function (fieldName, newValue) {
|
|
446
484
|
// Update form values
|
|
447
485
|
setFormValues(function (prev) {
|
|
448
486
|
var _a;
|
|
449
487
|
return (__assign(__assign({}, prev), (_a = {}, _a[fieldName] = newValue, _a)));
|
|
450
488
|
});
|
|
451
|
-
//
|
|
452
|
-
setTouched(function (prev) {
|
|
453
|
-
var _a;
|
|
454
|
-
return (__assign(__assign({}, prev), (_a = {}, _a[fieldName] = true, _a)));
|
|
455
|
-
});
|
|
456
|
-
// Validate the changed field
|
|
457
|
-
var field = finalFields.find(function (f) { return f.name === fieldName; });
|
|
458
|
-
if (!field)
|
|
459
|
-
return;
|
|
460
|
-
var error = validateField(field, newValue);
|
|
489
|
+
// Clear error for this field when user makes a change
|
|
461
490
|
setErrors(function (prev) {
|
|
462
|
-
var
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
else {
|
|
467
|
-
var newErrors = __assign({}, prev);
|
|
468
|
-
delete newErrors[fieldName];
|
|
469
|
-
return newErrors;
|
|
470
|
-
}
|
|
491
|
+
var newErrors = __assign({}, prev);
|
|
492
|
+
delete newErrors[fieldName];
|
|
493
|
+
return newErrors;
|
|
471
494
|
});
|
|
472
|
-
}, [
|
|
473
|
-
// Handle input
|
|
474
|
-
var
|
|
495
|
+
}, []);
|
|
496
|
+
// Handle input change - NO VALIDATION ON CHANGE
|
|
497
|
+
var handleInputEventChange = (0, react_1.useCallback)(function (e) {
|
|
498
|
+
var fieldName = e.target.name;
|
|
499
|
+
var value = e.target.value;
|
|
475
500
|
// Update form values
|
|
476
501
|
setFormValues(function (prev) {
|
|
477
502
|
var _a;
|
|
478
503
|
return (__assign(__assign({}, prev), (_a = {}, _a[fieldName] = value, _a)));
|
|
479
504
|
});
|
|
480
|
-
//
|
|
481
|
-
setTouched(function (prev) {
|
|
482
|
-
var _a;
|
|
483
|
-
return (__assign(__assign({}, prev), (_a = {}, _a[fieldName] = true, _a)));
|
|
484
|
-
});
|
|
485
|
-
// Validate the changed field
|
|
486
|
-
var field = finalFields.find(function (f) { return f.name === fieldName; });
|
|
487
|
-
if (!field)
|
|
488
|
-
return;
|
|
489
|
-
var error = validateField(field, value);
|
|
505
|
+
// Clear error for this field when user makes a change
|
|
490
506
|
setErrors(function (prev) {
|
|
491
|
-
var
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
else {
|
|
496
|
-
var newErrors = __assign({}, prev);
|
|
497
|
-
delete newErrors[fieldName];
|
|
498
|
-
return newErrors;
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
}, [finalFields, validateField]);
|
|
502
|
-
// Handle field blur (unchanged)
|
|
503
|
-
var handleFieldBlur = (0, react_1.useCallback)(function (fieldName) {
|
|
504
|
-
setTouched(function (prev) {
|
|
505
|
-
var _a;
|
|
506
|
-
return (__assign(__assign({}, prev), (_a = {}, _a[fieldName] = true, _a)));
|
|
507
|
+
var newErrors = __assign({}, prev);
|
|
508
|
+
delete newErrors[fieldName];
|
|
509
|
+
return newErrors;
|
|
507
510
|
});
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
if (!field)
|
|
511
|
-
return;
|
|
512
|
-
var value = formValues[fieldName];
|
|
513
|
-
var error = validateField(field, value);
|
|
514
|
-
setErrors(function (prev) {
|
|
515
|
-
var _a;
|
|
516
|
-
if (error) {
|
|
517
|
-
return __assign(__assign({}, prev), (_a = {}, _a[fieldName] = error, _a));
|
|
518
|
-
}
|
|
519
|
-
else {
|
|
520
|
-
var newErrors = __assign({}, prev);
|
|
521
|
-
delete newErrors[fieldName];
|
|
522
|
-
return newErrors;
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
}, [finalFields, validateField, formValues]);
|
|
526
|
-
// Handle form submission (unchanged)
|
|
511
|
+
}, []);
|
|
512
|
+
// Handle form submission
|
|
527
513
|
var handleSubmit = (0, react_1.useCallback)(function (e) { return __awaiter(void 0, void 0, void 0, function () {
|
|
528
|
-
var
|
|
514
|
+
var isValid, firstErrorField, element, message, cleanPhone, whatsappUrl, error_1;
|
|
529
515
|
return __generator(this, function (_a) {
|
|
530
516
|
switch (_a.label) {
|
|
531
517
|
case 0:
|
|
532
518
|
e.preventDefault();
|
|
533
|
-
allTouched = {};
|
|
534
|
-
finalFields.forEach(function (field) {
|
|
535
|
-
allTouched[field.name] = true;
|
|
536
|
-
});
|
|
537
|
-
setTouched(allTouched);
|
|
538
519
|
isValid = validateForm();
|
|
539
520
|
if (!isValid) {
|
|
540
521
|
firstErrorField = Object.keys(errors)[0];
|
|
@@ -549,20 +530,20 @@ var Form = function (_a) {
|
|
|
549
530
|
_a.label = 1;
|
|
550
531
|
case 1:
|
|
551
532
|
_a.trys.push([1, 7, 8, 9]);
|
|
552
|
-
if (!
|
|
553
|
-
message = formatWhatsAppMessage(formValues,
|
|
554
|
-
cleanPhone =
|
|
533
|
+
if (!whatsappContact) return [3 /*break*/, 4];
|
|
534
|
+
message = formatWhatsAppMessage(formValues, parsedFields, whatsappHeader, whatsappFooter);
|
|
535
|
+
cleanPhone = whatsappContact.replace(/[\s+\-()]/g, '');
|
|
555
536
|
whatsappUrl = "https://wa.me/".concat(cleanPhone, "?text=").concat(message);
|
|
556
537
|
window.open(whatsappUrl, '_blank');
|
|
557
|
-
if (!
|
|
558
|
-
return [4 /*yield*/,
|
|
538
|
+
if (!onSubmit) return [3 /*break*/, 3];
|
|
539
|
+
return [4 /*yield*/, onSubmit(formValues, true)];
|
|
559
540
|
case 2:
|
|
560
541
|
_a.sent();
|
|
561
542
|
_a.label = 3;
|
|
562
543
|
case 3: return [3 /*break*/, 6];
|
|
563
544
|
case 4:
|
|
564
|
-
if (!
|
|
565
|
-
return [4 /*yield*/,
|
|
545
|
+
if (!onSubmit) return [3 /*break*/, 6];
|
|
546
|
+
return [4 /*yield*/, onSubmit(formValues, false)];
|
|
566
547
|
case 5:
|
|
567
548
|
_a.sent();
|
|
568
549
|
_a.label = 6;
|
|
@@ -578,17 +559,17 @@ var Form = function (_a) {
|
|
|
578
559
|
case 9: return [2 /*return*/];
|
|
579
560
|
}
|
|
580
561
|
});
|
|
581
|
-
}); }, [
|
|
582
|
-
// Handle form reset
|
|
562
|
+
}); }, [validateForm, formValues, whatsappContact, whatsappHeader, whatsappFooter, onSubmit, errors, parsedFields]);
|
|
563
|
+
// Handle form reset
|
|
583
564
|
var handleReset = (0, react_1.useCallback)(function () {
|
|
584
565
|
// Reset to initial values
|
|
585
566
|
var initialValues = {};
|
|
586
|
-
|
|
567
|
+
parsedFields.forEach(function (field) {
|
|
587
568
|
if (field.value !== undefined) {
|
|
588
569
|
initialValues[field.name] = field.value;
|
|
589
570
|
}
|
|
590
|
-
else if (
|
|
591
|
-
initialValues[field.name] =
|
|
571
|
+
else if (parsedDefaultValues[field.name] !== undefined) {
|
|
572
|
+
initialValues[field.name] = parsedDefaultValues[field.name];
|
|
592
573
|
}
|
|
593
574
|
else {
|
|
594
575
|
if (field.type === 'checkbox' && field.multiple) {
|
|
@@ -604,49 +585,36 @@ var Form = function (_a) {
|
|
|
604
585
|
});
|
|
605
586
|
setFormValues(initialValues);
|
|
606
587
|
setErrors({});
|
|
607
|
-
|
|
608
|
-
}, [
|
|
609
|
-
// Get field status for Input component
|
|
588
|
+
setHasBeenSubmitted(false);
|
|
589
|
+
}, [parsedFields, parsedDefaultValues]);
|
|
590
|
+
// Get field status for Input component
|
|
610
591
|
var getFieldStatus = (0, react_1.useCallback)(function (fieldName) {
|
|
611
592
|
var error = errors[fieldName];
|
|
612
|
-
|
|
613
|
-
if (error)
|
|
593
|
+
// Only show validation status if form has been submitted and there's an error
|
|
594
|
+
if (hasBeenSubmitted && error) {
|
|
614
595
|
return 'danger';
|
|
615
|
-
if (isTouched && !error && formValues[fieldName] !== '') {
|
|
616
|
-
return 'success';
|
|
617
596
|
}
|
|
618
597
|
return undefined;
|
|
619
|
-
}, [errors,
|
|
620
|
-
// Check if form is valid for submission
|
|
598
|
+
}, [errors, hasBeenSubmitted]);
|
|
599
|
+
// Check if form is valid for submission
|
|
621
600
|
var isFormValid = (0, react_1.useMemo)(function () {
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
});
|
|
635
|
-
// Check if there are any validation errors
|
|
636
|
-
var hasValidationErrors = Object.keys(errors).length > 0;
|
|
637
|
-
return !hasEmptyRequiredFields && !hasValidationErrors;
|
|
638
|
-
}, [finalFields, formValues, errors]);
|
|
639
|
-
// Submit button disabled state (unchanged)
|
|
640
|
-
var isSubmitDisabled = isSubmitting || finalIsLoading || !isFormValid;
|
|
641
|
-
// Check if WhatsApp is configured (unchanged)
|
|
642
|
-
var hasWhatsApp = !!finalWhatsappContact;
|
|
643
|
-
// Render field based on type (unchanged)
|
|
601
|
+
// Only check validity when form has been submitted
|
|
602
|
+
if (hasBeenSubmitted) {
|
|
603
|
+
return Object.keys(errors).length === 0;
|
|
604
|
+
}
|
|
605
|
+
// If form hasn't been submitted yet, consider it valid
|
|
606
|
+
return true;
|
|
607
|
+
}, [hasBeenSubmitted, errors]);
|
|
608
|
+
// Submit button disabled state
|
|
609
|
+
var isSubmitDisabled = isSubmitting || isLoading;
|
|
610
|
+
// Check if WhatsApp is configured
|
|
611
|
+
var hasWhatsApp = !!whatsappContact;
|
|
612
|
+
// Render field based on type
|
|
644
613
|
var renderField = (0, react_1.useCallback)(function (field) {
|
|
645
614
|
var _a, _b, _c, _d, _e;
|
|
646
615
|
var status = getFieldStatus(field.name);
|
|
647
616
|
var error = errors[field.name];
|
|
648
|
-
var
|
|
649
|
-
var showError = error && isTouched;
|
|
617
|
+
var showError = hasBeenSubmitted && error;
|
|
650
618
|
var value = formValues[field.name];
|
|
651
619
|
// Field wrapper classes
|
|
652
620
|
var wrapperClass = "col min-w-200 field ".concat(showError ? 'field-error' : '').trim();
|
|
@@ -655,7 +623,7 @@ var Form = function (_a) {
|
|
|
655
623
|
// Generate unique ID for the input
|
|
656
624
|
var inputId = ((_a = field.inputProps) === null || _a === void 0 ? void 0 : _a.id) || "form-field-".concat(field.name);
|
|
657
625
|
// Base props for Input component
|
|
658
|
-
var baseProps = __assign({ id: inputId, name: field.name, label: field.label, placeholder: field.placeholder, helperText: helperText, disabled: field.disabled ||
|
|
626
|
+
var baseProps = __assign({ id: inputId, name: field.name, label: field.label, placeholder: field.placeholder, helperText: helperText, disabled: field.disabled || isLoading, fullWidth: fullWidth }, field.inputProps);
|
|
659
627
|
// Only add status if it's not undefined
|
|
660
628
|
if (status !== undefined) {
|
|
661
629
|
baseProps.status = status;
|
|
@@ -682,13 +650,13 @@ var Form = function (_a) {
|
|
|
682
650
|
var newValues = checkedValues_1.filter(function (v) { return v !== option.value; });
|
|
683
651
|
handleFieldChange(field.name, newValues);
|
|
684
652
|
}
|
|
685
|
-
}, disabled: field.disabled || option.disabled ||
|
|
653
|
+
}, disabled: field.disabled || option.disabled || isLoading, required: field.required && index === 0, value: option.value }));
|
|
686
654
|
}))));
|
|
687
655
|
}
|
|
688
656
|
else {
|
|
689
657
|
// Single checkbox (boolean)
|
|
690
658
|
return (react_1.default.createElement("div", { key: field.name, className: wrapperClass },
|
|
691
|
-
react_1.default.createElement(FormCheckbox, { label: field.label, checked: !!value, onChange: function (checked) { return handleFieldChange(field.name, checked); }, disabled: field.disabled ||
|
|
659
|
+
react_1.default.createElement(FormCheckbox, { label: field.label, checked: !!value, onChange: function (checked) { return handleFieldChange(field.name, checked); }, disabled: field.disabled || isLoading, required: field.required, id: inputId })));
|
|
692
660
|
}
|
|
693
661
|
case 'radio':
|
|
694
662
|
if (!((_c = field.options) === null || _c === void 0 ? void 0 : _c.length))
|
|
@@ -696,29 +664,999 @@ var Form = function (_a) {
|
|
|
696
664
|
return (react_1.default.createElement("div", { key: field.name, className: wrapperClass },
|
|
697
665
|
field.label && (react_1.default.createElement("div", { className: "form-label", style: { marginBottom: '0.5rem' } },
|
|
698
666
|
react_1.default.createElement(Text_1.default, { text: field.label + (field.required ? ' *' : ''), size: "sm", color: "text", bold: true }))),
|
|
699
|
-
react_1.default.createElement(Flex_1.default, { direction: "column", gap: "0.5rem" }, field.options.map(function (option, index) { return (react_1.default.createElement(FormRadio, { key: "".concat(field.name, "_").concat(index), id: "".concat(inputId, "_").concat(index), label: option.label, checked: value === option.value, onChange: function () { return handleFieldChange(field.name, option.value); }, disabled: field.disabled || option.disabled ||
|
|
667
|
+
react_1.default.createElement(Flex_1.default, { direction: "column", gap: "0.5rem" }, field.options.map(function (option, index) { return (react_1.default.createElement(FormRadio, { key: "".concat(field.name, "_").concat(index), id: "".concat(inputId, "_").concat(index), label: option.label, checked: value === option.value, onChange: function () { return handleFieldChange(field.name, option.value); }, disabled: field.disabled || option.disabled || isLoading, required: field.required && index === 0, value: option.value })); }))));
|
|
700
668
|
case 'textarea':
|
|
701
669
|
return (react_1.default.createElement("div", { key: field.name, className: wrapperClass },
|
|
702
|
-
react_1.default.createElement(Input_1.default, __assign({ multiline: true, rows: ((_d = field.inputProps) === null || _d === void 0 ? void 0 : _d.rows) || 4, value: value || '', onChange:
|
|
670
|
+
react_1.default.createElement(Input_1.default, __assign({ multiline: true, rows: ((_d = field.inputProps) === null || _d === void 0 ? void 0 : _d.rows) || 4, value: value || '', onChange: handleInputEventChange }, baseProps))));
|
|
703
671
|
case 'select':
|
|
704
672
|
return (react_1.default.createElement("div", { key: field.name, className: wrapperClass },
|
|
705
|
-
react_1.default.createElement(Input_1.default, __assign({ select: true, options: ((_e = field.options) === null || _e === void 0 ? void 0 : _e.map(function (opt) { return ({ text: opt.label, value: opt.value }); })) || [], value: value || '', onChange:
|
|
673
|
+
react_1.default.createElement(Input_1.default, __assign({ select: true, options: ((_e = field.options) === null || _e === void 0 ? void 0 : _e.map(function (opt) { return ({ text: opt.label, value: opt.value }); })) || [], value: value || '', onChange: handleInputEventChange }, baseProps))));
|
|
706
674
|
default:
|
|
707
675
|
// text, email, number, tel, date, file, password
|
|
708
676
|
return (react_1.default.createElement("div", { key: field.name, className: wrapperClass },
|
|
709
|
-
react_1.default.createElement(Input_1.default, __assign({ type: field.type, value: value || '', onChange:
|
|
677
|
+
react_1.default.createElement(Input_1.default, __assign({ type: field.type, value: value || '', onChange: handleInputEventChange }, baseProps))));
|
|
710
678
|
}
|
|
711
|
-
}, [errors,
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
679
|
+
}, [errors, hasBeenSubmitted, formValues, isLoading, getFieldStatus, handleFieldChange, handleInputEventChange, fullWidth]);
|
|
680
|
+
// Don't render if no fields are configured
|
|
681
|
+
if (parsedFields.length === 0) {
|
|
682
|
+
console.warn('Form: No fields configured. Please provide fields prop or configure via theme.');
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
return (react_1.default.createElement("div", { className: "form-wrapper ".concat(centered ? 'center' : '', " ").concat(className), style: { width: "100%", maxWidth: width || "450px" } },
|
|
686
|
+
title && (react_1.default.createElement("div", { className: "form-header", style: { marginBottom: '2rem' } },
|
|
687
|
+
react_1.default.createElement(Text_1.default, { text: title, size: titleSize || "3xl", color: titleColor || "", block: true }),
|
|
688
|
+
description && (react_1.default.createElement(Text_1.default, { article: true, size: descriptionSize || "sm", color: descriptionColor || "" },
|
|
689
|
+
react_1.default.createElement("div", { className: "article text-sm", dangerouslySetInnerHTML: { __html: description } }))))),
|
|
717
690
|
react_1.default.createElement("form", { className: "form", onSubmit: handleSubmit, style: { width: '100%' } },
|
|
718
|
-
react_1.default.createElement(Flex_1.default, { direction:
|
|
719
|
-
react_1.default.createElement(Flex_1.default, { direction: "column", gap: "1rem", style: { marginTop: '2rem', width:
|
|
720
|
-
react_1.default.createElement(Button_1.default, { type: "submit", text: hasWhatsApp ? "Send via WhatsApp" :
|
|
721
|
-
// startIcon={hasWhatsApp ? <PiWhatsappLogo /> : <PiPaperPlaneTilt />}
|
|
722
|
-
disabled: isSubmitDisabled, isLoading: isSubmitting || finalIsLoading, fullWidth: finalFullWidth })))));
|
|
691
|
+
react_1.default.createElement(Flex_1.default, { direction: layout === 'horizontal' ? 'row' : 'column', gap: gap, width: '100%' }, parsedFields.map(renderField)),
|
|
692
|
+
react_1.default.createElement(Flex_1.default, { direction: "column", gap: "1rem", style: { marginTop: '2rem', width: fullWidth ? '100%' : undefined } },
|
|
693
|
+
react_1.default.createElement(Button_1.default, { type: "submit", text: hasWhatsApp ? "Send via WhatsApp" : submitText, bg: submitBg || "primary", raised: true, prefix: submitPrefix || hasWhatsApp ? react_1.default.createElement(pi_1.PiWhatsappLogo, null) : react_1.default.createElement(pi_1.PiPaperPlaneTilt, null), suffix: submitSuffix, disabled: isSubmitDisabled, isLoading: isSubmitting || isLoading, fullWidth: fullWidth })))));
|
|
723
694
|
};
|
|
724
695
|
exports.default = Form;
|
|
696
|
+
// 'use client';
|
|
697
|
+
// import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
698
|
+
// import Button from '../button/Button';
|
|
699
|
+
// import Input from '../input/Input';
|
|
700
|
+
// import Flex from '../flex/Flex';
|
|
701
|
+
// import Text from '../text/Text';
|
|
702
|
+
// import { PiPaperPlaneTilt, PiWarningCircle, PiWhatsappLogo } from 'react-icons/pi';
|
|
703
|
+
// import { useComponentConfiguration } from '../../utils/componentUtils';
|
|
704
|
+
// import { useVariant } from '../theme/theme';
|
|
705
|
+
// // Field types supported
|
|
706
|
+
// export type InputType =
|
|
707
|
+
// | 'text'
|
|
708
|
+
// | 'email'
|
|
709
|
+
// | 'number'
|
|
710
|
+
// | 'tel'
|
|
711
|
+
// | 'textarea'
|
|
712
|
+
// | 'password'
|
|
713
|
+
// | 'date'
|
|
714
|
+
// | 'select'
|
|
715
|
+
// | 'checkbox'
|
|
716
|
+
// | 'radio'
|
|
717
|
+
// | 'file';
|
|
718
|
+
// // Field option
|
|
719
|
+
// export interface FieldOption {
|
|
720
|
+
// label: string;
|
|
721
|
+
// value: string;
|
|
722
|
+
// disabled?: boolean;
|
|
723
|
+
// }
|
|
724
|
+
// // Simplified field configuration
|
|
725
|
+
// export interface FormField {
|
|
726
|
+
// name: string;
|
|
727
|
+
// label?: string;
|
|
728
|
+
// type: InputType;
|
|
729
|
+
// required?: boolean;
|
|
730
|
+
// placeholder?: string;
|
|
731
|
+
// options?: FieldOption[];
|
|
732
|
+
// multiple?: boolean; // For checkboxes
|
|
733
|
+
// value?: any; // Initial value
|
|
734
|
+
// disabled?: boolean;
|
|
735
|
+
// helperText?: string;
|
|
736
|
+
// // Input component props (passed directly to Input)
|
|
737
|
+
// inputProps?: {
|
|
738
|
+
// startIcon?: React.ReactNode;
|
|
739
|
+
// endIcon?: React.ReactNode;
|
|
740
|
+
// prefix?: React.ReactNode;
|
|
741
|
+
// suffix?: React.ReactNode;
|
|
742
|
+
// stringPrefix?: string;
|
|
743
|
+
// stringSuffix?: string;
|
|
744
|
+
// iconicBg?: string;
|
|
745
|
+
// funcss?: string;
|
|
746
|
+
// bg?: string;
|
|
747
|
+
// flat?: boolean;
|
|
748
|
+
// bordered?: boolean;
|
|
749
|
+
// borderless?: boolean;
|
|
750
|
+
// rounded?: boolean;
|
|
751
|
+
// leftRounded?: boolean;
|
|
752
|
+
// rightRounded?: boolean;
|
|
753
|
+
// rows?: number;
|
|
754
|
+
// noBorder?: boolean;
|
|
755
|
+
// variant?: string;
|
|
756
|
+
// // Standard HTML attributes
|
|
757
|
+
// id?: string;
|
|
758
|
+
// autocomplete?: string;
|
|
759
|
+
// pattern?: string;
|
|
760
|
+
// min?: string | number;
|
|
761
|
+
// max?: string | number;
|
|
762
|
+
// minLength?: number;
|
|
763
|
+
// maxLength?: number;
|
|
764
|
+
// step?: string | number;
|
|
765
|
+
// };
|
|
766
|
+
// }
|
|
767
|
+
// // Form props
|
|
768
|
+
// export interface FormProps {
|
|
769
|
+
// fields?: FormField[] | string; // Allow string (JSON) or array
|
|
770
|
+
// onSubmit?: (values: Record<string, any>, viaWhatsApp?: boolean) => void;
|
|
771
|
+
// defaultValues?: Record<string, any> | string; // Allow string (JSON) or object
|
|
772
|
+
// submitText?: string;
|
|
773
|
+
// submitBg?: string;
|
|
774
|
+
// submitPrefix?: React.ReactNode | string;
|
|
775
|
+
// submitSuffix?: React.ReactNode | string;
|
|
776
|
+
// resetText?: string;
|
|
777
|
+
// showReset?: boolean;
|
|
778
|
+
// isLoading?: boolean;
|
|
779
|
+
// className?: string;
|
|
780
|
+
// layout?: 'vertical' | 'horizontal';
|
|
781
|
+
// gap?: number | string;
|
|
782
|
+
// title?: string;
|
|
783
|
+
// titleSize?: string;
|
|
784
|
+
// titleColor?: string;
|
|
785
|
+
// description?: string;
|
|
786
|
+
// descriptionSize?: string;
|
|
787
|
+
// descriptionColor?: string;
|
|
788
|
+
// // WhatsApp props (simple, not object)
|
|
789
|
+
// whatsappContact?: string;
|
|
790
|
+
// whatsappHeader?: string;
|
|
791
|
+
// whatsappFooter?: string;
|
|
792
|
+
// // New props for fullWidth
|
|
793
|
+
// fullWidth?: boolean;
|
|
794
|
+
// width?: string;
|
|
795
|
+
// centered?: boolean;
|
|
796
|
+
// // Variant support
|
|
797
|
+
// variant?: string;
|
|
798
|
+
// }
|
|
799
|
+
// // Helper function to parse JSON input
|
|
800
|
+
// const parseJsonInput = <T,>(input: T | string | undefined, defaultValue: T): T => {
|
|
801
|
+
// if (input === undefined || input === null) {
|
|
802
|
+
// return defaultValue;
|
|
803
|
+
// }
|
|
804
|
+
// // If it's already the correct type, return as is
|
|
805
|
+
// if (typeof input !== 'string') {
|
|
806
|
+
// return input;
|
|
807
|
+
// }
|
|
808
|
+
// try {
|
|
809
|
+
// // Try to parse as JSON
|
|
810
|
+
// const parsed = JSON.parse(input);
|
|
811
|
+
// return parsed;
|
|
812
|
+
// } catch (error) {
|
|
813
|
+
// console.warn('Failed to parse JSON input:', input, error);
|
|
814
|
+
// // If parsing fails, try to interpret as a string that might be valid
|
|
815
|
+
// try {
|
|
816
|
+
// // Try to handle common cases like arrays or objects without quotes
|
|
817
|
+
// const trimmed = input.trim();
|
|
818
|
+
// if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
819
|
+
// return JSON.parse(trimmed);
|
|
820
|
+
// } else if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
821
|
+
// return JSON.parse(trimmed);
|
|
822
|
+
// }
|
|
823
|
+
// } catch (e) {
|
|
824
|
+
// // If still fails, return default
|
|
825
|
+
// }
|
|
826
|
+
// return defaultValue;
|
|
827
|
+
// }
|
|
828
|
+
// };
|
|
829
|
+
// // Custom Checkbox Component (unchanged)
|
|
830
|
+
// const FormCheckbox: React.FC<{
|
|
831
|
+
// label?: string;
|
|
832
|
+
// checked: boolean;
|
|
833
|
+
// onChange: (checked: boolean) => void;
|
|
834
|
+
// disabled?: boolean;
|
|
835
|
+
// required?: boolean;
|
|
836
|
+
// value?: string;
|
|
837
|
+
// id?: string;
|
|
838
|
+
// }> = ({ label, checked, onChange, disabled, required, value, id }) => {
|
|
839
|
+
// return (
|
|
840
|
+
// <label
|
|
841
|
+
// className="funui_form-checkbox"
|
|
842
|
+
// style={{
|
|
843
|
+
// display: 'flex',
|
|
844
|
+
// alignItems: 'center',
|
|
845
|
+
// gap: '0.5rem',
|
|
846
|
+
// cursor: disabled ? 'not-allowed' : 'pointer',
|
|
847
|
+
// userSelect: 'none',
|
|
848
|
+
// width: 'fit-content',
|
|
849
|
+
// padding: '0.25rem 0',
|
|
850
|
+
// }}
|
|
851
|
+
// >
|
|
852
|
+
// <input
|
|
853
|
+
// type="checkbox"
|
|
854
|
+
// id={id}
|
|
855
|
+
// checked={checked}
|
|
856
|
+
// onChange={(e) => !disabled && onChange(e.target.checked)}
|
|
857
|
+
// disabled={disabled}
|
|
858
|
+
// required={required}
|
|
859
|
+
// value={value}
|
|
860
|
+
// style={{
|
|
861
|
+
// position: 'absolute',
|
|
862
|
+
// opacity: 0,
|
|
863
|
+
// width: 0,
|
|
864
|
+
// height: 0,
|
|
865
|
+
// pointerEvents: 'none'
|
|
866
|
+
// }}
|
|
867
|
+
// />
|
|
868
|
+
// <div
|
|
869
|
+
// className="funui_form-checkbox-box"
|
|
870
|
+
// style={{
|
|
871
|
+
// width: '1.25rem',
|
|
872
|
+
// height: '1.25rem',
|
|
873
|
+
// border: checked ? '2px solid var(--primary)' : '2px solid var(--borderColor)',
|
|
874
|
+
// borderRadius: '0.25rem',
|
|
875
|
+
// backgroundColor: checked ? 'var(--primary)' : 'transparent',
|
|
876
|
+
// position: 'relative',
|
|
877
|
+
// transition: 'all 0.2s ease',
|
|
878
|
+
// display: 'flex',
|
|
879
|
+
// alignItems: 'center',
|
|
880
|
+
// justifyContent: 'center',
|
|
881
|
+
// flexShrink: 0,
|
|
882
|
+
// }}
|
|
883
|
+
// >
|
|
884
|
+
// {checked && (
|
|
885
|
+
// <svg
|
|
886
|
+
// width="14"
|
|
887
|
+
// height="14"
|
|
888
|
+
// viewBox="0 0 14 14"
|
|
889
|
+
// fill="none"
|
|
890
|
+
// xmlns="http://www.w3.org2000/svg"
|
|
891
|
+
// style={{
|
|
892
|
+
// stroke: 'white',
|
|
893
|
+
// strokeWidth: '2',
|
|
894
|
+
// strokeLinecap: 'round',
|
|
895
|
+
// strokeLinejoin: 'round',
|
|
896
|
+
// }}
|
|
897
|
+
// >
|
|
898
|
+
// <path d="M3 7L6 10L11 4" />
|
|
899
|
+
// </svg>
|
|
900
|
+
// )}
|
|
901
|
+
// </div>
|
|
902
|
+
// {label && (
|
|
903
|
+
// <span
|
|
904
|
+
// className="funui_form-checkbox-label"
|
|
905
|
+
// style={{
|
|
906
|
+
// fontSize: '0.875rem',
|
|
907
|
+
// color: disabled ? 'var(--text-muted)' : 'var(--text)',
|
|
908
|
+
// fontWeight: checked ? '500' : '400',
|
|
909
|
+
// }}
|
|
910
|
+
// >
|
|
911
|
+
// {label}{required && ' *'}
|
|
912
|
+
// </span>
|
|
913
|
+
// )}
|
|
914
|
+
// </label>
|
|
915
|
+
// );
|
|
916
|
+
// };
|
|
917
|
+
// // Custom Radio Component (unchanged)
|
|
918
|
+
// const FormRadio: React.FC<{
|
|
919
|
+
// label?: string;
|
|
920
|
+
// checked: boolean;
|
|
921
|
+
// onChange: (checked: boolean) => void;
|
|
922
|
+
// disabled?: boolean;
|
|
923
|
+
// required?: boolean;
|
|
924
|
+
// value?: string;
|
|
925
|
+
// id?: string;
|
|
926
|
+
// }> = ({ label, checked, onChange, disabled, required, value, id }) => {
|
|
927
|
+
// return (
|
|
928
|
+
// <label
|
|
929
|
+
// className="funui_form-radio"
|
|
930
|
+
// style={{
|
|
931
|
+
// display: 'flex',
|
|
932
|
+
// alignItems: 'center',
|
|
933
|
+
// gap: '0.5rem',
|
|
934
|
+
// cursor: disabled ? 'not-allowed' : 'pointer',
|
|
935
|
+
// userSelect: 'none',
|
|
936
|
+
// width: 'fit-content',
|
|
937
|
+
// padding: '0.25rem 0',
|
|
938
|
+
// }}
|
|
939
|
+
// >
|
|
940
|
+
// <input
|
|
941
|
+
// type="radio"
|
|
942
|
+
// id={id}
|
|
943
|
+
// checked={checked}
|
|
944
|
+
// onChange={(e) => !disabled && onChange(e.target.checked)}
|
|
945
|
+
// disabled={disabled}
|
|
946
|
+
// required={required}
|
|
947
|
+
// value={value}
|
|
948
|
+
// style={{
|
|
949
|
+
// position: 'absolute',
|
|
950
|
+
// opacity: 0,
|
|
951
|
+
// width: 0,
|
|
952
|
+
// height: 0,
|
|
953
|
+
// pointerEvents: 'none'
|
|
954
|
+
// }}
|
|
955
|
+
// />
|
|
956
|
+
// <div
|
|
957
|
+
// className="funui_form-radio-circle"
|
|
958
|
+
// style={{
|
|
959
|
+
// width: '1.25rem',
|
|
960
|
+
// height: '1.25rem',
|
|
961
|
+
// border: checked ? '2px solid var(--primary)' : '2px solid var(--borderColor)',
|
|
962
|
+
// borderRadius: '50%',
|
|
963
|
+
// backgroundColor: 'transparent',
|
|
964
|
+
// position: 'relative',
|
|
965
|
+
// transition: 'all 0.2s ease',
|
|
966
|
+
// display: 'flex',
|
|
967
|
+
// alignItems: 'center',
|
|
968
|
+
// justifyContent: 'center',
|
|
969
|
+
// flexShrink: 0,
|
|
970
|
+
// }}
|
|
971
|
+
// >
|
|
972
|
+
// {checked && (
|
|
973
|
+
// <div
|
|
974
|
+
// style={{
|
|
975
|
+
// width: '0.75rem',
|
|
976
|
+
// height: '0.75rem',
|
|
977
|
+
// backgroundColor: 'var(--primary)',
|
|
978
|
+
// borderRadius: '50%',
|
|
979
|
+
// }}
|
|
980
|
+
// />
|
|
981
|
+
// )}
|
|
982
|
+
// </div>
|
|
983
|
+
// {label && (
|
|
984
|
+
// <span
|
|
985
|
+
// className="funui_form-radio-label"
|
|
986
|
+
// style={{
|
|
987
|
+
// fontSize: '0.875rem',
|
|
988
|
+
// color: disabled ? 'var(--text-muted)' : 'var(--text)',
|
|
989
|
+
// fontWeight: checked ? '500' : '400',
|
|
990
|
+
// }}
|
|
991
|
+
// >
|
|
992
|
+
// {label}{required && ' *'}
|
|
993
|
+
// </span>
|
|
994
|
+
// )}
|
|
995
|
+
// </label>
|
|
996
|
+
// );
|
|
997
|
+
// };
|
|
998
|
+
// // Function to format WhatsApp message - UPDATED with proper formatting
|
|
999
|
+
// const formatWhatsAppMessage = (
|
|
1000
|
+
// values: Record<string, any>,
|
|
1001
|
+
// fields: FormField[],
|
|
1002
|
+
// header?: string,
|
|
1003
|
+
// footer?: string
|
|
1004
|
+
// ): string => {
|
|
1005
|
+
// // Build message lines
|
|
1006
|
+
// let message = '';
|
|
1007
|
+
// // Add header if provided
|
|
1008
|
+
// if (header) {
|
|
1009
|
+
// message += `${header}\n\n`;
|
|
1010
|
+
// }
|
|
1011
|
+
// // Filter out empty/null/undefined values
|
|
1012
|
+
// const nonEmptyFields = fields.filter(field => {
|
|
1013
|
+
// const value = values[field.name];
|
|
1014
|
+
// // Skip if value is undefined, null, or empty string
|
|
1015
|
+
// if (value === undefined || value === null || value === '') {
|
|
1016
|
+
// return false;
|
|
1017
|
+
// }
|
|
1018
|
+
// // Skip if array is empty
|
|
1019
|
+
// if (Array.isArray(value) && value.length === 0) {
|
|
1020
|
+
// return false;
|
|
1021
|
+
// }
|
|
1022
|
+
// // Skip if checkbox is false
|
|
1023
|
+
// if (field.type === 'checkbox' && !field.multiple && value === false) {
|
|
1024
|
+
// return false;
|
|
1025
|
+
// }
|
|
1026
|
+
// return true;
|
|
1027
|
+
// });
|
|
1028
|
+
// // Format each field
|
|
1029
|
+
// const fieldLines = nonEmptyFields.map(field => {
|
|
1030
|
+
// const value = values[field.name];
|
|
1031
|
+
// let displayValue = value;
|
|
1032
|
+
// // Format array values (for multiple checkboxes)
|
|
1033
|
+
// if (Array.isArray(value)) {
|
|
1034
|
+
// displayValue = value.join(', ');
|
|
1035
|
+
// }
|
|
1036
|
+
// // Format checkbox values
|
|
1037
|
+
// if (field.type === 'checkbox' && !field.multiple) {
|
|
1038
|
+
// displayValue = value ? 'Yes' : 'No';
|
|
1039
|
+
// }
|
|
1040
|
+
// // Format select/radio values
|
|
1041
|
+
// if ((field.type === 'select' || field.type === 'radio') && field.options) {
|
|
1042
|
+
// const option = field.options.find(opt => opt.value === value);
|
|
1043
|
+
// displayValue = option ? option.label : value;
|
|
1044
|
+
// }
|
|
1045
|
+
// // Ensure displayValue is a string and preserve spaces/newlines
|
|
1046
|
+
// displayValue = String(displayValue);
|
|
1047
|
+
// // WhatsApp formatting:
|
|
1048
|
+
// // - Field label on its own line
|
|
1049
|
+
// // - Value on next line wrapped in backticks
|
|
1050
|
+
// // - Double newline between fields for readability
|
|
1051
|
+
// return `${field.label || field.name}\n\`${displayValue}\``;
|
|
1052
|
+
// });
|
|
1053
|
+
// // Join with double newline for spacing
|
|
1054
|
+
// message += fieldLines.join('\n\n');
|
|
1055
|
+
// // Add footer if provided
|
|
1056
|
+
// if (footer) {
|
|
1057
|
+
// message += `\n\n${footer}`;
|
|
1058
|
+
// }
|
|
1059
|
+
// return encodeURIComponent(message);
|
|
1060
|
+
// };
|
|
1061
|
+
// // Main Form Component - FIXED
|
|
1062
|
+
// const Form: React.FC<FormProps> = (props) => {
|
|
1063
|
+
// const {
|
|
1064
|
+
// fields: fieldsProp,
|
|
1065
|
+
// onSubmit: onSubmitProp,
|
|
1066
|
+
// defaultValues: defaultValuesProp = {},
|
|
1067
|
+
// submitText: submitTextProp = 'Submit',
|
|
1068
|
+
// submitBg: submitBgProp = 'primary',
|
|
1069
|
+
// submitPrefix: submitPrefixProp,
|
|
1070
|
+
// submitSuffix: submitSuffixProp,
|
|
1071
|
+
// resetText: resetTextProp = 'Reset',
|
|
1072
|
+
// showReset: showResetProp = true,
|
|
1073
|
+
// isLoading: isLoadingProp = false,
|
|
1074
|
+
// className: classNameProp = '',
|
|
1075
|
+
// layout: layoutProp = 'vertical',
|
|
1076
|
+
// gap: gapProp = '1.5rem',
|
|
1077
|
+
// title: titleProp,
|
|
1078
|
+
// titleSize: titleSizeProp,
|
|
1079
|
+
// titleColor: titleColorProp,
|
|
1080
|
+
// description: descriptionProp,
|
|
1081
|
+
// descriptionSize: descriptionSizeProp,
|
|
1082
|
+
// descriptionColor: descriptionColorProp,
|
|
1083
|
+
// whatsappContact: whatsappContactProp,
|
|
1084
|
+
// width: widthProp,
|
|
1085
|
+
// centered: centeredProp,
|
|
1086
|
+
// whatsappHeader: whatsappHeaderProp,
|
|
1087
|
+
// whatsappFooter: whatsappFooterProp,
|
|
1088
|
+
// fullWidth: fullWidthProp = true,
|
|
1089
|
+
// variant = '',
|
|
1090
|
+
// } = props;
|
|
1091
|
+
// // Use component configuration with variant
|
|
1092
|
+
// const { mergeWithLocal } = useComponentConfiguration('Form', variant);
|
|
1093
|
+
// // Create local props object
|
|
1094
|
+
// const localProps = {
|
|
1095
|
+
// fields: fieldsProp,
|
|
1096
|
+
// onSubmit: onSubmitProp,
|
|
1097
|
+
// defaultValues: defaultValuesProp,
|
|
1098
|
+
// submitText: submitTextProp,
|
|
1099
|
+
// submitBg: submitBgProp,
|
|
1100
|
+
// submitPrefix: submitPrefixProp,
|
|
1101
|
+
// submitSuffix: submitSuffixProp,
|
|
1102
|
+
// resetText: resetTextProp,
|
|
1103
|
+
// showReset: showResetProp,
|
|
1104
|
+
// isLoading: isLoadingProp,
|
|
1105
|
+
// className: classNameProp,
|
|
1106
|
+
// layout: layoutProp,
|
|
1107
|
+
// gap: gapProp,
|
|
1108
|
+
// title: titleProp,
|
|
1109
|
+
// titleSize: titleSizeProp,
|
|
1110
|
+
// titleColor: titleColorProp,
|
|
1111
|
+
// description: descriptionProp,
|
|
1112
|
+
// descriptionSize: descriptionSizeProp,
|
|
1113
|
+
// descriptionColor: descriptionColorProp,
|
|
1114
|
+
// whatsappContact: whatsappContactProp,
|
|
1115
|
+
// width: widthProp,
|
|
1116
|
+
// centered: centeredProp,
|
|
1117
|
+
// whatsappHeader: whatsappHeaderProp,
|
|
1118
|
+
// whatsappFooter: whatsappFooterProp,
|
|
1119
|
+
// fullWidth: fullWidthProp,
|
|
1120
|
+
// variant,
|
|
1121
|
+
// };
|
|
1122
|
+
// // Merge with theme configuration
|
|
1123
|
+
// const { props: mergedProps } = mergeWithLocal(localProps);
|
|
1124
|
+
// // Destructure with proper priority: local props override config props
|
|
1125
|
+
// const fields = fieldsProp !== undefined ? fieldsProp : mergedProps.fields;
|
|
1126
|
+
// const onSubmit = onSubmitProp !== undefined ? onSubmitProp : mergedProps.onSubmit;
|
|
1127
|
+
// const defaultValues = defaultValuesProp !== undefined ? defaultValuesProp : mergedProps.defaultValues;
|
|
1128
|
+
// const submitText = submitTextProp !== undefined ? submitTextProp : mergedProps.submitText;
|
|
1129
|
+
// const submitBg = submitBgProp !== undefined ? submitBgProp : mergedProps.submitBg;
|
|
1130
|
+
// const submitPrefix = submitPrefixProp !== undefined ? submitPrefixProp : mergedProps.submitPrefix;
|
|
1131
|
+
// const submitSuffix = submitSuffixProp !== undefined ? submitSuffixProp : mergedProps.submitSuffix;
|
|
1132
|
+
// const resetText = resetTextProp !== undefined ? resetTextProp : mergedProps.resetText;
|
|
1133
|
+
// const showReset = showResetProp !== undefined ? showResetProp : mergedProps.showReset;
|
|
1134
|
+
// const isLoading = isLoadingProp !== undefined ? isLoadingProp : mergedProps.isLoading;
|
|
1135
|
+
// const className = classNameProp !== undefined ? classNameProp : mergedProps.className;
|
|
1136
|
+
// const layout = layoutProp !== undefined ? layoutProp : mergedProps.layout;
|
|
1137
|
+
// const gap = gapProp !== undefined ? gapProp : mergedProps.gap;
|
|
1138
|
+
// const title = titleProp !== undefined ? titleProp : mergedProps.title;
|
|
1139
|
+
// const titleSize = titleSizeProp !== undefined ? titleSizeProp : mergedProps.titleSize;
|
|
1140
|
+
// const titleColor = titleColorProp !== undefined ? titleColorProp : mergedProps.titleColor;
|
|
1141
|
+
// const description = descriptionProp !== undefined ? descriptionProp : mergedProps.description;
|
|
1142
|
+
// const descriptionSize = descriptionSizeProp !== undefined ? descriptionSizeProp : mergedProps.descriptionSize;
|
|
1143
|
+
// const descriptionColor = descriptionColorProp !== undefined ? descriptionColorProp : mergedProps.descriptionColor;
|
|
1144
|
+
// const whatsappContact = whatsappContactProp !== undefined ? whatsappContactProp : mergedProps.whatsappContact;
|
|
1145
|
+
// const width = widthProp !== undefined ? widthProp : mergedProps.width;
|
|
1146
|
+
// const centered = centeredProp !== undefined ? centeredProp : mergedProps.centered;
|
|
1147
|
+
// const whatsappHeader = whatsappHeaderProp !== undefined ? whatsappHeaderProp : mergedProps.whatsappHeader;
|
|
1148
|
+
// const whatsappFooter = whatsappFooterProp !== undefined ? whatsappFooterProp : mergedProps.whatsappFooter;
|
|
1149
|
+
// const fullWidth = fullWidthProp !== undefined ? fullWidthProp : mergedProps.fullWidth;
|
|
1150
|
+
// // Parse JSON inputs
|
|
1151
|
+
// const parsedFields = useMemo(() => parseJsonInput(fields, [] as FormField[]), [fields]);
|
|
1152
|
+
// const parsedDefaultValues = useMemo(() => parseJsonInput(defaultValues, {} as Record<string, any>), [defaultValues]);
|
|
1153
|
+
// // State management
|
|
1154
|
+
// const [errors, setErrors] = useState<Record<string, string>>({});
|
|
1155
|
+
// const [touched, setTouched] = useState<Record<string, boolean>>({});
|
|
1156
|
+
// const [formValues, setFormValues] = useState<Record<string, any>>(() => {
|
|
1157
|
+
// // Initialize form values from defaultValues and field values
|
|
1158
|
+
// const initialValues: Record<string, any> = {};
|
|
1159
|
+
// parsedFields.forEach((field: any) => {
|
|
1160
|
+
// if (field.value !== undefined) {
|
|
1161
|
+
// initialValues[field.name] = field.value;
|
|
1162
|
+
// } else if (parsedDefaultValues[field.name] !== undefined) {
|
|
1163
|
+
// initialValues[field.name] = parsedDefaultValues[field.name];
|
|
1164
|
+
// } else {
|
|
1165
|
+
// // Set default empty values
|
|
1166
|
+
// if (field.type === 'checkbox' && field.multiple) {
|
|
1167
|
+
// initialValues[field.name] = [];
|
|
1168
|
+
// } else if (field.type === 'checkbox') {
|
|
1169
|
+
// initialValues[field.name] = false;
|
|
1170
|
+
// } else {
|
|
1171
|
+
// initialValues[field.name] = '';
|
|
1172
|
+
// }
|
|
1173
|
+
// }
|
|
1174
|
+
// });
|
|
1175
|
+
// return initialValues;
|
|
1176
|
+
// });
|
|
1177
|
+
// const [isSubmitting, setIsSubmitting] = useState(false);
|
|
1178
|
+
// // Update form values when defaultValues prop changes
|
|
1179
|
+
// useEffect(() => {
|
|
1180
|
+
// if (Object.keys(parsedDefaultValues).length > 0) {
|
|
1181
|
+
// setFormValues(prev => ({
|
|
1182
|
+
// ...prev,
|
|
1183
|
+
// ...parsedDefaultValues
|
|
1184
|
+
// }));
|
|
1185
|
+
// }
|
|
1186
|
+
// }, [parsedDefaultValues]);
|
|
1187
|
+
// // Validate a single field
|
|
1188
|
+
// const validateField = useCallback((field: FormField, value: any): string | null => {
|
|
1189
|
+
// // Required validation
|
|
1190
|
+
// if (field.required) {
|
|
1191
|
+
// if (value === undefined || value === null || value === '') {
|
|
1192
|
+
// return `${field.label || field.name} is required`;
|
|
1193
|
+
// }
|
|
1194
|
+
// if (field.type === 'checkbox' && field.multiple && Array.isArray(value) && value.length === 0) {
|
|
1195
|
+
// return `${field.label || field.name} is required`;
|
|
1196
|
+
// }
|
|
1197
|
+
// if (field.type === 'checkbox' && !field.multiple && value === false) {
|
|
1198
|
+
// return `${field.label || field.name} is required`;
|
|
1199
|
+
// }
|
|
1200
|
+
// }
|
|
1201
|
+
// // Type-specific validations (only for non-empty values)
|
|
1202
|
+
// if (value && (typeof value !== 'string' || value !== '')) {
|
|
1203
|
+
// // Email validation
|
|
1204
|
+
// if (field.type === 'email' && typeof value === 'string') {
|
|
1205
|
+
// const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1206
|
+
// if (!emailRegex.test(value)) {
|
|
1207
|
+
// return 'Please enter a valid email address';
|
|
1208
|
+
// }
|
|
1209
|
+
// }
|
|
1210
|
+
// // Number validation
|
|
1211
|
+
// if (field.type === 'number') {
|
|
1212
|
+
// let numValue: number;
|
|
1213
|
+
// if (typeof value === 'string') {
|
|
1214
|
+
// numValue = parseFloat(value);
|
|
1215
|
+
// } else if (typeof value === 'number') {
|
|
1216
|
+
// numValue = value;
|
|
1217
|
+
// } else {
|
|
1218
|
+
// return 'Please enter a valid number';
|
|
1219
|
+
// }
|
|
1220
|
+
// if (isNaN(numValue)) {
|
|
1221
|
+
// return 'Please enter a valid number';
|
|
1222
|
+
// }
|
|
1223
|
+
// const min = field.inputProps?.min;
|
|
1224
|
+
// const max = field.inputProps?.max;
|
|
1225
|
+
// if (min !== undefined && numValue < parseFloat(min.toString())) {
|
|
1226
|
+
// return `Minimum value is ${min}`;
|
|
1227
|
+
// }
|
|
1228
|
+
// if (max !== undefined && numValue > parseFloat(max.toString())) {
|
|
1229
|
+
// return `Maximum value is ${max}`;
|
|
1230
|
+
// }
|
|
1231
|
+
// }
|
|
1232
|
+
// // Phone validation (basic) - only for strings
|
|
1233
|
+
// if (field.type === 'tel' && typeof value === 'string') {
|
|
1234
|
+
// const phoneRegex = /^[\+]?[1-9][\d]{0,17}$/;
|
|
1235
|
+
// if (!phoneRegex.test(value.replace(/[\s+\-()]/g, ''))) {
|
|
1236
|
+
// return 'Please enter a valid phone number';
|
|
1237
|
+
// }
|
|
1238
|
+
// }
|
|
1239
|
+
// // Length validation for strings
|
|
1240
|
+
// if (typeof value === 'string') {
|
|
1241
|
+
// const minLength = field.inputProps?.minLength;
|
|
1242
|
+
// const maxLength = field.inputProps?.maxLength;
|
|
1243
|
+
// if (minLength && value.length < minLength) {
|
|
1244
|
+
// return `Minimum ${minLength} characters required`;
|
|
1245
|
+
// }
|
|
1246
|
+
// if (maxLength && value.length > maxLength) {
|
|
1247
|
+
// return `Maximum ${maxLength} characters allowed`;
|
|
1248
|
+
// }
|
|
1249
|
+
// }
|
|
1250
|
+
// // Pattern validation if provided - only for strings
|
|
1251
|
+
// if (field.inputProps?.pattern && typeof value === 'string') {
|
|
1252
|
+
// try {
|
|
1253
|
+
// const regex = new RegExp(field.inputProps.pattern);
|
|
1254
|
+
// if (!regex.test(value)) {
|
|
1255
|
+
// return 'Invalid format';
|
|
1256
|
+
// }
|
|
1257
|
+
// } catch (error) {
|
|
1258
|
+
// console.warn('Invalid regex pattern:', field.inputProps.pattern);
|
|
1259
|
+
// }
|
|
1260
|
+
// }
|
|
1261
|
+
// }
|
|
1262
|
+
// return null;
|
|
1263
|
+
// }, []);
|
|
1264
|
+
// // Validate form
|
|
1265
|
+
// const validateForm = useCallback((): boolean => {
|
|
1266
|
+
// const newErrors: Record<string, string> = {};
|
|
1267
|
+
// let hasErrors = false;
|
|
1268
|
+
// parsedFields.forEach((field: any) => {
|
|
1269
|
+
// const value = formValues[field.name];
|
|
1270
|
+
// const error = validateField(field, value);
|
|
1271
|
+
// if (error) {
|
|
1272
|
+
// newErrors[field.name] = error;
|
|
1273
|
+
// hasErrors = true;
|
|
1274
|
+
// }
|
|
1275
|
+
// });
|
|
1276
|
+
// setErrors(newErrors);
|
|
1277
|
+
// return !hasErrors;
|
|
1278
|
+
// }, [parsedFields, validateField, formValues]);
|
|
1279
|
+
// // Handle field change for checkboxes and radios
|
|
1280
|
+
// const handleFieldChange = useCallback((fieldName: string, newValue: any) => {
|
|
1281
|
+
// // Update form values
|
|
1282
|
+
// setFormValues(prev => ({
|
|
1283
|
+
// ...prev,
|
|
1284
|
+
// [fieldName]: newValue
|
|
1285
|
+
// }));
|
|
1286
|
+
// // Update touched state
|
|
1287
|
+
// setTouched(prev => ({ ...prev, [fieldName]: true }));
|
|
1288
|
+
// // Validate the changed field
|
|
1289
|
+
// const field = parsedFields.find((f: any) => f.name === fieldName);
|
|
1290
|
+
// if (!field) return;
|
|
1291
|
+
// const error = validateField(field, newValue);
|
|
1292
|
+
// setErrors(prev => {
|
|
1293
|
+
// if (error) {
|
|
1294
|
+
// return { ...prev, [fieldName]: error };
|
|
1295
|
+
// } else {
|
|
1296
|
+
// const newErrors = { ...prev };
|
|
1297
|
+
// delete newErrors[fieldName];
|
|
1298
|
+
// return newErrors;
|
|
1299
|
+
// }
|
|
1300
|
+
// });
|
|
1301
|
+
// }, [parsedFields, validateField]);
|
|
1302
|
+
// // FIXED: Handle input change - pass event directly to Input component
|
|
1303
|
+
// const handleInputEventChange = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
|
1304
|
+
// const fieldName = e.target.name;
|
|
1305
|
+
// const value = e.target.value;
|
|
1306
|
+
// // Update form values - PASS VALUE AS-IS (preserves spaces)
|
|
1307
|
+
// setFormValues(prev => ({
|
|
1308
|
+
// ...prev,
|
|
1309
|
+
// [fieldName]: value
|
|
1310
|
+
// }));
|
|
1311
|
+
// // Update touched state
|
|
1312
|
+
// setTouched(prev => ({ ...prev, [fieldName]: true }));
|
|
1313
|
+
// // Validate the changed field
|
|
1314
|
+
// const field = parsedFields.find((f: any) => f.name === fieldName);
|
|
1315
|
+
// if (!field) return;
|
|
1316
|
+
// const error = validateField(field, value);
|
|
1317
|
+
// setErrors(prev => {
|
|
1318
|
+
// if (error) {
|
|
1319
|
+
// return { ...prev, [fieldName]: error };
|
|
1320
|
+
// } else {
|
|
1321
|
+
// const newErrors = { ...prev };
|
|
1322
|
+
// delete newErrors[fieldName];
|
|
1323
|
+
// return newErrors;
|
|
1324
|
+
// }
|
|
1325
|
+
// });
|
|
1326
|
+
// }, [parsedFields, validateField]);
|
|
1327
|
+
// // FIXED: Handle blur event - pass event directly to Input component
|
|
1328
|
+
// const handleInputEventBlur = useCallback((e: React.FocusEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
|
1329
|
+
// const fieldName = e.target.name;
|
|
1330
|
+
// setTouched(prev => ({ ...prev, [fieldName]: true }));
|
|
1331
|
+
// // Validate the field
|
|
1332
|
+
// const field = parsedFields.find((f: any) => f.name === fieldName);
|
|
1333
|
+
// if (!field) return;
|
|
1334
|
+
// const value = formValues[fieldName];
|
|
1335
|
+
// const error = validateField(field, value);
|
|
1336
|
+
// setErrors(prev => {
|
|
1337
|
+
// if (error) {
|
|
1338
|
+
// return { ...prev, [fieldName]: error };
|
|
1339
|
+
// } else {
|
|
1340
|
+
// const newErrors = { ...prev };
|
|
1341
|
+
// delete newErrors[fieldName];
|
|
1342
|
+
// return newErrors;
|
|
1343
|
+
// }
|
|
1344
|
+
// });
|
|
1345
|
+
// }, [parsedFields, validateField, formValues]);
|
|
1346
|
+
// // Handle form submission
|
|
1347
|
+
// const handleSubmit = useCallback(async (e: React.FormEvent) => {
|
|
1348
|
+
// e.preventDefault();
|
|
1349
|
+
// // Mark all fields as touched
|
|
1350
|
+
// const allTouched: Record<string, boolean> = {};
|
|
1351
|
+
// parsedFields.forEach((field: any) => {
|
|
1352
|
+
// allTouched[field.name] = true;
|
|
1353
|
+
// });
|
|
1354
|
+
// setTouched(allTouched);
|
|
1355
|
+
// // Validate form
|
|
1356
|
+
// const isValid = validateForm();
|
|
1357
|
+
// if (!isValid) {
|
|
1358
|
+
// // Scroll to first error
|
|
1359
|
+
// const firstErrorField = Object.keys(errors)[0];
|
|
1360
|
+
// if (firstErrorField) {
|
|
1361
|
+
// const element = document.getElementById(`form-field-${firstErrorField}`);
|
|
1362
|
+
// element?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1363
|
+
// }
|
|
1364
|
+
// return;
|
|
1365
|
+
// }
|
|
1366
|
+
// // Get form data and submit
|
|
1367
|
+
// setIsSubmitting(true);
|
|
1368
|
+
// try {
|
|
1369
|
+
// if (whatsappContact) {
|
|
1370
|
+
// // Submit via WhatsApp
|
|
1371
|
+
// const message = formatWhatsAppMessage(formValues, parsedFields, whatsappHeader, whatsappFooter);
|
|
1372
|
+
// const cleanPhone = whatsappContact.replace(/[\s+\-()]/g, '');
|
|
1373
|
+
// const whatsappUrl = `https://wa.me/${cleanPhone}?text=${message}`;
|
|
1374
|
+
// window.open(whatsappUrl, '_blank');
|
|
1375
|
+
// // Still call onSubmit if provided
|
|
1376
|
+
// if (onSubmit) {
|
|
1377
|
+
// await onSubmit(formValues, true);
|
|
1378
|
+
// }
|
|
1379
|
+
// } else {
|
|
1380
|
+
// // Regular submission
|
|
1381
|
+
// if (onSubmit) {
|
|
1382
|
+
// await onSubmit(formValues, false);
|
|
1383
|
+
// }
|
|
1384
|
+
// }
|
|
1385
|
+
// } catch (error) {
|
|
1386
|
+
// console.error('Form submission error:', error);
|
|
1387
|
+
// setErrors(prev => ({
|
|
1388
|
+
// ...prev,
|
|
1389
|
+
// _form: 'There was an error submitting the form. Please try again.'
|
|
1390
|
+
// }));
|
|
1391
|
+
// } finally {
|
|
1392
|
+
// setIsSubmitting(false);
|
|
1393
|
+
// }
|
|
1394
|
+
// }, [parsedFields, validateForm, formValues, whatsappContact, whatsappHeader, whatsappFooter, onSubmit, errors]);
|
|
1395
|
+
// // Handle form reset
|
|
1396
|
+
// const handleReset = useCallback(() => {
|
|
1397
|
+
// // Reset to initial values
|
|
1398
|
+
// const initialValues: Record<string, any> = {};
|
|
1399
|
+
// parsedFields.forEach((field: any) => {
|
|
1400
|
+
// if (field.value !== undefined) {
|
|
1401
|
+
// initialValues[field.name] = field.value;
|
|
1402
|
+
// } else if (parsedDefaultValues[field.name] !== undefined) {
|
|
1403
|
+
// initialValues[field.name] = parsedDefaultValues[field.name];
|
|
1404
|
+
// } else {
|
|
1405
|
+
// if (field.type === 'checkbox' && field.multiple) {
|
|
1406
|
+
// initialValues[field.name] = [];
|
|
1407
|
+
// } else if (field.type === 'checkbox') {
|
|
1408
|
+
// initialValues[field.name] = false;
|
|
1409
|
+
// } else {
|
|
1410
|
+
// initialValues[field.name] = '';
|
|
1411
|
+
// }
|
|
1412
|
+
// }
|
|
1413
|
+
// });
|
|
1414
|
+
// setFormValues(initialValues);
|
|
1415
|
+
// setErrors({});
|
|
1416
|
+
// setTouched({});
|
|
1417
|
+
// }, [parsedFields, parsedDefaultValues]);
|
|
1418
|
+
// // Get field status for Input component
|
|
1419
|
+
// const getFieldStatus = useCallback((fieldName: string): 'success' | 'warning' | 'danger' | 'info' | undefined => {
|
|
1420
|
+
// const error = errors[fieldName];
|
|
1421
|
+
// const isTouched = touched[fieldName];
|
|
1422
|
+
// if (error) return 'danger';
|
|
1423
|
+
// if (isTouched && !error && formValues[fieldName] !== '') {
|
|
1424
|
+
// return 'success';
|
|
1425
|
+
// }
|
|
1426
|
+
// return undefined;
|
|
1427
|
+
// }, [errors, touched, formValues]);
|
|
1428
|
+
// // Check if form is valid for submission
|
|
1429
|
+
// const isFormValid = useMemo(() => {
|
|
1430
|
+
// // Check if any required fields are empty
|
|
1431
|
+
// const hasEmptyRequiredFields = parsedFields.some((field: any) => {
|
|
1432
|
+
// if (!field.required) return false;
|
|
1433
|
+
// const value = formValues[field.name];
|
|
1434
|
+
// if (field.type === 'checkbox' && field.multiple) {
|
|
1435
|
+
// return !Array.isArray(value) || value.length === 0;
|
|
1436
|
+
// }
|
|
1437
|
+
// if (field.type === 'checkbox') {
|
|
1438
|
+
// return value === false;
|
|
1439
|
+
// }
|
|
1440
|
+
// return !value || value === '';
|
|
1441
|
+
// });
|
|
1442
|
+
// // Check if there are any validation errors
|
|
1443
|
+
// const hasValidationErrors = Object.keys(errors).length > 0;
|
|
1444
|
+
// return !hasEmptyRequiredFields && !hasValidationErrors;
|
|
1445
|
+
// }, [parsedFields, formValues, errors]);
|
|
1446
|
+
// // Submit button disabled state
|
|
1447
|
+
// const isSubmitDisabled = isSubmitting || isLoading || !isFormValid;
|
|
1448
|
+
// // Check if WhatsApp is configured
|
|
1449
|
+
// const hasWhatsApp = !!whatsappContact;
|
|
1450
|
+
// // Render field based on type - FIXED
|
|
1451
|
+
// const renderField = useCallback((field: FormField) => {
|
|
1452
|
+
// const status = getFieldStatus(field.name);
|
|
1453
|
+
// const error = errors[field.name];
|
|
1454
|
+
// const isTouched = touched[field.name];
|
|
1455
|
+
// const showError = error && isTouched;
|
|
1456
|
+
// const value = formValues[field.name];
|
|
1457
|
+
// // Field wrapper classes
|
|
1458
|
+
// const wrapperClass = `col min-w-200 field ${showError ? 'field-error' : ''}`.trim();
|
|
1459
|
+
// // Helper text (error takes priority)
|
|
1460
|
+
// const helperText = showError ? error : field.helperText;
|
|
1461
|
+
// // Generate unique ID for the input
|
|
1462
|
+
// const inputId = field.inputProps?.id || `form-field-${field.name}`;
|
|
1463
|
+
// // Base props for Input component
|
|
1464
|
+
// const baseProps: any = {
|
|
1465
|
+
// id: inputId,
|
|
1466
|
+
// name: field.name, // IMPORTANT: Make sure name is passed
|
|
1467
|
+
// label: field.label,
|
|
1468
|
+
// placeholder: field.placeholder,
|
|
1469
|
+
// helperText: helperText,
|
|
1470
|
+
// disabled: field.disabled || isLoading,
|
|
1471
|
+
// fullWidth: fullWidth,
|
|
1472
|
+
// ...field.inputProps,
|
|
1473
|
+
// };
|
|
1474
|
+
// // Only add status if it's not undefined
|
|
1475
|
+
// if (status !== undefined) {
|
|
1476
|
+
// baseProps.status = status;
|
|
1477
|
+
// }
|
|
1478
|
+
// // Render based on field type
|
|
1479
|
+
// switch (field.type) {
|
|
1480
|
+
// case 'checkbox':
|
|
1481
|
+
// if (field.options?.length) {
|
|
1482
|
+
// // Multiple checkboxes (checkbox group)
|
|
1483
|
+
// const checkedValues = Array.isArray(value) ? value : [];
|
|
1484
|
+
// return (
|
|
1485
|
+
// <div key={field.name} className={wrapperClass}>
|
|
1486
|
+
// {field.label && (
|
|
1487
|
+
// <div className="form-label" style={{ marginBottom: '0.5rem' }}>
|
|
1488
|
+
// <Text text={field.label + (field.required ? ' *' : '')} size="sm" color="text" bold />
|
|
1489
|
+
// </div>
|
|
1490
|
+
// )}
|
|
1491
|
+
// <Flex direction="column" gap="0.5rem">
|
|
1492
|
+
// {field.options.map((option, index) => {
|
|
1493
|
+
// const isChecked = checkedValues.includes(option.value);
|
|
1494
|
+
// return (
|
|
1495
|
+
// <FormCheckbox
|
|
1496
|
+
// key={`${field.name}_${index}`}
|
|
1497
|
+
// id={`${inputId}_${index}`}
|
|
1498
|
+
// label={option.label}
|
|
1499
|
+
// checked={isChecked}
|
|
1500
|
+
// onChange={(checked) => {
|
|
1501
|
+
// if (checked) {
|
|
1502
|
+
// // Add value to array
|
|
1503
|
+
// const newValues = [...checkedValues, option.value];
|
|
1504
|
+
// handleFieldChange(field.name, newValues);
|
|
1505
|
+
// } else {
|
|
1506
|
+
// // Remove value from array
|
|
1507
|
+
// const newValues = checkedValues.filter(v => v !== option.value);
|
|
1508
|
+
// handleFieldChange(field.name, newValues);
|
|
1509
|
+
// }
|
|
1510
|
+
// }}
|
|
1511
|
+
// disabled={field.disabled || option.disabled || isLoading}
|
|
1512
|
+
// required={field.required && index === 0}
|
|
1513
|
+
// value={option.value}
|
|
1514
|
+
// />
|
|
1515
|
+
// );
|
|
1516
|
+
// })}
|
|
1517
|
+
// </Flex>
|
|
1518
|
+
// </div>
|
|
1519
|
+
// );
|
|
1520
|
+
// } else {
|
|
1521
|
+
// // Single checkbox (boolean)
|
|
1522
|
+
// return (
|
|
1523
|
+
// <div key={field.name} className={wrapperClass}>
|
|
1524
|
+
// <FormCheckbox
|
|
1525
|
+
// label={field.label}
|
|
1526
|
+
// checked={!!value}
|
|
1527
|
+
// onChange={(checked) => handleFieldChange(field.name, checked)}
|
|
1528
|
+
// disabled={field.disabled || isLoading}
|
|
1529
|
+
// required={field.required}
|
|
1530
|
+
// id={inputId}
|
|
1531
|
+
// />
|
|
1532
|
+
// </div>
|
|
1533
|
+
// );
|
|
1534
|
+
// }
|
|
1535
|
+
// case 'radio':
|
|
1536
|
+
// if (!field.options?.length) return null;
|
|
1537
|
+
// return (
|
|
1538
|
+
// <div key={field.name} className={wrapperClass}>
|
|
1539
|
+
// {field.label && (
|
|
1540
|
+
// <div className="form-label" style={{ marginBottom: '0.5rem' }}>
|
|
1541
|
+
// <Text text={field.label + (field.required ? ' *' : '')} size="sm" color="text" bold />
|
|
1542
|
+
// </div>
|
|
1543
|
+
// )}
|
|
1544
|
+
// <Flex direction="column" gap="0.5rem">
|
|
1545
|
+
// {field.options.map((option, index) => (
|
|
1546
|
+
// <FormRadio
|
|
1547
|
+
// key={`${field.name}_${index}`}
|
|
1548
|
+
// id={`${inputId}_${index}`}
|
|
1549
|
+
// label={option.label}
|
|
1550
|
+
// checked={value === option.value}
|
|
1551
|
+
// onChange={() => handleFieldChange(field.name, option.value)}
|
|
1552
|
+
// disabled={field.disabled || option.disabled || isLoading}
|
|
1553
|
+
// required={field.required && index === 0}
|
|
1554
|
+
// value={option.value}
|
|
1555
|
+
// />
|
|
1556
|
+
// ))}
|
|
1557
|
+
// </Flex>
|
|
1558
|
+
// </div>
|
|
1559
|
+
// );
|
|
1560
|
+
// case 'textarea':
|
|
1561
|
+
// return (
|
|
1562
|
+
// <div key={field.name} className={wrapperClass}>
|
|
1563
|
+
// <Input
|
|
1564
|
+
// multiline
|
|
1565
|
+
// rows={field.inputProps?.rows || 4}
|
|
1566
|
+
// value={value || ''}
|
|
1567
|
+
// onChange={handleInputEventChange} // FIXED: Pass event handler directly
|
|
1568
|
+
// onBlur={handleInputEventBlur} // FIXED: Pass event handler directly
|
|
1569
|
+
// {...baseProps}
|
|
1570
|
+
// />
|
|
1571
|
+
// </div>
|
|
1572
|
+
// );
|
|
1573
|
+
// case 'select':
|
|
1574
|
+
// return (
|
|
1575
|
+
// <div key={field.name} className={wrapperClass}>
|
|
1576
|
+
// <Input
|
|
1577
|
+
// select
|
|
1578
|
+
// options={field.options?.map(opt => ({ text: opt.label, value: opt.value })) || []}
|
|
1579
|
+
// value={value || ''}
|
|
1580
|
+
// onChange={handleInputEventChange} // FIXED: Pass event handler directly
|
|
1581
|
+
// onBlur={handleInputEventBlur} // FIXED: Pass event handler directly
|
|
1582
|
+
// {...baseProps}
|
|
1583
|
+
// />
|
|
1584
|
+
// </div>
|
|
1585
|
+
// );
|
|
1586
|
+
// default:
|
|
1587
|
+
// // text, email, number, tel, date, file, password
|
|
1588
|
+
// return (
|
|
1589
|
+
// <div key={field.name} className={wrapperClass}>
|
|
1590
|
+
// <Input
|
|
1591
|
+
// type={field.type}
|
|
1592
|
+
// value={value || ''}
|
|
1593
|
+
// onChange={handleInputEventChange} // FIXED: Pass event handler directly
|
|
1594
|
+
// onBlur={handleInputEventBlur} // FIXED: Pass event handler directly
|
|
1595
|
+
// {...baseProps}
|
|
1596
|
+
// />
|
|
1597
|
+
// </div>
|
|
1598
|
+
// );
|
|
1599
|
+
// }
|
|
1600
|
+
// }, [errors, touched, formValues, isLoading, getFieldStatus, handleFieldChange, handleInputEventChange, handleInputEventBlur, fullWidth]);
|
|
1601
|
+
// // Don't render if no fields are configured
|
|
1602
|
+
// if (parsedFields.length === 0) {
|
|
1603
|
+
// console.warn('Form: No fields configured. Please provide fields prop or configure via theme.');
|
|
1604
|
+
// return null;
|
|
1605
|
+
// }
|
|
1606
|
+
// return (
|
|
1607
|
+
// <div className={`form-wrapper ${centered ? 'center' : ''} ${className}`} style={{ width: "100%", maxWidth: width || "450px" }}>
|
|
1608
|
+
// {/* Title Section */}
|
|
1609
|
+
// {title && (
|
|
1610
|
+
// <div className="form-header" style={{ marginBottom: '2rem' }}>
|
|
1611
|
+
// <Text
|
|
1612
|
+
// text={title}
|
|
1613
|
+
// size={titleSize || "3xl"}
|
|
1614
|
+
// color={titleColor || ""}
|
|
1615
|
+
// block
|
|
1616
|
+
// />
|
|
1617
|
+
// {description && (
|
|
1618
|
+
// <Text article
|
|
1619
|
+
// size={descriptionSize || "sm"}
|
|
1620
|
+
// color={descriptionColor || ""}
|
|
1621
|
+
// >
|
|
1622
|
+
// <div
|
|
1623
|
+
// className="article text-sm"
|
|
1624
|
+
// dangerouslySetInnerHTML={{ __html: description }}
|
|
1625
|
+
// />
|
|
1626
|
+
// </Text>
|
|
1627
|
+
// )}
|
|
1628
|
+
// </div>
|
|
1629
|
+
// )}
|
|
1630
|
+
// <form
|
|
1631
|
+
// className="form"
|
|
1632
|
+
// onSubmit={handleSubmit}
|
|
1633
|
+
// style={{ width: '100%' }}
|
|
1634
|
+
// >
|
|
1635
|
+
// {/* Form fields wrapped in Flex */}
|
|
1636
|
+
// <Flex
|
|
1637
|
+
// direction={layout === 'horizontal' ? 'row' : 'column'}
|
|
1638
|
+
// gap={gap}
|
|
1639
|
+
// width='100%'
|
|
1640
|
+
// >
|
|
1641
|
+
// {parsedFields.map(renderField)}
|
|
1642
|
+
// </Flex>
|
|
1643
|
+
// {/* Form actions - SINGLE BUTTON */}
|
|
1644
|
+
// <Flex direction="column" gap="1rem" style={{ marginTop: '2rem', width: fullWidth ? '100%' : undefined }}>
|
|
1645
|
+
// {/* Single submit button */}
|
|
1646
|
+
// <Button
|
|
1647
|
+
// type="submit"
|
|
1648
|
+
// text={hasWhatsApp ? `Send via WhatsApp` : submitText}
|
|
1649
|
+
// bg={submitBg || "primary"}
|
|
1650
|
+
// raised
|
|
1651
|
+
// prefix={submitPrefix || hasWhatsApp ? <PiWhatsappLogo /> : <PiPaperPlaneTilt />}
|
|
1652
|
+
// suffix={submitSuffix}
|
|
1653
|
+
// disabled={isSubmitDisabled}
|
|
1654
|
+
// isLoading={isSubmitting || isLoading}
|
|
1655
|
+
// fullWidth={fullWidth}
|
|
1656
|
+
// />
|
|
1657
|
+
// </Flex>
|
|
1658
|
+
// </form>
|
|
1659
|
+
// </div>
|
|
1660
|
+
// );
|
|
1661
|
+
// };
|
|
1662
|
+
// export default Form;
|