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

@visitor-reach/schedule-canvas

v1.0.0

Published

A React-based interactive schedule canvas component with drag-and-drop functionality

Readme

@visitor-reach/schedule-canvas

A fully-featured, interactive schedule canvas component built with React. Features drag-and-drop scheduling, canvas-based rendering for performance, and a flexible prop-based configuration system.

npm version license

Features

  • 📅 Week-based Schedule - Display and manage weekly recurring schedules
  • 🎨 Canvas Rendering - High-performance canvas-based rendering
  • 🖱️ Drag & Drop - Intuitive drag-and-drop from sidebar to schedule
  • ⌨️ Keyboard Shortcuts - Full keyboard support (undo/redo, copy/paste, delete)
  • 🔍 Zoom Controls - Adjustable zoom levels and time increments
  • 📱 Touch Support - Full touch support for mobile devices
  • 🎯 Default Target - Set default targets for quick scheduling
  • 💾 Callbacks - Save and change callbacks for integration
  • Multi-select - Select multiple items with click or box selection

Installation

npm install @visitor-reach/schedule-canvas

Required Peer Dependencies

Make sure you have React installed:

npm install react react-dom

Required External Resources

Include Font Awesome and Google Fonts in your HTML:

<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

<!-- Google Fonts (Poppins) -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">

Quick Start

The component accepts schedule data as JSON via props. The schedule (items, title, defaultTarget) is passed as a single object, while sidebar sections are passed separately.

import React from 'react';
import ScheduleCanvas from '@visitor-reach/schedule-canvas';
import '@visitor-reach/schedule-canvas/style.css';

function App() {
  // Schedule data - can come from JSON, API, Redux, etc.
  const initialSchedule = {
    title: "Weekly Schedule",
    defaultTarget: null,
    items: [
      {
        id: 1,
        targetId: 'loc-1',
        name: 'Main Hall',
        day: 0, // Sunday
        startTime: '09:00',
        endTime: '12:00',
        color: '#9b59b6', // Optional: stored for rendering
        icon: 'fa-folder-tree', // Optional: stored for reference
        type: 'taptree' // Optional: stored for reference
      }
    ]
  };

  // Sidebar configuration - can come from JSON, API, etc.
  const sidebarSections = [
    {
      title: "Locations",
      icon: "fa-folder-tree",
      color: "#9b59b6",
      type: "taptree",
      isEditable: false, // Items can't have their id/name changed
      displayID: false,  // Don't show ID in addition to name
      addNew: false,     // Don't show "Add New" item
      items: [
        { id: "loc-1", name: "Main Hall" },
        { id: "loc-2", name: "Meeting Room" }
      ]
    },
    {
      title: "Websites",
      icon: "fa-globe",
      color: "#3498db",
      type: "url",
      isEditable: true,  // Items can have their id/name changed
      displayID: true,   // Show both name and URL (ID)
      addNew: true,      // Show "Add New" item for creating new URLs
      idLabel: "URL",    // Custom label for ID field
      nameLabel: "Website Name", // Custom label for name field
      idPlaceholder: "https://example.com", // Custom placeholder for ID field
      namePlaceholder: "My Website", // Custom placeholder for name field
      items: [
        { id: "https://example.com" }
        // Note: if no name provided, id is displayed as name
      ]
    }
  ];

  const handleSave = (schedule) => {
    console.log('Saving schedule:', schedule);
    // schedule = { items: [...], title: "...", defaultTarget: {...} }
    
    // Save to your backend API
    fetch('/api/schedule', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(schedule)
    });
    
    // Or use localStorage (optional)
    // localStorage.setItem('schedule', JSON.stringify(schedule));
  };

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <ScheduleCanvas
        initialSchedule={initialSchedule}
        sidebarSections={sidebarSections}
        onSave={handleSave}
      />
    </div>
  );
}

export default App;

Examples

The package includes a self-contained React app with multiple examples showing different integration patterns. After installing the package, you can explore the examples:

# Navigate to examples folder
cd node_modules/@visitor-reach/schedule-canvas/examples

# Install dependencies
npm install

# Run examples app
npm run dev

