@archpublicwebsite/rangepicker
v1.5.17
Published
Custom date range picker component for PBA Hotel Apps
Maintainers
Readme
@archpublicwebsite/rangepicker
A lightweight Vue 3 date range picker with Tailwind CSS support, built for Archipelago hotel booking flows.
Overview
@archpublicwebsite/rangepicker gives you a calendar date picker that handles both date-range
and single-date selection, per-day pricing, holiday/weekend styling, and desktop, mobile
bottom-sheet, and full-screen display variants.
Import Datepicker (the default export) to get a complete input + calendar out of the box, or
compose Rangepicker directly if you need to manage the trigger element and open state
yourself.
Features
- Range or single-date selection in one component (
singleMode) - Tailwind utility classes prefixed with
arch-so they never collide with your app's own Tailwind, Bootstrap, or Vuetify styles - Desktop dropdown, mobile bottom sheet, and full-screen variants, with viewport-aware positioning and scroll clamping for tall content
- Per-day pricing: currency formatting, a loading skeleton, and a low-price legend
- Configurable weekend days and holiday markers
- Themeable via HEX color props
- Full TypeScript definitions
- Keyboard and screen-reader support
- Works in Vue 3 and Nuxt 3
Installation
pnpm add @archpublicwebsite/rangepicker vue dayjs @vueuse/corevue, dayjs, and @vueuse/core are peer dependencies — install them alongside the package.
Quick start
Import the component and its stylesheet:
<script setup lang="ts">
import { ref } from 'vue'
import Datepicker from '@archpublicwebsite/rangepicker'
import '@archpublicwebsite/rangepicker/style.css'
const dates = ref('')
</script>
<template>
<Datepicker v-model="dates" primary-color="#3b82f6" placeholder="Check in / Check out" />
</template>What's new
The most recent release adds:
weekendDays— pick which days of the week render as "weekend" (red day number). Defaults to['saturday', 'sunday'].showOtherMonthDays— set tofalseto blank out leading/trailing adjacent-month cells instead of rendering them.mobileTopPositiondefault behavior changed — left unset, the mobile popup now anchors below the trigger input, matching desktop, instead of always centering. Pass'center'to keep the old behavior.variant="full"— a full-screen takeover variant, alongsidedesktop/mobile.monthChangeevent — fires with all currently visible months on prev/next navigation.- Desktop popups now clamp to the viewport height and scroll, matching mobile — tall calendars and price lists no longer clip.
See CHANGELOG.md for the full release history.
Public API
Components
| Export | Notes |
| --- | --- |
| Datepicker | Default export. Range or single-date via singleMode. Use this unless you need advanced composition. |
| Rangepicker | The underlying calendar/popup Datepicker wraps. Use it directly only when you manage triggerElement and isOpen yourself. |
| RangepickerInput, DatepickerInput | Deprecated — use Datepicker instead. Removed in v1.6. |
Composable
useRangepicker(triggerRef, options)— returnsisOpen,dateRange, andopen/close/togglehelpers.useDatepicker(...)— deprecated alias ofuseRangepicker.
Types
DateRange, DatepickerProps, RangepickerProps, RangepickerEmits, RangepickerInputProps,
DatepickerInputProps, DayPrice, PriceFormat, WeekendDay, CalendarDay, CalendarMonth,
MonthChangePayload, DisabledDateConfig, DisabledDateInput, HolidayConfig, HolidayInput,
HolidayType, HolidayLegendCategory, HolidayLegendLabels.
Usage
Type-safe options
<script setup lang="ts">
import { ref } from 'vue'
import Datepicker, { type DatepickerProps } from '@archpublicwebsite/rangepicker'
import '@archpublicwebsite/rangepicker/style.css'
const dates = ref('')
const pickerOptions: Partial<DatepickerProps> = {
format: 'DD/MM/YYYY',
autoApply: true,
minDays: 2,
maxDays: 30,
weekendDays: ['friday', 'saturday'],
}
</script>
<template>
<Datepicker v-model="dates" v-bind="pickerOptions" primary-color="#3b82f6" />
</template>Color themes
<Datepicker v-model="dates1" primary-color="#3b82f6" secondary-color="#60a5fa" />
<Datepicker v-model="dates2" primary-color="#8b5cf6" secondary-color="#a78bfa" />Advanced composition
Use Rangepicker directly when you need your own trigger element and open-state control:
<script setup lang="ts">
import { ref } from 'vue'
import { Rangepicker, useRangepicker, type RangepickerProps } from '@archpublicwebsite/rangepicker'
import '@archpublicwebsite/rangepicker/style.css'
const triggerRef = ref<HTMLElement | null>(null)
const options: Partial<RangepickerProps> = { valueOfMonths: 2, minDays: 1, autoApply: true }
const { isOpen, dateRange, toggle } = useRangepicker(triggerRef, options)
</script>
<template>
<div>
<button ref="triggerRef" type="button" @click="toggle">
{{ dateRange.startDate && dateRange.endDate ? `${dateRange.startDate} - ${dateRange.endDate}` : 'Select dates' }}
</button>
<Rangepicker v-model="dateRange" v-model:is-open="isOpen" :trigger-element="triggerRef" />
</div>
</template>API reference
Datepicker props
The default export. Recommended entry point for both range and single-date selection.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| modelValue | string | - | Range: "DD MMM YYYY - DD MMM YYYY". Single: "DD MMM YYYY". |
| singleMode | boolean | false | Select one date instead of a range. |
| variant | 'desktop' \| 'mobile' \| 'full' | 'desktop' | Inline dropdown, mobile bottom sheet, or full-screen takeover. |
| primaryColor / secondaryColor | string | - | HEX colors, e.g. #3b82f6. |
| minDate / maxDate | string \| Date | - | Selectable date bounds. |
| minDays / maxDays | number | - | Nights bounds in range mode. maxDays: 0 = unlimited. |
| numberOfMonths | number | 2 | Month panels shown side by side (range mode). |
| disabledDates | (string \| Date \| { date, label? })[] | [] | Blocked dates, optionally with a sold-out label. |
| holidays | HolidayInput[] | [] | Bare dates or { date, name, type? } objects — see "Holidays & the legend" below. |
| holidayLegendLabels | { holiday?, promoday?, other? } | - | Fallback legend label per category, used only for entries with no name. |
| weekendDays | WeekendDay[] | ['saturday','sunday'] | Days styled as weekends. |
| prices | DayPrice[] | - | Per-date nightly prices rendered in each cell. |
| pricesLoading | boolean | false | Show a loading skeleton in price slots. |
| priceFormat | 'short' \| 'currency' \| 'raw' | 'short' | Price label format. |
| priceCurrency | string | '₱' | Currency symbol for priceFormat: 'currency'. |
| showPriceLegend | boolean | false | Show a legend for the lowest-price color. |
| priceCurrencyLabel | string | - | Currency label in the price legend, e.g. 'IDR'. |
| showPriceTooltip | boolean | true | Include price in the hover tooltip. |
| showOtherMonthDays | boolean | true | Render adjacent-month days; false blanks those cells. |
| format | string | 'DD MMM YYYY' | dayjs display/emit format. |
| placeholder | string | mode-dependent | 'Select dates' (range) or 'Select a date' (single). |
| showToday | boolean | true | Highlight today's date. |
| showTooltip | boolean | true | Night-count tooltip on hover (range mode). |
| showClearButton | boolean | true | Show the ✕ clear button. |
| autoApply | boolean | true | Apply on selection complete, no Apply/Cancel buttons. |
| close | boolean | - | Set true to close the picker programmatically. |
| keepPositionOnScroll | boolean | true | Recalculate popup position on page scroll (desktop). |
| mobileTopPosition | number \| string | undefined | Unset anchors below the trigger; 'center' centers; a number/px string pins a fixed offset. |
| borderRadius | string | '0.75rem' | Calendar container border radius. |
| dayBorderRadius | string | - | Day cell border radius (falls back to --rangepicker-day-radius). |
| colorScheme | 'light' \| 'dark' | 'light' | Color scheme. |
| fontFamily | string | - | Custom font family. |
| id / name | string | - | Native id/name on the input element. |
| readonly | boolean | true | Prevent manual text entry; select via calendar only. |
| class | string | - | CSS class on the inner input element. |
Rangepicker props
Most props match Datepicker above. Naming differs in a few places (this component predates
Datepicker): valueOfMonths/valueOfColumns instead of numberOfMonths, plus isOpen,
triggerElement, position, delimiter, and colorStyles for manual composition. See
types.ts for the full RangepickerProps interface.
Holidays & the legend
Each holidays entry is either a bare date (defaults to type: 'holiday', no name) or a
{ date, name, type? } object:
const holidays = [
{ date: '17-08', name: 'Indonesian Independence Day' }, // recurring — no year, shown every year
{ date: '27-05-2026', name: 'Eid al-Adha', type: 'holiday' }, // specific — 2026 only
{ date: '2026-09-18', name: 'Long-weekend promo rate', type: 'promoday' }, // blue
{ date: '2026-09-10', name: 'Loyalty member birthday', type: 'birthday' }, // gray ("Other")
]datewith no year ('DD-MM') recurs every year the calendar renders; a full date (ISO,Date, or'DD-MM-YYYY') shows only in that one year — use this for lunar-calendar holidays whose Gregorian date shifts (Eid, Nyepi, Waisak, Isra Mi'raj).typehas three visual buckets:'holiday'(default, red day number + red legend date),'promoday'(blue), or anything else —'birthday', a custom string, or omitted — which is gray and labeled "Other".- Names are not shown in a tooltip. Passing
holidaysis the only thing needed to also get a legend below the calendar — it appears automatically, as one wrapping line of text per month currently on screen (e.g."17 Indonesian Independence Day · 25 Prophet Muhammad's Birthday"), with each entry's leading date number colored by category instead of a dot. It re-filters live as the user navigates prev/next. - There's no date-range field — a multi-day event is entered as several same-named dated
entries, one per day. The legend automatically merges same-name entries within a month into a
compact range (e.g.
"20, 23–24 Cuti Bersama Idulfitri") instead of repeating the name. holidayLegendLabelsoverrides the fallback label used only for entries with noname; named entries always show their own name.
AI-agent implementation contract
Use this deterministic mapping when generating holiday data:
| Input style | Use when | Example | Visibility behavior |
| --- | --- | --- | --- |
| DD-MM | Recurring annual holiday | 17-08 | Appears every year (2026, 2027, ...). |
| DD-MM-YYYY | One-off/specific year holiday | 17-08-2026 | Appears only in that exact year. |
| YYYY-MM-DD (ISO) | One-off/specific year holiday | 2026-08-17 | Appears only in that exact year. |
Implementation rules for agents:
- Convert recurring holidays to
DD-MMto avoid duplicating entries each year. - Keep moving-date holidays (Eid/Nyepi/Waisak/Isra Mi'raj) as explicit year dates.
- Prefer stable short names so merged legend rows remain readable.
- Never generate both recurring and explicit duplicates for the same holiday/date unless an explicit yearly override is intentional.
Validation checklist:
- Navigate to next year and confirm
DD-MMentries still appear. - Confirm
DD-MM-YYYYand ISO entries disappear outside their year. - Verify legend updates as visible months change (
monthChangeevent).
Events
| Event | Payload | Description |
| --- | --- | --- |
| update:modelValue | { startDate, endDate } (Rangepicker) / string (Datepicker) | Date value changed. |
| update:isOpen | boolean | Picker opened/closed (Rangepicker only). |
| dateSelected | Dayjs | A date was clicked. |
| rangeSelected | { start: Dayjs, end: Dayjs } | A range was completed. |
| monthChange | MonthChangePayload[] | Prev/next navigation, with all visible months. |
Key types
interface DateRange {
startDate: string
endDate: string
}
interface RangepickerEmits {
'update:modelValue': [value: { startDate: string, endDate: string }]
'update:isOpen': [value: boolean]
'dateSelected': [date: Dayjs]
'rangeSelected': [start: Dayjs, end: Dayjs]
'monthChange': [months: MonthChangePayload[]]
}
type WeekendDay = 'sunday' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday'Styling
The component ships Tailwind utilities prefixed with arch-, so it's safe alongside Vuetify,
Bootstrap, or your app's own Tailwind config. Three ways to theme it, from easiest to most
custom:
<!-- 1. Color props -->
<Datepicker primary-color="#3b82f6" secondary-color="#60a5fa" />/* 2. CSS variables (global), RGB channel values */
:root {
--color-primary: 59 130 246;
--color-secondary: 148 163 184;
}/* 3. Override a specific class */
.rangepicker-day-selected {
background-color: purple !important;
}If styles don't apply, confirm you imported @archpublicwebsite/rangepicker/style.css.
Quick implementation checklist
- Import stylesheet once:
@archpublicwebsite/rangepicker/style.css. - Use
holidayswith the date-format contract above. - Set
weekendDaysexplicitly for local market calendars when needed. - Test both one-month and two-month modes (
numberOfMonths: 1 | 2). - Test desktop and mobile variants for footer/legend wrapping.
Browser support
Chrome/Edge, Firefox, and Safari (latest 2 versions), Mobile Safari (iOS 14+), and Chrome for Android.
Development
pnpm install
pnpm dev # demo app with live examples
pnpm build # build the package
pnpm type-checkContributing
See the root README for contribution guidelines.
License
MIT © Archipelago Hotels
