@bookinglab/booking-ui-react 1.6.1 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -23,7 +23,7 @@ interface FieldConfig {
23
23
  /** Display label for the field */
24
24
  label: string;
25
25
  /** Input type */
26
- type: 'text' | 'email' | 'tel' | 'password' | 'select';
26
+ type: 'text' | 'email' | 'tel' | 'password' | 'select' | 'check';
27
27
  /** Whether the field is required */
28
28
  required?: boolean;
29
29
  /** Placeholder text */
@@ -53,6 +53,10 @@ interface RegistrationFormClassNames {
53
53
  input?: string;
54
54
  /** Input elements in error state */
55
55
  inputError?: string;
56
+ /** Checkbox input specifically */
57
+ checkbox?: string;
58
+ /** Checkbox wrapper (label + checkbox) */
59
+ checkboxWrapper?: string;
56
60
  /** Help text below fields */
57
61
  helpText?: string;
58
62
  /** Error message text */
@@ -60,6 +64,27 @@ interface RegistrationFormClassNames {
60
64
  /** Submit button */
61
65
  button?: string;
62
66
  }
67
+ /**
68
+ * Settings for individual default fields (hide, disable, change required status)
69
+ */
70
+ interface RegistrationFieldSettings {
71
+ /** Settings for the Address 1 field */
72
+ address1?: {
73
+ hidden?: boolean;
74
+ };
75
+ /** Settings for the Address 2 field */
76
+ address2?: {
77
+ hidden?: boolean;
78
+ };
79
+ /** Settings for the Town/City field */
80
+ city?: {
81
+ hidden?: boolean;
82
+ };
83
+ /** Settings for the Postcode field */
84
+ postcode?: {
85
+ hidden?: boolean;
86
+ };
87
+ }
63
88
  /**
64
89
  * Props for the RegistrationForm component
65
90
  */
@@ -80,6 +105,8 @@ interface RegistrationFormProps {
80
105
  className?: string;
81
106
  /** Custom class names for styling individual elements */
82
107
  classNames?: RegistrationFormClassNames;
108
+ /** Settings for controlling visibility of default fields */
109
+ fieldSettings?: RegistrationFieldSettings;
83
110
  }
84
111
  /**
85
112
  * Imperative handle methods exposed via ref
@@ -366,6 +393,9 @@ declare function BookingForm({ questions, onSubmit, submitLabel, className, labe
366
393
  * Features:
367
394
  * - Customizable fields via props
368
395
  * - Built-in validation with blur and submit triggers
396
+ * - Email and password confirmation with matching validation
397
+ * - fieldSettings prop to hide address fields
398
+ * - Checkbox field type support for custom questions
369
399
  * - Full accessibility support (ARIA attributes, keyboard navigation)
370
400
  * - Imperative handle for programmatic control (reset, setValues)
371
401
  * - Granular styling via classNames prop
@@ -379,6 +409,7 @@ declare function BookingForm({ questions, onSubmit, submitLabel, className, labe
379
409
  * onSubmit={(values) => console.log(values)}
380
410
  * onChange={(values, isValid) => console.log(isValid)}
381
411
  * submitLabel="Register"
412
+ * fieldSettings={{ address2: { hidden: true }, postcode: { hidden: true } }}
382
413
  * />
383
414
  *
384
415
  * // Programmatic control
@@ -460,4 +491,4 @@ declare function minLen(min: number): (value: string) => string | null;
460
491
  */
461
492
  declare function compose(...validators: Array<(value: string) => string | null>): (value: string) => string | null;
462
493
 
463
- export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, type ContactDetailsAnswerEntry, type ContactDetailsFieldSettings, ContactDetailsForm, type ContactDetailsFormClassNames, type ContactDetailsFormProps, type ContactDetailsFormRef, type ContactDetailsInitialValues, type ContactDetailsQuestionEntry, type ContactDetailsValues, type ContactFieldSettings, type FieldConfig, type FieldOption, type FormErrors, type FormValues, type Question, type QuestionOption, type QuestionSettings, RegistrationForm, type RegistrationFormClassNames, type RegistrationFormErrors, type RegistrationFormProps, type RegistrationFormRef, type RegistrationFormValues, ResetPasswordForm, type ResetPasswordFormClassNames, type ResetPasswordFormProps, type ResetPasswordFormRef, type ResetPasswordFormValues, compose, email, minLen, phone, required, ukPostcode };
494
+ export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, type ContactDetailsAnswerEntry, type ContactDetailsFieldSettings, ContactDetailsForm, type ContactDetailsFormClassNames, type ContactDetailsFormProps, type ContactDetailsFormRef, type ContactDetailsInitialValues, type ContactDetailsQuestionEntry, type ContactDetailsValues, type ContactFieldSettings, type FieldConfig, type FieldOption, type FormErrors, type FormValues, type Question, type QuestionOption, type QuestionSettings, type RegistrationFieldSettings, RegistrationForm, type RegistrationFormClassNames, type RegistrationFormErrors, type RegistrationFormProps, type RegistrationFormRef, type RegistrationFormValues, ResetPasswordForm, type ResetPasswordFormClassNames, type ResetPasswordFormProps, type ResetPasswordFormRef, type ResetPasswordFormValues, compose, email, minLen, phone, required, ukPostcode };
package/dist/index.d.ts CHANGED
@@ -23,7 +23,7 @@ interface FieldConfig {
23
23
  /** Display label for the field */
24
24
  label: string;
25
25
  /** Input type */
26
- type: 'text' | 'email' | 'tel' | 'password' | 'select';
26
+ type: 'text' | 'email' | 'tel' | 'password' | 'select' | 'check';
27
27
  /** Whether the field is required */
28
28
  required?: boolean;
29
29
  /** Placeholder text */
@@ -53,6 +53,10 @@ interface RegistrationFormClassNames {
53
53
  input?: string;
54
54
  /** Input elements in error state */
55
55
  inputError?: string;
56
+ /** Checkbox input specifically */
57
+ checkbox?: string;
58
+ /** Checkbox wrapper (label + checkbox) */
59
+ checkboxWrapper?: string;
56
60
  /** Help text below fields */
57
61
  helpText?: string;
58
62
  /** Error message text */
@@ -60,6 +64,27 @@ interface RegistrationFormClassNames {
60
64
  /** Submit button */
61
65
  button?: string;
62
66
  }
67
+ /**
68
+ * Settings for individual default fields (hide, disable, change required status)
69
+ */
70
+ interface RegistrationFieldSettings {
71
+ /** Settings for the Address 1 field */
72
+ address1?: {
73
+ hidden?: boolean;
74
+ };
75
+ /** Settings for the Address 2 field */
76
+ address2?: {
77
+ hidden?: boolean;
78
+ };
79
+ /** Settings for the Town/City field */
80
+ city?: {
81
+ hidden?: boolean;
82
+ };
83
+ /** Settings for the Postcode field */
84
+ postcode?: {
85
+ hidden?: boolean;
86
+ };
87
+ }
63
88
  /**
64
89
  * Props for the RegistrationForm component
65
90
  */
@@ -80,6 +105,8 @@ interface RegistrationFormProps {
80
105
  className?: string;
81
106
  /** Custom class names for styling individual elements */
82
107
  classNames?: RegistrationFormClassNames;
108
+ /** Settings for controlling visibility of default fields */
109
+ fieldSettings?: RegistrationFieldSettings;
83
110
  }
84
111
  /**
85
112
  * Imperative handle methods exposed via ref
@@ -366,6 +393,9 @@ declare function BookingForm({ questions, onSubmit, submitLabel, className, labe
366
393
  * Features:
367
394
  * - Customizable fields via props
368
395
  * - Built-in validation with blur and submit triggers
396
+ * - Email and password confirmation with matching validation
397
+ * - fieldSettings prop to hide address fields
398
+ * - Checkbox field type support for custom questions
369
399
  * - Full accessibility support (ARIA attributes, keyboard navigation)
370
400
  * - Imperative handle for programmatic control (reset, setValues)
371
401
  * - Granular styling via classNames prop
@@ -379,6 +409,7 @@ declare function BookingForm({ questions, onSubmit, submitLabel, className, labe
379
409
  * onSubmit={(values) => console.log(values)}
380
410
  * onChange={(values, isValid) => console.log(isValid)}
381
411
  * submitLabel="Register"
412
+ * fieldSettings={{ address2: { hidden: true }, postcode: { hidden: true } }}
382
413
  * />
383
414
  *
384
415
  * // Programmatic control
@@ -460,4 +491,4 @@ declare function minLen(min: number): (value: string) => string | null;
460
491
  */
461
492
  declare function compose(...validators: Array<(value: string) => string | null>): (value: string) => string | null;
462
493
 
463
- export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, type ContactDetailsAnswerEntry, type ContactDetailsFieldSettings, ContactDetailsForm, type ContactDetailsFormClassNames, type ContactDetailsFormProps, type ContactDetailsFormRef, type ContactDetailsInitialValues, type ContactDetailsQuestionEntry, type ContactDetailsValues, type ContactFieldSettings, type FieldConfig, type FieldOption, type FormErrors, type FormValues, type Question, type QuestionOption, type QuestionSettings, RegistrationForm, type RegistrationFormClassNames, type RegistrationFormErrors, type RegistrationFormProps, type RegistrationFormRef, type RegistrationFormValues, ResetPasswordForm, type ResetPasswordFormClassNames, type ResetPasswordFormProps, type ResetPasswordFormRef, type ResetPasswordFormValues, compose, email, minLen, phone, required, ukPostcode };
494
+ export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, type ContactDetailsAnswerEntry, type ContactDetailsFieldSettings, ContactDetailsForm, type ContactDetailsFormClassNames, type ContactDetailsFormProps, type ContactDetailsFormRef, type ContactDetailsInitialValues, type ContactDetailsQuestionEntry, type ContactDetailsValues, type ContactFieldSettings, type FieldConfig, type FieldOption, type FormErrors, type FormValues, type Question, type QuestionOption, type QuestionSettings, type RegistrationFieldSettings, RegistrationForm, type RegistrationFormClassNames, type RegistrationFormErrors, type RegistrationFormProps, type RegistrationFormRef, type RegistrationFormValues, ResetPasswordForm, type ResetPasswordFormClassNames, type ResetPasswordFormProps, type ResetPasswordFormRef, type ResetPasswordFormValues, compose, email, minLen, phone, required, ukPostcode };
package/dist/index.js CHANGED
@@ -428,10 +428,12 @@ var DEFAULT_FIELDS = [
428
428
  validate: compose(required, email)
429
429
  },
