@lichta/react 2.0.1 → 2.2.0

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 CHANGED
@@ -1,6 +1,9 @@
1
1
  # @lichta/react
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@lichta/react.svg)](https://www.npmjs.com/package/@lichta/react)
3
+ [![@lichta/react](https://img.shields.io/npm/v/@lichta/react.svg?label=%40lichta%2Freact)](https://www.npmjs.com/package/@lichta/react)
4
+ [![@lichta/core](https://img.shields.io/npm/v/@lichta/core.svg?label=%40lichta%2Fcore)](https://www.npmjs.com/package/@lichta/core)
5
+
6
+ 🚀 **[Demo trực tiếp](https://lichta.zeneo.app/)**
4
7
 
5
8
  Component `Calendar` (lịch tháng có ngày âm lịch) cho React, đóng gói trên [`@lichta/core`](https://www.npmjs.com/package/@lichta/core).
6
9
 
@@ -55,13 +58,58 @@ export default function App() {
55
58
  | `year` | `number` | Năm hiện tại | Năm hiển thị ban đầu |
56
59
  | `showLunar` | `boolean` | `true` | Hiện ngày âm lịch dưới ngày dương |
57
60
  | `locale` | `'vi' \| 'en' \| 'ja' \| 'ko'` | `'vi'` | Ngôn ngữ nhãn thứ trong tuần |
61
+ | `firstDayOfWeek` | `0 \| 1` | `0` | Ngày bắt đầu tuần: `0` = Chủ Nhật, `1` = Thứ Hai |
62
+ | `showWeekNumber` | `boolean` | `false` | Hiện số tuần (ISO-8601) ở đầu mỗi hàng |
58
63
  | `theme` | `'classic' \| 'glass'` | `'classic'` | Giao diện lịch |
59
64
  | `className` | `string` | `''` | Class CSS bổ sung cho container |
60
65
  | `onSelect` | `(date: Date, lunar: LunarDate) => void` | — | Callback khi chọn ngày |
66
+ | `onMonthChange` | `(month: number, year: number) => void` | — | Callback khi điều hướng tháng qua nút prev/next trong header |
61
67
  | `renderDay` | `(cell: DayCellData) => React.ReactNode` | — | Custom render cho mỗi ô ngày |
62
68
  | `children` | `React.ReactNode` | — | Nội dung footer tùy chỉnh |
63
69
 
64
- > Lưu ý: `month`/`year` hiện chỉ dùng làm giá trị khởi tạo state nội bộ (uncontrolled) thay đổi prop sau khi mount chưa tự động cập nhật lại lịch đang hiển thị.
70
+ > `month`/`year` controlled prop component tự đồng bộ lại khi cha đổi giá trị. Kết hợp với `onMonthChange` để đồng bộ 2 chiều với 1 nguồn điều hướng khác (ví dụ FullCalendar).
71
+
72
+ ## DatePicker
73
+
74
+ Component `DatePicker` (input + popover chọn ngày, dựng trên `Calendar` ở trên).
75
+
76
+ ```tsx
77
+ import { DatePicker } from '@lichta/react';
78
+ import '@lichta/core/styles/calendar-base.css';
79
+ import '@lichta/core/styles/calendar-glass.css'; // theme "glass" (tùy chọn)
80
+ import '@lichta/core/styles/datepicker-base.css';
81
+ import '@lichta/core/styles/datepicker-glass.css'; // theme "glass" (tùy chọn)
82
+
83
+ export default function App() {
84
+ return (
85
+ <DatePicker
86
+ theme="glass"
87
+ locale="vi"
88
+ onSelect={(date, lunar) => console.log(date, lunar)}
89
+ />
90
+ );
91
+ }
92
+ ```
93
+
94
+ > ⚠️ Cần import cả CSS của `Calendar` (dùng trong popover) lẫn `DatePicker` (input + popover chrome) — thiếu 1 trong 2 sẽ hiển thị thiếu style.
95
+
96
+ ### Props
97
+
98
+ | Prop | Kiểu | Mặc định | Mô tả |
99
+ |---|---|---|---|
100
+ | `value` | `Date \| null` | `null` | Ngày đang chọn (uncontrolled — component tự quản lý sau lần mount đầu) |
101
+ | `onSelect` | `(date: Date, lunar: LunarDate) => void` | — | Callback khi chọn ngày trong popover |
102
+ | `onMonthChange` | `(month: number, year: number) => void` | — | Callback khi điều hướng tháng qua nút prev/next trong popover |
103
+ | `placeholder` | `string` | `'Chọn ngày'` | Placeholder khi chưa chọn ngày |
104
+ | `locale` | `'vi' \| 'en' \| 'ja' \| 'ko'` | `'vi'` | Ngôn ngữ hiển thị trong popover |
105
+ | `firstDayOfWeek` | `0 \| 1` | `0` | Ngày bắt đầu tuần: `0` = Chủ Nhật, `1` = Thứ Hai |
106
+ | `theme` | `'classic' \| 'glass'` | `'classic'` | Giao diện input + popover |
107
+ | `showLunar` | `boolean` | `true` | Hiện ngày âm lịch trong popover |
108
+ | `format` | `(date: Date) => string` | `dd/MM/yyyy` | Hàm format ngày hiển thị trên input |
109
+ | `disabled` | `boolean` | `false` | Vô hiệu hoá input |
110
+ | `className` | `string` | `''` | Class CSS bổ sung cho container |
111
+
112
+ Popover tự đóng khi: chọn xong 1 ngày, click ra ngoài, hoặc nhấn phím `Escape`.
65
113
 
66
114
  ## Theming
67
115
 
@@ -78,6 +126,8 @@ export default function App() {
78
126
  }
79
127
  ```
80
128
 
129
+ `DatePicker` dùng chung biến CSS `--lichta-*` ở trên — không cần custom class riêng.
130
+
81
131
  ## License
82
132
 
83
133
  [MIT](./LICENSE)
package/dist/index.d.mts CHANGED
@@ -1,24 +1,50 @@
1
1
  import React from 'react';
2
- import { LunarDate, Locale } from '@lichta/core';
2
+ import { LunarDate, Locale, FirstDayOfWeek, CalendarDayCell } from '@lichta/core';
3
3
 
4
4
  interface CalendarProps {
5
5
  month?: number;
6
6
  year?: number;
7
+ /** Ngày được chọn (controlled). Bỏ qua thì Calendar tự quản lý selection nội bộ (uncontrolled). */
8
+ selectedDate?: Date | null;
7
9
  onSelect?: (date: Date, lunar: LunarDate) => void;
10
+ /** Callback khi user điều hướng tháng qua nút prev/next trong header */
11
+ onMonthChange?: (month: number, year: number) => void;
8
12
  showLunar?: boolean;
9
13
  locale?: Locale;
14
+ /** Ngày bắt đầu tuần: 0 = Chủ Nhật (mặc định), 1 = Thứ Hai */
15
+ firstDayOfWeek?: FirstDayOfWeek;
16
+ /** Hiển thị số tuần (ISO-8601) ở đầu mỗi hàng */
17
+ showWeekNumber?: boolean;
10
18
  theme?: 'classic' | 'glass';
11
19
  className?: string;
12
20
  children?: React.ReactNode;
13
21
  renderDay?: (cell: DayCellData) => React.ReactNode;
14
22
  }
15
- interface DayCellData {
16
- solar: Date;
17
- lunar: LunarDate;
18
- isToday: boolean;
19
- isSelected: boolean;
20
- isCurrentMonth: boolean;
21
- }
23
+ type DayCellData = CalendarDayCell;
22
24
  declare const Calendar: React.FC<CalendarProps>;
23
25
 
24
- export { Calendar, type CalendarProps };
26
+ interface DatePickerProps {
27
+ /** Ngày đang chọn (uncontrolled — component tự quản lý sau lần mount đầu) */
28
+ value?: Date | null;
29
+ /** Gọi khi người dùng chọn 1 ngày trong popover */
30
+ onSelect?: (date: Date, lunar: LunarDate) => void;
31
+ /** Callback khi user điều hướng tháng qua nút prev/next trong popover */
32
+ onMonthChange?: (month: number, year: number) => void;
33
+ /** Placeholder khi chưa chọn ngày nào */
34
+ placeholder?: string;
35
+ /** Ngôn ngữ hiển thị Can Chi/thứ trong tuần */
36
+ locale?: Locale;
37
+ /** Ngày bắt đầu tuần: 0 = Chủ Nhật (mặc định), 1 = Thứ Hai */
38
+ firstDayOfWeek?: FirstDayOfWeek;
39
+ /** Theme cho input + popover */
40
+ theme?: 'classic' | 'glass';
41
+ /** Hiển thị ngày âm lịch trong popover */
42
+ showLunar?: boolean;
43
+ /** Hàm format ngày hiển thị trên input, mặc định dd/MM/yyyy */
44
+ format?: (date: Date) => string;
45
+ disabled?: boolean;
46
+ className?: string;
47
+ }
48
+ declare const DatePicker: React.FC<DatePickerProps>;
49
+
50
+ export { Calendar, type CalendarProps, DatePicker, type DatePickerProps };
package/dist/index.d.ts CHANGED
@@ -1,24 +1,50 @@
1
1
  import React from 'react';
2
- import { LunarDate, Locale } from '@lichta/core';
2
+ import { LunarDate, Locale, FirstDayOfWeek, CalendarDayCell } from '@lichta/core';
3
3
 
4
4
  interface CalendarProps {
5
5
  month?: number;
6
6
  year?: number;
7
+ /** Ngày được chọn (controlled). Bỏ qua thì Calendar tự quản lý selection nội bộ (uncontrolled). */
8
+ selectedDate?: Date | null;
7
9
  onSelect?: (date: Date, lunar: LunarDate) => void;
10
+ /** Callback khi user điều hướng tháng qua nút prev/next trong header */
11
+ onMonthChange?: (month: number, year: number) => void;
8
12
  showLunar?: boolean;
9
13
  locale?: Locale;
14
+ /** Ngày bắt đầu tuần: 0 = Chủ Nhật (mặc định), 1 = Thứ Hai */
15
+ firstDayOfWeek?: FirstDayOfWeek;
16
+ /** Hiển thị số tuần (ISO-8601) ở đầu mỗi hàng */
17
+ showWeekNumber?: boolean;
10
18
  theme?: 'classic' | 'glass';
11
19
  className?: string;
12
20
  children?: React.ReactNode;
13
21
  renderDay?: (cell: DayCellData) => React.ReactNode;
14
22
  }
15
- interface DayCellData {
16
- solar: Date;
17
- lunar: LunarDate;
18
- isToday: boolean;
19
- isSelected: boolean;
20
- isCurrentMonth: boolean;
21
- }
23
+ type DayCellData = CalendarDayCell;
22
24
  declare const Calendar: React.FC<CalendarProps>;
23
25
 
24
- export { Calendar, type CalendarProps };
26
+ interface DatePickerProps {
27
+ /** Ngày đang chọn (uncontrolled — component tự quản lý sau lần mount đầu) */
28
+ value?: Date | null;
29
+ /** Gọi khi người dùng chọn 1 ngày trong popover */
30
+ onSelect?: (date: Date, lunar: LunarDate) => void;
31
+ /** Callback khi user điều hướng tháng qua nút prev/next trong popover */
32
+ onMonthChange?: (month: number, year: number) => void;
33
+ /** Placeholder khi chưa chọn ngày nào */
34
+ placeholder?: string;
35
+ /** Ngôn ngữ hiển thị Can Chi/thứ trong tuần */
36
+ locale?: Locale;
37
+ /** Ngày bắt đầu tuần: 0 = Chủ Nhật (mặc định), 1 = Thứ Hai */
38
+ firstDayOfWeek?: FirstDayOfWeek;
39
+ /** Theme cho input + popover */
40
+ theme?: 'classic' | 'glass';
41
+ /** Hiển thị ngày âm lịch trong popover */
42
+ showLunar?: boolean;
43
+ /** Hàm format ngày hiển thị trên input, mặc định dd/MM/yyyy */
44
+ format?: (date: Date) => string;
45
+ disabled?: boolean;
46
+ className?: string;
47
+ }
48
+ declare const DatePicker: React.FC<DatePickerProps>;
49
+
50
+ export { Calendar, type CalendarProps, DatePicker, type DatePickerProps };
package/dist/index.js CHANGED
@@ -30,7 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- Calendar: () => Calendar
33
+ Calendar: () => Calendar,
34
+ DatePicker: () => DatePicker
34
35
  });
35
36
  module.exports = __toCommonJS(index_exports);
36
37
 
@@ -38,32 +39,16 @@ module.exports = __toCommonJS(index_exports);
38
39
  var import_react = __toESM(require("react"));
39
40
  var import_core = require("@lichta/core");
40
41
  var import_jsx_runtime = require("react/jsx-runtime");
41
- var monthNames = [
42
- "Th\xE1ng 1",
43
- "Th\xE1ng 2",
44
- "Th\xE1ng 3",
45
- "Th\xE1ng 4",
46
- "Th\xE1ng 5",
47
- "Th\xE1ng 6",
48
- "Th\xE1ng 7",
49
- "Th\xE1ng 8",
50
- "Th\xE1ng 9",
51
- "Th\xE1ng 10",
52
- "Th\xE1ng 11",
53
- "Th\xE1ng 12"
54
- ];
55
- var weekDayLabelsByLocale = {
56
- vi: ["CN", "T2", "T3", "T4", "T5", "T6", "T7"],
57
- en: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
58
- ja: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
59
- ko: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
60
- };
61
42
  var Calendar = ({
62
43
  month = (/* @__PURE__ */ new Date()).getMonth() + 1,
63
44
  year = (/* @__PURE__ */ new Date()).getFullYear(),
45
+ selectedDate: selectedDateProp,
64
46
  onSelect,
47
+ onMonthChange,
65
48
  showLunar = true,
66
49
  locale = "vi",
50
+ firstDayOfWeek = 0,
51
+ showWeekNumber = false,
67
52
  theme = "classic",
68
53
  className = "",
69
54
  children,
@@ -71,84 +56,39 @@ var Calendar = ({
71
56
  }) => {
72
57
  const [currentMonth, setCurrentMonth] = (0, import_react.useState)(month);
73
58
  const [currentYear, setCurrentYear] = (0, import_react.useState)(year);
74
- const [selectedDate, setSelectedDate] = (0, import_react.useState)(null);
59
+ const [internalSelectedDate, setInternalSelectedDate] = (0, import_react.useState)(null);
60
+ const selectedDate = selectedDateProp !== void 0 ? selectedDateProp : internalSelectedDate;
61
+ (0, import_react.useEffect)(() => {
62
+ setCurrentMonth(month);
63
+ }, [month]);
64
+ (0, import_react.useEffect)(() => {
65
+ setCurrentYear(year);
66
+ }, [year]);
75
67
  const yearInfo = (0, import_react.useMemo)(() => (0, import_core.getYearDetails)(currentYear), [currentYear]);
76
- const weekDayLabels = weekDayLabelsByLocale[locale];
77
- const calendarGrid = (0, import_react.useMemo)(() => {
78
- const grid = [];
79
- const firstDay = new Date(currentYear, currentMonth - 1, 1);
80
- const lastDay = new Date(currentYear, currentMonth, 0);
81
- const startingDayOfWeek = firstDay.getDay();
82
- const daysInMonth = lastDay.getDate();
83
- const today = /* @__PURE__ */ new Date();
84
- const todayStr = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`;
85
- let selStr = "";
86
- if (selectedDate) {
87
- selStr = `${selectedDate.getFullYear()}-${selectedDate.getMonth() + 1}-${selectedDate.getDate()}`;
88
- }
89
- const prevMonthLastDay = new Date(currentYear, currentMonth - 1, 0).getDate();
90
- for (let i = startingDayOfWeek - 1; i >= 0; i--) {
91
- const d = prevMonthLastDay - i;
92
- const m = currentMonth - 1 < 1 ? 12 : currentMonth - 1;
93
- const y = currentMonth - 1 < 1 ? currentYear - 1 : currentYear;
94
- const solar = new Date(y, m - 1, d);
95
- const lunar = import_core.LichTa.toLunar(d, m, y);
96
- const dateStr = `${y}-${m}-${d}`;
97
- grid.push({
98
- solar,
99
- lunar,
100
- isToday: dateStr === todayStr,
101
- isSelected: dateStr === selStr,
102
- isCurrentMonth: false
103
- });
104
- }
105
- for (let d = 1; d <= daysInMonth; d++) {
106
- const solar = new Date(currentYear, currentMonth - 1, d);
107
- const lunar = import_core.LichTa.toLunar(d, currentMonth, currentYear);
108
- const dateStr = `${currentYear}-${currentMonth}-${d}`;
109
- grid.push({
110
- solar,
111
- lunar,
112
- isToday: dateStr === todayStr,
113
- isSelected: dateStr === selStr,
114
- isCurrentMonth: true
115
- });
116
- }
117
- const remaining = 42 - grid.length;
118
- for (let d = 1; d <= remaining; d++) {
119
- const m = currentMonth + 1 > 12 ? 1 : currentMonth + 1;
120
- const y = currentMonth + 1 > 12 ? currentYear + 1 : currentYear;
121
- const solar = new Date(y, m - 1, d);
122
- const lunar = import_core.LichTa.toLunar(d, m, y);
123
- const dateStr = `${y}-${m}-${d}`;
124
- grid.push({
125
- solar,
126
- lunar,
127
- isToday: dateStr === todayStr,
128
- isSelected: dateStr === selStr,
129
- isCurrentMonth: false
130
- });
131
- }
132
- return grid;
133
- }, [currentMonth, currentYear, selectedDate]);
68
+ const { solarMonthNames: monthNames } = (0, import_core.t)(locale);
69
+ const weekDayLabels = (0, import_react.useMemo)(() => (0, import_core.getWeekDayLabels)(locale, firstDayOfWeek), [locale, firstDayOfWeek]);
70
+ const calendarGrid = (0, import_react.useMemo)(
71
+ () => (0, import_core.getCalendarGrid)(currentMonth, currentYear, selectedDate, firstDayOfWeek),
72
+ [currentMonth, currentYear, selectedDate, firstDayOfWeek]
73
+ );
134
74
  const prevMonth = () => {
135
- if (currentMonth === 1) {
136
- setCurrentMonth(12);
137
- setCurrentYear((y) => y - 1);
138
- } else {
139
- setCurrentMonth((m) => m - 1);
140
- }
75
+ const newMonth = currentMonth === 1 ? 12 : currentMonth - 1;
76
+ const newYear = currentMonth === 1 ? currentYear - 1 : currentYear;
77
+ setCurrentMonth(newMonth);
78
+ setCurrentYear(newYear);
79
+ onMonthChange?.(newMonth, newYear);
141
80
  };
142
81
  const nextMonth = () => {
143
- if (currentMonth === 12) {
144
- setCurrentMonth(1);
145
- setCurrentYear((y) => y + 1);
146
- } else {
147
- setCurrentMonth((m) => m + 1);
148
- }
82
+ const newMonth = currentMonth === 12 ? 1 : currentMonth + 1;
83
+ const newYear = currentMonth === 12 ? currentYear + 1 : currentYear;
84
+ setCurrentMonth(newMonth);
85
+ setCurrentYear(newYear);
86
+ onMonthChange?.(newMonth, newYear);
149
87
  };
150
88
  const handleDayClick = (cell) => {
151
- setSelectedDate(cell.solar);
89
+ if (selectedDateProp === void 0) {
90
+ setInternalSelectedDate(cell.solar);
91
+ }
152
92
  if (onSelect) {
153
93
  onSelect(cell.solar, cell.lunar);
154
94
  }
@@ -159,7 +99,7 @@ var Calendar = ({
159
99
  handleDayClick(cell);
160
100
  }
161
101
  };
162
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `lich-ta-calendar lichta-theme-${theme} ${className}`, children: [
102
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `lich-ta-calendar lichta-theme-${theme} ${showWeekNumber ? "lich-ta-calendar--with-week-number" : ""} ${className}`, children: [
163
103
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lich-ta-calendar-header", children: [
164
104
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lich-ta-calendar-nav", onClick: prevMonth, "aria-label": "Th\xE1ng tr\u01B0\u1EDBc", children: "\u25C0" }),
165
105
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lich-ta-calendar-title", children: [
@@ -176,35 +116,200 @@ var Calendar = ({
176
116
  ] }),
177
117
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lich-ta-calendar-nav", onClick: nextMonth, "aria-label": "Th\xE1ng sau", children: "\u25B6" })
178
118
  ] }),
179
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lich-ta-calendar-weekdays", children: weekDayLabels.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lich-ta-calendar-weekday", children: label }, label)) }),
119
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lich-ta-calendar-weekdays", children: [
120
+ showWeekNumber && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lich-ta-calendar-week-number", "aria-hidden": "true" }),
121
+ weekDayLabels.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lich-ta-calendar-weekday", children: label }, label))
122
+ ] }),
180
123
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lich-ta-calendar-grid", children: calendarGrid.map((cell, idx) => {
124
+ const weekNumberCell = showWeekNumber && idx % 7 === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lich-ta-calendar-week-number", children: cell.weekNumber }, `wn-${idx}`) : null;
181
125
  if (renderDay) {
182
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.default.Fragment, { children: renderDay(cell) }, idx);
126
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.default.Fragment, { children: [
127
+ weekNumberCell,
128
+ renderDay(cell)
129
+ ] }, idx);
183
130
  }
