npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

zvenbook-ui

v0.0.9

Published

React booking widget component for ZvenBook booking system

Downloads

37

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/ui

Peer 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 services
  • GET /api/service-availability - Get available time slots
  • POST /api/bookings - Create booking

See the main README for complete API documentation.

🐛 Troubleshooting

Widget Not Loading

  1. Check API URL: Ensure baseUrl is correct and accessible
  2. Check Tenant ID: Verify tenantId exists in your system
  3. Check CORS: Ensure your API allows requests from your domain
  4. Check Console: Look for errors in browser console

Styling Not Applying

  1. Import Styles: Make sure to import @booking/ui/styles.css
  2. Tailwind Config: Ensure Tailwind is configured correctly
  3. CSS Specificity: Your custom styles might need higher specificity
  4. Dark Mode: Check if dark mode classes are interfering

Calendar Not Showing

  1. Week Start: Verify weekStartsOn matches your locale
  2. Date Format: Ensure API returns dates in ISO format
  3. 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


Need Help? Check out the main documentation or open an issue.