mehdi-akbari-calendar 0.1.5 → 0.1.6

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/dist/index.js CHANGED
@@ -1,8 +1 @@
1
- // src/index.ts
2
- /**
3
- * This is the main entry point for non-React functionalities and types.
4
- * It allows users to import types or utility functions without pulling in React.
5
- */
6
- export * from "./types";
7
- // In the future, you could also export utility functions from here:
8
- // export * from "./utils/dateAdapter";
1
+ //# sourceMappingURL=index.js.map
package/dist/react.js CHANGED
@@ -1,5 +1,450 @@
1
- // src/index.react.ts
2
1
  "use client";
3
- // مسیر صحیح بر اساس ساختار شما
4
- export { PersianCalendar } from "./components/Calendar/Calendar";
5
- export * from "./types";
2
+
3
+ // src/components/Calendar/Calendar.tsx
4
+ import clsx4 from "clsx";
5
+
6
+ // src/hooks/useCalendar.ts
7
+ import { useState, useMemo, useCallback } from "react";
8
+
9
+ // src/utils/dateAdapter.ts
10
+ import * as jdf from "date-fns-jalali";
11
+ import * as df from "date-fns";
12
+ import { faIR } from "date-fns-jalali/locale";
13
+ var getDateFunctions = (type) => {
14
+ const isJalali = type === "jalali";
15
+ const lib = isJalali ? jdf : df;
16
+ return {
17
+ startOfMonth: lib.startOfMonth,
18
+ endOfMonth: lib.endOfMonth,
19
+ startOfWeek: (date) => lib.startOfWeek(date, { locale: isJalali ? faIR : void 0, weekStartsOn: isJalali ? 6 : 1 }),
20
+ endOfWeek: (date) => lib.endOfWeek(date, { locale: isJalali ? faIR : void 0, weekStartsOn: isJalali ? 6 : 1 }),
21
+ eachDayOfInterval: lib.eachDayOfInterval,
22
+ isSameMonth: lib.isSameMonth,
23
+ isSameDay: lib.isSameDay,
24
+ format: (date, formatStr) => {
25
+ try {
26
+ return lib.format(date, formatStr, { locale: isJalali ? faIR : void 0 });
27
+ } catch {
28
+ return "";
29
+ }
30
+ },
31
+ addMonths: lib.addMonths,
32
+ subMonths: lib.subMonths,
33
+ getYear: lib.getYear,
34
+ setYear: lib.setYear,
35
+ getMonth: lib.getMonth,
36
+ setMonth: lib.setMonth,
37
+ startOfYear: lib.startOfYear,
38
+ eachMonthOfInterval: lib.eachMonthOfInterval,
39
+ endOfYear: lib.endOfYear,
40
+ addYears: lib.addYears,
41
+ subYears: lib.subYears,
42
+ isDate: lib.isDate,
43
+ isWithinInterval: lib.isWithinInterval
44
+ };
45
+ };
46
+ var getWeekDays = (type) => {
47
+ if (type === "jalali") {
48
+ return ["\u0634", "\u06CC", "\u062F", "\u0633", "\u0686", "\u067E", "\u062C"];
49
+ } else {
50
+ return ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
51
+ }
52
+ };
53
+
54
+ // src/hooks/useCalendar.ts
55
+ var useCalendar = ({
56
+ initialDate = /* @__PURE__ */ new Date(),
57
+ minYear = 1300,
58
+ maxYear = 1405,
59
+ initialCalendarType = "jalali",
60
+ range
61
+ } = {}) => {
62
+ const [calendarType, setCalendarType] = useState(initialCalendarType);
63
+ const dateFns = useMemo(() => getDateFunctions(calendarType), [calendarType]);
64
+ const effectiveMinYear = calendarType === "gregorian" && minYear === 1300 ? 1920 : minYear;
65
+ const effectiveMaxYear = calendarType === "gregorian" && maxYear === 1405 ? 2030 : maxYear;
66
+ const [viewMode, setViewMode] = useState("day");
67
+ const [currentDate, setCurrentDate] = useState(dateFns.startOfMonth(initialDate));
68
+ const [selectedDate, setSelectedDate] = useState(initialDate);
69
+ const daysOfMonth = useMemo(() => {
70
+ if (viewMode !== "day") return [];
71
+ const monthStart = dateFns.startOfMonth(currentDate);
72
+ const monthEnd = dateFns.endOfMonth(currentDate);
73
+ const startDate = dateFns.startOfWeek(monthStart);
74
+ const endDate = dateFns.endOfWeek(monthEnd);
75
+ return dateFns.eachDayOfInterval({ start: startDate, end: endDate });
76
+ }, [currentDate, viewMode, dateFns]);
77
+ const monthsOfYear = useMemo(() => {
78
+ if (viewMode !== "month") return [];
79
+ return dateFns.eachMonthOfInterval({
80
+ start: dateFns.startOfYear(currentDate),
81
+ end: dateFns.endOfYear(currentDate)
82
+ });
83
+ }, [currentDate, viewMode, dateFns]);
84
+ const yearsOfDecade = useMemo(() => {
85
+ if (viewMode !== "year") return [];
86
+ const year = dateFns.getYear(currentDate);
87
+ const decadeStartYear = Math.floor(year / 10) * 10;
88
+ const years = [];
89
+ for (let i = 0; i < 12; i++) {
90
+ years.push(decadeStartYear + i - 1);
91
+ }
92
+ return years;
93
+ }, [currentDate, viewMode, dateFns]);
94
+ const goToNext = useCallback(() => {
95
+ if (viewMode === "day") setCurrentDate((prev) => dateFns.addMonths(prev, 1));
96
+ if (viewMode === "month") setCurrentDate((prev) => dateFns.addYears(prev, 1));
97
+ if (viewMode === "year") setCurrentDate((prev) => dateFns.addYears(prev, 10));
98
+ }, [viewMode, dateFns]);
99
+ const goToPrev = useCallback(() => {
100
+ if (viewMode === "day") setCurrentDate((prev) => dateFns.subMonths(prev, 1));
101
+ if (viewMode === "month") setCurrentDate((prev) => dateFns.subYears(prev, 1));
102
+ if (viewMode === "year") setCurrentDate((prev) => dateFns.subYears(prev, 10));
103
+ }, [viewMode, dateFns]);
104
+ const handleSelectDay = useCallback((date) => {
105
+ setSelectedDate(date);
106
+ if (!dateFns.isSameMonth(currentDate, date)) {
107
+ setCurrentDate(dateFns.startOfMonth(date));
108
+ }
109
+ }, [currentDate, dateFns]);
110
+ const handleSelectMonth = useCallback((monthIndex) => {
111
+ setCurrentDate((prev) => dateFns.setMonth(prev, monthIndex));
112
+ setViewMode("day");
113
+ }, [dateFns]);
114
+ const handleSelectYear = useCallback((year) => {
115
+ setCurrentDate((prev) => dateFns.setYear(prev, year));
116
+ setViewMode("month");
117
+ }, [dateFns]);
118
+ const handleHeaderClick = useCallback(() => {
119
+ if (viewMode === "day") setViewMode("month");
120
+ if (viewMode === "month") setViewMode("year");
121
+ }, [viewMode]);
122
+ const toggleCalendarType = useCallback(() => {
123
+ setCalendarType((prev) => prev === "jalali" ? "gregorian" : "jalali");
124
+ setViewMode("day");
125
+ }, []);
126
+ const isDateSelected = useCallback((date) => {
127
+ if (range) {
128
+ return !!(range.from && dateFns.isSameDay(date, range.from) || range.to && dateFns.isSameDay(date, range.to));
129
+ }
130
+ return selectedDate ? dateFns.isSameDay(date, selectedDate) : false;
131
+ }, [selectedDate, range, dateFns]);
132
+ const isInRange = useCallback((date) => {
133
+ if (!range?.from || !range?.to) return false;
134
+ return dateFns.isWithinInterval(date, { start: range.from, end: range.to });
135
+ }, [range, dateFns]);
136
+ const isRangeStart = useCallback((date) => {
137
+ return !!(range?.from && dateFns.isSameDay(date, range.from));
138
+ }, [range, dateFns]);
139
+ const isRangeEnd = useCallback((date) => {
140
+ return !!(range?.to && dateFns.isSameDay(date, range.to));
141
+ }, [range, dateFns]);
142
+ const headerTitle = useMemo(() => {
143
+ if (viewMode === "day") return dateFns.format(currentDate, "LLLL yyyy");
144
+ if (viewMode === "month") return dateFns.format(currentDate, "yyyy");
145
+ if (viewMode === "year") {
146
+ const start = yearsOfDecade[0];
147
+ const end = yearsOfDecade[yearsOfDecade.length - 1];
148
+ return `${end} - ${start}`;
149
+ }
150
+ return "";
151
+ }, [currentDate, viewMode, yearsOfDecade, dateFns]);
152
+ return {
153
+ viewMode,
154
+ currentDate,
155
+ selectedDate,
156
+ daysOfMonth,
157
+ monthsOfYear,
158
+ yearsOfDecade,
159
+ headerTitle,
160
+ calendarType,
161
+ weekDays: getWeekDays(calendarType),
162
+ dateFns,
163
+ toggleCalendarType,
164
+ goToNext,
165
+ goToPrev,
166
+ handleSelectDay,
167
+ handleSelectMonth,
168
+ handleSelectYear,
169
+ handleHeaderClick,
170
+ isSameMonthAsCurrent: (date) => dateFns.isSameMonth(currentDate, date),
171
+ isDateSelected,
172
+ isInRange,
173
+ isRangeStart,
174
+ isRangeEnd,
175
+ isToday: (date) => dateFns.isSameDay(date, /* @__PURE__ */ new Date()),
176
+ isCurrentMonth: (monthIndex) => dateFns.getMonth(/* @__PURE__ */ new Date()) === monthIndex && dateFns.getYear(/* @__PURE__ */ new Date()) === dateFns.getYear(currentDate),
177
+ isCurrentYear: (year) => dateFns.getYear(/* @__PURE__ */ new Date()) === year,
178
+ isYearDisabled: (year) => year > effectiveMaxYear || year < effectiveMinYear
179
+ };
180
+ };
181
+
182
+ // src/components/internal/CalendarHeader.tsx
183
+ import { jsx, jsxs } from "react/jsx-runtime";
184
+ var SunIcon = () => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
185
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "4" }),
186
+ /* @__PURE__ */ jsx("path", { d: "M12 2v2" }),
187
+ /* @__PURE__ */ jsx("path", { d: "M12 20v2" }),
188
+ /* @__PURE__ */ jsx("path", { d: "m4.93 4.93 1.41 1.41" }),
189
+ /* @__PURE__ */ jsx("path", { d: "m17.66 17.66 1.41 1.41" }),
190
+ /* @__PURE__ */ jsx("path", { d: "M2 12h2" }),
191
+ /* @__PURE__ */ jsx("path", { d: "M20 12h2" }),
192
+ /* @__PURE__ */ jsx("path", { d: "m6.34 17.66-1.41 1.41" }),
193
+ /* @__PURE__ */ jsx("path", { d: "m19.07 4.93-1.41 1.41" })
194
+ ] });
195
+ var GlobeIcon = () => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
196
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
197
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "12", x2: "22", y2: "12" }),
198
+ /* @__PURE__ */ jsx("path", { d: "M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" })
199
+ ] });
200
+ var ChevronRightIcon = () => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "9 18 15 12 9 6" }) });
201
+ var ChevronLeftIcon = () => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "15 18 9 12 15 6" }) });
202
+ var CalendarHeader = ({
203
+ title,
204
+ calendarType,
205
+ onToggleCalendarType,
206
+ onNext,
207
+ onPrev,
208
+ onTitleClick
209
+ }) => {
210
+ return /* @__PURE__ */ jsxs("div", { className: "calendar-header", children: [
211
+ /* @__PURE__ */ jsx(
212
+ "button",
213
+ {
214
+ type: "button",
215
+ className: `calendar-header__toggle-btn ${calendarType}`,
216
+ onClick: onToggleCalendarType,
217
+ title: calendarType === "jalali" ? "\u062A\u063A\u06CC\u06CC\u0631 \u0628\u0647 \u062A\u0642\u0648\u06CC\u0645 \u0645\u06CC\u0644\u0627\u062F\u06CC" : "Switch to Persian Calendar",
218
+ children: calendarType === "jalali" ? /* @__PURE__ */ jsx(SunIcon, {}) : /* @__PURE__ */ jsx(GlobeIcon, {})
219
+ }
220
+ ),
221
+ /* @__PURE__ */ jsxs("div", { className: "calendar-header__controls", children: [
222
+ /* @__PURE__ */ jsx("button", { type: "button", className: "calendar-header__nav-button", onClick: onPrev, "aria-label": "\u0642\u0628\u0644\u06CC", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) }),
223
+ /* @__PURE__ */ jsx("button", { type: "button", className: "calendar-header__title", onClick: onTitleClick, children: title }),
224
+ /* @__PURE__ */ jsx("button", { type: "button", className: "calendar-header__nav-button", onClick: onNext, "aria-label": "\u0628\u0639\u062F\u06CC", children: /* @__PURE__ */ jsx(ChevronLeftIcon, {}) })
225
+ ] })
226
+ ] });
227
+ };
228
+
229
+ // src/components/internal/DayCell.tsx
230
+ import clsx from "clsx";
231
+ import { jsx as jsx2 } from "react/jsx-runtime";
232
+ var DayCell = ({
233
+ date,
234
+ isCurrentMonth,
235
+ isSelected,
236
+ isToday,
237
+ isInRange = false,
238
+ isRangeStart = false,
239
+ isRangeEnd = false,
240
+ isDisabled = false,
241
+ onClick,
242
+ dateFns
243
+ }) => {
244
+ const dayNumber = dateFns.format(date, "d");
245
+ const cellClassName = clsx("day-cell", {
246
+ "day-cell--not-current-month": !isCurrentMonth,
247
+ "day-cell--is-today": isToday,
248
+ "day-cell--is-selected": isSelected,
249
+ "day-cell--is-disabled": isDisabled,
250
+ // ✅ کلاس‌های جدید
251
+ "day-cell--in-range": isInRange,
252
+ "day-cell--range-start": isRangeStart,
253
+ "day-cell--range-end": isRangeEnd
254
+ });
255
+ return /* @__PURE__ */ jsx2("div", { className: cellClassName, children: /* @__PURE__ */ jsx2(
256
+ "button",
257
+ {
258
+ type: "button",
259
+ className: "day-cell__button",
260
+ onClick: () => !isDisabled && onClick(date),
261
+ disabled: isDisabled,
262
+ children: dayNumber
263
+ }
264
+ ) });
265
+ };
266
+
267
+ // src/components/internal/MonthView.tsx
268
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
269
+ var MonthView = ({
270
+ daysOfMonth,
271
+ weekDays,
272
+ dateFns,
273
+ onSelectDate,
274
+ isDateSelected,
275
+ isInRange,
276
+ isRangeStart,
277
+ isRangeEnd,
278
+ isSameMonthAsCurrent,
279
+ isToday
280
+ }) => {
281
+ return /* @__PURE__ */ jsxs2("div", { className: "month-view", children: [
282
+ /* @__PURE__ */ jsx3("div", { className: "month-view__weekdays", children: weekDays.map((dayName) => /* @__PURE__ */ jsx3("div", { className: "month-view__weekday-name", children: dayName }, dayName)) }),
283
+ /* @__PURE__ */ jsx3("div", { className: "month-view__days-grid", children: daysOfMonth.map((day) => /* @__PURE__ */ jsx3(
284
+ DayCell,
285
+ {
286
+ date: day,
287
+ onClick: onSelectDate,
288
+ isSelected: isDateSelected(day),
289
+ isInRange: isInRange(day),
290
+ isRangeStart: isRangeStart(day),
291
+ isRangeEnd: isRangeEnd(day),
292
+ isCurrentMonth: isSameMonthAsCurrent(day),
293
+ isToday: isToday(day),
294
+ dateFns
295
+ },
296
+ day.toISOString()
297
+ )) })
298
+ ] });
299
+ };
300
+
301
+ // src/components/internal/MonthPickerView.tsx
302
+ import clsx2 from "clsx";
303
+ import { jsx as jsx4 } from "react/jsx-runtime";
304
+ var MonthPickerView = ({
305
+ months,
306
+ onSelectMonth,
307
+ isCurrentMonth,
308
+ dateFns
309
+ }) => {
310
+ return /* @__PURE__ */ jsx4("div", { className: "picker-view month-picker-view", children: months.map((monthDate, index) => {
311
+ const monthClassName = clsx2("picker-item", {
312
+ "picker-item--is-current": isCurrentMonth(index)
313
+ });
314
+ return /* @__PURE__ */ jsx4(
315
+ "button",
316
+ {
317
+ type: "button",
318
+ className: monthClassName,
319
+ onClick: () => onSelectMonth(index),
320
+ children: dateFns.format(monthDate, "LLLL")
321
+ },
322
+ index
323
+ );
324
+ }) });
325
+ };
326
+
327
+ // src/components/internal/YearView.tsx
328
+ import clsx3 from "clsx";
329
+ import { jsx as jsx5 } from "react/jsx-runtime";
330
+ var YearView = ({
331
+ years,
332
+ onSelectYear,
333
+ isCurrentYear,
334
+ isYearDisabled
335
+ }) => {
336
+ return /* @__PURE__ */ jsx5("div", { className: "picker-view year-picker-view", children: years.map((year) => {
337
+ const isDisabled = isYearDisabled(year);
338
+ const yearClassName = clsx3("picker-item", {
339
+ "picker-item--is-current": isCurrentYear(year),
340
+ "picker-item--is-disabled": isDisabled
341
+ });
342
+ return /* @__PURE__ */ jsx5(
343
+ "button",
344
+ {
345
+ type: "button",
346
+ className: yearClassName,
347
+ onClick: () => !isDisabled && onSelectYear(year),
348
+ disabled: isDisabled,
349
+ "aria-current": isCurrentYear(year) ? "true" : void 0,
350
+ children: year
351
+ },
352
+ year
353
+ );
354
+ }) });
355
+ };
356
+
357
+ // src/components/Calendar/Calendar.tsx
358
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
359
+ var CheckCircleIcon = () => /* @__PURE__ */ jsxs3("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
360
+ /* @__PURE__ */ jsx6("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
361
+ /* @__PURE__ */ jsx6("polyline", { points: "22 4 12 14.01 9 11.01" })
362
+ ] });
363
+ var PersianCalendar = ({
364
+ initialDate,
365
+ onDateSelect,
366
+ minYear,
367
+ maxYear,
368
+ range,
369
+ className
370
+ }) => {
371
+ const {
372
+ viewMode,
373
+ daysOfMonth,
374
+ monthsOfYear,
375
+ yearsOfDecade,
376
+ headerTitle,
377
+ calendarType,
378
+ weekDays,
379
+ dateFns,
380
+ toggleCalendarType,
381
+ goToNext,
382
+ goToPrev,
383
+ handleSelectDay,
384
+ handleSelectMonth,
385
+ handleSelectYear,
386
+ handleHeaderClick,
387
+ isDateSelected,
388
+ isInRange,
389
+ isRangeStart,
390
+ isRangeEnd,
391
+ isSameMonthAsCurrent,
392
+ isToday,
393
+ isCurrentMonth,
394
+ isCurrentYear,
395
+ isYearDisabled
396
+ } = useCalendar({ initialDate, minYear, maxYear, range });
397
+ const onDayClick = (date) => {
398
+ handleSelectDay(date);
399
+ if (onDateSelect) {
400
+ onDateSelect(date);
401
+ }
402
+ };
403
+ const renderView = () => {
404
+ switch (viewMode) {
405
+ case "year":
406
+ return /* @__PURE__ */ jsx6(YearView, { years: yearsOfDecade, onSelectYear: handleSelectYear, isCurrentYear, isYearDisabled });
407
+ case "month":
408
+ return /* @__PURE__ */ jsx6(MonthPickerView, { months: monthsOfYear, onSelectMonth: handleSelectMonth, isCurrentMonth, dateFns });
409
+ case "day":
410
+ default:
411
+ return /* @__PURE__ */ jsx6(
412
+ MonthView,
413
+ {
414
+ daysOfMonth,
415
+ weekDays,
416
+ dateFns,
417
+ onSelectDate: onDayClick,
418
+ isDateSelected,
419
+ isInRange,
420
+ isRangeStart,
421
+ isRangeEnd,
422
+ isSameMonthAsCurrent,
423
+ isToday
424
+ }
425
+ );
426
+ }
427
+ };
428
+ return /* @__PURE__ */ jsxs3("div", { className: clsx4("prc-container", className), dir: calendarType === "jalali" ? "rtl" : "ltr", children: [
429
+ /* @__PURE__ */ jsx6(
430
+ CalendarHeader,
431
+ {
432
+ title: headerTitle,
433
+ calendarType,
434
+ onToggleCalendarType: toggleCalendarType,
435
+ onNext: goToNext,
436
+ onPrev: goToPrev,
437
+ onTitleClick: handleHeaderClick
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsx6("div", { className: "prc-view-container", children: renderView() }),
441
+ /* @__PURE__ */ jsxs3("footer", { className: "prc-footer", children: [
442
+ /* @__PURE__ */ jsx6("span", { children: "\u{1D4DF}\u{1D4F8}\u{1D500}\u{1D4EE}\u{1D4FB}\u{1D4EE}\u{1D4ED} \u{1D4EB}\u{1D502} \u{1D4DC}\u{1D4EE}\u{1D4F1}\u{1D4ED}\u{1D4F2} \u{1D4D0}\u{1D4F4}\u{1D4EB}\u{1D4EA}\u{1D4FB}\u{1D4F2}" }),
443
+ /* @__PURE__ */ jsx6(CheckCircleIcon, {})
444
+ ] })
445
+ ] });
446
+ };
447
+ export {
448
+ PersianCalendar
449
+ };
450
+ //# sourceMappingURL=react.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mehdi-akbari-calendar",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "A professional and customizable Persian (Jalali) calendar component for React.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,14 +32,14 @@
32
32
  },
33
33
  "./styles.css": "./dist/styles.css"
34
34
  },
