design-system-next 2.16.2 → 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 +7569 -6646
- package/dist/design-system-next.es.js.gz +0 -0
- package/dist/design-system-next.umd.js +14 -14
- 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/App.vue +43 -1642
- 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 +36 -5
- package/src/components/date-picker/date-picker.vue +2 -1
- package/src/components/date-picker/date-range-picker/date-range-picker.ts +14 -6
- package/src/components/date-picker/date-range-picker/date-range-picker.vue +2 -1
- 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/dropdown/dropdown.ts +25 -21
- package/src/components/dropdown/dropdown.vue +1 -1
- 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
- package/src/components/select/select-ladderized/select-ladderized.ts +16 -12
- package/src/components/select/select-ladderized/select-ladderized.vue +1 -1
- package/src/components/select/select-multiple/select-multiple.ts +8 -4
- package/src/components/select/select-multiple/select-multiple.vue +1 -1
- package/src/components/select/select-multiple/use-select-multiple.ts +15 -4
- package/src/components/select/select.ts +4 -0
- package/src/components/select/select.vue +1 -1
|
@@ -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>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="spr-grid spr-grid-cols-4 spr-gap-2">
|
|
3
|
+
<div
|
|
4
|
+
v-for="(month, monthIndex) in monthsList"
|
|
5
|
+
:key="monthIndex"
|
|
6
|
+
:class="[
|
|
7
|
+
'spr-subheading-xs spr-relative spr-flex spr-cursor-pointer spr-items-center spr-justify-center spr-rounded-lg spr-p-4',
|
|
8
|
+
'spr-border spr-border-solid',
|
|
9
|
+
'spr-transition spr-duration-150 spr-ease-in-out',
|
|
10
|
+
'active:spr-scale-95',
|
|
11
|
+
{
|
|
12
|
+
'spr-text-color-brand-base': month.monthValue === currentMonth,
|
|
13
|
+
'spr-border-color-weak hover:spr-background-color-hover active:spr-background-color-pressed':
|
|
14
|
+
month.monthValue !== selectedMonth,
|
|
15
|
+
'spr-border-color-brand-base spr-background-color-single-active':
|
|
16
|
+
month.monthValue === selectedMonth,
|
|
17
|
+
},
|
|
18
|
+
]"
|
|
19
|
+
@click="handleMonthClick(month)"
|
|
20
|
+
>
|
|
21
|
+
<span>{{ month.text }}</span>
|
|
22
|
+
|
|
23
|
+
<div
|
|
24
|
+
v-if="month.monthValue === currentMonth"
|
|
25
|
+
class="spr-background-color-brand-base spr-absolute spr-bottom-2 spr-m-auto spr-h-1 spr-w-1 spr-rounded-full"
|
|
26
|
+
></div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script lang="ts" setup>
|
|
32
|
+
import { computed, toRefs } from 'vue';
|
|
33
|
+
import dayjs from 'dayjs';
|
|
34
|
+
import type { TabComponentProps, MonthTabEmits } from '../date-picker';
|
|
35
|
+
|
|
36
|
+
interface Props extends TabComponentProps {
|
|
37
|
+
selectedMonth: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type Emits = MonthTabEmits;
|
|
41
|
+
|
|
42
|
+
const props = defineProps<Props>();
|
|
43
|
+
const emit = defineEmits<Emits>();
|
|
44
|
+
|
|
45
|
+
const { selectedMonth } = toRefs(props);
|
|
46
|
+
|
|
47
|
+
const monthsList = computed(() =>
|
|
48
|
+
Array.from({ length: 12 }, (_, i) => ({
|
|
49
|
+
text: dayjs().month(i).format('MMM'),
|
|
50
|
+
fullText: dayjs().month(i).format('MMMM'),
|
|
51
|
+
monthValue: i,
|
|
52
|
+
}))
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const currentMonth = computed(() => dayjs().month());
|
|
56
|
+
|
|
57
|
+
const handleMonthClick = (month: { text: string; fullText: string; monthValue: number }) => {
|
|
58
|
+
emit('update:month', month.monthValue);
|
|
59
|
+
};
|
|
60
|
+
</script>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="spr-grid spr-grid-cols-4 spr-gap-2">
|
|
3
|
+
<div
|
|
4
|
+
v-for="(year, index) in yearTabCurrentYearPage"
|
|
5
|
+
:key="index"
|
|
6
|
+
:class="[
|
|
7
|
+
'spr-subheading-xs spr-relative spr-flex spr-cursor-pointer spr-items-center spr-justify-center spr-rounded-lg spr-p-4',
|
|
8
|
+
'spr-border spr-border-solid',
|
|
9
|
+
'spr-transition spr-duration-150 spr-ease-in-out',
|
|
10
|
+
'active:spr-scale-95',
|
|
11
|
+
{
|
|
12
|
+
'spr-text-color-brand-base': year === currentYear,
|
|
13
|
+
'spr-border-color-weak hover:spr-background-color-hover active:spr-background-color-pressed':
|
|
14
|
+
year !== selectedYear,
|
|
15
|
+
'spr-border-color-brand-base spr-background-color-single-active': year === selectedYear,
|
|
16
|
+
},
|
|
17
|
+
]"
|
|
18
|
+
@click="handleYearClick(year)"
|
|
19
|
+
>
|
|
20
|
+
<span>{{ year }}</span>
|
|
21
|
+
<div
|
|
22
|
+
v-if="year === currentYear"
|
|
23
|
+
class="spr-background-color-brand-base spr-absolute spr-bottom-2 spr-m-auto spr-h-1 spr-w-1 spr-rounded-full"
|
|
24
|
+
></div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script lang="ts" setup>
|
|
30
|
+
import { computed, watch } from 'vue';
|
|
31
|
+
import dayjs from 'dayjs';
|
|
32
|
+
import type { TabComponentProps, YearTabEmits } from '../date-picker';
|
|
33
|
+
|
|
34
|
+
interface Props extends Omit<TabComponentProps, 'selectedYear'> {
|
|
35
|
+
selectedYear?: number;
|
|
36
|
+
itemsPerPage?: number;
|
|
37
|
+
yearsArray?: number[];
|
|
38
|
+
currentPage?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
42
|
+
selectedYear: undefined,
|
|
43
|
+
itemsPerPage: 12,
|
|
44
|
+
yearsArray: () => [],
|
|
45
|
+
currentPage: 0,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const emit = defineEmits<YearTabEmits>();
|
|
49
|
+
|
|
50
|
+
const yearsArray = computed(() => props.yearsArray || []);
|
|
51
|
+
|
|
52
|
+
const currentYear = computed(() => dayjs().year());
|
|
53
|
+
|
|
54
|
+
const yearTabCurrentYearPage = computed(() => {
|
|
55
|
+
const start = props.currentPage * props.itemsPerPage;
|
|
56
|
+
const remainingItems = yearsArray.value.slice(start);
|
|
57
|
+
|
|
58
|
+
return remainingItems.length < props.itemsPerPage
|
|
59
|
+
? remainingItems
|
|
60
|
+
: yearsArray.value.slice(start, start + props.itemsPerPage);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const isPreviousButtonDisabled = computed(() => {
|
|
64
|
+
return props.currentPage === 0;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const isNextButtonDisabled = computed(() => {
|
|
68
|
+
return (props.currentPage + 1) * props.itemsPerPage >= yearsArray.value.length;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const setCurrentPageYear = () => {
|
|
72
|
+
const currentYearIndex = yearsArray.value.indexOf(props.selectedYear || 0);
|
|
73
|
+
|
|
74
|
+
if (currentYearIndex !== -1) {
|
|
75
|
+
const page = Math.floor(currentYearIndex / props.itemsPerPage);
|
|
76
|
+
emit('update:currentPage', page);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleYearClick = (year: number) => {
|
|
81
|
+
emit('update:year', year);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Watch for changes in selectedYear to update current page
|
|
85
|
+
watch(
|
|
86
|
+
() => props.selectedYear,
|
|
87
|
+
(newYear) => {
|
|
88
|
+
if (newYear) {
|
|
89
|
+
const yearIndex = yearsArray.value.indexOf(newYear);
|
|
90
|
+
|
|
91
|
+
if (yearIndex !== -1) {
|
|
92
|
+
const page = Math.floor(yearIndex / props.itemsPerPage);
|
|
93
|
+
emit('update:currentPage', page);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{ immediate: true }
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Watch for changes in minMaxYear to reset current page
|
|
101
|
+
watch(
|
|
102
|
+
() => props.minMaxYear,
|
|
103
|
+
() => {
|
|
104
|
+
emit('update:currentPage', 0);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Expose methods for parent component
|
|
109
|
+
defineExpose({
|
|
110
|
+
isPreviousButtonDisabled,
|
|
111
|
+
isNextButtonDisabled,
|
|
112
|
+
setCurrentPageYear,
|
|
113
|
+
});
|
|
114
|
+
</script>
|
|
@@ -86,6 +86,29 @@ export const dropdownPropTypes = {
|
|
|
86
86
|
type: String,
|
|
87
87
|
default: '100%',
|
|
88
88
|
},
|
|
89
|
+
autoHide: {
|
|
90
|
+
type: Boolean,
|
|
91
|
+
default: true,
|
|
92
|
+
},
|
|
93
|
+
triggers: {
|
|
94
|
+
type: Array as PropType<(typeof TRIGGER_EVENTS)[number][]>,
|
|
95
|
+
validator: (value: (typeof TRIGGER_EVENTS)[number][]) => {
|
|
96
|
+
return value.every((val) => TRIGGER_EVENTS.includes(val));
|
|
97
|
+
},
|
|
98
|
+
default: () => ['click'],
|
|
99
|
+
},
|
|
100
|
+
popperTriggers: {
|
|
101
|
+
type: Array as PropType<(typeof TRIGGER_EVENTS)[number][]>,
|
|
102
|
+
validator: (value: (typeof TRIGGER_EVENTS)[number][]) => {
|
|
103
|
+
return value.every((val) => TRIGGER_EVENTS.includes(val));
|
|
104
|
+
},
|
|
105
|
+
default: () => [],
|
|
106
|
+
},
|
|
107
|
+
popperStrategy: {
|
|
108
|
+
type: String,
|
|
109
|
+
validator: (value: 'fixed' | 'absolute') => POPPER_STRATEGY_TYPES.includes(value),
|
|
110
|
+
default: 'absolute',
|
|
111
|
+
},
|
|
89
112
|
popperWidth: {
|
|
90
113
|
type: String,
|
|
91
114
|
default: '100%',
|
|
@@ -94,10 +117,9 @@ export const dropdownPropTypes = {
|
|
|
94
117
|
type: String,
|
|
95
118
|
default: 'unset',
|
|
96
119
|
},
|
|
97
|
-
|
|
120
|
+
popperContainer: {
|
|
98
121
|
type: String,
|
|
99
|
-
|
|
100
|
-
default: 'absolute',
|
|
122
|
+
default: '',
|
|
101
123
|
},
|
|
102
124
|
disabled: {
|
|
103
125
|
type: Boolean,
|
|
@@ -119,24 +141,6 @@ export const dropdownPropTypes = {
|
|
|
119
141
|
type: Boolean,
|
|
120
142
|
default: false,
|
|
121
143
|
},
|
|
122
|
-
triggers: {
|
|
123
|
-
type: Array as PropType<(typeof TRIGGER_EVENTS)[number][]>,
|
|
124
|
-
validator: (value: (typeof TRIGGER_EVENTS)[number][]) => {
|
|
125
|
-
return value.every((val) => TRIGGER_EVENTS.includes(val));
|
|
126
|
-
},
|
|
127
|
-
default: () => ['click'],
|
|
128
|
-
},
|
|
129
|
-
popperTriggers: {
|
|
130
|
-
type: Array as PropType<(typeof TRIGGER_EVENTS)[number][]>,
|
|
131
|
-
validator: (value: (typeof TRIGGER_EVENTS)[number][]) => {
|
|
132
|
-
return value.every((val) => TRIGGER_EVENTS.includes(val));
|
|
133
|
-
},
|
|
134
|
-
default: () => [],
|
|
135
|
-
},
|
|
136
|
-
autoHide: {
|
|
137
|
-
type: Boolean,
|
|
138
|
-
default: true,
|
|
139
|
-
},
|
|
140
144
|
};
|
|
141
145
|
|
|
142
146
|
export const dropdownEmitTypes = {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
:popper-triggers="props.popperTriggers"
|
|
9
9
|
:auto-hide="props.autoHide"
|
|
10
10
|
:disabled="isDropdownPopperDisabled"
|
|
11
|
-
:container="`#${props.id}`"
|
|
11
|
+
:container="props.popperContainer ? props.popperContainer : `#${props.id}`"
|
|
12
12
|
:strategy="
|
|
13
13
|
props.popperStrategy === 'fixed' || props.popperStrategy === 'absolute' ? props.popperStrategy : 'absolute'
|
|
14
14
|
"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PropType, ExtractPropTypes } from 'vue';
|
|
2
|
+
|
|
3
|
+
export const definePropType = <T>(val: unknown): PropType<T> => val as PropType<T>;
|
|
4
|
+
export const ICON_SIZE = ['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'] as const;
|
|
5
|
+
export const ICON_TONE = ['success', 'error', 'info', 'pending', 'caution'] as const;
|
|
6
|
+
export const ICON_VARIANTS = ['primary', 'secondary', 'tertiary'] as const;
|
|
7
|
+
|
|
8
|
+
export const iconPropTypes = {
|
|
9
|
+
id: {
|
|
10
|
+
type: String,
|
|
11
|
+
required: true,
|
|
12
|
+
default: 'icon',
|
|
13
|
+
},
|
|
14
|
+
icon: {
|
|
15
|
+
type: String,
|
|
16
|
+
required: true,
|
|
17
|
+
default: '',
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
type: String as PropType<(typeof ICON_SIZE)[number]>,
|
|
21
|
+
validator: (value: (typeof ICON_SIZE)[number]) => ICON_SIZE.includes(value),
|
|
22
|
+
default: 'md',
|
|
23
|
+
},
|
|
24
|
+
tone: {
|
|
25
|
+
type: String as PropType<(typeof ICON_TONE)[number]>,
|
|
26
|
+
validator: (value: (typeof ICON_TONE)[number]) => ICON_TONE.includes(value),
|
|
27
|
+
default: '',
|
|
28
|
+
},
|
|
29
|
+
variant: {
|
|
30
|
+
type: String as PropType<(typeof ICON_VARIANTS)[number]>,
|
|
31
|
+
validator: (value: (typeof ICON_VARIANTS)[number]) => ICON_VARIANTS.includes(value),
|
|
32
|
+
default: '',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type IconPropTypes = ExtractPropTypes<typeof iconPropTypes>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="iconClasses">
|
|
3
|
+
<Icon v-bind="$attrs" :id="props.id" :icon="props.icon" />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
<script lang="ts" setup>
|
|
7
|
+
import { Icon } from '@iconify/vue';
|
|
8
|
+
import { iconPropTypes } from './icon';
|
|
9
|
+
import { useIcon } from './use-icon';
|
|
10
|
+
const props = defineProps(iconPropTypes);
|
|
11
|
+
const { iconClasses } = useIcon(props);
|
|
12
|
+
</script>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { toRefs, computed } from 'vue';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import type { IconPropTypes } from './icon';
|
|
4
|
+
|
|
5
|
+
export const useIcon = (props: IconPropTypes) => {
|
|
6
|
+
const { size, tone, variant } = toRefs(props);
|
|
7
|
+
|
|
8
|
+
const iconClasses = computed(() => {
|
|
9
|
+
const BASE_CLASSES =
|
|
10
|
+
'spr-rounded-border-radius-md spr-relative spr-inline-block spr-rounded spr-flex spr-items-center spr-justify-center';
|
|
11
|
+
|
|
12
|
+
const TONE_MAP = {
|
|
13
|
+
success: {
|
|
14
|
+
primary: 'spr-border-color-brand-base spr-text-color-inverted-strong spr-background-color-success-base',
|
|
15
|
+
secondary: 'spr-border-color-brand-base spr-text-color-success-base spr-bg-kangkong-50',
|
|
16
|
+
tertiary: 'spr-text-color-success-base',
|
|
17
|
+
},
|
|
18
|
+
error: {
|
|
19
|
+
primary: 'spr-border-color-danger-base spr-text-color-inverted-strong spr-background-color-danger-base',
|
|
20
|
+
secondary: 'spr-border-color-danger-base spr-text-color-danger-base spr-bg-tomato-50',
|
|
21
|
+
tertiary: 'spr-text-color-danger-base',
|
|
22
|
+
},
|
|
23
|
+
info: {
|
|
24
|
+
primary:
|
|
25
|
+
'spr-border-color-information-base spr-text-color-inverted-strong spr-background-color-information-base',
|
|
26
|
+
secondary: 'spr-border-color-information-base spr-text-color-information-base spr-bg-sky-50',
|
|
27
|
+
tertiary: 'spr-text-color-information-base',
|
|
28
|
+
},
|
|
29
|
+
pending: {
|
|
30
|
+
primary: 'spr-border-color-pending-base spr-text-color-inverted-strong spr-background-color-pending-base',
|
|
31
|
+
secondary: 'spr-border-color-pending-base spr-text-color-pending-base spr-bg-yellow-50',
|
|
32
|
+
tertiary: 'spr-text-color-pending-base',
|
|
33
|
+
},
|
|
34
|
+
caution: {
|
|
35
|
+
primary: 'spr-border-color-caution-base spr-text-color-inverted-strong spr-background-color-caution-base',
|
|
36
|
+
secondary: 'spr-border-color-caution-base spr-text-color-caution-base spr-bg-orange-50',
|
|
37
|
+
tertiary: 'spr-text-color-caution-base',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const SIZE_MAP = {
|
|
42
|
+
'2xl': 'spr-h-20 spr-min-w-20 spr-font-size-700',
|
|
43
|
+
xl: 'spr-h-14 spr-min-w-14 spr-font-size-600',
|
|
44
|
+
lg: 'spr-h-10 spr-min-w-10 spr-font-size-500',
|
|
45
|
+
md: 'spr-h-9 spr-min-w-9 spr-font-size-400',
|
|
46
|
+
sm: 'spr-h-6 spr-min-w-6 spr-font-size-300 spr-rounded-border-radius-sm',
|
|
47
|
+
xs: 'spr-h-5 spr-min-w-5 spr-font-size-250 spr-rounded-border-radius-xs',
|
|
48
|
+
'2xs': 'spr-h-4 spr-min-w-4 spr-font-size-250 spr-rounded-border-radius-xs',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const toneClasses = classNames('spr-border spr-border-solid', TONE_MAP[tone.value]?.[variant.value] || '');
|
|
52
|
+
|
|
53
|
+
const sizeClasses = SIZE_MAP[size.value] || SIZE_MAP.md;
|
|
54
|
+
|
|
55
|
+
const variantClasses = classNames({
|
|
56
|
+
'spr-border-0': variant.value === 'primary',
|
|
57
|
+
'spr-border': variant.value === 'secondary',
|
|
58
|
+
'spr-border-0 spr-bg-transparent': variant.value === 'tertiary' || !variant.value,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return classNames(variantClasses, BASE_CLASSES, sizeClasses, toneClasses);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
iconClasses,
|
|
66
|
+
};
|
|
67
|
+
};
|