184
131
  let classes = "lich-ta-calendar-day";
185
132
  if (cell.isToday) classes += " is-today";
186
133
  if (cell.isSelected) classes += " is-selected";
187
134
  if (!cell.isCurrentMonth) classes += " is-other-month";
188
135
  if (cell.lunar.day === 1) classes += " is-first-lunar";
189
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
190
- "button",
191
- {
192
- className: classes,
193
- onClick: () => handleDayClick(cell),
194
- onKeyDown: (e) => handleKeydown(e, cell),
195
- tabIndex: cell.isCurrentMonth ? 0 : -1,
196
- children: [
197
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "solar-day", children: cell.solar.getDate() }),
198
- showLunar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "lunar-day", children: cell.lunar.day === 1 ? `${cell.lunar.day}/${cell.lunar.month}${cell.lunar.isLeap ? "*" : ""}` : cell.lunar.day })
199
- ]
200
- },
201
- idx
202
- );
136
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.default.Fragment, { children: [
137
+ weekNumberCell,
138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
139
+ "button",
140
+ {
141
+ className: classes,
142
+ onClick: () => handleDayClick(cell),
143
+ onKeyDown: (e) => handleKeydown(e, cell),
144
+ tabIndex: cell.isCurrentMonth ? 0 : -1,
145
+ children: [
146
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "solar-day", children: cell.solar.getDate() }),
147
+ showLunar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "lunar-day", children: cell.lunar.day === 1 ? `${cell.lunar.day}/${cell.lunar.month}${cell.lunar.isLeap ? "*" : ""}` : cell.lunar.day })
148
+ ]
149
+ }
150
+ )
151
+ ] }, idx);
203
152
  }) }),
