@getmicdrop/venue-calendar
A beautiful, customizable calendar component built with Svelte for displaying comedy events. Perfect for comedy clubs, venues, and event organizers who want to showcase their upcoming shows.
Features
Three View Modes: List, Gallery, and Calendar views
Beautiful UI: Modern, responsive design built with Tailwind CSS
Mobile-Friendly: Swipe gestures, touch-optimized, responsive design
Easy Integration: Works with React, Vue, vanilla JS, and more
One script tag: Self-hosted bundle — paste one <script>, no build step
Styles included: The bundle injects its own CSS — no separate stylesheet to link
Auto-Mount: Automatically finds and mounts to designated containers
Customizable: Configure views, navigation, and more
Dark Mode: Built-in light, dark, and high-contrast themes
Accessible: ARIA labels, keyboard navigation, screen reader support
Event Status: Visual badges for "On Sale", "Selling Fast", "Sold Out"
Installation
Add one <script> tag pointing at the Micdrop-hosted bundle. The package is
private (not on npm/jsDelivr) — it is delivered from get-micdrop.com:
<script
defer
src="https://get-micdrop.com/embed/venue-calendar.iife.js"
></script>- No stylesheet to link. The bundle injects its own CSS at runtime, so a
plain page with just this
<script>renders fully styled. - Use
defer(orasync) so the bundle never blocks page parse. It is ~310 KB gzipped. - Serve your page over HTTPS. Checkout stores a
Securecart cookie, which Safari drops on non-secure (http://) pages.
Heads-up: the exact hosted URL is being finalized (Micdrop ticket MIC-1130). Confirm the address with Micdrop before going live.
The
import { ... } from '@getmicdrop/venue-calendar'examples further down are for Micdrop-internal apps that build with a bundler and have registry access to the private package. Public sites (comedy clubs, etc.) use the<script>tag above — notnpm install.
Quick Start
Method 1: Auto-Mount (Easiest)
Simply add a div with the class micdrop-calendar-container and the calendar will automatically mount:
<!DOCTYPE html>
<html>
<head>
<title>My Comedy Club</title>
<!-- Preload the bundle while the page parses. defer keeps the
<script> non-blocking; preload starts the fetch earlier. -->
<link
rel="preload"
as="script"
href="https://get-micdrop.com/embed/venue-calendar.iife.js"
/>
<script
defer
src="https://get-micdrop.com/embed/venue-calendar.iife.js"
></script>
</head>
<body>
<!-- Calendar auto-mounts here. Use data-organization-id to show all of an
organization's shows, or data-venue-id for a single venue. -->
<div
class="micdrop-calendar-container"
data-organization-id="your-organization-id"
data-view="calendar"
data-show-view-options="true"
data-show-month-switcher="true"
data-locale="en-US"
></div>
</body>
</html>Method 2: Web Component
Use the custom <micdrop-calendar> element:
<!DOCTYPE html>
<html>
<head>
<title>My Comedy Club</title>
<script
defer
src="https://get-micdrop.com/embed/venue-calendar.iife.js"
></script>
</head>
<body>
<!-- Web Component -->
<micdrop-calendar
venue-id="your-venue-id"
view="calendar"
show-view-options="true"
show-month-switcher="true"
locale="en-US"
>
</micdrop-calendar>
</body>
</html>Method 3: JavaScript API
For more control, use the JavaScript API:
<!DOCTYPE html>
<html>
<head>
<title>My Comedy Club</title>
</head>
<body>
<div id="my-calendar"></div>
<!-- Load the bundle, then call the global it exposes. -->
<script
defer
src="https://get-micdrop.com/embed/venue-calendar.iife.js"
></script>
<script>
window.addEventListener('load', function () {
window.VenueCalendar.initVenueCalendar({
target: '#my-calendar',
organizationId: 'your-organization-id',
view: 'calendar',
showViewOptions: true,
showMonthSwitcher: true,
});
});
</script>
</body>
</html>Framework Integration
React
import React, { useEffect, useRef } from 'react';
import { initVenueCalendar, unmount } from '@getmicdrop/venue-calendar';
function VenueCalendarComponent({ venueId, view = 'calendar' }) {
const calendarRef = useRef(null);
const instanceRef = useRef(null);
useEffect(() => {
if (!calendarRef.current) return;
instanceRef.current = initVenueCalendar({
target: calendarRef.current,
venueId,
view,
events: [],
showViewOptions: true,
showMonthSwitcher: true,
});
return () => {
// Svelte 5 — components mounted via `mount()` are destroyed via
// the `unmount` helper, not `.$destroy()` (that was Svelte 4).
if (instanceRef.current) {
try {
unmount(instanceRef.current);
} catch {}
instanceRef.current = null;
}
};
}, [venueId, view]);
return <div ref={calendarRef}></div>;
}
export default VenueCalendarComponent;Error reporting and runtime config
Wire any errors caught by the widget into your existing monitoring:
import { configureVenueCalendar } from '@getmicdrop/venue-calendar';
configureVenueCalendar({
onError: (err, { source }) => {
Sentry.captureException(err, { tags: { source, micdrop: true } });
},
// Optional overrides for self-hosted backends or regional failover.
// apiBaseUrl: 'https://api.eu.micdrop.com',
// apiTimeout: 15000,
// apiRetries: 2,
});For ad-hoc support diagnostics, the widget exposes its version on
window:
window.__MICDROP_CALENDAR__.version; // e.g. "3.6.23"Usage in React App:
import React, { useState } from 'react';
import VenueCalendarComponent from './VenueCalendarComponent';
function App() {
const [venueId, setVenueId] = useState('comedy-club-123');
const [view, setView] = useState('calendar');
return (
<div style={{ padding: '20px' }}>
<h1>Event Viewer</h1>
<div style={{ marginBottom: '20px' }}>
<label>Venue ID:</label>
<input
type="text"
value={venueId}
onChange={e => setVenueId(e.target.value)}
/>
</div>
<div style={{ marginBottom: '20px' }}>
<label>Select View:</label>
<label>
<input
type="radio"
value="list"
checked={view === 'list'}
onChange={e => setView(e.target.value)}
/>
List
</label>
<label>
<input
type="radio"
value="gallery"
checked={view === 'gallery'}
onChange={e => setView(e.target.value)}
/>
Gallery
</label>
<label>
<input
type="radio"
value="calendar"
checked={view === 'calendar'}
onChange={e => setView(e.target.value)}
/>
Calendar
</label>
</div>
<VenueCalendarComponent venueId={venueId} view={view} />
</div>
);
}
export default App;Vue 3
<template>
<div ref="calendarContainer"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { initVenueCalendar } from '@getmicdrop/venue-calendar';
const props = defineProps({
venueId: String,
view: {
type: String,
default: 'calendar',
},
});
const calendarContainer = ref(null);
let calendarInstance = null;
onMounted(() => {
calendarInstance = initVenueCalendar({
target: calendarContainer.value,
venueId: props.venueId,
view: props.view,
events: [],
showViewOptions: true,
showMonthSwitcher: true,
});
});
onUnmounted(() => {
if (calendarInstance && calendarInstance.$destroy) {
calendarInstance.$destroy();
}
});
watch(
() => props.venueId,
newId => {
if (calendarInstance) {
calendarInstance.$destroy();
calendarInstance = initVenueCalendar({
target: calendarContainer.value,
venueId: newId,
view: props.view,
events: [],
showViewOptions: true,
showMonthSwitcher: true,
});
}
}
);
</script>Svelte
<script>
import { VenueCalendar } from '@getmicdrop/venue-calendar';
import { Calendar, Grid, List } from 'carbon-icons-svelte';
import { writable } from 'svelte/store';
let venueId = 'your-venue-id';
let currentMonth = writable(new Date().getUTCMonth());
let currentYear = writable(new Date().getUTCFullYear());
function handleNext() {
currentMonth.update(m => m + 1);
}
function handlePrev() {
currentMonth.update(m => m - 1);
}
</script>
<VenueCalendar
showViewOptions={[
{ id: 0, text: 'List view', icon: List },
{ id: 1, text: 'Gallery view', icon: Grid },
{ id: 2, text: 'Calendar view', icon: Calendar },
]}
showMonthSwitcher={true}
events={[]}
{currentMonth}
{currentYear}
{handleNext}
{handlePrev}
on:eventClick={e => console.log('Event clicked:', e.detail)}
/>Angular
import {
Component,
OnInit,
OnDestroy,
ElementRef,
ViewChild,
} from '@angular/core';
import { initVenueCalendar } from '@getmicdrop/venue-calendar';
@Component({
selector: 'app-venue-calendar',
template: '<div #calendarContainer></div>',
})
export class VenueCalendarComponent implements OnInit, OnDestroy {
@ViewChild('calendarContainer', { static: true })
calendarContainer!: ElementRef;
private calendarInstance: any;
ngOnInit() {
this.calendarInstance = initVenueCalendar({
target: this.calendarContainer.nativeElement,
venueId: 'your-venue-id',
view: 'calendar',
events: [],
showViewOptions: true,
showMonthSwitcher: true,
});
}
ngOnDestroy() {
if (this.calendarInstance && this.calendarInstance.$destroy) {
this.calendarInstance.$destroy();
}
}
}Configuration Options
Data Attributes (for auto-mount)
| Attribute | Type | Default | Description |
|---|---|---|---|
data-venue-id |
string | '' |
The venue ID to fetch events for |
data-view |
string | 'calendar' |
Initial view: 'list', 'gallery', or 'calendar' |
data-show-view-options |
boolean | true |
Show view switcher buttons |
data-show-month-switcher |
boolean | true |
Show month navigation controls |
JavaScript API Options
initVenueCalendar({
target: '.my-calendar', // CSS selector or HTMLElement (required)
venueId: 'venue-123', // Venue ID (optional)
view: 'calendar', // 'list', 'gallery', or 'calendar' (default: 'calendar')
events: [], // Array of event objects (default: [])
showViewOptions: true, // Show view switcher (default: true)
showMonthSwitcher: true, // Show month navigation (default: true)
});Event Object Structure
{
id: 'event-123',
name: 'Comedy Night',
date: '2024-10-25T20:00:00Z',
image: 'https://example.com/image.jpg',
status: 'On Sale',
timeline: '8:00 PM - 10:00 PM',
// ... other fields
}Views
Calendar View
The default view showing events in a monthly calendar grid. Perfect for venues with regular shows.
List View
A vertical list layout showing all upcoming events with details. Great for mobile experiences.
Gallery View
A grid layout displaying event posters in a gallery format. Ideal for showcasing event imagery.
WordPress Integration
For WordPress sites, you can add this to your page/post HTML:
<div
class="micdrop-calendar-container"
data-venue-id="your-venue-id"
data-view="calendar"
></div>
<script src="https://get-micdrop.com/embed/venue-calendar.iife.js"></script>Or add the script to your theme's footer and use the div anywhere in your content.
Styling
The calendar comes with built-in styles using Tailwind CSS. If you need to customize the appearance, you can override the CSS classes or add your own styles.
/* Example: Custom styling */
.micdrop-calendar-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}Theming
The calendar supports comprehensive theming via CSS custom properties and JavaScript utilities.
Using CSS Custom Properties
Override the default theme by setting CSS custom properties:
/* Custom brand colors */
.micdrop-calendar-container {
--Brand-Primary: 270 76% 60%; /* Purple */
--Text-Primary: 0 0% 10%;
--BG-Primary: 0 0% 100%;
}
/* Dark mode */
.dark .micdrop-calendar-container,
[data-theme='dark'] .micdrop-calendar-container {
--Brand-Primary: 270 76% 70%;
--Text-Primary: 0 0% 95%;
--BG-Primary: 0 0% 10%;
}Available CSS Variables
| Variable | Description | Default (Light) |
|---|---|---|
--Brand-Primary |
Primary brand color | 217 91% 60% (Blue) |
--Text-Primary |
Main text color | 0 0% 0% |
--Text-Secondary |
Secondary text | 0 0% 40% |
--BG-Primary |
Main background | 0 0% 100% |
--BG-Secondary |
Secondary background | 0 0% 98% |
--Stroke-Primary |
Border colors | 0 0% 80% |
--Status-OnSale |
"On Sale" badge | 217 91% 60% |
--Status-SellingFast |
"Selling Fast" badge | 38 92% 50% |
--Status-SoldOut |
"Sold Out" badge | 0 84% 60% |
--Today-BG |
Today's date background | 217 91% 97% |
--Focus-Ring |
Keyboard focus ring | 217 91% 60% |
Using JavaScript Theme Utilities
import {
applyTheme,
themes,
generateThemeCSS,
} from '@getmicdrop/venue-calendar';
// Apply a preset theme
applyTheme(themes.dark);
// Apply to a specific container
const container = document.querySelector('.micdrop-calendar-container');
applyTheme(themes.dark, container);
// Create a custom theme
const myTheme = {
brandPrimary: '270 76% 60%', // Purple
textPrimary: '0 0% 10%',
bgPrimary: '0 0% 100%',
statusOnSale: '142 71% 45%', // Green for on sale
};
applyTheme(myTheme);
// Generate CSS string for embedding
const cssString = generateThemeCSS(myTheme);
console.log(cssString);
// Output: :root { --Brand-Primary: 270 76% 60%; ... }Preset Themes
Three themes are included out of the box:
import { themes } from '@getmicdrop/venue-calendar';
// Light theme (default)
applyTheme(themes.light);
// Dark theme
applyTheme(themes.dark);
// High contrast (accessibility)
applyTheme(themes.highContrast);Automatic Dark Mode
The calendar automatically respects the user's system preference:
/* Automatically applied when user prefers dark mode */
@media (prefers-color-scheme: dark) {
/* Dark theme variables are applied */
}You can also manually toggle dark mode:
<!-- Add 'dark' class to enable dark theme -->
<div class="dark">
<div class="micdrop-calendar-container" data-venue-id="123"></div>
</div>
<!-- Or use data-theme attribute -->
<div data-theme="dark">
<div class="micdrop-calendar-container" data-venue-id="123"></div>
</div>Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Mobile browsers (iOS Safari, Chrome Mobile)
Development
Building the Package
# Install dependencies
npm install
# Build the library
npm run build:lib
# Development mode (SvelteKit app)
npm run dev
# Preview production build
npm run previewProject Structure
venue-calendar/
├── src/
│ ├── components/ # Svelte components
│ │ ├── Calendar/
│ │ ├── CalendarContainer/
│ │ └── Button/
│ ├── lib/ # Library entry points
│ │ ├── VenueCalendar.js
│ │ └── web-component.js
│ └── routes/ # SvelteKit routes (for dev)
├── dist/ # Built package (generated)
├── package.json
├── vite.config.lib.js # Library build config
└── README.md
Lockfile policy
package-lock.json is the single canonical lockfile — CI and the publish
workflow install with npm ci. Do not commit yarn.lock or
pnpm-lock.yaml (both gitignored). Local dev machines may use pnpm for the
svelte-components symlink workflow, but dependency changes must land in
package-lock.json via npm.
API Reference
initVenueCalendar(options)
Initialize a calendar instance.
Parameters:
options(Object): Configuration options
Returns: Svelte component instance
Example:
const calendar = initVenueCalendar({
target: '#calendar',
venueId: 'venue-123',
view: 'calendar',
});autoMount()
Automatically mount calendars to all elements with class micdrop-calendar-container.
Example:
import { autoMount } from '@getmicdrop/venue-calendar';
autoMount();Component Events
The calendar component emits events that you can listen to:
const calendar = initVenueCalendar({
target: '#calendar',
// ... other options
});
// Listen to component events (if using Svelte component directly)
calendar.$on('eventClick', event => {
console.log('Event clicked:', event.detail);
});Troubleshooting
Calendar not appearing
- Check the script is loaded: Open browser console and verify no errors
- Verify container exists: Make sure the target element exists in the DOM
- Check data attributes: Ensure attributes are correctly formatted with
data-prefix
Styles not applying
- CSS not loaded: The styles are bundled in the JS file and auto-injected — no separate stylesheet needed
- CSS conflicts: Check if other styles are overriding the calendar styles
- Bundle didn't load: Confirm the
<script src>points at the Micdrop-hosted bundle and returns 200 (not 404)
Events not showing
- Check event data format: Ensure events match the expected structure
- Date format: Use ISO 8601 format for dates (
YYYY-MM-DDTHH:mm:ssZ) - Venue ID: Verify the venue ID is correct
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT MicDrop
Support
For issues, questions, or feature requests, please visit: https://github.com/get-micdrop/venue-calendar/issues
Made with by the MicDrop team