@xsolla/xui-calendar
v0.185.6
Published
A cross-platform React calendar component supporting single date selection, date range selection, preset date chips, and a dual (side-by-side) calendar layout. <!-- BEGIN:xui-mcp-instructions:calendar --> xui-mcp-instructions:calendarA panel component tha
Readme
Calendar
A cross-platform React calendar component supporting single date selection, date range selection, preset date chips, and a dual (side-by-side) calendar layout.
xui-mcp-instructions:calendarA panel component that displays an interactive date grid for selecting a single date or a date range. Calendar is always triggered by a DatePicker field — it does not appear on its own.
When to use
- As the date selection panel opened by a DatePicker input
- When a user needs to pick a date by navigating a calendar view
- When selecting a date range spanning days, weeks, or months
Content guidelines
- Chip labels: use standard presets — Today, Yesterday, Last 7 days, Last 30 days, This month, Last month.
- Enable Chips whenever the Calendar is used for filtering or reporting — they significantly speed up selection.
- Use Bottom content for an explicit Apply / Cancel pair when the context requires confirmation before the selection takes effect.
Behaviour guidelines
- Calendar opens anchored below the DatePicker field; if there is not enough space below, it opens upward.
- Single date (Range=False): clicking a day cell selects it and closes the panel immediately.
- Range (Range=True): first click sets the start date; hovering shows a preview highlight; second click sets the end date and closes the panel. If the second click lands before the first date, the range resets and the clicked date becomes the new start.
- Clicking a chip sets the predefined date or range and closes the panel. The chip enters Active state.
- Pressing Escape closes the panel without changing the selection; focus returns to the DatePicker field.
- The panel renders as a floating overlay — it does not shift page layout.
- Always set Months=Two and Range=True together when using range selection.
Accessibility
- When the panel opens, focus moves to the selected date cell, or to today's date if nothing is selected.
- When the panel closes, focus returns to the DatePicker field that triggered it.
- Focus is trapped inside the panel while it is open — Tab cycles within the panel only.
- Apply role="dialog" and aria-modal="true" to the Calendar panel container.
- Apply aria-label="Choose a date" (or "Choose a date range" when Range=True) to the dialog.
- Apply aria-selected="true" to selected day cells.
- Apply aria-disabled="true" to disabled day cells.
- Apply aria-current="date" to today's date cell.
- Use aria-live to announce month navigation changes to screen readers.
Installation
npm install @xsolla/xui-calendarDemo
Single Date Selection
import * as React from "react";
import { Calendar } from "@xsolla/xui-calendar";
export default function SingleDate() {
const [date, setDate] = React.useState<Date | null>(null);
return (
<Calendar
selectedDate={date}
onChange={(newDate) => setDate(newDate as Date)}
/>
);
}Date Range Selection
import * as React from "react";
import { Calendar } from "@xsolla/xui-calendar";
export default function DateRange() {
const [startDate, setStartDate] = React.useState<Date | null>(null);
const [endDate, setEndDate] = React.useState<Date | null>(null);
return (
<Calendar
selectsRange
startDate={startDate}
endDate={endDate}
onChange={(range) => {
const [start, end] = range as [Date | null, Date | null];
setStartDate(start);
setEndDate(end);
}}
/>
);
}With Preset Chips
import * as React from "react";
import { Calendar } from "@xsolla/xui-calendar";
const chips = [
{ label: "Today", value: "today" },
{ label: "Last 7 days", value: "last7" },
{ label: "Last 30 days", value: "last30" },
{ label: "Last 90 days", value: "last90" },
];
export default function WithChips() {
const [date, setDate] = React.useState<Date | null>(null);
const [activeChip, setActiveChip] = React.useState<string | null>("last30");
return (
<Calendar
selectedDate={date}
onChange={(newDate) => setDate(newDate as Date)}
chips={chips}
activeChip={activeChip}
onChipSelect={setActiveChip}
/>
);
}Dual Calendar (Side-by-Side)
import * as React from "react";
import { DualCalendar } from "@xsolla/xui-calendar";
export default function DualRange() {
const [startDate, setStartDate] = React.useState<Date | null>(null);
const [endDate, setEndDate] = React.useState<Date | null>(null);
return (
<DualCalendar
startDate={startDate}
endDate={endDate}
onChange={(dates) => {
const [start, end] = dates;
setStartDate(start);
setEndDate(end);
}}
/>
);
}Anatomy
+---------------------------------------+
| [Custom top-content] | <- topContent slot
| [Today] [Last 7d] [Last 30d] ... | <- CalendarChips
| <- [Month ▾] [Year ▾] -> | <- CalendarHeader
| SU MO TU WE TH FR SA | <- CalendarGrid weekday row
| 29 30 31 1 2 3 4 |
| 5 6 7 8 9 10 11 |
| 12 13 14 [15] 16 17 18 | <- [15] = today/selected
| 23 20 21 22 23 24 25 |
| 26 27 28 29 30 1 2 |
| [Custom bottom-content] | <- bottomContent slot
+---------------------------------------+API Reference
Calendar
| Prop | Type | Default | Description |
| :------------------- | :------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------ |
| testID | string | — | Test ID for testing frameworks. On web this renders as data-testid; on React Native it renders as testID. |
| selectedDate | Date \| null | - | Selected date for single mode. |
| startDate | Date \| null | - | Start date for range mode. |
| endDate | Date \| null | - | End date for range mode. |
| selectsRange | boolean | false | Enable date range selection. |
| onChange | (date: Date \| [Date, Date]) => void | - | Date change callback. |
| locale | string | "enUS" | Date-fns locale identifier. |
| firstDayOfWeek | number | 0 | First day of week (0=Sun, 6=Sat). |
| initialMonth | Date | - | Initial month to display. |
| month | Date | - | Controlled month to display. |
| minDate | Date \| null | - | Minimum selectable date. |
| maxDate | Date \| null | - | Maximum selectable date. |
| chips | CalendarChipOption[] | - | Preset date range chips. |
| activeChip | string \| null | - | Currently active chip value. |
| onChipSelect | (value: string) => void | - | Chip selection callback. |
| topContent | ({ close }) => ReactNode | - | Custom content above chips. |
| bottomContent | ({ close }) => ReactNode | - | Custom content below grid. |
| contextMenuMaxHeight | number | - | Max height for month/year dropdowns. |
| testID | string | - | Test identifier. |
DualCalendar
| Prop | Type | Default | Description |
| :------------------- | :------------------------------ | :------- | :----------------------------------- |
| startDate | Date \| null | - | Start date of selected range. |
| endDate | Date \| null | - | End date of selected range. |
| onChange | (dates: [Date, Date]) => void | - | Range change callback. |
| locale | string | "enUS" | Date-fns locale identifier. |
| firstDayOfWeek | number | 0 | First day of week (0=Sun, 6=Sat). |
| initialMonth | Date | - | Initial month for left calendar. |
| month | Date | - | Controlled month for left calendar. |
| minDate | Date \| null | - | Minimum selectable date. |
| maxDate | Date \| null | - | Maximum selectable date. |
| chips | CalendarChipOption[] | - | Shared preset date range chips. |
| activeChip | string \| null | - | Currently active chip value. |
| onChipSelect | (value: string) => void | - | Chip selection callback. |
| topContent | ({ close }) => ReactNode | - | Custom content above chips. |
| bottomContent | ({ close }) => ReactNode | - | Custom content below grids. |
| contextMenuMaxHeight | number | - | Max height for month/year dropdowns. |
| testID | string | - | Test identifier. |
CalendarChips
| Prop | Type | Default | Description |
| :----------- | :------------------------ | :------- | :--------------------------- |
| chips | CalendarChipOption[] | required | Array of chip options. |
| activeChip | string \| null | - | Currently active chip value. |
| onChipSelect | (value: string) => void | - | Chip selection callback. |
| testID | string | - | Test identifier. |
CalendarChipOption
interface CalendarChipOption {
label: string;
value: string;
}CalendarGrid
| Prop | Type | Default | Description |
| :------------- | :--------------------- | :------- | :---------------------------- |
| currentMonth | Date | required | The month to render. |
| locale | string | "enUS" | Date-fns locale identifier. |
| firstDayOfWeek | number | 0 | First day of week. |
| selectsRange | boolean | false | Enable range mode. |
| minDate | Date \| null | - | Minimum selectable date. |
| maxDate | Date \| null | - | Maximum selectable date. |
| startDate | Date \| null | - | Range start date. |
| endDate | Date \| null | - | Range end date. |
| selectedDate | Date \| null | - | Selected date (single mode). |
| selectingRange | Date \| null | - | Intermediate range selection. |
| onDayPress | (date: Date) => void | - | Day press callback. |
| testID | string | - | Test identifier. |
Platform Support
This package works on both web and React Native. All components use cross-platform primitives (Box, Text) and avoid DOM-specific APIs.
Accessibility
- Navigation buttons have aria-labels ("Previous month", "Next month")
- Month and year selectors are accessible via Select component
- Day cells support keyboard navigation
- Chip selection follows radio-group pattern (single active item)
