updated datepicker dependency

This commit is contained in:
2025-12-11 11:00:02 -05:00
parent 92c0d657ea
commit dd472a5283
17 changed files with 275 additions and 31 deletions

View File

@@ -10,8 +10,9 @@ import {
fromDate, fromDate,
getLocalTimeZone, getLocalTimeZone,
parseDate, parseDate,
today,
} from "@internationalized/date" } from "@internationalized/date"
import type { DateRange } from "reka-ui" import type { DateRange, DateValue } from "reka-ui"
import type { Ref } from "vue" import type { Ref } from "vue"
import Popover from "@/components/ui/popover/Popover.vue"; import Popover from "@/components/ui/popover/Popover.vue";
import PopoverTrigger from "@/components/ui/popover/PopoverTrigger.vue"; import PopoverTrigger from "@/components/ui/popover/PopoverTrigger.vue";
@@ -84,6 +85,11 @@ onMounted(async () => {
console.log(currentMember.value); console.log(currentMember.value);
resetForm({ values: { member_id: currentMember.value?.member_id } }); resetForm({ values: { member_id: currentMember.value?.member_id } });
}); });
import type { LayoutTypes } from '@/components/ui/calendar'
const defaultPlaceholder = today(getLocalTimeZone())
const date = ref(today(getLocalTimeZone())) as Ref<DateValue>
const layout = ref<LayoutTypes>('month-and-year')
</script> </script>
@@ -175,7 +181,7 @@ onMounted(async () => {
</PopoverTrigger> </PopoverTrigger>
<PopoverContent class="w-auto p-0"> <PopoverContent class="w-auto p-0">
<Calendar <Calendar
:model-value="field.value ? fromDate(field.value, getLocalTimeZone()) : null" :model-value="field.value ? fromDate(field.value, getLocalTimeZone()) : null"
@update:model-value="(val: CalendarDate) => field.onChange(val.toDate(getLocalTimeZone()))" @update:model-value="(val: CalendarDate) => field.onChange(val.toDate(getLocalTimeZone()))"
layout="month-and-year" /> layout="month-and-year" />
</PopoverContent> </PopoverContent>
@@ -204,7 +210,8 @@ onMounted(async () => {
<Calendar <Calendar
:model-value="field.value ? new CalendarDate(field.value.getFullYear(), field.value.getMonth() + 1, field.value.getDate()) : null" :model-value="field.value ? new CalendarDate(field.value.getFullYear(), field.value.getMonth() + 1, field.value.getDate()) : null"
@update:model-value="(val: CalendarDate) => field.onChange(val.toDate(getLocalTimeZone()))" @update:model-value="(val: CalendarDate) => field.onChange(val.toDate(getLocalTimeZone()))"
layout="month-and-year" /> :default-placeholder="defaultPlaceholder" layout="month-and-year">
</Calendar>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
<div class="h-4"> <div class="h-4">
@@ -233,5 +240,11 @@ onMounted(async () => {
</div> </div>
</form> </form>
</div> </div>
<div class="flex flex-col gap-4 w-min">
<Calendar v-model="date" :default-placeholder="defaultPlaceholder" class="rounded-md border shadow-sm"
layout="month-and-year" disable-days-outside-current-view />
</div>
</div> </div>
</template> </template>

View File

@@ -1,7 +1,14 @@
<script setup> <script setup>
import { reactiveOmit } from "@vueuse/core"; import { getLocalTimeZone, today } from "@internationalized/date";
import { CalendarRoot, useForwardPropsEmits } from "reka-ui"; import { createReusableTemplate, reactiveOmit, useVModel } from "@vueuse/core";
import { CalendarRoot, useDateFormatter, useForwardPropsEmits } from "reka-ui";
import { createYear, createYearRange, toDate } from "reka-ui/date";
import { computed, toRaw } from "vue";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import {
NativeSelect,
NativeSelectOption,
} from '@/components/ui/native-select';
import { import {
CalendarCell, CalendarCell,
CalendarCellTrigger, CalendarCellTrigger,
@@ -38,34 +45,165 @@ const props = defineProps({
dir: { type: String, required: false }, dir: { type: String, required: false },
nextPage: { type: Function, required: false }, nextPage: { type: Function, required: false },
prevPage: { type: Function, required: false }, prevPage: { type: Function, required: false },
modelValue: { type: null, required: false }, modelValue: { type: null, required: false, default: undefined },
multiple: { type: Boolean, required: false }, multiple: { type: Boolean, required: false },
disableDaysOutsideCurrentView: { type: Boolean, required: false }, disableDaysOutsideCurrentView: { type: Boolean, required: false },
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false }, as: { type: null, required: false },
class: { type: null, required: false }, class: { type: null, required: false },
layout: { type: null, required: false, default: undefined },
yearRange: { type: Array, required: false },
}); });
const emits = defineEmits(["update:modelValue", "update:placeholder"]); const emits = defineEmits(["update:modelValue", "update:placeholder"]);
const delegatedProps = reactiveOmit(props, "class"); const delegatedProps = reactiveOmit(props, "class", "layout", "placeholder");
const placeholder = useVModel(props, "placeholder", emits, {
passive: true,
defaultValue: props.defaultPlaceholder ?? today(getLocalTimeZone()),
});
const formatter = useDateFormatter(props.locale ?? "en");
const yearRange = computed(() => {
return (
props.yearRange ??
createYearRange({
start:
props?.minValue ??
(
toRaw(props.placeholder) ??
props.defaultPlaceholder ??
today(getLocalTimeZone())
).cycle("year", -100),
end:
props?.maxValue ??
(
toRaw(props.placeholder) ??
props.defaultPlaceholder ??
today(getLocalTimeZone())
).cycle("year", 10),
})
);
});
const [DefineMonthTemplate, ReuseMonthTemplate] = createReusableTemplate();
const [DefineYearTemplate, ReuseYearTemplate] = createReusableTemplate();
const forwarded = useForwardPropsEmits(delegatedProps, emits); const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script> </script>
<template> <template>
<DefineMonthTemplate v-slot="{ date }">
<div class="**:data-[slot=native-select-icon]:right-1">
<div class="relative">
<div
class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none"
>
{{ formatter.custom(toDate(date), { month: "short" }) }}
</div>
<NativeSelect
class="text-xs h-8 pr-6 pl-2 text-transparent relative"
@change="
(e) => {
placeholder = placeholder.set({
month: Number(e?.target?.value),
});
}
"
>
<NativeSelectOption
v-for="month in createYear({ dateObj: date })"
:key="month.toString()"
:value="month.month"
:selected="date.month === month.month"
>
{{ formatter.custom(toDate(month), { month: "short" }) }}
</NativeSelectOption>
</NativeSelect>
</div>
</div>
</DefineMonthTemplate>
<DefineYearTemplate v-slot="{ date }">
<div class="**:data-[slot=native-select-icon]:right-1">
<div class="relative">
<div
class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none"
>
{{ formatter.custom(toDate(date), { year: "numeric" }) }}
</div>
<NativeSelect
class="text-xs h-8 pr-6 pl-2 text-transparent relative"
@change="
(e) => {
placeholder = placeholder.set({
year: Number(e?.target?.value),
});
}
"
>
<NativeSelectOption
v-for="year in yearRange"
:key="year.toString()"
:value="year.year"
:selected="date.year === year.year"
>
{{ formatter.custom(toDate(year), { year: "numeric" }) }}
</NativeSelectOption>
</NativeSelect>
</div>
</div>
</DefineYearTemplate>
<CalendarRoot <CalendarRoot
v-slot="{ grid, weekDays }" v-slot="{ grid, weekDays, date }"
v-bind="forwarded"
v-model:placeholder="placeholder"
data-slot="calendar" data-slot="calendar"
:class="cn('p-3', props.class)" :class="cn('p-3', props.class)"
v-bind="forwarded"
> >
<CalendarHeader> <CalendarHeader class="pt-0">
<CalendarHeading /> <nav
class="flex items-center gap-1 absolute top-0 inset-x-0 justify-between"
>
<CalendarPrevButton>
<slot name="calendar-prev-icon" />
</CalendarPrevButton>
<CalendarNextButton>
<slot name="calendar-next-icon" />
</CalendarNextButton>
</nav>
<div class="flex items-center gap-1"> <slot
<CalendarPrevButton /> name="calendar-heading"
<CalendarNextButton /> :date="date"
</div> :month="ReuseMonthTemplate"
:year="ReuseYearTemplate"
>
<template v-if="layout === 'month-and-year'">
<div class="flex items-center justify-center gap-1">
<ReuseMonthTemplate :date="date" />
<ReuseYearTemplate :date="date" />
</div>
</template>
<template v-else-if="layout === 'month-only'">
<div class="flex items-center justify-center gap-1">
<ReuseMonthTemplate :date="date" />
{{ formatter.custom(toDate(date), { year: "numeric" }) }}
</div>
</template>
<template v-else-if="layout === 'year-only'">
<div class="flex items-center justify-center gap-1">
{{ formatter.custom(toDate(date), { month: "short" }) }}
<ReuseYearTemplate :date="date" />
</div>
</template>
<template v-else>
<CalendarHeading />
</template>
</slot>
</CalendarHeader> </CalendarHeader>
<div class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0"> <div class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">

View File

@@ -6,7 +6,7 @@ import { cn } from "@/lib/utils";
const props = defineProps({ const props = defineProps({
date: { type: null, required: true }, date: { type: null, required: true },
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false }, as: { type: null, required: false },
class: { type: null, required: false }, class: { type: null, required: false },
}); });

View File

@@ -8,7 +8,7 @@ const props = defineProps({
day: { type: null, required: true }, day: { type: null, required: true },
month: { type: null, required: true }, month: { type: null, required: true },
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false, default: "button" }, as: { type: null, required: false, default: "button" },
class: { type: null, required: false }, class: { type: null, required: false },
}); });

View File

@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
const props = defineProps({ const props = defineProps({
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false }, as: { type: null, required: false },
class: { type: null, required: false }, class: { type: null, required: false },
}); });

