@bookinglab/booking-ui-react 1.5.0 → 1.6.0

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/README.md CHANGED
@@ -481,6 +481,85 @@ const formRef = useRef<ContactDetailsFormRef>(null);
481
481
 
482
482
  ---
483
483
 
484
+ ## ResetPasswordForm
485
+
486
+ A reusable password reset form with current password, new password, and confirm new password fields. Includes built-in validation, customizable submit button text, and programmatic control via ref.
487
+
488
+ ### Props
489
+
490
+ | Prop | Type | Default | Description |
491
+ |------|------|---------|-------------|
492
+ | `onSubmit` | `(values: ResetPasswordFormValues) => void` | required | Called with all 3 values on valid submit |
493
+ | `onChange` | `(values: Partial<ResetPasswordFormValues>, isValid: boolean) => void` | `undefined` | Called on every field change |
494
+ | `submitLabel` | `string` | `"Update Password"` | Text for the submit button |
495
+ | `validateOnBlur` | `boolean` | `true` | Whether to validate fields on blur |
496
+ | `className` | `string` | `""` | Class name for the form element |
497
+ | `classNames` | `ResetPasswordFormClassNames` | `undefined` | Custom class names for styling |
498
+
499
+ ### Fields
500
+
501
+ | Field | Label | Required | Validation |
502
+ |-------|-------|----------|------------|
503
+ | `currentPassword` | Current Password | Yes | required |
504
+ | `newPassword` | New Password | Yes | required |
505
+ | `confirmNewPassword` | Confirm New Password | Yes | required, must match New Password |
506
+
507
+ ### Usage
508
+
509
+ ```tsx
510
+ import { useRef } from 'react';
511
+ import {
512
+ ResetPasswordForm,
513
+ ResetPasswordFormRef,
514
+ ResetPasswordFormValues,
515
+ } from '@bookinglab/booking-ui-react';
516
+
517
+ function MyComponent() {
518
+ const formRef = useRef<ResetPasswordFormRef>(null);
519
+
520
+ const handleSubmit = (values: ResetPasswordFormValues) => {
521
+ console.log('Password reset:', values);
522
+ // { currentPassword: '...', newPassword: '...', confirmNewPassword: '...' }
523
+ };
524
+
525
+ return (
526
+ <>
527
+ <ResetPasswordForm
528
+ ref={formRef}
529
+ onSubmit={handleSubmit}
530
+ submitLabel="Change Password"
531
+ />
532
+ <button onClick={() => formRef.current?.reset()}>Reset</button>
533
+ </>
534
+ );
535
+ }
536
+ ```
537
+
538
+ ### Ref Methods
539
+
540
+ | Method | Description |
541
+ |--------|-------------|
542
+ | `reset()` | Clear all values, errors, and touched state |
543
+ | `setValues(values)` | Programmatically set field values |
544
+
545
+ ### Styling
546
+
547
+ ```tsx
548
+ <ResetPasswordForm
549
+ onSubmit={handleSubmit}
550
+ classNames={{
551
+ fieldWrapper: 'mb-6',
552
+ label: 'text-white font-bold',
553
+ input: 'border-2 border-gray-400 rounded-lg p-3',
554
+ inputError: 'border-red-500',
555
+ errorText: 'text-red-400 text-sm',
556
+ button: 'bg-primary text-white py-3 px-6 rounded-lg',
557
+ }}
558
+ />
559
+ ```
560
+
561
+ ---
562
+
484
563
  ## Requirements
485
564
 
486
565
  - React 17.0.0 or higher
@@ -515,6 +594,10 @@ import type {
515
594
  ContactDetailsFieldSettings,
516
595
  ContactDetailsInitialValues,
517
596
  ContactFieldSettings,
597
+ ResetPasswordFormProps,
598
+ ResetPasswordFormRef,
599
+ ResetPasswordFormValues,
600
+ ResetPasswordFormClassNames,
518
601
  } from '@bookinglab/booking-ui-react';
