@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.
Files changed (92) hide show
  1. package/README.md +665 -8
  2. package/dist/a11y/aria-helpers.d.ts +7 -0
  3. package/dist/a11y/aria-helpers.d.ts.map +1 -0
  4. package/dist/a11y/keyboard-navigation.d.ts +3 -0
  5. package/dist/a11y/keyboard-navigation.d.ts.map +1 -0
  6. package/dist/a11y/types.d.ts +20 -0
  7. package/dist/a11y/types.d.ts.map +1 -0
  8. package/dist/a11y/useCalendarKeyboard.d.ts +9 -0
  9. package/dist/a11y/useCalendarKeyboard.d.ts.map +1 -0
  10. package/dist/components/DayCell.d.ts +32 -0
  11. package/dist/components/DayCell.d.ts.map +1 -0
  12. package/dist/components/Header.d.ts +11 -0
  13. package/dist/components/Header.d.ts.map +1 -0
  14. package/dist/components/JalaliDatePicker.d.ts +52 -0
  15. package/dist/components/JalaliDatePicker.d.ts.map +1 -0
  16. package/dist/components/MonthYearPicker.d.ts +9 -0
  17. package/dist/components/MonthYearPicker.d.ts.map +1 -0
  18. package/dist/components/Weekdays.d.ts +10 -0
  19. package/dist/components/Weekdays.d.ts.map +1 -0
  20. package/dist/components/dual-calendar/DualMonthCalendar.d.ts +4 -0
  21. package/dist/components/dual-calendar/DualMonthCalendar.d.ts.map +1 -0
  22. package/dist/components/dual-calendar/index.d.ts +3 -0
  23. package/dist/components/dual-calendar/index.d.ts.map +1 -0
  24. package/dist/components/dual-calendar/types.d.ts +29 -0
  25. package/dist/components/dual-calendar/types.d.ts.map +1 -0
  26. package/dist/components/footer/CalendarFooter.d.ts +16 -0
  27. package/dist/components/footer/CalendarFooter.d.ts.map +1 -0
  28. package/dist/components/footer/types.d.ts +28 -0
  29. package/dist/components/footer/types.d.ts.map +1 -0
  30. package/dist/components/masked-input/MaskedDateInput.d.ts +4 -0
  31. package/dist/components/masked-input/MaskedDateInput.d.ts.map +1 -0
  32. package/dist/components/masked-input/index.d.ts +4 -0
  33. package/dist/components/masked-input/index.d.ts.map +1 -0
  34. package/dist/components/masked-input/mask-utils.d.ts +11 -0
  35. package/dist/components/masked-input/mask-utils.d.ts.map +1 -0
  36. package/dist/components/masked-input/types.d.ts +37 -0
  37. package/dist/components/masked-input/types.d.ts.map +1 -0
  38. package/dist/core/calendar-grid.d.ts +10 -0
  39. package/dist/core/calendar-grid.d.ts.map +1 -0
  40. package/dist/core/constants.d.ts +14 -0
  41. package/dist/core/constants.d.ts.map +1 -0
  42. package/dist/core/jalali-helpers.d.ts +35 -0
  43. package/dist/core/jalali-helpers.d.ts.map +1 -0
  44. package/dist/core/jalali-math.d.ts +27 -0
  45. package/dist/core/jalali-math.d.ts.map +1 -0
  46. package/dist/core/types.d.ts +62 -0
  47. package/dist/core/types.d.ts.map +1 -0
  48. package/dist/events/event-utils.d.ts +7 -0
  49. package/dist/events/event-utils.d.ts.map +1 -0
  50. package/dist/events/index.d.ts +3 -0
  51. package/dist/events/index.d.ts.map +1 -0
  52. package/dist/events/types.d.ts +17 -0
  53. package/dist/events/types.d.ts.map +1 -0
  54. package/dist/formatters/jalali-formatter.d.ts +18 -0
  55. package/dist/formatters/jalali-formatter.d.ts.map +1 -0
  56. package/dist/formatters/persian-digits.d.ts +9 -0
  57. package/dist/formatters/persian-digits.d.ts.map +1 -0
  58. package/dist/holidays/index.d.ts +3 -0
  59. package/dist/holidays/index.d.ts.map +1 -0
  60. package/dist/holidays/iran-holidays.d.ts +11 -0
  61. package/dist/holidays/iran-holidays.d.ts.map +1 -0
  62. package/dist/holidays/types.d.ts +21 -0
  63. package/dist/holidays/types.d.ts.map +1 -0
  64. package/dist/hooks/types.d.ts +53 -0
  65. package/dist/hooks/types.d.ts.map +1 -0
  66. package/dist/hooks/useJalaliDatePicker.d.ts +17 -0
  67. package/dist/hooks/useJalaliDatePicker.d.ts.map +1 -0
  68. package/dist/index.cjs +1 -1
  69. package/dist/index.cjs.map +1 -1
  70. package/dist/index.d.ts +27 -1
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.mjs +922 -1156
  73. package/dist/index.mjs.map +1 -1
  74. package/dist/plugins/time-picker/TimePicker.d.ts +21 -0
  75. package/dist/plugins/time-picker/TimePicker.d.ts.map +1 -0
  76. package/dist/plugins/time-picker/index.d.ts +4 -0
  77. package/dist/plugins/time-picker/index.d.ts.map +1 -0
  78. package/dist/plugins/time-picker/time-utils.d.ts +18 -0
  79. package/dist/plugins/time-picker/time-utils.d.ts.map +1 -0
  80. package/dist/plugins/time-picker/types.d.ts +36 -0
  81. package/dist/plugins/time-picker/types.d.ts.map +1 -0
  82. package/dist/theme/ThemeProvider.d.ts +15 -0
  83. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  84. package/dist/theme/style-slots.d.ts +46 -0
  85. package/dist/theme/style-slots.d.ts.map +1 -0
  86. package/dist/theme/tokens.d.ts +8 -0
  87. package/dist/theme/tokens.d.ts.map +1 -0
  88. package/dist/theme/types.d.ts +62 -0
  89. package/dist/theme/types.d.ts.map +1 -0
  90. package/package.json +39 -6
  91. package/dist/favicon.svg +0 -1
  92. package/dist/icons.svg +0 -24
package/README.md CHANGED
@@ -1,13 +1,670 @@
1
- # 📅 Jalali Datepicker (تقویم و انتخاب‌گر تاریخ جلالی)
1
+ # @mngh/jalali-datepicker
2
2
 
3
- A modern, highly customizable, accessible, and lightweight Jalali Date & Time Picker for React.
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
- ## 📦 نصب (Installation)
5
+ [![npm version](https://img.shields.io/npm/v/@mngh/jalali-datepicker.svg)](https://www.npmjs.com/package/@mngh/jalali-datepicker)
6
+ [![bundlephobia](https://img.shields.io/bundlephobia/minzip/@mngh/jalali-datepicker)](https://bundlephobia.com/package/@mngh/jalali-datepicker)
7
+ [![license](https://img.shields.io/npm/l/@mngh/jalali-datepicker.svg)](https://github.com/mngh/jalali-datepicker/blob/main/LICENSE)
8
+ [![types](https://img.shields.io/badge/types-TypeScript-blue.svg)](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
- npm install jalali-datepicker
9
- # یا
10
- pnpm add jalali-datepicker
11
- # یا
12
- yarn add jalali-datepicker
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.