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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zonetrix/viewer

v2.6.0

Published

Lightweight React component for rendering interactive seat maps

Downloads

999

Readme

@zonetrix/viewer

Lightweight React component for rendering interactive seat maps in your booking applications, event ticketing systems, and venue management platforms.

Overview

@zonetrix/viewer is a production-ready React component that renders beautiful, interactive seat maps with support for selection, zooming, and real-time state updates. Perfect for theaters, stadiums, cinemas, and event venues.

Features

  • 🎯 Read-only Display - Optimized for end-user viewing and selection
  • 🖱️ Interactive Selection - Click seats to select/deselect with visual feedback
  • 🔄 Real-time Updates - Support for dynamic seat state changes
  • 🌐 Flexible Config Loading - Load from JSON files or API endpoints
  • 🔍 Mouse Wheel Zoom - Smooth zoom with configurable limits
  • 🎨 Customizable Colors - Override default colors to match your brand
  • 🏢 Multi-floor Support - Filter and display seats by floor
  • 👁️ Hidden Seats - Automatically filters out hidden seats
  • 📱 Responsive - Works seamlessly across all screen sizes
  • Lightweight - Minimal dependencies
  • 🔒 Type-safe - Full TypeScript support

Installation

npm install @zonetrix/viewer
# or
yarn add @zonetrix/viewer
# or
pnpm add @zonetrix/viewer

Peer Dependencies

npm install react react-dom konva react-konva

Exports

// Main component
import { SeatMapViewer } from '@zonetrix/viewer';
import type { SeatMapViewerProps } from '@zonetrix/viewer';

// Types
import type {
  SeatState,
  SeatShape,
  SeatData,
  SeatMapConfig,
  ColorSettings,
  FloorConfig
} from '@zonetrix/viewer';

// Hooks
import { useConfigFetcher } from '@zonetrix/viewer';

// Constants
import { DEFAULT_COLORS } from '@zonetrix/viewer';

Quick Start

Basic Usage with JSON Config

import { SeatMapViewer } from '@zonetrix/viewer';
import type { SeatData, SeatMapConfig } from '@zonetrix/viewer';
import venueConfig from './venue-config.json';

function BookingApp() {
  return (
    <SeatMapViewer
      config={venueConfig}
      onSeatSelect={(seat) => console.log('Selected:', seat)}
      onSeatDeselect={(seat) => console.log('Deselected:', seat)}
    />
  );
}

Load Config from API

import { SeatMapViewer } from '@zonetrix/viewer';

function BookingApp() {
  return (
    <SeatMapViewer
      configUrl="https://api.example.com/venues/123/config"
      onSeatSelect={(seat) => addToCart(seat)}
      onSeatDeselect={(seat) => removeFromCart(seat)}
    />
  );
}

Props API

SeatMapViewerProps

| Prop | Type | Required | Description | |------|------|----------|-------------| | config | SeatMapConfig | No* | Seat map configuration object | | configUrl | string | No* | URL to fetch configuration from | | floorId | string | No | Filter seats/stages by floor ID (controlled mode) | | onFloorChange | (floorId: string) => void | No | Callback when floor changes | | reservedSeats | string[] | No | Array of seat IDs/numbers to mark as reserved | | unavailableSeats | string[] | No | Array of seat IDs/numbers to mark as unavailable | | selectedSeats | string[] | No | Array of seat IDs for controlled selection mode | | onSeatSelect | (seat: SeatData) => void | No | Callback when a seat is selected | | onSeatDeselect | (seat: SeatData) => void | No | Callback when a seat is deselected | | onSelectionChange | (seats: SeatData[]) => void | No | Callback when selection changes | | colorOverrides | Partial<ColorSettings> | No | Custom colors for seat states | | showTooltip | boolean | No | Show seat tooltips on hover with seat info, price, status (default: true) | | zoomEnabled | boolean | No | Enable/disable zoom functionality (default: true) | | className | string | No | Custom CSS class for the container | | onConfigLoad | (config: SeatMapConfig) => void | No | Callback when config is loaded | | onError | (error: Error) => void | No | Callback when an error occurs | | showFloorSelector | boolean | No | Show/hide built-in floor selector (default: true when floors > 1) | | floorSelectorPosition | string | No | Position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | | floorSelectorClassName | string | No | Custom CSS class for floor selector | | showAllFloorsOption | boolean | No | Show "All" button in floor selector (default: true) | | allFloorsLabel | string | No | Custom label for "All" button (default: 'All') | | fitToView | boolean | No | Auto-fit content on load (default: true) | | fitPadding | number | No | Padding around content when fitting (default: 40) | | showZoomControls | boolean | No | Show zoom +/- buttons (default: true) | | zoomControlsPosition | string | No | Position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' (default: 'bottom-right') | | zoomControlsClassName | string | No | Custom CSS class for zoom controls | | maxZoom | number | No | Maximum zoom level (default: 3) | | zoomStep | number | No | Zoom increment per click (default: 0.25) |

