@visitor-reach/schedule-canvas
v1.0.0
Published
A React-based interactive schedule canvas component with drag-and-drop functionality
Maintainers
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.
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-canvasRequired Peer Dependencies
Make sure you have React installed:
npm install react react-domRequired 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 devThe 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
isEditableis set tofalse, 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
displayIDis set totrue, 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
addNewis set totrue, 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. idLabelandnameLabelcustomize the field labels in the edit modal. For example, for a "Websites" section, you might useidLabel: "URL"andnameLabel: "Website Name"instead of the default "Target ID" and "Display Name (optional)".idPlaceholderandnamePlaceholdercustomize the placeholder text shown in the input fields. For example,idPlaceholder: "https://example.com"andnamePlaceholder: "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;
}) => voidonScheduleChange (optional)
Callback function called whenever schedule changes:
(schedule: {
items: ScheduleItem[];
title: string;
defaultTarget: DefaultTarget | null;
}) => voidKeyboard 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
typefield - they're all treated the same targetIdcan be any string: an ID, URL, or any identifier- If
nameis empty/null, thetargetIdis 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 buildContributing
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