430
430
  {
431
- name: "password",
432
- label: "Password",
433
- type: "password",
434
- required: false
431
+ name: "verifyEmail",
432
+ label: "Verify email address",
433
+ type: "email",
434
+ required: true,
435
+ // Cross-field validation is handled in the component
436
+ validate: required
435
437
  },
436
438
  {
437
439
  name: "phone",
@@ -440,6 +442,20 @@ var DEFAULT_FIELDS = [
440
442
  required: false,
441
443
  validate: phone
442
444
  },
445
+ {
446
+ name: "password",
447
+ label: "Password",
448
+ type: "password",
449
+ required: true,
450
+ validate: required
451
+ },
452
+ {
453
+ name: "confirmPassword",
454
+ label: "Confirm password",
455
+ type: "password",
456
+ required: true,
457
+ validate: required
458
+ },
443
459
  {
444
460
  name: "address1",
445
461
  label: "Address 1",
@@ -468,6 +484,7 @@ var DEFAULT_FIELDS = [
468
484
  validate: compose(required, ukPostcode)
469
485
  }
470
486
  ];
487
+ var HIDEABLE_FIELDS = ["address1", "address2", "city", "postcode"];
471
488
  var RegistrationForm = react.forwardRef(
472
489
  ({
473
490
  fields = DEFAULT_FIELDS,
@@ -477,31 +494,58 @@ var RegistrationForm = react.forwardRef(
477
494
  validateOnBlur = true,
478
495
  submitLabel = "Submit",
479
496
  className = "",
480
- classNames = {}
497
+ classNames = {},
498
+ fieldSettings = {}
481
499
  }, ref) => {
482
500
  const formId = react.useId();
483
501
  const [values, setValues] = react.useState({});
484
502
  const [errors, setErrors] = react.useState({});
485
503
  const [touched, setTouched] = react.useState({});
486
- const allFields = additionalFields ? [...fields, ...additionalFields] : fields;
504
+ const visibleFields = fields.filter((field) => {
505
+ if (HIDEABLE_FIELDS.includes(field.name)) {
506
+ const setting = fieldSettings[field.name];
507
+ if (setting?.hidden) return false;
508
+ }
509
+ return true;
510
+ });
511
+ const allFields = additionalFields ? [...visibleFields, ...additionalFields] : visibleFields;
512
+ const validateMatching = react.useCallback(
513
+ (fieldName, value, currentValues) => {
514
+ if (fieldName === "verifyEmail") {
515
+ if (value && currentValues.email && value !== currentValues.email) {
516
+ return "Email addresses do not match";
517
+ }
518
+ }
519
+ if (fieldName === "confirmPassword") {
520
+ if (value && currentValues.password && value !== currentValues.password) {
521
+ return "Passwords do not match";
522
+ }
523
+ }
524
+ return null;
525
+ },
526
+ []
527
+ );
487
528
  const validateField = react.useCallback(
488
- (field, value) => {
529
+ (field, value, currentValues) => {
489
530
  if (field.required && (!value || value.trim() === "")) {
490
531
  return "This field is required";
491
532
  }
492
533
  if (field.validate && value) {
493
- return field.validate(value);
534
+ const error = field.validate(value);
535
+ if (error) return error;
494
536
  }
537
+ const matchError = validateMatching(field.name, value, currentValues);
538
+ if (matchError) return matchError;
495
539
  return null;
496
540
  },
497
- []
541
+ [validateMatching]
498
542
  );
499
543
  const validateAll = react.useCallback(() => {
500
544
  const newErrors = {};
501
545
  let isValid = true;
502
546
  for (const field of allFields) {
503
547
  const value = values[field.name] || "";
504
- const error = validateField(field, value);
548
+ const error = validateField(field, value, values);
505
549
  if (error) {
506
550
  newErrors[field.name] = error;
507
551
  isValid = false;
@@ -514,7 +558,7 @@ var RegistrationForm = react.forwardRef(
514
558
  (currentValues) => {
515
559
  for (const field of allFields) {
516
560
  const value = currentValues[field.name] || "";
517
- const error = validateField(field, value);
561
+ const error = validateField(field, value, currentValues);
518
562
  if (error) return false;
519
563
  }
520
564
  return true;
@@ -528,7 +572,7 @@ var RegistrationForm = react.forwardRef(
528
572
  if (touched[fieldName]) {
529
573
  const field = allFields.find((f) => f.name === fieldName);
530
574
  if (field) {
531
- const error = validateField(field, value);
575
+ const error = validateField(field, value, newValues);
532
576
  if (!error) {
533
577
  setErrors((prev) => {
534
578
  const next = { ...prev };
@@ -538,6 +582,36 @@ var RegistrationForm = react.forwardRef(
538
582
  }
539
583
  }
540
584
  }
585
+ if (fieldName === "email" && touched.verifyEmail) {
586
+ const verifyField = allFields.find((f) => f.name === "verifyEmail");
587
+ if (verifyField) {
588
+ const verifyError = validateField(verifyField, newValues.verifyEmail || "", newValues);
589
+ setErrors((prev) => {
590
+ const next = { ...prev };
591
+ if (verifyError) {
592
+ next.verifyEmail = verifyError;
593
+ } else {
594
+ delete next.verifyEmail;
595
+ }
596
+ return next;
597
+ });
598
+ }
599
+ }
600
+ if (fieldName === "password" && touched.confirmPassword) {
601
+ const confirmField = allFields.find((f) => f.name === "confirmPassword");
602
+ if (confirmField) {
603
+ const confirmError = validateField(confirmField, newValues.confirmPassword || "", newValues);
604
+ setErrors((prev) => {
605
+ const next = { ...prev };
606
+ if (confirmError) {
607
+ next.confirmPassword = confirmError;
608
+ } else {
609
+ delete next.confirmPassword;
610
+ }
611
+ return next;
612
+ });
613
+ }
614
+ }
541
615
  if (onChange) {
542
616
  const isValid = checkIsValid(newValues);
543
617
  onChange(newValues, isValid);
@@ -545,6 +619,13 @@ var RegistrationForm = react.forwardRef(
545
619
  },
546
620
  [values, touched, allFields, validateField, onChange, checkIsValid]
547
621
  );
622
+ const handleCheckboxChange = react.useCallback(
623
+ (fieldName, checked) => {
624
+ const value = checked ? "true" : "";
625
+ handleChange(fieldName, value);
626
+ },
627
+ [handleChange]
628
+ );
548
629
  const handleBlur = react.useCallback(
549
630
  (fieldName) => {
550
631
  setTouched((prev) => ({ ...prev, [fieldName]: true }));
@@ -552,7 +633,7 @@ var RegistrationForm = react.forwardRef(
552
633
  const field = allFields.find((f) => f.name === fieldName);
553
634
  if (field) {
554
635
  const value = values[fieldName] || "";
555
- const error = validateField(field, value);
636
+ const error = validateField(field, value, values);
556
637
  if (error) {
557
638
  setErrors((prev) => ({ ...prev, [fieldName]: error }));
558
639
  } else {
@@ -608,6 +689,8 @@ var RegistrationForm = react.forwardRef(
608
689
  label: classNames.label || "block text-sm font-medium mb-1",
609
690
  input: classNames.input || "w-full px-3 py-2 border rounded-md",
610
691
  inputError: classNames.inputError || "border-red-500",
692
+ checkbox: classNames.checkbox || "",
693
+ checkboxWrapper: classNames.checkboxWrapper || "flex items-center gap-2",
611
694
  errorText: classNames.errorText || "mt-1 text-xs text-red-600",
612
695
  button: classNames.button || "w-full mt-4 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
613
696
  };
@@ -619,6 +702,32 @@ var RegistrationForm = react.forwardRef(
619
702
  const error = errors[field.name];
620
703
  const isTouched = touched[field.name];
621
704
  const showError = isTouched && error;
705
+ if (field.type === "check") {
706
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.fieldWrapper, children: [
707
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.checkboxWrapper, children: [
708
+ /* @__PURE__ */ jsxRuntime.jsx(
709
+ "input",
710
+ {
711
+ id: fieldId,
712
+ name: field.name,
713
+ type: "checkbox",
714
+ checked: value === "true",
715
+ onChange: (e) => handleCheckboxChange(field.name, e.target.checked),
716
+ onBlur: () => handleBlur(field.name),
717
+ className: `${styles.checkbox} ${showError ? styles.inputError : ""}`,
718
+ "aria-invalid": showError ? "true" : "false",
719
+ "aria-describedby": showError ? errorId : void 0,
720
+ "aria-required": field.required ? "true" : "false"
721
+ }
722
+ ),
723
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: fieldId, className: styles.label, children: [
724
+ field.label,
725
+ field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500 ml-1", "aria-hidden": "true", children: "*" })
726
+ ] })
727
+ ] }),
728
+ showError && /* @__PURE__ */ jsxRuntime.jsx("p", { id: errorId, className: styles.errorText, role: "alert", children: error })
729
+ ] }, field.name);
730
+ }
622
731
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.fieldWrapper, children: [
623
732
  /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: fieldId, className: styles.label, children: [
624
733
  field.label,