View File

@@ -3,7 +3,7 @@ import { CalendarGridBody } from "reka-ui";
const props = defineProps({ const props = defineProps({
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false }, as: { type: null, required: false },
}); });
</script> </script>

View File

@@ -3,7 +3,7 @@ import { CalendarGridHead } from "reka-ui";
const props = defineProps({ const props = defineProps({
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false }, as: { type: null, required: false },
class: { type: null, required: false }, class: { type: null, required: false },
}); });
</script> </script>

View File

@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
const props = defineProps({ const props = defineProps({
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false }, as: { type: null, required: false },
class: { type: null, required: false }, class: { type: null, required: false },
}); });

View File

@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
const props = defineProps({ const props = defineProps({
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false }, as: { type: null, required: false },
class: { type: null, required: false }, class: { type: null, required: false },
}); });
@@ -19,7 +19,7 @@ const forwardedProps = useForwardProps(delegatedProps);
data-slot="calendar-head-cell" data-slot="calendar-head-cell"
:class=" :class="
cn( cn(
'text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]', 'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem]',
props.class, props.class,
) )
" "

View File

@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
const props = defineProps({ const props = defineProps({
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false }, as: { type: null, required: false },
class: { type: null, required: false }, class: { type: null, required: false },
}); });
@@ -18,7 +18,10 @@ const forwardedProps = useForwardProps(delegatedProps);
<CalendarHeader <CalendarHeader
data-slot="calendar-header" data-slot="calendar-header"
:class=" :class="
cn('flex justify-center pt-1 relative items-center w-full', props.class) cn(
'flex justify-center pt-1 relative items-center w-full px-8',
props.class,
)
" "
v-bind="forwardedProps" v-bind="forwardedProps"
> >