*Note: Either config or configUrl must be provided.

Usage Examples

1. Booking System with Cart

import { useState } from 'react';
import { SeatMapViewer } from '@zonetrix/viewer';
import type { SeatData } from '@zonetrix/viewer';

function TheaterBooking() {
  const [cart, setCart] = useState<SeatData[]>([]);

  const handleSeatSelect = (seat: SeatData) => {
    setCart((prev) => [...prev, seat]);
  };

  const handleSeatDeselect = (seat: SeatData) => {
    setCart((prev) => prev.filter((s) => s.seatNumber !== seat.seatNumber));
  };

  const totalPrice = cart.reduce((sum, seat) => sum + (seat.price || 0), 0);

  return (
    <div>
      <SeatMapViewer
        configUrl="https://api.theater.com/venues/main-hall/config"
        reservedSeats={['A-1', 'A-2', 'B-5']}
        onSeatSelect={handleSeatSelect}
        onSeatDeselect={handleSeatDeselect}
      />

      <div className="cart">
        <h3>Your Selection</h3>
        <p>Seats: {cart.map(s => s.seatNumber).join(', ')}</p>
        <p>Total: ${totalPrice.toFixed(2)}</p>
        <button onClick={() => checkout(cart)}>Proceed to Checkout</button>
      </div>
    </div>
  );
}

2. Custom Colors (Brand Matching)

import { SeatMapViewer } from '@zonetrix/viewer';

function BrandedVenue() {
  const customColors = {
    seatAvailable: '#10b981',    // Green
    seatReserved: '#ef4444',     // Red
    seatSelected: '#3b82f6',     // Blue
    canvasBackground: '#ffffff', // White
  };

  return (
    <SeatMapViewer
      config={venueConfig}
      colorOverrides={customColors}
      onSeatSelect={(seat) => console.log('Selected:', seat)}
    />
  );
}

3. Real-time Updates from API

import { useState, useEffect } from 'react';
import { SeatMapViewer } from '@zonetrix/viewer';

function LiveEventSeating() {
  const [reservedSeats, setReservedSeats] = useState<string[]>([]);

  // Poll API every 5 seconds for reserved seats
  useEffect(() => {
    const interval = setInterval(async () => {
      const response = await fetch('/api/venue/123/reserved-seats');
      const data = await response.json();
      setReservedSeats(data.reserved);
    }, 5000);

    return () => clearInterval(interval);
  }, []);

  return (
    <SeatMapViewer
      config={venueConfig}
      reservedSeats={reservedSeats}
      onSeatSelect={(seat) => bookSeat(seat)}
    />
  );
}

4. Tracking Selection Changes

import { SeatMapViewer } from '@zonetrix/viewer';

function SelectionTracker() {
  const handleSelectionChange = (selectedSeats) => {
    console.log('Current selection:', selectedSeats);

    // Update analytics
    analytics.track('Seats Selected', {
      count: selectedSeats.length,
      seats: selectedSeats.map(s => s.seatNumber),
    });
  };

  return (
    <SeatMapViewer
      config={venueConfig}
      onSelectionChange={handleSelectionChange}
    />
  );
}

5. Customize Zoom Controls

import { SeatMapViewer } from '@zonetrix/viewer';

function CustomZoom() {
  return (
    <SeatMapViewer
      config={venueConfig}
      // Zoom controls appear in bottom-right by default
      zoomControlsPosition="bottom-left"
      maxZoom={5}        // Allow up to 5x zoom
      zoomStep={0.5}     // Larger zoom increments
      onSeatSelect={(seat) => handleSelection(seat)}
    />
  );
}

// Hide zoom controls entirely
function NoZoomControls() {
  return (
    <SeatMapViewer
      config={venueConfig}
      showZoomControls={false}
    />
  );
}

6. Multi-floor Venue (Built-in Floor Selector)

The viewer includes a built-in floor selector that automatically appears when your config has multiple floors.

import { SeatMapViewer } from '@zonetrix/viewer';

function MultiFloorVenue() {
  return (
    <SeatMapViewer
      config={venueConfig}
      onSeatSelect={(seat) => handleSelection(seat)}
      // Floor selector auto-shows when config.floors.length > 1
      // Customize position and labels:
      floorSelectorPosition="top-right"
      allFloorsLabel="All Floors"
    />
  );
}