519
602
  ```
520
603
 
package/dist/index.d.cts CHANGED
@@ -218,6 +218,58 @@ interface ContactDetailsFormRef {
218
218
  }>) => void;
219
219
  }
220
220
 
221
+ /**
222
+ * Values emitted by the ResetPasswordForm on submit
223
+ */
224
+ interface ResetPasswordFormValues {
225
+ currentPassword: string;
226
+ newPassword: string;
227
+ confirmNewPassword: string;
228
+ }
229
+ /**
230
+ * Custom class names for styling ResetPasswordForm elements
231
+ */
232
+ interface ResetPasswordFormClassNames {
233
+ /** Wrapper for each form field */
234
+ fieldWrapper?: string;
235
+ /** All labels */
236
+ label?: string;
237
+ /** All input elements */
238
+ input?: string;
239
+ /** Input elements in error state */
240
+ inputError?: string;
241
+ /** Error message text */
242
+ errorText?: string;
243
+ /** Submit button */
244
+ button?: string;
245
+ }
246
+ /**
247
+ * Props for the ResetPasswordForm component
248
+ */
249
+ interface ResetPasswordFormProps {
250
+ /** Called with all 3 password values on valid submit */
251
+ onSubmit: (values: ResetPasswordFormValues) => void;
252
+ /** Called on every field change with current values and validity */
253
+ onChange?: (values: Partial<ResetPasswordFormValues>, isValid: boolean) => void;
254
+ /** Whether to validate on blur (default: true) */
255
+ validateOnBlur?: boolean;
256
+ /** Text for the submit button (default: "Update Password") */
257
+ submitLabel?: string;
258
+ /** Class name for the form element */
259
+ className?: string;
260
+ /** Custom class names for styling individual elements */
261
+ classNames?: ResetPasswordFormClassNames;
262
+ }
263
+ /**
264
+ * Imperative handle methods exposed via ref
265
+ */
266
+ interface ResetPasswordFormRef {
267
+ /** Reset all form values and errors */
268
+ reset: () => void;
269
+ /** Programmatically set form values */
270
+ setValues: (values: Partial<ResetPasswordFormValues>) => void;
271
+ }
272
+
221
273
  interface QuestionOption {
222
274
  id: number;
223
275
  name: string;
@@ -355,6 +407,26 @@ declare const RegistrationForm: React.ForwardRefExoticComponent<RegistrationForm
355
407
  */
356
408
  declare const ContactDetailsForm: React.ForwardRefExoticComponent<ContactDetailsFormProps & React.RefAttributes<ContactDetailsFormRef>>;
357
409
 
410
+ /**
411
+ * A reusable, accessible reset-password form component.
412
+ *
413
+ * Features:
414
+ * - Three password fields: Current, New, Confirm New
415
+ * - Validation: all required; confirm must match new password
416
+ * - forwardRef for programmatic reset()/setValues()
417
+ * - onChange callback with live validity
418
+ * - Granular styling via classNames prop
419
+ *
420
+ * @example
421
+ * ```tsx
422
+ * <ResetPasswordForm
423
+ * onSubmit={(values) => console.log(values)}
424
+ * submitLabel="Change Password"
425
+ * />
426
+ * ```
427
+ */
428
+ declare const ResetPasswordForm: React.ForwardRefExoticComponent<ResetPasswordFormProps & React.RefAttributes<ResetPasswordFormRef>>;
429
+
358
430
  /**
359
431
  * Validation helper functions for form fields
360
432
  * Each validator returns an error message string if invalid, or null if valid
@@ -388,4 +460,4 @@ declare function minLen(min: number): (value: string) => string | null;
388
460
  */
389
461
  declare function compose(...validators: Array<(value: string) => string | null>): (value: string) => string | null;
390
462
 
391
- 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, compose, email, minLen, phone, required, ukPostcode };
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 };
package/dist/index.d.ts CHANGED
@@ -218,6 +218,58 @@ interface ContactDetailsFormRef {
218
218
  }>) => void;
219
219
  }
220
220
 
221
+ /**
222
+ * Values emitted by the ResetPasswordForm on submit
223
+ */
224
+ interface ResetPasswordFormValues {
225
+ currentPassword: string;
226
+ newPassword: string;
227
+ confirmNewPassword: string;
228
+ }
229
+ /**
230
+ * Custom class names for styling ResetPasswordForm elements
231
+ */
232
+ interface ResetPasswordFormClassNames {
233
+ /** Wrapper for each form field */
234
+ fieldWrapper?: string;
235
+ /** All labels */
236
+ label?: string;
237
+ /** All input elements */
238
+ input?: string;
239
+ /** Input elements in error state */
240
+ inputError?: string;
241
+ /** Error message text */
242
+ errorText?: string;
243
+ /** Submit button */
244
+ button?: string;
245
+ }
246
+ /**
247
+ * Props for the ResetPasswordForm component
248
+ */
249
+ interface ResetPasswordFormProps {
250
+ /** Called with all 3 password values on valid submit */
251
+ onSubmit: (values: ResetPasswordFormValues) => void;
252
+ /** Called on every field change with current values and validity */
253
+ onChange?: (values: Partial<ResetPasswordFormValues>, isValid: boolean) => void;
254
+ /** Whether to validate on blur (default: true) */
255
+ validateOnBlur?: boolean;
256
+ /** Text for the submit button (default: "Update Password") */
257
+ submitLabel?: string;
258
+ /** Class name for the form element */
259
+ className?: string;
260
+ /** Custom class names for styling individual elements */
261
+ classNames?: ResetPasswordFormClassNames;
262
+ }
263
+ /**
264
+ * Imperative handle methods exposed via ref
265
+ */
266
+ interface ResetPasswordFormRef {
267
+ /** Reset all form values and errors */
268
+ reset: () => void;
269
+ /** Programmatically set form values */
270
+ setValues: (values: Partial<ResetPasswordFormValues>) => void;
271
+ }
272
+
221
273
  interface QuestionOption {
222
274
  id: number;
223
275
  name: string;
@@ -355,6 +407,26 @@ declare const RegistrationForm: React.ForwardRefExoticComponent<RegistrationForm
355
407
  */
356
408
  declare const ContactDetailsForm: React.ForwardRefExoticComponent<ContactDetailsFormProps & React.RefAttributes<ContactDetailsFormRef>>;
357
409
 
410
+ /**
411
+ * A reusable, accessible reset-password form component.
412
+ *
413
+ * Features:
414
+ * - Three password fields: Current, New, Confirm New
415
+ * - Validation: all required; confirm must match new password
416
+ * - forwardRef for programmatic reset()/setValues()
417
+ * - onChange callback with live validity
418
+ * - Granular styling via classNames prop
419
+ *
420
+ * @example
421
+ * ```tsx
422
+ * <ResetPasswordForm
423
+ * onSubmit={(values) => console.log(values)}
424
+ * submitLabel="Change Password"
425
+ * />
426
+ * ```
427
+ */
428
+ declare const ResetPasswordForm: React.ForwardRefExoticComponent<ResetPasswordFormProps & React.RefAttributes<ResetPasswordFormRef>>;
429
+
358
430
  /**
359
431
  * Validation helper functions for form fields
360
432
  * Each validator returns an error message string if invalid, or null if valid
@@ -388,4 +460,4 @@ declare function minLen(min: number): (value: string) => string | null;
388
460
  */
389
461
  declare function compose(...validators: Array<(value: string) => string | null>): (value: string) => string | null;
390
462
 
391
- 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, compose, email, minLen, phone, required, ukPostcode };
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 };
package/dist/index.js CHANGED
@@ -1088,10 +1088,182 @@ var ContactDetailsForm = react.forwardRef(
1088
1088
  }
1089
1089
  );
1090
1090
  ContactDetailsForm.displayName = "ContactDetailsForm";
1091
+ var FIELDS = [
1092
+ { name: "currentPassword", label: "Current Password" },
1093
+ { name: "newPassword", label: "New Password" },
1094
+ { name: "confirmNewPassword", label: "Confirm New Password" }
1095
+ ];
1096
+ var ResetPasswordForm = react.forwardRef(
1097
+ ({
1098
+ onSubmit,
1099
+ onChange,
1100
+ validateOnBlur = true,
1101
+ submitLabel = "Update Password",
1102
+ className = "",
1103
+ classNames = {}
1104
+ }, ref) => {
1105
+ const formId = react.useId();
1106
+ const [values, setValues] = react.useState({
1107
+ currentPassword: "",
1108
+ newPassword: "",
1109
+ confirmNewPassword: ""
1110
+ });
1111
+ const [errors, setErrors] = react.useState({});
1112
+ const [touched, setTouched] = react.useState({});
1113
+ const validateField = react.useCallback(
1114
+ (name, val, allValues) => {
1115
+ if (!val || val.trim() === "") return "This field is required";
1116
+ if (name === "confirmNewPassword" && val !== allValues.newPassword) {
1117
+ return "Passwords do not match";
1118
+ }
1119
+ return null;
1120
+ },
1121
+ []
1122
+ );
1123
+ const checkIsValid = react.useCallback(
1124
+ (v) => {
1125
+ for (const field of FIELDS) {
1126
+ if (validateField(field.name, v[field.name], v)) return false;
1127
+ }
1128
+ return true;
1129
+ },
1130
+ [validateField]
1131
+ );
1132
+ const handleChange = react.useCallback(
1133
+ (name, val) => {
1134
+ const newValues = { ...values, [name]: val };
1135
+ setValues(newValues);
1136
+ if (touched[name]) {
1137
+ const error = validateField(name, val, newValues);
1138
+ if (!error) {
1139
+ setErrors((prev) => {
1140
+ const next = { ...prev };
1141
+ delete next[name];
1142
+ return next;
1143
+ });
1144
+ }
1145
+ }
1146
+ if (name === "newPassword" && touched.confirmNewPassword) {
1147
+ const confirmError = validateField("confirmNewPassword", newValues.confirmNewPassword, newValues);
1148
+ if (confirmError) {
1149
+ setErrors((prev) => ({ ...prev, confirmNewPassword: confirmError }));
1150
+ } else {
1151
+ setErrors((prev) => {
1152
+ const next = { ...prev };
1153
+ delete next.confirmNewPassword;
1154
+ return next;
1155
+ });
1156
+ }
1157
+ }
1158
+ onChange?.(newValues, checkIsValid(newValues));
1159
+ },
1160
+ [values, touched, validateField, onChange, checkIsValid]
1161
+ );
1162
+ const handleBlur = react.useCallback(
1163
+ (name) => {
1164
+ setTouched((prev) => ({ ...prev, [name]: true }));
1165
+ if (validateOnBlur) {
1166
+ const error = validateField(name, values[name], values);
1167
+ if (error) {
1168
+ setErrors((prev) => ({ ...prev, [name]: error }));
1169
+ } else {
1170
+ setErrors((prev) => {
1171
+ const next = { ...prev };
1172
+ delete next[name];
1173
+ return next;
1174
+ });
1175
+ }
1176
+ }
1177
+ },
1178
+ [validateOnBlur, values, validateField]
1179
+ );
1180
+ const handleSubmit = react.useCallback(
1181
+ (e) => {
1182
+ e.preventDefault();
1183
+ const allTouched = {};
1184
+ const newErrors = {};
1185
+ let isValid = true;
1186
+ for (const field of FIELDS) {
1187
+ allTouched[field.name] = true;
1188
+ const error = validateField(field.name, values[field.name], values);
1189
+ if (error) {
1190
+ newErrors[field.name] = error;
1191
+ isValid = false;
1192
+ }
1193
+ }
1194
+ setTouched(allTouched);
1195
+ setErrors(newErrors);
1196
+ if (isValid) onSubmit(values);
1197
+ },
1198
+ [values, validateField, onSubmit]
1199
+ );
1200
+ react.useImperativeHandle(ref, () => ({
1201
+ reset: () => {
1202
+ setValues({ currentPassword: "", newPassword: "", confirmNewPassword: "" });
1203
+ setErrors({});
1204
+ setTouched({});
1205
+ },
1206
+ setValues: (newVals) => {
1207
+ setValues((prev) => {
1208
+ const merged = { ...prev };
1209
+ for (const [key, value] of Object.entries(newVals)) {
1210
+ if (value !== void 0) {
1211
+ merged[key] = value;
1212
+ }
1213
+ }
1214
+ return merged;
1215
+ });
1216
+ }
1217
+ }), []);
1218
+ const styles = {
1219
+ fieldWrapper: classNames.fieldWrapper || "mb-4",
1220
+ label: classNames.label || "block text-sm font-medium mb-1",
1221
+ input: classNames.input || "w-full px-3 py-2 border rounded-md",
1222
+ inputError: classNames.inputError || "border-red-500",
1223
+ errorText: classNames.errorText || "mt-1 text-xs text-red-600",
1224
+ 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"
1225
+ };
1226
+ return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className, noValidate: true, children: [
1227
+ FIELDS.map((field) => {
1228
+ const fieldId = `${formId}-${field.name}`;
1229
+ const errorId = `${fieldId}-error`;
1230
+ const value = values[field.name];
1231
+ const error = errors[field.name];
1232
+ const isTouched = touched[field.name];
1233
+ const showError = isTouched && error;
1234
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.fieldWrapper, children: [
1235
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: fieldId, className: styles.label, children: [
1236
+ field.label,
1237
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500 ml-1", "aria-hidden": "true", children: "*" })
1238
+ ] }),
1239
+ /* @__PURE__ */ jsxRuntime.jsx(
1240
+ "input",
1241
+ {
1242
+ id: fieldId,
1243
+ name: field.name,
1244
+ type: "password",
1245
+ value,
1246
+ onChange: (e) => handleChange(field.name, e.target.value),
1247
+ onBlur: () => handleBlur(field.name),
1248
+ className: `${styles.input} ${showError ? styles.inputError : ""}`,
1249
+ "aria-invalid": showError ? "true" : "false",
1250
+ "aria-describedby": showError ? errorId : void 0,
1251
+ "aria-required": "true"
1252
+ }
1253
+ ),
1254
+ showError && /* @__PURE__ */ jsxRuntime.jsx("p", { id: errorId, className: styles.errorText, role: "alert", children: error })
1255
+ ] }, field.name);
1256
+ }),
1257
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: styles.button, children: submitLabel })
1258
+ ] });
1259
+ }
1260
+ );
1261
+ ResetPasswordForm.displayName = "ResetPasswordForm";
1091
1262
 
1092
1263
  exports.BookingForm = BookingForm;
1093
1264
  exports.ContactDetailsForm = ContactDetailsForm;
1094
1265
  exports.RegistrationForm = RegistrationForm;
1266
+ exports.ResetPasswordForm = ResetPasswordForm;
1095
1267
  exports.compose = compose;
1096
1268
  exports.email = email;
1097
1269
  exports.minLen = minLen;