@bookinglab/booking-ui-react
v1.10.0
Published
React UI components for BookingLab booking journeys
Readme
@bookinglab/booking-ui-react
React UI components for BookingLab booking journeys. Build dynamic, accessible booking forms with conditional logic, validation, and full styling customization.
Installation
npm install @bookinglab/booking-ui-reactQuick Start
import { BookingForm, Question } from '@bookinglab/booking-ui-react';
const questions: Question[] = [
{ id: 1, name: 'Personal Details', detail_type: 'heading' },
{ id: 2, name: 'Full Name', detail_type: 'text_field', required: true },
{ id: 3, name: 'Email', detail_type: 'text_field', required: true },
];
function App() {
const handleSubmit = (values) => {
console.log('Form submitted:', values);
};
return (
<BookingForm
questions={questions}
onSubmit={handleSubmit}
submitLabel="Book Now"
/>
);
}Components
BookingForm
A dynamic form component that renders form fields based on an array of Question objects.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| questions | Question[] | required | Array of question objects defining the form fields |
| onSubmit | (values: FormValues) => void | required | Callback fired when form is submitted with valid values |
| submitLabel | string | "Submit" | Text for the submit button |
| className | string | "" | Class name for the form element |
| classNames | BookingFormClassNames | undefined | Custom class names for styling individual elements |
Question Types
The detail_type property determines how each question is rendered:
| Type | Description | Renders |
|------|-------------|---------|
| heading | Section heading | <h3> element |
| text_field | Single-line text | <input type="text"> |
| text_area | Multi-line text | <textarea> |
| select | Dropdown selection | <select> with options |
| date | Date picker | <input type="date"> |
| number | Numeric input | <input type="number"> |
| check | Checkbox | <input type="checkbox"> |
Question Interface
interface Question {
id: number;
name: string;
detail_type: 'heading' | 'text_field' | 'text_area' | 'select' | 'date' | 'number' | 'check';
required?: boolean;
disabled?: boolean;
help_text?: string;
options?: QuestionOption[]; // For select type
settings?: QuestionSettings;
}
interface QuestionOption {
id: number;
name: string;
price?: number;
is_default?: boolean;
}
interface QuestionSettings {
conditional_question?: string;
conditional_answers?: Record<string, string>;
min?: number;
max?: number;
placeholder?: string;
}Conditional Questions
Show or hide questions based on answers to other questions using conditional_answers:
const questions: Question[] = [
{
id: 1,
name: 'Service Type',
detail_type: 'select',
required: true,
options: [
{ id: 1, name: 'Consultation' },
{ id: 2, name: 'Follow-up' },
],
},
{
id: 2,
name: 'First time visiting?',
detail_type: 'check',
settings: {
// Only show when "Consultation" (option id: 1) is selected
conditional_answers: { '1': '1' },
},
},
];Styling
Custom Class Names
Override default styles using the classNames prop:
<BookingForm
questions={questions}
onSubmit={handleSubmit}
classNames={{
fieldWrapper: 'mb-6',
label: 'text-white font-bold',
heading: 'text-2xl text-primary',
input: 'border-2 border-gray-400 rounded-lg p-3',
inputError: 'border-red-500',
checkbox: 'w-5 h-5',
helpText: 'text-gray-400 text-sm',
errorText: 'text-red-400',
button: 'bg-primary text-white py-3 px-6 rounded-lg',
}}
/>Available Class Name Keys
| Key | Description |
|-----|-------------|
| fieldWrapper | Container for each form field |
| label | All label elements |
| heading | Heading elements (detail_type: 'heading') |
| input | All input elements (text, textarea, select, date, number) |
| inputError | Additional classes for inputs in error state |
| checkbox | Checkbox input specifically |
| helpText | Help text below fields |
| errorText | Error message text |
| button | Submit button |
Form Values
The onSubmit callback receives a FormValues object mapping question IDs to their values:
type FormValues = Record<number, string | number | boolean>;
// Example output:
{
2: "John Doe", // text_field
3: "[email protected]", // text_field
4: 1, // select (option id)
5: true, // check
}Validation
- Required fields are automatically validated
- Number fields respect
minandmaxsettings - Error messages display after field is touched
- Hidden conditional fields are excluded from validation
RegistrationForm
A reusable, accessible registration form component with built-in validation, customizable fields, and programmatic control.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| fields | FieldConfig[] | Default fields | Custom field configuration |
| additionalFields | FieldConfig[] | undefined | Extra fields appended after default/custom fields |
| onSubmit | (values: FormValues) => void | required | Callback fired when form is submitted with valid values |
| onChange | (values: FormValues, isValid: boolean) => void | undefined | Callback fired on every field change |
| validateOnBlur | boolean | true | Whether to validate fields on blur |
| submitLabel | string | "Submit" | Text for the submit button |
| className | string | "" | Class name for the form element |
| classNames | RegistrationFormClassNames | undefined | Custom class names for styling |
Default Fields
| Field | Label | Type | Required | Validation |
|-------|-------|------|----------|------------|
| firstName | First name | text | Yes | required |
| lastName | Last name | text | Yes | required |
| email | Email address | email | Yes | required, email format |
| password | Password | password | No | none |
| phone | Contact number | tel | No | phone format (if provided) |
| address1 | Address 1 | text | Yes | required |
| address2 | Address 2 | text | No | none |
| city | Town/City | text | Yes | required |
| postcode | Postcode | text | Yes | required, UK postcode |
Custom Fields
Override default fields by passing a fields prop:
import { RegistrationForm, FieldConfig } from '@bookinglab/booking-ui-react';
const customFields: FieldConfig[] = [
{ name: 'username', label: 'Username', type: 'text', required: true },
{ name: 'email', label: 'Email', type: 'email', required: true },
{
name: 'age',
label: 'Age',
type: 'text',
validate: (value) => {
const num = parseInt(value, 10);
if (isNaN(num) || num < 18) return 'Must be 18 or older';
return null;
}
},
];
<RegistrationForm fields={customFields} onSubmit={handleSubmit} />Additional Fields
Append extra fields after the defaults using additionalFields:
import { RegistrationForm, FieldConfig } from '@bookinglab/booking-ui-react';
const extraFields: FieldConfig[] = [
{ name: 'company', label: 'Company Name', type: 'text' },
{
name: 'service',
label: 'Service Type',
type: 'select',
required: true,
options: [
{ id: 1, name: 'Consultation', price: 0, is_default: true },
{ id: 2, name: 'Premium Support', price: 50 },
{ id: 3, name: 'Enterprise', price: 200 },
],
},
];
<RegistrationForm additionalFields={extraFields} onSubmit={handleSubmit} />Select Field Options
Select fields accept an options array with the following structure:
interface FieldOption {
id: number | string; // Unique identifier (submitted as the value)
name: string; // Display text
price?: number; // Optional price modifier (displayed as "+£X")
is_default?: boolean; // Pre-select this option
}Ref Methods
Access imperative methods via ref:
import { useRef } from 'react';
import { RegistrationForm, RegistrationFormRef } from '@bookinglab/booking-ui-react';
function MyComponent() {
const formRef = useRef<RegistrationFormRef>(null);
return (
<>
<RegistrationForm ref={formRef} onSubmit={handleSubmit} />
<button onClick={() => formRef.current?.reset()}>Reset Form</button>
<button onClick={() => formRef.current?.setValues({ email: '[email protected]' })}>
Pre-fill Email
</button>
</>
);
}| Method | Description |
|--------|-------------|
| reset() | Clear all values, errors, and touched state |
| setValues(values) | Programmatically set field values |
Styling
Override styles using the classNames prop:
<RegistrationForm
onSubmit={handleSubmit}
classNames={{
fieldWrapper: 'mb-6',
label: 'text-white font-bold',
input: 'border-2 border-gray-400 rounded-lg p-3',
inputError: 'border-red-500',
errorText: 'text-red-400 text-sm',
button: 'bg-primary text-white py-3 px-6 rounded-lg',
}}
/>Validators
The package exports reusable validators:
import { required, email, phone, ukPostcode, minLen, compose } from '@bookinglab/booking-ui-react';
// Use in custom fields
const fields = [
{
name: 'password',
label: 'Password',
type: 'text',
validate: compose(required, minLen(8)),
},
];ContactDetailsForm
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.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| questions | Question[] | [] | Additional questions to render after the contact fields |
| initialValues | ContactDetailsInitialValues | undefined | Initial values for pre-populating the form |
| onSubmit | (values: ContactDetailsValues) => void | required | Callback fired on valid submission |
| onChange | (values: Partial<ContactDetailsValues>, isValid: boolean) => void | undefined | Callback fired on every field change |
| submitLabel | string | "Submit" | Text for the submit button |
| validateOnBlur | boolean | true | Whether to validate fields on blur |
| className | string | "" | Class name for the form element |
| classNames | ContactDetailsFormClassNames | undefined | Custom class names for styling |
| fieldSettings | ContactDetailsFieldSettings | undefined | Per-field required/disabled settings |
Default Fields
| Field | Label | Required | Validation |
|-------|-------|----------|------------|
| firstName | First name | Yes | required |
| lastName | Last name | Yes | required |
| email | Email | Yes | required, email format |
Field Settings
Control the required and disabled state of each default field:
import { ContactDetailsForm } from '@bookinglab/booking-ui-react';
<ContactDetailsForm
onSubmit={handleSubmit}
fieldSettings={{
firstName: { required: true, disabled: false },
lastName: { required: false }, // optional last name
email: { required: true, disabled: true }, // pre-filled, non-editable
}}
/>interface ContactFieldSettings {
required?: boolean; // default: true
disabled?: boolean; // default: false
}
interface ContactDetailsFieldSettings {
firstName?: ContactFieldSettings;
lastName?: ContactFieldSettings;
email?: ContactFieldSettings;
}Initial Values
Pre-populate the form with existing data using initialValues:
<ContactDetailsForm
onSubmit={handleSubmit}
initialValues={{
firstName: 'Sarah',
lastName: 'Grey',
email: '[email protected]',
questions: {
2: '5', // select option ID as string
7: 25, // number value
10: 'Wheelchair access needed',
12: true, // checkbox
},
}}
/>interface ContactDetailsInitialValues {
firstName?: string;
lastName?: string;
email?: string;
questions?: Record<number, string | number | boolean>;
}When reset() is called via ref, the form restores to these initial values rather than empty fields.
Pass additional questions to render after the contact fields:
<ContactDetailsForm
questions={[
{ id: 2, name: 'Purpose', detail_type: 'select', required: true,
options: [{ id: 5, name: 'School Trip' }, { id: 6, name: 'Corporate' }] },
{ id: 7, name: 'Group Size', detail_type: 'number', required: true },
]}
onSubmit={(values) => console.log(values)}
/>Output Format
The onSubmit callback receives a ContactDetailsValues object:
interface ContactDetailsValues {
firstName: string;
lastName: string;
email: string;
q: Record<string, { answer: string; answer_id: number; name: string }>;
answers: { question_id: number; name: string; answer: string; answer_id: number }[];
}Ref Methods
import { useRef } from 'react';
import { ContactDetailsForm, ContactDetailsFormRef } from '@bookinglab/booking-ui-react';
const formRef = useRef<ContactDetailsFormRef>(null);
<ContactDetailsForm ref={formRef} onSubmit={handleSubmit} />
<button onClick={() => formRef.current?.reset()}>Reset</button>
<button onClick={() => formRef.current?.setValues({ email: '[email protected]' })}>Pre-fill</button>| Method | Description |
|--------|-------------|
| reset() | Clear all values, errors, and touched state |
| setValues(values) | Programmatically set contact detail values |
ResetPasswordForm
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.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| onSubmit | (values: ResetPasswordFormValues) => void | required | Called with all 3 values on valid submit |
| onChange | (values: Partial<ResetPasswordFormValues>, isValid: boolean) => void | undefined | Called on every field change |
| submitLabel | string | "Update Password" | Text for the submit button |
| validateOnBlur | boolean | true | Whether to validate fields on blur |
| className | string | "" | Class name for the form element |
| classNames | ResetPasswordFormClassNames | undefined | Custom class names for styling |
Fields
| Field | Label | Required | Validation |
|-------|-------|----------|------------|
| currentPassword | Current Password | Yes | required |
| newPassword | New Password | Yes | required |
| confirmNewPassword | Confirm New Password | Yes | required, must match New Password |
Usage
import { useRef } from 'react';
import {
ResetPasswordForm,
ResetPasswordFormRef,
ResetPasswordFormValues,
} from '@bookinglab/booking-ui-react';
function MyComponent() {
const formRef = useRef<ResetPasswordFormRef>(null);
const handleSubmit = (values: ResetPasswordFormValues) => {
console.log('Password reset:', values);
// { currentPassword: '...', newPassword: '...', confirmNewPassword: '...' }
};
return (
<>
<ResetPasswordForm
ref={formRef}
onSubmit={handleSubmit}
submitLabel="Change Password"
/>
<button onClick={() => formRef.current?.reset()}>Reset</button>
</>
);
}Ref Methods
| Method | Description |
|--------|-------------|
| reset() | Clear all values, errors, and touched state |
| setValues(values) | Programmatically set field values |
Styling
<ResetPasswordForm
onSubmit={handleSubmit}
classNames={{
fieldWrapper: 'mb-6',
label: 'text-white font-bold',
input: 'border-2 border-gray-400 rounded-lg p-3',
inputError: 'border-red-500',
errorText: 'text-red-400 text-sm',
button: 'bg-primary text-white py-3 px-6 rounded-lg',
}}
/>Requirements
- React 17.0.0 or higher
- React DOM 17.0.0 or higher
TypeScript
All types are exported for TypeScript users:
import type {
Question,
QuestionOption,
QuestionSettings,
FormValues,
FormErrors,
BookingFormProps,
BookingFormClassNames,
FieldConfig,
FieldOption,
RegistrationFormProps,
RegistrationFormRef,
RegistrationFormValues,
RegistrationFormErrors,
RegistrationFormClassNames,
ContactDetailsFormProps,
ContactDetailsFormRef,
ContactDetailsValues,
ContactDetailsFormClassNames,
ContactDetailsQuestionEntry,
ContactDetailsAnswerEntry,
ContactDetailsFieldSettings,
ContactDetailsInitialValues,
ContactFieldSettings,
ResetPasswordFormProps,
ResetPasswordFormRef,
ResetPasswordFormValues,
ResetPasswordFormClassNames,
} from '@bookinglab/booking-ui-react';License
MIT