View File

@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
const props = defineProps({ const props = defineProps({
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false }, as: { type: null, required: false },
class: { type: null, required: false }, class: { type: null, required: false },
}); });

View File

@@ -8,7 +8,7 @@ import { buttonVariants } from '@/components/ui/button';
const props = defineProps({ const props = defineProps({
nextPage: { type: Function, required: false }, nextPage: { type: Function, required: false },
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false }, as: { type: null, required: false },
class: { type: null, required: false }, class: { type: null, required: false },
}); });
@@ -23,7 +23,6 @@ const forwardedProps = useForwardProps(delegatedProps);
:class=" :class="
cn( cn(
buttonVariants({ variant: 'outline' }), buttonVariants({ variant: 'outline' }),
'absolute right-1',
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100', 'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
props.class, props.class,
) )

View File

@@ -8,7 +8,7 @@ import { buttonVariants } from '@/components/ui/button';
const props = defineProps({ const props = defineProps({
prevPage: { type: Function, required: false }, prevPage: { type: Function, required: false },
asChild: { type: Boolean, required: false }, asChild: { type: Boolean, required: false },
as: { type: [String, Object, Function], required: false }, as: { type: null, required: false },
class: { type: null, required: false }, class: { type: null, required: false },
}); });
@@ -23,7 +23,6 @@ const forwardedProps = useForwardProps(delegatedProps);
:class=" :class="
cn( cn(
buttonVariants({ variant: 'outline' }), buttonVariants({ variant: 'outline' }),
'absolute left-1',
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100', 'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
props.class, props.class,
) )

