@mngh/jalali-datepicker 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +665 -8
- package/dist/a11y/aria-helpers.d.ts +7 -0
- package/dist/a11y/aria-helpers.d.ts.map +1 -0
- package/dist/a11y/keyboard-navigation.d.ts +3 -0
- package/dist/a11y/keyboard-navigation.d.ts.map +1 -0
- package/dist/a11y/types.d.ts +20 -0
- package/dist/a11y/types.d.ts.map +1 -0
- package/dist/a11y/useCalendarKeyboard.d.ts +9 -0
- package/dist/a11y/useCalendarKeyboard.d.ts.map +1 -0
- package/dist/components/DayCell.d.ts +32 -0
- package/dist/components/DayCell.d.ts.map +1 -0
- package/dist/components/Header.d.ts +11 -0
- package/dist/components/Header.d.ts.map +1 -0
- package/dist/components/JalaliDatePicker.d.ts +52 -0
- package/dist/components/JalaliDatePicker.d.ts.map +1 -0
- package/dist/components/MonthYearPicker.d.ts +9 -0
- package/dist/components/MonthYearPicker.d.ts.map +1 -0
- package/dist/components/Weekdays.d.ts +10 -0
- package/dist/components/Weekdays.d.ts.map +1 -0
- package/dist/components/dual-calendar/DualMonthCalendar.d.ts +4 -0
- package/dist/components/dual-calendar/DualMonthCalendar.d.ts.map +1 -0
- package/dist/components/dual-calendar/index.d.ts +3 -0
- package/dist/components/dual-calendar/index.d.ts.map +1 -0
- package/dist/components/dual-calendar/types.d.ts +29 -0
- package/dist/components/dual-calendar/types.d.ts.map +1 -0
- package/dist/components/footer/CalendarFooter.d.ts +16 -0
- package/dist/components/footer/CalendarFooter.d.ts.map +1 -0
- package/dist/components/footer/types.d.ts +28 -0
- package/dist/components/footer/types.d.ts.map +1 -0
- package/dist/components/masked-input/MaskedDateInput.d.ts +4 -0
- package/dist/components/masked-input/MaskedDateInput.d.ts.map +1 -0
- package/dist/components/masked-input/index.d.ts +4 -0
- package/dist/components/masked-input/index.d.ts.map +1 -0
- package/dist/components/masked-input/mask-utils.d.ts +11 -0
- package/dist/components/masked-input/mask-utils.d.ts.map +1 -0
- package/dist/components/masked-input/types.d.ts +37 -0
- package/dist/components/masked-input/types.d.ts.map +1 -0
- package/dist/core/calendar-grid.d.ts +10 -0
- package/dist/core/calendar-grid.d.ts.map +1 -0
- package/dist/core/constants.d.ts +14 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/jalali-helpers.d.ts +35 -0
- package/dist/core/jalali-helpers.d.ts.map +1 -0
- package/dist/core/jalali-math.d.ts +27 -0
- package/dist/core/jalali-math.d.ts.map +1 -0
- package/dist/core/types.d.ts +62 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/events/event-utils.d.ts +7 -0
- package/dist/events/event-utils.d.ts.map +1 -0
- package/dist/events/index.d.ts +3 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/types.d.ts +17 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/formatters/jalali-formatter.d.ts +18 -0
- package/dist/formatters/jalali-formatter.d.ts.map +1 -0
- package/dist/formatters/persian-digits.d.ts +9 -0
- package/dist/formatters/persian-digits.d.ts.map +1 -0
- package/dist/holidays/index.d.ts +3 -0
- package/dist/holidays/index.d.ts.map +1 -0
- package/dist/holidays/iran-holidays.d.ts +11 -0
- package/dist/holidays/iran-holidays.d.ts.map +1 -0
- package/dist/holidays/types.d.ts +21 -0
- package/dist/holidays/types.d.ts.map +1 -0
- package/dist/hooks/types.d.ts +53 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/useJalaliDatePicker.d.ts +17 -0
- package/dist/hooks/useJalaliDatePicker.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +922 -1156
- package/dist/index.mjs.map +1 -1
- package/dist/plugins/time-picker/TimePicker.d.ts +21 -0
- package/dist/plugins/time-picker/TimePicker.d.ts.map +1 -0
- package/dist/plugins/time-picker/index.d.ts +4 -0
- package/dist/plugins/time-picker/index.d.ts.map +1 -0
- package/dist/plugins/time-picker/time-utils.d.ts +18 -0
- package/dist/plugins/time-picker/time-utils.d.ts.map +1 -0
- package/dist/plugins/time-picker/types.d.ts +36 -0
- package/dist/plugins/time-picker/types.d.ts.map +1 -0
- package/dist/theme/ThemeProvider.d.ts +15 -0
- package/dist/theme/ThemeProvider.d.ts.map +1 -0
- package/dist/theme/style-slots.d.ts +46 -0
- package/dist/theme/style-slots.d.ts.map +1 -0
- package/dist/theme/tokens.d.ts +8 -0
- package/dist/theme/tokens.d.ts.map +1 -0
- package/dist/theme/types.d.ts +62 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/package.json +39 -6
- package/dist/favicon.svg +0 -1
- package/dist/icons.svg +0 -24
package/README.md
CHANGED
|
@@ -1,13 +1,670 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mngh/jalali-datepicker
|
|
2
2
|
|
|
3
|
-
A modern,
|
|
3
|
+
A modern, headless-friendly Jalali (Persian/Shamsi) Date & Time Picker for React — zero date-library runtime dependencies, fully typed with native `Date` objects, WAI-ARIA accessible, and themeable with Tailwind CSS or CSS variables.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@mngh/jalali-datepicker)
|
|
6
|
+
[](https://bundlephobia.com/package/@mngh/jalali-datepicker)
|
|
7
|
+
[](https://github.com/mngh/jalali-datepicker/blob/main/LICENSE)
|
|
8
|
+
[](https://github.com/mngh/jalali-datepicker)
|
|
9
|
+
|
|
10
|
+
> **Repository:** [github.com/mngh/jalali-datepicker](https://github.com/mngh/jalali-datepicker)
|
|
11
|
+
> **Bundle size:** ~38 kB (CJS, unminified) / ~51 kB (ESM, unminified), highly tree-shakeable
|
|
12
|
+
> **Peer dependencies:** React 18 or 19
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Table of Contents
|
|
17
|
+
|
|
18
|
+
1. [Introduction & Quick Start](#1-introduction--quick-start)
|
|
19
|
+
2. [Core Concepts & Data Flow](#2-core-concepts--data-flow)
|
|
20
|
+
3. [Display Variants & Selection Modes](#3-display-variants--selection-modes)
|
|
21
|
+
4. [Built-in Plugins & Advanced Features](#4-built-in-plugins--advanced-features)
|
|
22
|
+
5. [Headless Hook Architecture](#5-headless-hook-architecture-usejalalidatepicker)
|
|
23
|
+
6. [Complete API Reference](#6-complete-api-reference)
|
|
24
|
+
7. [Theming & Styling](#7-theming--styling-integration)
|
|
25
|
+
8. [Accessibility & Keyboard Shortcuts](#8-accessibility--keyboard-shortcuts)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 1. Introduction & Quick Start
|
|
30
|
+
|
|
31
|
+
### Why `@mngh/jalali-datepicker`?
|
|
32
|
+
|
|
33
|
+
- **Zero date-library overhead** — no Moment.js, no date-fns, no dayjs. The Jalali/Gregorian conversion math is implemented internally with plain arithmetic.
|
|
34
|
+
- **Native `Date` in, native `Date` out** — every prop and callback speaks standard JavaScript `Date` objects. You never touch a custom calendar object.
|
|
35
|
+
- **Three display variants** — inline, popover, and modal — and three selection modes — single, range, and multiple.
|
|
36
|
+
- **Headless-first** — the entire UI is built on top of a public hook (`useJalaliDatePicker`) that you can use to build your own component from scratch.
|
|
37
|
+
- **Accessible by default** — full keyboard navigation, roving tabindex, and ARIA roles (`grid`, `gridcell`, `dialog`) out of the box.
|
|
38
|
+
- **Themeable** — Tailwind CSS class slots and CSS custom properties, with a built-in `DatePickerThemeProvider` for light/dark mode.
|
|
39
|
+
|
|
40
|
+
### Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install @mngh/jalali-datepicker
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pnpm add @mngh/jalali-datepicker
|
|
48
|
+
```
|
|
6
49
|
|
|
7
50
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
51
|
+
yarn add @mngh/jalali-datepicker
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bun add @mngh/jalali-datepicker
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Quick Start
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { useState } from "react";
|
|
62
|
+
import {
|
|
63
|
+
JalaliDatePicker,
|
|
64
|
+
DatePickerThemeProvider,
|
|
65
|
+
} from "@mngh/jalali-datepicker";
|
|
66
|
+
import "@mngh/jalali-datepicker/styles.css";
|
|
67
|
+
|
|
68
|
+
export default function App() {
|
|
69
|
+
const [date, setDate] = useState<Date | null>(null);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<DatePickerThemeProvider mode="light">
|
|
73
|
+
<JalaliDatePicker
|
|
74
|
+
variant="popover"
|
|
75
|
+
mode="single"
|
|
76
|
+
value={date}
|
|
77
|
+
onChange={setDate}
|
|
78
|
+
placeholder="Select a date"
|
|
79
|
+
/>
|
|
80
|
+
</DatePickerThemeProvider>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
That's it — `date` is always a plain JavaScript `Date` object (or `null`). No conversion helpers, no adapters.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 2. Core Concepts & Data Flow
|
|
90
|
+
|
|
91
|
+
### Standard JavaScript `Date` only
|
|
92
|
+
|
|
93
|
+
`@mngh/jalali-datepicker` never asks the consumer to construct or parse a custom Jalali object. Every value that crosses the public API boundary — `value`, `defaultValue`, `minDate`, `maxDate`, the `onChange` payload — is a native `Date`, or one of the following shapes depending on `mode`:
|
|
94
|
+
|
|
95
|
+
| `mode` | Value shape | Example |
|
|
96
|
+
| ------------ | ------------------------------ | ----------------------------------------------- |
|
|
97
|
+
| `'single'` | `Date \| null` | `new Date(2026, 2, 21)` |
|
|
98
|
+
| `'range'` | `[Date \| null, Date \| null]` | `[new Date(2026, 2, 21), new Date(2026, 3, 1)]` |
|
|
99
|
+
| `'multiple'` | `Date[]` | `[new Date(2026, 2, 21), new Date(2026, 5, 1)]` |
|
|
100
|
+
|
|
101
|
+
Internally, the picker converts a `Date` to a Jalali year/month/day triple purely for rendering the grid, and converts back to `Date` the instant a value leaves the component. The consumer's state never has to know Jalali math exists.
|
|
102
|
+
|
|
103
|
+
### Under-the-hood math
|
|
104
|
+
|
|
105
|
+
The Jalali↔Gregorian conversion is implemented with a self-contained arithmetic algorithm (based on the 33-year leap-year cycle of the Jalali calendar), so there's no dependency on `Intl`, ICU data, or a third-party calendar library. This keeps the bundle small and behavior consistent across browsers and server-side rendering environments.
|
|
106
|
+
|
|
107
|
+
### Digit presentation
|
|
108
|
+
|
|
109
|
+
Calendar cells, headers, and the masked text input can render either Persian (`۰-۹`) or Latin (`0-9`) digits:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
<JalaliDatePicker digitType="persian" /> // ۱۴۰۵/۰۱/۰۱
|
|
113
|
+
<JalaliDatePicker digitType="latin" /> // 1405/01/01
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`digitType` defaults to `'persian'`.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 3. Display Variants & Selection Modes
|
|
121
|
+
|
|
122
|
+
### Display variants (`variant`)
|
|
123
|
+
|
|
124
|
+
#### `'inline'`
|
|
125
|
+
|
|
126
|
+
Renders the calendar permanently in the page flow — ideal for embedding inside a form or sidebar without a trigger input.
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
<JalaliDatePicker
|
|
130
|
+
variant="inline"
|
|
131
|
+
mode="single"
|
|
132
|
+
value={date}
|
|
133
|
+
onChange={setDate}
|
|
134
|
+
/>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### `'popover'`
|
|
138
|
+
|
|
139
|
+
The default — a text input that opens a floating calendar panel on focus/click and closes on outside click or `Escape`.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
<JalaliDatePicker
|
|
143
|
+
variant="popover"
|
|
144
|
+
mode="single"
|
|
145
|
+
value={date}
|
|
146
|
+
onChange={setDate}
|
|
147
|
+
placeholder="YYYY/MM/DD"
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### `'modal'`
|
|
152
|
+
|
|
153
|
+
Opens the calendar in a full-screen dialog with a backdrop blur, scroll locking on `<body>`, and an `Escape` key listener that closes the dialog and returns focus to the trigger.
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<JalaliDatePicker
|
|
157
|
+
variant="modal"
|
|
158
|
+
mode="single"
|
|
159
|
+
value={date}
|
|
160
|
+
onChange={setDate}
|
|
161
|
+
modalTitle="Choose a date"
|
|
162
|
+
/>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Selection modes (`mode`)
|
|
166
|
+
|
|
167
|
+
#### `'single'`
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
const [date, setDate] = useState<Date | null>(null);
|
|
171
|
+
|
|
172
|
+
<JalaliDatePicker mode="single" value={date} onChange={setDate} />;
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### `'range'`
|
|
176
|
+
|
|
177
|
+
Start/end selection with a live hover preview that highlights the would-be range as the pointer moves before the end date is confirmed.
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
const [range, setRange] = useState<[Date | null, Date | null]>([null, null]);
|
|
181
|
+
|
|
182
|
+
<JalaliDatePicker
|
|
183
|
+
mode="range"
|
|
184
|
+
value={range}
|
|
185
|
+
onChange={setRange}
|
|
186
|
+
numberOfMonths={2}
|
|
187
|
+
/>;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### `'multiple'`
|
|
191
|
+
|
|
192
|
+
Select any number of non-contiguous dates; clicking a selected date removes it.
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
const [dates, setDates] = useState<Date[]>([]);
|
|
196
|
+
|
|
197
|
+
<JalaliDatePicker mode="multiple" value={dates} onChange={setDates} />;
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 4. Built-in Plugins & Advanced Features
|
|
203
|
+
|
|
204
|
+
### Dual month view
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
<JalaliDatePicker
|
|
208
|
+
mode="range"
|
|
209
|
+
numberOfMonths={2}
|
|
210
|
+
value={range}
|
|
211
|
+
onChange={setRange}
|
|
212
|
+
/>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Renders two synchronized month grids side by side (or stacked on narrow viewports), sharing a single hover-preview state — the standard pattern for range pickers.
|
|
216
|
+
|
|
217
|
+
### Time picker integration
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
const [date, setDate] = useState<Date | null>(null);
|
|
221
|
+
const [time, setTime] = useState<{
|
|
222
|
+
hour: number;
|
|
223
|
+
minute: number;
|
|
224
|
+
second?: number;
|
|
225
|
+
}>({
|
|
226
|
+
hour: 12,
|
|
227
|
+
minute: 0,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
<JalaliDatePicker
|
|
231
|
+
mode="single"
|
|
232
|
+
value={date}
|
|
233
|
+
onChange={setDate}
|
|
234
|
+
enableTime
|
|
235
|
+
timeValue={time}
|
|
236
|
+
onTimeChange={setTime}
|
|
237
|
+
hourStep={1}
|
|
238
|
+
minuteStep={5}
|
|
239
|
+
showSeconds={false}
|
|
240
|
+
/>;
|
|
13
241
|
```
|
|
242
|
+
|
|
243
|
+
When `enableTime` is set, the resolved `Date` passed to `onChange` already has the selected hour/minute/second merged in — you don't need to combine `date` and `time` yourself.
|
|
244
|
+
|
|
245
|
+
| Prop | Type | Default | Description |
|
|
246
|
+
| -------------- | --------------------------------------------------- | ----------- | ----------------------------------------------- |
|
|
247
|
+
| `enableTime` | `boolean` | `false` | Shows the time picker panel below the calendar. |
|
|
248
|
+
| `timeValue` | `{ hour: number; minute: number; second?: number }` | `undefined` | Controlled time value. |
|
|
249
|
+
| `onTimeChange` | `(time) => void` | `undefined` | Fires when the time inputs change. |
|
|
250
|
+
| `hourStep` | `number` | `1` | Increment for the hour control. |
|
|
251
|
+
| `minuteStep` | `number` | `1` | Increment for the minute control. |
|
|
252
|
+
| `showSeconds` | `boolean` | `false` | Shows a seconds column. |
|
|
253
|
+
|
|
254
|
+
### Live masked input
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
<JalaliDatePicker
|
|
258
|
+
variant="popover"
|
|
259
|
+
mode="single"
|
|
260
|
+
value={date}
|
|
261
|
+
onChange={setDate}
|
|
262
|
+
useMaskedInput
|
|
263
|
+
maskFormat="YYYY/MM/DD"
|
|
264
|
+
/>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`useMaskedInput` turns the trigger `<input>` into a real-time Persian date mask: digits are inserted into the correct segment as the user types, slashes are auto-inserted, and an invalid segment (e.g. month `13`) is rejected without corrupting the rest of the string.
|
|
268
|
+
|
|
269
|
+
### Shortcuts & presets bar
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
<JalaliDatePicker
|
|
273
|
+
mode="range"
|
|
274
|
+
value={range}
|
|
275
|
+
onChange={setRange}
|
|
276
|
+
enablePresets
|
|
277
|
+
presetsOrientation="horizontal"
|
|
278
|
+
presets={[
|
|
279
|
+
{ label: "Today", getValue: () => [new Date(), new Date()] },
|
|
280
|
+
{ label: "This week", getValue: () => getThisWeekRange() },
|
|
281
|
+
{ label: "This month", getValue: () => getThisMonthRange() },
|
|
282
|
+
{ label: "Last 30 days", getValue: () => getLastNDaysRange(30) },
|
|
283
|
+
]}
|
|
284
|
+
/>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
| Prop | Type | Default | Description |
|
|
288
|
+
| -------------------- | ---------------------------- | ------------ | ------------------------------------------------------------ |
|
|
289
|
+
| `enablePresets` | `boolean` | `false` | Shows the presets sidebar/bar. |
|
|
290
|
+
| `presetsOrientation` | `'horizontal' \| 'vertical'` | `'vertical'` | Layout of the presets list relative to the calendar. |
|
|
291
|
+
| `presets` | `DatePickerPreset[]` | `[]` | Custom preset buttons; each computes its own value on click. |
|
|
292
|
+
|
|
293
|
+
### Iranian solar holidays & Fridays
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
<JalaliDatePicker
|
|
297
|
+
mode="single"
|
|
298
|
+
value={date}
|
|
299
|
+
onChange={setDate}
|
|
300
|
+
showHolidays
|
|
301
|
+
customHolidays={[{ month: 1, day: 1, label: "شرکت تعطیل است" }]}
|
|
302
|
+
/>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
When `showHolidays` is enabled, official Iranian solar-calendar holidays and every Friday are rendered in red with a hover/focus tooltip describing the occasion. `customHolidays` merges additional organization-specific dates into the same highlighting and tooltip system.
|
|
306
|
+
|
|
307
|
+
### Calendar events & badges
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
<JalaliDatePicker
|
|
311
|
+
mode="single"
|
|
312
|
+
value={date}
|
|
313
|
+
onChange={setDate}
|
|
314
|
+
events={[
|
|
315
|
+
{ date: new Date(2026, 2, 21), color: "blue", label: "Team standup" },
|
|
316
|
+
{ date: new Date(2026, 2, 25), color: "red", label: "Deadline" },
|
|
317
|
+
]}
|
|
318
|
+
/>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Each entry in `events` renders a small colored dot/badge under the corresponding day cell; hovering or focusing the cell shows the event `label` in a tooltip. Multiple events on the same day stack as multiple dots.
|
|
322
|
+
|
|
323
|
+
### Footer status & action buttons
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
<JalaliDatePicker
|
|
327
|
+
mode="single"
|
|
328
|
+
value={date}
|
|
329
|
+
onChange={setDate}
|
|
330
|
+
showFooter
|
|
331
|
+
showStatusText
|
|
332
|
+
showActions
|
|
333
|
+
onConfirm={(value) => console.log("confirmed:", value)}
|
|
334
|
+
/>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
| Prop | Type | Default | Description |
|
|
338
|
+
| ---------------- | ----------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
339
|
+
| `showFooter` | `boolean` | `false` | Master switch for the footer row. |
|
|
340
|
+
| `showStatusText` | `boolean` | `true` (when footer shown) | Shows a human-readable summary of the current selection, e.g. "5 days selected". |
|
|
341
|
+
| `showActions` | `boolean` | `true` (when footer shown) | Shows the **Today**, **Clear**, and **Confirm** buttons. |
|
|
342
|
+
| `onConfirm` | `(value) => void` | `undefined` | Fires when **Confirm** is clicked, with the currently pending selection. Useful when you want selection to be provisional until confirmed, especially in `modal` variant. |
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## 5. Headless Hook Architecture (`useJalaliDatePicker`)
|
|
347
|
+
|
|
348
|
+
For teams that need a completely custom UI — a bespoke calendar layout, a non-standard interaction pattern, or integration into an existing design system — `@mngh/jalali-datepicker` exposes the same state machine that powers its default components as a standalone hook.
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
import { useJalaliDatePicker } from "@mngh/jalali-datepicker";
|
|
352
|
+
|
|
353
|
+
function CustomCalendar() {
|
|
354
|
+
const {
|
|
355
|
+
viewYear,
|
|
356
|
+
viewMonth,
|
|
357
|
+
grid,
|
|
358
|
+
selected,
|
|
359
|
+
hoverDate,
|
|
360
|
+
goToPrevMonth,
|
|
361
|
+
goToNextMonth,
|
|
362
|
+
goToPrevYear,
|
|
363
|
+
goToNextYear,
|
|
364
|
+
goToToday,
|
|
365
|
+
selectDate,
|
|
366
|
+
setHoverDate,
|
|
367
|
+
clear,
|
|
368
|
+
isSelected,
|
|
369
|
+
isToday,
|
|
370
|
+
isDisabled,
|
|
371
|
+
} = useJalaliDatePicker({
|
|
372
|
+
mode: "single",
|
|
373
|
+
value: null,
|
|
374
|
+
onChange: (date) => console.log(date),
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<div role="grid" aria-label={`${viewYear}/${viewMonth + 1}`}>
|
|
379
|
+
<header>
|
|
380
|
+
<button onClick={goToPrevMonth} aria-label="Previous month">
|
|
381
|
+
‹
|
|
382
|
+
</button>
|
|
383
|
+
<span>
|
|
384
|
+
{viewYear}/{viewMonth + 1}
|
|
385
|
+
</span>
|
|
386
|
+
<button onClick={goToNextMonth} aria-label="Next month">
|
|
387
|
+
›
|
|
388
|
+
</button>
|
|
389
|
+
</header>
|
|
390
|
+
|
|
391
|
+
<div className="grid grid-cols-7">
|
|
392
|
+
{grid.map((cell) => (
|
|
393
|
+
<button
|
|
394
|
+
key={cell.key}
|
|
395
|
+
role="gridcell"
|
|
396
|
+
disabled={isDisabled(cell.date)}
|
|
397
|
+
aria-selected={isSelected(cell.date)}
|
|
398
|
+
data-today={isToday(cell.date)}
|
|
399
|
+
onMouseEnter={() => setHoverDate(cell.date)}
|
|
400
|
+
onClick={() => selectDate(cell.date)}
|
|
401
|
+
>
|
|
402
|
+
{cell.dayOfMonth}
|
|
403
|
+
</button>
|
|
404
|
+
))}
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<footer>
|
|
408
|
+
<button onClick={goToToday}>Today</button>
|
|
409
|
+
<button onClick={clear}>Clear</button>
|
|
410
|
+
</footer>
|
|
411
|
+
</div>
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Hook return values
|
|
417
|
+
|
|
418
|
+
| Value | Type | Description |
|
|
419
|
+
| --------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
420
|
+
| `viewYear` | `number` | The Jalali year currently displayed. |
|
|
421
|
+
| `viewMonth` | `number` | The Jalali month (0-indexed) currently displayed. |
|
|
422
|
+
| `grid` | `CalendarCell[]` | Flattened array of cell objects (including leading/trailing days from adjacent months) for the current view. |
|
|
423
|
+
| `selected` | `SelectedDateValue` | The current selection, shaped according to `mode`. |
|
|
424
|
+
| `hoverDate` | `Date \| null` | The date currently under pointer/keyboard focus, used for range-preview rendering. |
|
|
425
|
+
| `goToPrevMonth` / `goToNextMonth` | `() => void` | Step the view by one month. |
|
|
426
|
+
| `goToPrevYear` / `goToNextYear` | `() => void` | Step the view by one year. |
|
|
427
|
+
| `goToToday` | `() => void` | Reset the view to the month containing today. |
|
|
428
|
+
| `selectDate` | `(date: Date) => void` | Commit a date into the current selection according to `mode`. |
|
|
429
|
+
| `setHoverDate` | `(date: Date \| null) => void` | Update the hover-preview date (used for range mode). |
|
|
430
|
+
| `clear` | `() => void` | Reset the selection to its empty state (`null`, `[null, null]`, or `[]`). |
|
|
431
|
+
| `isSelected` | `(date: Date) => boolean` | Whether a given date is part of the current selection. |
|
|
432
|
+
| `isToday` | `(date: Date) => boolean` | Whether a given date is today. |
|
|
433
|
+
| `isDisabled` | `(date: Date) => boolean` | Whether a given date falls outside `minDate`/`maxDate` or matches a custom `disabledDates` rule. |
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## 6. Complete API Reference
|
|
438
|
+
|
|
439
|
+
### `JalaliDatePickerProps`
|
|
440
|
+
|
|
441
|
+
| Prop | Type | Default | Description |
|
|
442
|
+
| -------------------- | -------------------------------------------------------- | ------------------------------ | ----------------------------------------------------------- |
|
|
443
|
+
| `variant` | `'inline' \| 'popover' \| 'modal'` | `'popover'` | How the calendar is presented. |
|
|
444
|
+
| `mode` | `'single' \| 'range' \| 'multiple'` | `'single'` | Selection strategy. |
|
|
445
|
+
| `value` | `Date \| null \| [Date \| null, Date \| null] \| Date[]` | — | Controlled value, shaped by `mode`. |
|
|
446
|
+
| `defaultValue` | same as `value` | `null` / `[null, null]` / `[]` | Uncontrolled initial value. |
|
|
447
|
+
| `onChange` | `(value) => void` | — | Fires whenever the selection changes. |
|
|
448
|
+
| `minDate` | `Date` | `undefined` | Earliest selectable date. |
|
|
449
|
+
| `maxDate` | `Date` | `undefined` | Latest selectable date. |
|
|
450
|
+
| `disabledDates` | `(date: Date) => boolean` | `undefined` | Custom predicate to disable arbitrary dates. |
|
|
451
|
+
| `digitType` | `'persian' \| 'latin'` | `'persian'` | Digit glyphs used throughout the UI. |
|
|
452
|
+
| `numberOfMonths` | `1 \| 2` | `1` | Number of side-by-side month grids. |
|
|
453
|
+
| `enableTime` | `boolean` | `false` | Enables the time picker panel. |
|
|
454
|
+
| `timeValue` | `JalaliTime` | `undefined` | Controlled time-of-day value. |
|
|
455
|
+
| `onTimeChange` | `(time: JalaliTime) => void` | `undefined` | Fires when the time changes. |
|
|
456
|
+
| `hourStep` | `number` | `1` | Hour increment step. |
|
|
457
|
+
| `minuteStep` | `number` | `1` | Minute increment step. |
|
|
458
|
+
| `showSeconds` | `boolean` | `false` | Show a seconds column in the time picker. |
|
|
459
|
+
| `useMaskedInput` | `boolean` | `false` | Enables the live typing mask on the trigger input. |
|
|
460
|
+
| `maskFormat` | `string` | `'YYYY/MM/DD'` | Mask pattern for `useMaskedInput`. |
|
|
461
|
+
| `enablePresets` | `boolean` | `false` | Shows the presets bar. |
|
|
462
|
+
| `presetsOrientation` | `'horizontal' \| 'vertical'` | `'vertical'` | Presets bar layout. |
|
|
463
|
+
| `presets` | `DatePickerPreset[]` | `[]` | Custom preset definitions. |
|
|
464
|
+
| `showHolidays` | `boolean` | `false` | Highlights official holidays and Fridays. |
|
|
465
|
+
| `customHolidays` | `CustomHolidayRule[]` | `[]` | Additional holiday rules to highlight. |
|
|
466
|
+
| `events` | `CalendarEvent[]` | `[]` | Event dots/badges rendered on matching day cells. |
|
|
467
|
+
| `showFooter` | `boolean` | `false` | Shows the footer row. |
|
|
468
|
+
| `showStatusText` | `boolean` | `true` | Shows the selection-summary text in the footer. |
|
|
469
|
+
| `showActions` | `boolean` | `true` | Shows Today/Clear/Confirm buttons in the footer. |
|
|
470
|
+
| `onConfirm` | `(value) => void` | `undefined` | Fires when Confirm is pressed. |
|
|
471
|
+
| `placeholder` | `string` | `''` | Placeholder text for the trigger input (`popover`/`modal`). |
|
|
472
|
+
| `modalTitle` | `string` | `undefined` | Title rendered in the `modal` variant's dialog header. |
|
|
473
|
+
| `disabled` | `boolean` | `false` | Disables the entire component. |
|
|
474
|
+
| `readOnly` | `boolean` | `false` | Prevents changes while still allowing focus/scroll. |
|
|
475
|
+
| `classNames` | `JalaliDatePickerClassNames` | `{}` | Per-slot Tailwind/CSS class overrides. |
|
|
476
|
+
| `styles` | `JalaliDatePickerStyles` | `{}` | Per-slot inline style overrides. |
|
|
477
|
+
| `locale` | `'fa' \| 'en'` | `'fa'` | Language for weekday/month labels and ARIA strings. |
|
|
478
|
+
| `weekStartsOn` | `0 \| 1 \| ... \| 6` | `6` (Saturday) | First day of the week in the grid. |
|
|
479
|
+
|
|
480
|
+
### Style slots (`classNames` & `styles`)
|
|
481
|
+
|
|
482
|
+
Both `classNames` and `styles` accept the same set of slot keys, letting you target any part of the picker with Tailwind classes or inline styles respectively.
|
|
483
|
+
|
|
484
|
+
| Slot key | Targets |
|
|
485
|
+
| ---------------- | ----------------------------------------------------------------- |
|
|
486
|
+
| `root` | Outermost wrapper element. |
|
|
487
|
+
| `input` | The trigger text input (`popover`/`modal`). |
|
|
488
|
+
| `calendar` | The calendar panel container. |
|
|
489
|
+
| `header` | Month/year navigation header. |
|
|
490
|
+
| `grid` | The day-cell grid container. |
|
|
491
|
+
| `weekdays` | The weekday label row. |
|
|
492
|
+
| `dayCell` | An individual day cell (base state). |
|
|
493
|
+
| `selectedCell` | A cell that is part of the current selection. |
|
|
494
|
+
| `todayCell` | The cell representing today. |
|
|
495
|
+
| `holidayCell` | A cell flagged as a holiday or Friday. |
|
|
496
|
+
| `inRangeCell` | A cell between the range start and end (inclusive-between). |
|
|
497
|
+
| `rangeStartCell` | The first cell of a range selection. |
|
|
498
|
+
| `rangeEndCell` | The last cell of a range selection. |
|
|
499
|
+
| `disabledCell` | A cell outside `minDate`/`maxDate` or matched by `disabledDates`. |
|
|
500
|
+
| `footer` | The footer row (status text + actions). |
|
|
501
|
+
| `modalBackdrop` | The backdrop overlay behind the `modal` variant. |
|
|
502
|
+
| `presetsBar` | The presets sidebar/bar container. |
|
|
503
|
+
| `timePicker` | The time picker panel. |
|
|
504
|
+
|
|
505
|
+
```tsx
|
|
506
|
+
<JalaliDatePicker
|
|
507
|
+
classNames={{
|
|
508
|
+
root: "font-sans",
|
|
509
|
+
input: "rounded-lg border-gray-300 focus:ring-2 focus:ring-blue-500",
|
|
510
|
+
selectedCell: "bg-blue-600 text-white",
|
|
511
|
+
todayCell: "ring-1 ring-blue-400",
|
|
512
|
+
holidayCell: "text-red-500",
|
|
513
|
+
}}
|
|
514
|
+
/>
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Exported TypeScript interfaces & types
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
export type SelectedDateValue =
|
|
521
|
+
| Date
|
|
522
|
+
| null
|
|
523
|
+
| [Date | null, Date | null]
|
|
524
|
+
| Date[];
|
|
525
|
+
|
|
526
|
+
export interface DateRange {
|
|
527
|
+
start: Date | null;
|
|
528
|
+
end: Date | null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export interface JalaliTime {
|
|
532
|
+
hour: number;
|
|
533
|
+
minute: number;
|
|
534
|
+
second?: number;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export interface CalendarEvent {
|
|
538
|
+
date: Date;
|
|
539
|
+
color?: string;
|
|
540
|
+
label?: string;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export interface CustomHolidayRule {
|
|
544
|
+
/** Jalali month, 1-indexed */
|
|
545
|
+
month: number;
|
|
546
|
+
/** Jalali day of month */
|
|
547
|
+
day: number;
|
|
548
|
+
label?: string;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export interface DatePickerPreset {
|
|
552
|
+
label: string;
|
|
553
|
+
getValue: () => SelectedDateValue;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export interface CalendarCell {
|
|
557
|
+
key: string;
|
|
558
|
+
date: Date;
|
|
559
|
+
dayOfMonth: number;
|
|
560
|
+
isCurrentMonth: boolean;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export interface JalaliDatePickerClassNames {
|
|
564
|
+
root?: string;
|
|
565
|
+
input?: string;
|
|
566
|
+
calendar?: string;
|
|
567
|
+
header?: string;
|
|
568
|
+
grid?: string;
|
|
569
|
+
weekdays?: string;
|
|
570
|
+
dayCell?: string;
|
|
571
|
+
selectedCell?: string;
|
|
572
|
+
todayCell?: string;
|
|
573
|
+
holidayCell?: string;
|
|
574
|
+
inRangeCell?: string;
|
|
575
|
+
rangeStartCell?: string;
|
|
576
|
+
rangeEndCell?: string;
|
|
577
|
+
disabledCell?: string;
|
|
578
|
+
footer?: string;
|
|
579
|
+
modalBackdrop?: string;
|
|
580
|
+
presetsBar?: string;
|
|
581
|
+
timePicker?: string;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export type JalaliDatePickerStyles = {
|
|
585
|
+
[K in keyof JalaliDatePickerClassNames]?: React.CSSProperties;
|
|
586
|
+
};
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## 7. Theming & Styling Integration
|
|
592
|
+
|
|
593
|
+
### `DatePickerThemeProvider`
|
|
594
|
+
|
|
595
|
+
Wrap your app (or just the picker) in `DatePickerThemeProvider` to switch between light and dark palettes:
|
|
596
|
+
|
|
597
|
+
```tsx
|
|
598
|
+
import { DatePickerThemeProvider } from "@mngh/jalali-datepicker";
|
|
599
|
+
|
|
600
|
+
<DatePickerThemeProvider mode="dark">
|
|
601
|
+
<JalaliDatePicker mode="single" value={date} onChange={setDate} />
|
|
602
|
+
</DatePickerThemeProvider>;
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
| Prop | Type | Default | Description |
|
|
606
|
+
| ------ | ------------------- | --------- | ----------------------------------- |
|
|
607
|
+
| `mode` | `'light' \| 'dark'` | `'light'` | Sets the base CSS-variable palette. |
|
|
608
|
+
|
|
609
|
+
### CSS variables
|
|
610
|
+
|
|
611
|
+
Every visual token is exposed as a CSS custom property, so you can override the theme without touching class names:
|
|
612
|
+
|
|
613
|
+
```css
|
|
614
|
+
:root {
|
|
615
|
+
--pdp-surface-bg: #ffffff;
|
|
616
|
+
--pdp-surface-border: #e5e7eb;
|
|
617
|
+
--pdp-text-primary: #111827;
|
|
618
|
+
--pdp-text-muted: #6b7280;
|
|
619
|
+
--pdp-accent: #2563eb;
|
|
620
|
+
--pdp-accent-contrast: #ffffff;
|
|
621
|
+
--pdp-holiday-color: #dc2626;
|
|
622
|
+
--pdp-cell-size: 2.25rem;
|
|
623
|
+
--pdp-radius: 0.5rem;
|
|
624
|
+
--pdp-shadow: 0 10px 30px -10px rgb(0 0 0 / 0.15);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.dark {
|
|
628
|
+
--pdp-surface-bg: #111827;
|
|
629
|
+
--pdp-surface-border: #1f2937;
|
|
630
|
+
--pdp-text-primary: #f9fafb;
|
|
631
|
+
--pdp-text-muted: #9ca3af;
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Tailwind CSS via `classNames`
|
|
636
|
+
|
|
637
|
+
Because every slot accepts a plain class string, Tailwind utility classes compose directly with the component's own styling — no CSS-in-JS, no `!important` fights:
|
|
638
|
+
|
|
639
|
+
```tsx
|
|
640
|
+
<JalaliDatePicker
|
|
641
|
+
classNames={{
|
|
642
|
+
calendar: "shadow-xl border border-slate-200 rounded-2xl p-4",
|
|
643
|
+
header: "flex items-center justify-between mb-2",
|
|
644
|
+
dayCell: "h-9 w-9 rounded-full hover:bg-slate-100 transition-colors",
|
|
645
|
+
selectedCell: "bg-indigo-600 text-white hover:bg-indigo-600",
|
|
646
|
+
}}
|
|
647
|
+
/>
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## 8. Accessibility & Keyboard Shortcuts
|
|
653
|
+
|
|
654
|
+
The calendar grid uses `role="grid"` / `role="gridcell"` with a roving `tabindex`, and the popover/modal triggers manage focus trapping and restoration automatically.
|
|
655
|
+
|
|
656
|
+
| Key | Action |
|
|
657
|
+
| ----------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
658
|
+
| `Arrow Left` / `Arrow Right` | Move focus one day (respects `locale` direction — RTL for `fa`). |
|
|
659
|
+
| `Arrow Up` / `Arrow Down` | Move focus one week. |
|
|
660
|
+
| `Page Up` / `Page Down` | Move focus to the same day in the previous/next month. |
|
|
661
|
+
| `Alt + Page Up` / `Alt + Page Down` | Move focus to the same day in the previous/next year. |
|
|
662
|
+
| `Home` | Move focus to the first day of the current week. |
|
|
663
|
+
| `End` | Move focus to the last day of the current week. |
|
|
664
|
+
| `Enter` / `Space` | Select the focused date. |
|
|
665
|
+
| `Escape` | Close the `popover` or `modal` panel and return focus to the trigger. |
|
|
666
|
+
| `Tab` / `Shift + Tab` | Move focus between the trigger, calendar, presets bar, time picker, and footer actions, in visual order. |
|
|
667
|
+
|
|
668
|
+
> **Note:** When `locale="fa"`, the calendar grid renders right-to-left and `Arrow Left`/`Arrow Right` are swapped automatically so that "left" and "right" continue to match the visual direction of travel.
|
|
669
|
+
|
|
670
|
+
> **Tip:** `modal` variant applies `aria-modal="true"` and `role="dialog"`, and locks background scroll via a `overflow: hidden` toggle on `<body>` while open.
|