mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-25 01:33:51 +02:00
220 lines
7.2 KiB
Vue
220 lines
7.2 KiB
Vue
<script setup>
|
|
import {
|
|
CalendarCell,
|
|
CalendarCellTrigger,
|
|
CalendarGrid,
|
|
CalendarGridBody,
|
|
CalendarGridHead,
|
|
CalendarGridRow,
|
|
CalendarHeadCell,
|
|
CalendarHeader,
|
|
CalendarHeading,
|
|
CalendarNextButton,
|
|
CalendarPrevButton
|
|
} from '@/components/ui/calendar';
|
|
import { computed, ref, watch } from 'vue';
|
|
import { fromDate, getLocalTimeZone } from '@internationalized/date';
|
|
import { CalendarRoot } from 'reka-ui';
|
|
import { toDate } from 'reka-ui/date';
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: Date,
|
|
required: true
|
|
},
|
|
isLoading: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
eventsByDate: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
followingByDate: {
|
|
type: Object,
|
|
default: () => ({})
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['update:modelValue']);
|
|
|
|
const timeZone = getLocalTimeZone();
|
|
|
|
const internalValue = ref(fromDate(props.modelValue ?? new Date(), timeZone));
|
|
const placeholder = ref(fromDate(props.modelValue ?? new Date(), timeZone));
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
(next) => {
|
|
if (!next) return;
|
|
internalValue.value = fromDate(next, timeZone);
|
|
placeholder.value = fromDate(next, timeZone);
|
|
}
|
|
);
|
|
|
|
const selectedDayKey = computed(() => dayjs(props.modelValue).format('YYYY-MM-DD'));
|
|
|
|
function toKey(dateValue) {
|
|
return dayjs(toDate(dateValue, timeZone)).format('YYYY-MM-DD');
|
|
}
|
|
|
|
function eventCountFor(dateValue) {
|
|
const key = toKey(dateValue);
|
|
return props.eventsByDate?.[key]?.length ?? 0;
|
|
}
|
|
|
|
function hasFollowingFor(dateValue) {
|
|
const key = toKey(dateValue);
|
|
return Boolean(props.followingByDate?.[key]);
|
|
}
|
|
|
|
function onUpdateModelValue(next) {
|
|
if (!next) return;
|
|
internalValue.value = next;
|
|
const asDate = toDate(next, timeZone);
|
|
placeholder.value = next;
|
|
emit('update:modelValue', asDate);
|
|
}
|
|
|
|
function onUpdatePlaceholder(next) {
|
|
if (!next) return;
|
|
placeholder.value = next;
|
|
internalValue.value = next;
|
|
emit('update:modelValue', toDate(next, timeZone));
|
|
}
|
|
|
|
function dayLabel(dateValue) {
|
|
return dayjs(toDate(dateValue, timeZone)).format('D');
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="group-calendar-month">
|
|
<CalendarRoot
|
|
v-slot="{ grid, weekDays }"
|
|
:model-value="internalValue"
|
|
@update:modelValue="onUpdateModelValue"
|
|
:placeholder="placeholder"
|
|
@update:placeholder="onUpdatePlaceholder"
|
|
:prevent-deselect="true"
|
|
class="p-4">
|
|
<CalendarHeader class="pt-0">
|
|
<nav class="flex items-center gap-1 absolute top-0 inset-x-0 justify-between">
|
|
<CalendarPrevButton class="size-9" />
|
|
<CalendarNextButton class="size-9" />
|
|
</nav>
|
|
<div class="flex items-center justify-center">
|
|
<CalendarHeading class="text-base" />
|
|
</div>
|
|
</CalendarHeader>
|
|
|
|
<div class="flex flex-col gap-y-4 mt-6">
|
|
<CalendarGrid v-for="month in grid" :key="month.value.toString()" class="w-full">
|
|
<CalendarGridHead>
|
|
<CalendarGridRow>
|
|
<CalendarHeadCell v-for="day in weekDays" :key="day" class="text-sm">
|
|
{{ day }}
|
|
</CalendarHeadCell>
|
|
</CalendarGridRow>
|
|
</CalendarGridHead>
|
|
<CalendarGridBody>
|
|
<CalendarGridRow
|
|
v-for="(weekDates, index) in month.rows"
|
|
:key="`weekDate-${index}`"
|
|
class="mt-2 w-full">
|
|
<CalendarCell v-for="weekDate in weekDates" :key="weekDate.toString()" :date="weekDate">
|
|
<CalendarCellTrigger
|
|
:day="weekDate"
|
|
:month="month.value"
|
|
class="size-12 cursor-pointer">
|
|
<div class="date">
|
|
<div
|
|
class="calendar-date-content"
|
|
:class="{
|
|
'has-events': eventCountFor(weekDate) > 0,
|
|
'is-selected': toKey(weekDate) === selectedDayKey
|
|
}">
|
|
{{ dayLabel(weekDate) }}
|
|
<div
|
|
v-if="eventCountFor(weekDate) > 0"
|
|
class="calendar-event-badge text-zinc-900"
|
|
:class="hasFollowingFor(weekDate) ? 'has-following' : 'no-following'">
|
|
{{ eventCountFor(weekDate) }}
|
|
</div>
|
|
<!-- <div
|
|
v-if="eventCountFor(weekDate) > 0"
|
|
class="calendar-event-dot"
|
|
aria-hidden="true" /> -->
|
|
</div>
|
|
</div>
|
|
</CalendarCellTrigger>
|
|
</CalendarCell>
|
|
</CalendarGridRow>
|
|
</CalendarGridBody>
|
|
</CalendarGrid>
|
|
</div>
|
|
</CalendarRoot>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.group-calendar-month {
|
|
height: 100%;
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
padding: 0 12x 0 12px;
|
|
}
|
|
|
|
.date {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.calendar-date-content {
|
|
width: 80%;
|
|
height: 80%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 10px;
|
|
font-size: 18px;
|
|
position: relative;
|
|
}
|
|
|
|
.calendar-event-badge {
|
|
position: absolute;
|
|
top: -4px;
|
|
right: -4px;
|
|
min-width: 14px;
|
|
height: 14px;
|
|
border-radius: 9px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 10px;
|
|
z-index: 10;
|
|
padding: 0 5px;
|
|
line-height: 14px;
|
|
background-color: var(--color-accent);
|
|
}
|
|
|
|
.calendar-event-dot {
|
|
position: absolute;
|
|
left: 50%;
|
|
bottom: 4px;
|
|
transform: translateX(-50%);
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 9999px;
|
|
background-color: var(--group-calendar-event-dot, #ef4444);
|
|
z-index: 5;
|
|
pointer-events: none;
|
|
}
|
|
</style>
|