updated datepicker dependency
This commit is contained in:
@@ -10,8 +10,9 @@ import {
|
||||
fromDate,
|
||||
getLocalTimeZone,
|
||||
parseDate,
|
||||
today,
|
||||
} from "@internationalized/date"
|
||||
import type { DateRange } from "reka-ui"
|
||||
import type { DateRange, DateValue } from "reka-ui"
|
||||
import type { Ref } from "vue"
|
||||
import Popover from "@/components/ui/popover/Popover.vue";
|
||||
import PopoverTrigger from "@/components/ui/popover/PopoverTrigger.vue";
|
||||
@@ -84,6 +85,11 @@ onMounted(async () => {
|
||||
console.log(currentMember.value);
|
||||
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>
|
||||
|
||||
@@ -175,7 +181,7 @@ onMounted(async () => {
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<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()))"
|
||||
layout="month-and-year" />
|
||||
</PopoverContent>
|
||||
@@ -204,7 +210,8 @@ onMounted(async () => {
|
||||
<Calendar
|
||||
: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()))"
|
||||
layout="month-and-year" />
|
||||
:default-placeholder="defaultPlaceholder" layout="month-and-year">
|
||||
</Calendar>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div class="h-4">
|
||||
@@ -233,5 +240,11 @@ onMounted(async () => {
|
||||
</div>
|
||||
</form>
|
||||
</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>
|
||||
</template>
|
||||
@@ -1,7 +1,14 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { CalendarRoot, useForwardPropsEmits } from "reka-ui";
|
||||
import { getLocalTimeZone, today } from "@internationalized/date";
|
||||
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 {
|
||||
NativeSelect,
|
||||
NativeSelectOption,
|
||||
} from '@/components/ui/native-select';
|
||||
import {
|
||||
CalendarCell,
|
||||
CalendarCellTrigger,
|
||||
@@ -38,34 +45,165 @@ const props = defineProps({
|
||||
dir: { type: String, required: false },
|
||||
nextPage: { 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 },
|
||||
disableDaysOutsideCurrentView: { 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 },
|
||||
layout: { type: null, required: false, default: undefined },
|
||||
yearRange: { type: Array, required: false },
|
||||
});
|
||||
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);
|
||||
</script>
|
||||
|
||||
<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
|
||||
v-slot="{ grid, weekDays }"
|
||||
v-slot="{ grid, weekDays, date }"
|
||||
v-bind="forwarded"
|
||||
v-model:placeholder="placeholder"
|
||||
data-slot="calendar"
|
||||
:class="cn('p-3', props.class)"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<CalendarHeader>
|
||||
<CalendarHeading />
|
||||
<CalendarHeader class="pt-0">
|
||||
<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">
|
||||
<CalendarPrevButton />
|
||||
<CalendarNextButton />
|
||||
</div>
|
||||
<slot
|
||||
name="calendar-heading"
|
||||
:date="date"
|
||||
: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>
|
||||
|
||||
<div class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
|
||||
|
||||
@@ -6,7 +6,7 @@ import { cn } from "@/lib/utils";
|
||||
const props = defineProps({
|
||||
date: { type: null, required: true },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ const props = defineProps({
|
||||
day: { type: null, required: true },
|
||||
month: { type: null, required: true },
|
||||
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 },
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CalendarGridBody } from "reka-ui";
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
as: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CalendarGridHead } from "reka-ui";
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
||||
data-slot="calendar-head-cell"
|
||||
:class="
|
||||
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,
|
||||
)
|
||||
"
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
@@ -18,7 +18,10 @@ const forwardedProps = useForwardProps(delegatedProps);
|
||||
<CalendarHeader
|
||||
data-slot="calendar-header"
|
||||
: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"
|
||||
>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { buttonVariants } from '@/components/ui/button';
|
||||
const props = defineProps({
|
||||
nextPage: { type: Function, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
@@ -23,7 +23,6 @@ const forwardedProps = useForwardProps(delegatedProps);
|
||||
:class="
|
||||
cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'absolute right-1',
|
||||
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
props.class,
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import { buttonVariants } from '@/components/ui/button';
|
||||
const props = defineProps({
|
||||
prevPage: { type: Function, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
@@ -23,7 +23,6 @@ const forwardedProps = useForwardProps(delegatedProps);
|
||||
:class="
|
||||
cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'absolute left-1',
|
||||
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
props.class,
|
||||
)
|
||||
|
||||
51
ui/src/components/ui/native-select/NativeSelect.vue
Normal file
51
ui/src/components/ui/native-select/NativeSelect.vue
Normal 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>
|
||||
19
ui/src/components/ui/native-select/NativeSelectOptGroup.vue
Normal file
19
ui/src/components/ui/native-select/NativeSelectOptGroup.vue
Normal 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>
|
||||
19
ui/src/components/ui/native-select/NativeSelectOption.vue
Normal file
19
ui/src/components/ui/native-select/NativeSelectOption.vue
Normal 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>
|
||||
3
ui/src/components/ui/native-select/index.js
Normal file
3
ui/src/components/ui/native-select/index.js
Normal 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";
|
||||
Reference in New Issue
Block a user