@bookinglab/booking-ui-react 1.2.0 → 1.4.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
@@ -208,6 +208,7 @@ A reusable, accessible registration form component with built-in validation, cus
208
208
  | `firstName` | First name | text | Yes | required |
209
209
  | `lastName` | Last name | text | Yes | required |
210
210
  | `email` | Email address | email | Yes | required, email format |
211
+ | `password` | Password | password | No | none |
211
212
  | `phone` | Contact number | tel | No | phone format (if provided) |
212
213
  | `address1` | Address 1 | text | Yes | required |
213
214
  | `address2` | Address 2 | text | No | none |
@@ -343,6 +344,110 @@ const fields = [
343
344
 
344
345
  ---
345
346
 
347
+ ## ContactDetailsForm
348
+
349
+ A form component for collecting contact details (first name, last name, email) alongside optional dynamic questions. On submit, emits values with `q` and `answers` fields matching the JRNI Update Client / Member API format.
350
+
351
+ ### Props
352
+
353
+ | Prop | Type | Default | Description |
354
+ |------|------|---------|-------------|
355
+ | `questions` | `Question[]` | `[]` | Additional questions to render after the contact fields |
356
+ | `onSubmit` | `(values: ContactDetailsValues) => void` | required | Callback fired on valid submission |
357
+ | `onChange` | `(values: Partial<ContactDetailsValues>, isValid: boolean) => void` | `undefined` | Callback fired on every field change |
358
+ | `submitLabel` | `string` | `"Submit"` | Text for the submit button |
359
+ | `validateOnBlur` | `boolean` | `true` | Whether to validate fields on blur |
360
+ | `className` | `string` | `""` | Class name for the form element |
361
+ | `classNames` | `ContactDetailsFormClassNames` | `undefined` | Custom class names for styling |
362
+ | `fieldSettings` | `ContactDetailsFieldSettings` | `undefined` | Per-field required/disabled settings |
363
+
364
+ ### Default Fields
365
+
366
+ | Field | Label | Required | Validation |
367
+ |-------|-------|----------|------------|
368
+ | `firstName` | First name | Yes | required |
369
+ | `lastName` | Last name | Yes | required |
370
+ | `email` | Email | Yes | required, email format |
371
+
372
+ ### Field Settings
373
+
374
+ Control the `required` and `disabled` state of each default field:
375
+
376
+ ```tsx
377
+ import { ContactDetailsForm } from '@bookinglab/booking-ui-react';
378
+
379
+ <ContactDetailsForm
380
+ onSubmit={handleSubmit}
381
+ fieldSettings={{
382
+ firstName: { required: true, disabled: false },
383
+ lastName: { required: false }, // optional last name
384
+ email: { required: true, disabled: true }, // pre-filled, non-editable
385
+ }}
386
+ />
387
+ ```
388
+
389
+ ```typescript
390
+ interface ContactFieldSettings {
391
+ required?: boolean; // default: true
392
+ disabled?: boolean; // default: false
393
+ }
394
+
395
+ interface ContactDetailsFieldSettings {
396
+ firstName?: ContactFieldSettings;
397
+ lastName?: ContactFieldSettings;
398
+ email?: ContactFieldSettings;
399
+ }
400
+ ```
401
+
402
+ ### Dynamic Questions
403
+
404
+ Pass additional questions to render after the contact fields:
405
+
406
+ ```tsx
407
+ <ContactDetailsForm
408
+ questions={[
409
+ { id: 2, name: 'Purpose', detail_type: 'select', required: true,
410
+ options: [{ id: 5, name: 'School Trip' }, { id: 6, name: 'Corporate' }] },
411
+ { id: 7, name: 'Group Size', detail_type: 'number', required: true },
412
+ ]}
413
+ onSubmit={(values) => console.log(values)}
414
+ />
415
+ ```
416
+
417
+ ### Output Format
418
+
419
+ The `onSubmit` callback receives a `ContactDetailsValues` object:
420
+
421
+ ```typescript
422
+ interface ContactDetailsValues {
423
+ firstName: string;
424
+ lastName: string;
425
+ email: string;
426
+ q: Record<string, { answer: string; answer_id: number; name: string }>;
427
+ answers: { question_id: number; name: string; answer: string; answer_id: number }[];
428
+ }
429
+ ```
430
+
431
+ ### Ref Methods
432
+
433
+ ```tsx
434
+ import { useRef } from 'react';
435
+ import { ContactDetailsForm, ContactDetailsFormRef } from '@bookinglab/booking-ui-react';
436
+
437
+ const formRef = useRef<ContactDetailsFormRef>(null);
438
+
439
+ <ContactDetailsForm ref={formRef} onSubmit={handleSubmit} />
440
+ <button onClick={() => formRef.current?.reset()}>Reset</button>
441
+ <button onClick={() => formRef.current?.setValues({ email: 'test@example.com' })}>Pre-fill</button>
442
+ ```
443
+
444
+ | Method | Description |
445
+ |--------|-------------|
446
+ | `reset()` | Clear all values, errors, and touched state |
447
+ | `setValues(values)` | Programmatically set contact detail values |
448
+
449
+ ---
450
+
346
451
  ## Requirements
347
452
 
348
453
  - React 17.0.0 or higher
@@ -368,6 +473,14 @@ import type {
368
473
  RegistrationFormValues,
369
474
  RegistrationFormErrors,
370
475
  RegistrationFormClassNames,
476
+ ContactDetailsFormProps,
477
+ ContactDetailsFormRef,
478
+ ContactDetailsValues,
479
+ ContactDetailsFormClassNames,
480
+ ContactDetailsQuestionEntry,
481
+ ContactDetailsAnswerEntry,
482
+ ContactDetailsFieldSettings,
483
+ ContactFieldSettings,
371
484
  } from '@bookinglab/booking-ui-react';
372
485
  ```
373
486
 
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' | 'select';
26
+ type: 'text' | 'email' | 'tel' | 'password' | 'select';
27
27
  /** Whether the field is required */
28
28
  required?: boolean;
29
29
  /** Placeholder text */
@@ -91,6 +91,118 @@ interface RegistrationFormRef {
91
91
  setValues: (values: Partial<RegistrationFormValues>) => void;
92
92
  }
93
93
 
94
+ /**
95
+ * Entry in the `q` record, keyed by question ID string
96
+ */
97
+ interface ContactDetailsQuestionEntry {
98
+ /** The answer value (option name for selects, raw value for others) */
99
+ answer: string;
100
+ /** The selected option ID for selects, or 0 for text/number/other fields */
101
+ answer_id: number;
102
+ /** The question's display name */
103
+ name: string;
104
+ }
105
+ /**
106
+ * Entry in the `answers` array
107
+ */
108
+ interface ContactDetailsAnswerEntry {
109
+ /** The question ID */
110
+ question_id: number;
111
+ /** The question's display name */
112
+ name: string;
113
+ /** The answer value */
114
+ answer: string;
115
+ /** The selected option ID for selects, or 0 for text/number/other fields */
116
+ answer_id: number;
117
+ }
118
+ /**
119
+ * Values emitted by ContactDetailsForm on submit.
120
+ * The `q` and `answers` fields match the JRNI Update Client/Member API format.
121
+ */
122
+ interface ContactDetailsValues {
123
+ firstName: string;
124
+ lastName: string;
125
+ email: string;
126
+ /** Questions keyed by question ID string */
127
+ q: Record<string, ContactDetailsQuestionEntry>;
128
+ /** Questions as an array of answer entries */
129
+ answers: ContactDetailsAnswerEntry[];
130
+ }
131
+ /**
132
+ * Custom class names for styling ContactDetailsForm elements
133
+ */
134
+ interface ContactDetailsFormClassNames {
135
+ /** Wrapper for each form field */
136
+ fieldWrapper?: string;
137
+ /** All labels */
138
+ label?: string;
139
+ /** Heading elements */
140
+ heading?: string;
141
+ /** All input elements (text, textarea, select, date, number) */
142
+ input?: string;
143
+ /** Input elements in error state */
144
+ inputError?: string;
145
+ /** Checkbox input specifically */
146
+ checkbox?: string;
147
+ /** Help text below fields */
148
+ helpText?: string;
149
+ /** Error message text */
150
+ errorText?: string;
151
+ /** Submit button */
152
+ button?: string;
153
+ }
154
+ /**
155
+ * Per-field settings for a default contact field (firstName, lastName, email)
156
+ */
157
+ interface ContactFieldSettings {
158
+ /** Whether the field is required (default: true for all) */
159
+ required?: boolean;
160
+ /** Whether the field is disabled */
161
+ disabled?: boolean;
162
+ }
163
+ /**
164
+ * Settings for the default contact detail fields
165
+ */
166
+ interface ContactDetailsFieldSettings {
167
+ firstName?: ContactFieldSettings;
168
+ lastName?: ContactFieldSettings;
169
+ email?: ContactFieldSettings;
170
+ }
171
+ /**
172
+ * Props for the ContactDetailsForm component
173
+ */
174
+ interface ContactDetailsFormProps {
175
+ /** Additional questions to render after the contact fields */
176
+ questions?: Question[];
177
+ /** Callback fired when form is submitted with valid values */
178
+ onSubmit: (values: ContactDetailsValues) => void;
179
+ /** Callback fired on every field change */
180
+ onChange?: (values: Partial<ContactDetailsValues>, isValid: boolean) => void;
181
+ /** Text for the submit button (default: "Submit") */
182
+ submitLabel?: string;
183
+ /** Whether to validate on blur (default: true) */
184
+ validateOnBlur?: boolean;
185
+ /** Class name for the form element */
186
+ className?: string;
187
+ /** Custom class names for styling individual elements */
188
+ classNames?: ContactDetailsFormClassNames;
189
+ /** Per-field settings for the default contact fields (required, disabled) */
190
+ fieldSettings?: ContactDetailsFieldSettings;
191
+ }
192
+ /**
193
+ * Imperative handle methods exposed via ref
194
+ */
195
+ interface ContactDetailsFormRef {
196
+ /** Reset all form values and errors */
197
+ reset: () => void;
198
+ /** Programmatically set contact detail values */
199
+ setValues: (values: Partial<{
200
+ firstName: string;
201
+ lastName: string;
202
+ email: string;
203
+ }>) => void;
204
+ }
205
+
94
206
  interface QuestionOption {
95
207
  id: number;
96
208
  name: string;
@@ -209,6 +321,25 @@ declare function BookingForm({ questions, onSubmit, submitLabel, className, labe
209
321
  */
210
322
  declare const RegistrationForm: React.ForwardRefExoticComponent<RegistrationFormProps & React.RefAttributes<RegistrationFormRef>>;
211
323
 
324
+ /**
325
+ * ContactDetailsForm — collects first name, last name, email and optional
326
+ * dynamic questions. On submit it emits values with `q` and `answers` fields
327
+ * matching the JRNI Update Client / Member API format.
328
+ *
329
+ * @example
330
+ * ```tsx
331
+ * <ContactDetailsForm
332
+ * questions={[
333
+ * { id: 2, name: 'Purpose', detail_type: 'select', required: true,
334
+ * options: [{ id: 5, name: 'School Trip' }, { id: 6, name: 'Corporate' }] },
335
+ * { id: 7, name: 'Group Size', detail_type: 'number', required: true },
336
+ * ]}
337
+ * onSubmit={(values) => console.log(values)}
338
+ * />
339
+ * ```
340
+ */
341
+ declare const ContactDetailsForm: React.ForwardRefExoticComponent<ContactDetailsFormProps & React.RefAttributes<ContactDetailsFormRef>>;
342
+
212
343
  /**
213
344
  * Validation helper functions for form fields
214
345
  * Each validator returns an error message string if invalid, or null if valid
@@ -242,4 +373,4 @@ declare function minLen(min: number): (value: string) => string | null;
242
373
  */
243
374
  declare function compose(...validators: Array<(value: string) => string | null>): (value: string) => string | null;
244
375
 
245
- export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, 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 };
376
+ export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, type ContactDetailsAnswerEntry, type ContactDetailsFieldSettings, ContactDetailsForm, type ContactDetailsFormClassNames, type ContactDetailsFormProps, type ContactDetailsFormRef, 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 };
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' | 'select';
26
+ type: 'text' | 'email' | 'tel' | 'password' | 'select';
27
27
  /** Whether the field is required */
28
28
  required?: boolean;
29
29
  /** Placeholder text */
@@ -91,6 +91,118 @@ interface RegistrationFormRef {
91
91
  setValues: (values: Partial<RegistrationFormValues>) => void;
92
92
  }
93
93
 
94
+ /**
95
+ * Entry in the `q` record, keyed by question ID string
96
+ */
97
+ interface ContactDetailsQuestionEntry {
98
+ /** The answer value (option name for selects, raw value for others) */
99
+ answer: string;
100
+ /** The selected option ID for selects, or 0 for text/number/other fields */
101
+ answer_id: number;
102
+ /** The question's display name */
103
+ name: string;
104
+ }
105
+ /**
106
+ * Entry in the `answers` array
107
+ */
108
+ interface ContactDetailsAnswerEntry {
109
+ /** The question ID */
110
+ question_id: number;
111
+ /** The question's display name */
112
+ name: string;
113
+ /** The answer value */
114
+ answer: string;
115
+ /** The selected option ID for selects, or 0 for text/number/other fields */
116
+ answer_id: number;
117
+ }
118
+ /**
119
+ * Values emitted by ContactDetailsForm on submit.
120
+ * The `q` and `answers` fields match the JRNI Update Client/Member API format.
121
+ */
122
+ interface ContactDetailsValues {
123
+ firstName: string;
124
+ lastName: string;
125
+ email: string;
126
+ /** Questions keyed by question ID string */
127
+ q: Record<string, ContactDetailsQuestionEntry>;
128
+ /** Questions as an array of answer entries */
129
+ answers: ContactDetailsAnswerEntry[];
130
+ }
131
+ /**
132
+ * Custom class names for styling ContactDetailsForm elements
133
+ */
134
+ interface ContactDetailsFormClassNames {
135
+ /** Wrapper for each form field */
136
+ fieldWrapper?: string;
137
+ /** All labels */
138
+ label?: string;
139
+ /** Heading elements */
140
+ heading?: string;
141
+ /** All input elements (text, textarea, select, date, number) */
142
+ input?: string;
143
+ /** Input elements in error state */
144
+ inputError?: string;
145
+ /** Checkbox input specifically */
146
+ checkbox?: string;
147
+ /** Help text below fields */
148
+ helpText?: string;
149
+ /** Error message text */
150
+ errorText?: string;
151
+ /** Submit button */
152
+ button?: string;
153
+ }
154
+ /**
155
+ * Per-field settings for a default contact field (firstName, lastName, email)
156
+ */
157
+ interface ContactFieldSettings {
158
+ /** Whether the field is required (default: true for all) */
159
+ required?: boolean;
160
+ /** Whether the field is disabled */
161
+ disabled?: boolean;
162
+ }
163
+ /**
164
+ * Settings for the default contact detail fields
165
+ */
166
+ interface ContactDetailsFieldSettings {
167
+ firstName?: ContactFieldSettings;
168
+ lastName?: ContactFieldSettings;
169
+ email?: ContactFieldSettings;
170
+ }
171
+ /**
172
+ * Props for the ContactDetailsForm component
173
+ */
174
+ interface ContactDetailsFormProps {
175
+ /** Additional questions to render after the contact fields */
176
+ questions?: Question[];
177
+ /** Callback fired when form is submitted with valid values */
178
+ onSubmit: (values: ContactDetailsValues) => void;
179
+ /** Callback fired on every field change */
180
+ onChange?: (values: Partial<ContactDetailsValues>, isValid: boolean) => void;
181
+ /** Text for the submit button (default: "Submit") */
182
+ submitLabel?: string;
183
+ /** Whether to validate on blur (default: true) */
184
+ validateOnBlur?: boolean;
185
+ /** Class name for the form element */
186
+ className?: string;
187
+ /** Custom class names for styling individual elements */
188
+ classNames?: ContactDetailsFormClassNames;
189
+ /** Per-field settings for the default contact fields (required, disabled) */
190
+ fieldSettings?: ContactDetailsFieldSettings;
191
+ }
192
+ /**
193
+ * Imperative handle methods exposed via ref
194
+ */
195
+ interface ContactDetailsFormRef {
196
+ /** Reset all form values and errors */
197
+ reset: () => void;
198
+ /** Programmatically set contact detail values */
199
+ setValues: (values: Partial<{
200
+ firstName: string;
201
+ lastName: string;
202
+ email: string;
203
+ }>) => void;
204
+ }
205
+
94
206
  interface QuestionOption {
95
207
  id: number;
96
208
  name: string;
@@ -209,6 +321,25 @@ declare function BookingForm({ questions, onSubmit, submitLabel, className, labe
209
321
  */
210
322
  declare const RegistrationForm: React.ForwardRefExoticComponent<RegistrationFormProps & React.RefAttributes<RegistrationFormRef>>;
211
323
 
324
+ /**
325
+ * ContactDetailsForm — collects first name, last name, email and optional
326
+ * dynamic questions. On submit it emits values with `q` and `answers` fields
327
+ * matching the JRNI Update Client / Member API format.
328
+ *
329
+ * @example
330
+ * ```tsx
331
+ * <ContactDetailsForm
332
+ * questions={[
333
+ * { id: 2, name: 'Purpose', detail_type: 'select', required: true,
334
+ * options: [{ id: 5, name: 'School Trip' }, { id: 6, name: 'Corporate' }] },
335
+ * { id: 7, name: 'Group Size', detail_type: 'number', required: true },
336
+ * ]}
337
+ * onSubmit={(values) => console.log(values)}
338
+ * />
339
+ * ```
340
+ */
341
+ declare const ContactDetailsForm: React.ForwardRefExoticComponent<ContactDetailsFormProps & React.RefAttributes<ContactDetailsFormRef>>;
342
+
212
343
  /**
213
344
  * Validation helper functions for form fields
214
345
  * Each validator returns an error message string if invalid, or null if valid
@@ -242,4 +373,4 @@ declare function minLen(min: number): (value: string) => string | null;
242
373
  */
243
374
  declare function compose(...validators: Array<(value: string) => string | null>): (value: string) => string | null;
244
375
 
245
- export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, 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 };
376
+ export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, type ContactDetailsAnswerEntry, type ContactDetailsFieldSettings, ContactDetailsForm, type ContactDetailsFormClassNames, type ContactDetailsFormProps, type ContactDetailsFormRef, 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 };