View File

@@ -0,0 +1,51 @@
<script setup>
import { reactiveOmit, useVModel } from "@vueuse/core";
import { ChevronDownIcon } from "lucide-vue-next";
import { cn } from "@/lib/utils";
defineOptions({
inheritAttrs: false,
});
const props = defineProps({
modelValue: { type: null, required: false },
class: { type: null, required: false },
});
const emit = defineEmits(["update:modelValue"]);
const modelValue = useVModel(props, "modelValue", emit, {
passive: true,
defaultValue: "",
});
const delegatedProps = reactiveOmit(props, "class");
</script>
<template>
<div
class="group/native-select relative w-fit has-[select:disabled]:opacity-50"
data-slot="native-select-wrapper"
>
<select
v-bind="{ ...$attrs, ...delegatedProps }"
v-model="modelValue"
data-slot="native-select"
:class="
cn(
'border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 h-9 w-full min-w-0 appearance-none rounded-md border bg-transparent px-3 py-2 pr-9 text-sm shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
props.class,
)
"
>
<slot />
</select>
<ChevronDownIcon
class="text-muted-foreground pointer-events-none absolute top-1/2 right-3.5 size-4 -translate-y-1/2 opacity-50 select-none"
aria-hidden="true"
data-slot="native-select-icon"
/>
</div>
</template>

View File

@@ -0,0 +1,19 @@
<!-- @fallthroughAttributes true -->
<!-- @strictTemplates true -->
<script setup>
import { cn } from "@/lib/utils";
const props = defineProps({
class: { type: null, required: false },
});
</script>
<template>
<optgroup
data-slot="native-select-optgroup"
:class="cn('bg-popover text-popover-foreground', props.class)"
>
<slot />
</optgroup>
</template>

View File

@@ -0,0 +1,19 @@
<!-- @fallthroughAttributes true -->
<!-- @strictTemplates true -->
<script setup>
import { cn } from "@/lib/utils";
const props = defineProps({
class: { type: null, required: false },
});
</script>
<template>
<option
data-slot="native-select-option"
:class="cn('bg-popover text-popover-foreground', props.class)"
>
<slot />
</option>
</template>

View File

@@ -0,0 +1,3 @@
export { default as NativeSelect } from "./NativeSelect.vue";
export { default as NativeSelectOptGroup } from "./NativeSelectOptGroup.vue";
export { default as NativeSelectOption } from "./NativeSelectOption.vue";