Custom Floor Selector (Controlled Mode)

For full control over the floor selector UI, use controlled mode:

import { useState } from 'react';
import { SeatMapViewer } from '@zonetrix/viewer';

function CustomFloorSelector() {
  const [currentFloor, setCurrentFloor] = useState<string | null>(null);

  return (
    <div>
      {/* Your custom floor selector */}
      <div className="floor-tabs">
        <button onClick={() => setCurrentFloor(null)}>All</button>
        {venueConfig.floors?.map((floor) => (
          <button
            key={floor.id}
            onClick={() => setCurrentFloor(floor.id)}
            className={currentFloor === floor.id ? 'active' : ''}
          >
            {floor.name}
          </button>
        ))}
      </div>

      <SeatMapViewer
        config={venueConfig}
        showFloorSelector={false}  // Hide built-in selector
        floorId={currentFloor || undefined}
        onFloorChange={setCurrentFloor}
        onSeatSelect={(seat) => handleSelection(seat)}
      />
    </div>
  );
}

7. Controlled Selection Mode

For complete control over seat selection state (e.g., syncing with external state, URL params, or database):

import { useState } from 'react';
import { SeatMapViewer } from '@zonetrix/viewer';
import type { SeatData } from '@zonetrix/viewer';

function ControlledSelection() {
  const [selectedSeatIds, setSelectedSeatIds] = useState<string[]>([]);

  const handleSeatSelect = (seat: SeatData) => {
    setSelectedSeatIds(prev => [...prev, seat.id]);
  };

  const handleSeatDeselect = (seat: SeatData) => {
    setSelectedSeatIds(prev => prev.filter(id => id !== seat.id));
  };

  return (
    <SeatMapViewer
      config={venueConfig}
      selectedSeats={selectedSeatIds}  // Controlled mode
      onSeatSelect={handleSeatSelect}
      onSeatDeselect={handleSeatDeselect}
    />
  );
}

Use cases for controlled selection mode:

  • Syncing selection with URL query parameters
  • Persisting selection in Redux/Zustand store
  • Loading pre-selected seats from database
  • Implementing undo/redo functionality
  • Syncing selection across multiple components
  • Integrating with shopping cart state management

Example with cart integration:

import { useState } from 'react';
import { SeatMapViewer } from '@zonetrix/viewer';
import type { SeatData } from '@zonetrix/viewer';

function CartIntegration() {
  const [cartItems, setCartItems] = useState<SeatData[]>([]);

  // Derive selected IDs from cart
  const selectedSeatIds = cartItems.map(seat => seat.id);

  const handleSeatSelect = (seat: SeatData) => {
    setCartItems(prev => [...prev, seat]);
  };

  const handleSeatDeselect = (seat: SeatData) => {
    setCartItems(prev => prev.filter(item => item.id !== seat.id));
  };

  const totalPrice = cartItems.reduce((sum, seat) => sum + (seat.price || 0), 0);

  return (
    <div>
      <SeatMapViewer
        config={venueConfig}
        selectedSeats={selectedSeatIds}
        onSeatSelect={handleSeatSelect}
        onSeatDeselect={handleSeatDeselect}
      />

      <div className="cart">
        <h3>Shopping Cart</h3>
        <p>Selected: {cartItems.length} seat(s)</p>
        <p>Total: ${totalPrice.toFixed(2)}</p>
      </div>
    </div>
  );
}

8. Error Handling

import { SeatMapViewer } from '@zonetrix/viewer';

function RobustViewer() {
  const handleConfigLoad = (config) => {
    console.log('Config loaded:', config.metadata.name);
    console.log('Total seats:', config.seats.length);
  };

  const handleError = (error) => {
    console.error('Failed to load seat map:', error.message);
    // Show error notification to user
  };

  return (
    <SeatMapViewer
      configUrl="https://api.example.com/venues/123/config"
      onConfigLoad={handleConfigLoad}
      onError={handleError}
      onSeatSelect={(seat) => console.log('Selected:', seat)}
    />
  );
}

Configuration Format

The viewer accepts a SeatMapConfig object. You can create these configurations using our creator studio or build them programmatically.

Example Configuration