The examples app includes:

  • JSON File - Loading data from a static JSON file
  • API Data - Fetching and saving data to an API (with mock implementation)
  • Props from Parent - Using the component with data from parent state/Redux/Context

See examples/README.md for more details.

Props

initialSchedule (optional)

Schedule data object containing items, title, and default target:

{
  title: string;          // Schedule title
  defaultTarget: {        // Default target (optional)
    targetId: string;     // Target identifier (ID or URL)
    name: string;         // Display name
  } | null;
  items: Array<{          // Schedule items
    id: number;           // Unique identifier
    targetId: string;     // Target ID (can be ID, URL, or any string)
    name: string;         // Display name (if empty, displays targetId)
    day: number;          // Day of week (0=Sunday, 6=Saturday)
    startTime: string;    // Start time 'HH:MM'
    endTime: string;      // End time 'HH:MM'
    color?: string;       // Hex color code (optional, for rendering)
    icon?: string;        // Font Awesome icon class (optional, for reference)
    type?: string;        // Item type identifier (optional, e.g., 'taptree', 'url')
  }>;
}

Note: The targetId can be any string (ID, URL, etc.). If name is empty, the targetId is displayed. The type field is optional and gets automatically populated when items are dragged from the sidebar.

Default: { items: [], title: 'Schedule', defaultTarget: null }

sidebarSections (required)

Array of sidebar sections containing draggable items:

{
  title: string;            // Section title (e.g., "Taptrees")
  icon: string;             // Font Awesome icon class (e.g., "fa-folder-tree")
  color?: string;           // Hex color code for all items in section (optional, e.g., "#9b59b6")
  type?: string;            // Item type identifier (optional, e.g., "taptree", "url")
  isEditable?: boolean;     // Whether items in this section can have their id/name edited (optional, defaults to true)
  displayID?: boolean;      // Whether to show ID in addition to name on scheduled items (optional, defaults to false)
  addNew?: boolean;         // Whether to show an "Add New" item at the bottom of the section (optional, defaults to false)
  idLabel?: string;         // Custom label for the ID field in the editor modal (optional, defaults to "Target ID")
  nameLabel?: string;       // Custom label for the name field in the editor modal (optional, defaults to "Display Name (optional)")
  idPlaceholder?: string;   // Custom placeholder for the ID field in the editor modal (optional, defaults to empty)
  namePlaceholder?: string; // Custom placeholder for the name field in the editor modal (optional, defaults to empty)
  items: Array<{
    id: string;             // Unique identifier or URL
    name?: string;          // Display name (optional, uses id if not provided)
  }>;
}[]

Notes:

  • When isEditable is set to false, scheduled items from this section will display their target ID and name as read-only in the edit modal. Users can still edit the schedule properties (day, start time, end time) but not the item identity.
  • When displayID is set to true, scheduled items will show both the name (if present) and the ID on separate lines. This is useful for URLs where you want to show a friendly name above the actual URL.
  • When addNew is set to true, an "Add New..." item will appear at the bottom of the section. Dragging this item onto the schedule will open the edit modal, allowing users to create new items dynamically.
  • idLabel and nameLabel customize the field labels in the edit modal. For example, for a "Websites" section, you might use idLabel: "URL" and nameLabel: "Website Name" instead of the default "Target ID" and "Display Name (optional)".
  • idPlaceholder and namePlaceholder customize the placeholder text shown in the input fields. For example, idPlaceholder: "https://example.com" and namePlaceholder: "My Website" provide helpful hints to users about what to enter.

Important: If an item has an id but no name, the component will display the id as the name.

onSave (optional)

Callback function called when schedule should be saved:

(schedule: {
  items: ScheduleItem[];
  title: string;
  defaultTarget: DefaultTarget | null;
}) => void

onScheduleChange (optional)

Callback function called whenever schedule changes:

(schedule: {
  items: ScheduleItem[];
  title: string;
  defaultTarget: DefaultTarget | null;
}) => void

