zvenbook-ui
v0.0.9
Published
React booking widget component for ZvenBook booking system
Downloads
37
Maintainers
Readme
@booking/ui - Booking Widget Component
A production-ready, fully customizable React booking widget component for embedding appointment scheduling directly into your website.
✨ Features
- 🎯 Complete Booking Flow - Service selection → Date/Time → Contact details → Confirmation
- 🌍 Internationalization - Built-in English & Swedish, easy to add more languages
- 🎨 Fully Customizable - Match your brand with CSS variables and className overrides
- 📱 Mobile Responsive - Works beautifully on all devices
- 🌙 Dark Mode Support - Automatic dark mode detection
- ✅ Form Validation - Real-time email validation and error handling
- ⚡ Loading States - Smooth loading indicators throughout
- ♿ Accessible - Built with accessibility in mind
📦 Installation
npm install @booking/ui
# or
yarn add @booking/ui
# or
pnpm add @booking/uiPeer Dependencies
This package requires React 18+ and React DOM 18+:
npm install react react-dom🚀 Quick Start
import { BookingStepsWidget } from '@booking/ui';
function App() {
return (
<BookingStepsWidget
baseUrl="https://your-api-url.com"
tenantId="your-tenant-id"
/>
);
}📖 Basic Usage
Minimal Example
import { BookingStepsWidget } from '@booking/ui';
<BookingStepsWidget
baseUrl="https://api.example.com"
tenantId="tenant-123"
/>With Customization
import { BookingStepsWidget, swedishLabels } from '@booking/ui';
<BookingStepsWidget
baseUrl="https://api.example.com"
tenantId="tenant-123"
weekStartsOn="monday"
labels={swedishLabels}
layout="wizard"
spacing="cozy"
radius="lg"
styles={{
primaryColor: '#0066FF',
accentColor: '#00AA44',
serviceCardClassName: 'hover:shadow-lg',
submitButtonClassName: 'w-full font-bold',
}}
/>🔧 Props Reference
Required Props
| Prop | Type | Description |
|------|------|-------------|
| baseUrl | string | Base URL of your booking API (e.g., "https://api.example.com") |
| tenantId | string | Your tenant ID from the booking system |
Optional Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| className | string | - | Additional CSS classes for the root container |
| layout | "horizontal" \| "vertical" \| "wizard" | "horizontal" | Layout style for the widget |
| spacing | "compact" \| "normal" \| "cozy" | "normal" | Spacing between elements |
| radius | "none" \| "sm" \| "md" \| "lg" \| "full" | "md" | Border radius for elements |
| color | "accent" \| "primary" \| "muted" \| "transparent" | "accent" | Color scheme variant |
| weekStartsOn | "sunday" \| "monday" | "sunday" | Calendar week start day |
| labels | BookingLabels | defaultLabels | Translation labels (see Internationalization) |
| styles | BookingWidgetStyles | - | Styling customization (see Styling) |
🎨 Styling Customization
The widget can be customized in two ways:
1. CSS Custom Properties (Theme Colors)
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
styles={{
primaryColor: '#0066FF', // Primary buttons, progress bars
accentColor: '#00AA44', // Highlights, active states
borderColor: '#E0E0E0', // Borders
textColor: '#1F2937', // Text color
backgroundColor: '#FFFFFF', // Background
}}
/>2. className Overrides (Fine-grained Control)
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
styles={{
// Stepper component
stepperClassName: 'custom-stepper',
// Calendar component
calendarClassName: 'shadow-lg border-2',
// Service selection cards
serviceCardClassName: 'hover:scale-105 transition-transform',
// Time slot buttons
timeSlotClassName: 'bg-blue-600 hover:bg-blue-700 text-white',
// Form inputs
inputClassName: 'font-sans text-base border-2',
// Navigation buttons (Back/Next)
buttonClassName: 'px-6 py-3 rounded-lg',
// Submit button (overrides buttonClassName)
submitButtonClassName: 'w-full font-bold uppercase tracking-wide',
}}
/>Complete Styling Example
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
styles={{
// Theme colors
primaryColor: '#0066FF',
accentColor: '#00AA44',
// Component styling
serviceCardClassName: cn(
'border-2 hover:border-accent transition-all',
'shadow-sm hover:shadow-md'
),
timeSlotClassName: 'bg-primary hover:bg-primary/90 text-white',
inputClassName: 'border-2 focus:ring-2 focus:ring-primary',
submitButtonClassName: 'w-full bg-primary hover:bg-primary/90',
}}
/>🌍 Internationalization
Built-in Languages
English (Default)
import { BookingStepsWidget, defaultLabels } from '@booking/ui';
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
labels={defaultLabels} // Optional, already default
/>Swedish
import { BookingStepsWidget, swedishLabels } from '@booking/ui';
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
labels={swedishLabels}
weekStartsOn="monday" // Swedish calendar starts Monday
/>Custom Labels
import { BookingStepsWidget, defaultLabels, BookingLabels } from '@booking/ui';
const frenchLabels: BookingLabels = {
...defaultLabels,
stepService: 'Service',
stepDateTime: 'Date et heure',
stepDetails: 'Vos coordonnées',
chooseService: 'Choisir un service',
selectDate: 'Sélectionner une date',
loadingTimes: 'Chargement des horaires...',
noSlotsAvailable: 'Aucun créneau disponible.',
yourDetails: 'Vos coordonnées',
firstNamePlaceholder: 'Prénom',
lastNamePlaceholder: 'Nom',
emailPlaceholder: 'Email',
phonePlaceholder: 'Téléphone (optionnel)',
back: 'Retour',
next: 'Suivant',
bookAppointment: 'Réserver',
booking: 'Réservation…',
bookingConfirmed: 'Réservation confirmée',
changeService: 'Service sélectionné • modifier',
changeDate: '{date} • modifier',
changeTime: '{time} • modifier',
dayLabels: {
sunday: 'Di',
monday: 'Lu',
tuesday: 'Ma',
wednesday: 'Me',
thursday: 'Je',
friday: 'Ve',
saturday: 'Sa',
},
};
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
labels={frenchLabels}
/>Label Interface
interface BookingLabels {
// Stepper labels
stepService: string;
stepDateTime: string;
stepDetails: string;
// Service selection
chooseService: string;
// Date & time selection
selectDate: string;
loadingTimes: string;
noSlotsAvailable: string;
// Calendar day labels
dayLabels: {
sunday: string;
monday: string;
tuesday: string;
wednesday: string;
thursday: string;
friday: string;
saturday: string;
};
// Contact form
yourDetails: string;
firstNamePlaceholder: string;
lastNamePlaceholder: string;
emailPlaceholder: string;
phonePlaceholder: string;
// Navigation
back: string;
next: string;
bookAppointment: string;
booking: string;
// Success
bookingConfirmed: string;
// Change buttons
changeService: string;
changeDate: string; // Use {date} placeholder
changeTime: string; // Use {time} placeholder
}📱 Layout Options
Horizontal Layout
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
layout="horizontal"
/>Vertical Layout
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
layout="vertical"
/>Wizard Layout (Recommended)
<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
layout="wizard"
spacing="cozy"
/>💡 Complete Examples
Basic Integration
'use client';
import { BookingStepsWidget } from '@booking/ui';
export default function BookingPage() {
return (
<div className="container mx-auto p-6">
<h1 className="text-3xl font-bold mb-6">Book an Appointment</h1>
<BookingStepsWidget
baseUrl={process.env.NEXT_PUBLIC_API_URL || ''}
tenantId={process.env.NEXT_PUBLIC_TENANT_ID || ''}
/>
</div>
);
}Custom Styled Widget
import { BookingStepsWidget } from '@booking/ui';
import { cn } from '@/lib/utils';
export function CustomBookingWidget() {
return (
<div className="max-w-2xl mx-auto">
<BookingStepsWidget
baseUrl="https://api.example.com"
tenantId="tenant-123"
layout="wizard"
spacing="cozy"
radius="lg"
styles={{
primaryColor: '#0066FF',
accentColor: '#00AA44',
serviceCardClassName: cn(
'border-2 border-gray-200',
'hover:border-accent hover:shadow-lg',
'transition-all duration-200'
),
timeSlotClassName: 'bg-primary text-white hover:bg-primary/90',
inputClassName: 'border-2 focus:ring-2 focus:ring-primary',
submitButtonClassName: 'w-full bg-primary hover:bg-primary/90 font-bold',
}}
/>
</div>
);
}Multi-language Support
import { BookingStepsWidget, swedishLabels, defaultLabels } from '@booking/ui';
import { useRouter } from 'next/router';
export function LocalizedBookingWidget() {
const router = useRouter();
const locale = router.locale || 'en';
const labels = locale === 'sv' ? swedishLabels : defaultLabels;
const weekStartsOn = locale === 'sv' ? 'monday' : 'sunday';
return (
<BookingStepsWidget
baseUrl={process.env.NEXT_PUBLIC_API_URL || ''}
tenantId={process.env.NEXT_PUBLIC_TENANT_ID || ''}
labels={labels}
weekStartsOn={weekStartsOn}
/>
);
}With Tailwind CSS
If you're using Tailwind CSS, make sure to include the UI package styles:
// In your app's main CSS file
@import '@booking/ui/styles.css';
// Or import directly in your component
import '@booking/ui/styles.css';Custom CSS Styling
/* Override default styles */
.booking-widget {
--booking-primary: #0066FF;
--booking-accent: #00AA44;
}
/* Custom service cards */
.custom-service-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
/* Custom buttons */
.custom-submit-button {
background: #00AA44;
padding: 1rem 2rem;
font-weight: bold;
text-transform: uppercase;
}<BookingStepsWidget
baseUrl={baseUrl}
tenantId={tenantId}
className="booking-widget"
styles={{
serviceCardClassName: 'custom-service-card',
submitButtonClassName: 'custom-submit-button',
}}
/>🎯 Best Practices
1. Environment Variables
Always use environment variables for API configuration:
// .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_TENANT_ID=your-tenant-id
// Component
<BookingStepsWidget
baseUrl={process.env.NEXT_PUBLIC_API_URL || ''}
tenantId={process.env.NEXT_PUBLIC_TENANT_ID || ''}
/>2. Error Handling
The component handles errors internally, but you can wrap it for additional error handling:
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error }) {
return (
<div className="p-4 bg-red-50 border border-red-200 rounded">
<p className="text-red-800">Something went wrong: {error.message}</p>
</div>
);
}
<ErrorBoundary FallbackComponent={ErrorFallback}>
<BookingStepsWidget {...props} />
</ErrorBoundary>3. Loading States
The component includes built-in loading states, but you can add a wrapper:
import { Suspense } from 'react';
<Suspense fallback={<div>Loading booking widget...</div>}>
<BookingStepsWidget {...props} />
</Suspense>4. Accessibility
The component is accessible by default, but ensure your page has proper structure:
<main>
<h1>Book an Appointment</h1>
<BookingStepsWidget {...props} />
</main>🔍 API Requirements
The widget expects your API to implement these endpoints:
GET /api/services?tenantId={tenantId}- List servicesGET /api/service-availability- Get available time slotsPOST /api/bookings- Create booking
See the main README for complete API documentation.
🐛 Troubleshooting
Widget Not Loading
- Check API URL: Ensure
baseUrlis correct and accessible - Check Tenant ID: Verify
tenantIdexists in your system - Check CORS: Ensure your API allows requests from your domain
- Check Console: Look for errors in browser console
Styling Not Applying
- Import Styles: Make sure to import
@booking/ui/styles.css - Tailwind Config: Ensure Tailwind is configured correctly
- CSS Specificity: Your custom styles might need higher specificity
- Dark Mode: Check if dark mode classes are interfering
Calendar Not Showing
- Week Start: Verify
weekStartsOnmatches your locale - Date Format: Ensure API returns dates in ISO format
- Timezone: Check timezone handling in API responses
📚 TypeScript Support
Full TypeScript support is included. Import types as needed:
import type {
BookingStepsWidget,
BookingLabels,
BookingWidgetStyles,
} from '@booking/ui';🤝 Contributing
Contributions welcome! Please see the main Contributing Guide.
📄 License
MIT License - see LICENSE for details.
🔗 Related Packages
@booking/sdk- TypeScript SDK for API integration@booking/core- Core business logic@booking/db- Database schemas
Need Help? Check out the main documentation or open an issue.
