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