Calendar & Maps
Adminator integrates FullCalendar 6.1 and jsvectormap 1.7 — both real libraries, both fully themed via CSS variables, both re-render on theme toggle. Drop a data attribute on an element, get a calendar or a world map.
Last updated May 21, 2026
The Calendar and Vector Maps pages aren’t mockups — they’re real FullCalendar and jsvectormap instances wired into the design system. Each lives in its own module:
src/assets/scripts/2026/calendar.js— FullCalendar 6.1, four views, 24 seed eventssrc/assets/scripts/2026/maps.js— jsvectormap 1.7, world map, city markers
Both modules use the same theme-aware pattern as Charts: a tokens() function reads CSS variables, the library instance is built with those values, and a MutationObserver rebuilds when the theme attribute changes.
FullCalendar
Mounting a calendar
<section class="cal-main">
<div class="cal-toolbar">
<div class="cal-toolbar-left">
<div class="cal-month">April <span class="yr">2026</span></div>
<div class="cal-nav">
<button class="cal-nav-btn" aria-label="Previous">‹</button>
<button class="cal-today-btn">Today</button>
<button class="cal-nav-btn" aria-label="Next">›</button>
</div>
</div>
<div class="cal-views">
<button class="cal-view-tab">Day</button>
<button class="cal-view-tab">Week</button>
<button class="cal-view-tab is-active">Month</button>
<button class="cal-view-tab">Agenda</button>
</div>
</div>
<div data-fc style="min-height: 540px;"></div>
</section>
<div data-fc></div> is the FullCalendar mount point. The default FullCalendar header is hidden (headerToolbar: false) — the surrounding .cal-toolbar is what users interact with. Adminator’s wiring in calendar.js binds those buttons by class:
.cal-nav-btn— bound by position within the.cal-toolbar-leftgroup. The first one callscalendar.prev(), the second callscalendar.next(). (A third.cal-nav-btnin.cal-toolbar-rightis wired separately as a filter button.).cal-today-btn— callscalendar.today().cal-view-tab— bound bytextContent('Day','Week','Month','Agenda'), each mapped to a FullCalendar view name viaVIEW_MAP. Adds.is-activeto the clicked tab..cal-month— display target for the current month/year, updated whenever navigation changes the visible range.
This is class-driven rather than data-*-driven because the markup pre-existed the JS wiring — and the binding logic is intentionally simple (no per-button configuration, no event delegation). If you’re customizing the toolbar, keep the class names and the order of .cal-nav-btn buttons in the left group.
Seed events
By default, the module loads 24 sample events across April 2026:
const SEED_EVENTS = [
{ title: 'Q2 kickoff', start: '2026-04-01T09:00', classNames: ['fc-cat-work'] },
{ title: 'Design review', start: '2026-04-02T11:00', classNames: ['fc-cat-team'] },
{ title: '🎂 Sara birthday', start: '2026-04-05', allDay: true, classNames: ['fc-cat-birthday'] },
{ title: '✈ Lisbon trip', start: '2026-04-09', end: '2026-04-13', allDay: true, classNames: ['fc-cat-travel'] },
// ...
];
The classNames field tags each event with a category — fc-cat-work, fc-cat-team, fc-cat-personal, fc-cat-travel, fc-cat-finance, fc-cat-birthday. Each category has its own colored chip in _fullcalendar.scss:
.fc .fc-event.fc-cat-work { background: var(--primary-soft); color: var(--primary); border-left-color: var(--primary); }
.fc .fc-event.fc-cat-team { background: var(--success-soft); color: var(--success); border-left-color: var(--success); }
.fc .fc-event.fc-cat-personal { background: var(--purple-soft); color: var(--purple); border-left-color: var(--purple); }
.fc .fc-event.fc-cat-travel { background: var(--info-soft); color: var(--info); border-left-color: var(--info); }
.fc .fc-event.fc-cat-finance { background: var(--warning-soft); color: var(--warning); border-left-color: var(--warning); }
.fc .fc-event.fc-cat-birthday { background: var(--pink-soft); color: var(--pink); border-left-color: var(--pink); }
To use real events, replace SEED_EVENTS with your own array, or pass them to the calendar dynamically — FullCalendar supports events as a function or a URL too.
Replacing seed events with real data
Two common patterns:
Static replacement — edit SEED_EVENTS directly. Fine for demos and one-off pages.
Dynamic loading — change the events option in calendar.js from the seed array to a fetcher:
calendar = new Calendar(host, {
// ...
events: async (info, successCallback, failureCallback) => {
try {
const r = await fetch(`/api/events?start=${info.startStr}&end=${info.endStr}`);
successCallback(await r.json());
} catch (e) {
failureCallback(e);
}
},
});
FullCalendar calls the function every time the visible range changes — month/week navigation, view switching, etc. Your API returns events in the shape { title, start, end?, allDay?, classNames?: [] }.
See the FullCalendar docs on event sources for the full schema.
The four views
| View | FullCalendar plugin | URL hash | Use |
|---|---|---|---|
| Month | dayGridMonth | Month | Default overview |
| Week | timeGridWeek | Week | Hour-by-hour scheduling |
| Day | timeGridDay | Day | Single-day focus |
| Agenda | listWeek | Agenda | Flat list, good for mobile |
The mapping lives in calendar.js:
const VIEW_MAP = {
Day: 'timeGridDay',
Week: 'timeGridWeek',
Month: 'dayGridMonth',
Agenda: 'listWeek',
};
If you want to drop Agenda (you don’t ship a list view) or add a year view, edit VIEW_MAP and the corresponding toolbar buttons in calendar.html.
FullCalendar theming
_fullcalendar.scss (~130 lines) overrides FullCalendar’s default colors with CSS variables. Day cells, headers, event chips, “today” highlight, weekend tinting — all token-driven. Light and dark mode work without any per-mode CSS because they reference the same variable names.
jsvectormap (world map)
Mounting a vector map
<div class="card">
<div class="card-head">
<h3>Active regions</h3>
</div>
<div data-vmap style="height: 360px"></div>
</div>
<div data-vmap></div> is the mount point. maps.js finds it on page load and instantiates jsVectorMap with the world map and a list of city markers.
Default markers
The module ships with 10 markers — cities chosen to span every continent:
const MARKERS = [
{ name: 'Riga', coords: [56.95, 24.10] },
{ name: 'New York', coords: [40.71, -74.00] },
{ name: 'San Francisco', coords: [37.77, -122.42] },
{ name: 'London', coords: [51.50, -0.12] },
{ name: 'Berlin', coords: [52.52, 13.40] },
{ name: 'Tokyo', coords: [35.68, 139.69] },
{ name: 'Sydney', coords: [-33.86, 151.21] },
{ name: 'São Paulo', coords: [-23.55, -46.63] },
{ name: 'Cape Town', coords: [-33.92, 18.42] },
{ name: 'Dubai', coords: [25.27, 55.30] },
];
Coordinates are [latitude, longitude]. Replace this array with your real data points.
Map theming
maps.js themes jsvectormap with five CSS variables — --primary, --purple, --border-soft, --border, and --bg-card. The library config is nested under regionStyle and markerStyle, each with initial and hover states:
regionStyle: {
initial: { fill: t.soft, stroke: t.border, strokeWidth: 0.4, fillOpacity: 1 },
hover: { fill: t.primary, fillOpacity: 0.5 },
},
markerStyle: {
initial: { fill: t.primary, stroke: t.bg, strokeWidth: 2, r: 5 },
hover: { fill: t.purple, stroke: t.bg, strokeWidth: 2, r: 7 },
},
So: land is the soft border color (a subtle muted tone), hovering a country flushes it with translucent primary, markers are primary dots that grow and switch to purple on hover.
When the theme toggles, the module destroys the existing map instance and rebuilds with fresh tokens — no flicker, no manual color updates. The pattern is the same MutationObserver used by Charts.
Markers vs. choropleth
The default integration shows markers (dots at specific lat/long). For a choropleth (color-by-region) map, jsvectormap supports a series.regions config:
const map = new jsVectorMap({
selector: host,
map: 'world',
series: {
regions: [{
values: { US: 1200, GB: 890, DE: 670, JP: 540 /* ... */ },
scale: [t.primary + '20', t.primary],
normalizeFunction: 'polynomial',
}],
},
});
That replaces the marker array with a country-keyed values object. Region codes are ISO 3166-1 alpha-2 (US, GB, DE, FR, JP, etc.).
Other maps
jsvectormap ships with world, us-aea, us-merc, us-lcc, spain, russia, canada, and a handful of others. To use one:
import 'jsvectormap/dist/maps/us-aea.js';
const map = new jsVectorMap({
selector: host,
map: 'us-aea',
// ...
});
Imports register the map into jsvectormap’s internal registry — multiple maps can be loaded at once and chosen by name.
A note on bundle size
| Library | Min+gzip | Lazy-loaded |
|---|---|---|
| FullCalendar 6.1 + 4 plugins | ~75 KB | Yes (vendor-fullcalendar.js) |
| jsvectormap 1.7 + world map | ~28 KB | Yes (vendors.js chunk) |
| Chart.js 4.5 (with registerables) | ~52 KB | Yes (vendor-chartjs.js) |
Webpack’s chunk-splitting means pages that don’t use a library don’t load it. The Charts page pulls in vendor-chartjs.js, the Calendar page pulls in vendor-fullcalendar.js, and the Vector Maps page pulls in jsvectormap. The dashboard uses Chart.js so it pulls the chartjs chunk but not the others.
If you remove a page that’s the only consumer of a library, also remove the library from package.json — otherwise it sits in node_modules as dead weight.