35
- "scripts": {
36
- "clean": "npx rimraf dist",
37
- "build:js": "tsup",
38
- "build:types": "tsc --project tsconfig.json",
39
- "build": "npm run clean && npm run build:js && npm run build:types && copyfiles -u 1 src/styles.css dist",
40
- "dev": "tsup --watch",
41
- "prepublishOnly": "npm run build"
42
- },
35
+ "scripts": {
36
+ "clean": "npx rimraf dist",
37
+ "build:js": "tsup",
38
+ "build:types": "tsc --project tsconfig.dts.json",
39
+ "build": "npm run clean && npm run build:js && npm run build:types && copyfiles -u 1 src/styles.css dist",
40
+ "dev": "tsup --watch",
41
+ "prepublishOnly": "npm run build"
42
+ },
43
43
  "peerDependencies": {
44
44
  "react": ">=18",
45
45
  "react-dom": ">=18"
@@ -1,30 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import clsx from 'clsx';
4
- import { useCalendar } from '../../hooks/useCalendar';
5
- import { CalendarHeader } from '../internal/CalendarHeader';
6
- import { MonthView } from '../internal/MonthView';
7
- import { MonthPickerView } from '../internal/MonthPickerView';
8
- import { YearView } from '../internal/YearView';
9
- const CheckCircleIcon = () => (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }), _jsx("polyline", { points: "22 4 12 14.01 9 11.01" })] }));
10
- export const PersianCalendar = ({ initialDate, onDateSelect, minYear, maxYear, range, className }) => {
11
- const { viewMode, daysOfMonth, monthsOfYear, yearsOfDecade, headerTitle, calendarType, weekDays, dateFns, toggleCalendarType, goToNext, goToPrev, handleSelectDay, handleSelectMonth, handleSelectYear, handleHeaderClick, isDateSelected, isInRange, isRangeStart, isRangeEnd, isSameMonthAsCurrent, isToday, isCurrentMonth, isCurrentYear, isYearDisabled, } = useCalendar({ initialDate, minYear, maxYear, range });
12
- const onDayClick = (date) => {
13
- handleSelectDay(date);
14
- if (onDateSelect) {
15
- onDateSelect(date);
16
- }
17
- };
18
- const renderView = () => {
19
- switch (viewMode) {
20
- case 'year':
21
- return _jsx(YearView, { years: yearsOfDecade, onSelectYear: handleSelectYear, isCurrentYear: isCurrentYear, isYearDisabled: isYearDisabled });
22
- case 'month':
23
- return _jsx(MonthPickerView, { months: monthsOfYear, onSelectMonth: handleSelectMonth, isCurrentMonth: isCurrentMonth, dateFns: dateFns });
24
- case 'day':
25
- default:
26
- return (_jsx(MonthView, { daysOfMonth: daysOfMonth, weekDays: weekDays, dateFns: dateFns, onSelectDate: onDayClick, isDateSelected: isDateSelected, isInRange: isInRange, isRangeStart: isRangeStart, isRangeEnd: isRangeEnd, isSameMonthAsCurrent: isSameMonthAsCurrent, isToday: isToday }));
27
- }
28
- };
29
- return (_jsxs("div", { className: clsx("prc-container", className), dir: calendarType === 'jalali' ? 'rtl' : 'ltr', children: [_jsx(CalendarHeader, { title: headerTitle, calendarType: calendarType, onToggleCalendarType: toggleCalendarType, onNext: goToNext, onPrev: goToPrev, onTitleClick: handleHeaderClick }), _jsx("div", { className: "prc-view-container", children: renderView() }), _jsxs("footer", { className: "prc-footer", children: [_jsx("span", { children: "\uD835\uDCDF\uD835\uDCF8\uD835\uDD00\uD835\uDCEE\uD835\uDCFB\uD835\uDCEE\uD835\uDCED \uD835\uDCEB\uD835\uDD02 \uD835\uDCDC\uD835\uDCEE\uD835\uDCF1\uD835\uDCED\uD835\uDCF2 \uD835\uDCD0\uD835\uDCF4\uD835\uDCEB\uD835\uDCEA\uD835\uDCFB\uD835\uDCF2" }), _jsx(CheckCircleIcon, {})] })] }));
30
- };
@@ -1,10 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // آیکون خورشید برای تقویم شمسی
3
- const SunIcon = () => (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "12", cy: "12", r: "4" }), _jsx("path", { d: "M12 2v2" }), _jsx("path", { d: "M12 20v2" }), _jsx("path", { d: "m4.93 4.93 1.41 1.41" }), _jsx("path", { d: "m17.66 17.66 1.41 1.41" }), _jsx("path", { d: "M2 12h2" }), _jsx("path", { d: "M20 12h2" }), _jsx("path", { d: "m6.34 17.66-1.41 1.41" }), _jsx("path", { d: "m19.07 4.93-1.41 1.41" })] }));
4
- // آیکون کره زمین برای تقویم میلادی
5
- const GlobeIcon = () => (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "12", cy: "12", r: "10" }), _jsx("line", { x1: "2", y1: "12", x2: "22", y2: "12" }), _jsx("path", { d: "M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" })] }));
6
- const ChevronRightIcon = () => (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "9 18 15 12 9 6" }) }));
7
- const ChevronLeftIcon = () => (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "15 18 9 12 15 6" }) }));
8
- export const CalendarHeader = ({ title, calendarType, onToggleCalendarType, onNext, onPrev, onTitleClick, }) => {
9
- return (_jsxs("div", { className: "calendar-header", children: [_jsx("button", { type: "button", className: `calendar-header__toggle-btn ${calendarType}`, onClick: onToggleCalendarType, title: calendarType === 'jalali' ? "تغییر به تقویم میلادی" : "Switch to Persian Calendar", children: calendarType === 'jalali' ? _jsx(SunIcon, {}) : _jsx(GlobeIcon, {}) }), _jsxs("div", { className: "calendar-header__controls", children: [_jsx("button", { type: "button", className: "calendar-header__nav-button", onClick: onPrev, "aria-label": "\u0642\u0628\u0644\u06CC", children: _jsx(ChevronRightIcon, {}) }), _jsx("button", { type: "button", className: "calendar-header__title", onClick: onTitleClick, children: title }), _jsx("button", { type: "button", className: "calendar-header__nav-button", onClick: onNext, "aria-label": "\u0628\u0639\u062F\u06CC", children: _jsx(ChevronLeftIcon, {}) })] })] }));
10
- };
@@ -1,16 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import clsx from 'clsx';
3
- export const DayCell = ({ date, isCurrentMonth, isSelected, isToday, isInRange = false, isRangeStart = false, isRangeEnd = false, isDisabled = false, onClick, dateFns, }) => {
4
- const dayNumber = dateFns.format(date, 'd');
5
- const cellClassName = clsx('day-cell', {
6
- 'day-cell--not-current-month': !isCurrentMonth,
7
- 'day-cell--is-today': isToday,
8
- 'day-cell--is-selected': isSelected,
9
- 'day-cell--is-disabled': isDisabled,
10
- // ✅ کلاس‌های جدید
11
- 'day-cell--in-range': isInRange,
12
- 'day-cell--range-start': isRangeStart,
13
- 'day-cell--range-end': isRangeEnd,
14
- });
15
- return (_jsx("div", { className: cellClassName, children: _jsx("button", { type: "button", className: "day-cell__button", onClick: () => !isDisabled && onClick(date), disabled: isDisabled, children: dayNumber }) }));
16
- };
@@ -1,10 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import clsx from 'clsx';
3
- export const MonthPickerView = ({ months, onSelectMonth, isCurrentMonth, dateFns }) => {
4
- return (_jsx("div", { className: "picker-view month-picker-view", children: months.map((monthDate, index) => {
5
- const monthClassName = clsx('picker-item', {
6
- 'picker-item--is-current': isCurrentMonth(index),
7
- });
8
- return (_jsx("button", { type: "button", className: monthClassName, onClick: () => onSelectMonth(index), children: dateFns.format(monthDate, 'LLLL') }, index));
9
- }) }));
10
- };
@@ -1,7 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { DayCell } from './DayCell';
3
- export const MonthView = ({ daysOfMonth, weekDays, dateFns, onSelectDate, isDateSelected, isInRange, isRangeStart, isRangeEnd, isSameMonthAsCurrent, isToday, }) => {
4
- return (_jsxs("div", { className: "month-view", children: [_jsx("div", { className: "month-view__weekdays", children: weekDays.map(dayName => (_jsx("div", { className: "month-view__weekday-name", children: dayName }, dayName))) }), _jsx("div", { className: "month-view__days-grid", children: daysOfMonth.map(day => (_jsx(DayCell, { date: day, onClick: onSelectDate, isSelected: isDateSelected(day),
5
- // ✅ پاس دادن مقادیر محاسباتی
6
- isInRange: isInRange(day), isRangeStart: isRangeStart(day), isRangeEnd: isRangeEnd(day), isCurrentMonth: isSameMonthAsCurrent(day), isToday: isToday(day), dateFns: dateFns }, day.toISOString()))) })] }));
7
- };
@@ -1,14 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import clsx from 'clsx';
4
- // ✅ اطمینان حاصل کنید که کلمه export دقیقاً قبل از const قرار دارد
5
- export const YearView = ({ years, onSelectYear, isCurrentYear, isYearDisabled }) => {
6
- return (_jsx("div", { className: "picker-view year-picker-view", children: years.map(year => {
7
- const isDisabled = isYearDisabled(year);
8
- const yearClassName = clsx('picker-item', {
9
- 'picker-item--is-current': isCurrentYear(year),
10
- 'picker-item--is-disabled': isDisabled,
11
- });
12
- return (_jsx("button", { type: "button", className: yearClassName, onClick: () => !isDisabled && onSelectYear(year), disabled: isDisabled, "aria-current": isCurrentYear(year) ? 'true' : undefined, children: year }, year));
13
- }) }));
14
- };
@@ -1,139 +0,0 @@
1
- "use client";
2
- import { useState, useMemo, useCallback } from 'react';
3
- // ✅ مرحله ۱: ایمپورت توابع از dateAdapter
4
- import { getDateFunctions, getWeekDays } from '../utils/dateAdapter';
5
- export const useCalendar = ({ initialDate = new Date(), minYear = 1300, maxYear = 1405, initialCalendarType = 'jalali', range } = {}) => {
6
- const [calendarType, setCalendarType] = useState(initialCalendarType);
7
- const dateFns = useMemo(() => getDateFunctions(calendarType), [calendarType]);
8
- const effectiveMinYear = calendarType === 'gregorian' && minYear === 1300 ? 1920 : minYear;
9
- const effectiveMaxYear = calendarType === 'gregorian' && maxYear === 1405 ? 2030 : maxYear;
10
- const [viewMode, setViewMode] = useState('day');
11
- const [currentDate, setCurrentDate] = useState(dateFns.startOfMonth(initialDate));
12
- const [selectedDate, setSelectedDate] = useState(initialDate);
13
- const daysOfMonth = useMemo(() => {
14
- if (viewMode !== 'day')
15
- return [];
16
- const monthStart = dateFns.startOfMonth(currentDate);
17
- const monthEnd = dateFns.endOfMonth(currentDate);
18
- const startDate = dateFns.startOfWeek(monthStart);
19
- const endDate = dateFns.endOfWeek(monthEnd);
20
- return dateFns.eachDayOfInterval({ start: startDate, end: endDate });
21
- }, [currentDate, viewMode, dateFns]);
22
- const monthsOfYear = useMemo(() => {
23
- if (viewMode !== 'month')
24
- return [];
25
- return dateFns.eachMonthOfInterval({
26
- start: dateFns.startOfYear(currentDate),
27
- end: dateFns.endOfYear(currentDate),
28
- });
29
- }, [currentDate, viewMode, dateFns]);
30
- const yearsOfDecade = useMemo(() => {
31
- if (viewMode !== 'year')
32
- return [];
33
- const year = dateFns.getYear(currentDate);
34
- const decadeStartYear = Math.floor(year / 10) * 10;
35
- const years = [];
36
- for (let i = 0; i < 12; i++) {
37
- years.push(decadeStartYear + i - 1);
38
- }
39
- return years;
40
- }, [currentDate, viewMode, dateFns]);
41
- const goToNext = useCallback(() => {
42
- if (viewMode === 'day')
43
- setCurrentDate(prev => dateFns.addMonths(prev, 1));
44
- if (viewMode === 'month')
45
- setCurrentDate(prev => dateFns.addYears(prev, 1));
46
- if (viewMode === 'year')
47
- setCurrentDate(prev => dateFns.addYears(prev, 10));
48
- }, [viewMode, dateFns]);
49
- const goToPrev = useCallback(() => {
50
- if (viewMode === 'day')
51
- setCurrentDate(prev => dateFns.subMonths(prev, 1));
52
- if (viewMode === 'month')
53
- setCurrentDate(prev => dateFns.subYears(prev, 1));
54
- if (viewMode === 'year')
55
- setCurrentDate(prev => dateFns.subYears(prev, 10));
56
- }, [viewMode, dateFns]);
57
- const handleSelectDay = useCallback((date) => {
58
- setSelectedDate(date);
59
- if (!dateFns.isSameMonth(currentDate, date)) {
60
- setCurrentDate(dateFns.startOfMonth(date));
61
- }
62
- }, [currentDate, dateFns]);
63
- const handleSelectMonth = useCallback((monthIndex) => {
64
- setCurrentDate(prev => dateFns.setMonth(prev, monthIndex));
65
- setViewMode('day');
66
- }, [dateFns]);
67
- const handleSelectYear = useCallback((year) => {
68
- setCurrentDate(prev => dateFns.setYear(prev, year));
69
- setViewMode('month');
70
- }, [dateFns]);
71
- const handleHeaderClick = useCallback(() => {
72
- if (viewMode === 'day')
73
- setViewMode('month');
74
- if (viewMode === 'month')
75
- setViewMode('year');
76
- }, [viewMode]);
77
- const toggleCalendarType = useCallback(() => {
78
- setCalendarType(prev => prev === 'jalali' ? 'gregorian' : 'jalali');
79
- setViewMode('day');
80
- }, []);
81
- const isDateSelected = useCallback((date) => {
82
- if (range) {
83
- return !!((range.from && dateFns.isSameDay(date, range.from)) ||
84
- (range.to && dateFns.isSameDay(date, range.to)));
85
- }
86
- return selectedDate ? dateFns.isSameDay(date, selectedDate) : false;
87
- }, [selectedDate, range, dateFns]);
88
- const isInRange = useCallback((date) => {
89
- if (!range?.from || !range?.to)
90
- return false;
91
- return dateFns.isWithinInterval(date, { start: range.from, end: range.to });
92
- }, [range, dateFns]);
93
- const isRangeStart = useCallback((date) => {
94
- return !!(range?.from && dateFns.isSameDay(date, range.from));
95
- }, [range, dateFns]);
96
- const isRangeEnd = useCallback((date) => {
97
- return !!(range?.to && dateFns.isSameDay(date, range.to));
98
- }, [range, dateFns]);
99
- const headerTitle = useMemo(() => {
100
- if (viewMode === 'day')
101
- return dateFns.format(currentDate, 'LLLL yyyy');
102
- if (viewMode === 'month')
103
- return dateFns.format(currentDate, 'yyyy');
104
- if (viewMode === 'year') {
105
- const start = yearsOfDecade[0];
106
- const end = yearsOfDecade[yearsOfDecade.length - 1];
107
- return `${end} - ${start}`;
108
- }
109
- return '';
110
- }, [currentDate, viewMode, yearsOfDecade, dateFns]);
111
- return {
112
- viewMode,
113
- currentDate,
114
- selectedDate,
115
- daysOfMonth,
116
- monthsOfYear,
117
- yearsOfDecade,
118
- headerTitle,
119
- calendarType,
120
- weekDays: getWeekDays(calendarType),
121
- dateFns,
122
- toggleCalendarType,
123
- goToNext,
124
- goToPrev,
125
- handleSelectDay,
126
- handleSelectMonth,
127
- handleSelectYear,
128
- handleHeaderClick,
129
- isSameMonthAsCurrent: (date) => dateFns.isSameMonth(currentDate, date),
130
- isDateSelected,
131
- isInRange,
132
- isRangeStart,
133
- isRangeEnd,
134
- isToday: (date) => dateFns.isSameDay(date, new Date()),
135
- isCurrentMonth: (monthIndex) => dateFns.getMonth(new Date()) === monthIndex && dateFns.getYear(new Date()) === dateFns.getYear(currentDate),
136
- isCurrentYear: (year) => dateFns.getYear(new Date()) === year,
137
- isYearDisabled: (year) => year > effectiveMaxYear || year < effectiveMinYear,
138
- };
139
- };
@@ -1,2 +0,0 @@
1
- // src/types/index.ts
2
- export {};
@@ -1,45 +0,0 @@
1
- import * as jdf from 'date-fns-jalali';
2
- import * as df from 'date-fns';
3
- import { faIR } from 'date-fns-jalali/locale';
4
- export const getDateFunctions = (type) => {
5
- const isJalali = type === 'jalali';
6
- const lib = isJalali ? jdf : df;
7
- return {
8
- startOfMonth: lib.startOfMonth,
9
- endOfMonth: lib.endOfMonth,
10
- startOfWeek: (date) => lib.startOfWeek(date, { locale: isJalali ? faIR : undefined, weekStartsOn: isJalali ? 6 : 1 }),
11
- endOfWeek: (date) => lib.endOfWeek(date, { locale: isJalali ? faIR : undefined, weekStartsOn: isJalali ? 6 : 1 }),
12
- eachDayOfInterval: lib.eachDayOfInterval,
13
- isSameMonth: lib.isSameMonth,
14
- isSameDay: lib.isSameDay,
15
- format: (date, formatStr) => {
16
- try {
17
- return lib.format(date, formatStr, { locale: isJalali ? faIR : undefined });
18
- }
19
- catch {
20
- return '';
21
- }
22
- },
23
- addMonths: lib.addMonths,
24
- subMonths: lib.subMonths,
25
- getYear: lib.getYear,
26
- setYear: lib.setYear,
27
- getMonth: lib.getMonth,
28
- setMonth: lib.setMonth,
29
- startOfYear: lib.startOfYear,
30
- eachMonthOfInterval: lib.eachMonthOfInterval,
31
- endOfYear: lib.endOfYear,
32
- addYears: lib.addYears,
33
- subYears: lib.subYears,
34
- isDate: lib.isDate,
35
- isWithinInterval: lib.isWithinInterval,
36
- };
37
- };
38
- export const getWeekDays = (type) => {
39
- if (type === 'jalali') {
40
- return ['ش', 'ی', 'د', 'س', 'چ', 'پ', 'ج'];
41
- }
42
- else {
43
- return ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
44
- }
45
- };