204
153
  children && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lich-ta-calendar-footer", children })
205
154
  ] });
206
155
  };
156
+
157
+ // src/DatePicker.tsx
158
+ var import_react2 = require("react");
159
+ var import_core2 = require("@lichta/core");
160
+ var import_jsx_runtime2 = require("react/jsx-runtime");
161
+ function defaultFormat(date) {
162
+ const dd = String(date.getDate()).padStart(2, "0");
163
+ const mm = String(date.getMonth() + 1).padStart(2, "0");
164
+ return `${dd}/${mm}/${date.getFullYear()}`;
165
+ }
166
+ var DatePicker = ({
167
+ value = null,
168
+ onSelect,
169
+ onMonthChange,
170
+ placeholder = "Ch\u1ECDn ng\xE0y",
171
+ locale = "vi",
172
+ firstDayOfWeek = 0,
173
+ theme = "classic",
174
+ showLunar = true,
175
+ format = defaultFormat,
176
+ disabled = false,
177
+ className = ""
178
+ }) => {
179
+ const [selected, setSelected] = (0, import_react2.useState)(value);
180
+ const [isOpen, setIsOpen] = (0, import_react2.useState)(false);
181
+ const anchor = selected ?? /* @__PURE__ */ new Date();
182
+ const [viewMonth, setViewMonth] = (0, import_react2.useState)(anchor.getMonth() + 1);
183
+ const [viewYear, setViewYear] = (0, import_react2.useState)(anchor.getFullYear());
184
+ const rootRef = (0, import_react2.useRef)(null);
185
+ (0, import_react2.useEffect)(() => {
186
+ const next = value ?? null;
187
+ setSelected(next);
188
+ if (next) {
189
+ setViewMonth(next.getMonth() + 1);
190
+ setViewYear(next.getFullYear());
191
+ }
192
+ }, [value]);
193
+ (0, import_react2.useEffect)(() => {
194
+ if (!isOpen) return;
195
+ function handlePointerDown(event) {
196
+ if (rootRef.current && !rootRef.current.contains(event.target)) {
197
+ setIsOpen(false);
198
+ }
199
+ }
200
+ function handleKeydown(event) {
201
+ if (event.key === "Escape") setIsOpen(false);
202
+ }
203
+ document.addEventListener("mousedown", handlePointerDown);
204
+ document.addEventListener("keydown", handleKeydown);
205
+ return () => {
206
+ document.removeEventListener("mousedown", handlePointerDown);
207
+ document.removeEventListener("keydown", handleKeydown);
208
+ };
209
+ }, [isOpen]);
210
+ const yearInfo = (0, import_react2.useMemo)(() => (0, import_core2.getYearDetails)(viewYear), [viewYear]);
211
+ const { solarMonthNames: monthNames } = (0, import_core2.t)(locale);
212
+ const weekDayLabels = (0, import_react2.useMemo)(() => (0, import_core2.getWeekDayLabels)(locale, firstDayOfWeek), [locale, firstDayOfWeek]);
213
+ const grid = (0, import_react2.useMemo)(
214
+ () => (0, import_core2.getCalendarGrid)(viewMonth, viewYear, selected, firstDayOfWeek),
215
+ [viewMonth, viewYear, selected, firstDayOfWeek]
216
+ );
217
+ function prevMonth() {
218
+ const newMonth = viewMonth === 1 ? 12 : viewMonth - 1;
219
+ const newYear = viewMonth === 1 ? viewYear - 1 : viewYear;
220
+ setViewMonth(newMonth);
221
+ setViewYear(newYear);
222
+ onMonthChange?.(newMonth, newYear);
223
+ }
224
+ function nextMonth() {
225
+ const newMonth = viewMonth === 12 ? 1 : viewMonth + 1;
226
+ const newYear = viewMonth === 12 ? viewYear + 1 : viewYear;
227
+ setViewMonth(newMonth);
228
+ setViewYear(newYear);
229
+ onMonthChange?.(newMonth, newYear);
230
+ }
231
+ function handleDaySelect(cell) {
232
+ setSelected(cell.solar);
233
+ setIsOpen(false);
234
+ onSelect?.(cell.solar, cell.lunar);
235
+ }
236
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref: rootRef, className: `lich-ta-datepicker lichta-theme-${theme} ${className}`, children: [
237
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
238
+ "input",
239
+ {
240
+ type: "text",
241
+ className: "lich-ta-datepicker-input",
242
+ readOnly: true,
243
+ disabled,
244
+ placeholder,
245
+ value: selected ? format(selected) : "",
246
+ "aria-haspopup": "dialog",
247
+ "aria-expanded": isOpen,
248
+ onClick: () => !disabled && setIsOpen((open) => !open)
249
+ }
250
+ ),
251
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "lich-ta-datepicker-popover", role: "dialog", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "lich-ta-datepicker-calendar", children: [
252
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "lich-ta-datepicker-calendar-header", children: [
253
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
254
+ "button",
255
+ {
256
+ type: "button",
257
+ className: "lich-ta-datepicker-calendar-nav",
258
+ onClick: prevMonth,
259
+ "aria-label": "Th\xE1ng tr\u01B0\u1EDBc",
260
+ children: "\u25C0"
261
+ }
262
+ ),
263
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "lich-ta-datepicker-calendar-title", children: [
264
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "lich-ta-datepicker-calendar-month-year", children: [
265
+ monthNames[viewMonth - 1],
266
+ ", ",
267
+ viewYear
268
+ ] }),
269
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "lich-ta-datepicker-calendar-canchi", children: [
270
+ yearInfo.can,
271
+ " ",
272
+ yearInfo.chi
273
+ ] })
274
+ ] }),
275
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
276
+ "button",
277
+ {
278
+ type: "button",
279
+ className: "lich-ta-datepicker-calendar-nav",
280
+ onClick: nextMonth,
281
+ "aria-label": "Th\xE1ng sau",
282
+ children: "\u25B6"
283
+ }
284
+ )
285
+ ] }),
286
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "lich-ta-datepicker-calendar-weekdays", children: weekDayLabels.map((label) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "lich-ta-datepicker-calendar-weekday", children: label }, label)) }),
287
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "lich-ta-datepicker-calendar-grid", children: grid.map((cell, idx) => {
288
+ let classes = "lich-ta-datepicker-calendar-day";
289
+ if (cell.isToday) classes += " is-today";
290
+ if (cell.isSelected) classes += " is-selected";
291
+ if (!cell.isCurrentMonth) classes += " is-other-month";
292
+ if (cell.lunar.day === 1) classes += " is-first-lunar";
293
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
294
+ "button",
295
+ {
296
+ type: "button",
297
+ className: classes,
298
+ onClick: () => handleDaySelect(cell),
299
+ tabIndex: cell.isCurrentMonth ? 0 : -1,
300
+ children: [
301
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "solar-day", children: cell.solar.getDate() }),
302
+ showLunar && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "lunar-day", children: cell.lunar.day === 1 ? `${cell.lunar.day}/${cell.lunar.month}${cell.lunar.isLeap ? "*" : ""}` : cell.lunar.day })
303
+ ]
304
+ },
305
+ idx
306
+ );
307
+ }) })
308
+ ] }) })
309
+ ] });
310
+ };
207
311
  // Annotate the CommonJS export names for ESM import in node:
