feat: new calendar components (#468)
This commit is contained in:
parent
11d77fb9a1
commit
cfccb8e510
|
|
@ -1,6 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { addDays, startOfToday } from 'date-fns'
|
||||
import { type Ref, ref } from 'vue'
|
||||
import type { DateRange } from 'radix-vue'
|
||||
import { getLocalTimeZone, today } from '@internationalized/date'
|
||||
|
||||
import ThemingLayout from './../../layout/ThemingLayout.vue'
|
||||
|
||||
import CookieSettings from '@/examples/cards/components/CookieSettings.vue'
|
||||
|
|
@ -19,12 +21,14 @@ import CardStats from '@/lib/registry/new-york/example/CardStats.vue'
|
|||
import {
|
||||
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({
|
||||
start: startOfToday(),
|
||||
end: addDays(startOfToday(), 8),
|
||||
})
|
||||
start: now,
|
||||
end: now.add({ days: 8 }),
|
||||
}) as Ref<DateRange>
|
||||
</script>
|
||||
|
||||
<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="hidden gap-1 sm:grid-cols-[280px_1fr] md:grid">
|
||||
<Card class="max-w-[280px]">
|
||||
<Calendar v-model.range="range" />
|
||||
<RangeCalendar v-model="range" />
|
||||
</Card>
|
||||
|
||||
<div class="pt-3 sm:pl-2 sm:pt-0 xl:pl-3">
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ export const docsConfig: DocsConfig = {
|
|||
title: 'Contribution',
|
||||
href: '/docs/contribution',
|
||||
items: [],
|
||||
label: 'New',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -172,7 +171,6 @@ export const docsConfig: DocsConfig = {
|
|||
title: 'Breadcrumb',
|
||||
href: '/docs/components/breadcrumb',
|
||||
items: [],
|
||||
label: 'New',
|
||||
},
|
||||
{
|
||||
title: 'Button',
|
||||
|
|
@ -183,6 +181,7 @@ export const docsConfig: DocsConfig = {
|
|||
title: 'Calendar',
|
||||
href: '/docs/components/calendar',
|
||||
items: [],
|
||||
label: 'Updated',
|
||||
},
|
||||
{
|
||||
title: 'Card',
|
||||
|
|
@ -192,7 +191,6 @@ export const docsConfig: DocsConfig = {
|
|||
{
|
||||
title: 'Carousel',
|
||||
href: '/docs/components/carousel',
|
||||
label: 'New',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
|
|
@ -229,6 +227,7 @@ export const docsConfig: DocsConfig = {
|
|||
title: 'Date Picker',
|
||||
href: '/docs/components/date-picker',
|
||||
items: [],
|
||||
label: 'Updated',
|
||||
},
|
||||
{
|
||||
title: 'Dialog',
|
||||
|
|
@ -239,7 +238,6 @@ export const docsConfig: DocsConfig = {
|
|||
title: 'Drawer',
|
||||
href: '/docs/components/drawer',
|
||||
items: [],
|
||||
label: 'New',
|
||||
},
|
||||
{
|
||||
title: 'Dropdown Menu',
|
||||
|
|
@ -284,7 +282,6 @@ export const docsConfig: DocsConfig = {
|
|||
{
|
||||
title: 'Pin Input',
|
||||
href: '/docs/components/pin-input',
|
||||
label: 'New',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
|
|
@ -302,10 +299,15 @@ export const docsConfig: DocsConfig = {
|
|||
href: '/docs/components/radio-group',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Range Calendar',
|
||||
href: '/docs/components/range-calendar',
|
||||
items: [],
|
||||
label: 'New',
|
||||
},
|
||||
{
|
||||
title: 'Resizable',
|
||||
href: '/docs/components/resizable',
|
||||
label: 'New',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
|
|
@ -341,7 +343,6 @@ export const docsConfig: DocsConfig = {
|
|||
{
|
||||
title: 'Sonner',
|
||||
href: '/docs/components/sonner',
|
||||
label: 'New',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
|
|
@ -362,7 +363,6 @@ export const docsConfig: DocsConfig = {
|
|||
{
|
||||
title: 'Tags Input',
|
||||
href: '/docs/components/tags-input',
|
||||
label: 'New',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -192,6 +192,20 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/CalendarDemo.vue").then((m) => m.default),
|
||||
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": {
|
||||
name: "CardChat",
|
||||
type: "components:example",
|
||||
|
|
@ -398,38 +412,31 @@ export const Index = {
|
|||
"DatePickerDemo": {
|
||||
name: "DatePickerDemo",
|
||||
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),
|
||||
files: ["../src/lib/registry/default/example/DatePickerDemo.vue"],
|
||||
},
|
||||
"DatePickerForm": {
|
||||
name: "DatePickerForm",
|
||||
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),
|
||||
files: ["../src/lib/registry/default/example/DatePickerForm.vue"],
|
||||
},
|
||||
"DatePickerWithPresets": {
|
||||
name: "DatePickerWithPresets",
|
||||
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),
|
||||
files: ["../src/lib/registry/default/example/DatePickerWithPresets.vue"],
|
||||
},
|
||||
"DatePickerWithRange": {
|
||||
name: "DatePickerWithRange",
|
||||
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),
|
||||
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": {
|
||||
name: "DialogCustomCloseButton",
|
||||
type: "components:example",
|
||||
|
|
@ -654,12 +661,12 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/RadioGroupForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/RadioGroupForm.vue"],
|
||||
},
|
||||
"RangePickerWithSlot": {
|
||||
name: "RangePickerWithSlot",
|
||||
"RangeCalendarDemo": {
|
||||
name: "RangeCalendarDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["utils","button","calendar","popover"],
|
||||
component: () => import("../src/lib/registry/default/example/RangePickerWithSlot.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/RangePickerWithSlot.vue"],
|
||||
registryDependencies: ["range-calendar"],
|
||||
component: () => import("../src/lib/registry/default/example/RangeCalendarDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/RangeCalendarDemo.vue"],
|
||||
},
|
||||
"ResizableDemo": {
|
||||
name: "ResizableDemo",
|
||||
|
|
@ -1081,6 +1088,55 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/TypographyTable.vue").then((m) => m.default),
|
||||
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": {
|
||||
name: "ActivityGoal",
|
||||
type: "components:example",
|
||||
|
|
@ -1369,6 +1425,20 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/CalendarDemo.vue").then((m) => m.default),
|
||||
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": {
|
||||
name: "CardChat",
|
||||
type: "components:example",
|
||||
|
|
@ -1575,38 +1645,31 @@ export const Index = {
|
|||
"DatePickerDemo": {
|
||||
name: "DatePickerDemo",
|
||||
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),
|
||||
files: ["../src/lib/registry/new-york/example/DatePickerDemo.vue"],
|
||||
},
|
||||
"DatePickerForm": {
|
||||
name: "DatePickerForm",
|
||||
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),
|
||||
files: ["../src/lib/registry/new-york/example/DatePickerForm.vue"],
|
||||
},
|
||||
"DatePickerWithPresets": {
|
||||
name: "DatePickerWithPresets",
|
||||
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),
|
||||
files: ["../src/lib/registry/new-york/example/DatePickerWithPresets.vue"],
|
||||
},
|
||||
"DatePickerWithRange": {
|
||||
name: "DatePickerWithRange",
|
||||
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),
|
||||
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": {
|
||||
name: "DialogCustomCloseButton",
|
||||
type: "components:example",
|
||||
|
|
@ -1831,12 +1894,12 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/RadioGroupForm.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/RadioGroupForm.vue"],
|
||||
},
|
||||
"RangePickerWithSlot": {
|
||||
name: "RangePickerWithSlot",
|
||||
"RangeCalendarDemo": {
|
||||
name: "RangeCalendarDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["utils","button","calendar","popover"],
|
||||
component: () => import("../src/lib/registry/new-york/example/RangePickerWithSlot.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/RangePickerWithSlot.vue"],
|
||||
registryDependencies: ["range-calendar"],
|
||||
component: () => import("../src/lib/registry/new-york/example/RangeCalendarDemo.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/RangeCalendarDemo.vue"],
|
||||
},
|
||||
"ResizableDemo": {
|
||||
name: "ResizableDemo",
|
||||
|
|
@ -2258,6 +2321,55 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/TypographyTable.vue").then((m) => m.default),
|
||||
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": {
|
||||
name: "ActivityGoal",
|
||||
type: "components:example",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.1",
|
||||
"@internationalized/date": "^3.5.2",
|
||||
"@radix-icons/vue": "^1.0.0",
|
||||
"@stackblitz/sdk": "^1.9.0",
|
||||
"@tanstack/vue-table": "^8.14.0",
|
||||
|
|
@ -32,7 +33,7 @@
|
|||
"embla-carousel-vue": "^8.0.0",
|
||||
"lucide-vue-next": "^0.359.0",
|
||||
"magic-string": "^0.30.8",
|
||||
"radix-vue": "^1.6.2",
|
||||
"radix-vue": "^1.7.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"v-calendar": "^3.1.2",
|
||||
"vaul-vue": "^0.1.0",
|
||||
|
|
@ -67,8 +68,8 @@
|
|||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.4.2",
|
||||
"unplugin-icons": "^0.18.5",
|
||||
"vite": "^5.2.2",
|
||||
"vitepress": "^1.0.0-rc.45",
|
||||
"vite": "^5.2.7",
|
||||
"vitepress": "^1.0.1",
|
||||
"vue-tsc": "^2.0.7"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,90 +2,41 @@
|
|||
title: Calendar
|
||||
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/
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
<TabPreview name="CLI">
|
||||
<template #CLI>
|
||||
|
||||
```bash
|
||||
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
|
||||
npm install v-calendar
|
||||
```
|
||||
### Form
|
||||
|
||||
### 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>
|
||||
</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>
|
||||
```
|
||||
<ComponentPreview name="CalendarWithSelect" />
|
||||
|
|
|
|||
|
|
@ -1,79 +1,38 @@
|
|||
---
|
||||
title: Date Picker
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
## 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>
|
||||
```
|
||||
See installations instructions for the [Popover](/docs/components/popover), [Calendar](/docs/components/calendar), and [Range Calendar](/docs/components/range-calendar) components.
|
||||
|
||||
## Examples
|
||||
|
||||
### Date Picker
|
||||
|
||||
<ComponentPreview name="DatePickerDemo" />
|
||||
<ComponentPreview name="DatePickerDemo" />
|
||||
|
||||
### Date Range Picker
|
||||
|
||||
<ComponentPreview name="DatePickerWithRange" />
|
||||
|
||||
### Date Time Picker
|
||||
|
||||
<ComponentPreview name="DateTimePickerDemo" />
|
||||
<ComponentPreview name="DatePickerWithRange" />
|
||||
|
||||
### With Presets
|
||||
|
||||
<ComponentPreview name="DatePickerWithPresets" />
|
||||
|
||||
### With Slot
|
||||
|
||||
<ComponentPreview name="RangePickerWithSlot" />
|
||||
<ComponentPreview name="DatePickerWithPresets" />
|
||||
|
||||
### 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">
|
||||
import { addDays, format } from 'date-fns'
|
||||
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 { 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 {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/lib/registry/new-york/ui/popover'
|
||||
|
||||
const date = ref({
|
||||
start: new Date(2023, 0, 20),
|
||||
end: addDays(new Date(2023, 0, 20), 20),
|
||||
const df = new DateFormatter('en-US', {
|
||||
dateStyle: 'medium',
|
||||
})
|
||||
|
||||
const calendarDate = new CalendarDate(2023, 0, 20)
|
||||
|
||||
const value = ref({
|
||||
start: calendarDate,
|
||||
end: calendarDate.add({ days: 20 }),
|
||||
}) as Ref<DateRange>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -26,24 +33,34 @@ const date = ref({
|
|||
id="date"
|
||||
:variant="'outline'"
|
||||
:class="cn(
|
||||
'w-[260px] justify-start text-left font-normal',
|
||||
!date && 'text-muted-foreground',
|
||||
'w-[300px] justify-start text-left font-normal',
|
||||
!value && '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>
|
||||
<template v-if="value.start">
|
||||
<template v-if="value.end">
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{ df.format(value.end.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
Pick a date
|
||||
</template>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0" :align="'end'">
|
||||
<Calendar
|
||||
v-model.range="date"
|
||||
:columns="2"
|
||||
<PopoverContent class="w-auto p-0" align="end">
|
||||
<RangeCalendar
|
||||
v-model="value"
|
||||
weekday-format="short"
|
||||
:number-of-months="2"
|
||||
initial-focus
|
||||
:placeholder="value.start"
|
||||
@update:start-value="(startDate) => value.start = startDate"
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { h, ref } from 'vue'
|
||||
import * as z from 'zod'
|
||||
import { format } from 'date-fns'
|
||||
import { toDate } from 'radix-vue'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { Check, ChevronsUpDown } from 'lucide-vue-next'
|
||||
import { CalendarDate, DateFormatter, getLocalTimeZone, today } from '@internationalized/date'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
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 { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||
import {
|
||||
|
|
@ -18,17 +19,19 @@ import {
|
|||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from '@/lib/registry/default/ui/command'
|
||||
} from '@/lib/registry/new-york/ui/command'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/lib/registry/default/ui/popover'
|
||||
} from '@/lib/registry/new-york/ui/popover'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||
|
||||
const open = ref(false)
|
||||
const dateValue = ref()
|
||||
const placeholder = ref()
|
||||
|
||||
const languages = [
|
||||
{ label: 'English', value: 'en' },
|
||||
|
|
@ -42,25 +45,25 @@ const languages = [
|
|||
{ label: 'Chinese', value: 'zh' },
|
||||
] as const
|
||||
|
||||
const df = new DateFormatter('en-US', {
|
||||
dateStyle: 'long',
|
||||
})
|
||||
|
||||
const accountFormSchema = toTypedSchema(z.object({
|
||||
name: z
|
||||
.string()
|
||||
.string({
|
||||
required_error: 'Required.',
|
||||
})
|
||||
.min(2, {
|
||||
message: 'Name must be at least 2 characters.',
|
||||
})
|
||||
.max(30, {
|
||||
message: 'Name must not be longer than 30 characters.',
|
||||
}),
|
||||
dob: z.date({
|
||||
required_error: 'A date of birth is required.',
|
||||
}),
|
||||
language: z.string().nonempty({
|
||||
message: 'Please select a language.',
|
||||
}),
|
||||
dob: z.string().datetime().optional().refine(date => date !== undefined, 'Please select a valid date.'),
|
||||
language: z.string().min(1, '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/discussions/3571
|
||||
async function onSubmit(values: any) {
|
||||
|
|
@ -95,7 +98,7 @@ async function onSubmit(values: any) {
|
|||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ componentField, value }" name="dob">
|
||||
<FormField v-slot="{ field, value }" name="dob">
|
||||
<FormItem class="flex flex-col">
|
||||
<FormLabel>Date of birth</FormLabel>
|
||||
<Popover>
|
||||
|
|
@ -103,17 +106,38 @@ async function onSubmit(values: any) {
|
|||
<FormControl>
|
||||
<Button
|
||||
variant="outline" :class="cn(
|
||||
'w-[280px] pl-3 text-left font-normal',
|
||||
'w-[240px] justify-start text-left font-normal',
|
||||
!value && 'text-muted-foreground',
|
||||
)"
|
||||
>
|
||||
<span>{{ value ? format(value, "PPP") : "Pick a date" }}</span>
|
||||
<RadixIconsCalendar class="ml-auto h-4 w-4 opacity-50" />
|
||||
<RadixIconsCalendar class="mr-2 h-4 w-4 opacity-50" />
|
||||
<span>{{ value ? df.format(toDate(dateValue, getLocalTimeZone())) : "Pick a date" }}</span>
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<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>
|
||||
</Popover>
|
||||
<FormDescription>
|
||||
|
|
@ -121,6 +145,7 @@ async function onSubmit(values: any) {
|
|||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
<input type="hidden" v-bind="field">
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ value }" name="language">
|
||||
|
|
@ -155,7 +180,7 @@ async function onSubmit(values: any) {
|
|||
@select="() => {
|
||||
setValues({
|
||||
language: language.value,
|
||||
})
|
||||
}, false)
|
||||
open = false
|
||||
}"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
<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'
|
||||
|
||||
const date = ref(new Date())
|
||||
const value = ref(today(getLocalTimeZone())) as Ref<DateValue>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Calendar v-model="date" class="rounded-md border" />
|
||||
<Calendar v-model="value" :weekday-format="'short'" class="rounded-md border" />
|
||||
</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">
|
||||
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/calendar'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/lib/registry/default/ui/popover'
|
||||
DateFormatter,
|
||||
type DateValue,
|
||||
getLocalTimeZone,
|
||||
} 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>
|
||||
|
||||
<template>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
:variant="'outline'"
|
||||
variant="outline"
|
||||
:class="cn(
|
||||
'w-[280px] justify-start text-left font-normal',
|
||||
!date && 'text-muted-foreground',
|
||||
!value && 'text-muted-foreground',
|
||||
)"
|
||||
>
|
||||
<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>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<Calendar v-model="date" />
|
||||
<Calendar v-model="value" initial-focus />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { format } from 'date-fns'
|
||||
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 * as z from 'zod'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import { z } from 'zod'
|
||||
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
|
|
@ -17,22 +16,32 @@ import {
|
|||
FormLabel,
|
||||
FormMessage,
|
||||
} 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 { cn } from '@/lib/utils'
|
||||
|
||||
const df = new DateFormatter('en-US', {
|
||||
dateStyle: 'long',
|
||||
})
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
dob: z.date({
|
||||
required_error: 'A date of birth is required.',
|
||||
}),
|
||||
dob: z
|
||||
.string()
|
||||
.refine(v => v, { message: 'A date of birth is required.' }),
|
||||
}))
|
||||
|
||||
const { handleSubmit } = useForm({
|
||||
const placeholder = ref()
|
||||
|
||||
const { handleSubmit, setValues, values } = useForm({
|
||||
validationSchema: formSchema,
|
||||
initialValues: {
|
||||
|
||||
},
|
||||
})
|
||||
|
||||
const value = computed({
|
||||
get: () => values.dob ? parseDate(values.dob) : undefined,
|
||||
set: val => val,
|
||||
})
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
|
|
@ -45,7 +54,7 @@ const onSubmit = handleSubmit((values) => {
|
|||
|
||||
<template>
|
||||
<form class="space-y-8" @submit="onSubmit">
|
||||
<FormField v-slot="{ componentField, value }" name="dob">
|
||||
<FormField name="dob">
|
||||
<FormItem class="flex flex-col">
|
||||
<FormLabel>Date of birth</FormLabel>
|
||||
<Popover>
|
||||
|
|
@ -57,13 +66,34 @@ const onSubmit = handleSubmit((values) => {
|
|||
!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" />
|
||||
</Button>
|
||||
<input hidden>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="p-0">
|
||||
<Calendar v-bind="componentField" />
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,31 @@
|
|||
<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/calendar'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/lib/registry/default/ui/popover'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/lib/registry/default/ui/select'
|
||||
DateFormatter,
|
||||
type DateValue,
|
||||
getLocalTimeZone,
|
||||
today,
|
||||
} 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 { 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>
|
||||
|
||||
<template>
|
||||
|
|
@ -29,45 +35,30 @@ const date = ref<Date>()
|
|||
variant="outline"
|
||||
:class="cn(
|
||||
'w-[280px] justify-start text-left font-normal',
|
||||
!date && 'text-muted-foreground',
|
||||
!value && '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>
|
||||
{{ value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date" }}
|
||||
</Button>
|
||||
</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
|
||||
@update:model-value="(value) => {
|
||||
date = addDays(new Date(), parseInt(value))
|
||||
@update:model-value="(v) => {
|
||||
if (!v) return;
|
||||
value = today(getLocalTimeZone()).add({ days: Number(v) });
|
||||
}"
|
||||
>
|
||||
<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
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in items" :key="item.value" :value="item.value.toString()">
|
||||
{{ item.label }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div class="rounded-md border">
|
||||
<Calendar v-model="date" mode="single" />
|
||||
</div>
|
||||
<Calendar v-model="value" />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,51 +1,55 @@
|
|||
<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/calendar'
|
||||
import { type Ref, ref } from 'vue'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/lib/registry/default/ui/popover'
|
||||
CalendarDate,
|
||||
DateFormatter,
|
||||
getLocalTimeZone,
|
||||
} from '@internationalized/date'
|
||||
|
||||
const date = ref({
|
||||
start: new Date(2022, 0, 20),
|
||||
end: addDays(new Date(2022, 0, 20), 20),
|
||||
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||
import type { DateRange } from 'radix-vue'
|
||||
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>
|
||||
|
||||
<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" />
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
:class="cn(
|
||||
'w-[280px] justify-start text-left font-normal',
|
||||
!value && 'text-muted-foreground',
|
||||
)"
|
||||
>
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
<template v-if="value.start">
|
||||
<template v-if="value.end">
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{ df.format(value.end.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
|
||||
<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 v-else>
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
Pick a date
|
||||
</template>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<RangeCalendar v-model="value" initial-focus :number-of-months="2" :placeholder="value?.start" @update:start-value="(startDate) => value.start = startDate" />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</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 { 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/v-calendar'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
|
|
@ -5,7 +5,7 @@ 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 { Calendar } from '@/lib/registry/default/ui/v-calendar'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
|
|
@ -1,331 +1,60 @@
|
|||
<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 '.'
|
||||
<script lang="ts" setup>
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNextButton, CalendarPrevButton } 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
|
||||
}
|
||||
const props = defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
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 emits = defineEmits<CalendarRootEmits>()
|
||||
|
||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
passive: true,
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
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
|
||||
}, {})
|
||||
})
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</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')" type="button" @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')" type="button" @click="handleNav('next')">
|
||||
<ChevronRight class="w-4 h-4" />
|
||||
</button>
|
||||
<CalendarRoot
|
||||
v-slot="{ grid, weekDays }"
|
||||
:class="cn('p-3', props.class)"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<CalendarHeader>
|
||||
<CalendarPrevButton />
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</CalendarRoot>
|
||||
</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'
|
||||
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)
|
||||
}
|
||||
export { default as CalendarCell } from './CalendarCell.vue'
|
||||
export { default as CalendarCellTrigger } from './CalendarCellTrigger.vue'
|
||||
export { default as CalendarGrid } from './CalendarGrid.vue'
|
||||
export { default as CalendarGridBody } from './CalendarGridBody.vue'
|
||||
export { default as CalendarGridHead } from './CalendarGridHead.vue'
|
||||
export { default as CalendarGridRow } from './CalendarGridRow.vue'
|
||||
export { default as CalendarHeadCell } from './CalendarHeadCell.vue'
|
||||
export { default as CalendarHeader } from './CalendarHeader.vue'
|
||||
export { default as CalendarHeading } from './CalendarHeading.vue'
|
||||
export { default as CalendarNextButton } from './CalendarNextButton.vue'
|
||||
export { default as CalendarPrevButton } from './CalendarPrevButton.vue'
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
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'
|
||||
|
||||
const date = ref(new Date())
|
||||
const value = ref(today(getLocalTimeZone())) as Ref<DateValue>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Calendar v-model="date" class="rounded-md border" />
|
||||
<Calendar v-model="value" :weekday-format="'short'" class="rounded-md border" />
|
||||
</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>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<Card class="h-full">
|
||||
<CardHeader class="pb-4">
|
||||
<CardTitle class="text-base">
|
||||
Move Goal
|
||||
|
|
|
|||
|
|
@ -1,36 +1,40 @@
|
|||
<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/calendar'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/lib/registry/new-york/ui/popover'
|
||||
DateFormatter,
|
||||
type DateValue,
|
||||
getLocalTimeZone,
|
||||
} 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>
|
||||
|
||||
<template>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
:variant="'outline'"
|
||||
variant="outline"
|
||||
:class="cn(
|
||||
'w-[280px] justify-start text-left font-normal',
|
||||
!date && 'text-muted-foreground',
|
||||
!value && 'text-muted-foreground',
|
||||
)"
|
||||
>
|
||||
<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>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<Calendar v-model="date" />
|
||||
<Calendar v-model="value" initial-focus />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { format } from 'date-fns'
|
||||
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 * as z from 'zod'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
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,
|
||||
|
|
@ -17,22 +16,32 @@ import {
|
|||
FormLabel,
|
||||
FormMessage,
|
||||
} 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 { cn } from '@/lib/utils'
|
||||
|
||||
const df = new DateFormatter('en-US', {
|
||||
dateStyle: 'long',
|
||||
})
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
dob: z.date({
|
||||
required_error: 'A date of birth is required.',
|
||||
}),
|
||||
dob: z
|
||||
.string()
|
||||
.refine(v => v, { message: 'A date of birth is required.' }),
|
||||
}))
|
||||
|
||||
const { handleSubmit } = useForm({
|
||||
const placeholder = ref()
|
||||
|
||||
const { handleSubmit, setValues, values } = useForm({
|
||||
validationSchema: formSchema,
|
||||
initialValues: {
|
||||
|
||||
},
|
||||
})
|
||||
|
||||
const value = computed({
|
||||
get: () => values.dob ? parseDate(values.dob) : undefined,
|
||||
set: val => val,
|
||||
})
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
|
|
@ -45,7 +54,7 @@ const onSubmit = handleSubmit((values) => {
|
|||
|
||||
<template>
|
||||
<form class="space-y-8" @submit="onSubmit">
|
||||
<FormField v-slot="{ componentField, value }" name="dob">
|
||||
<FormField name="dob">
|
||||
<FormItem class="flex flex-col">
|
||||
<FormLabel>Date of birth</FormLabel>
|
||||
<Popover>
|
||||
|
|
@ -57,13 +66,34 @@ const onSubmit = handleSubmit((values) => {
|
|||
!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" />
|
||||
</Button>
|
||||
<input hidden>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="p-0">
|
||||
<Calendar v-bind="componentField" />
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,31 @@
|
|||
<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/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'
|
||||
DateFormatter,
|
||||
type DateValue,
|
||||
getLocalTimeZone,
|
||||
today,
|
||||
} 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 { 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>
|
||||
|
||||
<template>
|
||||
|
|
@ -29,45 +35,30 @@ const date = ref<Date>()
|
|||
variant="outline"
|
||||
:class="cn(
|
||||
'w-[280px] justify-start text-left font-normal',
|
||||
!date && 'text-muted-foreground',
|
||||
!value && '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>
|
||||
{{ value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date" }}
|
||||
</Button>
|
||||
</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
|
||||
@update:model-value="(value) => {
|
||||
date = addDays(new Date(), parseInt(value))
|
||||
@update:model-value="(v) => {
|
||||
if (!v) return;
|
||||
value = today(getLocalTimeZone()).add({ days: Number(v) });
|
||||
}"
|
||||
>
|
||||
<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
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in items" :key="item.value" :value="item.value.toString()">
|
||||
{{ item.label }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div class="rounded-md border">
|
||||
<Calendar v-model="date" mode="single" />
|
||||
</div>
|
||||
<Calendar v-model="value" />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,51 +1,55 @@
|
|||
<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/calendar'
|
||||
import { type Ref, ref } from 'vue'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/lib/registry/new-york/ui/popover'
|
||||
CalendarDate,
|
||||
DateFormatter,
|
||||
getLocalTimeZone,
|
||||
} from '@internationalized/date'
|
||||
|
||||
const date = ref({
|
||||
start: new Date(2022, 0, 20),
|
||||
end: addDays(new Date(2022, 0, 20), 20),
|
||||
import { CalendarIcon } from '@radix-icons/vue'
|
||||
import type { DateRange } from 'radix-vue'
|
||||
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>
|
||||
|
||||
<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" />
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
:class="cn(
|
||||
'w-[280px] justify-start text-left font-normal',
|
||||
!value && 'text-muted-foreground',
|
||||
)"
|
||||
>
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
<template v-if="value.start">
|
||||
<template v-if="value.end">
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{ df.format(value.end.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
|
||||
<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 v-else>
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
Pick a date
|
||||
</template>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<RangeCalendar v-model="value" initial-focus :number-of-months="2" :placeholder="value?.start" @update:start-value="(startDate) => value.start = startDate" />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</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">
|
||||
import { format } from 'date-fns'
|
||||
import { ref } from 'vue'
|
||||
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/calendar'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
|
|
@ -5,7 +5,7 @@ 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 { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
|
|
@ -1,325 +1,60 @@
|
|||
<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 '.'
|
||||
<script lang="ts" setup>
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNextButton, CalendarPrevButton } 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
|
||||
}
|
||||
const props = defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
const emits = defineEmits<CalendarRootEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
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
|
||||
}, {})
|
||||
})
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</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')" type="button" @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')" type="button" @click="handleNav('next')">
|
||||
<ChevronRightIcon class="w-4 h-4" />
|
||||
</button>
|
||||
<CalendarRoot
|
||||
v-slot="{ grid, weekDays }"
|
||||
:class="cn('p-3', props.class)"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<CalendarHeader>
|
||||
<CalendarPrevButton />
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</CalendarRoot>
|
||||
</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'
|
||||
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)
|
||||
}
|
||||
export { default as CalendarCell } from './CalendarCell.vue'
|
||||
export { default as CalendarCellTrigger } from './CalendarCellTrigger.vue'
|
||||
export { default as CalendarGrid } from './CalendarGrid.vue'
|
||||
export { default as CalendarGridBody } from './CalendarGridBody.vue'
|
||||
export { default as CalendarGridHead } from './CalendarGridHead.vue'
|
||||
export { default as CalendarGridRow } from './CalendarGridRow.vue'
|
||||
export { default as CalendarHeadCell } from './CalendarHeadCell.vue'
|
||||
export { default as CalendarHeader } from './CalendarHeader.vue'
|
||||
export { default as CalendarHeading } from './CalendarHeading.vue'
|
||||
export { default as CalendarNextButton } from './CalendarNextButton.vue'
|
||||
export { default as CalendarPrevButton } from './CalendarPrevButton.vue'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -117,16 +117,24 @@
|
|||
},
|
||||
{
|
||||
"name": "calendar",
|
||||
"dependencies": [
|
||||
"@vueuse/core",
|
||||
"v-calendar@next"
|
||||
],
|
||||
"dependencies": [],
|
||||
"registryDependencies": [
|
||||
"utils",
|
||||
"button"
|
||||
],
|
||||
"files": [
|
||||
"ui/calendar/Calendar.vue",
|
||||
"ui/calendar/CalendarCell.vue",
|
||||
"ui/calendar/CalendarCellTrigger.vue",
|
||||
"ui/calendar/CalendarGrid.vue",
|
||||
"ui/calendar/CalendarGridBody.vue",
|
||||
"ui/calendar/CalendarGridHead.vue",
|
||||
"ui/calendar/CalendarGridRow.vue",
|
||||
"ui/calendar/CalendarHeadCell.vue",
|
||||
"ui/calendar/CalendarHeader.vue",
|
||||
"ui/calendar/CalendarHeading.vue",
|
||||
"ui/calendar/CalendarNextButton.vue",
|
||||
"ui/calendar/CalendarPrevButton.vue",
|
||||
"ui/calendar/index.ts"
|
||||
],
|
||||
"type": "components:ui"
|
||||
|
|
@ -485,6 +493,30 @@
|
|||
],
|
||||
"type": "components:ui"
|
||||
},
|
||||
{
|
||||
"name": "range-calendar",
|
||||
"dependencies": [],
|
||||
"registryDependencies": [
|
||||
"utils",
|
||||
"button"
|
||||
],
|
||||
"files": [
|
||||
"ui/range-calendar/RangeCalendar.vue",
|
||||
"ui/range-calendar/RangeCalendarCell.vue",
|
||||
"ui/range-calendar/RangeCalendarCellTrigger.vue",
|
||||
"ui/range-calendar/RangeCalendarGrid.vue",
|
||||
"ui/range-calendar/RangeCalendarGridBody.vue",
|
||||
"ui/range-calendar/RangeCalendarGridHead.vue",
|
||||
"ui/range-calendar/RangeCalendarGridRow.vue",
|
||||
"ui/range-calendar/RangeCalendarHeadCell.vue",
|
||||
"ui/range-calendar/RangeCalendarHeader.vue",
|
||||
"ui/range-calendar/RangeCalendarHeading.vue",
|
||||
"ui/range-calendar/RangeCalendarNextButton.vue",
|
||||
"ui/range-calendar/RangeCalendarPrevButton.vue",
|
||||
"ui/range-calendar/index.ts"
|
||||
],
|
||||
"type": "components:ui"
|
||||
},
|
||||
{
|
||||
"name": "resizable",
|
||||
"dependencies": [],
|
||||
|
|
@ -737,5 +769,21 @@
|
|||
"ui/tooltip/index.ts"
|
||||
],
|
||||
"type": "components:ui"
|
||||
},
|
||||
{
|
||||
"name": "v-calendar",
|
||||
"dependencies": [
|
||||
"@vueuse/core",
|
||||
"v-calendar@next"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"utils",
|
||||
"button"
|
||||
],
|
||||
"files": [
|
||||
"ui/v-calendar/Calendar.vue",
|
||||
"ui/v-calendar/index.ts"
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user