feat: new calendar components (#468)
This commit is contained in:
parent
11d77fb9a1
commit
cfccb8e510
|
|
@ -1,6 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { type Ref, ref } from 'vue'
|
||||||
import { addDays, startOfToday } from 'date-fns'
|
import type { DateRange } from 'radix-vue'
|
||||||
|
import { getLocalTimeZone, today } from '@internationalized/date'
|
||||||
|
|
||||||
import ThemingLayout from './../../layout/ThemingLayout.vue'
|
import ThemingLayout from './../../layout/ThemingLayout.vue'
|
||||||
|
|
||||||
import CookieSettings from '@/examples/cards/components/CookieSettings.vue'
|
import CookieSettings from '@/examples/cards/components/CookieSettings.vue'
|
||||||
|
|
@ -19,12 +21,14 @@ import CardStats from '@/lib/registry/new-york/example/CardStats.vue'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
} from '@/lib/registry/new-york/ui/card'
|
} from '@/lib/registry/new-york/ui/card'
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
import { RangeCalendar } from '@/lib/registry/new-york/ui/range-calendar'
|
||||||
|
|
||||||
|
const now = today(getLocalTimeZone())
|
||||||
|
|
||||||
const range = ref({
|
const range = ref({
|
||||||
start: startOfToday(),
|
start: now,
|
||||||
end: addDays(startOfToday(), 8),
|
end: now.add({ days: 8 }),
|
||||||
})
|
}) as Ref<DateRange>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -52,7 +56,7 @@ const range = ref({
|
||||||
<div class="space-y-4 lg:col-span-6 xl:col-span-5 xl:space-y-4">
|
<div class="space-y-4 lg:col-span-6 xl:col-span-5 xl:space-y-4">
|
||||||
<div class="hidden gap-1 sm:grid-cols-[280px_1fr] md:grid">
|
<div class="hidden gap-1 sm:grid-cols-[280px_1fr] md:grid">
|
||||||
<Card class="max-w-[280px]">
|
<Card class="max-w-[280px]">
|
||||||
<Calendar v-model.range="range" />
|
<RangeCalendar v-model="range" />
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<div class="pt-3 sm:pl-2 sm:pt-0 xl:pl-3">
|
<div class="pt-3 sm:pl-2 sm:pt-0 xl:pl-3">
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,6 @@ export const docsConfig: DocsConfig = {
|
||||||
title: 'Contribution',
|
title: 'Contribution',
|
||||||
href: '/docs/contribution',
|
href: '/docs/contribution',
|
||||||
items: [],
|
items: [],
|
||||||
label: 'New',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -172,7 +171,6 @@ export const docsConfig: DocsConfig = {
|
||||||
title: 'Breadcrumb',
|
title: 'Breadcrumb',
|
||||||
href: '/docs/components/breadcrumb',
|
href: '/docs/components/breadcrumb',
|
||||||
items: [],
|
items: [],
|
||||||
label: 'New',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Button',
|
title: 'Button',
|
||||||
|
|
@ -183,6 +181,7 @@ export const docsConfig: DocsConfig = {
|
||||||
title: 'Calendar',
|
title: 'Calendar',
|
||||||
href: '/docs/components/calendar',
|
href: '/docs/components/calendar',
|
||||||
items: [],
|
items: [],
|
||||||
|
label: 'Updated',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Card',
|
title: 'Card',
|
||||||
|
|
@ -192,7 +191,6 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Carousel',
|
title: 'Carousel',
|
||||||
href: '/docs/components/carousel',
|
href: '/docs/components/carousel',
|
||||||
label: 'New',
|
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -229,6 +227,7 @@ export const docsConfig: DocsConfig = {
|
||||||
title: 'Date Picker',
|
title: 'Date Picker',
|
||||||
href: '/docs/components/date-picker',
|
href: '/docs/components/date-picker',
|
||||||
items: [],
|
items: [],
|
||||||
|
label: 'Updated',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Dialog',
|
title: 'Dialog',
|
||||||
|
|
@ -239,7 +238,6 @@ export const docsConfig: DocsConfig = {
|
||||||
title: 'Drawer',
|
title: 'Drawer',
|
||||||
href: '/docs/components/drawer',
|
href: '/docs/components/drawer',
|
||||||
items: [],
|
items: [],
|
||||||
label: 'New',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Dropdown Menu',
|
title: 'Dropdown Menu',
|
||||||
|
|
@ -284,7 +282,6 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Pin Input',
|
title: 'Pin Input',
|
||||||
href: '/docs/components/pin-input',
|
href: '/docs/components/pin-input',
|
||||||
label: 'New',
|
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -302,10 +299,15 @@ export const docsConfig: DocsConfig = {
|
||||||
href: '/docs/components/radio-group',
|
href: '/docs/components/radio-group',
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Range Calendar',
|
||||||
|
href: '/docs/components/range-calendar',
|
||||||
|
items: [],
|
||||||
|
label: 'New',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Resizable',
|
title: 'Resizable',
|
||||||
href: '/docs/components/resizable',
|
href: '/docs/components/resizable',
|
||||||
label: 'New',
|
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -341,7 +343,6 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Sonner',
|
title: 'Sonner',
|
||||||
href: '/docs/components/sonner',
|
href: '/docs/components/sonner',
|
||||||
label: 'New',
|
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -362,7 +363,6 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Tags Input',
|
title: 'Tags Input',
|
||||||
href: '/docs/components/tags-input',
|
href: '/docs/components/tags-input',
|
||||||
label: 'New',
|
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,20 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/default/example/CalendarDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/CalendarDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/CalendarDemo.vue"],
|
files: ["../src/lib/registry/default/example/CalendarDemo.vue"],
|
||||||
},
|
},
|
||||||
|
"CalendarForm": {
|
||||||
|
name: "CalendarForm",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["calendar","button","form","popover","toast","utils"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/CalendarForm.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/CalendarForm.vue"],
|
||||||
|
},
|
||||||
|
"CalendarWithSelect": {
|
||||||
|
name: "CalendarWithSelect",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["calendar","select","utils"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/CalendarWithSelect.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/CalendarWithSelect.vue"],
|
||||||
|
},
|
||||||
"CardChat": {
|
"CardChat": {
|
||||||
name: "CardChat",
|
name: "CardChat",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -398,38 +412,31 @@ export const Index = {
|
||||||
"DatePickerDemo": {
|
"DatePickerDemo": {
|
||||||
name: "DatePickerDemo",
|
name: "DatePickerDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
registryDependencies: ["utils","button","calendar","popover"],
|
registryDependencies: ["calendar","button","popover","utils"],
|
||||||
component: () => import("../src/lib/registry/default/example/DatePickerDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/DatePickerDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/DatePickerDemo.vue"],
|
files: ["../src/lib/registry/default/example/DatePickerDemo.vue"],
|
||||||
},
|
},
|
||||||
"DatePickerForm": {
|
"DatePickerForm": {
|
||||||
name: "DatePickerForm",
|
name: "DatePickerForm",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
registryDependencies: ["utils","button","calendar","form","popover","toast"],
|
registryDependencies: ["calendar","button","form","popover","toast","utils"],
|
||||||
component: () => import("../src/lib/registry/default/example/DatePickerForm.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/DatePickerForm.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/DatePickerForm.vue"],
|
files: ["../src/lib/registry/default/example/DatePickerForm.vue"],
|
||||||
},
|
},
|
||||||
"DatePickerWithPresets": {
|
"DatePickerWithPresets": {
|
||||||
name: "DatePickerWithPresets",
|
name: "DatePickerWithPresets",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
registryDependencies: ["utils","button","calendar","popover","select"],
|
registryDependencies: ["calendar","button","popover","select","utils"],
|
||||||
component: () => import("../src/lib/registry/default/example/DatePickerWithPresets.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/DatePickerWithPresets.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/DatePickerWithPresets.vue"],
|
files: ["../src/lib/registry/default/example/DatePickerWithPresets.vue"],
|
||||||
},
|
},
|
||||||
"DatePickerWithRange": {
|
"DatePickerWithRange": {
|
||||||
name: "DatePickerWithRange",
|
name: "DatePickerWithRange",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
registryDependencies: ["utils","button","calendar","popover"],
|
registryDependencies: ["range-calendar","button","popover","utils"],
|
||||||
component: () => import("../src/lib/registry/default/example/DatePickerWithRange.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/DatePickerWithRange.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/DatePickerWithRange.vue"],
|
files: ["../src/lib/registry/default/example/DatePickerWithRange.vue"],
|
||||||
},
|
},
|
||||||
"DateTimePickerDemo": {
|
|
||||||
name: "DateTimePickerDemo",
|
|
||||||
type: "components:example",
|
|
||||||
registryDependencies: ["utils","button","calendar","popover"],
|
|
||||||
component: () => import("../src/lib/registry/default/example/DateTimePickerDemo.vue").then((m) => m.default),
|
|
||||||
files: ["../src/lib/registry/default/example/DateTimePickerDemo.vue"],
|
|
||||||
},
|
|
||||||
"DialogCustomCloseButton": {
|
"DialogCustomCloseButton": {
|
||||||
name: "DialogCustomCloseButton",
|
name: "DialogCustomCloseButton",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -654,12 +661,12 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/default/example/RadioGroupForm.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/RadioGroupForm.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/RadioGroupForm.vue"],
|
files: ["../src/lib/registry/default/example/RadioGroupForm.vue"],
|
||||||
},
|
},
|
||||||
"RangePickerWithSlot": {
|
"RangeCalendarDemo": {
|
||||||
name: "RangePickerWithSlot",
|
name: "RangeCalendarDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
registryDependencies: ["utils","button","calendar","popover"],
|
registryDependencies: ["range-calendar"],
|
||||||
component: () => import("../src/lib/registry/default/example/RangePickerWithSlot.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/RangeCalendarDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/RangePickerWithSlot.vue"],
|
files: ["../src/lib/registry/default/example/RangeCalendarDemo.vue"],
|
||||||
},
|
},
|
||||||
"ResizableDemo": {
|
"ResizableDemo": {
|
||||||
name: "ResizableDemo",
|
name: "ResizableDemo",
|
||||||
|
|
@ -1081,6 +1088,55 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/default/example/TypographyTable.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/default/example/TypographyTable.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/default/example/TypographyTable.vue"],
|
files: ["../src/lib/registry/default/example/TypographyTable.vue"],
|
||||||
},
|
},
|
||||||
|
"VCalendarDemo": {
|
||||||
|
name: "VCalendarDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["v-calendar"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/VCalendarDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/VCalendarDemo.vue"],
|
||||||
|
},
|
||||||
|
"VDatePickerDemo": {
|
||||||
|
name: "VDatePickerDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","popover"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/VDatePickerDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/VDatePickerDemo.vue"],
|
||||||
|
},
|
||||||
|
"VDatePickerForm": {
|
||||||
|
name: "VDatePickerForm",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","form","popover","toast"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/VDatePickerForm.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/VDatePickerForm.vue"],
|
||||||
|
},
|
||||||
|
"VDatePickerWithPresets": {
|
||||||
|
name: "VDatePickerWithPresets",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","popover","select"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/VDatePickerWithPresets.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/VDatePickerWithPresets.vue"],
|
||||||
|
},
|
||||||
|
"VDatePickerWithRange": {
|
||||||
|
name: "VDatePickerWithRange",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","popover"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/VDatePickerWithRange.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/VDatePickerWithRange.vue"],
|
||||||
|
},
|
||||||
|
"VDateTimePickerDemo": {
|
||||||
|
name: "VDateTimePickerDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","popover"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/VDateTimePickerDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/VDateTimePickerDemo.vue"],
|
||||||
|
},
|
||||||
|
"VRangePickerWithSlot": {
|
||||||
|
name: "VRangePickerWithSlot",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","popover"],
|
||||||
|
component: () => import("../src/lib/registry/default/example/VRangePickerWithSlot.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/default/example/VRangePickerWithSlot.vue"],
|
||||||
|
},
|
||||||
"ActivityGoal": {
|
"ActivityGoal": {
|
||||||
name: "ActivityGoal",
|
name: "ActivityGoal",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -1369,6 +1425,20 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/new-york/example/CalendarDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/CalendarDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/CalendarDemo.vue"],
|
files: ["../src/lib/registry/new-york/example/CalendarDemo.vue"],
|
||||||
},
|
},
|
||||||
|
"CalendarForm": {
|
||||||
|
name: "CalendarForm",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["calendar","button","form","popover","toast","utils"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/CalendarForm.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/CalendarForm.vue"],
|
||||||
|
},
|
||||||
|
"CalendarWithSelect": {
|
||||||
|
name: "CalendarWithSelect",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["calendar","select","utils"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/CalendarWithSelect.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/CalendarWithSelect.vue"],
|
||||||
|
},
|
||||||
"CardChat": {
|
"CardChat": {
|
||||||
name: "CardChat",
|
name: "CardChat",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -1575,38 +1645,31 @@ export const Index = {
|
||||||
"DatePickerDemo": {
|
"DatePickerDemo": {
|
||||||
name: "DatePickerDemo",
|
name: "DatePickerDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
registryDependencies: ["utils","button","calendar","popover"],
|
registryDependencies: ["calendar","button","popover","utils"],
|
||||||
component: () => import("../src/lib/registry/new-york/example/DatePickerDemo.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/DatePickerDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/DatePickerDemo.vue"],
|
files: ["../src/lib/registry/new-york/example/DatePickerDemo.vue"],
|
||||||
},
|
},
|
||||||
"DatePickerForm": {
|
"DatePickerForm": {
|
||||||
name: "DatePickerForm",
|
name: "DatePickerForm",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
registryDependencies: ["utils","button","calendar","form","popover","toast"],
|
registryDependencies: ["calendar","button","form","popover","toast","utils"],
|
||||||
component: () => import("../src/lib/registry/new-york/example/DatePickerForm.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/DatePickerForm.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/DatePickerForm.vue"],
|
files: ["../src/lib/registry/new-york/example/DatePickerForm.vue"],
|
||||||
},
|
},
|
||||||
"DatePickerWithPresets": {
|
"DatePickerWithPresets": {
|
||||||
name: "DatePickerWithPresets",
|
name: "DatePickerWithPresets",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
registryDependencies: ["utils","button","calendar","popover","select"],
|
registryDependencies: ["calendar","button","popover","select","utils"],
|
||||||
component: () => import("../src/lib/registry/new-york/example/DatePickerWithPresets.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/DatePickerWithPresets.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/DatePickerWithPresets.vue"],
|
files: ["../src/lib/registry/new-york/example/DatePickerWithPresets.vue"],
|
||||||
},
|
},
|
||||||
"DatePickerWithRange": {
|
"DatePickerWithRange": {
|
||||||
name: "DatePickerWithRange",
|
name: "DatePickerWithRange",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
registryDependencies: ["utils","button","calendar","popover"],
|
registryDependencies: ["range-calendar","button","popover","utils"],
|
||||||
component: () => import("../src/lib/registry/new-york/example/DatePickerWithRange.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/DatePickerWithRange.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/DatePickerWithRange.vue"],
|
files: ["../src/lib/registry/new-york/example/DatePickerWithRange.vue"],
|
||||||
},
|
},
|
||||||
"DateTimePickerDemo": {
|
|
||||||
name: "DateTimePickerDemo",
|
|
||||||
type: "components:example",
|
|
||||||
registryDependencies: ["utils","button","calendar","popover"],
|
|
||||||
component: () => import("../src/lib/registry/new-york/example/DateTimePickerDemo.vue").then((m) => m.default),
|
|
||||||
files: ["../src/lib/registry/new-york/example/DateTimePickerDemo.vue"],
|
|
||||||
},
|
|
||||||
"DialogCustomCloseButton": {
|
"DialogCustomCloseButton": {
|
||||||
name: "DialogCustomCloseButton",
|
name: "DialogCustomCloseButton",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
@ -1831,12 +1894,12 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/new-york/example/RadioGroupForm.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/RadioGroupForm.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/RadioGroupForm.vue"],
|
files: ["../src/lib/registry/new-york/example/RadioGroupForm.vue"],
|
||||||
},
|
},
|
||||||
"RangePickerWithSlot": {
|
"RangeCalendarDemo": {
|
||||||
name: "RangePickerWithSlot",
|
name: "RangeCalendarDemo",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
registryDependencies: ["utils","button","calendar","popover"],
|
registryDependencies: ["range-calendar"],
|
||||||
component: () => import("../src/lib/registry/new-york/example/RangePickerWithSlot.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/RangeCalendarDemo.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/RangePickerWithSlot.vue"],
|
files: ["../src/lib/registry/new-york/example/RangeCalendarDemo.vue"],
|
||||||
},
|
},
|
||||||
"ResizableDemo": {
|
"ResizableDemo": {
|
||||||
name: "ResizableDemo",
|
name: "ResizableDemo",
|
||||||
|
|
@ -2258,6 +2321,55 @@ export const Index = {
|
||||||
component: () => import("../src/lib/registry/new-york/example/TypographyTable.vue").then((m) => m.default),
|
component: () => import("../src/lib/registry/new-york/example/TypographyTable.vue").then((m) => m.default),
|
||||||
files: ["../src/lib/registry/new-york/example/TypographyTable.vue"],
|
files: ["../src/lib/registry/new-york/example/TypographyTable.vue"],
|
||||||
},
|
},
|
||||||
|
"VCalendarDemo": {
|
||||||
|
name: "VCalendarDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["v-calendar"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/VCalendarDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/VCalendarDemo.vue"],
|
||||||
|
},
|
||||||
|
"VDatePickerDemo": {
|
||||||
|
name: "VDatePickerDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","popover"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/VDatePickerDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/VDatePickerDemo.vue"],
|
||||||
|
},
|
||||||
|
"VDatePickerForm": {
|
||||||
|
name: "VDatePickerForm",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","form","popover","toast"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/VDatePickerForm.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/VDatePickerForm.vue"],
|
||||||
|
},
|
||||||
|
"VDatePickerWithPresets": {
|
||||||
|
name: "VDatePickerWithPresets",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","popover","select"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/VDatePickerWithPresets.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/VDatePickerWithPresets.vue"],
|
||||||
|
},
|
||||||
|
"VDatePickerWithRange": {
|
||||||
|
name: "VDatePickerWithRange",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","popover"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/VDatePickerWithRange.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/VDatePickerWithRange.vue"],
|
||||||
|
},
|
||||||
|
"VDateTimePickerDemo": {
|
||||||
|
name: "VDateTimePickerDemo",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","popover"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/VDateTimePickerDemo.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/VDateTimePickerDemo.vue"],
|
||||||
|
},
|
||||||
|
"VRangePickerWithSlot": {
|
||||||
|
name: "VRangePickerWithSlot",
|
||||||
|
type: "components:example",
|
||||||
|
registryDependencies: ["utils","button","v-calendar","popover"],
|
||||||
|
component: () => import("../src/lib/registry/new-york/example/VRangePickerWithSlot.vue").then((m) => m.default),
|
||||||
|
files: ["../src/lib/registry/new-york/example/VRangePickerWithSlot.vue"],
|
||||||
|
},
|
||||||
"ActivityGoal": {
|
"ActivityGoal": {
|
||||||
name: "ActivityGoal",
|
name: "ActivityGoal",
|
||||||
type: "components:example",
|
type: "components:example",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formkit/auto-animate": "^0.8.1",
|
"@formkit/auto-animate": "^0.8.1",
|
||||||
|
"@internationalized/date": "^3.5.2",
|
||||||
"@radix-icons/vue": "^1.0.0",
|
"@radix-icons/vue": "^1.0.0",
|
||||||
"@stackblitz/sdk": "^1.9.0",
|
"@stackblitz/sdk": "^1.9.0",
|
||||||
"@tanstack/vue-table": "^8.14.0",
|
"@tanstack/vue-table": "^8.14.0",
|
||||||
|
|
@ -32,7 +33,7 @@
|
||||||
"embla-carousel-vue": "^8.0.0",
|
"embla-carousel-vue": "^8.0.0",
|
||||||
"lucide-vue-next": "^0.359.0",
|
"lucide-vue-next": "^0.359.0",
|
||||||
"magic-string": "^0.30.8",
|
"magic-string": "^0.30.8",
|
||||||
"radix-vue": "^1.6.2",
|
"radix-vue": "^1.7.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"v-calendar": "^3.1.2",
|
"v-calendar": "^3.1.2",
|
||||||
"vaul-vue": "^0.1.0",
|
"vaul-vue": "^0.1.0",
|
||||||
|
|
@ -67,8 +68,8 @@
|
||||||
"tsx": "^4.7.1",
|
"tsx": "^4.7.1",
|
||||||
"typescript": "^5.4.2",
|
"typescript": "^5.4.2",
|
||||||
"unplugin-icons": "^0.18.5",
|
"unplugin-icons": "^0.18.5",
|
||||||
"vite": "^5.2.2",
|
"vite": "^5.2.7",
|
||||||
"vitepress": "^1.0.0-rc.45",
|
"vitepress": "^1.0.1",
|
||||||
"vue-tsc": "^2.0.7"
|
"vue-tsc": "^2.0.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,90 +2,41 @@
|
||||||
title: Calendar
|
title: Calendar
|
||||||
description: A date field component that allows users to enter and edit date.
|
description: A date field component that allows users to enter and edit date.
|
||||||
source: apps/www/src/lib/registry/default/ui/calendar
|
source: apps/www/src/lib/registry/default/ui/calendar
|
||||||
primitive: https://vcalendar.io/
|
primitive: https://www.radix-vue.com/components/calendar.html
|
||||||
---
|
---
|
||||||
|
|
||||||
<ComponentPreview name="CalendarDemo" />
|
<ComponentPreview name="CalendarDemo" />
|
||||||
|
|
||||||
|
<Callout class="text-base mt-12">
|
||||||
|
|
||||||
|
If you're looking for **previous** Calendar implementation, checkout to <span class="font-bold underline">[VCalendar](/docs/components/v-calendar)</span> component
|
||||||
|
|
||||||
|
</Callout>
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
The `Calendar` component is built on top of [VCalendar](https://vcalendar.io/getting-started/installation.html).
|
The `<Calendar />` component is built on top of the [RadixVue Calendar](https://www.radix-vue.com/components/calendar.html) component, which uses the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) package to handle dates.
|
||||||
|
|
||||||
|
If you're looking for a range calendar, check out the [Range Calendar](/docs/components/range-calendar) component.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
<TabPreview name="CLI">
|
|
||||||
<template #CLI>
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx shadcn-vue@latest add calendar
|
npx shadcn-vue@latest add calendar
|
||||||
```
|
```
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #Manual>
|
## Datepicker
|
||||||
|
|
||||||
<Steps>
|
You can use the `<Calendar />` component to build a date picker. See the [Date Picker](/docs/components/date-picker) page for more information.
|
||||||
|
|
||||||
### Install the following dependency
|
## Examples
|
||||||
|
|
||||||
```bash
|
### Form
|
||||||
npm install v-calendar
|
|
||||||
```
|
|
||||||
|
|
||||||
### Copy and paste the following code into your project
|
<ComponentPreview name="CalendarForm" />
|
||||||
|
|
||||||
<<< @/lib/registry/default/ui/calendar/Calendar.vue
|
## Advanced Customization
|
||||||
|
|
||||||
</Steps>
|
### Month & Year Selects
|
||||||
|
|
||||||
</template>
|
<ComponentPreview name="CalendarWithSelect" />
|
||||||
</TabPreview>
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { Calendar } from '@/components/ui/calendar'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Calendar />
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
The API is essentially the same, i.e. props and slots. See the [VCalendar](https://vcalendar.io/getting-started/installation.html) documentation for more information.
|
|
||||||
|
|
||||||
### Slots
|
|
||||||
|
|
||||||
The slots available are [those currently supported](https://github.com/nathanreyes/v-calendar/blob/v3.1.2/src/components/Calendar/CalendarSlot.vue#L16-L28) by VCalendar, namely :
|
|
||||||
|
|
||||||
- `day-content`
|
|
||||||
- `day-popover`
|
|
||||||
- `dp-footer`
|
|
||||||
- `footer`
|
|
||||||
- `header-title-wrapper`
|
|
||||||
- `header-title`
|
|
||||||
- `header-prev-button`
|
|
||||||
- `header-next-button`
|
|
||||||
- `nav`
|
|
||||||
- `nav-prev-button`
|
|
||||||
- `nav-next-button`
|
|
||||||
- `page`
|
|
||||||
- `time-header`
|
|
||||||
|
|
||||||
Example using the `day-content` slot:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { Calendar } from '@/components/ui/calendar'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Calendar>
|
|
||||||
<template #day-content="{ day, dayProps, dayEvents }">
|
|
||||||
<div v-bind="dayProps" v-on="dayEvents">
|
|
||||||
{{ day.label }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Calendar>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -1,79 +1,38 @@
|
||||||
---
|
---
|
||||||
title: Date Picker
|
title: Date Picker
|
||||||
description: A date picker component with range and presets.
|
description: A date picker component with range and presets.
|
||||||
|
source: apps/www/src/lib/registry/default/example/DatePickerDemo.vue
|
||||||
|
primitive: https://www.radix-vue.com/components/calendar.html
|
||||||
---
|
---
|
||||||
|
|
||||||
<ComponentPreview name="DatePickerDemo" />
|
<ComponentPreview name="DatePickerDemo" />
|
||||||
|
|
||||||
|
<Callout class="text-base mt-12">
|
||||||
|
|
||||||
|
If you're looking for **previous** Date Picker implementation, checkout to <span class="font-bold underline">[VCalendar Datepicker](/docs/components/v-date-picker)</span> component
|
||||||
|
|
||||||
|
</Callout>
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
The Date Picker is built using a composition of the `<Popover />` and the `<Calendar />` components.
|
The Date Picker is built using a composition of the `<Popover />` and either the `<Calendar />` or `<RangeCalendar />` components.
|
||||||
|
|
||||||
See installation instructions for the [Popover](/docs/components/popover#installation) and the [Calendar](/docs/components/calendar#installation) components.
|
See installations instructions for the [Popover](/docs/components/popover), [Calendar](/docs/components/calendar), and [Range Calendar](/docs/components/range-calendar) components.
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { format } from 'date-fns'
|
|
||||||
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
|
||||||
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { Calendar } from '@/components/ui/calendar'
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from '@/components/ui/popover'
|
|
||||||
|
|
||||||
const date = ref<Date>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger as-child>
|
|
||||||
<Button
|
|
||||||
:variant="'outline'"
|
|
||||||
:class="cn(
|
|
||||||
'w-[280px] justify-start text-left font-normal',
|
|
||||||
!date && 'text-muted-foreground',
|
|
||||||
)"
|
|
||||||
>
|
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
|
||||||
<span>{{ date ? format(date, "PPP") : "Pick a date" }}</span>
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent class="w-auto p-0">
|
|
||||||
<Calendar v-model="date" />
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Date Picker
|
### Date Picker
|
||||||
|
|
||||||
<ComponentPreview name="DatePickerDemo" />
|
<ComponentPreview name="DatePickerDemo" />
|
||||||
|
|
||||||
### Date Range Picker
|
### Date Range Picker
|
||||||
|
|
||||||
<ComponentPreview name="DatePickerWithRange" />
|
<ComponentPreview name="DatePickerWithRange" />
|
||||||
|
|
||||||
### Date Time Picker
|
|
||||||
|
|
||||||
<ComponentPreview name="DateTimePickerDemo" />
|
|
||||||
|
|
||||||
### With Presets
|
### With Presets
|
||||||
|
|
||||||
<ComponentPreview name="DatePickerWithPresets" />
|
<ComponentPreview name="DatePickerWithPresets" />
|
||||||
|
|
||||||
### With Slot
|
|
||||||
|
|
||||||
<ComponentPreview name="RangePickerWithSlot" />
|
|
||||||
|
|
||||||
### Form
|
### Form
|
||||||
|
|
||||||
<ComponentPreview name="DatePickerForm" />
|
<ComponentPreview name="DatePickerForm" />
|
||||||
|
|
|
||||||
18
apps/www/src/content/docs/components/range-calendar.md
Normal file
18
apps/www/src/content/docs/components/range-calendar.md
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
title: Range Calendar
|
||||||
|
description: A calendar component that allows users to select a range of dates.
|
||||||
|
source: apps/www/src/lib/registry/default/ui/range-calendar
|
||||||
|
primitive: https://www.radix-vue.com/components/range-calendar.html
|
||||||
|
---
|
||||||
|
|
||||||
|
<ComponentPreview name="RangeCalendarDemo" />
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
The `<RangeCalendar />` component is built on top of the [RadixVue Range Calendar](https://www.bits-ui.com/docs/components/range-calendar) component, which uses the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) package to handle dates.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn-vue@latest add range-calendar
|
||||||
|
```
|
||||||
91
apps/www/src/content/docs/components/v-calendar.md
Normal file
91
apps/www/src/content/docs/components/v-calendar.md
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
---
|
||||||
|
title: VCalendar
|
||||||
|
description: A date field component that allows users to enter and edit date.
|
||||||
|
source: apps/www/src/lib/registry/default/ui/calendar
|
||||||
|
primitive: https://vcalendar.io/
|
||||||
|
---
|
||||||
|
|
||||||
|
<ComponentPreview name="VCalendarDemo" />
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
The `Calendar` component is built on top of [VCalendar](https://vcalendar.io/getting-started/installation.html).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
<TabPreview name="CLI">
|
||||||
|
<template #CLI>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn-vue@latest add v-calendar
|
||||||
|
```
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #Manual>
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
### Install the following dependency
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install v-calendar
|
||||||
|
```
|
||||||
|
|
||||||
|
### Copy and paste the following code into your project
|
||||||
|
|
||||||
|
<<< @/lib/registry/default/ui/v-calendar/Calendar.vue
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
</TabPreview>
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Calendar } from '@/components/ui/v-calendar'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Calendar />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
The API is essentially the same, i.e. props and slots. See the [VCalendar](https://vcalendar.io/getting-started/installation.html) documentation for more information.
|
||||||
|
|
||||||
|
### Slots
|
||||||
|
|
||||||
|
The slots available are [those currently supported](https://github.com/nathanreyes/v-calendar/blob/v3.1.2/src/components/Calendar/CalendarSlot.vue#L16-L28) by VCalendar, namely :
|
||||||
|
|
||||||
|
- `day-content`
|
||||||
|
- `day-popover`
|
||||||
|
- `dp-footer`
|
||||||
|
- `footer`
|
||||||
|
- `header-title-wrapper`
|
||||||
|
- `header-title`
|
||||||
|
- `header-prev-button`
|
||||||
|
- `header-next-button`
|
||||||
|
- `nav`
|
||||||
|
- `nav-prev-button`
|
||||||
|
- `nav-next-button`
|
||||||
|
- `page`
|
||||||
|
- `time-header`
|
||||||
|
|
||||||
|
Example using the `day-content` slot:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Calendar } from '@/components/ui/v-calendar'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Calendar>
|
||||||
|
<template #day-content="{ day, dayProps, dayEvents }">
|
||||||
|
<div v-bind="dayProps" v-on="dayEvents">
|
||||||
|
{{ day.label }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Calendar>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
79
apps/www/src/content/docs/components/v-date-picker.md
Normal file
79
apps/www/src/content/docs/components/v-date-picker.md
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
---
|
||||||
|
title: VCalendar Date Picker
|
||||||
|
description: A date picker component with range and presets.
|
||||||
|
---
|
||||||
|
|
||||||
|
<ComponentPreview name="VDatePickerDemo" />
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
The Date Picker is built using a composition of the `<Popover />` and the `<Calendar />` components.
|
||||||
|
|
||||||
|
See installation instructions for the [Popover](/docs/components/popover#installation) and the [Calendar](/docs/components/calendar#installation) components.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Calendar } from '@/components/ui/v-calendar'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover'
|
||||||
|
|
||||||
|
const date = ref<Date>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
:variant="'outline'"
|
||||||
|
:class="cn(
|
||||||
|
'w-[280px] justify-start text-left font-normal',
|
||||||
|
!date && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
<span>{{ date ? format(date, "PPP") : "Pick a date" }}</span>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0">
|
||||||
|
<Calendar v-model="date" />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Date Picker
|
||||||
|
|
||||||
|
<ComponentPreview name="VDatePickerDemo" />
|
||||||
|
|
||||||
|
### Date Range Picker
|
||||||
|
|
||||||
|
<ComponentPreview name="VDatePickerWithRange" />
|
||||||
|
|
||||||
|
### Date Time Picker
|
||||||
|
|
||||||
|
<ComponentPreview name="VDateTimePickerDemo" />
|
||||||
|
|
||||||
|
### With Presets
|
||||||
|
|
||||||
|
<ComponentPreview name="VDatePickerWithPresets" />
|
||||||
|
|
||||||
|
### With Slot
|
||||||
|
|
||||||
|
<ComponentPreview name="VRangePickerWithSlot" />
|
||||||
|
|
||||||
|
### Form
|
||||||
|
|
||||||
|
<ComponentPreview name="VDatePickerForm" />
|
||||||
|
|
@ -1,21 +1,28 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { addDays, format } from 'date-fns'
|
|
||||||
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
import type { DateRange } from 'radix-vue'
|
||||||
|
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date'
|
||||||
|
|
||||||
import { ref } from 'vue'
|
import { type Ref, ref } from 'vue'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
import { RangeCalendar } from '@/lib/registry/new-york/ui/range-calendar'
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/lib/registry/new-york/ui/popover'
|
} from '@/lib/registry/new-york/ui/popover'
|
||||||
|
|
||||||
const date = ref({
|
const df = new DateFormatter('en-US', {
|
||||||
start: new Date(2023, 0, 20),
|
dateStyle: 'medium',
|
||||||
end: addDays(new Date(2023, 0, 20), 20),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const calendarDate = new CalendarDate(2023, 0, 20)
|
||||||
|
|
||||||
|
const value = ref({
|
||||||
|
start: calendarDate,
|
||||||
|
end: calendarDate.add({ days: 20 }),
|
||||||
|
}) as Ref<DateRange>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -26,24 +33,34 @@ const date = ref({
|
||||||
id="date"
|
id="date"
|
||||||
:variant="'outline'"
|
:variant="'outline'"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'w-[260px] justify-start text-left font-normal',
|
'w-[300px] justify-start text-left font-normal',
|
||||||
!date && 'text-muted-foreground',
|
!value && 'text-muted-foreground',
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
|
||||||
<span>
|
<template v-if="value.start">
|
||||||
{{ date.start ? (
|
<template v-if="value.end">
|
||||||
date.end ? `${format(date.start, 'LLL dd, y')} - ${format(date.end, 'LLL dd, y')}`
|
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{ df.format(value.end.toDate(getLocalTimeZone())) }}
|
||||||
: format(date.start, 'LLL dd, y')
|
</template>
|
||||||
) : 'Pick a date' }}
|
|
||||||
</span>
|
<template v-else>
|
||||||
|
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
Pick a date
|
||||||
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="w-auto p-0" :align="'end'">
|
<PopoverContent class="w-auto p-0" align="end">
|
||||||
<Calendar
|
<RangeCalendar
|
||||||
v-model.range="date"
|
v-model="value"
|
||||||
:columns="2"
|
weekday-format="short"
|
||||||
|
:number-of-months="2"
|
||||||
|
initial-focus
|
||||||
|
:placeholder="value.start"
|
||||||
|
@update:start-value="(startDate) => value.start = startDate"
|
||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, ref } from 'vue'
|
import { h, ref } from 'vue'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
import { format } from 'date-fns'
|
import { toDate } from 'radix-vue'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import { Check, ChevronsUpDown } from 'lucide-vue-next'
|
import { Check, ChevronsUpDown } from 'lucide-vue-next'
|
||||||
|
import { CalendarDate, DateFormatter, getLocalTimeZone, today } from '@internationalized/date'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
import RadixIconsCalendar from '~icons/radix-icons/calendar'
|
import RadixIconsCalendar from '~icons/radix-icons/calendar'
|
||||||
|
|
||||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form'
|
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||||
import {
|
import {
|
||||||
|
|
@ -18,17 +19,19 @@ import {
|
||||||
CommandInput,
|
CommandInput,
|
||||||
CommandItem,
|
CommandItem,
|
||||||
CommandList,
|
CommandList,
|
||||||
} from '@/lib/registry/default/ui/command'
|
} from '@/lib/registry/new-york/ui/command'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/lib/registry/default/ui/popover'
|
} from '@/lib/registry/new-york/ui/popover'
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||||
import { toast } from '@/lib/registry/new-york/ui/toast'
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
|
const dateValue = ref()
|
||||||
|
const placeholder = ref()
|
||||||
|
|
||||||
const languages = [
|
const languages = [
|
||||||
{ label: 'English', value: 'en' },
|
{ label: 'English', value: 'en' },
|
||||||
|
|
@ -42,25 +45,25 @@ const languages = [
|
||||||
{ label: 'Chinese', value: 'zh' },
|
{ label: 'Chinese', value: 'zh' },
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
const df = new DateFormatter('en-US', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const accountFormSchema = toTypedSchema(z.object({
|
const accountFormSchema = toTypedSchema(z.object({
|
||||||
name: z
|
name: z
|
||||||
.string()
|
.string({
|
||||||
|
required_error: 'Required.',
|
||||||
|
})
|
||||||
.min(2, {
|
.min(2, {
|
||||||
message: 'Name must be at least 2 characters.',
|
message: 'Name must be at least 2 characters.',
|
||||||
})
|
})
|
||||||
.max(30, {
|
.max(30, {
|
||||||
message: 'Name must not be longer than 30 characters.',
|
message: 'Name must not be longer than 30 characters.',
|
||||||
}),
|
}),
|
||||||
dob: z.date({
|
dob: z.string().datetime().optional().refine(date => date !== undefined, 'Please select a valid date.'),
|
||||||
required_error: 'A date of birth is required.',
|
language: z.string().min(1, 'Please select a language.'),
|
||||||
}),
|
|
||||||
language: z.string().nonempty({
|
|
||||||
message: 'Please select a language.',
|
|
||||||
}),
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const filterFunction = (list: typeof languages, search: string) => list.filter(i => i.value.toLowerCase().includes(search.toLowerCase()))
|
|
||||||
|
|
||||||
// https://github.com/logaretm/vee-validate/issues/3521
|
// https://github.com/logaretm/vee-validate/issues/3521
|
||||||
// https://github.com/logaretm/vee-validate/discussions/3571
|
// https://github.com/logaretm/vee-validate/discussions/3571
|
||||||
async function onSubmit(values: any) {
|
async function onSubmit(values: any) {
|
||||||
|
|
@ -95,7 +98,7 @@ async function onSubmit(values: any) {
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormField v-slot="{ componentField, value }" name="dob">
|
<FormField v-slot="{ field, value }" name="dob">
|
||||||
<FormItem class="flex flex-col">
|
<FormItem class="flex flex-col">
|
||||||
<FormLabel>Date of birth</FormLabel>
|
<FormLabel>Date of birth</FormLabel>
|
||||||
<Popover>
|
<Popover>
|
||||||
|
|
@ -103,17 +106,38 @@ async function onSubmit(values: any) {
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Button
|
<Button
|
||||||
variant="outline" :class="cn(
|
variant="outline" :class="cn(
|
||||||
'w-[280px] pl-3 text-left font-normal',
|
'w-[240px] justify-start text-left font-normal',
|
||||||
!value && 'text-muted-foreground',
|
!value && 'text-muted-foreground',
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<span>{{ value ? format(value, "PPP") : "Pick a date" }}</span>
|
<RadixIconsCalendar class="mr-2 h-4 w-4 opacity-50" />
|
||||||
<RadixIconsCalendar class="ml-auto h-4 w-4 opacity-50" />
|
<span>{{ value ? df.format(toDate(dateValue, getLocalTimeZone())) : "Pick a date" }}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="p-0">
|
<PopoverContent class="p-0">
|
||||||
<Calendar v-bind="componentField" />
|
<Calendar
|
||||||
|
v-model:placeholder="placeholder"
|
||||||
|
v-model="dateValue"
|
||||||
|
calendar-label="Date of birth"
|
||||||
|
initial-focus
|
||||||
|
:min-value="new CalendarDate(1900, 1, 1)"
|
||||||
|
:max-value="today(getLocalTimeZone())"
|
||||||
|
@update:model-value="(v) => {
|
||||||
|
if (v) {
|
||||||
|
dateValue = v
|
||||||
|
setValues({
|
||||||
|
dob: toDate(v).toISOString(),
|
||||||
|
}, false)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dateValue = undefined
|
||||||
|
setValues({
|
||||||
|
dob: undefined,
|
||||||
|
}, false)
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
|
|
@ -121,6 +145,7 @@ async function onSubmit(values: any) {
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<input type="hidden" v-bind="field">
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormField v-slot="{ value }" name="language">
|
<FormField v-slot="{ value }" name="language">
|
||||||
|
|
@ -155,7 +180,7 @@ async function onSubmit(values: any) {
|
||||||
@select="() => {
|
@select="() => {
|
||||||
setValues({
|
setValues({
|
||||||
language: language.value,
|
language: language.value,
|
||||||
})
|
}, false)
|
||||||
open = false
|
open = false
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { type Ref, ref } from 'vue'
|
||||||
|
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date'
|
||||||
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
||||||
|
|
||||||
const date = ref(new Date())
|
const value = ref(today(getLocalTimeZone())) as Ref<DateValue>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Calendar v-model="date" class="rounded-md border" />
|
<Calendar v-model="value" :weekday-format="'short'" class="rounded-md border" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
105
apps/www/src/lib/registry/default/example/CalendarForm.vue
Normal file
105
apps/www/src/lib/registry/default/example/CalendarForm.vue
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, h, ref } from 'vue'
|
||||||
|
import { CalendarDate, DateFormatter, getLocalTimeZone, parseDate, today } from '@internationalized/date'
|
||||||
|
import { toDate } from 'radix-vue/date'
|
||||||
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/lib/registry/default/ui/form'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const df = new DateFormatter('en-US', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(z.object({
|
||||||
|
dob: z
|
||||||
|
.string()
|
||||||
|
.refine(v => v, { message: 'A date of birth is required.' }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const placeholder = ref()
|
||||||
|
|
||||||
|
const { handleSubmit, setValues, values } = useForm({
|
||||||
|
validationSchema: formSchema,
|
||||||
|
})
|
||||||
|
|
||||||
|
const value = computed({
|
||||||
|
get: () => values.dob ? parseDate(values.dob) : undefined,
|
||||||
|
set: val => val,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit((values) => {
|
||||||
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form class="space-y-8" @submit="onSubmit">
|
||||||
|
<FormField name="dob">
|
||||||
|
<FormItem class="flex flex-col">
|
||||||
|
<FormLabel>Date of birth</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant="outline" :class="cn(
|
||||||
|
'w-[240px] ps-3 text-start font-normal',
|
||||||
|
!value && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<span>{{ value ? df.format(toDate(value)) : "Pick a date" }}</span>
|
||||||
|
<CalendarIcon class="ms-auto h-4 w-4 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
<input hidden>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0">
|
||||||
|
<Calendar
|
||||||
|
v-model:placeholder="placeholder"
|
||||||
|
v-model="value"
|
||||||
|
calendar-label="Date of birth"
|
||||||
|
initial-focus
|
||||||
|
:min-value="new CalendarDate(1900, 1, 1)"
|
||||||
|
:max-value="today(getLocalTimeZone())"
|
||||||
|
@update:model-value="(v) => {
|
||||||
|
if (v) {
|
||||||
|
setValues({
|
||||||
|
dob: v.toString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setValues({
|
||||||
|
dob: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormDescription>
|
||||||
|
Your date of birth is used to calculate your age.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
<Button type="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
127
apps/www/src/lib/registry/default/example/CalendarWithSelect.vue
Normal file
127
apps/www/src/lib/registry/default/example/CalendarWithSelect.vue
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { type HTMLAttributes, type Ref, computed, toRef } from 'vue'
|
||||||
|
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useDateFormatter, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import { createDecade, createYear, toDate } from 'radix-vue/date'
|
||||||
|
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading } from '@/lib/registry/default/ui/calendar'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/lib/registry/default/ui/select'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>(), {
|
||||||
|
modelValue: undefined,
|
||||||
|
placeholder() {
|
||||||
|
return today(getLocalTimeZone())
|
||||||
|
},
|
||||||
|
weekdayFormat: 'short',
|
||||||
|
})
|
||||||
|
const emits = defineEmits<CalendarRootEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, placeholder: __, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const placeholder = useVModel(props, 'modelValue', emits, {
|
||||||
|
passive: true,
|
||||||
|
defaultValue: today(getLocalTimeZone()),
|
||||||
|
}) as Ref<DateValue>
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
|
||||||
|
const formatter = useDateFormatter('en')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarRoot
|
||||||
|
v-slot="{ date, grid, weekDays }"
|
||||||
|
v-model:placeholder="placeholder"
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="cn('rounded-md border p-3', props.class)"
|
||||||
|
>
|
||||||
|
<CalendarHeader>
|
||||||
|
<CalendarHeading class="flex w-full items-center justify-between gap-2">
|
||||||
|
<Select
|
||||||
|
:default-value="placeholder.month.toString()"
|
||||||
|
@update:model-value="(v) => {
|
||||||
|
if (!v || !placeholder) return;
|
||||||
|
if (Number(v) === placeholder?.month) return;
|
||||||
|
placeholder = placeholder.set({
|
||||||
|
month: Number(v),
|
||||||
|
})
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<SelectTrigger aria-label="Select month" class="w-[60%]">
|
||||||
|
<SelectValue placeholder="Select month" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent class="max-h-[200px]">
|
||||||
|
<SelectItem
|
||||||
|
v-for="month in createYear({ dateObj: date })"
|
||||||
|
:key="month.toString()" :value="month.month.toString()"
|
||||||
|
>
|
||||||
|
{{ formatter.custom(toDate(month), { month: 'long' }) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
:default-value="props.placeholder.year.toString()"
|
||||||
|
@update:model-value="(v) => {
|
||||||
|
if (!v || !placeholder) return;
|
||||||
|
if (Number(v) === placeholder?.year) return;
|
||||||
|
placeholder = placeholder.set({
|
||||||
|
year: Number(v),
|
||||||
|
})
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<SelectTrigger aria-label="Select year" class="w-[40%]">
|
||||||
|
<SelectValue placeholder="Select year" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent class="max-h-[200px]">
|
||||||
|
<SelectItem
|
||||||
|
v-for="yearValue in createDecade({ dateObj: date, startIndex: -10, endIndex: 10 })"
|
||||||
|
:key="yearValue.toString()" :value="yearValue.year.toString()"
|
||||||
|
>
|
||||||
|
{{ yearValue.year }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</CalendarHeading>
|
||||||
|
</CalendarHeader>
|
||||||
|
|
||||||
|
<div class="flex flex-col space-y-4 pt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
|
||||||
|
<CalendarGrid v-for="month in grid" :key="month.value.toString()">
|
||||||
|
<CalendarGridHead>
|
||||||
|
<CalendarGridRow>
|
||||||
|
<CalendarHeadCell
|
||||||
|
v-for="day in weekDays" :key="day"
|
||||||
|
>
|
||||||
|
{{ day }}
|
||||||
|
</CalendarHeadCell>
|
||||||
|
</CalendarGridRow>
|
||||||
|
</CalendarGridHead>
|
||||||
|
<CalendarGridBody class="grid">
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
|
</CalendarCell>
|
||||||
|
</CalendarGridRow>
|
||||||
|
</CalendarGridBody>
|
||||||
|
</CalendarGrid>
|
||||||
|
</div>
|
||||||
|
</CalendarRoot>
|
||||||
|
</template>
|
||||||
|
|
@ -1,36 +1,40 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { format } from 'date-fns'
|
|
||||||
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
|
||||||
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
|
||||||
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
DateFormatter,
|
||||||
PopoverContent,
|
type DateValue,
|
||||||
PopoverTrigger,
|
getLocalTimeZone,
|
||||||
} from '@/lib/registry/default/ui/popover'
|
} from '@internationalized/date'
|
||||||
|
|
||||||
const date = ref<Date>()
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const df = new DateFormatter('en-US', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
|
const value = ref<DateValue>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger as-child>
|
<PopoverTrigger as-child>
|
||||||
<Button
|
<Button
|
||||||
:variant="'outline'"
|
variant="outline"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'w-[280px] justify-start text-left font-normal',
|
'w-[280px] justify-start text-left font-normal',
|
||||||
!date && 'text-muted-foreground',
|
!value && 'text-muted-foreground',
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
<span>{{ date ? format(date, "PPP") : "Pick a date" }}</span>
|
{{ value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date" }}
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="w-auto p-0">
|
<PopoverContent class="w-auto p-0">
|
||||||
<Calendar v-model="date" />
|
<Calendar v-model="value" initial-focus />
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { computed, h, ref } from 'vue'
|
||||||
import { format } from 'date-fns'
|
import { CalendarDate, DateFormatter, getLocalTimeZone, parseDate, today } from '@internationalized/date'
|
||||||
|
import { toDate } from 'radix-vue/date'
|
||||||
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
|
||||||
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
FormDescription,
|
||||||
|
|
@ -17,22 +16,32 @@ import {
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/default/ui/form'
|
} from '@/lib/registry/default/ui/form'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from '@/lib/registry/default/ui/popover'
|
|
||||||
import { toast } from '@/lib/registry/default/ui/toast'
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const df = new DateFormatter('en-US', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
dob: z.date({
|
dob: z
|
||||||
required_error: 'A date of birth is required.',
|
.string()
|
||||||
}),
|
.refine(v => v, { message: 'A date of birth is required.' }),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const { handleSubmit } = useForm({
|
const placeholder = ref()
|
||||||
|
|
||||||
|
const { handleSubmit, setValues, values } = useForm({
|
||||||
validationSchema: formSchema,
|
validationSchema: formSchema,
|
||||||
|
initialValues: {
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const value = computed({
|
||||||
|
get: () => values.dob ? parseDate(values.dob) : undefined,
|
||||||
|
set: val => val,
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
|
|
@ -45,7 +54,7 @@ const onSubmit = handleSubmit((values) => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form class="space-y-8" @submit="onSubmit">
|
<form class="space-y-8" @submit="onSubmit">
|
||||||
<FormField v-slot="{ componentField, value }" name="dob">
|
<FormField name="dob">
|
||||||
<FormItem class="flex flex-col">
|
<FormItem class="flex flex-col">
|
||||||
<FormLabel>Date of birth</FormLabel>
|
<FormLabel>Date of birth</FormLabel>
|
||||||
<Popover>
|
<Popover>
|
||||||
|
|
@ -57,13 +66,34 @@ const onSubmit = handleSubmit((values) => {
|
||||||
!value && 'text-muted-foreground',
|
!value && 'text-muted-foreground',
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<span>{{ value ? format(value, "PPP") : "Pick a date" }}</span>
|
<span>{{ value ? df.format(toDate(value)) : "Pick a date" }}</span>
|
||||||
<CalendarIcon class="ms-auto h-4 w-4 opacity-50" />
|
<CalendarIcon class="ms-auto h-4 w-4 opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
|
<input hidden>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="p-0">
|
<PopoverContent class="w-auto p-0">
|
||||||
<Calendar v-bind="componentField" />
|
<Calendar
|
||||||
|
v-model:placeholder="placeholder"
|
||||||
|
v-model="value"
|
||||||
|
calendar-label="Date of birth"
|
||||||
|
initial-focus
|
||||||
|
:min-value="new CalendarDate(1900, 1, 1)"
|
||||||
|
:max-value="today(getLocalTimeZone())"
|
||||||
|
@update:model-value="(v) => {
|
||||||
|
if (v) {
|
||||||
|
setValues({
|
||||||
|
dob: v.toString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setValues({
|
||||||
|
dob: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,31 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { addDays, format } from 'date-fns'
|
|
||||||
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
|
||||||
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
|
||||||
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
DateFormatter,
|
||||||
PopoverContent,
|
type DateValue,
|
||||||
PopoverTrigger,
|
getLocalTimeZone,
|
||||||
} from '@/lib/registry/default/ui/popover'
|
today,
|
||||||
import {
|
} from '@internationalized/date'
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/lib/registry/default/ui/select'
|
|
||||||
|
|
||||||
const date = ref<Date>()
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/lib/registry/default/ui/select'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const df = new DateFormatter('en-US', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ value: 0, label: 'Today' },
|
||||||
|
{ value: 1, label: 'Tomorrow' },
|
||||||
|
{ value: 3, label: 'In 3 days' },
|
||||||
|
{ value: 7, label: 'In a week' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const value = ref<DateValue>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -29,45 +35,30 @@ const date = ref<Date>()
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'w-[280px] justify-start text-left font-normal',
|
'w-[280px] justify-start text-left font-normal',
|
||||||
!date && 'text-muted-foreground',
|
!value && 'text-muted-foreground',
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
<template v-if="date">
|
{{ value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date" }}
|
||||||
{{ format(date, "PPP") }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span>Pick a date</span>
|
|
||||||
</template>
|
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="flex w-auto flex-col space-y-2 p-2">
|
<PopoverContent class="flex w-auto flex-col gap-y-2 p-2">
|
||||||
<Select
|
<Select
|
||||||
@update:model-value="(value) => {
|
@update:model-value="(v) => {
|
||||||
date = addDays(new Date(), parseInt(value))
|
if (!v) return;
|
||||||
|
value = today(getLocalTimeZone()).add({ days: Number(v) });
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select" />
|
<SelectValue placeholder="Select" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent position="popper">
|
<SelectContent>
|
||||||
<SelectItem value="0">
|
<SelectItem v-for="item in items" :key="item.value" :value="item.value.toString()">
|
||||||
Today
|
{{ item.label }}
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="1">
|
|
||||||
Tomorrow
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="3">
|
|
||||||
In 3 days
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="7">
|
|
||||||
In a week
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<div class="rounded-md border">
|
<Calendar v-model="value" />
|
||||||
<Calendar v-model="date" mode="single" />
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,55 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { addDays, format } from 'date-fns'
|
import { type Ref, ref } from 'vue'
|
||||||
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
|
||||||
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
|
||||||
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
CalendarDate,
|
||||||
PopoverContent,
|
DateFormatter,
|
||||||
PopoverTrigger,
|
getLocalTimeZone,
|
||||||
} from '@/lib/registry/default/ui/popover'
|
} from '@internationalized/date'
|
||||||
|
|
||||||
const date = ref({
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
start: new Date(2022, 0, 20),
|
import type { DateRange } from 'radix-vue'
|
||||||
end: addDays(new Date(2022, 0, 20), 20),
|
import { RangeCalendar } from '@/lib/registry/default/ui/range-calendar'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const df = new DateFormatter('en-US', {
|
||||||
|
dateStyle: 'medium',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const value = ref({
|
||||||
|
start: new CalendarDate(2022, 1, 20),
|
||||||
|
end: new CalendarDate(2022, 1, 20).add({ days: 20 }),
|
||||||
|
}) as Ref<DateRange>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="cn('grid gap-2', $attrs.class ?? '')">
|
<Popover>
|
||||||
<Popover>
|
<PopoverTrigger as-child>
|
||||||
<PopoverTrigger as-child>
|
<Button
|
||||||
<Button
|
variant="outline"
|
||||||
id="date"
|
:class="cn(
|
||||||
:variant="'outline'"
|
'w-[280px] justify-start text-left font-normal',
|
||||||
:class="cn(
|
!value && 'text-muted-foreground',
|
||||||
'w-[300px] justify-start text-left font-normal',
|
)"
|
||||||
!date && 'text-muted-foreground',
|
>
|
||||||
)"
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
>
|
<template v-if="value.start">
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
<template v-if="value.end">
|
||||||
|
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{ df.format(value.end.toDate(getLocalTimeZone())) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
<span>
|
<template v-else>
|
||||||
{{ date.start ? (
|
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
|
||||||
date.end ? `${format(date.start, 'LLL dd, y')} - ${format(date.end, 'LLL dd, y')}`
|
</template>
|
||||||
: format(date.start, 'LLL dd, y')
|
</template>
|
||||||
) : 'Pick a date' }}
|
<template v-else>
|
||||||
</span>
|
Pick a date
|
||||||
</Button>
|
</template>
|
||||||
</PopoverTrigger>
|
</Button>
|
||||||
<PopoverContent class="w-auto p-0" align="start">
|
</PopoverTrigger>
|
||||||
<Calendar
|
<PopoverContent class="w-auto p-0">
|
||||||
v-model.range="date"
|
<RangeCalendar v-model="value" initial-focus :number-of-months="2" :placeholder="value?.start" @update:start-value="(startDate) => value.start = startDate" />
|
||||||
:columns="2"
|
</PopoverContent>
|
||||||
/>
|
</Popover>
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { type Ref, ref } from 'vue'
|
||||||
|
import type { DateRange } from 'radix-vue'
|
||||||
|
import { getLocalTimeZone, today } from '@internationalized/date'
|
||||||
|
import { RangeCalendar } from '@/lib/registry/default/ui/range-calendar'
|
||||||
|
|
||||||
|
const start = today(getLocalTimeZone())
|
||||||
|
const end = start.add({ days: 7 })
|
||||||
|
|
||||||
|
const value = ref({
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
}) as Ref<DateRange>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendar v-model="value" class="rounded-md border" />
|
||||||
|
</template>
|
||||||
10
apps/www/src/lib/registry/default/example/VCalendarDemo.vue
Normal file
10
apps/www/src/lib/registry/default/example/VCalendarDemo.vue
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { Calendar } from '@/lib/registry/default/ui/v-calendar'
|
||||||
|
|
||||||
|
const date = ref(new Date())
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Calendar v-model="date" class="rounded-md border" />
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { Calendar } from '@/lib/registry/default/ui/v-calendar'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/lib/registry/default/ui/popover'
|
||||||
|
|
||||||
|
const date = ref<Date>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
:variant="'outline'"
|
||||||
|
:class="cn(
|
||||||
|
'w-[280px] justify-start text-left font-normal',
|
||||||
|
!date && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
<span>{{ date ? format(date, "PPP") : "Pick a date" }}</span>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0">
|
||||||
|
<Calendar v-model="date" />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { Calendar } from '@/lib/registry/default/ui/v-calendar'
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/lib/registry/default/ui/form'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/lib/registry/default/ui/popover'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(z.object({
|
||||||
|
dob: z.date({
|
||||||
|
required_error: 'A date of birth is required.',
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { handleSubmit } = useForm({
|
||||||
|
validationSchema: formSchema,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit((values) => {
|
||||||
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form class="space-y-8" @submit="onSubmit">
|
||||||
|
<FormField v-slot="{ componentField, value }" name="dob">
|
||||||
|
<FormItem class="flex flex-col">
|
||||||
|
<FormLabel>Date of birth</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant="outline" :class="cn(
|
||||||
|
'w-[240px] ps-3 text-start font-normal',
|
||||||
|
!value && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<span>{{ value ? format(value, "PPP") : "Pick a date" }}</span>
|
||||||
|
<CalendarIcon class="ms-auto h-4 w-4 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="p-0">
|
||||||
|
<Calendar v-bind="componentField" />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormDescription>
|
||||||
|
Your date of birth is used to calculate your age.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
<Button type="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { addDays, format } from 'date-fns'
|
||||||
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { Calendar } from '@/lib/registry/default/ui/v-calendar'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/lib/registry/default/ui/popover'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/lib/registry/default/ui/select'
|
||||||
|
|
||||||
|
const date = ref<Date>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
:class="cn(
|
||||||
|
'w-[280px] justify-start text-left font-normal',
|
||||||
|
!date && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
<template v-if="date">
|
||||||
|
{{ format(date, "PPP") }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span>Pick a date</span>
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="flex w-auto flex-col space-y-2 p-2">
|
||||||
|
<Select
|
||||||
|
@update:model-value="(value) => {
|
||||||
|
date = addDays(new Date(), parseInt(value))
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent position="popper">
|
||||||
|
<SelectItem value="0">
|
||||||
|
Today
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="1">
|
||||||
|
Tomorrow
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="3">
|
||||||
|
In 3 days
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="7">
|
||||||
|
In a week
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<div class="rounded-md border">
|
||||||
|
<Calendar v-model="date" mode="single" />
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { addDays, format } from 'date-fns'
|
||||||
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { Calendar } from '@/lib/registry/default/ui/v-calendar'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/lib/registry/default/ui/popover'
|
||||||
|
|
||||||
|
const date = ref({
|
||||||
|
start: new Date(2022, 0, 20),
|
||||||
|
end: addDays(new Date(2022, 0, 20), 20),
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('grid gap-2', $attrs.class ?? '')">
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
id="date"
|
||||||
|
:variant="'outline'"
|
||||||
|
:class="cn(
|
||||||
|
'w-[300px] justify-start text-left font-normal',
|
||||||
|
!date && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{ date.start ? (
|
||||||
|
date.end ? `${format(date.start, 'LLL dd, y')} - ${format(date.end, 'LLL dd, y')}`
|
||||||
|
: format(date.start, 'LLL dd, y')
|
||||||
|
) : 'Pick a date' }}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0" align="start">
|
||||||
|
<Calendar
|
||||||
|
v-model.range="date"
|
||||||
|
:columns="2"
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -5,7 +5,7 @@ import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
import { Calendar } from '@/lib/registry/default/ui/v-calendar'
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
|
|
@ -5,7 +5,7 @@ import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
import { Calendar } from '@/lib/registry/default/ui/v-calendar'
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
|
|
@ -1,331 +1,60 @@
|
||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import { useVModel } from '@vueuse/core'
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||||
import type { Calendar } from 'v-calendar'
|
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNextButton, CalendarPrevButton } from '.'
|
||||||
import { DatePicker } from 'v-calendar'
|
|
||||||
import { computed, nextTick, onMounted, ref, useSlots } from 'vue'
|
|
||||||
import { isVCalendarSlot } from '.'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
|
||||||
|
|
||||||
/* Extracted from v-calendar */
|
const props = defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>()
|
||||||
type DatePickerModel = DatePickerDate | DatePickerRangeObject
|
|
||||||
type DateSource = Date | string | number
|
|
||||||
type DatePickerDate = DateSource | Partial<SimpleDateParts> | null
|
|
||||||
interface DatePickerRangeObject {
|
|
||||||
start: Exclude<DatePickerDate, null>
|
|
||||||
end: Exclude<DatePickerDate, null>
|
|
||||||
}
|
|
||||||
interface SimpleDateParts {
|
|
||||||
year: number
|
|
||||||
month: number
|
|
||||||
day: number
|
|
||||||
hours: number
|
|
||||||
minutes: number
|
|
||||||
seconds: number
|
|
||||||
milliseconds: number
|
|
||||||
}
|
|
||||||
|
|
||||||
defineOptions({
|
const emits = defineEmits<CalendarRootEmits>()
|
||||||
inheritAttrs: false,
|
|
||||||
})
|
|
||||||
const props = withDefaults(defineProps< {
|
|
||||||
modelValue?: string | number | Date | DatePickerModel
|
|
||||||
modelModifiers?: object
|
|
||||||
columns?: number
|
|
||||||
type?: 'single' | 'range'
|
|
||||||
}>(), {
|
|
||||||
type: 'single',
|
|
||||||
columns: 1,
|
|
||||||
})
|
|
||||||
const emits = defineEmits<{
|
|
||||||
(e: 'update:modelValue', payload: typeof props.modelValue): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
const delegatedProps = computed(() => {
|
||||||
passive: true,
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
})
|
})
|
||||||
|
|
||||||
const datePicker = ref<InstanceType<typeof DatePicker>>()
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
// @ts-expect-error in this current version of v-calendar has the calendaRef instance, which is required to handle arrow nav.
|
|
||||||
const calendarRef = computed<InstanceType<typeof Calendar>>(() => datePicker.value.calendarRef)
|
|
||||||
|
|
||||||
function handleNav(direction: 'prev' | 'next') {
|
|
||||||
if (!calendarRef.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (direction === 'prev')
|
|
||||||
calendarRef.value.movePrev()
|
|
||||||
else calendarRef.value.moveNext()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await nextTick()
|
|
||||||
if (modelValue.value instanceof Date && calendarRef.value)
|
|
||||||
calendarRef.value.focusDate(modelValue.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const $slots = useSlots()
|
|
||||||
const vCalendarSlots = computed(() => {
|
|
||||||
return Object.keys($slots)
|
|
||||||
.filter(name => isVCalendarSlot(name))
|
|
||||||
.reduce((obj: Record<string, any>, key: string) => {
|
|
||||||
obj[key] = $slots[key]
|
|
||||||
return obj
|
|
||||||
}, {})
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative">
|
<CalendarRoot
|
||||||
<div v-if="$attrs.mode !== 'time'" class="absolute flex justify-between w-full px-4 top-3 z-[1]">
|
v-slot="{ grid, weekDays }"
|
||||||
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" type="button" @click="handleNav('prev')">
|
:class="cn('p-3', props.class)"
|
||||||
<ChevronLeft class="w-4 h-4" />
|
v-bind="forwarded"
|
||||||
</button>
|
>
|
||||||
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" type="button" @click="handleNav('next')">
|
<CalendarHeader>
|
||||||
<ChevronRight class="w-4 h-4" />
|
<CalendarPrevButton />
|
||||||
</button>
|
<CalendarHeading />
|
||||||
|
<CalendarNextButton />
|
||||||
|
</CalendarHeader>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
|
||||||
|
<CalendarGrid v-for="month in grid" :key="month.value.toString()">
|
||||||
|
<CalendarGridHead>
|
||||||
|
<CalendarGridRow>
|
||||||
|
<CalendarHeadCell
|
||||||
|
v-for="day in weekDays" :key="day"
|
||||||
|
>
|
||||||
|
{{ 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"
|
||||||
|
/>
|
||||||
|
</CalendarCell>
|
||||||
|
</CalendarGridRow>
|
||||||
|
</CalendarGridBody>
|
||||||
|
</CalendarGrid>
|
||||||
</div>
|
</div>
|
||||||
|
</CalendarRoot>
|
||||||
<DatePicker
|
|
||||||
ref="datePicker"
|
|
||||||
v-bind="$attrs"
|
|
||||||
v-model="modelValue"
|
|
||||||
:model-modifiers="modelModifiers"
|
|
||||||
class="calendar"
|
|
||||||
trim-weeks
|
|
||||||
:transition="'none'"
|
|
||||||
:columns="columns"
|
|
||||||
>
|
|
||||||
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
|
|
||||||
<slot :name="slot" v-bind="scope" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #nav-prev-button>
|
|
||||||
<ChevronLeft />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #nav-next-button>
|
|
||||||
<ChevronRight />
|
|
||||||
</template>
|
|
||||||
</DatePicker>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="css">
|
|
||||||
.calendar {
|
|
||||||
@apply p-3 text-center;
|
|
||||||
}
|
|
||||||
.calendar .vc-pane-layout {
|
|
||||||
@apply grid gap-4;
|
|
||||||
}
|
|
||||||
.calendar .vc-title {
|
|
||||||
@apply text-sm font-medium relative z-20;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-popover-content {
|
|
||||||
@apply mt-3 rounded-md max-w-xs border bg-background;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-nav-header {
|
|
||||||
@apply flex justify-between items-center p-2;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-nav-items {
|
|
||||||
@apply grid grid-cols-4 gap-2 p-2;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item {
|
|
||||||
@apply rounded-md px-2 py-1;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item:hover {
|
|
||||||
@apply text-muted-foreground bg-muted;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item.is-active {
|
|
||||||
@apply bg-primary text-primary-foreground;
|
|
||||||
}
|
|
||||||
.calendar .vc-pane-header-wrapper {
|
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
.calendar .vc-weeks {
|
|
||||||
@apply mt-4;
|
|
||||||
}
|
|
||||||
.calendar .vc-weekdays {
|
|
||||||
@apply justify-items-center;
|
|
||||||
}
|
|
||||||
.calendar .vc-weekday {
|
|
||||||
@apply text-muted-foreground rounded-md font-normal text-[0.8rem];
|
|
||||||
}
|
|
||||||
.calendar .vc-weeks {
|
|
||||||
@apply w-full space-y-2 flex flex-col [&>_div]:grid [&>_div]:grid-cols-7;
|
|
||||||
}
|
|
||||||
.calendar .vc-day:has(.vc-highlights) {
|
|
||||||
@apply first:rounded-l-md last:rounded-r-md;
|
|
||||||
}
|
|
||||||
.calendar .vc-day.is-today:not(:has(.vc-day-layer)) .vc-day-content {
|
|
||||||
@apply bg-secondary text-primary rounded-md;
|
|
||||||
}
|
|
||||||
.calendar .vc-day:has(.vc-highlight-base-start) {
|
|
||||||
@apply rounded-l-md;
|
|
||||||
}
|
|
||||||
.calendar .vc-day:has(.vc-highlight-base-end) {
|
|
||||||
@apply rounded-r-md;
|
|
||||||
}
|
|
||||||
.calendar .vc-day:has(.vc-highlight-bg-outline):not(:has(.vc-highlight-base-start)):not(:has(.vc-highlight-base-end)) {
|
|
||||||
@apply rounded-md;
|
|
||||||
}
|
|
||||||
.calendar .vc-day-content {
|
|
||||||
@apply text-center text-sm p-0 relative focus-within:relative focus-within:z-20 inline-flex items-center justify-center ring-offset-background hover:transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground h-9 w-9 font-normal aria-selected:opacity-100 select-none;
|
|
||||||
}
|
|
||||||
.calendar .vc-day-content:not(.vc-highlight-content-light) {
|
|
||||||
@apply rounded-md;
|
|
||||||
}
|
|
||||||
.calendar .is-not-in-month:not(:has(.vc-highlight-content-solid)):not(:has(.vc-highlight-content-light)):not(:has(.vc-highlight-content-outline)),
|
|
||||||
.calendar .vc-disabled {
|
|
||||||
@apply text-muted-foreground opacity-50;
|
|
||||||
}
|
|
||||||
.calendar .vc-highlight-content-solid, .calendar .vc-highlight-content-outline {
|
|
||||||
@apply bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground;
|
|
||||||
}
|
|
||||||
.calendar .vc-highlight-content-light {
|
|
||||||
@apply bg-accent text-accent-foreground;
|
|
||||||
}
|
|
||||||
.calendar .vc-pane-container.in-transition {
|
|
||||||
@apply overflow-hidden;
|
|
||||||
}
|
|
||||||
.calendar .vc-pane-container {
|
|
||||||
@apply w-full relative;
|
|
||||||
}
|
|
||||||
:root {
|
|
||||||
--vc-slide-translate: 22px;
|
|
||||||
--vc-slide-duration: 0.15s;
|
|
||||||
--vc-slide-timing: ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar .vc-fade-enter-active,
|
|
||||||
.calendar .vc-fade-leave-active,
|
|
||||||
.calendar .vc-slide-left-enter-active,
|
|
||||||
.calendar .vc-slide-left-leave-active,
|
|
||||||
.calendar .vc-slide-right-enter-active,
|
|
||||||
.calendar .vc-slide-right-leave-active,
|
|
||||||
.calendar .vc-slide-up-enter-active,
|
|
||||||
.calendar .vc-slide-up-leave-active,
|
|
||||||
.calendar .vc-slide-down-enter-active,
|
|
||||||
.calendar .vc-slide-down-leave-active,
|
|
||||||
.calendar .vc-slide-fade-enter-active,
|
|
||||||
.calendar .vc-slide-fade-leave-active {
|
|
||||||
transition:
|
|
||||||
opacity var(--vc-slide-duration) var(--vc-slide-timing),
|
|
||||||
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
|
|
||||||
transition:
|
|
||||||
transform var(--vc-slide-duration) var(--vc-slide-timing),
|
|
||||||
opacity var(--vc-slide-duration) var(--vc-slide-timing);
|
|
||||||
transition:
|
|
||||||
transform var(--vc-slide-duration) var(--vc-slide-timing),
|
|
||||||
opacity var(--vc-slide-duration) var(--vc-slide-timing),
|
|
||||||
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
|
|
||||||
-webkit-backface-visibility: hidden;
|
|
||||||
backface-visibility: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar .vc-none-leave-active,
|
|
||||||
.calendar .vc-fade-leave-active,
|
|
||||||
.calendar .vc-slide-left-leave-active,
|
|
||||||
.calendar .vc-slide-right-leave-active,
|
|
||||||
.calendar .vc-slide-up-leave-active,
|
|
||||||
.calendar .vc-slide-down-leave-active {
|
|
||||||
position: absolute !important;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar .vc-none-enter-from,
|
|
||||||
.calendar .vc-none-leave-to,
|
|
||||||
.calendar .vc-fade-enter-from,
|
|
||||||
.calendar .vc-fade-leave-to,
|
|
||||||
.calendar .vc-slide-left-enter-from,
|
|
||||||
.calendar .vc-slide-left-leave-to,
|
|
||||||
.calendar .vc-slide-right-enter-from,
|
|
||||||
.calendar .vc-slide-right-leave-to,
|
|
||||||
.calendar .vc-slide-up-enter-from,
|
|
||||||
.calendar .vc-slide-up-leave-to,
|
|
||||||
.calendar .vc-slide-down-enter-from,
|
|
||||||
.calendar .vc-slide-down-leave-to,
|
|
||||||
.calendar .vc-slide-fade-enter-from,
|
|
||||||
.calendar .vc-slide-fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar .vc-slide-left-enter-from,
|
|
||||||
.calendar .vc-slide-right-leave-to,
|
|
||||||
.calendar .vc-slide-fade-enter-from.direction-left,
|
|
||||||
.calendar .vc-slide-fade-leave-to.direction-left {
|
|
||||||
-webkit-transform: translateX(var(--vc-slide-translate));
|
|
||||||
transform: translateX(var(--vc-slide-translate));
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar .vc-slide-right-enter-from,
|
|
||||||
.calendar .vc-slide-left-leave-to,
|
|
||||||
.calendar .vc-slide-fade-enter-from.direction-right,
|
|
||||||
.calendar .vc-slide-fade-leave-to.direction-right {
|
|
||||||
-webkit-transform: translateX(calc(-1 * var(--vc-slide-translate)));
|
|
||||||
transform: translateX(calc(-1 * var(--vc-slide-translate)));
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar .vc-slide-up-enter-from,
|
|
||||||
.calendar .vc-slide-down-leave-to,
|
|
||||||
.calendar .vc-slide-fade-enter-from.direction-top,
|
|
||||||
.calendar .vc-slide-fade-leave-to.direction-top {
|
|
||||||
-webkit-transform: translateY(var(--vc-slide-translate));
|
|
||||||
transform: translateY(var(--vc-slide-translate));
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar .vc-slide-down-enter-from,
|
|
||||||
.calendar .vc-slide-up-leave-to,
|
|
||||||
.calendar .vc-slide-fade-enter-from.direction-bottom,
|
|
||||||
.calendar .vc-slide-fade-leave-to.direction-bottom {
|
|
||||||
-webkit-transform: translateY(calc(-1 * var(--vc-slide-translate)));
|
|
||||||
transform: translateY(calc(-1 * var(--vc-slide-translate)));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Timepicker styles
|
|
||||||
*/
|
|
||||||
.vc-time-picker {
|
|
||||||
@apply flex flex-col items-center p-2;
|
|
||||||
}
|
|
||||||
.vc-time-picker.vc-invalid {
|
|
||||||
@apply pointer-events-none opacity-50;
|
|
||||||
}
|
|
||||||
.vc-time-picker.vc-attached {
|
|
||||||
@apply border-t border-solid border-secondary mt-2;
|
|
||||||
}
|
|
||||||
.vc-time-picker > * + * {
|
|
||||||
@apply mt-1;
|
|
||||||
}
|
|
||||||
.vc-time-header {
|
|
||||||
@apply flex items-center text-sm font-semibold uppercase mt-1 px-1 leading-6;
|
|
||||||
}
|
|
||||||
.vc-time-select-group {
|
|
||||||
@apply inline-flex items-center px-1 rounded-md bg-primary-foreground border border-solid border-secondary;
|
|
||||||
}
|
|
||||||
.vc-time-select-group .vc-base-icon {
|
|
||||||
@apply mr-1 text-primary stroke-primary;
|
|
||||||
}
|
|
||||||
.vc-time-select-group select {
|
|
||||||
@apply bg-primary-foreground p-1 appearance-none outline-none text-center;
|
|
||||||
}
|
|
||||||
.vc-time-weekday {
|
|
||||||
@apply text-muted-foreground tracking-wide;
|
|
||||||
}
|
|
||||||
.vc-time-month {
|
|
||||||
@apply text-primary ml-2;
|
|
||||||
}
|
|
||||||
.vc-time-day {
|
|
||||||
@apply text-primary ml-1;
|
|
||||||
}
|
|
||||||
.vc-time-year {
|
|
||||||
@apply text-muted-foreground ml-2;
|
|
||||||
}
|
|
||||||
.vc-time-colon {
|
|
||||||
@apply mb-0.5;
|
|
||||||
}
|
|
||||||
.vc-time-decimal {
|
|
||||||
@apply ml-0.5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarCell, type CalendarCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarCell
|
||||||
|
:class="cn('relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarCell>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarCellTrigger, type CalendarCellTriggerProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarCellTrigger
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'ghost' }),
|
||||||
|
'h-9 w-9 p-0 font-normal',
|
||||||
|
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground',
|
||||||
|
// Selected
|
||||||
|
'data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:opacity-100 data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-foreground',
|
||||||
|
// Disabled
|
||||||
|
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
|
||||||
|
// Unavailable
|
||||||
|
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
|
||||||
|
// Outside months
|
||||||
|
'data-[outside-month]:pointer-events-none data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground [&[data-outside-month][data-selected]]:opacity-30',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarCellTrigger>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarGrid, type CalendarGridProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGrid
|
||||||
|
:class="cn('w-full border-collapse space-y-1', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarGrid>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { CalendarGridBody, type CalendarGridBodyProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridBodyProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGridBody v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</CalendarGridBody>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { CalendarGridHead, type CalendarGridHeadProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridHeadProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGridHead v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</CalendarGridHead>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarGridRow, type CalendarGridRowProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridRowProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGridRow :class="cn('flex', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</CalendarGridRow>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarHeadCell, type CalendarHeadCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarHeadCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarHeadCell :class="cn('w-9 rounded-md text-[0.8rem] font-normal text-muted-foreground', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</CalendarHeadCell>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarHeader, type CalendarHeaderProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarHeaderProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarHeader :class="cn('relative flex w-full items-center justify-between pt-1', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</CalendarHeader>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarHeading, type CalendarHeadingProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarHeadingProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarHeading
|
||||||
|
v-slot="{ headingValue }"
|
||||||
|
:class="cn('text-sm font-medium', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot :heading-value>
|
||||||
|
{{ headingValue }}
|
||||||
|
</slot>
|
||||||
|
</CalendarHeading>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarNext, type CalendarNextProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { ChevronRight } from 'lucide-vue-next'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarNextProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarNext
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'outline' }),
|
||||||
|
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronRight class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</CalendarNext>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarPrev, type CalendarPrevProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { ChevronLeft } from 'lucide-vue-next'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarPrevProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarPrev
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'outline' }),
|
||||||
|
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronLeft class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</CalendarPrev>
|
||||||
|
</template>
|
||||||
|
|
@ -1,22 +1,12 @@
|
||||||
export { default as Calendar } from './Calendar.vue'
|
export { default as Calendar } from './Calendar.vue'
|
||||||
import type { CalendarSlotName } from 'v-calendar/dist/types/src/components/Calendar/CalendarSlot.vue.d.ts'
|
export { default as CalendarCell } from './CalendarCell.vue'
|
||||||
|
export { default as CalendarCellTrigger } from './CalendarCellTrigger.vue'
|
||||||
export function isVCalendarSlot(slotName: string): slotName is CalendarSlotName {
|
export { default as CalendarGrid } from './CalendarGrid.vue'
|
||||||
const validSlots: CalendarSlotName[] = [
|
export { default as CalendarGridBody } from './CalendarGridBody.vue'
|
||||||
'day-content',
|
export { default as CalendarGridHead } from './CalendarGridHead.vue'
|
||||||
'day-popover',
|
export { default as CalendarGridRow } from './CalendarGridRow.vue'
|
||||||
'dp-footer',
|
export { default as CalendarHeadCell } from './CalendarHeadCell.vue'
|
||||||
'footer',
|
export { default as CalendarHeader } from './CalendarHeader.vue'
|
||||||
'header-title-wrapper',
|
export { default as CalendarHeading } from './CalendarHeading.vue'
|
||||||
'header-title',
|
export { default as CalendarNextButton } from './CalendarNextButton.vue'
|
||||||
'header-prev-button',
|
export { default as CalendarPrevButton } from './CalendarPrevButton.vue'
|
||||||
'header-next-button',
|
|
||||||
'nav',
|
|
||||||
'nav-prev-button',
|
|
||||||
'nav-next-button',
|
|
||||||
'page',
|
|
||||||
'time-header',
|
|
||||||
]
|
|
||||||
|
|
||||||
return validSlots.includes(slotName as CalendarSlotName)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarRoot, type RangeCalendarRootEmits, type RangeCalendarRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import { RangeCalendarCell, RangeCalendarCellTrigger, RangeCalendarGrid, RangeCalendarGridBody, RangeCalendarGridHead, RangeCalendarGridRow, RangeCalendarHeadCell, RangeCalendarHeader, RangeCalendarHeading, RangeCalendarNextButton, RangeCalendarPrevButton } from '.'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarRootProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const emits = defineEmits<RangeCalendarRootEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarRoot
|
||||||
|
v-slot="{ grid, weekDays }"
|
||||||
|
:class="cn('p-3', props.class)"
|
||||||
|
v-bind="forwarded"
|
||||||
|
>
|
||||||
|
<RangeCalendarHeader>
|
||||||
|
<RangeCalendarPrevButton />
|
||||||
|
<RangeCalendarHeading />
|
||||||
|
<RangeCalendarNextButton />
|
||||||
|
</RangeCalendarHeader>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
|
||||||
|
<RangeCalendarGrid v-for="month in grid" :key="month.value.toString()">
|
||||||
|
<RangeCalendarGridHead>
|
||||||
|
<RangeCalendarGridRow>
|
||||||
|
<RangeCalendarHeadCell
|
||||||
|
v-for="day in weekDays" :key="day"
|
||||||
|
>
|
||||||
|
{{ day }}
|
||||||
|
</RangeCalendarHeadCell>
|
||||||
|
</RangeCalendarGridRow>
|
||||||
|
</RangeCalendarGridHead>
|
||||||
|
<RangeCalendarGridBody>
|
||||||
|
<RangeCalendarGridRow v-for="(weekDates, index) in month.rows" :key="`weekDate-${index}`" class="mt-2 w-full">
|
||||||
|
<RangeCalendarCell
|
||||||
|
v-for="weekDate in weekDates"
|
||||||
|
:key="weekDate.toString()"
|
||||||
|
:date="weekDate"
|
||||||
|
>
|
||||||
|
<RangeCalendarCellTrigger
|
||||||
|
:day="weekDate"
|
||||||
|
:month="month.value"
|
||||||
|
/>
|
||||||
|
</RangeCalendarCell>
|
||||||
|
</RangeCalendarGridRow>
|
||||||
|
</RangeCalendarGridBody>
|
||||||
|
</RangeCalendarGrid>
|
||||||
|
</div>
|
||||||
|
</RangeCalendarRoot>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarCell, type RangeCalendarCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarCell
|
||||||
|
:class="cn('relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:bg-accent first:[&:has([data-selected])]:rounded-l-md last:[&:has([data-selected])]:rounded-r-md [&:has([data-selected][data-outside-month])]:bg-accent/50 [&:has([data-selected][data-selection-end])]:rounded-r-md [&:has([data-selected][data-selection-start])]:rounded-l-md', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarCell>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarCellTrigger, type RangeCalendarCellTriggerProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarCellTrigger
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'ghost' }),
|
||||||
|
'h-9 w-9 p-0 font-normal data-[selected]:opacity-100',
|
||||||
|
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground',
|
||||||
|
// Selection Start
|
||||||
|
'data-[selection-start]:bg-primary data-[selection-start]:text-primary-foreground data-[selection-start]:hover:bg-primary data-[selection-start]:hover:text-primary-foreground data-[selection-start]:focus:bg-primary data-[selection-start]:focus:text-primary-foreground',
|
||||||
|
// Selection End
|
||||||
|
'data-[selection-end]:bg-primary data-[selection-end]:text-primary-foreground data-[selection-end]:hover:bg-primary data-[selection-end]:hover:text-primary-foreground data-[selection-end]:focus:bg-primary data-[selection-end]:focus:text-primary-foreground',
|
||||||
|
// Outside months
|
||||||
|
'data-[outside-month]:pointer-events-none data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground [&[data-outside-month][data-selected]]:opacity-30',
|
||||||
|
// Disabled
|
||||||
|
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
|
||||||
|
// Unavailable
|
||||||
|
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarCellTrigger>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarGrid, type RangeCalendarGridProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarGridProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarGrid
|
||||||
|
:class="cn('w-full border-collapse space-y-1', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarGrid>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { RangeCalendarGridBody, type RangeCalendarGridBodyProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarGridBodyProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarGridBody v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarGridBody>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { RangeCalendarGridHead, type RangeCalendarGridHeadProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarGridHeadProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarGridHead v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarGridHead>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarGridRow, type RangeCalendarGridRowProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarGridRowProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarGridRow :class="cn('flex mt-2 w-full', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarGridRow>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarHeadCell, type RangeCalendarHeadCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarHeadCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarHeadCell :class="cn('w-8 rounded-md text-[0.8rem] font-normal text-muted-foreground', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarHeadCell>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarHeader, type RangeCalendarHeaderProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarHeaderProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarHeader :class="cn('relative flex w-full items-center justify-between pt-1', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarHeader>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarHeading, type RangeCalendarHeadingProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarHeadingProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarHeading
|
||||||
|
v-slot="{ headingValue }"
|
||||||
|
:class="cn('text-sm font-medium', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot :heading-value>
|
||||||
|
{{ headingValue }}
|
||||||
|
</slot>
|
||||||
|
</RangeCalendarHeading>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarNext, type RangeCalendarNextProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { ChevronRight } from 'lucide-vue-next'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarNextProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarNext
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'outline' }),
|
||||||
|
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronRight class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</RangeCalendarNext>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarPrev, type RangeCalendarPrevProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { ChevronLeft } from 'lucide-vue-next'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarPrevProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarPrev
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'outline' }),
|
||||||
|
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronLeft class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</RangeCalendarPrev>
|
||||||
|
</template>
|
||||||
12
apps/www/src/lib/registry/default/ui/range-calendar/index.ts
Normal file
12
apps/www/src/lib/registry/default/ui/range-calendar/index.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
export { default as RangeCalendar } from './RangeCalendar.vue'
|
||||||
|
export { default as RangeCalendarCell } from './RangeCalendarCell.vue'
|
||||||
|
export { default as RangeCalendarCellTrigger } from './RangeCalendarCellTrigger.vue'
|
||||||
|
export { default as RangeCalendarGrid } from './RangeCalendarGrid.vue'
|
||||||
|
export { default as RangeCalendarGridBody } from './RangeCalendarGridBody.vue'
|
||||||
|
export { default as RangeCalendarGridHead } from './RangeCalendarGridHead.vue'
|
||||||
|
export { default as RangeCalendarGridRow } from './RangeCalendarGridRow.vue'
|
||||||
|
export { default as RangeCalendarHeadCell } from './RangeCalendarHeadCell.vue'
|
||||||
|
export { default as RangeCalendarHeader } from './RangeCalendarHeader.vue'
|
||||||
|
export { default as RangeCalendarHeading } from './RangeCalendarHeading.vue'
|
||||||
|
export { default as RangeCalendarNextButton } from './RangeCalendarNextButton.vue'
|
||||||
|
export { default as RangeCalendarPrevButton } from './RangeCalendarPrevButton.vue'
|
||||||
331
apps/www/src/lib/registry/default/ui/v-calendar/Calendar.vue
Normal file
331
apps/www/src/lib/registry/default/ui/v-calendar/Calendar.vue
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
||||||
|
import type { Calendar } from 'v-calendar'
|
||||||
|
import { DatePicker } from 'v-calendar'
|
||||||
|
import { computed, nextTick, onMounted, ref, useSlots } from 'vue'
|
||||||
|
import { isVCalendarSlot } from '.'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
/* Extracted from v-calendar */
|
||||||
|
type DatePickerModel = DatePickerDate | DatePickerRangeObject
|
||||||
|
type DateSource = Date | string | number
|
||||||
|
type DatePickerDate = DateSource | Partial<SimpleDateParts> | null
|
||||||
|
interface DatePickerRangeObject {
|
||||||
|
start: Exclude<DatePickerDate, null>
|
||||||
|
end: Exclude<DatePickerDate, null>
|
||||||
|
}
|
||||||
|
interface SimpleDateParts {
|
||||||
|
year: number
|
||||||
|
month: number
|
||||||
|
day: number
|
||||||
|
hours: number
|
||||||
|
minutes: number
|
||||||
|
seconds: number
|
||||||
|
milliseconds: number
|
||||||
|
}
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
const props = withDefaults(defineProps< {
|
||||||
|
modelValue?: string | number | Date | DatePickerModel
|
||||||
|
modelModifiers?: object
|
||||||
|
columns?: number
|
||||||
|
type?: 'single' | 'range'
|
||||||
|
}>(), {
|
||||||
|
type: 'single',
|
||||||
|
columns: 1,
|
||||||
|
})
|
||||||
|
const emits = defineEmits<{
|
||||||
|
(e: 'update:modelValue', payload: typeof props.modelValue): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||||
|
passive: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const datePicker = ref<InstanceType<typeof DatePicker>>()
|
||||||
|
// @ts-expect-error in this current version of v-calendar has the calendaRef instance, which is required to handle arrow nav.
|
||||||
|
const calendarRef = computed<InstanceType<typeof Calendar>>(() => datePicker.value.calendarRef)
|
||||||
|
|
||||||
|
function handleNav(direction: 'prev' | 'next') {
|
||||||
|
if (!calendarRef.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (direction === 'prev')
|
||||||
|
calendarRef.value.movePrev()
|
||||||
|
else calendarRef.value.moveNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick()
|
||||||
|
if (modelValue.value instanceof Date && calendarRef.value)
|
||||||
|
calendarRef.value.focusDate(modelValue.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const $slots = useSlots()
|
||||||
|
const vCalendarSlots = computed(() => {
|
||||||
|
return Object.keys($slots)
|
||||||
|
.filter(name => isVCalendarSlot(name))
|
||||||
|
.reduce((obj: Record<string, any>, key: string) => {
|
||||||
|
obj[key] = $slots[key]
|
||||||
|
return obj
|
||||||
|
}, {})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative">
|
||||||
|
<div v-if="$attrs.mode !== 'time'" class="absolute flex justify-between w-full px-4 top-3 z-[1]">
|
||||||
|
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('prev')">
|
||||||
|
<ChevronLeft class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('next')">
|
||||||
|
<ChevronRight class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DatePicker
|
||||||
|
ref="datePicker"
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model="modelValue"
|
||||||
|
:model-modifiers="modelModifiers"
|
||||||
|
class="calendar"
|
||||||
|
trim-weeks
|
||||||
|
:transition="'none'"
|
||||||
|
:columns="columns"
|
||||||
|
>
|
||||||
|
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
|
||||||
|
<slot :name="slot" v-bind="scope" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #nav-prev-button>
|
||||||
|
<ChevronLeft />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #nav-next-button>
|
||||||
|
<ChevronRight />
|
||||||
|
</template>
|
||||||
|
</DatePicker>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
.calendar {
|
||||||
|
@apply p-3 text-center;
|
||||||
|
}
|
||||||
|
.calendar .vc-pane-layout {
|
||||||
|
@apply grid gap-4;
|
||||||
|
}
|
||||||
|
.calendar .vc-title {
|
||||||
|
@apply text-sm font-medium relative z-20;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-popover-content {
|
||||||
|
@apply mt-3 rounded-md max-w-xs border bg-background;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-nav-header {
|
||||||
|
@apply flex justify-between items-center p-2;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-nav-items {
|
||||||
|
@apply grid grid-cols-4 gap-2 p-2;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item {
|
||||||
|
@apply rounded-md px-2 py-1;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item:hover {
|
||||||
|
@apply text-muted-foreground bg-muted;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item.is-active {
|
||||||
|
@apply bg-primary text-primary-foreground;
|
||||||
|
}
|
||||||
|
.calendar .vc-pane-header-wrapper {
|
||||||
|
@apply hidden;
|
||||||
|
}
|
||||||
|
.calendar .vc-weeks {
|
||||||
|
@apply mt-4;
|
||||||
|
}
|
||||||
|
.calendar .vc-weekdays {
|
||||||
|
@apply justify-items-center;
|
||||||
|
}
|
||||||
|
.calendar .vc-weekday {
|
||||||
|
@apply text-muted-foreground rounded-md font-normal text-[0.8rem];
|
||||||
|
}
|
||||||
|
.calendar .vc-weeks {
|
||||||
|
@apply w-full space-y-2 flex flex-col [&>_div]:grid [&>_div]:grid-cols-7;
|
||||||
|
}
|
||||||
|
.calendar .vc-day:has(.vc-highlights) {
|
||||||
|
@apply first:rounded-l-md last:rounded-r-md;
|
||||||
|
}
|
||||||
|
.calendar .vc-day.is-today:not(:has(.vc-day-layer)) .vc-day-content {
|
||||||
|
@apply bg-secondary text-primary rounded-md;
|
||||||
|
}
|
||||||
|
.calendar .vc-day:has(.vc-highlight-base-start) {
|
||||||
|
@apply rounded-l-md;
|
||||||
|
}
|
||||||
|
.calendar .vc-day:has(.vc-highlight-base-end) {
|
||||||
|
@apply rounded-r-md;
|
||||||
|
}
|
||||||
|
.calendar .vc-day:has(.vc-highlight-bg-outline):not(:has(.vc-highlight-base-start)):not(:has(.vc-highlight-base-end)) {
|
||||||
|
@apply rounded-md;
|
||||||
|
}
|
||||||
|
.calendar .vc-day-content {
|
||||||
|
@apply text-center text-sm p-0 relative focus-within:relative focus-within:z-20 inline-flex items-center justify-center ring-offset-background hover:transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground h-9 w-9 font-normal aria-selected:opacity-100 select-none;
|
||||||
|
}
|
||||||
|
.calendar .vc-day-content:not(.vc-highlight-content-light) {
|
||||||
|
@apply rounded-md;
|
||||||
|
}
|
||||||
|
.calendar .is-not-in-month:not(:has(.vc-highlight-content-solid)):not(:has(.vc-highlight-content-light)):not(:has(.vc-highlight-content-outline)),
|
||||||
|
.calendar .vc-disabled {
|
||||||
|
@apply text-muted-foreground opacity-50;
|
||||||
|
}
|
||||||
|
.calendar .vc-highlight-content-solid, .calendar .vc-highlight-content-outline {
|
||||||
|
@apply bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground;
|
||||||
|
}
|
||||||
|
.calendar .vc-highlight-content-light {
|
||||||
|
@apply bg-accent text-accent-foreground;
|
||||||
|
}
|
||||||
|
.calendar .vc-pane-container.in-transition {
|
||||||
|
@apply overflow-hidden;
|
||||||
|
}
|
||||||
|
.calendar .vc-pane-container {
|
||||||
|
@apply w-full relative;
|
||||||
|
}
|
||||||
|
:root {
|
||||||
|
--vc-slide-translate: 22px;
|
||||||
|
--vc-slide-duration: 0.15s;
|
||||||
|
--vc-slide-timing: ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .vc-fade-enter-active,
|
||||||
|
.calendar .vc-fade-leave-active,
|
||||||
|
.calendar .vc-slide-left-enter-active,
|
||||||
|
.calendar .vc-slide-left-leave-active,
|
||||||
|
.calendar .vc-slide-right-enter-active,
|
||||||
|
.calendar .vc-slide-right-leave-active,
|
||||||
|
.calendar .vc-slide-up-enter-active,
|
||||||
|
.calendar .vc-slide-up-leave-active,
|
||||||
|
.calendar .vc-slide-down-enter-active,
|
||||||
|
.calendar .vc-slide-down-leave-active,
|
||||||
|
.calendar .vc-slide-fade-enter-active,
|
||||||
|
.calendar .vc-slide-fade-leave-active {
|
||||||
|
transition:
|
||||||
|
opacity var(--vc-slide-duration) var(--vc-slide-timing),
|
||||||
|
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
|
||||||
|
transition:
|
||||||
|
transform var(--vc-slide-duration) var(--vc-slide-timing),
|
||||||
|
opacity var(--vc-slide-duration) var(--vc-slide-timing);
|
||||||
|
transition:
|
||||||
|
transform var(--vc-slide-duration) var(--vc-slide-timing),
|
||||||
|
opacity var(--vc-slide-duration) var(--vc-slide-timing),
|
||||||
|
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .vc-none-leave-active,
|
||||||
|
.calendar .vc-fade-leave-active,
|
||||||
|
.calendar .vc-slide-left-leave-active,
|
||||||
|
.calendar .vc-slide-right-leave-active,
|
||||||
|
.calendar .vc-slide-up-leave-active,
|
||||||
|
.calendar .vc-slide-down-leave-active {
|
||||||
|
position: absolute !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .vc-none-enter-from,
|
||||||
|
.calendar .vc-none-leave-to,
|
||||||
|
.calendar .vc-fade-enter-from,
|
||||||
|
.calendar .vc-fade-leave-to,
|
||||||
|
.calendar .vc-slide-left-enter-from,
|
||||||
|
.calendar .vc-slide-left-leave-to,
|
||||||
|
.calendar .vc-slide-right-enter-from,
|
||||||
|
.calendar .vc-slide-right-leave-to,
|
||||||
|
.calendar .vc-slide-up-enter-from,
|
||||||
|
.calendar .vc-slide-up-leave-to,
|
||||||
|
.calendar .vc-slide-down-enter-from,
|
||||||
|
.calendar .vc-slide-down-leave-to,
|
||||||
|
.calendar .vc-slide-fade-enter-from,
|
||||||
|
.calendar .vc-slide-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .vc-slide-left-enter-from,
|
||||||
|
.calendar .vc-slide-right-leave-to,
|
||||||
|
.calendar .vc-slide-fade-enter-from.direction-left,
|
||||||
|
.calendar .vc-slide-fade-leave-to.direction-left {
|
||||||
|
-webkit-transform: translateX(var(--vc-slide-translate));
|
||||||
|
transform: translateX(var(--vc-slide-translate));
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .vc-slide-right-enter-from,
|
||||||
|
.calendar .vc-slide-left-leave-to,
|
||||||
|
.calendar .vc-slide-fade-enter-from.direction-right,
|
||||||
|
.calendar .vc-slide-fade-leave-to.direction-right {
|
||||||
|
-webkit-transform: translateX(calc(-1 * var(--vc-slide-translate)));
|
||||||
|
transform: translateX(calc(-1 * var(--vc-slide-translate)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .vc-slide-up-enter-from,
|
||||||
|
.calendar .vc-slide-down-leave-to,
|
||||||
|
.calendar .vc-slide-fade-enter-from.direction-top,
|
||||||
|
.calendar .vc-slide-fade-leave-to.direction-top {
|
||||||
|
-webkit-transform: translateY(var(--vc-slide-translate));
|
||||||
|
transform: translateY(var(--vc-slide-translate));
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .vc-slide-down-enter-from,
|
||||||
|
.calendar .vc-slide-up-leave-to,
|
||||||
|
.calendar .vc-slide-fade-enter-from.direction-bottom,
|
||||||
|
.calendar .vc-slide-fade-leave-to.direction-bottom {
|
||||||
|
-webkit-transform: translateY(calc(-1 * var(--vc-slide-translate)));
|
||||||
|
transform: translateY(calc(-1 * var(--vc-slide-translate)));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Timepicker styles
|
||||||
|
*/
|
||||||
|
.vc-time-picker {
|
||||||
|
@apply flex flex-col items-center p-2;
|
||||||
|
}
|
||||||
|
.vc-time-picker.vc-invalid {
|
||||||
|
@apply pointer-events-none opacity-50;
|
||||||
|
}
|
||||||
|
.vc-time-picker.vc-attached {
|
||||||
|
@apply border-t border-solid border-secondary mt-2;
|
||||||
|
}
|
||||||
|
.vc-time-picker > * + * {
|
||||||
|
@apply mt-1;
|
||||||
|
}
|
||||||
|
.vc-time-header {
|
||||||
|
@apply flex items-center text-sm font-semibold uppercase mt-1 px-1 leading-6;
|
||||||
|
}
|
||||||
|
.vc-time-select-group {
|
||||||
|
@apply inline-flex items-center px-1 rounded-md bg-primary-foreground border border-solid border-secondary;
|
||||||
|
}
|
||||||
|
.vc-time-select-group .vc-base-icon {
|
||||||
|
@apply mr-1 text-primary stroke-primary;
|
||||||
|
}
|
||||||
|
.vc-time-select-group select {
|
||||||
|
@apply bg-primary-foreground p-1 appearance-none outline-none text-center;
|
||||||
|
}
|
||||||
|
.vc-time-weekday {
|
||||||
|
@apply text-muted-foreground tracking-wide;
|
||||||
|
}
|
||||||
|
.vc-time-month {
|
||||||
|
@apply text-primary ml-2;
|
||||||
|
}
|
||||||
|
.vc-time-day {
|
||||||
|
@apply text-primary ml-1;
|
||||||
|
}
|
||||||
|
.vc-time-year {
|
||||||
|
@apply text-muted-foreground ml-2;
|
||||||
|
}
|
||||||
|
.vc-time-colon {
|
||||||
|
@apply mb-0.5;
|
||||||
|
}
|
||||||
|
.vc-time-decimal {
|
||||||
|
@apply ml-0.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
22
apps/www/src/lib/registry/default/ui/v-calendar/index.ts
Normal file
22
apps/www/src/lib/registry/default/ui/v-calendar/index.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
export { default as Calendar } from './Calendar.vue'
|
||||||
|
import type { CalendarSlotName } from 'v-calendar/dist/types/src/components/Calendar/CalendarSlot.vue.d.ts'
|
||||||
|
|
||||||
|
export function isVCalendarSlot(slotName: string): slotName is CalendarSlotName {
|
||||||
|
const validSlots: CalendarSlotName[] = [
|
||||||
|
'day-content',
|
||||||
|
'day-popover',
|
||||||
|
'dp-footer',
|
||||||
|
'footer',
|
||||||
|
'header-title-wrapper',
|
||||||
|
'header-title',
|
||||||
|
'header-prev-button',
|
||||||
|
'header-next-button',
|
||||||
|
'nav',
|
||||||
|
'nav-prev-button',
|
||||||
|
'nav-next-button',
|
||||||
|
'page',
|
||||||
|
'time-header',
|
||||||
|
]
|
||||||
|
|
||||||
|
return validSlots.includes(slotName as CalendarSlotName)
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { type Ref, ref } from 'vue'
|
||||||
|
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date'
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||||
|
|
||||||
const date = ref(new Date())
|
const value = ref(today(getLocalTimeZone())) as Ref<DateValue>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Calendar v-model="date" class="rounded-md border" />
|
<Calendar v-model="value" :weekday-format="'short'" class="rounded-md border" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
105
apps/www/src/lib/registry/new-york/example/CalendarForm.vue
Normal file
105
apps/www/src/lib/registry/new-york/example/CalendarForm.vue
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, h, ref } from 'vue'
|
||||||
|
import { CalendarDate, DateFormatter, getLocalTimeZone, parseDate, today } from '@internationalized/date'
|
||||||
|
import { toDate } from 'radix-vue/date'
|
||||||
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const df = new DateFormatter('en-US', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(z.object({
|
||||||
|
dob: z
|
||||||
|
.string()
|
||||||
|
.refine(v => v, { message: 'A date of birth is required.' }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const placeholder = ref()
|
||||||
|
|
||||||
|
const { handleSubmit, setValues, values } = useForm({
|
||||||
|
validationSchema: formSchema,
|
||||||
|
})
|
||||||
|
|
||||||
|
const value = computed({
|
||||||
|
get: () => values.dob ? parseDate(values.dob) : undefined,
|
||||||
|
set: val => val,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit((values) => {
|
||||||
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form class="space-y-8" @submit="onSubmit">
|
||||||
|
<FormField name="dob">
|
||||||
|
<FormItem class="flex flex-col">
|
||||||
|
<FormLabel>Date of birth</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant="outline" :class="cn(
|
||||||
|
'w-[240px] ps-3 text-start font-normal',
|
||||||
|
!value && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<span>{{ value ? df.format(toDate(value)) : "Pick a date" }}</span>
|
||||||
|
<CalendarIcon class="ms-auto h-4 w-4 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
<input hidden>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0">
|
||||||
|
<Calendar
|
||||||
|
v-model:placeholder="placeholder"
|
||||||
|
v-model="value"
|
||||||
|
calendar-label="Date of birth"
|
||||||
|
initial-focus
|
||||||
|
:min-value="new CalendarDate(1900, 1, 1)"
|
||||||
|
:max-value="today(getLocalTimeZone())"
|
||||||
|
@update:model-value="(v) => {
|
||||||
|
if (v) {
|
||||||
|
setValues({
|
||||||
|
dob: v.toString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setValues({
|
||||||
|
dob: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormDescription>
|
||||||
|
Your date of birth is used to calculate your age.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
<Button type="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { type HTMLAttributes, type Ref, computed, toRef } from 'vue'
|
||||||
|
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useDateFormatter, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import { createDecade, createYear, toDate } from 'radix-vue/date'
|
||||||
|
import { type DateValue, getLocalTimeZone, today } from '@internationalized/date'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading } from '@/lib/registry/new-york/ui/calendar'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/lib/registry/new-york/ui/select'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>(), {
|
||||||
|
modelValue: undefined,
|
||||||
|
placeholder() {
|
||||||
|
return today(getLocalTimeZone())
|
||||||
|
},
|
||||||
|
weekdayFormat: 'short',
|
||||||
|
})
|
||||||
|
const emits = defineEmits<CalendarRootEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, placeholder: __, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const placeholder = useVModel(props, 'modelValue', emits, {
|
||||||
|
passive: true,
|
||||||
|
defaultValue: today(getLocalTimeZone()),
|
||||||
|
}) as Ref<DateValue>
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
|
||||||
|
const formatter = useDateFormatter('en')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarRoot
|
||||||
|
v-slot="{ date, grid, weekDays }"
|
||||||
|
v-model:placeholder="placeholder"
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="cn('rounded-md border p-3', props.class)"
|
||||||
|
>
|
||||||
|
<CalendarHeader>
|
||||||
|
<CalendarHeading class="flex w-full items-center justify-between gap-2">
|
||||||
|
<Select
|
||||||
|
:default-value="placeholder.month.toString()"
|
||||||
|
@update:model-value="(v) => {
|
||||||
|
if (!v || !placeholder) return;
|
||||||
|
if (Number(v) === placeholder?.month) return;
|
||||||
|
placeholder = placeholder.set({
|
||||||
|
month: Number(v),
|
||||||
|
})
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<SelectTrigger aria-label="Select month" class="w-[60%]">
|
||||||
|
<SelectValue placeholder="Select month" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent class="max-h-[200px]">
|
||||||
|
<SelectItem
|
||||||
|
v-for="month in createYear({ dateObj: date })"
|
||||||
|
:key="month.toString()" :value="month.month.toString()"
|
||||||
|
>
|
||||||
|
{{ formatter.custom(toDate(month), { month: 'long' }) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
:default-value="props.placeholder.year.toString()"
|
||||||
|
@update:model-value="(v) => {
|
||||||
|
if (!v || !placeholder) return;
|
||||||
|
if (Number(v) === placeholder?.year) return;
|
||||||
|
placeholder = placeholder.set({
|
||||||
|
year: Number(v),
|
||||||
|
})
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<SelectTrigger aria-label="Select year" class="w-[40%]">
|
||||||
|
<SelectValue placeholder="Select year" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent class="max-h-[200px]">
|
||||||
|
<SelectItem
|
||||||
|
v-for="yearValue in createDecade({ dateObj: date, startIndex: -10, endIndex: 10 })"
|
||||||
|
:key="yearValue.toString()" :value="yearValue.year.toString()"
|
||||||
|
>
|
||||||
|
{{ yearValue.year }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</CalendarHeading>
|
||||||
|
</CalendarHeader>
|
||||||
|
|
||||||
|
<div class="flex flex-col space-y-4 pt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
|
||||||
|
<CalendarGrid v-for="month in grid" :key="month.value.toString()">
|
||||||
|
<CalendarGridHead>
|
||||||
|
<CalendarGridRow>
|
||||||
|
<CalendarHeadCell
|
||||||
|
v-for="day in weekDays" :key="day"
|
||||||
|
>
|
||||||
|
{{ day }}
|
||||||
|
</CalendarHeadCell>
|
||||||
|
</CalendarGridRow>
|
||||||
|
</CalendarGridHead>
|
||||||
|
<CalendarGridBody class="grid">
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
|
</CalendarCell>
|
||||||
|
</CalendarGridRow>
|
||||||
|
</CalendarGridBody>
|
||||||
|
</CalendarGrid>
|
||||||
|
</div>
|
||||||
|
</CalendarRoot>
|
||||||
|
</template>
|
||||||
|
|
@ -37,7 +37,7 @@ const data = [
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Card>
|
<Card class="h-full">
|
||||||
<CardHeader class="pb-4">
|
<CardHeader class="pb-4">
|
||||||
<CardTitle class="text-base">
|
<CardTitle class="text-base">
|
||||||
Move Goal
|
Move Goal
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,40 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { format } from 'date-fns'
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { CalendarIcon } from '@radix-icons/vue'
|
|
||||||
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
DateFormatter,
|
||||||
PopoverContent,
|
type DateValue,
|
||||||
PopoverTrigger,
|
getLocalTimeZone,
|
||||||
} from '@/lib/registry/new-york/ui/popover'
|
} from '@internationalized/date'
|
||||||
|
|
||||||
const date = ref<Date>()
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
|
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const df = new DateFormatter('en-US', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
|
const value = ref<DateValue>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger as-child>
|
<PopoverTrigger as-child>
|
||||||
<Button
|
<Button
|
||||||
:variant="'outline'"
|
variant="outline"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'w-[280px] justify-start text-left font-normal',
|
'w-[280px] justify-start text-left font-normal',
|
||||||
!date && 'text-muted-foreground',
|
!value && 'text-muted-foreground',
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
<span>{{ date ? format(date, "PPP") : "Pick a date" }}</span>
|
{{ value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date" }}
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="w-auto p-0">
|
<PopoverContent class="w-auto p-0">
|
||||||
<Calendar v-model="date" />
|
<Calendar v-model="value" initial-focus />
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { computed, h, ref } from 'vue'
|
||||||
import { format } from 'date-fns'
|
import { CalendarDate, DateFormatter, getLocalTimeZone, parseDate, today } from '@internationalized/date'
|
||||||
|
import { toDate } from 'radix-vue/date'
|
||||||
import { CalendarIcon } from '@radix-icons/vue'
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
FormDescription,
|
||||||
|
|
@ -17,22 +16,32 @@ import {
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/new-york/ui/form'
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from '@/lib/registry/new-york/ui/popover'
|
|
||||||
import { toast } from '@/lib/registry/new-york/ui/toast'
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const df = new DateFormatter('en-US', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
dob: z.date({
|
dob: z
|
||||||
required_error: 'A date of birth is required.',
|
.string()
|
||||||
}),
|
.refine(v => v, { message: 'A date of birth is required.' }),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const { handleSubmit } = useForm({
|
const placeholder = ref()
|
||||||
|
|
||||||
|
const { handleSubmit, setValues, values } = useForm({
|
||||||
validationSchema: formSchema,
|
validationSchema: formSchema,
|
||||||
|
initialValues: {
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const value = computed({
|
||||||
|
get: () => values.dob ? parseDate(values.dob) : undefined,
|
||||||
|
set: val => val,
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
|
|
@ -45,7 +54,7 @@ const onSubmit = handleSubmit((values) => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form class="space-y-8" @submit="onSubmit">
|
<form class="space-y-8" @submit="onSubmit">
|
||||||
<FormField v-slot="{ componentField, value }" name="dob">
|
<FormField name="dob">
|
||||||
<FormItem class="flex flex-col">
|
<FormItem class="flex flex-col">
|
||||||
<FormLabel>Date of birth</FormLabel>
|
<FormLabel>Date of birth</FormLabel>
|
||||||
<Popover>
|
<Popover>
|
||||||
|
|
@ -57,13 +66,34 @@ const onSubmit = handleSubmit((values) => {
|
||||||
!value && 'text-muted-foreground',
|
!value && 'text-muted-foreground',
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<span>{{ value ? format(value, "PPP") : "Pick a date" }}</span>
|
<span>{{ value ? df.format(toDate(value)) : "Pick a date" }}</span>
|
||||||
<CalendarIcon class="ms-auto h-4 w-4 opacity-50" />
|
<CalendarIcon class="ms-auto h-4 w-4 opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
|
<input hidden>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="p-0">
|
<PopoverContent class="w-auto p-0">
|
||||||
<Calendar v-bind="componentField" />
|
<Calendar
|
||||||
|
v-model:placeholder="placeholder"
|
||||||
|
v-model="value"
|
||||||
|
calendar-label="Date of birth"
|
||||||
|
initial-focus
|
||||||
|
:min-value="new CalendarDate(1900, 1, 1)"
|
||||||
|
:max-value="today(getLocalTimeZone())"
|
||||||
|
@update:model-value="(v) => {
|
||||||
|
if (v) {
|
||||||
|
setValues({
|
||||||
|
dob: v.toString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setValues({
|
||||||
|
dob: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,31 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { addDays, format } from 'date-fns'
|
|
||||||
import { CalendarIcon } from '@radix-icons/vue'
|
|
||||||
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
DateFormatter,
|
||||||
PopoverContent,
|
type DateValue,
|
||||||
PopoverTrigger,
|
getLocalTimeZone,
|
||||||
} from '@/lib/registry/new-york/ui/popover'
|
today,
|
||||||
import {
|
} from '@internationalized/date'
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/lib/registry/new-york/ui/select'
|
|
||||||
|
|
||||||
const date = ref<Date>()
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
|
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/lib/registry/new-york/ui/select'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const df = new DateFormatter('en-US', {
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ value: 0, label: 'Today' },
|
||||||
|
{ value: 1, label: 'Tomorrow' },
|
||||||
|
{ value: 3, label: 'In 3 days' },
|
||||||
|
{ value: 7, label: 'In a week' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const value = ref<DateValue>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -29,45 +35,30 @@ const date = ref<Date>()
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'w-[280px] justify-start text-left font-normal',
|
'w-[280px] justify-start text-left font-normal',
|
||||||
!date && 'text-muted-foreground',
|
!value && 'text-muted-foreground',
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
<template v-if="date">
|
{{ value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date" }}
|
||||||
{{ format(date, "PPP") }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span>Pick a date</span>
|
|
||||||
</template>
|
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="flex w-auto flex-col space-y-2 p-2">
|
<PopoverContent class="flex w-auto flex-col gap-y-2 p-2">
|
||||||
<Select
|
<Select
|
||||||
@update:model-value="(value) => {
|
@update:model-value="(v) => {
|
||||||
date = addDays(new Date(), parseInt(value))
|
if (!v) return;
|
||||||
|
value = today(getLocalTimeZone()).add({ days: Number(v) });
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select" />
|
<SelectValue placeholder="Select" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent position="popper">
|
<SelectContent>
|
||||||
<SelectItem value="0">
|
<SelectItem v-for="item in items" :key="item.value" :value="item.value.toString()">
|
||||||
Today
|
{{ item.label }}
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="1">
|
|
||||||
Tomorrow
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="3">
|
|
||||||
In 3 days
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="7">
|
|
||||||
In a week
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<div class="rounded-md border">
|
<Calendar v-model="value" />
|
||||||
<Calendar v-model="date" mode="single" />
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,55 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { addDays, format } from 'date-fns'
|
import { type Ref, ref } from 'vue'
|
||||||
import { ref } from 'vue'
|
|
||||||
import { CalendarIcon } from '@radix-icons/vue'
|
|
||||||
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
CalendarDate,
|
||||||
PopoverContent,
|
DateFormatter,
|
||||||
PopoverTrigger,
|
getLocalTimeZone,
|
||||||
} from '@/lib/registry/new-york/ui/popover'
|
} from '@internationalized/date'
|
||||||
|
|
||||||
const date = ref({
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
start: new Date(2022, 0, 20),
|
import type { DateRange } from 'radix-vue'
|
||||||
end: addDays(new Date(2022, 0, 20), 20),
|
import { RangeCalendar } from '@/lib/registry/new-york/ui/range-calendar'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const df = new DateFormatter('en-US', {
|
||||||
|
dateStyle: 'medium',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const value = ref({
|
||||||
|
start: new CalendarDate(2022, 1, 20),
|
||||||
|
end: new CalendarDate(2022, 1, 20).add({ days: 20 }),
|
||||||
|
}) as Ref<DateRange>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="cn('grid gap-2', $attrs.class ?? '')">
|
<Popover>
|
||||||
<Popover>
|
<PopoverTrigger as-child>
|
||||||
<PopoverTrigger as-child>
|
<Button
|
||||||
<Button
|
variant="outline"
|
||||||
id="date"
|
:class="cn(
|
||||||
:variant="'outline'"
|
'w-[280px] justify-start text-left font-normal',
|
||||||
:class="cn(
|
!value && 'text-muted-foreground',
|
||||||
'w-[300px] justify-start text-left font-normal',
|
)"
|
||||||
!date && 'text-muted-foreground',
|
>
|
||||||
)"
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
>
|
<template v-if="value.start">
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
<template v-if="value.end">
|
||||||
|
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{ df.format(value.end.toDate(getLocalTimeZone())) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
<span>
|
<template v-else>
|
||||||
{{ date.start ? (
|
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
|
||||||
date.end ? `${format(date.start, 'LLL dd, y')} - ${format(date.end, 'LLL dd, y')}`
|
</template>
|
||||||
: format(date.start, 'LLL dd, y')
|
</template>
|
||||||
) : 'Pick a date' }}
|
<template v-else>
|
||||||
</span>
|
Pick a date
|
||||||
</Button>
|
</template>
|
||||||
</PopoverTrigger>
|
</Button>
|
||||||
<PopoverContent class="w-auto p-0" align="start">
|
</PopoverTrigger>
|
||||||
<Calendar
|
<PopoverContent class="w-auto p-0">
|
||||||
v-model.range="date"
|
<RangeCalendar v-model="value" initial-focus :number-of-months="2" :placeholder="value?.start" @update:start-value="(startDate) => value.start = startDate" />
|
||||||
:columns="2"
|
</PopoverContent>
|
||||||
/>
|
</Popover>
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { type Ref, ref } from 'vue'
|
||||||
|
import { getLocalTimeZone, today } from '@internationalized/date'
|
||||||
|
import type { DateRange } from 'radix-vue'
|
||||||
|
import { RangeCalendar } from '@/lib/registry/new-york/ui/range-calendar'
|
||||||
|
|
||||||
|
const start = today(getLocalTimeZone())
|
||||||
|
const end = start.add({ days: 7 })
|
||||||
|
|
||||||
|
const value = ref({
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
}) as Ref<DateRange>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendar v-model="value" class="rounded-md border" />
|
||||||
|
</template>
|
||||||
10
apps/www/src/lib/registry/new-york/example/VCalendarDemo.vue
Normal file
10
apps/www/src/lib/registry/new-york/example/VCalendarDemo.vue
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||||
|
|
||||||
|
const date = ref(new Date())
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Calendar v-model="date" class="rounded-md border" />
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/lib/registry/new-york/ui/popover'
|
||||||
|
|
||||||
|
const date = ref<Date>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
:variant="'outline'"
|
||||||
|
:class="cn(
|
||||||
|
'w-[280px] justify-start text-left font-normal',
|
||||||
|
!date && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
<span>{{ date ? format(date, "PPP") : "Pick a date" }}</span>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0">
|
||||||
|
<Calendar v-model="date" />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/lib/registry/new-york/ui/popover'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(z.object({
|
||||||
|
dob: z.date({
|
||||||
|
required_error: 'A date of birth is required.',
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { handleSubmit } = useForm({
|
||||||
|
validationSchema: formSchema,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit((values) => {
|
||||||
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form class="space-y-8" @submit="onSubmit">
|
||||||
|
<FormField v-slot="{ componentField, value }" name="dob">
|
||||||
|
<FormItem class="flex flex-col">
|
||||||
|
<FormLabel>Date of birth</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant="outline" :class="cn(
|
||||||
|
'w-[240px] ps-3 text-start font-normal',
|
||||||
|
!value && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<span>{{ value ? format(value, "PPP") : "Pick a date" }}</span>
|
||||||
|
<CalendarIcon class="ms-auto h-4 w-4 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="p-0">
|
||||||
|
<Calendar v-bind="componentField" />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormDescription>
|
||||||
|
Your date of birth is used to calculate your age.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
<Button type="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { addDays, format } from 'date-fns'
|
||||||
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/lib/registry/new-york/ui/popover'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/lib/registry/new-york/ui/select'
|
||||||
|
|
||||||
|
const date = ref<Date>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
:class="cn(
|
||||||
|
'w-[280px] justify-start text-left font-normal',
|
||||||
|
!date && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
<template v-if="date">
|
||||||
|
{{ format(date, "PPP") }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span>Pick a date</span>
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="flex w-auto flex-col space-y-2 p-2">
|
||||||
|
<Select
|
||||||
|
@update:model-value="(value) => {
|
||||||
|
date = addDays(new Date(), parseInt(value))
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent position="popper">
|
||||||
|
<SelectItem value="0">
|
||||||
|
Today
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="1">
|
||||||
|
Tomorrow
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="3">
|
||||||
|
In 3 days
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="7">
|
||||||
|
In a week
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<div class="rounded-md border">
|
||||||
|
<Calendar v-model="date" mode="single" />
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { addDays, format } from 'date-fns'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/lib/registry/new-york/ui/popover'
|
||||||
|
|
||||||
|
const date = ref({
|
||||||
|
start: new Date(2022, 0, 20),
|
||||||
|
end: addDays(new Date(2022, 0, 20), 20),
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('grid gap-2', $attrs.class ?? '')">
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
id="date"
|
||||||
|
:variant="'outline'"
|
||||||
|
:class="cn(
|
||||||
|
'w-[300px] justify-start text-left font-normal',
|
||||||
|
!date && 'text-muted-foreground',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{ date.start ? (
|
||||||
|
date.end ? `${format(date.start, 'LLL dd, y')} - ${format(date.end, 'LLL dd, y')}`
|
||||||
|
: format(date.start, 'LLL dd, y')
|
||||||
|
) : 'Pick a date' }}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0" align="start">
|
||||||
|
<Calendar
|
||||||
|
v-model.range="date"
|
||||||
|
:columns="2"
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
import { ref } from 'vue'
|
|
||||||
import { CalendarIcon } from '@radix-icons/vue'
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
import { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
|
|
@ -5,7 +5,7 @@ import { CalendarIcon } from '@radix-icons/vue'
|
||||||
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
import { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
|
|
@ -1,325 +1,60 @@
|
||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import { useVModel } from '@vueuse/core'
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue'
|
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||||
import type { Calendar } from 'v-calendar'
|
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNextButton, CalendarPrevButton } from '.'
|
||||||
import { DatePicker } from 'v-calendar'
|
|
||||||
import { computed, nextTick, onMounted, ref, useSlots } from 'vue'
|
|
||||||
import { isVCalendarSlot } from '.'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
|
||||||
|
|
||||||
/* Extracted from v-calendar */
|
const props = defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>()
|
||||||
type DatePickerModel = DatePickerDate | DatePickerRangeObject
|
|
||||||
type DateSource = Date | string | number
|
|
||||||
type DatePickerDate = DateSource | Partial<SimpleDateParts> | null
|
|
||||||
interface DatePickerRangeObject {
|
|
||||||
start: Exclude<DatePickerDate, null>
|
|
||||||
end: Exclude<DatePickerDate, null>
|
|
||||||
}
|
|
||||||
interface SimpleDateParts {
|
|
||||||
year: number
|
|
||||||
month: number
|
|
||||||
day: number
|
|
||||||
hours: number
|
|
||||||
minutes: number
|
|
||||||
seconds: number
|
|
||||||
milliseconds: number
|
|
||||||
}
|
|
||||||
|
|
||||||
defineOptions({
|
const emits = defineEmits<CalendarRootEmits>()
|
||||||
inheritAttrs: false,
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
})
|
})
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
modelValue?: string | number | Date | DatePickerModel
|
|
||||||
modelModifiers?: object
|
|
||||||
columns?: number
|
|
||||||
type?: 'single' | 'range'
|
|
||||||
}>(), {
|
|
||||||
type: 'single',
|
|
||||||
columns: 1,
|
|
||||||
})
|
|
||||||
const emits = defineEmits<{
|
|
||||||
(e: 'update:modelValue', payload: typeof props.modelValue): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
|
||||||
passive: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const datePicker = ref<InstanceType<typeof DatePicker>>()
|
|
||||||
// @ts-expect-error in this current version of v-calendar has the calendaRef instance, which is required to handle arrow nav.
|
|
||||||
const calendarRef = computed<InstanceType<typeof Calendar>>(() => datePicker.value.calendarRef)
|
|
||||||
|
|
||||||
function handleNav(direction: 'prev' | 'next') {
|
|
||||||
if (!calendarRef.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (direction === 'prev')
|
|
||||||
calendarRef.value.movePrev()
|
|
||||||
else calendarRef.value.moveNext()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await nextTick()
|
|
||||||
if (modelValue.value instanceof Date && calendarRef.value)
|
|
||||||
calendarRef.value.focusDate(modelValue.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const $slots = useSlots()
|
|
||||||
const vCalendarSlots = computed(() => {
|
|
||||||
return Object.keys($slots)
|
|
||||||
.filter(name => isVCalendarSlot(name))
|
|
||||||
.reduce((obj: Record<string, any>, key: string) => {
|
|
||||||
obj[key] = $slots[key]
|
|
||||||
return obj
|
|
||||||
}, {})
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative">
|
<CalendarRoot
|
||||||
<div v-if="$attrs.mode !== 'time'" class="absolute flex justify-between w-full px-4 top-3 z-[1]">
|
v-slot="{ grid, weekDays }"
|
||||||
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" type="button" @click="handleNav('prev')">
|
:class="cn('p-3', props.class)"
|
||||||
<ChevronLeftIcon class="w-4 h-4" />
|
v-bind="forwarded"
|
||||||
</button>
|
>
|
||||||
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" type="button" @click="handleNav('next')">
|
<CalendarHeader>
|
||||||
<ChevronRightIcon class="w-4 h-4" />
|
<CalendarPrevButton />
|
||||||
</button>
|
<CalendarHeading />
|
||||||
|
<CalendarNextButton />
|
||||||
|
</CalendarHeader>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
|
||||||
|
<CalendarGrid v-for="month in grid" :key="month.value.toString()">
|
||||||
|
<CalendarGridHead>
|
||||||
|
<CalendarGridRow>
|
||||||
|
<CalendarHeadCell
|
||||||
|
v-for="day in weekDays" :key="day"
|
||||||
|
>
|
||||||
|
{{ 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"
|
||||||
|
/>
|
||||||
|
</CalendarCell>
|
||||||
|
</CalendarGridRow>
|
||||||
|
</CalendarGridBody>
|
||||||
|
</CalendarGrid>
|
||||||
</div>
|
</div>
|
||||||
|
</CalendarRoot>
|
||||||
<DatePicker
|
|
||||||
ref="datePicker"
|
|
||||||
v-model="modelValue"
|
|
||||||
v-bind="$attrs"
|
|
||||||
:model-modifiers="modelModifiers"
|
|
||||||
class="calendar"
|
|
||||||
trim-weeks
|
|
||||||
:transition="'none'"
|
|
||||||
:columns="columns"
|
|
||||||
>
|
|
||||||
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
|
|
||||||
<slot :name="slot" v-bind="scope" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #nav-prev-button>
|
|
||||||
<ChevronLeftIcon />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #nav-next-button>
|
|
||||||
<ChevronRightIcon />
|
|
||||||
</template>
|
|
||||||
</DatePicker>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="css">
|
|
||||||
.calendar {
|
|
||||||
@apply p-3 text-center;
|
|
||||||
}
|
|
||||||
.calendar .vc-pane-layout {
|
|
||||||
@apply grid gap-4;
|
|
||||||
}
|
|
||||||
.calendar .vc-title {
|
|
||||||
@apply text-sm font-medium relative z-20;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-popover-content {
|
|
||||||
@apply mt-3 rounded-md max-w-xs border bg-background;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-nav-header {
|
|
||||||
@apply flex justify-between items-center p-2;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-nav-items {
|
|
||||||
@apply grid grid-cols-4 gap-2 p-2;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item {
|
|
||||||
@apply rounded-md px-2 py-1;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item:hover {
|
|
||||||
@apply text-muted-foreground bg-muted;
|
|
||||||
}
|
|
||||||
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item.is-active {
|
|
||||||
@apply bg-primary text-primary-foreground;
|
|
||||||
}
|
|
||||||
.calendar .vc-pane-header-wrapper {
|
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
.calendar .vc-weeks {
|
|
||||||
@apply mt-4;
|
|
||||||
}
|
|
||||||
.calendar .vc-weekdays {
|
|
||||||
@apply justify-items-center;
|
|
||||||
}
|
|
||||||
.calendar .vc-weekday {
|
|
||||||
@apply text-muted-foreground rounded-md font-normal text-[0.8rem];
|
|
||||||
}
|
|
||||||
.calendar .vc-weeks {
|
|
||||||
@apply w-full space-y-2 flex flex-col [&>_div]:grid [&>_div]:grid-cols-7;
|
|
||||||
}
|
|
||||||
.calendar .vc-day:has(.vc-highlights) {
|
|
||||||
@apply first:rounded-l-md last:rounded-r-md;
|
|
||||||
}
|
|
||||||
.calendar .vc-day.is-today:not(:has(.vc-day-layer)) .vc-day-content {
|
|
||||||
@apply bg-secondary text-primary rounded-md;
|
|
||||||
}
|
|
||||||
.calendar .vc-day:has(.vc-highlight-base-start) {
|
|
||||||
@apply rounded-l-md;
|
|
||||||
}
|
|
||||||
.calendar .vc-day:has(.vc-highlight-base-end) {
|
|
||||||
@apply rounded-r-md;
|
|
||||||
}
|
|
||||||
.calendar .vc-day:has(.vc-highlight-bg-outline):not(:has(.vc-highlight-base-start)):not(:has(.vc-highlight-base-end)) {
|
|
||||||
@apply rounded-md;
|
|
||||||
}
|
|
||||||
.calendar .vc-day-content {
|
|
||||||
@apply text-center text-sm p-0 relative focus-within:relative focus-within:z-20 inline-flex items-center justify-center ring-offset-background hover:transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground h-9 w-9 font-normal aria-selected:opacity-100 select-none;
|
|
||||||
}
|
|
||||||
.calendar .vc-day-content:not(.vc-highlight-content-light) {
|
|
||||||
@apply rounded-md;
|
|
||||||
}
|
|
||||||
.calendar .is-not-in-month:not(:has(.vc-highlight-content-solid)):not(:has(.vc-highlight-content-light)):not(:has(.vc-highlight-content-outline)),
|
|
||||||
.calendar .vc-disabled {
|
|
||||||
@apply text-muted-foreground opacity-50;
|
|
||||||
}
|
|
||||||
.calendar .vc-highlight-content-solid, .calendar .vc-highlight-content-outline {
|
|
||||||
@apply bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground;
|
|
||||||
}
|
|
||||||
.calendar .vc-highlight-content-light {
|
|
||||||
@apply bg-accent text-accent-foreground;
|
|
||||||
}
|
|
||||||
.calendar .vc-pane-container.in-transition {
|
|
||||||
@apply overflow-hidden;
|
|
||||||
}
|
|
||||||
.calendar .vc-pane-container {
|
|
||||||
@apply w-full relative;
|
|
||||||
}
|
|
||||||
:root {
|
|
||||||
--vc-slide-translate: 22px;
|
|
||||||
--vc-slide-duration: 0.15s;
|
|
||||||
--vc-slide-timing: ease;
|
|
||||||
}
|
|
||||||
.calendar .vc-fade-enter-active,
|
|
||||||
.calendar .vc-fade-leave-active,
|
|
||||||
.calendar .vc-slide-left-enter-active,
|
|
||||||
.calendar .vc-slide-left-leave-active,
|
|
||||||
.calendar .vc-slide-right-enter-active,
|
|
||||||
.calendar .vc-slide-right-leave-active,
|
|
||||||
.calendar .vc-slide-up-enter-active,
|
|
||||||
.calendar .vc-slide-up-leave-active,
|
|
||||||
.calendar .vc-slide-down-enter-active,
|
|
||||||
.calendar .vc-slide-down-leave-active,
|
|
||||||
.calendar .vc-slide-fade-enter-active,
|
|
||||||
.calendar .vc-slide-fade-leave-active {
|
|
||||||
transition:
|
|
||||||
opacity var(--vc-slide-duration) var(--vc-slide-timing),
|
|
||||||
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
|
|
||||||
transition:
|
|
||||||
transform var(--vc-slide-duration) var(--vc-slide-timing),
|
|
||||||
opacity var(--vc-slide-duration) var(--vc-slide-timing);
|
|
||||||
transition:
|
|
||||||
transform var(--vc-slide-duration) var(--vc-slide-timing),
|
|
||||||
opacity var(--vc-slide-duration) var(--vc-slide-timing),
|
|
||||||
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
|
|
||||||
-webkit-backface-visibility: hidden;
|
|
||||||
backface-visibility: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.calendar .vc-none-leave-active,
|
|
||||||
.calendar .vc-fade-leave-active,
|
|
||||||
.calendar .vc-slide-left-leave-active,
|
|
||||||
.calendar .vc-slide-right-leave-active,
|
|
||||||
.calendar .vc-slide-up-leave-active,
|
|
||||||
.calendar .vc-slide-down-leave-active {
|
|
||||||
position: absolute !important;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.calendar .vc-none-enter-from,
|
|
||||||
.calendar .vc-none-leave-to,
|
|
||||||
.calendar .vc-fade-enter-from,
|
|
||||||
.calendar .vc-fade-leave-to,
|
|
||||||
.calendar .vc-slide-left-enter-from,
|
|
||||||
.calendar .vc-slide-left-leave-to,
|
|
||||||
.calendar .vc-slide-right-enter-from,
|
|
||||||
.calendar .vc-slide-right-leave-to,
|
|
||||||
.calendar .vc-slide-up-enter-from,
|
|
||||||
.calendar .vc-slide-up-leave-to,
|
|
||||||
.calendar .vc-slide-down-enter-from,
|
|
||||||
.calendar .vc-slide-down-leave-to,
|
|
||||||
.calendar .vc-slide-fade-enter-from,
|
|
||||||
.calendar .vc-slide-fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.calendar .vc-slide-left-enter-from,
|
|
||||||
.calendar .vc-slide-right-leave-to,
|
|
||||||
.calendar .vc-slide-fade-enter-from.direction-left,
|
|
||||||
.calendar .vc-slide-fade-leave-to.direction-left {
|
|
||||||
-webkit-transform: translateX(var(--vc-slide-translate));
|
|
||||||
transform: translateX(var(--vc-slide-translate));
|
|
||||||
}
|
|
||||||
.calendar .vc-slide-right-enter-from,
|
|
||||||
.calendar .vc-slide-left-leave-to,
|
|
||||||
.calendar .vc-slide-fade-enter-from.direction-right,
|
|
||||||
.calendar .vc-slide-fade-leave-to.direction-right {
|
|
||||||
-webkit-transform: translateX(calc(-1 * var(--vc-slide-translate)));
|
|
||||||
transform: translateX(calc(-1 * var(--vc-slide-translate)));
|
|
||||||
}
|
|
||||||
.calendar .vc-slide-up-enter-from,
|
|
||||||
.calendar .vc-slide-down-leave-to,
|
|
||||||
.calendar .vc-slide-fade-enter-from.direction-top,
|
|
||||||
.calendar .vc-slide-fade-leave-to.direction-top {
|
|
||||||
-webkit-transform: translateY(var(--vc-slide-translate));
|
|
||||||
transform: translateY(var(--vc-slide-translate));
|
|
||||||
}
|
|
||||||
.calendar .vc-slide-down-enter-from,
|
|
||||||
.calendar .vc-slide-up-leave-to,
|
|
||||||
.calendar .vc-slide-fade-enter-from.direction-bottom,
|
|
||||||
.calendar .vc-slide-fade-leave-to.direction-bottom {
|
|
||||||
-webkit-transform: translateY(calc(-1 * var(--vc-slide-translate)));
|
|
||||||
transform: translateY(calc(-1 * var(--vc-slide-translate)));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Timepicker styles
|
|
||||||
*/
|
|
||||||
.vc-time-picker {
|
|
||||||
@apply flex flex-col items-center p-2;
|
|
||||||
}
|
|
||||||
.vc-time-picker.vc-invalid {
|
|
||||||
@apply pointer-events-none opacity-50;
|
|
||||||
}
|
|
||||||
.vc-time-picker.vc-attached {
|
|
||||||
@apply border-t border-solid border-secondary mt-2;
|
|
||||||
}
|
|
||||||
.vc-time-picker > * + * {
|
|
||||||
@apply mt-1;
|
|
||||||
}
|
|
||||||
.vc-time-header {
|
|
||||||
@apply flex items-center text-sm font-semibold uppercase mt-1 px-1 leading-6;
|
|
||||||
}
|
|
||||||
.vc-time-select-group {
|
|
||||||
@apply inline-flex items-center px-1 rounded-md bg-primary-foreground border border-solid border-secondary;
|
|
||||||
}
|
|
||||||
.vc-time-select-group .vc-base-icon {
|
|
||||||
@apply mr-1 text-primary stroke-primary;
|
|
||||||
}
|
|
||||||
.vc-time-select-group select {
|
|
||||||
@apply bg-primary-foreground p-1 appearance-none outline-none text-center;
|
|
||||||
}
|
|
||||||
.vc-time-weekday {
|
|
||||||
@apply text-muted-foreground tracking-wide;
|
|
||||||
}
|
|
||||||
.vc-time-month {
|
|
||||||
@apply text-primary ml-2;
|
|
||||||
}
|
|
||||||
.vc-time-day {
|
|
||||||
@apply text-primary ml-1;
|
|
||||||
}
|
|
||||||
.vc-time-year {
|
|
||||||
@apply text-muted-foreground ml-2;
|
|
||||||
}
|
|
||||||
.vc-time-colon {
|
|
||||||
@apply mb-0.5;
|
|
||||||
}
|
|
||||||
.vc-time-decimal {
|
|
||||||
@apply ml-0.5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarCell, type CalendarCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarCell
|
||||||
|
:class="cn('relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarCell>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarCellTrigger, type CalendarCellTriggerProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarCellTrigger
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'ghost' }),
|
||||||
|
'h-8 w-8 p-0 font-normal',
|
||||||
|
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground',
|
||||||
|
// Selected
|
||||||
|
'data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:opacity-100 data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-foreground',
|
||||||
|
// Disabled
|
||||||
|
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
|
||||||
|
// Unavailable
|
||||||
|
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
|
||||||
|
// Outside months
|
||||||
|
'data-[outside-month]:pointer-events-none data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground [&[data-outside-month][data-selected]]:opacity-30',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarCellTrigger>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarGrid, type CalendarGridProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGrid
|
||||||
|
:class="cn('w-full border-collapse space-y-1', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarGrid>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { CalendarGridBody, type CalendarGridBodyProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridBodyProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGridBody v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</CalendarGridBody>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { CalendarGridHead, type CalendarGridHeadProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridHeadProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGridHead v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</CalendarGridHead>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarGridRow, type CalendarGridRowProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridRowProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGridRow :class="cn('flex', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</CalendarGridRow>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarHeadCell, type CalendarHeadCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarHeadCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarHeadCell :class="cn('w-8 rounded-md text-[0.8rem] font-normal text-muted-foreground', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</CalendarHeadCell>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarHeader, type CalendarHeaderProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarHeaderProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarHeader :class="cn('relative flex w-full items-center justify-between pt-1', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</CalendarHeader>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarHeading, type CalendarHeadingProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarHeadingProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarHeading
|
||||||
|
v-slot="{ headingValue }"
|
||||||
|
:class="cn('text-sm font-medium', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot :heading-value>
|
||||||
|
{{ headingValue }}
|
||||||
|
</slot>
|
||||||
|
</CalendarHeading>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarNext, type CalendarNextProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { ChevronRightIcon } from '@radix-icons/vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarNextProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarNext
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'outline' }),
|
||||||
|
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronRightIcon class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</CalendarNext>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { CalendarPrev, type CalendarPrevProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { ChevronLeftIcon } from '@radix-icons/vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarPrevProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarPrev
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'outline' }),
|
||||||
|
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronLeftIcon class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</CalendarPrev>
|
||||||
|
</template>
|
||||||
|
|
@ -1,22 +1,12 @@
|
||||||
export { default as Calendar } from './Calendar.vue'
|
export { default as Calendar } from './Calendar.vue'
|
||||||
import type { CalendarSlotName } from 'v-calendar/dist/types/src/components/Calendar/CalendarSlot.vue.d.ts'
|
export { default as CalendarCell } from './CalendarCell.vue'
|
||||||
|
export { default as CalendarCellTrigger } from './CalendarCellTrigger.vue'
|
||||||
export function isVCalendarSlot(slotName: string): slotName is CalendarSlotName {
|
export { default as CalendarGrid } from './CalendarGrid.vue'
|
||||||
const validSlots: CalendarSlotName[] = [
|
export { default as CalendarGridBody } from './CalendarGridBody.vue'
|
||||||
'day-content',
|
export { default as CalendarGridHead } from './CalendarGridHead.vue'
|
||||||
'day-popover',
|
export { default as CalendarGridRow } from './CalendarGridRow.vue'
|
||||||
'dp-footer',
|
export { default as CalendarHeadCell } from './CalendarHeadCell.vue'
|
||||||
'footer',
|
export { default as CalendarHeader } from './CalendarHeader.vue'
|
||||||
'header-title-wrapper',
|
export { default as CalendarHeading } from './CalendarHeading.vue'
|
||||||
'header-title',
|
export { default as CalendarNextButton } from './CalendarNextButton.vue'
|
||||||
'header-prev-button',
|
export { default as CalendarPrevButton } from './CalendarPrevButton.vue'
|
||||||
'header-next-button',
|
|
||||||
'nav',
|
|
||||||
'nav-prev-button',
|
|
||||||
'nav-next-button',
|
|
||||||
'page',
|
|
||||||
'time-header',
|
|
||||||
]
|
|
||||||
|
|
||||||
return validSlots.includes(slotName as CalendarSlotName)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarRoot, type RangeCalendarRootEmits, type RangeCalendarRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import { RangeCalendarCell, RangeCalendarCellTrigger, RangeCalendarGrid, RangeCalendarGridBody, RangeCalendarGridHead, RangeCalendarGridRow, RangeCalendarHeadCell, RangeCalendarHeader, RangeCalendarHeading, RangeCalendarNextButton, RangeCalendarPrevButton } from '.'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarRootProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const emits = defineEmits<RangeCalendarRootEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarRoot
|
||||||
|
v-slot="{ grid, weekDays }"
|
||||||
|
:class="cn('p-3', props.class)"
|
||||||
|
v-bind="forwarded"
|
||||||
|
>
|
||||||
|
<RangeCalendarHeader>
|
||||||
|
<RangeCalendarPrevButton />
|
||||||
|
<RangeCalendarHeading />
|
||||||
|
<RangeCalendarNextButton />
|
||||||
|
</RangeCalendarHeader>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
|
||||||
|
<RangeCalendarGrid v-for="month in grid" :key="month.value.toString()">
|
||||||
|
<RangeCalendarGridHead>
|
||||||
|
<RangeCalendarGridRow>
|
||||||
|
<RangeCalendarHeadCell
|
||||||
|
v-for="day in weekDays" :key="day"
|
||||||
|
>
|
||||||
|
{{ day }}
|
||||||
|
</RangeCalendarHeadCell>
|
||||||
|
</RangeCalendarGridRow>
|
||||||
|
</RangeCalendarGridHead>
|
||||||
|
<RangeCalendarGridBody>
|
||||||
|
<RangeCalendarGridRow v-for="(weekDates, index) in month.rows" :key="`weekDate-${index}`" class="mt-2 w-full">
|
||||||
|
<RangeCalendarCell
|
||||||
|
v-for="weekDate in weekDates"
|
||||||
|
:key="weekDate.toString()"
|
||||||
|
:date="weekDate"
|
||||||
|
>
|
||||||
|
<RangeCalendarCellTrigger
|
||||||
|
:day="weekDate"
|
||||||
|
:month="month.value"
|
||||||
|
/>
|
||||||
|
</RangeCalendarCell>
|
||||||
|
</RangeCalendarGridRow>
|
||||||
|
</RangeCalendarGridBody>
|
||||||
|
</RangeCalendarGrid>
|
||||||
|
</div>
|
||||||
|
</RangeCalendarRoot>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarCell, type RangeCalendarCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarCell
|
||||||
|
:class="cn('relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:bg-accent first:[&:has([data-selected])]:rounded-l-md last:[&:has([data-selected])]:rounded-r-md [&:has([data-selected][data-outside-month])]:bg-accent/50 [&:has([data-selected][data-selection-end])]:rounded-r-md [&:has([data-selected][data-selection-start])]:rounded-l-md', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarCell>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarCellTrigger, type RangeCalendarCellTriggerProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarCellTrigger
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'ghost' }),
|
||||||
|
'h-9 w-9 p-0 font-normal data-[selected]:opacity-100',
|
||||||
|
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground',
|
||||||
|
// Selection Start
|
||||||
|
'data-[selection-start]:bg-primary data-[selection-start]:text-primary-foreground data-[selection-start]:hover:bg-primary data-[selection-start]:hover:text-primary-foreground data-[selection-start]:focus:bg-primary data-[selection-start]:focus:text-primary-foreground',
|
||||||
|
// Selection End
|
||||||
|
'data-[selection-end]:bg-primary data-[selection-end]:text-primary-foreground data-[selection-end]:hover:bg-primary data-[selection-end]:hover:text-primary-foreground data-[selection-end]:focus:bg-primary data-[selection-end]:focus:text-primary-foreground',
|
||||||
|
// Outside months
|
||||||
|
'data-[outside-month]:pointer-events-none data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground [&[data-outside-month][data-selected]]:opacity-30',
|
||||||
|
// Disabled
|
||||||
|
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
|
||||||
|
// Unavailable
|
||||||
|
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarCellTrigger>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarGrid, type RangeCalendarGridProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarGridProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarGrid
|
||||||
|
:class="cn('w-full border-collapse space-y-1', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarGrid>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { RangeCalendarGridBody, type RangeCalendarGridBodyProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarGridBodyProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarGridBody v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarGridBody>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { RangeCalendarGridHead, type RangeCalendarGridHeadProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarGridHeadProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarGridHead v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarGridHead>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarGridRow, type RangeCalendarGridRowProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarGridRowProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarGridRow :class="cn('flex mt-2 w-full', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarGridRow>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarHeadCell, type RangeCalendarHeadCellProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarHeadCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarHeadCell :class="cn('w-8 rounded-md text-[0.8rem] font-normal text-muted-foreground', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarHeadCell>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarHeader, type RangeCalendarHeaderProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarHeaderProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarHeader :class="cn('relative flex w-full items-center justify-between pt-1', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</RangeCalendarHeader>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarHeading, type RangeCalendarHeadingProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarHeadingProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarHeading
|
||||||
|
v-slot="{ headingValue }"
|
||||||
|
:class="cn('text-sm font-medium', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot :heading-value>
|
||||||
|
{{ headingValue }}
|
||||||
|
</slot>
|
||||||
|
</RangeCalendarHeading>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarNext, type RangeCalendarNextProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { ChevronRightIcon } from '@radix-icons/vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarNextProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarNext
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'outline' }),
|
||||||
|
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronRightIcon class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</RangeCalendarNext>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type HTMLAttributes, computed } from 'vue'
|
||||||
|
import { RangeCalendarPrev, type RangeCalendarPrevProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { ChevronLeftIcon } from '@radix-icons/vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<RangeCalendarPrevProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RangeCalendarPrev
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'outline' }),
|
||||||
|
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronLeftIcon class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</RangeCalendarPrev>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
export { default as RangeCalendar } from './RangeCalendar.vue'
|
||||||
|
export { default as RangeCalendarCell } from './RangeCalendarCell.vue'
|
||||||
|
export { default as RangeCalendarCellTrigger } from './RangeCalendarCellTrigger.vue'
|
||||||
|
export { default as RangeCalendarGrid } from './RangeCalendarGrid.vue'
|
||||||
|
export { default as RangeCalendarGridBody } from './RangeCalendarGridBody.vue'
|
||||||
|
export { default as RangeCalendarGridHead } from './RangeCalendarGridHead.vue'
|
||||||
|
export { default as RangeCalendarGridRow } from './RangeCalendarGridRow.vue'
|
||||||
|
export { default as RangeCalendarHeadCell } from './RangeCalendarHeadCell.vue'
|
||||||
|
export { default as RangeCalendarHeader } from './RangeCalendarHeader.vue'
|
||||||
|
export { default as RangeCalendarHeading } from './RangeCalendarHeading.vue'
|
||||||
|
export { default as RangeCalendarNextButton } from './RangeCalendarNextButton.vue'
|
||||||
|
export { default as RangeCalendarPrevButton } from './RangeCalendarPrevButton.vue'
|
||||||
325
apps/www/src/lib/registry/new-york/ui/v-calendar/Calendar.vue
Normal file
325
apps/www/src/lib/registry/new-york/ui/v-calendar/Calendar.vue
Normal file
|
|
@ -0,0 +1,325 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue'
|
||||||
|
import type { Calendar } from 'v-calendar'
|
||||||
|
import { DatePicker } from 'v-calendar'
|
||||||
|
import { computed, nextTick, onMounted, ref, useSlots } from 'vue'
|
||||||
|
import { isVCalendarSlot } from '.'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||||
|
|
||||||
|
/* Extracted from v-calendar */
|
||||||
|
type DatePickerModel = DatePickerDate | DatePickerRangeObject
|
||||||
|
type DateSource = Date | string | number
|
||||||
|
type DatePickerDate = DateSource | Partial<SimpleDateParts> | null
|
||||||
|
interface DatePickerRangeObject {
|
||||||
|
start: Exclude<DatePickerDate, null>
|
||||||
|
end: Exclude<DatePickerDate, null>
|
||||||
|
}
|
||||||
|
interface SimpleDateParts {
|
||||||
|
year: number
|
||||||
|
month: number
|
||||||
|
day: number
|
||||||
|
hours: number
|
||||||
|
minutes: number
|
||||||
|
seconds: number
|
||||||
|
milliseconds: number
|
||||||
|
}
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
modelValue?: string | number | Date | DatePickerModel
|
||||||
|
modelModifiers?: object
|
||||||
|
columns?: number
|
||||||
|
type?: 'single' | 'range'
|
||||||
|
}>(), {
|
||||||
|
type: 'single',
|
||||||
|
columns: 1,
|
||||||
|
})
|
||||||
|
const emits = defineEmits<{
|
||||||
|
(e: 'update:modelValue', payload: typeof props.modelValue): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||||
|
passive: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const datePicker = ref<InstanceType<typeof DatePicker>>()
|
||||||
|
// @ts-expect-error in this current version of v-calendar has the calendaRef instance, which is required to handle arrow nav.
|
||||||
|
const calendarRef = computed<InstanceType<typeof Calendar>>(() => datePicker.value.calendarRef)
|
||||||
|
|
||||||
|
function handleNav(direction: 'prev' | 'next') {
|
||||||
|
if (!calendarRef.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (direction === 'prev')
|
||||||
|
calendarRef.value.movePrev()
|
||||||
|
else calendarRef.value.moveNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick()
|
||||||
|
if (modelValue.value instanceof Date && calendarRef.value)
|
||||||
|
calendarRef.value.focusDate(modelValue.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const $slots = useSlots()
|
||||||
|
const vCalendarSlots = computed(() => {
|
||||||
|
return Object.keys($slots)
|
||||||
|
.filter(name => isVCalendarSlot(name))
|
||||||
|
.reduce((obj: Record<string, any>, key: string) => {
|
||||||
|
obj[key] = $slots[key]
|
||||||
|
return obj
|
||||||
|
}, {})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative">
|
||||||
|
<div v-if="$attrs.mode !== 'time'" class="absolute flex justify-between w-full px-4 top-3 z-[1]">
|
||||||
|
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('prev')">
|
||||||
|
<ChevronLeftIcon class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('next')">
|
||||||
|
<ChevronRightIcon class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DatePicker
|
||||||
|
ref="datePicker"
|
||||||
|
v-model="modelValue"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:model-modifiers="modelModifiers"
|
||||||
|
class="calendar"
|
||||||
|
trim-weeks
|
||||||
|
:transition="'none'"
|
||||||
|
:columns="columns"
|
||||||
|
>
|
||||||
|
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
|
||||||
|
<slot :name="slot" v-bind="scope" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #nav-prev-button>
|
||||||
|
<ChevronLeftIcon />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #nav-next-button>
|
||||||
|
<ChevronRightIcon />
|
||||||
|
</template>
|
||||||
|
</DatePicker>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
.calendar {
|
||||||
|
@apply p-3 text-center;
|
||||||
|
}
|
||||||
|
.calendar .vc-pane-layout {
|
||||||
|
@apply grid gap-4;
|
||||||
|
}
|
||||||
|
.calendar .vc-title {
|
||||||
|
@apply text-sm font-medium relative z-20;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-popover-content {
|
||||||
|
@apply mt-3 rounded-md max-w-xs border bg-background;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-nav-header {
|
||||||
|
@apply flex justify-between items-center p-2;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-nav-items {
|
||||||
|
@apply grid grid-cols-4 gap-2 p-2;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item {
|
||||||
|
@apply rounded-md px-2 py-1;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item:hover {
|
||||||
|
@apply text-muted-foreground bg-muted;
|
||||||
|
}
|
||||||
|
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item.is-active {
|
||||||
|
@apply bg-primary text-primary-foreground;
|
||||||
|
}
|
||||||
|
.calendar .vc-pane-header-wrapper {
|
||||||
|
@apply hidden;
|
||||||
|
}
|
||||||
|
.calendar .vc-weeks {
|
||||||
|
@apply mt-4;
|
||||||
|
}
|
||||||
|
.calendar .vc-weekdays {
|
||||||
|
@apply justify-items-center;
|
||||||
|
}
|
||||||
|
.calendar .vc-weekday {
|
||||||
|
@apply text-muted-foreground rounded-md font-normal text-[0.8rem];
|
||||||
|
}
|
||||||
|
.calendar .vc-weeks {
|
||||||
|
@apply w-full space-y-2 flex flex-col [&>_div]:grid [&>_div]:grid-cols-7;
|
||||||
|
}
|
||||||
|
.calendar .vc-day:has(.vc-highlights) {
|
||||||
|
@apply first:rounded-l-md last:rounded-r-md;
|
||||||
|
}
|
||||||
|
.calendar .vc-day.is-today:not(:has(.vc-day-layer)) .vc-day-content {
|
||||||
|
@apply bg-secondary text-primary rounded-md;
|
||||||
|
}
|
||||||
|
.calendar .vc-day:has(.vc-highlight-base-start) {
|
||||||
|
@apply rounded-l-md;
|
||||||
|
}
|
||||||
|
.calendar .vc-day:has(.vc-highlight-base-end) {
|
||||||
|
@apply rounded-r-md;
|
||||||
|
}
|
||||||
|
.calendar .vc-day:has(.vc-highlight-bg-outline):not(:has(.vc-highlight-base-start)):not(:has(.vc-highlight-base-end)) {
|
||||||
|
@apply rounded-md;
|
||||||
|
}
|
||||||
|
.calendar .vc-day-content {
|
||||||
|
@apply text-center text-sm p-0 relative focus-within:relative focus-within:z-20 inline-flex items-center justify-center ring-offset-background hover:transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground h-9 w-9 font-normal aria-selected:opacity-100 select-none;
|
||||||
|
}
|
||||||
|
.calendar .vc-day-content:not(.vc-highlight-content-light) {
|
||||||
|
@apply rounded-md;
|
||||||
|
}
|
||||||
|
.calendar .is-not-in-month:not(:has(.vc-highlight-content-solid)):not(:has(.vc-highlight-content-light)):not(:has(.vc-highlight-content-outline)),
|
||||||
|
.calendar .vc-disabled {
|
||||||
|
@apply text-muted-foreground opacity-50;
|
||||||
|
}
|
||||||
|
.calendar .vc-highlight-content-solid, .calendar .vc-highlight-content-outline {
|
||||||
|
@apply bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground;
|
||||||
|
}
|
||||||
|
.calendar .vc-highlight-content-light {
|
||||||
|
@apply bg-accent text-accent-foreground;
|
||||||
|
}
|
||||||
|
.calendar .vc-pane-container.in-transition {
|
||||||
|
@apply overflow-hidden;
|
||||||
|
}
|
||||||
|
.calendar .vc-pane-container {
|
||||||
|
@apply w-full relative;
|
||||||
|
}
|
||||||
|
:root {
|
||||||
|
--vc-slide-translate: 22px;
|
||||||
|
--vc-slide-duration: 0.15s;
|
||||||
|
--vc-slide-timing: ease;
|
||||||
|
}
|
||||||
|
.calendar .vc-fade-enter-active,
|
||||||
|
.calendar .vc-fade-leave-active,
|
||||||
|
.calendar .vc-slide-left-enter-active,
|
||||||
|
.calendar .vc-slide-left-leave-active,
|
||||||
|
.calendar .vc-slide-right-enter-active,
|
||||||
|
.calendar .vc-slide-right-leave-active,
|
||||||
|
.calendar .vc-slide-up-enter-active,
|
||||||
|
.calendar .vc-slide-up-leave-active,
|
||||||
|
.calendar .vc-slide-down-enter-active,
|
||||||
|
.calendar .vc-slide-down-leave-active,
|
||||||
|
.calendar .vc-slide-fade-enter-active,
|
||||||
|
.calendar .vc-slide-fade-leave-active {
|
||||||
|
transition:
|
||||||
|
opacity var(--vc-slide-duration) var(--vc-slide-timing),
|
||||||
|
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
|
||||||
|
transition:
|
||||||
|
transform var(--vc-slide-duration) var(--vc-slide-timing),
|
||||||
|
opacity var(--vc-slide-duration) var(--vc-slide-timing);
|
||||||
|
transition:
|
||||||
|
transform var(--vc-slide-duration) var(--vc-slide-timing),
|
||||||
|
opacity var(--vc-slide-duration) var(--vc-slide-timing),
|
||||||
|
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.calendar .vc-none-leave-active,
|
||||||
|
.calendar .vc-fade-leave-active,
|
||||||
|
.calendar .vc-slide-left-leave-active,
|
||||||
|
.calendar .vc-slide-right-leave-active,
|
||||||
|
.calendar .vc-slide-up-leave-active,
|
||||||
|
.calendar .vc-slide-down-leave-active {
|
||||||
|
position: absolute !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.calendar .vc-none-enter-from,
|
||||||
|
.calendar .vc-none-leave-to,
|
||||||
|
.calendar .vc-fade-enter-from,
|
||||||
|
.calendar .vc-fade-leave-to,
|
||||||
|
.calendar .vc-slide-left-enter-from,
|
||||||
|
.calendar .vc-slide-left-leave-to,
|
||||||
|
.calendar .vc-slide-right-enter-from,
|
||||||
|
.calendar .vc-slide-right-leave-to,
|
||||||
|
.calendar .vc-slide-up-enter-from,
|
||||||
|
.calendar .vc-slide-up-leave-to,
|
||||||
|
.calendar .vc-slide-down-enter-from,
|
||||||
|
.calendar .vc-slide-down-leave-to,
|
||||||
|
.calendar .vc-slide-fade-enter-from,
|
||||||
|
.calendar .vc-slide-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.calendar .vc-slide-left-enter-from,
|
||||||
|
.calendar .vc-slide-right-leave-to,
|
||||||
|
.calendar .vc-slide-fade-enter-from.direction-left,
|
||||||
|
.calendar .vc-slide-fade-leave-to.direction-left {
|
||||||
|
-webkit-transform: translateX(var(--vc-slide-translate));
|
||||||
|
transform: translateX(var(--vc-slide-translate));
|
||||||
|
}
|
||||||
|
.calendar .vc-slide-right-enter-from,
|
||||||
|
.calendar .vc-slide-left-leave-to,
|
||||||
|
.calendar .vc-slide-fade-enter-from.direction-right,
|
||||||
|
.calendar .vc-slide-fade-leave-to.direction-right {
|
||||||
|
-webkit-transform: translateX(calc(-1 * var(--vc-slide-translate)));
|
||||||
|
transform: translateX(calc(-1 * var(--vc-slide-translate)));
|
||||||
|
}
|
||||||
|
.calendar .vc-slide-up-enter-from,
|
||||||
|
.calendar .vc-slide-down-leave-to,
|
||||||
|
.calendar .vc-slide-fade-enter-from.direction-top,
|
||||||
|
.calendar .vc-slide-fade-leave-to.direction-top {
|
||||||
|
-webkit-transform: translateY(var(--vc-slide-translate));
|
||||||
|
transform: translateY(var(--vc-slide-translate));
|
||||||
|
}
|
||||||
|
.calendar .vc-slide-down-enter-from,
|
||||||
|
.calendar .vc-slide-up-leave-to,
|
||||||
|
.calendar .vc-slide-fade-enter-from.direction-bottom,
|
||||||
|
.calendar .vc-slide-fade-leave-to.direction-bottom {
|
||||||
|
-webkit-transform: translateY(calc(-1 * var(--vc-slide-translate)));
|
||||||
|
transform: translateY(calc(-1 * var(--vc-slide-translate)));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Timepicker styles
|
||||||
|
*/
|
||||||
|
.vc-time-picker {
|
||||||
|
@apply flex flex-col items-center p-2;
|
||||||
|
}
|
||||||
|
.vc-time-picker.vc-invalid {
|
||||||
|
@apply pointer-events-none opacity-50;
|
||||||
|
}
|
||||||
|
.vc-time-picker.vc-attached {
|
||||||
|
@apply border-t border-solid border-secondary mt-2;
|
||||||
|
}
|
||||||
|
.vc-time-picker > * + * {
|
||||||
|
@apply mt-1;
|
||||||
|
}
|
||||||
|
.vc-time-header {
|
||||||
|
@apply flex items-center text-sm font-semibold uppercase mt-1 px-1 leading-6;
|
||||||
|
}
|
||||||
|
.vc-time-select-group {
|
||||||
|
@apply inline-flex items-center px-1 rounded-md bg-primary-foreground border border-solid border-secondary;
|
||||||
|
}
|
||||||
|
.vc-time-select-group .vc-base-icon {
|
||||||
|
@apply mr-1 text-primary stroke-primary;
|
||||||
|
}
|
||||||
|
.vc-time-select-group select {
|
||||||
|
@apply bg-primary-foreground p-1 appearance-none outline-none text-center;
|
||||||
|
}
|
||||||
|
.vc-time-weekday {
|
||||||
|
@apply text-muted-foreground tracking-wide;
|
||||||
|
}
|
||||||
|
.vc-time-month {
|
||||||
|
@apply text-primary ml-2;
|
||||||
|
}
|
||||||
|
.vc-time-day {
|
||||||
|
@apply text-primary ml-1;
|
||||||
|
}
|
||||||
|
.vc-time-year {
|
||||||
|
@apply text-muted-foreground ml-2;
|
||||||
|
}
|
||||||
|
.vc-time-colon {
|
||||||
|
@apply mb-0.5;
|
||||||
|
}
|
||||||
|
.vc-time-decimal {
|
||||||
|
@apply ml-0.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
22
apps/www/src/lib/registry/new-york/ui/v-calendar/index.ts
Normal file
22
apps/www/src/lib/registry/new-york/ui/v-calendar/index.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
export { default as Calendar } from './Calendar.vue'
|
||||||
|
import type { CalendarSlotName } from 'v-calendar/dist/types/src/components/Calendar/CalendarSlot.vue.d.ts'
|
||||||
|
|
||||||
|
export function isVCalendarSlot(slotName: string): slotName is CalendarSlotName {
|
||||||
|
const validSlots: CalendarSlotName[] = [
|
||||||
|
'day-content',
|
||||||
|
'day-popover',
|
||||||
|
'dp-footer',
|
||||||
|
'footer',
|
||||||
|
'header-title-wrapper',
|
||||||
|
'header-title',
|
||||||
|
'header-prev-button',
|
||||||
|
'header-next-button',
|
||||||
|
'nav',
|
||||||
|
'nav-prev-button',
|
||||||
|
'nav-next-button',
|
||||||
|
'page',
|
||||||
|
'time-header',
|
||||||
|
]
|
||||||
|
|
||||||
|
return validSlots.includes(slotName as CalendarSlotName)
|
||||||
|
}
|
||||||
|
|
@ -89,4 +89,4 @@
|
||||||
},
|
},
|
||||||
"inlineColorsTemplate": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n",
|
"inlineColorsTemplate": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n",
|
||||||
"cssVarsTemplate": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n \n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 224 71.4% 4.1%;\n \n --muted: 220 14.3% 95.9%;\n --muted-foreground: 220 8.9% 46.1%;\n \n --popover: 0 0% 100%;\n --popover-foreground: 224 71.4% 4.1%;\n \n --card: 0 0% 100%;\n --card-foreground: 224 71.4% 4.1%;\n \n --border: 220 13% 91%;\n --input: 220 13% 91%;\n \n --primary: 220.9 39.3% 11%;\n --primary-foreground: 210 20% 98%;\n \n --secondary: 220 14.3% 95.9%;\n --secondary-foreground: 220.9 39.3% 11%;\n \n --accent: 220 14.3% 95.9%;\n --accent-foreground: 220.9 39.3% 11%;\n \n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 20% 98%;\n \n --ring: 224 71.4% 4.1%;\n \n --radius: 0.5rem;\n }\n \n .dark {\n --background: 224 71.4% 4.1%;\n --foreground: 210 20% 98%;\n \n --muted: 215 27.9% 16.9%;\n --muted-foreground: 217.9 10.6% 64.9%;\n \n --popover: 224 71.4% 4.1%;\n --popover-foreground: 210 20% 98%;\n \n --card: 224 71.4% 4.1%;\n --card-foreground: 210 20% 98%;\n \n --border: 215 27.9% 16.9%;\n --input: 215 27.9% 16.9%;\n \n --primary: 210 20% 98%;\n --primary-foreground: 220.9 39.3% 11%;\n \n --secondary: 215 27.9% 16.9%;\n --secondary-foreground: 210 20% 98%;\n \n --accent: 215 27.9% 16.9%;\n --accent-foreground: 210 20% 98%;\n \n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 210 20% 98%;\n \n --ring: 216 12.2% 83.9%;\n }\n}\n \n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}"
|
"cssVarsTemplate": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n \n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 224 71.4% 4.1%;\n \n --muted: 220 14.3% 95.9%;\n --muted-foreground: 220 8.9% 46.1%;\n \n --popover: 0 0% 100%;\n --popover-foreground: 224 71.4% 4.1%;\n \n --card: 0 0% 100%;\n --card-foreground: 224 71.4% 4.1%;\n \n --border: 220 13% 91%;\n --input: 220 13% 91%;\n \n --primary: 220.9 39.3% 11%;\n --primary-foreground: 210 20% 98%;\n \n --secondary: 220 14.3% 95.9%;\n --secondary-foreground: 220.9 39.3% 11%;\n \n --accent: 220 14.3% 95.9%;\n --accent-foreground: 220.9 39.3% 11%;\n \n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 20% 98%;\n \n --ring: 224 71.4% 4.1%;\n \n --radius: 0.5rem;\n }\n \n .dark {\n --background: 224 71.4% 4.1%;\n --foreground: 210 20% 98%;\n \n --muted: 215 27.9% 16.9%;\n --muted-foreground: 217.9 10.6% 64.9%;\n \n --popover: 224 71.4% 4.1%;\n --popover-foreground: 210 20% 98%;\n \n --card: 224 71.4% 4.1%;\n --card-foreground: 210 20% 98%;\n \n --border: 215 27.9% 16.9%;\n --input: 215 27.9% 16.9%;\n \n --primary: 210 20% 98%;\n --primary-foreground: 220.9 39.3% 11%;\n \n --secondary: 215 27.9% 16.9%;\n --secondary-foreground: 210 20% 98%;\n \n --accent: 215 27.9% 16.9%;\n --accent-foreground: 210 20% 98%;\n \n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 210 20% 98%;\n \n --ring: 216 12.2% 83.9%;\n }\n}\n \n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}"
|
||||||
}
|
}
|
||||||
|
|
@ -1996,4 +1996,4 @@
|
||||||
"hslChannel": "343.1 87.7% 15.9%"
|
"hslChannel": "343.1 87.7% 15.9%"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user