design-system-next 2.16.3 → 2.17.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/design-system-next.es.js +8148 -7277
- package/dist/design-system-next.es.js.gz +0 -0
- package/dist/design-system-next.umd.js +13 -13
- package/dist/design-system-next.umd.js.gz +0 -0
- package/dist/main.css +1 -1
- package/dist/main.css.gz +0 -0
- package/dist/package.json.d.ts +1 -1
- package/package.json +1 -1
- package/src/assets/styles/tailwind.css +20 -0
- package/src/components/attribute-filter/attribute-filter.ts +5 -1
- package/src/components/attribute-filter/attribute-filter.vue +3 -3
- package/src/components/calendar/calendar.ts +1 -1
- package/src/components/calendar/calendar.vue +41 -18
- package/src/components/calendar/use-calendar.ts +13 -6
- package/src/components/calendar-cell/calendar-cell.ts +4 -0
- package/src/components/calendar-cell/use-calendar-cell.ts +21 -2
- package/src/components/card/card.ts +5 -0
- package/src/components/card/use-card.ts +15 -3
- package/src/components/date-picker/date-picker.ts +23 -0
- package/src/components/date-picker/reusable-calendar/reusable-calendar.ts +121 -0
- package/src/components/date-picker/reusable-calendar/reusable-calendar.vue +192 -0
- package/src/components/date-picker/reusable-calendar/use-reusable-calendar.ts +366 -0
- package/src/components/date-picker/tabs/DatePickerCalendarTab.vue +321 -0
- package/src/components/date-picker/tabs/DatePickerMonthTab.vue +60 -0
- package/src/components/date-picker/tabs/DatePickerYearTab.vue +114 -0
- package/src/components/icon/icon.ts +36 -0
- package/src/components/icon/icon.vue +12 -0
- package/src/components/icon/use-icon.ts +67 -0
- package/src/components/lozenge/lozenge.ts +1 -1
- package/src/components/popper/popper.ts +12 -0
- package/src/components/popper/popper.vue +36 -0
- package/src/components/popper/use-popper.ts +16 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { computed, ref, watch } from 'vue';
|
|
2
|
+
import { toRefs } from 'vue';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
|
|
5
|
+
import type { SetupContext } from 'vue';
|
|
6
|
+
import type { ReusableCalendarEmitTypes, ReusableCalendarPropTypes } from './reusable-calendar';
|
|
7
|
+
|
|
8
|
+
export const useReusableCalendar = (
|
|
9
|
+
props: ReusableCalendarPropTypes,
|
|
10
|
+
emit: SetupContext<ReusableCalendarEmitTypes>['emit']
|
|
11
|
+
) => {
|
|
12
|
+
// Extract reactive props
|
|
13
|
+
const {
|
|
14
|
+
mode,
|
|
15
|
+
} = toRefs(props);
|
|
16
|
+
|
|
17
|
+
// Internal state
|
|
18
|
+
const currentTab = ref<string>('tab-calendar');
|
|
19
|
+
const calendarTabPageData = ref({
|
|
20
|
+
selectedMonth: props.selectedMonth ?? dayjs().month(),
|
|
21
|
+
selectedYear: props.selectedYear ?? dayjs().year(),
|
|
22
|
+
calendarDays: [] as Array<{ date: Date; inactive: boolean }>,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const yearTabPageData = ref({
|
|
26
|
+
currentPage: 0,
|
|
27
|
+
itemsPerPage: 12,
|
|
28
|
+
yearsArray: Array.from(
|
|
29
|
+
{ length: props.minMaxYear.max - props.minMaxYear.min + 1 },
|
|
30
|
+
(_, index) => props.minMaxYear.min + index,
|
|
31
|
+
).filter((year) => year <= props.minMaxYear.max && year >= props.minMaxYear.min),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Computed properties
|
|
35
|
+
const showMonthInput = computed(() => mode.value === 'full' || mode.value === 'month-year');
|
|
36
|
+
const showDateInput = computed(() => mode.value === 'full');
|
|
37
|
+
const showYearInput = computed(() => mode.value === 'full' || mode.value === 'month-year' || mode.value === 'year-only');
|
|
38
|
+
|
|
39
|
+
const calendarDays = computed(() => calendarTabPageData.value.calendarDays);
|
|
40
|
+
const selectedMonthComputed = computed(() => calendarTabPageData.value.selectedMonth);
|
|
41
|
+
const selectedYearComputed = computed(() => calendarTabPageData.value.selectedYear);
|
|
42
|
+
const selectedDayComputed = computed(() => props.modelValue ? dayjs(props.modelValue, props.format).date() : 0);
|
|
43
|
+
|
|
44
|
+
// Calendar navigation computed properties
|
|
45
|
+
const calendarTabIsMinMonth = computed(() =>
|
|
46
|
+
dayjs()
|
|
47
|
+
.year(calendarTabPageData.value.selectedYear)
|
|
48
|
+
.month(calendarTabPageData.value.selectedMonth)
|
|
49
|
+
.isSame(dayjs().year(props.minMaxYear.min).month(0), 'month'),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const calendarTabIsMaxMonth = computed(() =>
|
|
53
|
+
dayjs()
|
|
54
|
+
.year(calendarTabPageData.value.selectedYear)
|
|
55
|
+
.month(calendarTabPageData.value.selectedMonth)
|
|
56
|
+
.isSame(dayjs().year(props.minMaxYear.max).month(11), 'month'),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const yearTabIsPreviousButtonDisabled = computed(() => {
|
|
60
|
+
return yearTabPageData.value.currentPage === 0;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const yearTabIsNextButtonDisabled = computed(() => {
|
|
64
|
+
return (
|
|
65
|
+
(yearTabPageData.value.currentPage + 1) * yearTabPageData.value.itemsPerPage >=
|
|
66
|
+
yearTabPageData.value.yearsArray.length
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Calendar update function
|
|
71
|
+
const calendarTabUpdateCalendar = () => {
|
|
72
|
+
const firstDay = dayjs()
|
|
73
|
+
.year(calendarTabPageData.value.selectedYear)
|
|
74
|
+
.month(calendarTabPageData.value.selectedMonth)
|
|
75
|
+
.startOf('month')
|
|
76
|
+
.day();
|
|
77
|
+
|
|
78
|
+
const lastDate = dayjs()
|
|
79
|
+
.year(calendarTabPageData.value.selectedYear)
|
|
80
|
+
.month(calendarTabPageData.value.selectedMonth)
|
|
81
|
+
.endOf('month')
|
|
82
|
+
.date();
|
|
83
|
+
|
|
84
|
+
const prevMonthDays = firstDay;
|
|
85
|
+
const totalDays = prevMonthDays + lastDate;
|
|
86
|
+
const nextMonthDays = totalDays % 7 === 0 ? 0 : 7 - (totalDays % 7);
|
|
87
|
+
|
|
88
|
+
const calendar = [];
|
|
89
|
+
|
|
90
|
+
// Previous month days
|
|
91
|
+
for (let index = prevMonthDays; index > 0; index--) {
|
|
92
|
+
const date = dayjs()
|
|
93
|
+
.year(calendarTabPageData.value.selectedYear)
|
|
94
|
+
.month(calendarTabPageData.value.selectedMonth)
|
|
95
|
+
.date(-index + 1);
|
|
96
|
+
|
|
97
|
+
calendar.push({ date, inactive: true });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Current month days
|
|
101
|
+
for (let index = 1; index <= lastDate; index++) {
|
|
102
|
+
const date = dayjs()
|
|
103
|
+
.year(calendarTabPageData.value.selectedYear)
|
|
104
|
+
.month(calendarTabPageData.value.selectedMonth)
|
|
105
|
+
.date(index);
|
|
106
|
+
|
|
107
|
+
calendar.push({ date, inactive: false });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Next month days
|
|
111
|
+
for (let index = 1; index <= nextMonthDays; index++) {
|
|
112
|
+
const date = dayjs()
|
|
113
|
+
.year(calendarTabPageData.value.selectedYear)
|
|
114
|
+
.month(calendarTabPageData.value.selectedMonth + 1)
|
|
115
|
+
.date(index);
|
|
116
|
+
|
|
117
|
+
calendar.push({ date, inactive: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
calendarTabPageData.value.calendarDays = calendar.map((day) => ({ ...day, date: day.date.toDate() }));
|
|
121
|
+
goBackToCalendarTab();
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const goBackToCalendarTab = () => {
|
|
125
|
+
if(props.mode === 'full' && currentTab.value !== 'tab-calendar') {
|
|
126
|
+
currentTab.value = 'tab-calendar';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Navigation functions
|
|
131
|
+
const calendarTabPrevMonth = () => {
|
|
132
|
+
if (calendarTabIsMinMonth.value) return;
|
|
133
|
+
|
|
134
|
+
const newDate = dayjs()
|
|
135
|
+
.year(calendarTabPageData.value.selectedYear)
|
|
136
|
+
.month(calendarTabPageData.value.selectedMonth)
|
|
137
|
+
.subtract(1, 'month');
|
|
138
|
+
|
|
139
|
+
calendarTabPageData.value.selectedMonth = newDate.month();
|
|
140
|
+
calendarTabPageData.value.selectedYear = newDate.year();
|
|
141
|
+
|
|
142
|
+
calendarTabUpdateCalendar();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const calendarTabNextMonth = () => {
|
|
146
|
+
if (calendarTabIsMaxMonth.value) return;
|
|
147
|
+
|
|
148
|
+
const newDate = dayjs()
|
|
149
|
+
.year(calendarTabPageData.value.selectedYear)
|
|
150
|
+
.month(calendarTabPageData.value.selectedMonth)
|
|
151
|
+
.add(1, 'month');
|
|
152
|
+
|
|
153
|
+
calendarTabPageData.value.selectedMonth = newDate.month();
|
|
154
|
+
calendarTabPageData.value.selectedYear = newDate.year();
|
|
155
|
+
|
|
156
|
+
calendarTabUpdateCalendar();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const yearTabGoToPreviousPage = () => {
|
|
160
|
+
if (yearTabPageData.value.currentPage > 0) {
|
|
161
|
+
yearTabPageData.value.currentPage--;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const yearTabGoToNextPage = () => {
|
|
166
|
+
if (
|
|
167
|
+
(yearTabPageData.value.currentPage + 1) * yearTabPageData.value.itemsPerPage <
|
|
168
|
+
yearTabPageData.value.yearsArray.length
|
|
169
|
+
) {
|
|
170
|
+
yearTabPageData.value.currentPage++;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Tab functions
|
|
175
|
+
const getTabClasses = (tab: string) => {
|
|
176
|
+
return {
|
|
177
|
+
'spr-cursor-pointer': true,
|
|
178
|
+
'spr-background-color-pressed !spr-shadow-button': currentTab.value === tab,
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const handleTabClick = (tab: string) => {
|
|
183
|
+
if (currentTab.value === tab && props.mode === 'full') {
|
|
184
|
+
currentTab.value = 'tab-calendar';
|
|
185
|
+
} else {
|
|
186
|
+
currentTab.value = tab;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Month list for display
|
|
191
|
+
const monthsList = ref(
|
|
192
|
+
Array.from({ length: 12 }, (_, i) => ({
|
|
193
|
+
text: dayjs().month(i).format('MMM'),
|
|
194
|
+
fullText: dayjs().month(i).format('MMMM'),
|
|
195
|
+
monthValue: i,
|
|
196
|
+
})),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const getMonthObject = (field: string, value: string | number) => {
|
|
200
|
+
return monthsList.value.find(
|
|
201
|
+
(_month: { text: string; fullText: string; monthValue: number }) =>
|
|
202
|
+
_month[field as keyof typeof _month].toString().toLowerCase() === value.toString().toLowerCase(),
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Event handlers
|
|
207
|
+
const handleCalendarDateUpdate = (day: { date: Date; inactive: boolean }) => {
|
|
208
|
+
const selectedDate = dayjs(day.date);
|
|
209
|
+
|
|
210
|
+
// Update internal state
|
|
211
|
+
calendarTabPageData.value.selectedMonth = day.date.getMonth();
|
|
212
|
+
calendarTabPageData.value.selectedYear = day.date.getFullYear();
|
|
213
|
+
|
|
214
|
+
// Update calendar display
|
|
215
|
+
calendarTabUpdateCalendar();
|
|
216
|
+
|
|
217
|
+
// Emit events
|
|
218
|
+
emit('update:month', day.date.getMonth());
|
|
219
|
+
emit('update:year', day.date.getFullYear());
|
|
220
|
+
emit('update:day', day.date.getDate());
|
|
221
|
+
emit('update:modelValue', selectedDate.format(props.format));
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const handleCalendarMonthUpdate = (month: number) => {
|
|
225
|
+
calendarTabPageData.value.selectedMonth = month;
|
|
226
|
+
emit('update:month', month);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const handleCalendarYearUpdate = (year: number) => {
|
|
230
|
+
calendarTabPageData.value.selectedYear = year;
|
|
231
|
+
emit('update:year', year);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const handleMonthTabMonthUpdate = (month: number) => {
|
|
235
|
+
calendarTabPageData.value.selectedMonth = month;
|
|
236
|
+
calendarTabUpdateCalendar();
|
|
237
|
+
emit('update:month', month);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const handleYearTabYearUpdate = (year: number) => {
|
|
241
|
+
calendarTabPageData.value.selectedYear = year;
|
|
242
|
+
calendarTabUpdateCalendar();
|
|
243
|
+
emit('update:year', year);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const handleYearTabCurrentPageUpdate = (page: number) => {
|
|
247
|
+
yearTabPageData.value.currentPage = page;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Wrapper functions for event handlers
|
|
251
|
+
const handleCalendarDateUpdateWrapper = (day: { date: Date; inactive: boolean }) => {
|
|
252
|
+
handleCalendarDateUpdate(day);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const handleCalendarMonthUpdateWrapper = (month: number) => {
|
|
256
|
+
handleCalendarMonthUpdate(month);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const handleCalendarYearUpdateWrapper = (year: number) => {
|
|
260
|
+
handleCalendarYearUpdate(year);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const handleMonthTabMonthUpdateWrapper = (month: number) => {
|
|
264
|
+
handleMonthTabMonthUpdate(month);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const handleYearTabYearUpdateWrapper = (year: number) => {
|
|
268
|
+
handleYearTabYearUpdate(year);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const handleYearTabCurrentPageUpdateWrapper = (page: number) => {
|
|
272
|
+
handleYearTabCurrentPageUpdate(page);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// Initialize based on mode
|
|
276
|
+
const getInitialTab = () => {
|
|
277
|
+
switch (props.mode) {
|
|
278
|
+
case 'month-year':
|
|
279
|
+
return 'tab-months';
|
|
280
|
+
case 'year-only':
|
|
281
|
+
return 'tab-years';
|
|
282
|
+
default:
|
|
283
|
+
return 'tab-calendar';
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Initialize component
|
|
288
|
+
currentTab.value = getInitialTab();
|
|
289
|
+
calendarTabUpdateCalendar();
|
|
290
|
+
|
|
291
|
+
// Watch for prop changes
|
|
292
|
+
watch(() => props.modelValue, (newValue) => {
|
|
293
|
+
if (newValue) {
|
|
294
|
+
const parsedDate = dayjs(newValue, props.format);
|
|
295
|
+
if (parsedDate.isValid()) {
|
|
296
|
+
calendarTabPageData.value.selectedMonth = parsedDate.month();
|
|
297
|
+
calendarTabPageData.value.selectedYear = parsedDate.year();
|
|
298
|
+
calendarTabUpdateCalendar();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
watch(() => props.selectedMonth, (newMonth) => {
|
|
304
|
+
if (newMonth !== undefined) {
|
|
305
|
+
calendarTabPageData.value.selectedMonth = newMonth;
|
|
306
|
+
calendarTabUpdateCalendar();
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
watch(() => props.selectedYear, (newYear) => {
|
|
311
|
+
if (newYear !== undefined) {
|
|
312
|
+
calendarTabPageData.value.selectedYear = newYear;
|
|
313
|
+
calendarTabUpdateCalendar();
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
watch(() => props.mode, () => {
|
|
318
|
+
currentTab.value = getInitialTab();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
watch(() => props.minMaxYear, () => {
|
|
322
|
+
yearTabPageData.value.yearsArray = Array.from(
|
|
323
|
+
{ length: props.minMaxYear.max - props.minMaxYear.min + 1 },
|
|
324
|
+
(_, index) => props.minMaxYear.min + index,
|
|
325
|
+
).filter((year) => year <= props.minMaxYear.max && year >= props.minMaxYear.min);
|
|
326
|
+
|
|
327
|
+
yearTabPageData.value.currentPage = 0;
|
|
328
|
+
}, { deep: true });
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
// State
|
|
332
|
+
currentTab,
|
|
333
|
+
calendarTabPageData,
|
|
334
|
+
yearTabPageData,
|
|
335
|
+
|
|
336
|
+
// Computed properties
|
|
337
|
+
showMonthInput,
|
|
338
|
+
showDateInput,
|
|
339
|
+
showYearInput,
|
|
340
|
+
calendarDays,
|
|
341
|
+
selectedMonthComputed,
|
|
342
|
+
selectedYearComputed,
|
|
343
|
+
selectedDayComputed,
|
|
344
|
+
calendarTabIsMinMonth,
|
|
345
|
+
calendarTabIsMaxMonth,
|
|
346
|
+
yearTabIsPreviousButtonDisabled,
|
|
347
|
+
yearTabIsNextButtonDisabled,
|
|
348
|
+
|
|
349
|
+
// Functions
|
|
350
|
+
getTabClasses,
|
|
351
|
+
handleTabClick,
|
|
352
|
+
getMonthObject,
|
|
353
|
+
calendarTabPrevMonth,
|
|
354
|
+
calendarTabNextMonth,
|
|
355
|
+
yearTabGoToPreviousPage,
|
|
356
|
+
yearTabGoToNextPage,
|
|
357
|
+
|
|
358
|
+
// Event handlers
|
|
359
|
+
handleCalendarDateUpdateWrapper,
|
|
360
|
+
handleCalendarMonthUpdateWrapper,
|
|
361
|
+
handleCalendarYearUpdateWrapper,
|
|
362
|
+
handleMonthTabMonthUpdateWrapper,
|
|
363
|
+
handleYearTabYearUpdateWrapper,
|
|
364
|
+
handleYearTabCurrentPageUpdateWrapper,
|
|
365
|
+
};
|
|
366
|
+
};
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="spr-grid spr-grid-cols-7">
|
|
3
|
+
<div
|
|
4
|
+
v-for="(dayOfWeek, dayOfWeekIndex) in daysOfWeek"
|
|
5
|
+
:key="dayOfWeekIndex"
|
|
6
|
+
class="spr-py-1 spr-text-center spr-font-semibold"
|
|
7
|
+
>
|
|
8
|
+
{{ dayOfWeek.text }}
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<template v-for="day in calendarDays" :key="day.date">
|
|
12
|
+
<div
|
|
13
|
+
v-if="minMaxYear.min <= day.date.getFullYear() && minMaxYear.max >= day.date.getFullYear()"
|
|
14
|
+
:class="[
|
|
15
|
+
'spr-relative spr-box-border spr-flex spr-h-[40px] spr-items-center spr-justify-center spr-p-2',
|
|
16
|
+
'spr-transition spr-duration-150 spr-ease-in-out',
|
|
17
|
+
{
|
|
18
|
+
// Rest Days
|
|
19
|
+
'spr-background-color-disabled': isRestDay(day),
|
|
20
|
+
|
|
21
|
+
// Today Indicator - only apply brand color if not selected
|
|
22
|
+
'spr-text-color-brand-base': isTodayIndicator(day) && !isSelectedDate(day),
|
|
23
|
+
|
|
24
|
+
// Active Month Dates - only apply if not selected and not today
|
|
25
|
+
'spr-text-color-strong':
|
|
26
|
+
isActiveMonthDates(day) &&
|
|
27
|
+
!isSelectedDate(day) &&
|
|
28
|
+
!isTodayIndicator(day),
|
|
29
|
+
|
|
30
|
+
// Inactive Month Dates (Past/Future)
|
|
31
|
+
'spr-text-color-disabled': isInactiveMonthDates(day),
|
|
32
|
+
|
|
33
|
+
// Selected Date
|
|
34
|
+
'spr-background-color-brand-base active:spr-background-color-brand-pressed spr-text-color-inverted-strong spr-cursor-pointer !spr-text-white-50 active:spr-scale-95':
|
|
35
|
+
isSelectedDate(day),
|
|
36
|
+
|
|
37
|
+
// Unselected Date
|
|
38
|
+
'hover:spr-background-color-hover spr-border-color-weak active:spr-background-color-pressed spr-cursor-pointer spr-border spr-border-solid active:spr-scale-95':
|
|
39
|
+
isUnSelectedDate(day),
|
|
40
|
+
|
|
41
|
+
// Disabled Dates
|
|
42
|
+
'spr-cursor-not-allowed spr-opacity-30': isDateDisabled(day),
|
|
43
|
+
},
|
|
44
|
+
]"
|
|
45
|
+
@click="!isDateDisabled(day) ? handleDateClick(day) : null"
|
|
46
|
+
>
|
|
47
|
+
<span>{{ day.date.getDate() }}</span>
|
|
48
|
+
<div
|
|
49
|
+
v-if="isTodayIndicator(day)"
|
|
50
|
+
class="spr-background-color-brand-base spr-absolute spr-bottom-1 spr-m-auto spr-h-1 spr-w-1 spr-rounded-full"
|
|
51
|
+
></div>
|
|
52
|
+
</div>
|
|
53
|
+
<div v-else></div>
|
|
54
|
+
</template>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<script lang="ts" setup>
|
|
59
|
+
import { computed } from 'vue';
|
|
60
|
+
import dayjs from 'dayjs';
|
|
61
|
+
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
|
|
62
|
+
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
|
|
63
|
+
import type { TabComponentProps, CalendarTabEmits, RestDayType, DisabledDatesType } from '../date-picker';
|
|
64
|
+
|
|
65
|
+
dayjs.extend(isSameOrAfter);
|
|
66
|
+
dayjs.extend(isSameOrBefore);
|
|
67
|
+
|
|
68
|
+
interface Props extends TabComponentProps {
|
|
69
|
+
calendarDays: Array<{ date: Date; inactive: boolean }>;
|
|
70
|
+
restDays: RestDayType[];
|
|
71
|
+
disabledDates?: DisabledDatesType;
|
|
72
|
+
selectedDate?: Date;
|
|
73
|
+
selectedMonth: number;
|
|
74
|
+
selectedDay?: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type Emits = CalendarTabEmits;
|
|
78
|
+
|
|
79
|
+
const props = defineProps<Props>();
|
|
80
|
+
const emit = defineEmits<Emits>();
|
|
81
|
+
|
|
82
|
+
const daysOfWeek = computed(() =>
|
|
83
|
+
Array.from({ length: 7 }, (_, i) => ({
|
|
84
|
+
text: dayjs().day(i).format('dd'),
|
|
85
|
+
fullText: dayjs().day(i).format('dddd'),
|
|
86
|
+
dayValue: i,
|
|
87
|
+
}))
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const currentDate = computed(() => dayjs());
|
|
91
|
+
|
|
92
|
+
const isRestDay = (day: { date: Date; inactive: boolean }) => {
|
|
93
|
+
const restDaysValue = props.restDays.map((restDay) => {
|
|
94
|
+
return daysOfWeek.value.find((d) => d.text.toLowerCase() === restDay.toLowerCase())?.dayValue;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (isSelectedDate(day)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (restDaysValue.includes(day.date.getDay())) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return false;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const isTodayIndicator = (day: { date: Date }) => {
|
|
109
|
+
if (day.date.toDateString() === currentDate.value.format('ddd MMM DD YYYY')) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return false;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const isActiveMonthDates = (day: { date: Date; inactive: boolean }) => {
|
|
117
|
+
if (!day.inactive && !isDateDisabled(day)) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return false;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const isInactiveMonthDates = (day: { date: Date; inactive: boolean }) => {
|
|
125
|
+
if (day.inactive && !isDateDisabled(day)) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return false;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const isSelectedDate = (day: { date: Date; inactive: boolean }) => {
|
|
133
|
+
if (isDateDisabled(day)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check if day, month, and year are selected (full mode)
|
|
138
|
+
if (props.selectedDay && props.selectedMonth !== undefined && props.selectedYear) {
|
|
139
|
+
return (
|
|
140
|
+
day.date.getDate() === props.selectedDay &&
|
|
141
|
+
day.date.getMonth() === props.selectedMonth &&
|
|
142
|
+
day.date.getFullYear() === props.selectedYear &&
|
|
143
|
+
!day.inactive
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check if day and month are selected, but not year
|
|
148
|
+
if (props.selectedDay && props.selectedMonth !== undefined && !props.selectedYear) {
|
|
149
|
+
return day.date.getDate() === props.selectedDay && day.date.getMonth() === props.selectedMonth && !day.inactive;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Check if only day is selected
|
|
153
|
+
if (props.selectedDay && !props.selectedMonth && !props.selectedYear) {
|
|
154
|
+
return day.date.getDate() === props.selectedDay && !day.inactive;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
return false;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const isUnSelectedDate = (day: { date: Date }) => {
|
|
162
|
+
if (isDateDisabled(day)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// If no selection at all, all dates are unselected
|
|
167
|
+
if (!props.selectedDay && !props.selectedMonth && !props.selectedYear) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// If only month is selected, all dates are unselected
|
|
172
|
+
if (!props.selectedDay && props.selectedMonth !== undefined && !props.selectedYear) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// If day, month, and year are selected, check if this date matches
|
|
177
|
+
if (props.selectedDay && props.selectedMonth !== undefined && props.selectedYear) {
|
|
178
|
+
return !(
|
|
179
|
+
day.date.getDate() === props.selectedDay &&
|
|
180
|
+
day.date.getMonth() === props.selectedMonth &&
|
|
181
|
+
day.date.getFullYear() === props.selectedYear
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// If day and month are selected, check if this date matches
|
|
186
|
+
if (props.selectedDay && props.selectedMonth !== undefined && !props.selectedYear) {
|
|
187
|
+
return !(
|
|
188
|
+
day.date.getDate() === props.selectedDay &&
|
|
189
|
+
day.date.getMonth() === props.selectedMonth
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// If only day is selected, check if this date matches
|
|
194
|
+
if (props.selectedDay && !props.selectedMonth && !props.selectedYear) {
|
|
195
|
+
return day.date.getDate() !== props.selectedDay;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return true;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const isDateDisabled = (day: { date: Date }) => {
|
|
202
|
+
if (
|
|
203
|
+
isDateDisabledFromTo(day) ||
|
|
204
|
+
isDateDisabledPastDate(day) ||
|
|
205
|
+
isDateDisabledFutureDate(day) ||
|
|
206
|
+
isDateDisabledSelectedDates(day) ||
|
|
207
|
+
isDateDisabledWeekends(day) ||
|
|
208
|
+
isDateDisabledWeekdays(day) ||
|
|
209
|
+
isDateDisabledSelectedDays(day)
|
|
210
|
+
) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return false;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const isDateDisabledFromTo = (day: { date: Date }) => {
|
|
218
|
+
if (props.disabledDates?.from && props.disabledDates?.to) {
|
|
219
|
+
const disabledFrom = dayjs(props.disabledDates.from, 'MM-DD-YYYY').startOf('day');
|
|
220
|
+
const disabledTo = dayjs(props.disabledDates.to, 'MM-DD-YYYY').endOf('day');
|
|
221
|
+
|
|
222
|
+
const dayDate = dayjs(day.date);
|
|
223
|
+
|
|
224
|
+
return dayDate.isSameOrAfter(disabledFrom, 'day') && dayDate.isSameOrBefore(disabledTo, 'day');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return false;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const isDateDisabledPastDate = (day: { date: Date }) => {
|
|
231
|
+
if (props.disabledDates?.pastDates) {
|
|
232
|
+
const dayDate = dayjs(day.date);
|
|
233
|
+
|
|
234
|
+
if (typeof props.disabledDates.pastDates === 'boolean') {
|
|
235
|
+
return dayDate.isBefore(currentDate.value.startOf('day'));
|
|
236
|
+
} else {
|
|
237
|
+
const selectedDate = dayjs(props.disabledDates.pastDates);
|
|
238
|
+
|
|
239
|
+
return dayDate.isBefore(dayjs(selectedDate, 'MM-DD-YYYY').startOf('day'));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return false;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const isDateDisabledFutureDate = (day: { date: Date }) => {
|
|
247
|
+
if (props.disabledDates?.futureDates) {
|
|
248
|
+
const dayDate = dayjs(day.date);
|
|
249
|
+
|
|
250
|
+
if (typeof props.disabledDates.futureDates === 'boolean') {
|
|
251
|
+
return dayDate.isAfter(currentDate.value.endOf('day'));
|
|
252
|
+
} else {
|
|
253
|
+
const selectedDate = dayjs(props.disabledDates.futureDates);
|
|
254
|
+
|
|
255
|
+
return dayDate.isAfter(dayjs(selectedDate, 'MM-DD-YYYY').endOf('day'));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return false;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const isDateDisabledSelectedDates = (day: { date: Date }) => {
|
|
263
|
+
if (props.disabledDates?.selectedDates && props.disabledDates.selectedDates.length > 0) {
|
|
264
|
+
const dayDate = dayjs(day.date);
|
|
265
|
+
|
|
266
|
+
props.disabledDates.selectedDates.forEach((_date) => {
|
|
267
|
+
if (!dayjs(_date).isValid()) {
|
|
268
|
+
console.error(`Error: disabledDates Props - Selected Dates - Invalid date format: "${_date}"`);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return props.disabledDates.selectedDates.some((date) => dayDate.isSame(dayjs(date, 'MM-DD-YYYY'), 'day'));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return false;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const isDateDisabledWeekends = (day: { date: Date }) => {
|
|
279
|
+
if (props.disabledDates?.weekends) {
|
|
280
|
+
const dayDate = dayjs(day.date);
|
|
281
|
+
|
|
282
|
+
return dayDate.day() === 0 || dayDate.day() === 6;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return false;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const isDateDisabledWeekdays = (day: { date: Date }) => {
|
|
289
|
+
if (props.disabledDates?.weekdays) {
|
|
290
|
+
const dayDate = dayjs(day.date);
|
|
291
|
+
|
|
292
|
+
return dayDate.day() > 0 && dayDate.day() < 6;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return false;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const isDateDisabledSelectedDays = (day: { date: Date }) => {
|
|
299
|
+
if (props.disabledDates?.selectedDays) {
|
|
300
|
+
const dayDate = dayjs(day.date);
|
|
301
|
+
|
|
302
|
+
return props.disabledDates.selectedDays.some((day) => {
|
|
303
|
+
const foundDay = daysOfWeek.value.find((d) => d.text.toLowerCase() === day.toLowerCase());
|
|
304
|
+
|
|
305
|
+
if (!foundDay) {
|
|
306
|
+
console.error(`Error: disabledDates Props - Selected Days - Invalid day: "${day}"`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return foundDay ? dayDate.day() === daysOfWeek.value.indexOf(foundDay) : false;
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return false;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const handleDateClick = (day: { date: Date; inactive: boolean }) => {
|
|
317
|
+
emit('update:date', day);
|
|
318
|
+
emit('update:month', day.date.getMonth());
|
|
319
|
+
emit('update:year', day.date.getFullYear());
|
|
320
|
+
};
|
|
321
|
+
</script>
|