208
312
  0 && (module.exports = {
209
- Calendar
313
+ Calendar,
314
+ DatePicker
210
315
  });
package/dist/index.mjs CHANGED
@@ -1,33 +1,17 @@
1
1
  // src/Calendar.tsx
2
- import React, { useState, useMemo } from "react";
3
- import { LichTa, getYearDetails } from "@lichta/core";
2
+ import React, { useState, useMemo, useEffect } from "react";
3
+ import { getYearDetails, getCalendarGrid, getWeekDayLabels, t } from "@lichta/core";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
- var monthNames = [
6
- "Th\xE1ng 1",
7
- "Th\xE1ng 2",
8
- "Th\xE1ng 3",
9
- "Th\xE1ng 4",
10
- "Th\xE1ng 5",
11
- "Th\xE1ng 6",
12
- "Th\xE1ng 7",
13
- "Th\xE1ng 8",
14
- "Th\xE1ng 9",
15
- "Th\xE1ng 10",
16
- "Th\xE1ng 11",
17
- "Th\xE1ng 12"
18
- ];
19
- var weekDayLabelsByLocale = {
20
- vi: ["CN", "T2", "T3", "T4", "T5", "T6", "T7"],
21
- en: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
22
- ja: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
23
- ko: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
24
- };
25
5
  var Calendar = ({
26
6
  month = (/* @__PURE__ */ new Date()).getMonth() + 1,
27
7
  year = (/* @__PURE__ */ new Date()).getFullYear(),
8
+ selectedDate: selectedDateProp,
28
9
  onSelect,
10
+ onMonthChange,
29
11
  showLunar = true,
30
12
  locale = "vi",
13
+ firstDayOfWeek = 0,
14
+ showWeekNumber = false,
31
15
  theme = "classic",
32
16
  className = "",
33
17
  children,
@@ -35,84 +19,39 @@ var Calendar = ({
35
19
  }) => {
36
20
  const [currentMonth, setCurrentMonth] = useState(month);
37
21
  const [currentYear, setCurrentYear] = useState(year);
38
- const [selectedDate, setSelectedDate] = useState(null);
22
+ const [internalSelectedDate, setInternalSelectedDate] = useState(null);
23
+ const selectedDate = selectedDateProp !== void 0 ? selectedDateProp : internalSelectedDate;
24
+ useEffect(() => {
25
+ setCurrentMonth(month);
26
+ }, [month]);
27
+ useEffect(() => {
28
+ setCurrentYear(year);
29
+ }, [year]);
39
30
  const yearInfo = useMemo(() => getYearDetails(currentYear), [currentYear]);
40
- const weekDayLabels = weekDayLabelsByLocale[locale];
41
- const calendarGrid = useMemo(() => {
42
- const grid = [];
43
- const firstDay = new Date(currentYear, currentMonth - 1, 1);
44
- const lastDay = new Date(currentYear, currentMonth, 0);
45
- const startingDayOfWeek = firstDay.getDay();
46
- const daysInMonth = lastDay.getDate();
47
- const today = /* @__PURE__ */ new Date();
48
- const todayStr = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`;
49
- let selStr = "";
50
- if (selectedDate) {
51
- selStr = `${selectedDate.getFullYear()}-${selectedDate.getMonth() + 1}-${selectedDate.getDate()}`;
52
- }
53
- const prevMonthLastDay = new Date(currentYear, currentMonth - 1, 0).getDate();
54
- for (let i = startingDayOfWeek - 1; i >= 0; i--) {
55
- const d = prevMonthLastDay - i;
56
- const m = currentMonth - 1 < 1 ? 12 : currentMonth - 1;
57
- const y = currentMonth - 1 < 1 ? currentYear - 1 : currentYear;
58
- const solar = new Date(y, m - 1, d);
59
- const lunar = LichTa.toLunar(d, m, y);
60
- const dateStr = `${y}-${m}-${d}`;
61
- grid.push({
62
- solar,
63
- lunar,
64
- isToday: dateStr === todayStr,
65
- isSelected: dateStr === selStr,
66
- isCurrentMonth: false
67
- });
68
- }
69
- for (let d = 1; d <= daysInMonth; d++) {
70
- const solar = new Date(currentYear, currentMonth - 1, d);
71
- const lunar = LichTa.toLunar(d, currentMonth, currentYear);
72
- const dateStr = `${currentYear}-${currentMonth}-${d}`;
73
- grid.push({
74
- solar,
75
- lunar,
76
- isToday: dateStr === todayStr,
77
- isSelected: dateStr === selStr,
78
- isCurrentMonth: true
79
- });
80
- }
81
- const remaining = 42 - grid.length;
82
- for (let d = 1; d <= remaining; d++) {
83
- const m = currentMonth + 1 > 12 ? 1 : currentMonth + 1;
84
- const y = currentMonth + 1 > 12 ? currentYear + 1 : currentYear;
85
- const solar = new Date(y, m - 1, d);
86
- const lunar = LichTa.toLunar(d, m, y);
87
- const dateStr = `${y}-${m}-${d}`;
88
- grid.push({
89
- solar,
90
- lunar,
91
- isToday: dateStr === todayStr,
92
- isSelected: dateStr === selStr,
93
- isCurrentMonth: false
94
- });
95
- }
96
- return grid;
97
- }, [currentMonth, currentYear, selectedDate]);
31
+ const { solarMonthNames: monthNames } = t(locale);
32
+ const weekDayLabels = useMemo(() => getWeekDayLabels(locale, firstDayOfWeek), [locale, firstDayOfWeek]);
33
+ const calendarGrid = useMemo(
34
+ () => getCalendarGrid(currentMonth, currentYear, selectedDate, firstDayOfWeek),
35
+ [currentMonth, currentYear, selectedDate, firstDayOfWeek]
36
+ );
98
37
  const prevMonth = () => {
99
- if (currentMonth === 1) {
100
- setCurrentMonth(12);
101
- setCurrentYear((y) => y - 1);
102
- } else {
103
- setCurrentMonth((m) => m - 1);
104
- }
38
+ const newMonth = currentMonth === 1 ? 12 : currentMonth - 1;
39
+ const newYear = currentMonth === 1 ? currentYear - 1 : currentYear;
40
+ setCurrentMonth(newMonth);
41
+ setCurrentYear(newYear);
42
+ onMonthChange?.(newMonth, newYear);
105
43
  };
106
44
  const nextMonth = () => {
107
- if (currentMonth === 12) {
108
- setCurrentMonth(1);
109
- setCurrentYear((y) => y + 1);
110
- } else {
111
- setCurrentMonth((m) => m + 1);
112
- }
45
+ const newMonth = currentMonth === 12 ? 1 : currentMonth + 1;
46
+ const newYear = currentMonth === 12 ? currentYear + 1 : currentYear;
47
+ setCurrentMonth(newMonth);
48
+ setCurrentYear(newYear);
49
+ onMonthChange?.(newMonth, newYear);
113
50
  };
114
51
  const handleDayClick = (cell) => {
115
- setSelectedDate(cell.solar);
52
+ if (selectedDateProp === void 0) {
53
+ setInternalSelectedDate(cell.solar);
54
+ }
116
55
  if (onSelect) {
117
56
  onSelect(cell.solar, cell.lunar);
118
57
  }
@@ -123,7 +62,7 @@ var Calendar = ({
123
62
  handleDayClick(cell);
124
63
  }
125
64
  };
126
- return /* @__PURE__ */ jsxs("div", { className: `lich-ta-calendar lichta-theme-${theme} ${className}`, children: [
65
+ return /* @__PURE__ */ jsxs("div", { className: `lich-ta-calendar lichta-theme-${theme} ${showWeekNumber ? "lich-ta-calendar--with-week-number" : ""} ${className}`, children: [
127
66
  /* @__PURE__ */ jsxs("div", { className: "lich-ta-calendar-header", children: [
128
67
  /* @__PURE__ */ jsx("button", { className: "lich-ta-calendar-nav", onClick: prevMonth, "aria-label": "Th\xE1ng tr\u01B0\u1EDBc", children: "\u25C0" }),
129
68
  /* @__PURE__ */ jsxs("div", { className: "lich-ta-calendar-title", children: [
@@ -140,34 +79,199 @@ var Calendar = ({
140
79
  ] }),
141
80
  /* @__PURE__ */ jsx("button", { className: "lich-ta-calendar-nav", onClick: nextMonth, "aria-label": "Th\xE1ng sau", children: "\u25B6" })
142
81
  ] }),
143
- /* @__PURE__ */ jsx("div", { className: "lich-ta-calendar-weekdays", children: weekDayLabels.map((label) => /* @__PURE__ */ jsx("div", { className: "lich-ta-calendar-weekday", children: label }, label)) }),
82
+ /* @__PURE__ */ jsxs("div", { className: "lich-ta-calendar-weekdays", children: [
83
+ showWeekNumber && /* @__PURE__ */ jsx("div", { className: "lich-ta-calendar-week-number", "aria-hidden": "true" }),
84
+ weekDayLabels.map((label) => /* @__PURE__ */ jsx("div", { className: "lich-ta-calendar-weekday", children: label }, label))
85
+ ] }),
144
86
  /* @__PURE__ */ jsx("div", { className: "lich-ta-calendar-grid", children: calendarGrid.map((cell, idx) => {
87
+ const weekNumberCell = showWeekNumber && idx % 7 === 0 ? /* @__PURE__ */ jsx("div", { className: "lich-ta-calendar-week-number", children: cell.weekNumber }, `wn-${idx}`) : null;
145
88
  if (renderDay) {
146
- return /* @__PURE__ */ jsx(React.Fragment, { children: renderDay(cell) }, idx);
89
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
90
+ weekNumberCell,
91
+ renderDay(cell)
92
+ ] }, idx);
147
93
  }
148
94
  let classes = "lich-ta-calendar-day";
149
95
  if (cell.isToday) classes += " is-today";
150
96
  if (cell.isSelected) classes += " is-selected";
151
97
  if (!cell.isCurrentMonth) classes += " is-other-month";
152
98
  if (cell.lunar.day === 1) classes += " is-first-lunar";
153
- return /* @__PURE__ */ jsxs(
154
- "button",
155
- {
156
- className: classes,
157
- onClick: () => handleDayClick(cell),
158
- onKeyDown: (e) => handleKeydown(e, cell),
159
- tabIndex: cell.isCurrentMonth ? 0 : -1,
160
- children: [
161
- /* @__PURE__ */ jsx("span", { className: "solar-day", children: cell.solar.getDate() }),
162
- showLunar && /* @__PURE__ */ jsx("span", { className: "lunar-day", children: cell.lunar.day === 1 ? `${cell.lunar.day}/${cell.lunar.month}${cell.lunar.isLeap ? "*" : ""}` : cell.lunar.day })
163
- ]
164
- },
165
- idx
166
- );
99
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
100
+ weekNumberCell,
101
+ /* @__PURE__ */ jsxs(
102
+ "button",
103
+ {
104
+ className: classes,
105
+ onClick: () => handleDayClick(cell),
106
+ onKeyDown: (e) => handleKeydown(e, cell),
107
+ tabIndex: cell.isCurrentMonth ? 0 : -1,
108
+ children: [
109
+ /* @__PURE__ */ jsx("span", { className: "solar-day", children: cell.solar.getDate() }),
110
+ showLunar && /* @__PURE__ */ jsx("span", { className: "lunar-day", children: cell.lunar.day === 1 ? `${cell.lunar.day}/${cell.lunar.month}${cell.lunar.isLeap ? "*" : ""}` : cell.lunar.day })
111
+ ]
112
+ }
113
+ )
114
+ ] }, idx);
167
115
  }) }),
168
116
  children && /* @__PURE__ */ jsx("div", { className: "lich-ta-calendar-footer", children })
169
117
  ] });
170
118
  };
119
+
120
+ // src/DatePicker.tsx
121
+ import { useState as useState2, useRef, useEffect as useEffect2, useMemo as useMemo2 } from "react";
122
+ import { getYearDetails as getYearDetails2, getCalendarGrid as getCalendarGrid2, getWeekDayLabels as getWeekDayLabels2, t as t2 } from "@lichta/core";
123
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
124
+ function defaultFormat(date) {
125
+ const dd = String(date.getDate()).padStart(2, "0");
126
+ const mm = String(date.getMonth() + 1).padStart(2, "0");
127
+ return `${dd}/${mm}/${date.getFullYear()}`;
128
+ }
129
+ var DatePicker = ({
130
+ value = null,
131
+ onSelect,
132
+ onMonthChange,
133
+ placeholder = "Ch\u1ECDn ng\xE0y",
134
+ locale = "vi",
135
+ firstDayOfWeek = 0,
136
+ theme = "classic",
137
+ showLunar = true,
138
+ format = defaultFormat,
139
+ disabled = false,
140
+ className = ""
141
+ }) => {
142
+ const [selected, setSelected] = useState2(value);
143
+ const [isOpen, setIsOpen] = useState2(false);
144
+ const anchor = selected ?? /* @__PURE__ */ new Date();
145
+ const [viewMonth, setViewMonth] = useState2(anchor.getMonth() + 1);
146
+ const [viewYear, setViewYear] = useState2(anchor.getFullYear());
147
+ const rootRef = useRef(null);
148
+ useEffect2(() => {
149
+ const next = value ?? null;
150
+ setSelected(next);
151
+ if (next) {
152
+ setViewMonth(next.getMonth() + 1);
153
+ setViewYear(next.getFullYear());
154
+ }
155
+ }, [value]);
156
+ useEffect2(() => {
157
+ if (!isOpen) return;
158
+ function handlePointerDown(event) {
159
+ if (rootRef.current && !rootRef.current.contains(event.target)) {
160
+ setIsOpen(false);
161
+ }
162
+ }
163
+ function handleKeydown(event) {
164
+ if (event.key === "Escape") setIsOpen(false);
165
+ }
166
+ document.addEventListener("mousedown", handlePointerDown);
167
+ document.addEventListener("keydown", handleKeydown);
168
+ return () => {
169
+ document.removeEventListener("mousedown", handlePointerDown);
170
+ document.removeEventListener("keydown", handleKeydown);
171
+ };
172
+ }, [isOpen]);
173
+ const yearInfo = useMemo2(() => getYearDetails2(viewYear), [viewYear]);
174
+ const { solarMonthNames: monthNames } = t2(locale);
175
+ const weekDayLabels = useMemo2(() => getWeekDayLabels2(locale, firstDayOfWeek), [locale, firstDayOfWeek]);
176
+ const grid = useMemo2(
177
+ () => getCalendarGrid2(viewMonth, viewYear, selected, firstDayOfWeek),
178
+ [viewMonth, viewYear, selected, firstDayOfWeek]
179
+ );
180
+ function prevMonth() {
181
+ const newMonth = viewMonth === 1 ? 12 : viewMonth - 1;
182
+ const newYear = viewMonth === 1 ? viewYear - 1 : viewYear;
183
+ setViewMonth(newMonth);
184
+ setViewYear(newYear);
185
+ onMonthChange?.(newMonth, newYear);
186
+ }
187
+ function nextMonth() {
188
+ const newMonth = viewMonth === 12 ? 1 : viewMonth + 1;
189
+ const newYear = viewMonth === 12 ? viewYear + 1 : viewYear;
190
+ setViewMonth(newMonth);
191
+ setViewYear(newYear);
192
+ onMonthChange?.(newMonth, newYear);
193
+ }
194
+ function handleDaySelect(cell) {
195
+ setSelected(cell.solar);
196
+ setIsOpen(false);
197
+ onSelect?.(cell.solar, cell.lunar);
198
+ }
199
+ return /* @__PURE__ */ jsxs2("div", { ref: rootRef, className: `lich-ta-datepicker lichta-theme-${theme} ${className}`, children: [
200
+ /* @__PURE__ */ jsx2(
201
+ "input",
202
+ {
203
+ type: "text",
204
+ className: "lich-ta-datepicker-input",
205
+ readOnly: true,
206
+ disabled,
207
+ placeholder,
208
+ value: selected ? format(selected) : "",
209
+ "aria-haspopup": "dialog",
210
+ "aria-expanded": isOpen,
211
+ onClick: () => !disabled && setIsOpen((open) => !open)
212
+ }
213
+ ),
214
+ isOpen && /* @__PURE__ */ jsx2("div", { className: "lich-ta-datepicker-popover", role: "dialog", children: /* @__PURE__ */ jsxs2("div", { className: "lich-ta-datepicker-calendar", children: [
215
+ /* @__PURE__ */ jsxs2("div", { className: "lich-ta-datepicker-calendar-header", children: [
216
+ /* @__PURE__ */ jsx2(
217
+ "button",
218
+ {
219
+ type: "button",
220
+ className: "lich-ta-datepicker-calendar-nav",
221
+ onClick: prevMonth,
222
+ "aria-label": "Th\xE1ng tr\u01B0\u1EDBc",
223
+ children: "\u25C0"
224
+ }
225
+ ),
226
+ /* @__PURE__ */ jsxs2("div", { className: "lich-ta-datepicker-calendar-title", children: [
227
+ /* @__PURE__ */ jsxs2("span", { className: "lich-ta-datepicker-calendar-month-year", children: [
228
+ monthNames[viewMonth - 1],
229
+ ", ",
230
+ viewYear
231
+ ] }),
232
+ /* @__PURE__ */ jsxs2("span", { className: "lich-ta-datepicker-calendar-canchi", children: [
233
+ yearInfo.can,
234
+ " ",
235
+ yearInfo.chi
236
+ ] })
237
+ ] }),
238
+ /* @__PURE__ */ jsx2(
239
+ "button",
240
+ {
241
+ type: "button",
242
+ className: "lich-ta-datepicker-calendar-nav",
243
+ onClick: nextMonth,
244
+ "aria-label": "Th\xE1ng sau",
245
+ children: "\u25B6"
246
+ }
247
+ )
248
+ ] }),
249
+ /* @__PURE__ */ jsx2("div", { className: "lich-ta-datepicker-calendar-weekdays", children: weekDayLabels.map((label) => /* @__PURE__ */ jsx2("div", { className: "lich-ta-datepicker-calendar-weekday", children: label }, label)) }),
250
+ /* @__PURE__ */ jsx2("div", { className: "lich-ta-datepicker-calendar-grid", children: grid.map((cell, idx) => {
251
+ let classes = "lich-ta-datepicker-calendar-day";
252
+ if (cell.isToday) classes += " is-today";
253
+ if (cell.isSelected) classes += " is-selected";
254
+ if (!cell.isCurrentMonth) classes += " is-other-month";
255
+ if (cell.lunar.day === 1) classes += " is-first-lunar";
256
+ return /* @__PURE__ */ jsxs2(
257
+ "button",
258
+ {
259
+ type: "button",
260
+ className: classes,
261
+ onClick: () => handleDaySelect(cell),
262
+ tabIndex: cell.isCurrentMonth ? 0 : -1,
263
+ children: [
264
+ /* @__PURE__ */ jsx2("span", { className: "solar-day", children: cell.solar.getDate() }),
265
+ showLunar && /* @__PURE__ */ jsx2("span", { className: "lunar-day", children: cell.lunar.day === 1 ? `${cell.lunar.day}/${cell.lunar.month}${cell.lunar.isLeap ? "*" : ""}` : cell.lunar.day })
266
+ ]
267
+ },
268
+ idx
269
+ );
270
+ }) })
271
+ ] }) })
272
+ ] });
273
+ };
171
274
  export {
172
- Calendar
275
+ Calendar,
276
+ DatePicker
173
277
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lichta/react",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "React components for LichTa",
5
5
  "author": "Zeforc Labs | Stridev",
6
6
  "license": "MIT",
@@ -12,7 +12,7 @@
12
12
  "LICENSE"
13
13
  ],
14
14
  "dependencies": {
15
- "@lichta/core": "2.0.1"
15
+ "@lichta/core": "2.2.0"
16
16
  },
17
17
  "peerDependencies": {
18
18
  "react": ">=18.0.0",