@ttdk-design-system/library-react 0.0.0-stage → 1.0.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/AI_GUIDE.md +6518 -0
- package/llms.txt +6518 -0
- package/package.json +1098 -4
- package/src/core/index.ts +2 -0
- package/src/core/tailwind-preset.cjs +62 -0
- package/src/core/tailwind-preset.ts +85 -0
- package/src/core/theme/tokens.css +663 -0
- package/src/core/types.ts +836 -0
- package/src/index.ts +17 -0
- package/src/tailwind-preset.cjs +62 -0
- package/src/tailwind-preset.ts +85 -0
- package/src/types.ts +1 -0
- package/src/web/accordion/AI_GUIDE.md +149 -0
- package/src/web/accordion/accordion.css +1 -0
- package/src/web/accordion/accordion.tsx +430 -0
- package/src/web/accordion/index.ts +3 -0
- package/src/web/accordion/manifest.json +351 -0
- package/src/web/alert/AI_GUIDE.md +139 -0
- package/src/web/alert/alert.css +1 -0
- package/src/web/alert/alert.tsx +318 -0
- package/src/web/alert/index.ts +3 -0
- package/src/web/alert/manifest.json +240 -0
- package/src/web/avatar/AI_GUIDE.md +118 -0
- package/src/web/avatar/avatar.css +1 -0
- package/src/web/avatar/avatar.tsx +233 -0
- package/src/web/avatar/index.ts +3 -0
- package/src/web/avatar/manifest.json +195 -0
- package/src/web/badge/AI_GUIDE.md +94 -0
- package/src/web/badge/badge.css +1 -0
- package/src/web/badge/badge.tsx +140 -0
- package/src/web/badge/index.ts +2 -0
- package/src/web/badge/manifest.json +141 -0
- package/src/web/banner/AI_GUIDE.md +130 -0
- package/src/web/banner/banner.css +1 -0
- package/src/web/banner/banner.tsx +334 -0
- package/src/web/banner/index.ts +3 -0
- package/src/web/banner/manifest.json +240 -0
- package/src/web/breadcrumb/AI_GUIDE.md +120 -0
- package/src/web/breadcrumb/breadcrumb.css +1 -0
- package/src/web/breadcrumb/breadcrumb.tsx +306 -0
- package/src/web/breadcrumb/index.ts +3 -0
- package/src/web/breadcrumb/manifest.json +225 -0
- package/src/web/button/AI_GUIDE.md +121 -0
- package/src/web/button/button.css +1 -0
- package/src/web/button/button.tsx +287 -0
- package/src/web/button/index.ts +2 -0
- package/src/web/button/manifest.json +177 -0
- package/src/web/calendar/AI_GUIDE.md +151 -0
- package/src/web/calendar/calendar.css +1 -0
- package/src/web/calendar/calendar.tsx +1611 -0
- package/src/web/calendar/index.ts +3 -0
- package/src/web/calendar/manifest.json +470 -0
- package/src/web/card/AI_GUIDE.md +133 -0
- package/src/web/card/card.css +1 -0
- package/src/web/card/card.tsx +408 -0
- package/src/web/card/index.ts +4 -0
- package/src/web/card/manifest.json +357 -0
- package/src/web/checkbox/AI_GUIDE.md +146 -0
- package/src/web/checkbox/checkbox.css +1 -0
- package/src/web/checkbox/checkbox.tsx +307 -0
- package/src/web/checkbox/index.ts +2 -0
- package/src/web/checkbox/manifest.json +294 -0
- package/src/web/chip/AI_GUIDE.md +186 -0
- package/src/web/chip/chip.css +1 -0
- package/src/web/chip/chip.tsx +230 -0
- package/src/web/chip/index.ts +2 -0
- package/src/web/chip/manifest.json +186 -0
- package/src/web/dropdown/AI_GUIDE.md +167 -0
- package/src/web/dropdown/dropdown.css +1 -0
- package/src/web/dropdown/dropdown.tsx +1274 -0
- package/src/web/dropdown/index.ts +3 -0
- package/src/web/dropdown/manifest.json +576 -0
- package/src/web/flex/AI_GUIDE.md +113 -0
- package/src/web/flex/flex-item.tsx +97 -0
- package/src/web/flex/flex.css +1 -0
- package/src/web/flex/flex.tsx +108 -0
- package/src/web/flex/index.ts +5 -0
- package/src/web/flex/manifest.json +162 -0
- package/src/web/form/index.ts +7 -0
- package/src/web/grid/AI_GUIDE.md +112 -0
- package/src/web/grid/grid-item.tsx +112 -0
- package/src/web/grid/grid.css +1 -0
- package/src/web/grid/grid.tsx +99 -0
- package/src/web/grid/index.ts +5 -0
- package/src/web/grid/manifest.json +153 -0
- package/src/web/index.ts +86 -0
- package/src/web/input/AI_GUIDE.md +159 -0
- package/src/web/input/index.ts +3 -0
- package/src/web/input/input.css +1 -0
- package/src/web/input/input.tsx +537 -0
- package/src/web/input/manifest.json +465 -0
- package/src/web/input-number/AI_GUIDE.md +164 -0
- package/src/web/input-number/index.ts +3 -0
- package/src/web/input-number/input-number.css +1 -0
- package/src/web/input-number/input-number.tsx +1193 -0
- package/src/web/input-number/manifest.json +575 -0
- package/src/web/modal/AI_GUIDE.md +171 -0
- package/src/web/modal/index.ts +3 -0
- package/src/web/modal/manifest.json +375 -0
- package/src/web/modal/modal.css +1 -0
- package/src/web/modal/modal.tsx +571 -0
- package/src/web/pagination/AI_GUIDE.md +148 -0
- package/src/web/pagination/index.ts +21 -0
- package/src/web/pagination/manifest.json +432 -0
- package/src/web/pagination/pagination-content.tsx +45 -0
- package/src/web/pagination/pagination-ellipsis.tsx +54 -0
- package/src/web/pagination/pagination-first.tsx +155 -0
- package/src/web/pagination/pagination-item.tsx +45 -0
- package/src/web/pagination/pagination-last.tsx +155 -0
- package/src/web/pagination/pagination-link.tsx +121 -0
- package/src/web/pagination/pagination-next.tsx +158 -0
- package/src/web/pagination/pagination-previous.tsx +158 -0
- package/src/web/pagination/pagination.css +1 -0
- package/src/web/pagination/pagination.tsx +687 -0
- package/src/web/popover/AI_GUIDE.md +163 -0
- package/src/web/popover/index.ts +3 -0
- package/src/web/popover/manifest.json +396 -0
- package/src/web/popover/popover.css +1 -0
- package/src/web/popover/popover.tsx +702 -0
- package/src/web/radio/AI_GUIDE.md +155 -0
- package/src/web/radio/index.ts +2 -0
- package/src/web/radio/manifest.json +294 -0
- package/src/web/radio/radio.css +1 -0
- package/src/web/radio/radio.tsx +290 -0
- package/src/web/selectbox/AI_GUIDE.md +216 -0
- package/src/web/selectbox/index.ts +3 -0
- package/src/web/selectbox/manifest.json +477 -0
- package/src/web/selectbox/selectbox.css +1 -0
- package/src/web/selectbox/selectbox.tsx +1048 -0
- package/src/web/sonner/AI_GUIDE.md +98 -0
- package/src/web/sonner/index.ts +114 -0
- package/src/web/sonner/manifest.json +132 -0
- package/src/web/sonner/sonner.css +1 -0
- package/src/web/sonner/sonner.tsx +177 -0
- package/src/web/table/AI_GUIDE.md +145 -0
- package/src/web/table/index.ts +35 -0
- package/src/web/table/manifest.json +168 -0
- package/src/web/table/table-body.tsx +45 -0
- package/src/web/table/table-caption.tsx +45 -0
- package/src/web/table/table-cell.tsx +74 -0
- package/src/web/table/table-empty.tsx +108 -0
- package/src/web/table/table-footer.tsx +45 -0
- package/src/web/table/table-head.tsx +80 -0
- package/src/web/table/table-header.tsx +45 -0
- package/src/web/table/table-row.tsx +69 -0
- package/src/web/table/table.css +1 -0
- package/src/web/table/table.tsx +305 -0
- package/src/web/tabs/AI_GUIDE.md +150 -0
- package/src/web/tabs/index.ts +3 -0
- package/src/web/tabs/manifest.json +315 -0
- package/src/web/tabs/tabs.css +1 -0
- package/src/web/tabs/tabs.tsx +332 -0
- package/src/web/text/AI_GUIDE.md +129 -0
- package/src/web/text/index.ts +3 -0
- package/src/web/text/manifest.json +195 -0
- package/src/web/text/text.css +1 -0
- package/src/web/text/text.tsx +689 -0
- package/src/web/textarea/AI_GUIDE.md +141 -0
- package/src/web/textarea/index.ts +3 -0
- package/src/web/textarea/manifest.json +438 -0
- package/src/web/textarea/textarea.css +1 -0
- package/src/web/textarea/textarea.tsx +467 -0
- package/src/web/tooltip/AI_GUIDE.md +110 -0
- package/src/web/tooltip/index.ts +3 -0
- package/src/web/tooltip/manifest.json +222 -0
- package/src/web/tooltip/tooltip.css +1 -0
- package/src/web/tooltip/tooltip.tsx +423 -0
- package/src/webview/bottomsheet/AI_GUIDE.md +141 -0
- package/src/webview/bottomsheet/bottomsheet.css +1 -0
- package/src/webview/bottomsheet/bottomsheet.tsx +603 -0
- package/src/webview/bottomsheet/index.ts +4 -0
- package/src/webview/bottomsheet/manifest.json +377 -0
- package/src/webview/button/AI_GUIDE.md +79 -0
- package/src/webview/button/button-ios.css +1 -0
- package/src/webview/button/button-ios.tsx +138 -0
- package/src/webview/button/button-material.css +1 -0
- package/src/webview/button/button-material.tsx +138 -0
- package/src/webview/button/index.ts +7 -0
- package/src/webview/button/manifest.json +148 -0
- package/src/webview/index.ts +7 -0
- package/tailwind-preset.cjs +62 -0
- package/tailwind-preset.js +62 -0
- package/ttdk-manifest.json +10188 -0
- package/README.md +0 -3
|
@@ -0,0 +1,1611 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
|
|
5
|
+
export type CalendarUiProps = UiProps;
|
|
6
|
+
export interface DateRange {
|
|
7
|
+
start: string | Date | null;
|
|
8
|
+
end: string | Date | null;
|
|
9
|
+
}
|
|
10
|
+
export interface DateMarker {
|
|
11
|
+
date: string;
|
|
12
|
+
color?: string;
|
|
13
|
+
tooltip?: string;
|
|
14
|
+
dot?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface CalendarProps {
|
|
17
|
+
/**
|
|
18
|
+
* Nilai tanggal terpilih pada mode controlled.
|
|
19
|
+
* - Single: string ('YYYY-MM-DD') atau Date
|
|
20
|
+
* - Range: { start: string | Date | null, end: string | Date | null }
|
|
21
|
+
* - Multiple: string[] atau Date[]
|
|
22
|
+
*/
|
|
23
|
+
value?: any;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Nilai awal tanggal terpilih pada mode uncontrolled.
|
|
27
|
+
*/
|
|
28
|
+
defaultValue?: any;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Atribut ID unik untuk elemen root kalender.
|
|
32
|
+
*/
|
|
33
|
+
id?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Mengaktifkan mode pemilihan rentang tanggal (start date - end date).
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
range?: boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Mengaktifkan mode pemilihan banyak tanggal sekaligus.
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
multiple?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Batas tanggal paling awal yang dapat dipilih.
|
|
49
|
+
*/
|
|
50
|
+
min?: string | Date;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Alias untuk min (kompatibel Nuxt UI: min-value).
|
|
54
|
+
*/
|
|
55
|
+
minValue?: string | Date;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Batas tanggal paling akhir yang dapat dipilih.
|
|
59
|
+
*/
|
|
60
|
+
max?: string | Date;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Alias untuk max (kompatibel Nuxt UI: max-value).
|
|
64
|
+
*/
|
|
65
|
+
maxValue?: string | Date;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Fungsi untuk menonaktifkan tanggal tertentu secara spesifik (misal: hari libur / akhir pekan).
|
|
69
|
+
*/
|
|
70
|
+
isDateDisabled?: (date: Date) => boolean;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Fungsi untuk menandai tanggal tidak tersedia (ditampilkan coret strikethrough).
|
|
74
|
+
*/
|
|
75
|
+
isDateUnavailable?: (date: Date) => boolean;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Jumlah bulan yang ditampilkan secara berdampingan.
|
|
79
|
+
* @default 1
|
|
80
|
+
*/
|
|
81
|
+
numberOfMonths?: number;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Hari awal dalam seminggu:
|
|
85
|
+
* - 0 = Minggu (Sunday)
|
|
86
|
+
* - 1 = Senin (Monday)
|
|
87
|
+
* @default 1
|
|
88
|
+
*/
|
|
89
|
+
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Kode lokal bahasa kalender (misal: 'id-ID', 'en-US').
|
|
93
|
+
* @default 'id-ID'
|
|
94
|
+
*/
|
|
95
|
+
locale?: string;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Bulan yang sedang ditampilkan (1 - 12) pada mode navigasi controlled.
|
|
99
|
+
*/
|
|
100
|
+
month?: number;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Tahun yang sedang ditampilkan (misal: 2026) pada mode navigasi controlled.
|
|
104
|
+
*/
|
|
105
|
+
year?: number;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Bulan awal yang sedang ditampilkan (1 - 12).
|
|
109
|
+
*/
|
|
110
|
+
defaultMonth?: number;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Tahun awal yang sedang ditampilkan.
|
|
114
|
+
*/
|
|
115
|
+
defaultYear?: number;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Menampilkan tombol navigasi lompat tahun (<< dan >>).
|
|
119
|
+
* @default true
|
|
120
|
+
*/
|
|
121
|
+
yearControls?: boolean;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Menampilkan hari-hari redup di luar bulan yang aktif (leading & trailing days).
|
|
125
|
+
* @default true
|
|
126
|
+
*/
|
|
127
|
+
showOutsideDays?: boolean;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Selalu merender 6 baris minggu agar tinggi kalender konsisten stabil antar bulan.
|
|
131
|
+
* @default true
|
|
132
|
+
*/
|
|
133
|
+
fixedWeeks?: boolean;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Daftar penanda agenda / jadwal faskes pada tanggal tertentu.
|
|
137
|
+
*/
|
|
138
|
+
dateMarkers?: DateMarker[];
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Skala ukuran kalender (sm: kompak 32px, md: standar 38px, lg: besar 44px).
|
|
142
|
+
* @default 'md'
|
|
143
|
+
*/
|
|
144
|
+
size?: "sm" | "md" | "lg";
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Radius sudut kelengkungan container kalender.
|
|
148
|
+
* @default 'md'
|
|
149
|
+
*/
|
|
150
|
+
rounded?: "none" | "sm" | "md" | "lg" | "xl" | "full";
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Varian visual tampilan kalender:
|
|
154
|
+
* - 'default': Bersih dengan background putih/gelap.
|
|
155
|
+
* - 'bordered': Dengan bingkai border solid luar.
|
|
156
|
+
* - 'card': Dengan elevasi bayangan halus kartu.
|
|
157
|
+
* - 'flat': Minimalis tanpa border dan tanpa shadow.
|
|
158
|
+
* @default 'bordered'
|
|
159
|
+
*/
|
|
160
|
+
variant?: "default" | "bordered" | "card" | "flat";
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Menonaktifkan seluruh kalender dari interaksi.
|
|
164
|
+
* @default false
|
|
165
|
+
*/
|
|
166
|
+
disabled?: boolean;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Mode hanya-baca (read-only).
|
|
170
|
+
* @default false
|
|
171
|
+
*/
|
|
172
|
+
readOnly?: boolean;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Callback event saat nilai tanggal berubah.
|
|
176
|
+
*/
|
|
177
|
+
onValueChange?: (value: any) => void;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Callback event native saat nilai berubah.
|
|
181
|
+
*/
|
|
182
|
+
onChange?: (event: any) => void;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Callback saat navigasi bulan atau tahun berubah.
|
|
186
|
+
*/
|
|
187
|
+
onMonthChange?: (month: number, year: number) => void;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Callback saat tahun tampilan kalender berubah.
|
|
191
|
+
*/
|
|
192
|
+
onYearChange?: (month: number, year: number) => void;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Mode tampilan awal kalender ('day', 'month', 'year').
|
|
196
|
+
* @default 'day'
|
|
197
|
+
*/
|
|
198
|
+
defaultViewMode?: "day" | "month" | "year";
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Mode tampilan kalender saat ini ('day', 'month', 'year') untuk mode controlled.
|
|
202
|
+
*/
|
|
203
|
+
viewMode?: "day" | "month" | "year";
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Callback saat mode tampilan kalender berubah ('day', 'month', 'year').
|
|
207
|
+
*/
|
|
208
|
+
onViewModeChange?: (mode: "day" | "month" | "year") => void;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Class CSS kustom untuk container utama.
|
|
212
|
+
*/
|
|
213
|
+
className?: string;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Inline style CSS kustom untuk container utama.
|
|
217
|
+
*/
|
|
218
|
+
style?: React.CSSProperties;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Class CSS kustom untuk header kalender.
|
|
222
|
+
*/
|
|
223
|
+
headerClass?: string;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Inline style kustom untuk header kalender.
|
|
227
|
+
*/
|
|
228
|
+
headerStyle?: UiProps;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Class CSS kustom untuk tombol navigasi.
|
|
232
|
+
*/
|
|
233
|
+
navBtnClass?: string;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Inline style kustom untuk tombol navigasi.
|
|
237
|
+
*/
|
|
238
|
+
navBtnStyle?: UiProps;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Class CSS kustom untuk cell tanggal.
|
|
242
|
+
*/
|
|
243
|
+
cellClass?: string;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Inline style kustom untuk cell tanggal.
|
|
247
|
+
*/
|
|
248
|
+
cellStyle?: UiProps;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Kustomisasi visual granular UiProps.
|
|
252
|
+
*/
|
|
253
|
+
ui?: UiProps;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
import { buildUiStyle, buildUiVars, UiProps } from "../../core/types";
|
|
257
|
+
|
|
258
|
+
function Calendar(props: CalendarProps) {
|
|
259
|
+
function initialParsed() {
|
|
260
|
+
const target = props.value !== undefined ? props.value : props.defaultValue;
|
|
261
|
+
if (!target) return null;
|
|
262
|
+
if (props.range && typeof target === "object") {
|
|
263
|
+
const s = target.start;
|
|
264
|
+
if (s instanceof Date) return isNaN(s.getTime()) ? null : s;
|
|
265
|
+
if (typeof s === "string" && s.trim()) {
|
|
266
|
+
const parts = s.split("-");
|
|
267
|
+
if (parts.length === 3) {
|
|
268
|
+
const y = parseInt(parts[0], 10);
|
|
269
|
+
const m = parseInt(parts[1], 10) - 1;
|
|
270
|
+
const d = parseInt(parts[2], 10);
|
|
271
|
+
const res = new Date(y, m, d);
|
|
272
|
+
return isNaN(res.getTime()) ? null : res;
|
|
273
|
+
}
|
|
274
|
+
const p = new Date(s);
|
|
275
|
+
return isNaN(p.getTime()) ? null : p;
|
|
276
|
+
}
|
|
277
|
+
} else if (props.multiple && Array.isArray(target) && target.length > 0) {
|
|
278
|
+
const first = target[0];
|
|
279
|
+
if (first instanceof Date) return isNaN(first.getTime()) ? null : first;
|
|
280
|
+
if (typeof first === "string" && first.trim()) {
|
|
281
|
+
const parts = first.split("-");
|
|
282
|
+
if (parts.length === 3) {
|
|
283
|
+
const y = parseInt(parts[0], 10);
|
|
284
|
+
const m = parseInt(parts[1], 10) - 1;
|
|
285
|
+
const d = parseInt(parts[2], 10);
|
|
286
|
+
const res = new Date(y, m, d);
|
|
287
|
+
return isNaN(res.getTime()) ? null : res;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} else if (typeof target === "string" && target.trim()) {
|
|
291
|
+
const parts = target.split("-");
|
|
292
|
+
if (parts.length === 3) {
|
|
293
|
+
const y = parseInt(parts[0], 10);
|
|
294
|
+
const m = parseInt(parts[1], 10) - 1;
|
|
295
|
+
const d = parseInt(parts[2], 10);
|
|
296
|
+
const res = new Date(y, m, d);
|
|
297
|
+
return isNaN(res.getTime()) ? null : res;
|
|
298
|
+
}
|
|
299
|
+
const p = new Date(target);
|
|
300
|
+
return isNaN(p.getTime()) ? null : p;
|
|
301
|
+
} else if (target instanceof Date && !isNaN(target.getTime())) {
|
|
302
|
+
return target;
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const [viewYear, setViewYear] = useState(() =>
|
|
308
|
+
(() => {
|
|
309
|
+
if (props.year) return props.year;
|
|
310
|
+
if (props.defaultYear) return props.defaultYear;
|
|
311
|
+
const target =
|
|
312
|
+
props.value !== undefined ? props.value : props.defaultValue;
|
|
313
|
+
if (target) {
|
|
314
|
+
const valStr =
|
|
315
|
+
props.range && typeof target === "object"
|
|
316
|
+
? target.start
|
|
317
|
+
: props.multiple && Array.isArray(target)
|
|
318
|
+
? target[0]
|
|
319
|
+
: target;
|
|
320
|
+
if (valStr instanceof Date && !isNaN(valStr.getTime()))
|
|
321
|
+
return valStr.getFullYear();
|
|
322
|
+
if (typeof valStr === "string" && valStr.trim()) {
|
|
323
|
+
const parts = valStr.split("-");
|
|
324
|
+
if (parts.length === 3) {
|
|
325
|
+
const y = parseInt(parts[0], 10);
|
|
326
|
+
if (!isNaN(y)) return y;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return new Date().getFullYear();
|
|
331
|
+
})()
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const [viewMonth, setViewMonth] = useState(() =>
|
|
335
|
+
(() => {
|
|
336
|
+
if (props.month) return props.month;
|
|
337
|
+
if (props.defaultMonth) return props.defaultMonth;
|
|
338
|
+
const target =
|
|
339
|
+
props.value !== undefined ? props.value : props.defaultValue;
|
|
340
|
+
if (target) {
|
|
341
|
+
const valStr =
|
|
342
|
+
props.range && typeof target === "object"
|
|
343
|
+
? target.start
|
|
344
|
+
: props.multiple && Array.isArray(target)
|
|
345
|
+
? target[0]
|
|
346
|
+
: target;
|
|
347
|
+
if (valStr instanceof Date && !isNaN(valStr.getTime()))
|
|
348
|
+
return valStr.getMonth() + 1;
|
|
349
|
+
if (typeof valStr === "string" && valStr.trim()) {
|
|
350
|
+
const parts = valStr.split("-");
|
|
351
|
+
if (parts.length === 3) {
|
|
352
|
+
const m = parseInt(parts[1], 10);
|
|
353
|
+
if (!isNaN(m)) return m;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return new Date().getMonth() + 1;
|
|
358
|
+
})()
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const [currentViewMode, setCurrentViewMode] = useState(
|
|
362
|
+
() => props.viewMode || props.defaultViewMode || "day"
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const [decadeStart, setDecadeStart] = useState<number | null>(() => null);
|
|
366
|
+
|
|
367
|
+
const [selectedSingle, setSelectedSingle] = useState<string | null>(() => null);
|
|
368
|
+
|
|
369
|
+
const [rangeStart, setRangeStart] = useState<string | null>(() => null);
|
|
370
|
+
|
|
371
|
+
const [rangeEnd, setRangeEnd] = useState<string | null>(() => null);
|
|
372
|
+
|
|
373
|
+
const [hoverDate, setHoverDate] = useState<string | null>(() => null);
|
|
374
|
+
|
|
375
|
+
const [selectedMultiple, setSelectedMultiple] = useState<string[]>(() => []);
|
|
376
|
+
|
|
377
|
+
const [lastParsedKey, setLastParsedKey] = useState(() =>
|
|
378
|
+
(() => {
|
|
379
|
+
const target =
|
|
380
|
+
props.value !== undefined ? props.value : props.defaultValue;
|
|
381
|
+
if (!target) return "";
|
|
382
|
+
const valStr =
|
|
383
|
+
props.range && typeof target === "object"
|
|
384
|
+
? target.start
|
|
385
|
+
: props.multiple && Array.isArray(target)
|
|
386
|
+
? target[0]
|
|
387
|
+
: target;
|
|
388
|
+
return valStr instanceof Date
|
|
389
|
+
? "" + valStr.getTime()
|
|
390
|
+
: typeof valStr === "string"
|
|
391
|
+
? valStr
|
|
392
|
+
: "";
|
|
393
|
+
})()
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
function padZero(num: number) {
|
|
397
|
+
return num < 10 ? "0" + num : "" + num;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function toDateString(d: Date) {
|
|
401
|
+
if (!d || isNaN(d.getTime())) {
|
|
402
|
+
const now = new Date();
|
|
403
|
+
return (
|
|
404
|
+
now.getFullYear() +
|
|
405
|
+
"-" +
|
|
406
|
+
padZero(now.getMonth() + 1) +
|
|
407
|
+
"-" +
|
|
408
|
+
padZero(now.getDate())
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
return (
|
|
412
|
+
d.getFullYear() +
|
|
413
|
+
"-" +
|
|
414
|
+
padZero(d.getMonth() + 1) +
|
|
415
|
+
"-" +
|
|
416
|
+
padZero(d.getDate())
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function parseDate(val: any) {
|
|
421
|
+
if (!val) return null;
|
|
422
|
+
if (val instanceof Date) return isNaN(val.getTime()) ? null : val;
|
|
423
|
+
if (typeof val === "string") {
|
|
424
|
+
const parts = val.split("-");
|
|
425
|
+
if (parts.length === 3) {
|
|
426
|
+
const y = parseInt(parts[0], 10);
|
|
427
|
+
const m = parseInt(parts[1], 10) - 1;
|
|
428
|
+
const d = parseInt(parts[2], 10);
|
|
429
|
+
const res = new Date(y, m, d);
|
|
430
|
+
return isNaN(res.getTime()) ? null : res;
|
|
431
|
+
}
|
|
432
|
+
const parsed = new Date(val);
|
|
433
|
+
return isNaN(parsed.getTime()) ? null : parsed;
|
|
434
|
+
}
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function getViewYear(monthOffset: number = 0) {
|
|
439
|
+
const now = new Date();
|
|
440
|
+
let m = (props.month || viewMonth || now.getMonth() + 1) - 1 + monthOffset;
|
|
441
|
+
let y = props.year || viewYear || now.getFullYear();
|
|
442
|
+
if (isNaN(m)) m = now.getMonth() + monthOffset;
|
|
443
|
+
if (isNaN(y)) y = now.getFullYear();
|
|
444
|
+
while (m > 11) {
|
|
445
|
+
m -= 12;
|
|
446
|
+
y += 1;
|
|
447
|
+
}
|
|
448
|
+
while (m < 0) {
|
|
449
|
+
m += 12;
|
|
450
|
+
y -= 1;
|
|
451
|
+
}
|
|
452
|
+
return y;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function getViewMonth(monthOffset: number = 0) {
|
|
456
|
+
const now = new Date();
|
|
457
|
+
let m = (props.month || viewMonth || now.getMonth() + 1) - 1 + monthOffset;
|
|
458
|
+
if (isNaN(m)) m = now.getMonth() + monthOffset;
|
|
459
|
+
while (m > 11) {
|
|
460
|
+
m -= 12;
|
|
461
|
+
}
|
|
462
|
+
while (m < 0) {
|
|
463
|
+
m += 12;
|
|
464
|
+
}
|
|
465
|
+
return m + 1;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function getMonthLabel(year: number, month: number) {
|
|
469
|
+
const loc = props.locale || "id-ID";
|
|
470
|
+
const d = new Date(year, month - 1, 1);
|
|
471
|
+
return d.toLocaleDateString(loc, {
|
|
472
|
+
month: "long",
|
|
473
|
+
year: "numeric",
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function getMonthOnlyLabel(year: number, month: number) {
|
|
478
|
+
const loc = props.locale || "id-ID";
|
|
479
|
+
const d = new Date(year, month - 1, 1);
|
|
480
|
+
return d.toLocaleDateString(loc, {
|
|
481
|
+
month: "long",
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function getViewMode() {
|
|
486
|
+
return props.viewMode || currentViewMode || "day";
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function setViewMode(mode: "day" | "month" | "year") {
|
|
490
|
+
setCurrentViewMode(mode as any);
|
|
491
|
+
if (props.onViewModeChange) {
|
|
492
|
+
props.onViewModeChange(mode);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function openMonthView() {
|
|
497
|
+
if (props.disabled) return;
|
|
498
|
+
setViewMode("month");
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function openYearView() {
|
|
502
|
+
if (props.disabled) return;
|
|
503
|
+
setViewMode("year");
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function getDecadeStart() {
|
|
507
|
+
if (decadeStart !== null) {
|
|
508
|
+
return decadeStart;
|
|
509
|
+
}
|
|
510
|
+
const y = props.year || viewYear;
|
|
511
|
+
return Math.floor(y / 10) * 10;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function getDecadeLabel() {
|
|
515
|
+
const start = getDecadeStart();
|
|
516
|
+
const end = start + 11;
|
|
517
|
+
return start + " - " + end;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function prevDecade() {
|
|
521
|
+
if (props.disabled) return;
|
|
522
|
+
setDecadeStart(getDecadeStart() - 12);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function nextDecade() {
|
|
526
|
+
if (props.disabled) return;
|
|
527
|
+
setDecadeStart(getDecadeStart() + 12);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function getYearsList() {
|
|
531
|
+
const start = getDecadeStart();
|
|
532
|
+
const currentY = props.year || viewYear;
|
|
533
|
+
const actualY = new Date().getFullYear();
|
|
534
|
+
const list: any[] = [];
|
|
535
|
+
for (let i = 0; i < 12; i++) {
|
|
536
|
+
const y = start + i;
|
|
537
|
+
list.push({
|
|
538
|
+
year: y,
|
|
539
|
+
label: "" + y,
|
|
540
|
+
isSelected: y === currentY,
|
|
541
|
+
isCurrent: y === actualY,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
return list;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function selectYear(year: number) {
|
|
548
|
+
if (props.disabled) return;
|
|
549
|
+
setViewYear(year);
|
|
550
|
+
setDecadeStart(null);
|
|
551
|
+
setViewMode("month");
|
|
552
|
+
if (props.onYearChange) {
|
|
553
|
+
props.onYearChange(getViewMonth(), year);
|
|
554
|
+
}
|
|
555
|
+
if (props.onMonthChange) {
|
|
556
|
+
props.onMonthChange(getViewMonth(), year);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function getMonthsList() {
|
|
561
|
+
const currentY = props.year || viewYear;
|
|
562
|
+
const currentM = props.month || viewMonth;
|
|
563
|
+
const actualNow = new Date();
|
|
564
|
+
const actualY = actualNow.getFullYear();
|
|
565
|
+
const actualM = actualNow.getMonth() + 1;
|
|
566
|
+
const loc = props.locale || "id-ID";
|
|
567
|
+
const list: any[] = [];
|
|
568
|
+
for (let m = 1; m <= 12; m++) {
|
|
569
|
+
const d = new Date(currentY, m - 1, 1);
|
|
570
|
+
const shortName = d.toLocaleDateString(loc, {
|
|
571
|
+
month: "short",
|
|
572
|
+
});
|
|
573
|
+
const fullName = d.toLocaleDateString(loc, {
|
|
574
|
+
month: "long",
|
|
575
|
+
});
|
|
576
|
+
list.push({
|
|
577
|
+
month: m,
|
|
578
|
+
label: shortName,
|
|
579
|
+
fullName: fullName,
|
|
580
|
+
isSelected: m === currentM,
|
|
581
|
+
isCurrent: currentY === actualY && m === actualM,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
return list;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function selectMonth(month: number) {
|
|
588
|
+
if (props.disabled) return;
|
|
589
|
+
setViewMonth(month);
|
|
590
|
+
setViewMode("day");
|
|
591
|
+
if (props.onMonthChange) {
|
|
592
|
+
props.onMonthChange(month, getViewYear());
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function getWeekDays() {
|
|
597
|
+
const startOn = props.weekStartsOn !== undefined ? props.weekStartsOn : 1;
|
|
598
|
+
const idLabels = ["Min", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab"];
|
|
599
|
+
const enLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
600
|
+
const loc = props.locale || "id-ID";
|
|
601
|
+
const base = loc.toLowerCase().indexOf("en") === 0 ? enLabels : idLabels;
|
|
602
|
+
const result: string[] = [];
|
|
603
|
+
for (let i = 0; i < 7; i++) {
|
|
604
|
+
const idx = (startOn + i) % 7;
|
|
605
|
+
result.push(base[idx]);
|
|
606
|
+
}
|
|
607
|
+
return result;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function prevMonth() {
|
|
611
|
+
if (props.disabled) return;
|
|
612
|
+
let nextM = viewMonth - 1;
|
|
613
|
+
let nextY = viewYear;
|
|
614
|
+
if (nextM < 1) {
|
|
615
|
+
nextM = 12;
|
|
616
|
+
nextY -= 1;
|
|
617
|
+
}
|
|
618
|
+
setViewMonth(nextM);
|
|
619
|
+
setViewYear(nextY);
|
|
620
|
+
if (props.onMonthChange) {
|
|
621
|
+
props.onMonthChange(nextM, nextY);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function nextMonth() {
|
|
626
|
+
if (props.disabled) return;
|
|
627
|
+
let nextM = viewMonth + 1;
|
|
628
|
+
let nextY = viewYear;
|
|
629
|
+
if (nextM > 12) {
|
|
630
|
+
nextM = 1;
|
|
631
|
+
nextY += 1;
|
|
632
|
+
}
|
|
633
|
+
setViewMonth(nextM);
|
|
634
|
+
setViewYear(nextY);
|
|
635
|
+
if (props.onMonthChange) {
|
|
636
|
+
props.onMonthChange(nextM, nextY);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function prevYear() {
|
|
641
|
+
if (props.disabled) return;
|
|
642
|
+
const nextY = viewYear - 1;
|
|
643
|
+
setViewYear(nextY);
|
|
644
|
+
if (props.onMonthChange) {
|
|
645
|
+
props.onMonthChange(viewMonth, nextY);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function nextYear() {
|
|
650
|
+
if (props.disabled) return;
|
|
651
|
+
const nextY = viewYear + 1;
|
|
652
|
+
setViewYear(nextY);
|
|
653
|
+
if (props.onMonthChange) {
|
|
654
|
+
props.onMonthChange(viewMonth, nextY);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function goToToday() {
|
|
659
|
+
if (props.disabled) return;
|
|
660
|
+
const today = new Date();
|
|
661
|
+
setViewYear(today.getFullYear());
|
|
662
|
+
setViewMonth(today.getMonth() + 1);
|
|
663
|
+
setDecadeStart(null);
|
|
664
|
+
setViewMode("day");
|
|
665
|
+
handleSelectDate(toDateString(today));
|
|
666
|
+
if (props.onMonthChange) {
|
|
667
|
+
props.onMonthChange(viewMonth, viewYear);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function isToday(dateStr: string) {
|
|
672
|
+
const todayStr = toDateString(new Date());
|
|
673
|
+
return dateStr === todayStr;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function isDateDisabledCheck(date: Date) {
|
|
677
|
+
if (props.disabled) return true;
|
|
678
|
+
const minDate = parseDate(props.min || props.minValue);
|
|
679
|
+
if (
|
|
680
|
+
minDate &&
|
|
681
|
+
date <
|
|
682
|
+
new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate())
|
|
683
|
+
) {
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
const maxDate = parseDate(props.max || props.maxValue);
|
|
687
|
+
if (
|
|
688
|
+
maxDate &&
|
|
689
|
+
date >
|
|
690
|
+
new Date(
|
|
691
|
+
maxDate.getFullYear(),
|
|
692
|
+
maxDate.getMonth(),
|
|
693
|
+
maxDate.getDate(),
|
|
694
|
+
23,
|
|
695
|
+
59,
|
|
696
|
+
59
|
|
697
|
+
)
|
|
698
|
+
) {
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
if (props.isDateDisabled) {
|
|
702
|
+
return props.isDateDisabled(date);
|
|
703
|
+
}
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function isDateUnavailableCheck(date: Date) {
|
|
708
|
+
if (props.isDateUnavailable) {
|
|
709
|
+
return props.isDateUnavailable(date);
|
|
710
|
+
}
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function getDateMarker(dateStr: string) {
|
|
715
|
+
if (!props.dateMarkers || !props.dateMarkers.length) return null;
|
|
716
|
+
for (let i = 0; i < props.dateMarkers.length; i++) {
|
|
717
|
+
if (props.dateMarkers[i].date === dateStr) {
|
|
718
|
+
return props.dateMarkers[i];
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function isSelected(dateStr: string) {
|
|
725
|
+
if (props.range) {
|
|
726
|
+
return isRangeStart(dateStr) || isRangeEnd(dateStr);
|
|
727
|
+
}
|
|
728
|
+
if (props.multiple) {
|
|
729
|
+
const rawList =
|
|
730
|
+
props.value !== undefined
|
|
731
|
+
? Array.isArray(props.value)
|
|
732
|
+
? props.value
|
|
733
|
+
: []
|
|
734
|
+
: selectedMultiple;
|
|
735
|
+
const strList = rawList.map((item: any) =>
|
|
736
|
+
typeof item === "string"
|
|
737
|
+
? item
|
|
738
|
+
: item instanceof Date
|
|
739
|
+
? toDateString(item)
|
|
740
|
+
: ""
|
|
741
|
+
);
|
|
742
|
+
return strList.indexOf(dateStr) !== -1;
|
|
743
|
+
}
|
|
744
|
+
const current =
|
|
745
|
+
props.value !== undefined
|
|
746
|
+
? typeof props.value === "string"
|
|
747
|
+
? props.value
|
|
748
|
+
: props.value instanceof Date
|
|
749
|
+
? toDateString(props.value)
|
|
750
|
+
: null
|
|
751
|
+
: selectedSingle;
|
|
752
|
+
if (!current && props.defaultValue && !selectedSingle) {
|
|
753
|
+
const def =
|
|
754
|
+
typeof props.defaultValue === "string"
|
|
755
|
+
? props.defaultValue
|
|
756
|
+
: props.defaultValue instanceof Date
|
|
757
|
+
? toDateString(props.defaultValue)
|
|
758
|
+
: null;
|
|
759
|
+
return def === dateStr;
|
|
760
|
+
}
|
|
761
|
+
return current === dateStr;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function isRangeStart(dateStr: string) {
|
|
765
|
+
if (!props.range) return false;
|
|
766
|
+
const raw =
|
|
767
|
+
props.value && props.value.start
|
|
768
|
+
? props.value.start
|
|
769
|
+
: props.defaultValue && props.defaultValue.start
|
|
770
|
+
? props.defaultValue.start
|
|
771
|
+
: rangeStart;
|
|
772
|
+
const start =
|
|
773
|
+
typeof raw === "string"
|
|
774
|
+
? raw
|
|
775
|
+
: raw instanceof Date
|
|
776
|
+
? toDateString(raw)
|
|
777
|
+
: null;
|
|
778
|
+
return start === dateStr;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function isRangeEnd(dateStr: string) {
|
|
782
|
+
if (!props.range) return false;
|
|
783
|
+
const raw =
|
|
784
|
+
props.value && props.value.end
|
|
785
|
+
? props.value.end
|
|
786
|
+
: props.defaultValue && props.defaultValue.end
|
|
787
|
+
? props.defaultValue.end
|
|
788
|
+
: rangeEnd;
|
|
789
|
+
const end =
|
|
790
|
+
typeof raw === "string"
|
|
791
|
+
? raw
|
|
792
|
+
: raw instanceof Date
|
|
793
|
+
? toDateString(raw)
|
|
794
|
+
: null;
|
|
795
|
+
return end === dateStr;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function isInRange(dateStr: string) {
|
|
799
|
+
if (!props.range) return false;
|
|
800
|
+
const rawStart =
|
|
801
|
+
props.value && props.value.start
|
|
802
|
+
? props.value.start
|
|
803
|
+
: props.defaultValue && props.defaultValue.start
|
|
804
|
+
? props.defaultValue.start
|
|
805
|
+
: rangeStart;
|
|
806
|
+
let rawEnd =
|
|
807
|
+
props.value && props.value.end
|
|
808
|
+
? props.value.end
|
|
809
|
+
: props.defaultValue && props.defaultValue.end
|
|
810
|
+
? props.defaultValue.end
|
|
811
|
+
: rangeEnd;
|
|
812
|
+
const start =
|
|
813
|
+
typeof rawStart === "string"
|
|
814
|
+
? rawStart
|
|
815
|
+
: rawStart instanceof Date
|
|
816
|
+
? toDateString(rawStart)
|
|
817
|
+
: null;
|
|
818
|
+
let end =
|
|
819
|
+
typeof rawEnd === "string"
|
|
820
|
+
? rawEnd
|
|
821
|
+
: rawEnd instanceof Date
|
|
822
|
+
? toDateString(rawEnd)
|
|
823
|
+
: null;
|
|
824
|
+
if (start && !end && hoverDate) {
|
|
825
|
+
end = hoverDate;
|
|
826
|
+
}
|
|
827
|
+
if (!start || !end) return false;
|
|
828
|
+
const minD = start < end ? start : end;
|
|
829
|
+
const maxD = start < end ? end : start;
|
|
830
|
+
return dateStr > minD && dateStr < maxD;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function getCellRangeClass(dateStr: string) {
|
|
834
|
+
if (!props.range) return "";
|
|
835
|
+
const isStart = isRangeStart(dateStr);
|
|
836
|
+
const isEnd = isRangeEnd(dateStr);
|
|
837
|
+
const inRange = isInRange(dateStr);
|
|
838
|
+
if (isStart && isEnd) return "ttdk-calendar-cell-range-single";
|
|
839
|
+
if (isStart) return "ttdk-calendar-cell-range-start";
|
|
840
|
+
if (isEnd) return "ttdk-calendar-cell-range-end";
|
|
841
|
+
if (inRange) return "ttdk-calendar-cell-range";
|
|
842
|
+
return "";
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function handleDayHover(dateStr: string) {
|
|
846
|
+
if (!props.range || props.disabled || props.readOnly) return;
|
|
847
|
+
const rawStart =
|
|
848
|
+
props.value && props.value.start
|
|
849
|
+
? props.value.start
|
|
850
|
+
: props.defaultValue && props.defaultValue.start
|
|
851
|
+
? props.defaultValue.start
|
|
852
|
+
: rangeStart;
|
|
853
|
+
const rawEnd =
|
|
854
|
+
props.value && props.value.end
|
|
855
|
+
? props.value.end
|
|
856
|
+
: props.defaultValue && props.defaultValue.end
|
|
857
|
+
? props.defaultValue.end
|
|
858
|
+
: rangeEnd;
|
|
859
|
+
const start =
|
|
860
|
+
typeof rawStart === "string"
|
|
861
|
+
? rawStart
|
|
862
|
+
: rawStart instanceof Date
|
|
863
|
+
? toDateString(rawStart)
|
|
864
|
+
: null;
|
|
865
|
+
const end =
|
|
866
|
+
typeof rawEnd === "string"
|
|
867
|
+
? rawEnd
|
|
868
|
+
: rawEnd instanceof Date
|
|
869
|
+
? toDateString(rawEnd)
|
|
870
|
+
: null;
|
|
871
|
+
if (start && !end) {
|
|
872
|
+
setHoverDate(dateStr);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function handleDayHoverLeave() {
|
|
877
|
+
if (hoverDate) {
|
|
878
|
+
setHoverDate(null);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function handleSelectDate(dateStr: string) {
|
|
883
|
+
if (props.disabled || props.readOnly) return;
|
|
884
|
+
const dateObj = parseDate(dateStr);
|
|
885
|
+
if (!dateObj) return;
|
|
886
|
+
if (isDateDisabledCheck(dateObj) || isDateUnavailableCheck(dateObj)) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (props.range) {
|
|
890
|
+
const rawStart =
|
|
891
|
+
props.value && props.value.start
|
|
892
|
+
? props.value.start
|
|
893
|
+
: props.defaultValue && props.defaultValue.start
|
|
894
|
+
? props.defaultValue.start
|
|
895
|
+
: rangeStart;
|
|
896
|
+
const rawEnd =
|
|
897
|
+
props.value && props.value.end
|
|
898
|
+
? props.value.end
|
|
899
|
+
: props.defaultValue && props.defaultValue.end
|
|
900
|
+
? props.defaultValue.end
|
|
901
|
+
: rangeEnd;
|
|
902
|
+
const curStart =
|
|
903
|
+
typeof rawStart === "string"
|
|
904
|
+
? rawStart
|
|
905
|
+
: rawStart instanceof Date
|
|
906
|
+
? toDateString(rawStart)
|
|
907
|
+
: null;
|
|
908
|
+
const curEnd =
|
|
909
|
+
typeof rawEnd === "string"
|
|
910
|
+
? rawEnd
|
|
911
|
+
: rawEnd instanceof Date
|
|
912
|
+
? toDateString(rawEnd)
|
|
913
|
+
: null;
|
|
914
|
+
if (!curStart || (curStart && curEnd)) {
|
|
915
|
+
setRangeStart(dateStr);
|
|
916
|
+
setRangeEnd(null);
|
|
917
|
+
setHoverDate(null);
|
|
918
|
+
setLastParsedKey(dateStr);
|
|
919
|
+
if (props.onValueChange) {
|
|
920
|
+
props.onValueChange({
|
|
921
|
+
start: dateStr,
|
|
922
|
+
end: null,
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
} else {
|
|
926
|
+
let startRes = curStart;
|
|
927
|
+
let endRes = dateStr;
|
|
928
|
+
if (dateStr < curStart) {
|
|
929
|
+
startRes = dateStr;
|
|
930
|
+
endRes = curStart;
|
|
931
|
+
}
|
|
932
|
+
setRangeStart(startRes);
|
|
933
|
+
setRangeEnd(endRes);
|
|
934
|
+
setHoverDate(null);
|
|
935
|
+
if (props.onValueChange) {
|
|
936
|
+
props.onValueChange({
|
|
937
|
+
start: startRes,
|
|
938
|
+
end: endRes,
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
} else if (props.multiple) {
|
|
943
|
+
let currentList =
|
|
944
|
+
props.value !== undefined
|
|
945
|
+
? Array.isArray(props.value)
|
|
946
|
+
? [...props.value]
|
|
947
|
+
: []
|
|
948
|
+
: [...selectedMultiple];
|
|
949
|
+
const strList = currentList.map((item: any) =>
|
|
950
|
+
typeof item === "string" ? item : toDateString(item)
|
|
951
|
+
);
|
|
952
|
+
const idx = strList.indexOf(dateStr);
|
|
953
|
+
if (idx >= 0) {
|
|
954
|
+
strList.splice(idx, 1);
|
|
955
|
+
} else {
|
|
956
|
+
strList.push(dateStr);
|
|
957
|
+
}
|
|
958
|
+
setSelectedMultiple(strList);
|
|
959
|
+
if (props.onValueChange) {
|
|
960
|
+
props.onValueChange(strList);
|
|
961
|
+
}
|
|
962
|
+
} else {
|
|
963
|
+
setSelectedSingle(dateStr);
|
|
964
|
+
if (dateObj) {
|
|
965
|
+
setViewYear(dateObj.getFullYear());
|
|
966
|
+
setViewMonth(dateObj.getMonth() + 1);
|
|
967
|
+
setLastParsedKey(dateStr);
|
|
968
|
+
}
|
|
969
|
+
if (props.onValueChange) {
|
|
970
|
+
props.onValueChange(dateStr);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (props.onChange) {
|
|
974
|
+
props.onChange({
|
|
975
|
+
target: {
|
|
976
|
+
value: dateStr,
|
|
977
|
+
},
|
|
978
|
+
value: dateStr,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function getMonthWeeks(year: number, month: number) {
|
|
984
|
+
const startOn = props.weekStartsOn !== undefined ? props.weekStartsOn : 1;
|
|
985
|
+
const showOutside = props.showOutsideDays !== false;
|
|
986
|
+
const fixed = props.fixedWeeks !== false;
|
|
987
|
+
const todayStr = toDateString(new Date());
|
|
988
|
+
|
|
989
|
+
// Precompute disabled bounds
|
|
990
|
+
const minDateObj =
|
|
991
|
+
props.min || props.minValue
|
|
992
|
+
? parseDate(props.min || props.minValue)
|
|
993
|
+
: null;
|
|
994
|
+
const maxDateObj =
|
|
995
|
+
props.max || props.maxValue
|
|
996
|
+
? parseDate(props.max || props.maxValue)
|
|
997
|
+
: null;
|
|
998
|
+
const minTime = minDateObj
|
|
999
|
+
? new Date(
|
|
1000
|
+
minDateObj.getFullYear(),
|
|
1001
|
+
minDateObj.getMonth(),
|
|
1002
|
+
minDateObj.getDate()
|
|
1003
|
+
).getTime()
|
|
1004
|
+
: null;
|
|
1005
|
+
const maxTime = maxDateObj
|
|
1006
|
+
? new Date(
|
|
1007
|
+
maxDateObj.getFullYear(),
|
|
1008
|
+
maxDateObj.getMonth(),
|
|
1009
|
+
maxDateObj.getDate(),
|
|
1010
|
+
23,
|
|
1011
|
+
59,
|
|
1012
|
+
59
|
|
1013
|
+
).getTime()
|
|
1014
|
+
: null;
|
|
1015
|
+
|
|
1016
|
+
// Precompute single selection
|
|
1017
|
+
const selectedSingleStr =
|
|
1018
|
+
!props.range && !props.multiple
|
|
1019
|
+
? props.value !== undefined
|
|
1020
|
+
? typeof props.value === "string"
|
|
1021
|
+
? props.value
|
|
1022
|
+
: props.value instanceof Date
|
|
1023
|
+
? toDateString(props.value)
|
|
1024
|
+
: null
|
|
1025
|
+
: selectedSingle ||
|
|
1026
|
+
(props.defaultValue
|
|
1027
|
+
? typeof props.defaultValue === "string"
|
|
1028
|
+
? props.defaultValue
|
|
1029
|
+
: toDateString(props.defaultValue)
|
|
1030
|
+
: null)
|
|
1031
|
+
: null;
|
|
1032
|
+
|
|
1033
|
+
// Precompute range selection
|
|
1034
|
+
let rStart: string | null = null;
|
|
1035
|
+
let rEnd: string | null = null;
|
|
1036
|
+
if (props.range) {
|
|
1037
|
+
const rawS =
|
|
1038
|
+
props.value && props.value.start
|
|
1039
|
+
? props.value.start
|
|
1040
|
+
: props.defaultValue && props.defaultValue.start
|
|
1041
|
+
? props.defaultValue.start
|
|
1042
|
+
: rangeStart;
|
|
1043
|
+
const rawE =
|
|
1044
|
+
props.value && props.value.end
|
|
1045
|
+
? props.value.end
|
|
1046
|
+
: props.defaultValue && props.defaultValue.end
|
|
1047
|
+
? props.defaultValue.end
|
|
1048
|
+
: rangeEnd;
|
|
1049
|
+
rStart =
|
|
1050
|
+
typeof rawS === "string"
|
|
1051
|
+
? rawS
|
|
1052
|
+
: rawS instanceof Date
|
|
1053
|
+
? toDateString(rawS)
|
|
1054
|
+
: null;
|
|
1055
|
+
rEnd =
|
|
1056
|
+
typeof rawE === "string"
|
|
1057
|
+
? rawE
|
|
1058
|
+
: rawE instanceof Date
|
|
1059
|
+
? toDateString(rawE)
|
|
1060
|
+
: null;
|
|
1061
|
+
if (rStart && !rEnd && hoverDate) {
|
|
1062
|
+
rEnd = hoverDate;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
const rangeMin = rStart && rEnd ? (rStart < rEnd ? rStart : rEnd) : null;
|
|
1066
|
+
const rangeMax = rStart && rEnd ? (rStart < rEnd ? rEnd : rStart) : null;
|
|
1067
|
+
|
|
1068
|
+
// Precompute multiple selection Set
|
|
1069
|
+
const multipleSet: any = props.multiple
|
|
1070
|
+
? new Set(
|
|
1071
|
+
(props.value !== undefined
|
|
1072
|
+
? Array.isArray(props.value)
|
|
1073
|
+
? props.value
|
|
1074
|
+
: []
|
|
1075
|
+
: selectedMultiple
|
|
1076
|
+
).map((item: any) =>
|
|
1077
|
+
typeof item === "string"
|
|
1078
|
+
? item
|
|
1079
|
+
: item instanceof Date
|
|
1080
|
+
? toDateString(item)
|
|
1081
|
+
: ""
|
|
1082
|
+
)
|
|
1083
|
+
)
|
|
1084
|
+
: null;
|
|
1085
|
+
|
|
1086
|
+
// Precompute marker map
|
|
1087
|
+
const markerMap: any =
|
|
1088
|
+
props.dateMarkers && props.dateMarkers.length
|
|
1089
|
+
? new Map(props.dateMarkers.map((m: any) => [m.date, m]))
|
|
1090
|
+
: null;
|
|
1091
|
+
const firstDay = new Date(year, month - 1, 1);
|
|
1092
|
+
const lastDay = new Date(year, month, 0);
|
|
1093
|
+
const totalDays = lastDay.getDate();
|
|
1094
|
+
let firstDayIndex = firstDay.getDay() - startOn;
|
|
1095
|
+
if (firstDayIndex < 0) firstDayIndex += 7;
|
|
1096
|
+
const prevMonthLastDay = new Date(year, month - 1, 0).getDate();
|
|
1097
|
+
const days: any[] = [];
|
|
1098
|
+
const buildCell = (d: number, dDate: Date, isCurMonth: boolean) => {
|
|
1099
|
+
const dateStr = toDateString(dDate);
|
|
1100
|
+
const dTime = dDate.getTime();
|
|
1101
|
+
const isDisabled =
|
|
1102
|
+
props.disabled ||
|
|
1103
|
+
(minTime !== null && dTime < minTime) ||
|
|
1104
|
+
(maxTime !== null && dTime > maxTime) ||
|
|
1105
|
+
(props.isDateDisabled ? props.isDateDisabled(dDate) : false);
|
|
1106
|
+
const isUnavailable = props.isDateUnavailable
|
|
1107
|
+
? props.isDateUnavailable(dDate)
|
|
1108
|
+
: false;
|
|
1109
|
+
const isRangeS = props.range ? rStart === dateStr : false;
|
|
1110
|
+
const isRangeE = props.range ? rEnd === dateStr : false;
|
|
1111
|
+
const inRange =
|
|
1112
|
+
rangeMin !== null &&
|
|
1113
|
+
rangeMax !== null &&
|
|
1114
|
+
dateStr > rangeMin &&
|
|
1115
|
+
dateStr < rangeMax;
|
|
1116
|
+
const isSel = props.range
|
|
1117
|
+
? isRangeS || isRangeE
|
|
1118
|
+
: props.multiple
|
|
1119
|
+
? multipleSet
|
|
1120
|
+
? multipleSet.has(dateStr)
|
|
1121
|
+
: false
|
|
1122
|
+
: dateStr === selectedSingleStr;
|
|
1123
|
+
const rangeCls = !props.range
|
|
1124
|
+
? ""
|
|
1125
|
+
: (isRangeS && isRangeE) || (isRangeS && !rEnd)
|
|
1126
|
+
? "ttdk-calendar-cell-range-single"
|
|
1127
|
+
: isRangeS
|
|
1128
|
+
? "ttdk-calendar-cell-range-start"
|
|
1129
|
+
: isRangeE
|
|
1130
|
+
? "ttdk-calendar-cell-range-end"
|
|
1131
|
+
: inRange
|
|
1132
|
+
? "ttdk-calendar-cell-range"
|
|
1133
|
+
: "";
|
|
1134
|
+
const markerObj = markerMap ? markerMap.get(dateStr) || null : null;
|
|
1135
|
+
return {
|
|
1136
|
+
day: d,
|
|
1137
|
+
dateStr: dateStr,
|
|
1138
|
+
isCurrentMonth: isCurMonth,
|
|
1139
|
+
isOutside: !isCurMonth,
|
|
1140
|
+
date: dDate,
|
|
1141
|
+
disabled: isDisabled,
|
|
1142
|
+
unavailable: isUnavailable,
|
|
1143
|
+
today: dateStr === todayStr,
|
|
1144
|
+
selected: isSel,
|
|
1145
|
+
inRange: inRange,
|
|
1146
|
+
isRangeStart: isRangeS,
|
|
1147
|
+
isRangeEnd: isRangeE,
|
|
1148
|
+
rangeClass: rangeCls,
|
|
1149
|
+
marker: markerObj,
|
|
1150
|
+
};
|
|
1151
|
+
};
|
|
1152
|
+
for (let i = firstDayIndex - 1; i >= 0; i--) {
|
|
1153
|
+
const d = prevMonthLastDay - i;
|
|
1154
|
+
const prevMonthDate = new Date(year, month - 2, d);
|
|
1155
|
+
days.push(buildCell(d, prevMonthDate, false));
|
|
1156
|
+
}
|
|
1157
|
+
for (let i = 1; i <= totalDays; i++) {
|
|
1158
|
+
const curDate = new Date(year, month - 1, i);
|
|
1159
|
+
days.push(buildCell(i, curDate, true));
|
|
1160
|
+
}
|
|
1161
|
+
const targetLength = fixed ? 42 : Math.ceil(days.length / 7) * 7;
|
|
1162
|
+
let nextDayNum = 1;
|
|
1163
|
+
while (days.length < targetLength) {
|
|
1164
|
+
const nextMonthDate = new Date(year, month, nextDayNum);
|
|
1165
|
+
days.push(buildCell(nextDayNum, nextMonthDate, false));
|
|
1166
|
+
nextDayNum++;
|
|
1167
|
+
}
|
|
1168
|
+
const weeks: any[][] = [];
|
|
1169
|
+
for (let i = 0; i < days.length; i += 7) {
|
|
1170
|
+
weeks.push(days.slice(i, i + 7));
|
|
1171
|
+
}
|
|
1172
|
+
return weeks;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function getMonths() {
|
|
1176
|
+
const count =
|
|
1177
|
+
props.numberOfMonths && props.numberOfMonths > 1
|
|
1178
|
+
? props.numberOfMonths
|
|
1179
|
+
: 1;
|
|
1180
|
+
const list: any[] = [];
|
|
1181
|
+
for (let i = 0; i < count; i++) {
|
|
1182
|
+
const y = getViewYear(i);
|
|
1183
|
+
const m = getViewMonth(i);
|
|
1184
|
+
list.push({
|
|
1185
|
+
offset: i,
|
|
1186
|
+
year: y,
|
|
1187
|
+
month: m,
|
|
1188
|
+
label: getMonthLabel(y, m),
|
|
1189
|
+
monthName: getMonthOnlyLabel(y, m),
|
|
1190
|
+
isFirst: i === 0,
|
|
1191
|
+
isLast: i === count - 1,
|
|
1192
|
+
weeks: getMonthWeeks(y, m),
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
return list;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
useEffect(() => {
|
|
1199
|
+
const target = props.value !== undefined ? props.value : props.defaultValue;
|
|
1200
|
+
const valStr = target
|
|
1201
|
+
? props.range && typeof target === "object"
|
|
1202
|
+
? target.start
|
|
1203
|
+
: props.multiple && Array.isArray(target)
|
|
1204
|
+
? target[0]
|
|
1205
|
+
: target
|
|
1206
|
+
: null;
|
|
1207
|
+
const valKey =
|
|
1208
|
+
valStr instanceof Date
|
|
1209
|
+
? "" + valStr.getTime()
|
|
1210
|
+
: typeof valStr === "string"
|
|
1211
|
+
? valStr
|
|
1212
|
+
: "";
|
|
1213
|
+
if (valKey !== lastParsedKey) {
|
|
1214
|
+
setLastParsedKey(valKey);
|
|
1215
|
+
if (valStr) {
|
|
1216
|
+
const parsed = parseDate(valStr);
|
|
1217
|
+
if (parsed) {
|
|
1218
|
+
if (!props.year) {
|
|
1219
|
+
setViewYear(parsed.getFullYear());
|
|
1220
|
+
}
|
|
1221
|
+
if (!props.month) {
|
|
1222
|
+
setViewMonth(parsed.getMonth() + 1);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
return (
|
|
1230
|
+
<div
|
|
1231
|
+
role="group"
|
|
1232
|
+
aria-label="Kalender"
|
|
1233
|
+
id={props.id}
|
|
1234
|
+
className={`ttdk-calendar ttdk-calendar-${
|
|
1235
|
+
props.variant || "bordered"
|
|
1236
|
+
} ttdk-calendar-${props.size || "md"} ttdk-calendar-rounded-${
|
|
1237
|
+
props.rounded || "md"
|
|
1238
|
+
} ${props.disabled ? "ttdk-calendar-disabled" : ""} ${
|
|
1239
|
+
props.className || ""
|
|
1240
|
+
}`.trim()}
|
|
1241
|
+
style={{
|
|
1242
|
+
...buildUiVars(props.ui),
|
|
1243
|
+
...props.style,
|
|
1244
|
+
}}
|
|
1245
|
+
>
|
|
1246
|
+
{getViewMode() === "day" ? (
|
|
1247
|
+
<div className="ttdk-calendar-months">
|
|
1248
|
+
{getMonths()?.map((monthItem) => (
|
|
1249
|
+
<div
|
|
1250
|
+
className="ttdk-calendar-month"
|
|
1251
|
+
key={`${monthItem.year}-${monthItem.month}`}
|
|
1252
|
+
>
|
|
1253
|
+
<div
|
|
1254
|
+
className={`ttdk-calendar-header ${
|
|
1255
|
+
props.headerClass || ""
|
|
1256
|
+
}`.trim()}
|
|
1257
|
+
style={{
|
|
1258
|
+
...buildUiStyle(props.headerStyle),
|
|
1259
|
+
}}
|
|
1260
|
+
>
|
|
1261
|
+
<div className="ttdk-calendar-nav-group">
|
|
1262
|
+
{monthItem.isFirst ? (
|
|
1263
|
+
<>
|
|
1264
|
+
{props.yearControls !== false ? (
|
|
1265
|
+
<button
|
|
1266
|
+
type="button"
|
|
1267
|
+
aria-label="Tahun Sebelumnya"
|
|
1268
|
+
className={`ttdk-calendar-nav-btn ${
|
|
1269
|
+
props.navBtnClass || ""
|
|
1270
|
+
}`.trim()}
|
|
1271
|
+
style={{
|
|
1272
|
+
...buildUiStyle(props.navBtnStyle),
|
|
1273
|
+
}}
|
|
1274
|
+
disabled={props.disabled}
|
|
1275
|
+
onClick={(event) => prevYear()}
|
|
1276
|
+
>
|
|
1277
|
+
<svg
|
|
1278
|
+
viewBox="0 0 24 24"
|
|
1279
|
+
fill="none"
|
|
1280
|
+
stroke="currentColor"
|
|
1281
|
+
>
|
|
1282
|
+
<polyline points="11 17 6 12 11 7" />
|
|
1283
|
+
<polyline points="18 17 13 12 18 7" />
|
|
1284
|
+
</svg>
|
|
1285
|
+
</button>
|
|
1286
|
+
) : null}
|
|
1287
|
+
<button
|
|
1288
|
+
type="button"
|
|
1289
|
+
aria-label="Bulan Sebelumnya"
|
|
1290
|
+
className={`ttdk-calendar-nav-btn ${
|
|
1291
|
+
props.navBtnClass || ""
|
|
1292
|
+
}`.trim()}
|
|
1293
|
+
style={{
|
|
1294
|
+
...buildUiStyle(props.navBtnStyle),
|
|
1295
|
+
}}
|
|
1296
|
+
disabled={props.disabled}
|
|
1297
|
+
onClick={(event) => prevMonth()}
|
|
1298
|
+
>
|
|
1299
|
+
<svg
|
|
1300
|
+
viewBox="0 0 24 24"
|
|
1301
|
+
fill="none"
|
|
1302
|
+
stroke="currentColor"
|
|
1303
|
+
>
|
|
1304
|
+
<polyline points="15 18 9 12 15 6" />
|
|
1305
|
+
</svg>
|
|
1306
|
+
</button>
|
|
1307
|
+
</>
|
|
1308
|
+
) : null}
|
|
1309
|
+
</div>
|
|
1310
|
+
<div className="ttdk-calendar-title-group">
|
|
1311
|
+
<button
|
|
1312
|
+
type="button"
|
|
1313
|
+
className="ttdk-calendar-title-btn"
|
|
1314
|
+
aria-label="Pilih Bulan"
|
|
1315
|
+
disabled={props.disabled}
|
|
1316
|
+
onClick={(event) => openMonthView()}
|
|
1317
|
+
>
|
|
1318
|
+
{monthItem.monthName}
|
|
1319
|
+
</button>
|
|
1320
|
+
<button
|
|
1321
|
+
type="button"
|
|
1322
|
+
className="ttdk-calendar-title-btn"
|
|
1323
|
+
aria-label="Pilih Tahun"
|
|
1324
|
+
disabled={props.disabled}
|
|
1325
|
+
onClick={(event) => openYearView()}
|
|
1326
|
+
>
|
|
1327
|
+
{monthItem.year}
|
|
1328
|
+
</button>
|
|
1329
|
+
</div>
|
|
1330
|
+
<div className="ttdk-calendar-nav-group">
|
|
1331
|
+
{monthItem.isLast ? (
|
|
1332
|
+
<>
|
|
1333
|
+
<button
|
|
1334
|
+
type="button"
|
|
1335
|
+
aria-label="Bulan Berikutnya"
|
|
1336
|
+
className={`ttdk-calendar-nav-btn ${
|
|
1337
|
+
props.navBtnClass || ""
|
|
1338
|
+
}`.trim()}
|
|
1339
|
+
style={{
|
|
1340
|
+
...buildUiStyle(props.navBtnStyle),
|
|
1341
|
+
}}
|
|
1342
|
+
disabled={props.disabled}
|
|
1343
|
+
onClick={(event) => nextMonth()}
|
|
1344
|
+
>
|
|
1345
|
+
<svg
|
|
1346
|
+
viewBox="0 0 24 24"
|
|
1347
|
+
fill="none"
|
|
1348
|
+
stroke="currentColor"
|
|
1349
|
+
>
|
|
1350
|
+
<polyline points="9 18 15 12 9 6" />
|
|
1351
|
+
</svg>
|
|
1352
|
+
</button>
|
|
1353
|
+
{props.yearControls !== false ? (
|
|
1354
|
+
<button
|
|
1355
|
+
type="button"
|
|
1356
|
+
aria-label="Tahun Berikutnya"
|
|
1357
|
+
className={`ttdk-calendar-nav-btn ${
|
|
1358
|
+
props.navBtnClass || ""
|
|
1359
|
+
}`.trim()}
|
|
1360
|
+
style={{
|
|
1361
|
+
...buildUiStyle(props.navBtnStyle),
|
|
1362
|
+
}}
|
|
1363
|
+
disabled={props.disabled}
|
|
1364
|
+
onClick={(event) => nextYear()}
|
|
1365
|
+
>
|
|
1366
|
+
<svg
|
|
1367
|
+
viewBox="0 0 24 24"
|
|
1368
|
+
fill="none"
|
|
1369
|
+
stroke="currentColor"
|
|
1370
|
+
>
|
|
1371
|
+
<polyline points="13 17 18 12 13 7" />
|
|
1372
|
+
<polyline points="6 17 11 12 6 7" />
|
|
1373
|
+
</svg>
|
|
1374
|
+
</button>
|
|
1375
|
+
) : null}
|
|
1376
|
+
</>
|
|
1377
|
+
) : null}
|
|
1378
|
+
</div>
|
|
1379
|
+
</div>
|
|
1380
|
+
<table className="ttdk-calendar-table" role="grid">
|
|
1381
|
+
<thead>
|
|
1382
|
+
<tr className="ttdk-calendar-thead-row" role="row">
|
|
1383
|
+
{getWeekDays()?.map((weekday) => (
|
|
1384
|
+
<th
|
|
1385
|
+
className="ttdk-calendar-head-cell"
|
|
1386
|
+
role="columnheader"
|
|
1387
|
+
key={weekday}
|
|
1388
|
+
aria-label={weekday}
|
|
1389
|
+
>
|
|
1390
|
+
{weekday}
|
|
1391
|
+
</th>
|
|
1392
|
+
))}
|
|
1393
|
+
</tr>
|
|
1394
|
+
</thead>
|
|
1395
|
+
<tbody className="ttdk-calendar-tbody" role="rowgroup">
|
|
1396
|
+
{monthItem.weeks?.map((week: any, wIdx: any) => (
|
|
1397
|
+
<tr className="ttdk-calendar-row" role="row" key={wIdx}>
|
|
1398
|
+
{week?.map((cell, cIdx) => (
|
|
1399
|
+
<td
|
|
1400
|
+
role="gridcell"
|
|
1401
|
+
key={`${cell.dateStr}-${cIdx}`}
|
|
1402
|
+
className={`ttdk-calendar-cell ${cell.rangeClass} ${
|
|
1403
|
+
props.cellClass || ""
|
|
1404
|
+
}`.trim()}
|
|
1405
|
+
aria-selected={cell.selected ? "true" : "false"}
|
|
1406
|
+
aria-disabled={cell.disabled ? "true" : "false"}
|
|
1407
|
+
style={{
|
|
1408
|
+
...buildUiStyle(props.cellStyle),
|
|
1409
|
+
}}
|
|
1410
|
+
onMouseEnter={(event) => handleDayHover(cell.dateStr)}
|
|
1411
|
+
onMouseLeave={(event) => handleDayHoverLeave()}
|
|
1412
|
+
>
|
|
1413
|
+
<button
|
|
1414
|
+
type="button"
|
|
1415
|
+
className={`ttdk-calendar-day ${
|
|
1416
|
+
cell.selected ? "ttdk-calendar-day-selected" : ""
|
|
1417
|
+
} ${cell.today ? "ttdk-calendar-day-today" : ""} ${
|
|
1418
|
+
cell.inRange ? "ttdk-calendar-day-in-range" : ""
|
|
1419
|
+
} ${
|
|
1420
|
+
cell.isRangeStart
|
|
1421
|
+
? "ttdk-calendar-day-range-start"
|
|
1422
|
+
: ""
|
|
1423
|
+
} ${
|
|
1424
|
+
cell.isRangeEnd
|
|
1425
|
+
? "ttdk-calendar-day-range-end"
|
|
1426
|
+
: ""
|
|
1427
|
+
} ${
|
|
1428
|
+
cell.isOutside ? "ttdk-calendar-day-outside" : ""
|
|
1429
|
+
} ${
|
|
1430
|
+
cell.disabled ? "ttdk-calendar-day-disabled" : ""
|
|
1431
|
+
} ${
|
|
1432
|
+
cell.unavailable
|
|
1433
|
+
? "ttdk-calendar-day-unavailable"
|
|
1434
|
+
: ""
|
|
1435
|
+
}`}
|
|
1436
|
+
disabled={cell.disabled}
|
|
1437
|
+
tabIndex={cell.selected || cell.today ? 0 : -1}
|
|
1438
|
+
onClick={(event) => handleSelectDate(cell.dateStr)}
|
|
1439
|
+
>
|
|
1440
|
+
<span>{cell.day}</span>
|
|
1441
|
+
{cell.marker ? (
|
|
1442
|
+
<span
|
|
1443
|
+
className="ttdk-calendar-marker-dot"
|
|
1444
|
+
style={{
|
|
1445
|
+
backgroundColor: cell.marker
|
|
1446
|
+
? cell.marker.color
|
|
1447
|
+
: undefined,
|
|
1448
|
+
}}
|
|
1449
|
+
title={
|
|
1450
|
+
cell.marker ? cell.marker.tooltip : undefined
|
|
1451
|
+
}
|
|
1452
|
+
/>
|
|
1453
|
+
) : null}
|
|
1454
|
+
</button>
|
|
1455
|
+
</td>
|
|
1456
|
+
))}
|
|
1457
|
+
</tr>
|
|
1458
|
+
))}
|
|
1459
|
+
</tbody>
|
|
1460
|
+
</table>
|
|
1461
|
+
</div>
|
|
1462
|
+
))}
|
|
1463
|
+
</div>
|
|
1464
|
+
) : null}
|
|
1465
|
+
{getViewMode() === "month" ? (
|
|
1466
|
+
<div className="ttdk-calendar-month-picker-view">
|
|
1467
|
+
<div
|
|
1468
|
+
className={`ttdk-calendar-header ${props.headerClass || ""}`.trim()}
|
|
1469
|
+
style={{
|
|
1470
|
+
...buildUiStyle(props.headerStyle),
|
|
1471
|
+
}}
|
|
1472
|
+
>
|
|
1473
|
+
<div className="ttdk-calendar-nav-group">
|
|
1474
|
+
<button
|
|
1475
|
+
type="button"
|
|
1476
|
+
aria-label="Tahun Sebelumnya"
|
|
1477
|
+
className={`ttdk-calendar-nav-btn ${
|
|
1478
|
+
props.navBtnClass || ""
|
|
1479
|
+
}`.trim()}
|
|
1480
|
+
style={{
|
|
1481
|
+
...buildUiStyle(props.navBtnStyle),
|
|
1482
|
+
}}
|
|
1483
|
+
disabled={props.disabled}
|
|
1484
|
+
onClick={(event) => prevYear()}
|
|
1485
|
+
>
|
|
1486
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
1487
|
+
<polyline points="15 18 9 12 15 6" />
|
|
1488
|
+
</svg>
|
|
1489
|
+
</button>
|
|
1490
|
+
</div>
|
|
1491
|
+
<div className="ttdk-calendar-title-group">
|
|
1492
|
+
<button
|
|
1493
|
+
type="button"
|
|
1494
|
+
className="ttdk-calendar-title-btn"
|
|
1495
|
+
aria-label="Pilih Tahun"
|
|
1496
|
+
disabled={props.disabled}
|
|
1497
|
+
onClick={(event) => openYearView()}
|
|
1498
|
+
>
|
|
1499
|
+
{getViewYear()}
|
|
1500
|
+
</button>
|
|
1501
|
+
</div>
|
|
1502
|
+
<div className="ttdk-calendar-nav-group">
|
|
1503
|
+
<button
|
|
1504
|
+
type="button"
|
|
1505
|
+
aria-label="Tahun Berikutnya"
|
|
1506
|
+
className={`ttdk-calendar-nav-btn ${
|
|
1507
|
+
props.navBtnClass || ""
|
|
1508
|
+
}`.trim()}
|
|
1509
|
+
style={{
|
|
1510
|
+
...buildUiStyle(props.navBtnStyle),
|
|
1511
|
+
}}
|
|
1512
|
+
disabled={props.disabled}
|
|
1513
|
+
onClick={(event) => nextYear()}
|
|
1514
|
+
>
|
|
1515
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
1516
|
+
<polyline points="9 18 15 12 9 6" />
|
|
1517
|
+
</svg>
|
|
1518
|
+
</button>
|
|
1519
|
+
</div>
|
|
1520
|
+
</div>
|
|
1521
|
+
<div className="ttdk-calendar-grid-picker" role="grid">
|
|
1522
|
+
{getMonthsList()?.map((m: any) => (
|
|
1523
|
+
<button
|
|
1524
|
+
type="button"
|
|
1525
|
+
key={m.month}
|
|
1526
|
+
className={`ttdk-calendar-picker-item ${
|
|
1527
|
+
m.isSelected ? "ttdk-calendar-picker-item-selected" : ""
|
|
1528
|
+
} ${m.isCurrent ? "ttdk-calendar-picker-item-current" : ""}`}
|
|
1529
|
+
disabled={props.disabled}
|
|
1530
|
+
aria-label={m.fullName}
|
|
1531
|
+
onClick={(event) => selectMonth(m.month)}
|
|
1532
|
+
>
|
|
1533
|
+
{m.label}
|
|
1534
|
+
</button>
|
|
1535
|
+
))}
|
|
1536
|
+
</div>
|
|
1537
|
+
</div>
|
|
1538
|
+
) : null}
|
|
1539
|
+
{getViewMode() === "year" ? (
|
|
1540
|
+
<div className="ttdk-calendar-year-picker-view">
|
|
1541
|
+
<div
|
|
1542
|
+
className={`ttdk-calendar-header ${props.headerClass || ""}`.trim()}
|
|
1543
|
+
style={{
|
|
1544
|
+
...buildUiStyle(props.headerStyle),
|
|
1545
|
+
}}
|
|
1546
|
+
>
|
|
1547
|
+
<div className="ttdk-calendar-nav-group">
|
|
1548
|
+
<button
|
|
1549
|
+
type="button"
|
|
1550
|
+
aria-label="Rentang Tahun Sebelumnya"
|
|
1551
|
+
className={`ttdk-calendar-nav-btn ${
|
|
1552
|
+
props.navBtnClass || ""
|
|
1553
|
+
}`.trim()}
|
|
1554
|
+
style={{
|
|
1555
|
+
...buildUiStyle(props.navBtnStyle),
|
|
1556
|
+
}}
|
|
1557
|
+
disabled={props.disabled}
|
|
1558
|
+
onClick={(event) => prevDecade()}
|
|
1559
|
+
>
|
|
1560
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
1561
|
+
<polyline points="15 18 9 12 15 6" />
|
|
1562
|
+
</svg>
|
|
1563
|
+
</button>
|
|
1564
|
+
</div>
|
|
1565
|
+
<div className="ttdk-calendar-title-group">
|
|
1566
|
+
<span className="ttdk-calendar-title">{getDecadeLabel()}</span>
|
|
1567
|
+
</div>
|
|
1568
|
+
<div className="ttdk-calendar-nav-group">
|
|
1569
|
+
<button
|
|
1570
|
+
type="button"
|
|
1571
|
+
aria-label="Rentang Tahun Berikutnya"
|
|
1572
|
+
className={`ttdk-calendar-nav-btn ${
|
|
1573
|
+
props.navBtnClass || ""
|
|
1574
|
+
}`.trim()}
|
|
1575
|
+
style={{
|
|
1576
|
+
...buildUiStyle(props.navBtnStyle),
|
|
1577
|
+
}}
|
|
1578
|
+
disabled={props.disabled}
|
|
1579
|
+
onClick={(event) => nextDecade()}
|
|
1580
|
+
>
|
|
1581
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
1582
|
+
<polyline points="9 18 15 12 9 6" />
|
|
1583
|
+
</svg>
|
|
1584
|
+
</button>
|
|
1585
|
+
</div>
|
|
1586
|
+
</div>
|
|
1587
|
+
<div className="ttdk-calendar-grid-picker" role="grid">
|
|
1588
|
+
{getYearsList()?.map((y: any) => (
|
|
1589
|
+
<button
|
|
1590
|
+
type="button"
|
|
1591
|
+
key={y.year}
|
|
1592
|
+
className={`ttdk-calendar-picker-item ${
|
|
1593
|
+
y.isSelected ? "ttdk-calendar-picker-item-selected" : ""
|
|
1594
|
+
} ${y.isCurrent ? "ttdk-calendar-picker-item-current" : ""}`}
|
|
1595
|
+
disabled={props.disabled}
|
|
1596
|
+
aria-label={y.label}
|
|
1597
|
+
onClick={(event) => selectYear(y.year)}
|
|
1598
|
+
>
|
|
1599
|
+
{y.label}
|
|
1600
|
+
</button>
|
|
1601
|
+
))}
|
|
1602
|
+
</div>
|
|
1603
|
+
</div>
|
|
1604
|
+
) : null}
|
|
1605
|
+
</div>
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
export default Calendar;
|
|
1610
|
+
|
|
1611
|
+
export { Calendar };
|