Keyboard Shortcuts

  • Delete/Backspace - Delete selected items
  • Ctrl/Cmd + Z - Undo
  • Ctrl/Cmd + Shift + Z - Redo
  • Ctrl/Cmd + C - Copy selected items
  • Ctrl/Cmd + V - Paste copied items

Mouse/Touch Interactions

  • Click - Select item
  • Ctrl/Cmd + Click - Toggle item selection
  • Double-click empty space - Add new item
  • Double-click item - Edit item
  • Drag item - Move to new time/day
  • Drag resize handle - Change duration
  • Drag from sidebar - Add new item
  • Box selection - Click and drag to select multiple

Styling

The component includes default styles via style.css. You can override these styles:

/* Example: Customize sidebar */
.sidebar {
  width: 300px;
  background-color: #f5f5f5;
}

/* Customize item colors */
.sidebar-item:hover {
  background-color: #e0e0e0;
}

Data Source Options

The component is data source agnostic - it accepts JSON data via props and doesn't assume any specific persistence method. You can use:

Option 1: From JSON File

import data from './schedule-data.json';

<ScheduleCanvas
  initialSchedule={data.schedule}
  sidebarSections={data.sidebarSections}
/>

Option 2: From API

const [data, setData] = useState(null);

useEffect(() => {
  fetch('/api/schedule/123')
    .then(res => res.json())
    .then(data => setData(data));
}, []);

return data && (
  <ScheduleCanvas
    initialSchedule={data.schedule}
    sidebarSections={data.sidebarSections}
    onSave={handleSaveToAPI}
  />
);

Option 3: From Props (Redux, Context, etc.)

function MyComponent({ scheduleData }) {
  return (
    <ScheduleCanvas
      initialSchedule={scheduleData.schedule}
      sidebarSections={scheduleData.sidebarSections}
    />
  );
}

Option 4: Hardcoded JSON

const schedule = {
  title: "Weekly Schedule",
  defaultTarget: null,
  items: [
    { id: 1, type: 'taptree', targetId: 'tt-1', name: 'Main Hall', 
      day: 0, startTime: '09:00', endTime: '12:00' }
  ]
};

const sidebarSections = [
  { title: "Locations", icon: "fa-folder-tree", 
    items: [{ id: "tt-1", name: "Main Hall" }] }
];

<ScheduleCanvas
  initialSchedule={schedule}
  sidebarSections={sidebarSections}
/>

JSON Data Format

Complete Data Structure

{
  "schedule": {
    "title": "Weekly Schedule",
    "defaultTarget": null,
    "items": [
      {
        "id": 1,
        "targetId": "loc-1",
        "name": "Main Hall",
        "day": 0,
        "startTime": "09:00",
        "endTime": "12:00"
      },
      {
        "id": 2,
        "targetId": "https://example.com",
        "name": "",
        "day": 1,
        "startTime": "14:00",
        "endTime": "16:00"
      }
    ]
  },
  "sidebarSections": [
    {
      "title": "Locations",
      "icon": "fa-folder-tree",
      "items": [
        { "id": "loc-1", "name": "Main Hall" },
        { "id": "loc-2", "name": "Meeting Room" }
      ]
    },
    {
      "title": "Websites",
      "icon": "fa-globe",
      "items": [
        { "id": "https://example.com" }
      ]
    }
  ]
}

Important Notes:

  • Schedule data (title, items, defaultTarget) is grouped together, while sidebar sections are separate
  • Items have no type field - they're all treated the same
  • targetId can be any string: an ID, URL, or any identifier
  • If name is empty/null, the targetId is displayed as the name

See the /examples directory for complete examples of loading data from JSON files, APIs, and other sources.

Browser Support

  • Chrome 90+
  • Firefox 88+
  • Safari 14+
  • Edge 90+

Modern browsers with Canvas API support required.

Development

# Clone the repository
git clone https://github.com/visitor-reach/schedule-canvas.git

# Install dependencies
npm install

# Run development server
npm run dev

# Build library for distribution
npm run build:lib

# Build example app
npm run build

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © Visitor Reach

Support

For issues and questions:

  • GitHub Issues: https://github.com/visitor-reach/schedule-canvas/issues
  • Documentation: README-REACT.md