{
  "version": "1.0.0",
  "metadata": {
    "name": "Main Auditorium",
    "venue": "Grand Theater",
    "capacity": 500,
    "createdAt": "2025-01-01T00:00:00Z",
    "updatedAt": "2025-01-01T00:00:00Z"
  },
  "canvas": {
    "width": 1200,
    "height": 800,
    "backgroundColor": "#1a1a1a"
  },
  "colors": {
    "canvasBackground": "#1a1a1a",
    "stageColor": "#808080",
    "seatAvailable": "#2C2B30",
    "seatReserved": "#FCEA00",
    "seatSelected": "#3A7DE5",
    "seatUnavailable": "#6b7280",
    "gridLines": "#404040",
    "currency": "USD"
  },
  "seats": [
    {
      "id": "seat_001",
      "position": { "x": 100, "y": 100 },
      "shape": "rounded-square",
      "state": "available",
      "sectionName": "Orchestra",
      "rowLabel": "A",
      "columnLabel": "1",
      "seatNumber": "A-1",
      "price": 50.00
    }
  ],
  "sections": [],
  "stages": [],
  "floors": [
    { "id": "floor_1", "name": "Ground Floor", "order": 0 },
    { "id": "floor_2", "name": "First Floor", "order": 1 }
  ]
}

TypeScript Types

SeatData

interface SeatData {
  id: string;
  state: SeatState;
  shape?: SeatShape;
  sectionName?: string;
  rowLabel?: string;
  columnLabel?: string;
  price?: number;
  seatNumber?: string;
  floorId?: string;
}

SeatState

type SeatState = 'available' | 'reserved' | 'selected' | 'unavailable' | 'hidden';

Note: Hidden seats are automatically filtered out from the viewer.

SeatShape

type SeatShape = 'circle' | 'square' | 'rounded-square';

ColorSettings

interface ColorSettings {
  canvasBackground: string;
  stageColor: string;
  seatAvailable: string;
  seatReserved: string;
  seatSelected: string;
  seatUnavailable: string;
  seatHidden: string;
  gridLines: string;
  currency: string;
}

FloorConfig

interface FloorConfig {
  id: string;        // Unique identifier (e.g., "floor_1")
  name: string;      // Display name (e.g., "Ground Floor")
  order: number;     // Sort order (0 = first)
  color?: string;    // Optional floor color
}

Seat States Explained

| State | Description | User Can Select? | Visual | |-------|-------------|------------------|--------| | available | Seat is free and can be selected | ✅ Yes | Default color | | reserved | Seat is booked by another user | ❌ No | Yellow/Warning color | | selected | Seat is selected by current user | ✅ Yes (to deselect) | Primary/Blue color | | unavailable | Seat is blocked (maintenance, etc.) | ❌ No | Gray color | | hidden | Seat exists but is not displayed | ❌ No | Not rendered |

Events & Callbacks

onSeatSelect

Called when a user selects an available seat.

const handleSelect = (seat: SeatData) => {
  console.log('Seat selected:', seat.seatNumber);
  console.log('Price:', seat.price);
  console.log('Section:', seat.sectionName);
};

onSeatDeselect

Called when a user deselects a previously selected seat.

const handleDeselect = (seat: SeatData) => {
  console.log('Seat deselected:', seat.seatNumber);
};

onSelectionChange

Called whenever the selection changes (includes all selected seats).

const handleSelectionChange = (selectedSeats: SeatData[]) => {
  console.log('Total selected:', selectedSeats.length);
  const total = selectedSeats.reduce((sum, s) => sum + (s.price || 0), 0);
  console.log('Total price:', total);
};

Styling

The component uses inline styles generated from the configuration. To customize the container, wrap it in a styled div:

<div style={{ width: '100%', height: '600px', border: '1px solid #ccc' }}>
  <SeatMapViewer config={venueConfig} />
</div>

Performance Tips

  1. Large Venues (500+ seats): Consider splitting into sections
  2. API Loading: Show a loading spinner while configUrl is being fetched
  3. Mobile: Disable zoom on mobile devices for better UX
  4. Memoization: Wrap callbacks with useCallback to prevent unnecessary re-renders

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Common Issues

Configuration not loading from API

Ensure your API returns valid JSON and has proper CORS headers:

// Server-side (Express example)
res.setHeader('Access-Control-Allow-Origin', '*');
res.json(seatMapConfig);

Seats not responding to clicks

Make sure seat states are correctly set. Only available and selected seats can be clicked.

Canvas size issues

The canvas size is determined by the canvas.width and canvas.height in your configuration. Adjust these values or wrap the component in a responsive container.

Related Packages

Examples Repository

Check out our examples repository for more use cases:

  • Booking system integration
  • API polling for real-time updates
  • Custom theming
  • Mobile-optimized layouts

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting PRs.

License

MIT

Author

Fahad Khan (@fahadkhan1740)

Links

Support

For questions and support:


Made with ❤️ by Fahad Khan