@umbra.ui/core 0.1.18 → 0.1.20
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/components/controls/Button/Button.vue +417 -0
- package/dist/components/controls/Button/README.md +348 -0
- package/dist/components/controls/Button/theme.css +200 -0
- package/dist/components/controls/Checkbox/Checkbox.vue +164 -0
- package/dist/components/controls/Checkbox/README.md +441 -0
- package/dist/components/controls/Checkbox/theme.css +36 -0
- package/dist/components/controls/Dropdown/Dropdown.vue +476 -0
- package/dist/components/controls/Dropdown/README.md +370 -0
- package/dist/components/controls/Dropdown/theme.css +50 -0
- package/dist/components/controls/Dropdown/types.ts +6 -0
- package/dist/components/controls/IconButton/IconButton.vue +267 -0
- package/dist/components/controls/IconButton/README.md +502 -0
- package/dist/components/controls/IconButton/theme.css +89 -0
- package/dist/components/controls/Radio/README.md +591 -0
- package/dist/components/controls/Radio/Radio.vue +89 -0
- package/dist/components/controls/Radio/theme.css +14 -0
- package/dist/components/controls/RangeSlider/README.md +608 -0
- package/dist/components/controls/RangeSlider/RangeSlider.vue +535 -0
- package/dist/components/controls/RangeSlider/theme.css +80 -0
- package/dist/components/controls/SegmentedControl/README.md +587 -0
- package/dist/components/controls/SegmentedControl/SegmentedControl.vue +284 -0
- package/dist/components/controls/SegmentedControl/theme.css +60 -0
- package/dist/components/controls/SegmentedControl/types.ts +5 -0
- package/dist/components/controls/Slider/README.md +627 -0
- package/dist/components/controls/Slider/Slider.vue +260 -0
- package/dist/components/controls/Slider/theme.css +74 -0
- package/dist/components/controls/Stepper/README.md +601 -0
- package/dist/components/controls/Stepper/Stepper.vue +103 -0
- package/dist/components/controls/Stepper/theme.css +53 -0
- package/dist/components/controls/Switch/README.md +667 -0
- package/dist/components/controls/Switch/Switch.vue +127 -0
- package/dist/components/controls/Switch/theme.css +42 -0
- package/dist/components/dialogs/Alert/Alert.vue +218 -0
- package/dist/components/dialogs/Alert/README.md +450 -0
- package/dist/components/dialogs/Alert/theme.css +44 -0
- package/dist/components/dialogs/Alert/types.ts +11 -0
- package/dist/components/dialogs/Toast/README.md +522 -0
- package/dist/components/dialogs/Toast/Toast.vue +296 -0
- package/dist/components/dialogs/Toast/ToastContainer.vue +330 -0
- package/dist/components/dialogs/Toast/theme.css +44 -0
- package/dist/components/dialogs/Toast/types.ts +46 -0
- package/dist/components/dialogs/Toast/useToast.ts +127 -0
- package/dist/components/indicators/ProgressBar/ProgressBar.vue +98 -0
- package/dist/components/indicators/ProgressBar/README.md +744 -0
- package/dist/components/indicators/ProgressBar/theme.css +36 -0
- package/dist/components/indicators/Tooltip/README.md +723 -0
- package/dist/components/indicators/Tooltip/TooltipProvider.vue +142 -0
- package/dist/components/indicators/Tooltip/theme.css +18 -0
- package/dist/components/indicators/Tooltip/tooltip.ts +48 -0
- package/dist/components/indicators/Tooltip/types.ts +15 -0
- package/dist/components/indicators/Tooltip/useTooltip.ts +71 -0
- package/dist/components/inputs/AutogrowTextView/AutogrowTextView.vue +110 -0
- package/dist/components/inputs/AutogrowTextView/README.md +643 -0
- package/dist/components/inputs/AutogrowTextView/theme.css +28 -0
- package/dist/components/inputs/InputCard/InputCard.vue +600 -0
- package/dist/components/inputs/InputCard/README.md +636 -0
- package/dist/components/inputs/InputEmail/InputEmail.vue +698 -0
- package/dist/components/inputs/InputEmail/README.md +764 -0
- package/dist/components/inputs/InputNumber/InputNumber.vue +300 -0
- package/dist/components/inputs/InputNumber/README.md +749 -0
- package/dist/components/inputs/InputPhone/InputPhone.vue +645 -0
- package/dist/components/inputs/InputPhone/README.md +636 -0
- package/dist/components/inputs/InputSecure/InputSecure.vue +646 -0
- package/dist/components/inputs/InputSecure/README.md +771 -0
- package/dist/components/inputs/InputText/InputText.vue +225 -0
- package/dist/components/inputs/InputText/README.md +844 -0
- package/dist/components/inputs/OTP/OTP.vue +349 -0
- package/dist/components/inputs/OTP/README.md +736 -0
- package/dist/components/inputs/OTP/theme.css +50 -0
- package/dist/components/inputs/StringCapture/README.md +718 -0
- package/dist/components/inputs/StringCapture/StringCapture.vue +315 -0
- package/dist/components/inputs/StringCapture/theme.css +86 -0
- package/dist/components/inputs/Tags/README.md +897 -0
- package/dist/components/inputs/Tags/TagBar.vue +793 -0
- package/dist/components/inputs/Tags/TagCreation.vue +219 -0
- package/dist/components/inputs/Tags/TagPicker.vue +380 -0
- package/dist/components/inputs/Tags/tag-bar-styles.ts +354 -0
- package/dist/components/inputs/Tags/theme.css +121 -0
- package/dist/components/inputs/Tags/types.ts +346 -0
- package/dist/components/inputs/search/README.md +759 -0
- package/dist/components/inputs/search/SearchBar.vue +394 -0
- package/dist/components/inputs/search/SearchResults.vue +310 -0
- package/dist/components/inputs/search/theme.css +187 -0
- package/dist/components/inputs/search/types.ts +8 -0
- package/dist/components/inputs/theme.css +102 -0
- package/dist/components/menus/ActionMenu/ActionMenu.vue +383 -0
- package/dist/components/menus/ActionMenu/README.md +825 -0
- package/dist/components/menus/ActionMenu/theme.css +93 -0
- package/dist/components/models/Popover/Popover.vue +551 -0
- package/dist/components/models/Popover/README.md +885 -0
- package/dist/components/models/Popover/theme.css +52 -0
- package/dist/components/models/Sheet/README.md +1159 -0
- package/dist/components/models/Sheet/Sheet.vue +465 -0
- package/dist/components/models/Sheet/theme.css +72 -0
- package/dist/components/models/Sidebar/README.md +1228 -0
- package/dist/components/models/Sidebar/Sidebar.vue +480 -0
- package/dist/components/models/Sidebar/theme.css +90 -0
- package/dist/components/navigation/adaptive/AdaptiveLayout.vue +779 -0
- package/dist/components/navigation/adaptive/AdaptiveLayoutBreadcrumbs.vue +192 -0
- package/dist/components/navigation/adaptive/AdaptiveLayoutMenuButton.vue +149 -0
- package/dist/components/navigation/adaptive/README.md +768 -0
- package/dist/components/navigation/adaptive/types.ts +19 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.ts +89 -0
- package/dist/components/navigation/adaptive/useBreakpoints.ts +41 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.ts +214 -0
- package/dist/components/navigation/adaptive/useViewAnimation.ts +721 -0
- package/dist/components/navigation/adaptive/useViewResize.ts +211 -0
- package/dist/components/navigation/navstack/NavigationStack.vue +180 -0
- package/dist/components/navigation/navstack/README.md +994 -0
- package/dist/components/navigation/navstack/useNavigationStack.ts +164 -0
- package/dist/components/navigation/slideover/README.md +1275 -0
- package/dist/components/navigation/slideover/SlideoverController.vue +287 -0
- package/dist/components/navigation/slideover/useSlideoverController.ts +320 -0
- package/dist/components/navigation/splitview/README.md +1115 -0
- package/dist/components/navigation/splitview/SplitViewController.vue +176 -0
- package/dist/components/navigation/splitview/useSplitViewController.ts +388 -0
- package/dist/components/navigation/tabcontroller/README.md +919 -0
- package/dist/components/navigation/tabcontroller/TabController.vue +307 -0
- package/dist/components/navigation/tabcontroller/TabItem.vue +57 -0
- package/dist/components/navigation/tabcontroller/types.ts +24 -0
- package/dist/components/navigation/tabcontroller/useTabController.ts +18 -0
- package/dist/components/navigation/theme.css +91 -0
- package/dist/components/navigation/types.ts +7 -0
- package/dist/components/pickers/CollectionPicker/CollectionPicker.vue +398 -0
- package/dist/components/pickers/CollectionPicker/README.md +1115 -0
- package/dist/components/pickers/CollectionPicker/theme.css +14 -0
- package/dist/components/pickers/CollectionPicker/types.ts +11 -0
- package/dist/components/pickers/ColorPicker/ColorPicker.vue +376 -0
- package/dist/components/pickers/ColorPicker/README.md +1439 -0
- package/dist/components/pickers/ColorPicker/colors.ts +299 -0
- package/dist/components/pickers/ColorPicker/theme.css +32 -0
- package/dist/components/pickers/DatePicker/DatePicker.vue +660 -0
- package/dist/components/pickers/DatePicker/README.md +1195 -0
- package/dist/components/pickers/DatePicker/theme.css +22 -0
- package/dist/components/pickers/FilePicker/FilePicker.vue +534 -0
- package/dist/components/pickers/FilePicker/README.md +1542 -0
- package/dist/components/pickers/FilePicker/theme.css +48 -0
- package/dist/components/pickers/FilePicker/types.ts +10 -0
- package/dist/components/pickers/IconPicker/IconPicker.vue +327 -0
- package/dist/components/pickers/IconPicker/README.md +1161 -0
- package/dist/components/pickers/IconPicker/theme.css +28 -0
- package/dist/components/pickers/theme.css +82 -0
- package/dist/components/views/MarkdownViewer/MarkdownViewer.vue +442 -0
- package/dist/components/views/MarkdownViewer/README.md +833 -0
- package/dist/components/views/MarkdownViewer/theme.css +130 -0
- package/dist/css/umbra-ui.css +42 -0
- package/package.json +6 -3
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ChevronRightIcon, CalendarDaysIcon } from "@umbra.ui/icons";
|
|
3
|
+
// - Imports
|
|
4
|
+
import { ref, watch, onMounted, nextTick, computed, onUnmounted } from "vue";
|
|
5
|
+
import {
|
|
6
|
+
offset,
|
|
7
|
+
flip,
|
|
8
|
+
shift,
|
|
9
|
+
size,
|
|
10
|
+
computePosition,
|
|
11
|
+
hide,
|
|
12
|
+
autoUpdate,
|
|
13
|
+
} from "@floating-ui/vue";
|
|
14
|
+
import "./theme.css";
|
|
15
|
+
// - Interfaces
|
|
16
|
+
interface Month {
|
|
17
|
+
title: string;
|
|
18
|
+
days: Day[];
|
|
19
|
+
}
|
|
20
|
+
interface Day {
|
|
21
|
+
key: number;
|
|
22
|
+
day: number;
|
|
23
|
+
inCurrentMonth: boolean;
|
|
24
|
+
year: number;
|
|
25
|
+
month: number;
|
|
26
|
+
date: Date;
|
|
27
|
+
}
|
|
28
|
+
// - Props
|
|
29
|
+
export interface Props {
|
|
30
|
+
date: Date;
|
|
31
|
+
}
|
|
32
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
33
|
+
date: () => new Date(),
|
|
34
|
+
});
|
|
35
|
+
// - Emits
|
|
36
|
+
const emits = defineEmits(["update:date"]);
|
|
37
|
+
// - State Management
|
|
38
|
+
const weekdays = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"];
|
|
39
|
+
const days = ref<Day[]>([]);
|
|
40
|
+
const showAlldays = ref<boolean>(false);
|
|
41
|
+
const months = ref<Month[]>([]);
|
|
42
|
+
const selectedDate = ref<Date>(props.date);
|
|
43
|
+
// - Computed Properties
|
|
44
|
+
watch(
|
|
45
|
+
() => props.date,
|
|
46
|
+
(newValue) => {
|
|
47
|
+
selectedDate.value = newValue;
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
const dateString = computed(() => {
|
|
51
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
52
|
+
weekday: "long",
|
|
53
|
+
month: "long",
|
|
54
|
+
day: "numeric",
|
|
55
|
+
}).format(selectedDate.value);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const yearString = computed(() => {
|
|
59
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
60
|
+
year: "numeric",
|
|
61
|
+
}).format(selectedDate.value);
|
|
62
|
+
});
|
|
63
|
+
// - Element References
|
|
64
|
+
const showPopover = ref<boolean>(false);
|
|
65
|
+
const button = ref<HTMLElement | null>(null);
|
|
66
|
+
const picker = ref<HTMLElement | null>(null);
|
|
67
|
+
const container = ref<HTMLElement | null>(null);
|
|
68
|
+
const overlay = ref<HTMLElement | null>(null);
|
|
69
|
+
|
|
70
|
+
// - Position tracking
|
|
71
|
+
let cleanupAutoUpdate: (() => void) | null = null;
|
|
72
|
+
|
|
73
|
+
// - Lifecycle
|
|
74
|
+
onMounted(async () => {
|
|
75
|
+
setupCompactCalendar();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
onUnmounted(() => {
|
|
79
|
+
if (cleanupAutoUpdate) {
|
|
80
|
+
cleanupAutoUpdate();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// - Calendar Setup Toggling
|
|
85
|
+
const setupCompactCalendar = () => {
|
|
86
|
+
months.value = [];
|
|
87
|
+
// set up days for compact calendar
|
|
88
|
+
days.value = generateMonth(new Date(props.date)).days;
|
|
89
|
+
};
|
|
90
|
+
const setupFullCalendar = async () => {
|
|
91
|
+
allowScroll.value = false;
|
|
92
|
+
days.value = [];
|
|
93
|
+
|
|
94
|
+
// set up months with the next 5 months
|
|
95
|
+
for (let i = 0; i < 4; i++) {
|
|
96
|
+
months.value.push(
|
|
97
|
+
generateMonth(
|
|
98
|
+
new Date(props.date.getFullYear(), props.date.getMonth() + i, 1)
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// set up months with the previous five months
|
|
104
|
+
for (let i = 0; i < 4; i++) {
|
|
105
|
+
fetchPreviousMonth();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// scroll back to the month in the compact calendar
|
|
109
|
+
await nextTick();
|
|
110
|
+
if (scrollview.value) {
|
|
111
|
+
scrollview.value.scrollTop = 470;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// allow the month labels to show when the user scrolls
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
allowScroll.value = true;
|
|
117
|
+
}, 200);
|
|
118
|
+
};
|
|
119
|
+
const toggleCalendar = async () => {
|
|
120
|
+
showAlldays.value = !showAlldays.value;
|
|
121
|
+
|
|
122
|
+
if (showAlldays.value) {
|
|
123
|
+
setupFullCalendar();
|
|
124
|
+
} else {
|
|
125
|
+
setupCompactCalendar();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
// - Date Calculations
|
|
129
|
+
const fetchNextMonth = () => {
|
|
130
|
+
const month = months.value[months.value.length - 1];
|
|
131
|
+
const day = month.days[Math.floor(month.days.length / 2)];
|
|
132
|
+
const date = new Date(day.year, day.month, day.day);
|
|
133
|
+
date.setMonth(date.getMonth() + 1);
|
|
134
|
+
months.value.push(generateMonth(date));
|
|
135
|
+
};
|
|
136
|
+
const fetchPreviousMonth = () => {
|
|
137
|
+
const month = months.value[0];
|
|
138
|
+
const day = month.days[Math.floor(month.days.length / 2)];
|
|
139
|
+
const date = new Date(day.year, day.month, day.day);
|
|
140
|
+
date.setMonth(date.getMonth() - 1);
|
|
141
|
+
months.value.unshift(generateMonth(date));
|
|
142
|
+
};
|
|
143
|
+
function generateMonth(date: Date): Month {
|
|
144
|
+
const firstDayOfMonth = new Date(date.getFullYear(), date.getMonth(), 1);
|
|
145
|
+
const daysInMonth = new Date(
|
|
146
|
+
date.getFullYear(),
|
|
147
|
+
date.getMonth() + 1,
|
|
148
|
+
0
|
|
149
|
+
).getDate();
|
|
150
|
+
|
|
151
|
+
// Calculate the offset based on the day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
|
|
152
|
+
let startDay = firstDayOfMonth.getDay(); // Adjusted startDay calculation
|
|
153
|
+
|
|
154
|
+
// If the startDay is 0 (Sunday), set it to 7 for consistency
|
|
155
|
+
startDay = startDay === 0 ? 7 : startDay;
|
|
156
|
+
|
|
157
|
+
const generatedDays: Day[] = [];
|
|
158
|
+
|
|
159
|
+
const year = date.getFullYear();
|
|
160
|
+
const month = date.getMonth();
|
|
161
|
+
|
|
162
|
+
for (let i = 1; i <= daysInMonth; i++) {
|
|
163
|
+
const currentDate = new Date(year, month, i);
|
|
164
|
+
|
|
165
|
+
let inCurrentMonth = true;
|
|
166
|
+
generatedDays.push({
|
|
167
|
+
key: i, // Key starts from 1
|
|
168
|
+
day: i,
|
|
169
|
+
inCurrentMonth,
|
|
170
|
+
year,
|
|
171
|
+
month,
|
|
172
|
+
date: currentDate, // Set the newly added date field
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Fill in days from the previous month if needed to complete the week
|
|
177
|
+
const previousMonthLastDay = new Date(year, month, 0).getDate();
|
|
178
|
+
const previousMonthDaysNeeded = startDay - 1;
|
|
179
|
+
for (
|
|
180
|
+
let i = previousMonthLastDay - previousMonthDaysNeeded + 1;
|
|
181
|
+
i <= previousMonthLastDay;
|
|
182
|
+
i++
|
|
183
|
+
) {
|
|
184
|
+
const currentDate = new Date(year, month - 1, i); // Calculate date for the previous month
|
|
185
|
+
generatedDays.unshift({
|
|
186
|
+
key: i,
|
|
187
|
+
day: i,
|
|
188
|
+
inCurrentMonth: false,
|
|
189
|
+
year: month === 0 ? year - 1 : year,
|
|
190
|
+
month: month === 0 ? 11 : month - 1,
|
|
191
|
+
date: currentDate, // Set the newly added date field
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Fill in days from the next month if needed to complete the grid
|
|
196
|
+
const remainingDays = 35 - generatedDays.length;
|
|
197
|
+
for (let i = 1; i <= remainingDays; i++) {
|
|
198
|
+
const nextMonthDate = new Date(year, month + 1, i); // Calculate date for the next month
|
|
199
|
+
generatedDays.push({
|
|
200
|
+
key: i,
|
|
201
|
+
day: i,
|
|
202
|
+
inCurrentMonth: false,
|
|
203
|
+
year: month === 11 ? year + 1 : year,
|
|
204
|
+
month: month === 11 ? 0 : month + 1,
|
|
205
|
+
date: nextMonthDate, // Set the newly added date field
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const monthTitle = new Intl.DateTimeFormat("en-US", {
|
|
210
|
+
month: "long",
|
|
211
|
+
year: "numeric",
|
|
212
|
+
}).format(date);
|
|
213
|
+
return {
|
|
214
|
+
title: monthTitle,
|
|
215
|
+
days: generatedDays,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// - Handle Day Cell Appearance
|
|
220
|
+
function drawBorderTop(day: Day): boolean {
|
|
221
|
+
if (day.inCurrentMonth && day.day <= 7) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
function drawBorderLeft(day: Day, index: number): boolean {
|
|
227
|
+
if (index === 0) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
if (day.inCurrentMonth && day.day === 1) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
function setMarginTopOffset(month: Month, index: number): boolean {
|
|
236
|
+
if (index === 0) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
if (month.days[0].inCurrentMonth) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// - Date Selection
|
|
246
|
+
function isDaySelected(day: Day): boolean {
|
|
247
|
+
return selectedDate.value.getTime() === day.date.getTime();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function select(date: Date) {
|
|
251
|
+
selectedDate.value = date;
|
|
252
|
+
emits("update:date", date);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// - Handle Scrolling
|
|
256
|
+
const scrollview = ref<HTMLElement | null>(null);
|
|
257
|
+
const fetchInProgress = ref<boolean>(false);
|
|
258
|
+
const allowScroll = ref<boolean>(false);
|
|
259
|
+
const isScrolling = ref<boolean>(false);
|
|
260
|
+
let scrollTimeout: NodeJS.Timeout | undefined;
|
|
261
|
+
const handleScroll = () => {
|
|
262
|
+
// Set isScrolling to true when scrolling starts
|
|
263
|
+
if (allowScroll.value) {
|
|
264
|
+
isScrolling.value = true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Clear the timeout if it exists
|
|
268
|
+
if (scrollTimeout !== undefined) {
|
|
269
|
+
clearTimeout(scrollTimeout);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Set a timeout to reset isScrolling after a short delay (e.g., 200ms)
|
|
273
|
+
scrollTimeout = setTimeout(() => {
|
|
274
|
+
isScrolling.value = false;
|
|
275
|
+
}, 400); // Adjust the delay as needed
|
|
276
|
+
|
|
277
|
+
if (!scrollview.value) return;
|
|
278
|
+
|
|
279
|
+
const percentage =
|
|
280
|
+
(scrollview.value.scrollTop /
|
|
281
|
+
(scrollview.value.scrollHeight - scrollview.value.clientHeight)) *
|
|
282
|
+
100;
|
|
283
|
+
if (percentage <= 25 && !fetchInProgress.value) {
|
|
284
|
+
fetchInProgress.value = true;
|
|
285
|
+
fetchPreviousMonth();
|
|
286
|
+
fetchInProgress.value = false;
|
|
287
|
+
}
|
|
288
|
+
// fetch more data to show if the user has scrolled more than 75% down
|
|
289
|
+
else if (percentage > 75 && !fetchInProgress.value) {
|
|
290
|
+
fetchInProgress.value = true;
|
|
291
|
+
fetchNextMonth();
|
|
292
|
+
fetchInProgress.value = false;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// - Popover Management
|
|
297
|
+
const togglePopover = () => {
|
|
298
|
+
showPopover.value = !showPopover.value;
|
|
299
|
+
if (showPopover.value) {
|
|
300
|
+
nextTick(() => {
|
|
301
|
+
updatePopoverPosition();
|
|
302
|
+
});
|
|
303
|
+
} else {
|
|
304
|
+
// revert back to the small calendar
|
|
305
|
+
showAlldays.value = false;
|
|
306
|
+
setupCompactCalendar();
|
|
307
|
+
|
|
308
|
+
// Clean up auto-update
|
|
309
|
+
if (cleanupAutoUpdate) {
|
|
310
|
+
cleanupAutoUpdate();
|
|
311
|
+
cleanupAutoUpdate = null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const updatePopoverPosition = async () => {
|
|
317
|
+
if (!button.value || !picker.value) return;
|
|
318
|
+
|
|
319
|
+
// Wait for the DOM to be updated
|
|
320
|
+
await nextTick();
|
|
321
|
+
|
|
322
|
+
// Clean up any existing auto-update
|
|
323
|
+
if (cleanupAutoUpdate) {
|
|
324
|
+
cleanupAutoUpdate();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Set up auto-update to track position changes
|
|
328
|
+
cleanupAutoUpdate = autoUpdate(button.value, picker.value, () => {
|
|
329
|
+
computePosition(button.value!, picker.value!, {
|
|
330
|
+
placement: "bottom-start",
|
|
331
|
+
middleware: [
|
|
332
|
+
offset(4), // px between anchor and popover
|
|
333
|
+
flip(), // switch side of space becomes too narrow
|
|
334
|
+
shift(),
|
|
335
|
+
size({
|
|
336
|
+
padding: 20,
|
|
337
|
+
apply({
|
|
338
|
+
availableWidth,
|
|
339
|
+
availableHeight,
|
|
340
|
+
elements,
|
|
341
|
+
}: {
|
|
342
|
+
availableWidth: number;
|
|
343
|
+
availableHeight: number;
|
|
344
|
+
elements: {
|
|
345
|
+
floating: {
|
|
346
|
+
style: {
|
|
347
|
+
maxWidth: string;
|
|
348
|
+
maxHeight: string;
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
}) {
|
|
353
|
+
// Change styles, e.g.
|
|
354
|
+
Object.assign(elements.floating.style, {
|
|
355
|
+
maxWidth: `${availableWidth}px`,
|
|
356
|
+
maxHeight: `${availableHeight}px`,
|
|
357
|
+
});
|
|
358
|
+
},
|
|
359
|
+
}),
|
|
360
|
+
],
|
|
361
|
+
}).then(({ x, y }) => {
|
|
362
|
+
if (picker.value) {
|
|
363
|
+
Object.assign(picker.value.style, {
|
|
364
|
+
left: `${x}px`,
|
|
365
|
+
top: `${y}px`,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const handleOverlayClick = () => {
|
|
373
|
+
togglePopover();
|
|
374
|
+
};
|
|
375
|
+
</script>
|
|
376
|
+
|
|
377
|
+
<template>
|
|
378
|
+
<div :class="$style.container" ref="container">
|
|
379
|
+
<div
|
|
380
|
+
:class="[
|
|
381
|
+
$style.button,
|
|
382
|
+
showPopover ? $style.button_selected : $style.button_normal,
|
|
383
|
+
]"
|
|
384
|
+
@click="togglePopover"
|
|
385
|
+
ref="button"
|
|
386
|
+
>
|
|
387
|
+
<CalendarDaysIcon :size="16" />
|
|
388
|
+
<p :class="['callout', $style.button_label]">{{ dateString }}</p>
|
|
389
|
+
<p :class="['callout', $style.button_sublabel]">{{ yearString }}</p>
|
|
390
|
+
</div>
|
|
391
|
+
|
|
392
|
+
<!-- Teleport the overlay and picker to body -->
|
|
393
|
+
<Teleport to="body">
|
|
394
|
+
<div
|
|
395
|
+
v-if="showPopover"
|
|
396
|
+
:class="$style.overlay"
|
|
397
|
+
ref="overlay"
|
|
398
|
+
@click="handleOverlayClick"
|
|
399
|
+
></div>
|
|
400
|
+
<div v-if="showPopover" :class="$style.picker" ref="picker">
|
|
401
|
+
<div :class="$style.weekdays">
|
|
402
|
+
<p v-for="weekday in weekdays" :key="weekday">{{ weekday }}</p>
|
|
403
|
+
</div>
|
|
404
|
+
<div v-if="!showAlldays" :class="[$style.days]">
|
|
405
|
+
<p
|
|
406
|
+
v-for="day in days"
|
|
407
|
+
:key="`compact-${day.year}-${day.month}-${day.day}`"
|
|
408
|
+
:class="[
|
|
409
|
+
'callout',
|
|
410
|
+
$style.day,
|
|
411
|
+
isDaySelected(day) ? $style.day_selected : '',
|
|
412
|
+
]"
|
|
413
|
+
:style="{ opacity: day.inCurrentMonth ? 1 : 0.5 }"
|
|
414
|
+
@click="select(day.date)"
|
|
415
|
+
>
|
|
416
|
+
{{ day.day }}
|
|
417
|
+
</p>
|
|
418
|
+
<ChevronRightIcon
|
|
419
|
+
:class="[$style.day, $style.more_dates_button]"
|
|
420
|
+
@click="toggleCalendar"
|
|
421
|
+
/>
|
|
422
|
+
</div>
|
|
423
|
+
<div
|
|
424
|
+
v-else
|
|
425
|
+
:class="$style.months"
|
|
426
|
+
ref="scrollview"
|
|
427
|
+
@scroll="handleScroll"
|
|
428
|
+
>
|
|
429
|
+
<div
|
|
430
|
+
v-for="(month, index) in months"
|
|
431
|
+
:key="month.title"
|
|
432
|
+
:class="[
|
|
433
|
+
$style.month,
|
|
434
|
+
setMarginTopOffset(month, index) ? $style.margin_top_offset : '',
|
|
435
|
+
]"
|
|
436
|
+
>
|
|
437
|
+
<div
|
|
438
|
+
:class="$style.month_days"
|
|
439
|
+
:style="{ opacity: isScrolling ? 0.15 : 1 }"
|
|
440
|
+
>
|
|
441
|
+
<p
|
|
442
|
+
v-for="(day, index) in month.days"
|
|
443
|
+
:key="`full-${day.year}-${day.month}-${day.day}`"
|
|
444
|
+
:class="[
|
|
445
|
+
'callout',
|
|
446
|
+
$style.day,
|
|
447
|
+
drawBorderTop(day) ? $style.border_top : '',
|
|
448
|
+
drawBorderLeft(day, index) ? $style.border_left : '',
|
|
449
|
+
isDaySelected(day) ? $style.day_selected : '',
|
|
450
|
+
]"
|
|
451
|
+
:style="{
|
|
452
|
+
opacity: day.inCurrentMonth ? 1 : 0,
|
|
453
|
+
pointerEvents: day.inCurrentMonth ? 'auto' : 'none',
|
|
454
|
+
zIndex: day.inCurrentMonth ? 1 : 0,
|
|
455
|
+
}"
|
|
456
|
+
@click="select(day.date)"
|
|
457
|
+
>
|
|
458
|
+
{{ day.day }}
|
|
459
|
+
</p>
|
|
460
|
+
</div>
|
|
461
|
+
<div
|
|
462
|
+
:class="$style.month_header"
|
|
463
|
+
:style="{ opacity: isScrolling ? 1 : 0 }"
|
|
464
|
+
>
|
|
465
|
+
<p class="headline">{{ month.title }}</p>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
</div>
|
|
470
|
+
</Teleport>
|
|
471
|
+
</div>
|
|
472
|
+
</template>
|
|
473
|
+
|
|
474
|
+
<style module>
|
|
475
|
+
.container {
|
|
476
|
+
display: flex;
|
|
477
|
+
flex-direction: column;
|
|
478
|
+
gap: 0.235rem;
|
|
479
|
+
align-items: start;
|
|
480
|
+
}
|
|
481
|
+
.button {
|
|
482
|
+
padding-top: 0.588rem;
|
|
483
|
+
padding-bottom: 0.588rem;
|
|
484
|
+
border-radius: 0.353rem;
|
|
485
|
+
cursor: default;
|
|
486
|
+
user-select: none;
|
|
487
|
+
display: flex;
|
|
488
|
+
align-items: center;
|
|
489
|
+
gap: 0.588rem;
|
|
490
|
+
transition: padding-left 0.3s, padding-right 0.3s, background-color 0.3s,
|
|
491
|
+
box-shadow 0.3s;
|
|
492
|
+
}
|
|
493
|
+
.button_normal {
|
|
494
|
+
background-color: var(--picker-button-bg);
|
|
495
|
+
border: var(--picker-button-border);
|
|
496
|
+
}
|
|
497
|
+
.button_normal:hover {
|
|
498
|
+
background-color: var(--picker-button-hover-bg);
|
|
499
|
+
padding-left: 0.588rem;
|
|
500
|
+
padding-right: 0.588rem;
|
|
501
|
+
box-shadow: 0px 1px 0px 0px var(--picker-button-hover-shadow),
|
|
502
|
+
inset 0px 1px 0px 0px var(--picker-button-hover-inset-shadow);
|
|
503
|
+
border: var(--picker-button-hover-border);
|
|
504
|
+
}
|
|
505
|
+
.button_selected {
|
|
506
|
+
background-color: var(--picker-button-selected-bg);
|
|
507
|
+
padding-left: 0.588rem;
|
|
508
|
+
padding-right: 0.588rem;
|
|
509
|
+
border: var(--picker-button-hover-border);
|
|
510
|
+
}
|
|
511
|
+
.button_label {
|
|
512
|
+
font-weight: 700 !important;
|
|
513
|
+
font-variation-settings: "wght" 700 !important;
|
|
514
|
+
}
|
|
515
|
+
.button_sublabel {
|
|
516
|
+
opacity: 0.5;
|
|
517
|
+
}
|
|
518
|
+
.picker {
|
|
519
|
+
position: absolute;
|
|
520
|
+
top: 0;
|
|
521
|
+
left: 0;
|
|
522
|
+
background-color: var(--picker-picker-bg);
|
|
523
|
+
border-radius: 0.353rem;
|
|
524
|
+
overflow: hidden;
|
|
525
|
+
box-shadow: 0px 1px 0px 0px var(--picker-picker-shadow),
|
|
526
|
+
inset 0px 1px 0px 0px var(--picker-picker-inset-shadow);
|
|
527
|
+
border: var(--picker-picker-border);
|
|
528
|
+
z-index: 1000;
|
|
529
|
+
}
|
|
530
|
+
.weekdays {
|
|
531
|
+
display: grid;
|
|
532
|
+
grid-template-columns: repeat(7, 1fr);
|
|
533
|
+
align-items: center;
|
|
534
|
+
padding-left: 0.294rem;
|
|
535
|
+
padding-right: 0.294rem;
|
|
536
|
+
min-height: 2rem;
|
|
537
|
+
background-color: var(--picker-bg-subtle);
|
|
538
|
+
border-bottom: 1px solid var(--picker-border-light);
|
|
539
|
+
}
|
|
540
|
+
.weekdays p {
|
|
541
|
+
opacity: var(--datepicker-weekdays-opacity);
|
|
542
|
+
font-size: 0.765rem;
|
|
543
|
+
min-width: 2.588rem;
|
|
544
|
+
text-align: center;
|
|
545
|
+
color: var(--picker-text-secondary);
|
|
546
|
+
}
|
|
547
|
+
.days {
|
|
548
|
+
display: grid;
|
|
549
|
+
grid-template-columns: repeat(7, 1fr);
|
|
550
|
+
gap: 0;
|
|
551
|
+
align-items: center;
|
|
552
|
+
max-height: 32rem;
|
|
553
|
+
overflow: auto;
|
|
554
|
+
color: var(--picker-text-primary);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.days :nth-last-child(2) {
|
|
558
|
+
display: none;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.months {
|
|
562
|
+
display: flex;
|
|
563
|
+
flex-direction: column;
|
|
564
|
+
max-height: 32rem;
|
|
565
|
+
overflow: auto;
|
|
566
|
+
}
|
|
567
|
+
.months::-webkit-scrollbar {
|
|
568
|
+
display: none;
|
|
569
|
+
}
|
|
570
|
+
.month {
|
|
571
|
+
display: grid;
|
|
572
|
+
grid-template-columns: 1fr;
|
|
573
|
+
grid-template-rows: 1fr;
|
|
574
|
+
grid-template-areas: "content";
|
|
575
|
+
}
|
|
576
|
+
.month_header {
|
|
577
|
+
padding-left: 0.706rem;
|
|
578
|
+
padding-right: 0.706rem;
|
|
579
|
+
padding-top: 0.5rem;
|
|
580
|
+
padding-bottom: 0.5rem;
|
|
581
|
+
grid-area: content;
|
|
582
|
+
display: flex;
|
|
583
|
+
align-items: center;
|
|
584
|
+
justify-content: center;
|
|
585
|
+
transition: opacity 0.3s ease;
|
|
586
|
+
user-select: none;
|
|
587
|
+
cursor: default;
|
|
588
|
+
pointer-events: none;
|
|
589
|
+
color: var(--picker-text-primary);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.month_days {
|
|
593
|
+
display: grid;
|
|
594
|
+
grid-template-columns: repeat(7, 1fr);
|
|
595
|
+
gap: 0;
|
|
596
|
+
align-items: center;
|
|
597
|
+
max-height: 32rem;
|
|
598
|
+
overflow: auto;
|
|
599
|
+
grid-area: content;
|
|
600
|
+
transition: opacity 0.3s ease;
|
|
601
|
+
color: var(--picker-text-primary);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.day {
|
|
605
|
+
min-width: 2.588rem;
|
|
606
|
+
height: 2rem;
|
|
607
|
+
text-align: center;
|
|
608
|
+
display: flex;
|
|
609
|
+
align-items: center;
|
|
610
|
+
justify-content: center;
|
|
611
|
+
user-select: none;
|
|
612
|
+
cursor: default;
|
|
613
|
+
border-radius: 0.235rem;
|
|
614
|
+
padding: 0.294rem;
|
|
615
|
+
}
|
|
616
|
+
.day:hover {
|
|
617
|
+
background-color: var(--picker-bg-hover);
|
|
618
|
+
}
|
|
619
|
+
.day_selected {
|
|
620
|
+
background-color: var(--datepicker-day-selected-bg);
|
|
621
|
+
border-radius: 999px;
|
|
622
|
+
border: 1px solid var(--datepicker-day-selected-border);
|
|
623
|
+
}
|
|
624
|
+
.day_selected:hover {
|
|
625
|
+
background-color: var(--datepicker-day-selected-bg);
|
|
626
|
+
}
|
|
627
|
+
.border_top {
|
|
628
|
+
border-top: 1px solid var(--picker-border-light);
|
|
629
|
+
border-top-right-radius: 0;
|
|
630
|
+
border-top-left-radius: 0;
|
|
631
|
+
}
|
|
632
|
+
.border_left {
|
|
633
|
+
border-left: 1px solid var(--picker-border-light);
|
|
634
|
+
border-bottom-left-radius: 0;
|
|
635
|
+
}
|
|
636
|
+
.margin_top_offset {
|
|
637
|
+
margin-top: -2rem;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.more_dates_button {
|
|
641
|
+
width: 2.588rem;
|
|
642
|
+
height: 2rem;
|
|
643
|
+
padding: 0.471rem;
|
|
644
|
+
background-color: var(--picker-selection-bar-bg);
|
|
645
|
+
}
|
|
646
|
+
.more_dates_button:hover {
|
|
647
|
+
background-color: var(--picker-bg-hover);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.overlay {
|
|
651
|
+
position: fixed;
|
|
652
|
+
top: 0;
|
|
653
|
+
left: 0;
|
|
654
|
+
width: 100%;
|
|
655
|
+
height: 100%;
|
|
656
|
+
background-color: var(--picker-overlay-bg);
|
|
657
|
+
opacity: 0;
|
|
658
|
+
z-index: 999;
|
|
659
|
+
}
|
|
660
|
+
</style>
|