Dual calendar plugin for BW DatePicker - Two months side by side
npm install @bw-ui/datepicker-dual-calendarbash
npm install @bw-ui/datepicker @bw-ui/datepicker-dual-calendar
`
> ā ļø Peer Dependency: Requires @bw-ui/datepicker core package
š Quick Start
$3
`javascript
import { BWDatePicker } from '@bw-ui/datepicker';
import { DualCalendarPlugin } from '@bw-ui/datepicker-dual-calendar';
import '@bw-ui/datepicker/css';
import '@bw-ui/datepicker-dual-calendar/css';
const picker = new BWDatePicker('#date-input').use(DualCalendarPlugin);
`
$3
`html
rel="stylesheet"
href="https://unpkg.com/@bw-ui/datepicker/dist/bw-datepicker.min.css"
/>
rel="stylesheet"
href="https://unpkg.com/@bw-ui/datepicker-dual-calendar/dist/bw-dual-calendar.min.css"
/>
`
āļø Options
`javascript
.use(DualCalendarPlugin, {
// Navigation
linked: true, // Navigate all calendars together (default: true)
months: 2, // Number of months to show: 2-4 (default: 2)
gap: 1, // Months gap between calendars (default: 1)
// Starting position
leftMonth: null, // Starting month 0-11 (default: current)
leftYear: null, // Starting year (default: current)
// Display
showNavigation: 'outside', // Navigation button position
})
`
$3
| Option | Type | Default | Description |
| ---------------- | --------- | ----------- | -------------------------------------------------------- |
| linked | boolean | true | Navigate all calendars together |
| months | number | 2 | Number of months to display (2-4) |
| gap | number | 1 | Months gap between calendars (1 = consecutive) |
| leftMonth | number | null | Starting month (0-11), null = current month |
| leftYear | number | null | Starting year, null = current year |
| showNavigation | string | 'outside' | Navigation position: 'outside' \| 'both' \| 'left' |
š Examples
$3
`javascript
new BWDatePicker('#date-input').use(DualCalendarPlugin);
`
`
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā December 2025 January 2026 āŗ ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Su Mo Tu We Th Fr Sa ā Su Mo Tu We Th Fr Sa ā
ā 1 2 3 4 5 6 ā 1 2 3 ā
ā 7 8 9 10 11 12 13 ā 4 5 6 7 8 9 10 ā
ā 14 15 16 17 18 19 20 ā 11 12 13 14 15 16 17 ā
ā 21 22 23 24 25 26 27 ā 18 19 20 21 22 23 24 ā
ā 28 29 30 31 ā 25 26 27 28 29 30 31 ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāā
`
$3
`javascript
import { RangePlugin } from '@bw-ui/datepicker-range';
import { DualCalendarPlugin } from '@bw-ui/datepicker-dual-calendar';
new BWDatePicker('#booking-date')
.use(DualCalendarPlugin, {
linked: true,
gap: 1,
})
.use(RangePlugin, {
minRange: 1,
maxRange: 30,
});
`
$3
`javascript
.use(DualCalendarPlugin, {
firstDayOfWeek: 1, // Monday
})
`
$3
`javascript
.use(DualCalendarPlugin, {
linked: true,
gap: 6, // Show 6 months apart (e.g., Jan + July)
})
`
$3
`javascript
.use(DualCalendarPlugin, {
linked: false, // Each calendar navigates separately
})
`
$3
`javascript
.use(DualCalendarPlugin, {
monthNames: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
dayNames: ['Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa'],
firstDayOfWeek: 1,
})
`
$3
`javascript
import { LocalePlugin } from '@bw-ui/datepicker-locale';
import { DualCalendarPlugin } from '@bw-ui/datepicker-dual-calendar';
new BWDatePicker('#date-input')
.use(LocalePlugin, { locale: 'de-DE' })
.use(DualCalendarPlugin);
// Month/day names automatically use German locale
`
š API Methods
Access via picker.getPlugin('dual-calendar'):
`javascript
const dual = picker.getPlugin('dual-calendar');
// Get calendar states
dual.getLeft(); // { month: 11, year: 2025 }
dual.getRight(); // { month: 0, year: 2026 }
// Navigation
dual.prevMonth(); // Go to previous month
dual.nextMonth(); // Go to next month
dual.prevYear(); // Go to previous year
dual.nextYear(); // Go to next year
// Go to specific date
dual.goToDate(new Date('2025-06-15'));
// Go to today
dual.goToToday();
// Linked mode
dual.setLinked(false); // Disable linked navigation
dual.isLinked(); // Check if linked
// Refresh
dual.refresh();
`
š” Events
`javascript
const picker = new BWDatePicker('#date-input').use(DualCalendarPlugin);
// Navigation event
picker.on('dual:navigate', ({ left, right }) => {
console.log('Left calendar:', left.month, left.year);
console.log('Right calendar:', right.month, right.year);
});
`
šØ Theming
$3
`css
.bw-datepicker--dual {
/ Gap between calendars /
--bw-dual-gap: 16px;
/ Navigation /
--bw-dual-border: #e5e7eb;
--bw-dual-btn-bg: #fff;
--bw-dual-btn-color: #374151;
--bw-dual-btn-hover-bg: #f3f4f6;
--bw-dual-btn-hover-border: #d1d5db;
--bw-dual-btn-active-bg: #e5e7eb;
/ Title /
--bw-dual-title-color: #111827;
/ Weekdays /
--bw-dual-weekday-color: #6b7280;
/ Days /
--bw-dual-day-color: #111827;
--bw-dual-day-bg: transparent;
--bw-dual-day-hover-bg: #f3f4f6;
--bw-dual-day-other-color: #d1d5db;
--bw-dual-today-color: #3b82f6;
--bw-dual-selected-bg: #3b82f6;
}
`
$3
`css
.bw-datepicker--dual {
--bw-dual-gap: 24px;
--bw-dual-selected-bg: #10b981;
--bw-dual-today-color: #10b981;
}
`
$3
`javascript
import { ThemingPlugin } from '@bw-ui/datepicker-theming';
import { DualCalendarPlugin } from '@bw-ui/datepicker-dual-calendar';
new BWDatePicker('#date-input')
.use(ThemingPlugin, {
theme: 'dark',
customVars: {
'--bw-dual-selected-bg': '#10b981',
'--bw-dual-today-color': '#34d399',
},
})
.use(DualCalendarPlugin);
`
$3
Dark mode is automatically supported via:
- [data-bw-theme="dark"] attribute (Theming plugin)
- .bw-datepicker--dark class (manual)
š Combining with Other Plugins
$3
`javascript
import { BWDatePicker } from '@bw-ui/datepicker';
import { ThemingPlugin } from '@bw-ui/datepicker-theming';
import { DualCalendarPlugin } from '@bw-ui/datepicker-dual-calendar';
import { RangePlugin } from '@bw-ui/datepicker-range';
import { DataPlugin } from '@bw-ui/datepicker-data';
const picker = new BWDatePicker('#booking')
.use(ThemingPlugin, { theme: 'light' })
.use(DualCalendarPlugin, { linked: true })
.use(DataPlugin, {
data: {
'2025-12-24': { price: 199, status: 'available' },
'2025-12-25': { price: 299, status: 'limited' },
'2025-12-31': { price: 499, status: 'sold-out' },
},
renderDay: (date, data) => (data ? $${data.price} : ''),
})
.use(RangePlugin, {
minRange: 1,
maxRange: 14,
presets: ['thisWeek', 'thisMonth'],
presetsPosition: 'left',
});
`
š What's Included
`
dist/
āāā bw-dual-calendar.min.js # IIFE build (for