feat: new calendar components
This commit is contained in:
parent
75d10d5b8e
commit
828b5b7077
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ export const docsConfig: DocsConfig = {
|
|||
title: 'Calendar',
|
||||
href: '/docs/components/calendar',
|
||||
items: [],
|
||||
label: 'Updated',
|
||||
},
|
||||
{
|
||||
title: 'Card',
|
||||
|
|
@ -229,6 +230,7 @@ export const docsConfig: DocsConfig = {
|
|||
title: 'Date Picker',
|
||||
href: '/docs/components/date-picker',
|
||||
items: [],
|
||||
label: 'Updated',
|
||||
},
|
||||
{
|
||||
title: 'Dialog',
|
||||
|
|
@ -302,6 +304,12 @@ 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',
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
@ -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](#asdasd) 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,30 @@ 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>
|
||||
</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"
|
||||
/>
|
||||
</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'
|
||||
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="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>
|
||||
124
apps/www/src/lib/registry/default/example/CalendarWithSelect.vue
Normal file
124
apps/www/src/lib/registry/default/example/CalendarWithSelect.vue
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, type Ref, computed, toRef } from 'vue'
|
||||
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, toDate, useForwardPropsEmits } from 'radix-vue'
|
||||
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)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CalendarRoot
|
||||
v-slot="{ getMonths, getYears, formatter, 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 getMonths"
|
||||
: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 getYears({ 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 class="mb-1 grid w-full grid-cols-7">
|
||||
<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="grid grid-cols-7">
|
||||
<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'
|
||||
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" />
|
||||
<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,73 +1,64 @@
|
|||
<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>
|
||||
<Popover>
|
||||
<Popover onfocus>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
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,52 @@
|
|||
<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>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<RangeCalendar v-model="value" initial-focus :number-of-months="2" :placeholder="value?.start" />
|
||||
</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 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 class="mb-1 grid w-full grid-cols-7">
|
||||
<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="grid grid-cols-7">
|
||||
<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-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 { 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="mt-4 flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
<RangeCalendarGrid v-for="month in grid" :key="month.value.toString()">
|
||||
<RangeCalendarGridHead>
|
||||
<RangeCalendarGridRow class="mb-1 grid w-full grid-cols-7">
|
||||
<RangeCalendarHeadCell
|
||||
v-for="day in weekDays" :key="day"
|
||||
>
|
||||
{{ day }}
|
||||
</RangeCalendarHeadCell>
|
||||
</RangeCalendarGridRow>
|
||||
</RangeCalendarGridHead>
|
||||
<RangeCalendarGridBody class="grid">
|
||||
<RangeCalendarGridRow v-for="(weekDates, index) in month.rows" :key="`weekDate-${index}`" class="grid grid-cols-7">
|
||||
<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'
|
||||
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="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,124 @@
|
|||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, type Ref, computed, toRef } from 'vue'
|
||||
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, toDate, useForwardPropsEmits } from 'radix-vue'
|
||||
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)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CalendarRoot
|
||||
v-slot="{ getMonths, getYears, formatter, 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 getMonths"
|
||||
: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 getYears({ 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 class="mb-1 grid w-full grid-cols-7">
|
||||
<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="grid grid-cols-7">
|
||||
<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'
|
||||
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" />
|
||||
<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,73 +1,64 @@
|
|||
<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>
|
||||
<Popover>
|
||||
<Popover onfocus>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
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,52 @@
|
|||
<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>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<RangeCalendar v-model="value" initial-focus :number-of-months="2" :placeholder="value?.start" />
|
||||
</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 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 class="mb-1 grid w-full grid-cols-7">
|
||||
<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="grid grid-cols-7">
|
||||
<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 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-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="mt-4 flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
<RangeCalendarGrid v-for="month in grid" :key="month.value.toString()">
|
||||
<RangeCalendarGridHead>
|
||||
<RangeCalendarGridRow class="mb-1 grid w-full grid-cols-7">
|
||||
<RangeCalendarHeadCell
|
||||
v-for="day in weekDays" :key="day"
|
||||
>
|
||||
{{ day }}
|
||||
</RangeCalendarHeadCell>
|
||||
</RangeCalendarGridRow>
|
||||
</RangeCalendarGridHead>
|
||||
<RangeCalendarGridBody class="grid">
|
||||
<RangeCalendarGridRow v-for="(weekDates, index) in month.rows" :key="`weekDate-${index}`" class="grid grid-cols-7">
|
||||
<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 { ChevronRightIcon } from '@radix-icons/vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||
|
||||
const props = defineProps<RangeCalendarNextProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RangeCalendarNext
|
||||
:class="cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
props.class,
|
||||
)"
|
||||
v-bind="forwardedProps"
|
||||
>
|
||||
<slot>
|
||||
<ChevronRightIcon class="h-4 w-4" />
|
||||
</slot>
|
||||
</RangeCalendarNext>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<script lang="ts" setup>
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { RangeCalendarPrev, type RangeCalendarPrevProps, useForwardProps } from 'radix-vue'
|
||||
import { ChevronLeftIcon } from '@radix-icons/vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/lib/registry/default/ui/button'
|
||||
|
||||
const props = defineProps<RangeCalendarPrevProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RangeCalendarPrev
|
||||
:class="cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
props.class,
|
||||
)"
|
||||
v-bind="forwardedProps"
|
||||
>
|
||||
<slot>
|
||||
<ChevronLeftIcon class="h-4 w-4" />
|
||||
</slot>
|
||||
</RangeCalendarPrev>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
export { default as RangeCalendar } from './RangeCalendar.vue'
|
||||
export { default as RangeCalendarCell } from './RangeCalendarCell.vue'
|
||||
export { default as RangeCalendarCellTrigger } from './RangeCalendarCellTrigger.vue'
|
||||
export { default as RangeCalendarGrid } from './RangeCalendarGrid.vue'
|
||||
export { default as RangeCalendarGridBody } from './RangeCalendarGridBody.vue'
|
||||
export { default as RangeCalendarGridHead } from './RangeCalendarGridHead.vue'
|
||||
export { default as RangeCalendarGridRow } from './RangeCalendarGridRow.vue'
|
||||
export { default as RangeCalendarHeadCell } from './RangeCalendarHeadCell.vue'
|
||||
export { default as RangeCalendarHeader } from './RangeCalendarHeader.vue'
|
||||
export { default as RangeCalendarHeading } from './RangeCalendarHeading.vue'
|
||||
export { default as RangeCalendarNextButton } from './RangeCalendarNextButton.vue'
|
||||
export { default as RangeCalendarPrevButton } from './RangeCalendarPrevButton.vue'
|
||||
325
apps/www/src/lib/registry/new-york/ui/v-calendar/Calendar.vue
Normal file
325
apps/www/src/lib/registry/new-york/ui/v-calendar/Calendar.vue
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue'
|
||||
import type { Calendar } from 'v-calendar'
|
||||
import { DatePicker } from 'v-calendar'
|
||||
import { computed, nextTick, onMounted, ref, useSlots } from 'vue'
|
||||
import { isVCalendarSlot } from '.'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||
|
||||
/* Extracted from v-calendar */
|
||||
type DatePickerModel = DatePickerDate | DatePickerRangeObject
|
||||
type DateSource = Date | string | number
|
||||
type DatePickerDate = DateSource | Partial<SimpleDateParts> | null
|
||||
interface DatePickerRangeObject {
|
||||
start: Exclude<DatePickerDate, null>
|
||||
end: Exclude<DatePickerDate, null>
|
||||
}
|
||||
interface SimpleDateParts {
|
||||
year: number
|
||||
month: number
|
||||
day: number
|
||||
hours: number
|
||||
minutes: number
|
||||
seconds: number
|
||||
milliseconds: number
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue?: string | number | Date | DatePickerModel
|
||||
modelModifiers?: object
|
||||
columns?: number
|
||||
type?: 'single' | 'range'
|
||||
}>(), {
|
||||
type: 'single',
|
||||
columns: 1,
|
||||
})
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:modelValue', payload: typeof props.modelValue): void
|
||||
}>()
|
||||
|
||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
passive: true,
|
||||
})
|
||||
|
||||
const datePicker = ref<InstanceType<typeof DatePicker>>()
|
||||
// @ts-expect-error in this current version of v-calendar has the calendaRef instance, which is required to handle arrow nav.
|
||||
const calendarRef = computed<InstanceType<typeof Calendar>>(() => datePicker.value.calendarRef)
|
||||
|
||||
function handleNav(direction: 'prev' | 'next') {
|
||||
if (!calendarRef.value)
|
||||
return
|
||||
|
||||
if (direction === 'prev')
|
||||
calendarRef.value.movePrev()
|
||||
else calendarRef.value.moveNext()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
if (modelValue.value instanceof Date && calendarRef.value)
|
||||
calendarRef.value.focusDate(modelValue.value)
|
||||
})
|
||||
|
||||
const $slots = useSlots()
|
||||
const vCalendarSlots = computed(() => {
|
||||
return Object.keys($slots)
|
||||
.filter(name => isVCalendarSlot(name))
|
||||
.reduce((obj: Record<string, any>, key: string) => {
|
||||
obj[key] = $slots[key]
|
||||
return obj
|
||||
}, {})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div v-if="$attrs.mode !== 'time'" class="absolute flex justify-between w-full px-4 top-3 z-[1]">
|
||||
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('prev')">
|
||||
<ChevronLeftIcon class="w-4 h-4" />
|
||||
</button>
|
||||
<button :class="cn(buttonVariants({ variant: 'outline' }), 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100')" @click="handleNav('next')">
|
||||
<ChevronRightIcon class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<DatePicker
|
||||
ref="datePicker"
|
||||
v-model="modelValue"
|
||||
v-bind="$attrs"
|
||||
:model-modifiers="modelModifiers"
|
||||
class="calendar"
|
||||
trim-weeks
|
||||
:transition="'none'"
|
||||
:columns="columns"
|
||||
>
|
||||
<template v-for="(_, slot) of vCalendarSlots" #[slot]="scope">
|
||||
<slot :name="slot" v-bind="scope" />
|
||||
</template>
|
||||
|
||||
<template #nav-prev-button>
|
||||
<ChevronLeftIcon />
|
||||
</template>
|
||||
|
||||
<template #nav-next-button>
|
||||
<ChevronRightIcon />
|
||||
</template>
|
||||
</DatePicker>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
.calendar {
|
||||
@apply p-3 text-center;
|
||||
}
|
||||
.calendar .vc-pane-layout {
|
||||
@apply grid gap-4;
|
||||
}
|
||||
.calendar .vc-title {
|
||||
@apply text-sm font-medium relative z-20;
|
||||
}
|
||||
.vc-popover-content-wrapper .vc-popover-content {
|
||||
@apply mt-3 rounded-md max-w-xs border bg-background;
|
||||
}
|
||||
.vc-popover-content-wrapper .vc-nav-header {
|
||||
@apply flex justify-between items-center p-2;
|
||||
}
|
||||
.vc-popover-content-wrapper .vc-nav-items {
|
||||
@apply grid grid-cols-4 gap-2 p-2;
|
||||
}
|
||||
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item {
|
||||
@apply rounded-md px-2 py-1;
|
||||
}
|
||||
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item:hover {
|
||||
@apply text-muted-foreground bg-muted;
|
||||
}
|
||||
.vc-popover-content-wrapper .vc-nav-items .vc-nav-item.is-active {
|
||||
@apply bg-primary text-primary-foreground;
|
||||
}
|
||||
.calendar .vc-pane-header-wrapper {
|
||||
@apply hidden;
|
||||
}
|
||||
.calendar .vc-weeks {
|
||||
@apply mt-4;
|
||||
}
|
||||
.calendar .vc-weekdays {
|
||||
@apply justify-items-center;
|
||||
}
|
||||
.calendar .vc-weekday {
|
||||
@apply text-muted-foreground rounded-md font-normal text-[0.8rem];
|
||||
}
|
||||
.calendar .vc-weeks {
|
||||
@apply w-full space-y-2 flex flex-col [&>_div]:grid [&>_div]:grid-cols-7;
|
||||
}
|
||||
.calendar .vc-day:has(.vc-highlights) {
|
||||
@apply first:rounded-l-md last:rounded-r-md;
|
||||
}
|
||||
.calendar .vc-day.is-today:not(:has(.vc-day-layer)) .vc-day-content {
|
||||
@apply bg-secondary text-primary rounded-md;
|
||||
}
|
||||
.calendar .vc-day:has(.vc-highlight-base-start) {
|
||||
@apply rounded-l-md;
|
||||
}
|
||||
.calendar .vc-day:has(.vc-highlight-base-end) {
|
||||
@apply rounded-r-md;
|
||||
}
|
||||
.calendar .vc-day:has(.vc-highlight-bg-outline):not(:has(.vc-highlight-base-start)):not(:has(.vc-highlight-base-end)) {
|
||||
@apply rounded-md;
|
||||
}
|
||||
.calendar .vc-day-content {
|
||||
@apply text-center text-sm p-0 relative focus-within:relative focus-within:z-20 inline-flex items-center justify-center ring-offset-background hover:transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:bg-accent hover:text-accent-foreground h-9 w-9 font-normal aria-selected:opacity-100 select-none;
|
||||
}
|
||||
.calendar .vc-day-content:not(.vc-highlight-content-light) {
|
||||
@apply rounded-md;
|
||||
}
|
||||
.calendar .is-not-in-month:not(:has(.vc-highlight-content-solid)):not(:has(.vc-highlight-content-light)):not(:has(.vc-highlight-content-outline)),
|
||||
.calendar .vc-disabled {
|
||||
@apply text-muted-foreground opacity-50;
|
||||
}
|
||||
.calendar .vc-highlight-content-solid, .calendar .vc-highlight-content-outline {
|
||||
@apply bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground;
|
||||
}
|
||||
.calendar .vc-highlight-content-light {
|
||||
@apply bg-accent text-accent-foreground;
|
||||
}
|
||||
.calendar .vc-pane-container.in-transition {
|
||||
@apply overflow-hidden;
|
||||
}
|
||||
.calendar .vc-pane-container {
|
||||
@apply w-full relative;
|
||||
}
|
||||
:root {
|
||||
--vc-slide-translate: 22px;
|
||||
--vc-slide-duration: 0.15s;
|
||||
--vc-slide-timing: ease;
|
||||
}
|
||||
.calendar .vc-fade-enter-active,
|
||||
.calendar .vc-fade-leave-active,
|
||||
.calendar .vc-slide-left-enter-active,
|
||||
.calendar .vc-slide-left-leave-active,
|
||||
.calendar .vc-slide-right-enter-active,
|
||||
.calendar .vc-slide-right-leave-active,
|
||||
.calendar .vc-slide-up-enter-active,
|
||||
.calendar .vc-slide-up-leave-active,
|
||||
.calendar .vc-slide-down-enter-active,
|
||||
.calendar .vc-slide-down-leave-active,
|
||||
.calendar .vc-slide-fade-enter-active,
|
||||
.calendar .vc-slide-fade-leave-active {
|
||||
transition:
|
||||
opacity var(--vc-slide-duration) var(--vc-slide-timing),
|
||||
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
|
||||
transition:
|
||||
transform var(--vc-slide-duration) var(--vc-slide-timing),
|
||||
opacity var(--vc-slide-duration) var(--vc-slide-timing);
|
||||
transition:
|
||||
transform var(--vc-slide-duration) var(--vc-slide-timing),
|
||||
opacity var(--vc-slide-duration) var(--vc-slide-timing),
|
||||
-webkit-transform var(--vc-slide-duration) var(--vc-slide-timing);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.calendar .vc-none-leave-active,
|
||||
.calendar .vc-fade-leave-active,
|
||||
.calendar .vc-slide-left-leave-active,
|
||||
.calendar .vc-slide-right-leave-active,
|
||||
.calendar .vc-slide-up-leave-active,
|
||||
.calendar .vc-slide-down-leave-active {
|
||||
position: absolute !important;
|
||||
width: 100%;
|
||||
}
|
||||
.calendar .vc-none-enter-from,
|
||||
.calendar .vc-none-leave-to,
|
||||
.calendar .vc-fade-enter-from,
|
||||
.calendar .vc-fade-leave-to,
|
||||
.calendar .vc-slide-left-enter-from,
|
||||
.calendar .vc-slide-left-leave-to,
|
||||
.calendar .vc-slide-right-enter-from,
|
||||
.calendar .vc-slide-right-leave-to,
|
||||
.calendar .vc-slide-up-enter-from,
|
||||
.calendar .vc-slide-up-leave-to,
|
||||
.calendar .vc-slide-down-enter-from,
|
||||
.calendar .vc-slide-down-leave-to,
|
||||
.calendar .vc-slide-fade-enter-from,
|
||||
.calendar .vc-slide-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.calendar .vc-slide-left-enter-from,
|
||||
.calendar .vc-slide-right-leave-to,
|
||||
.calendar .vc-slide-fade-enter-from.direction-left,
|
||||
.calendar .vc-slide-fade-leave-to.direction-left {
|
||||
-webkit-transform: translateX(var(--vc-slide-translate));
|
||||
transform: translateX(var(--vc-slide-translate));
|
||||
}
|
||||
.calendar .vc-slide-right-enter-from,
|
||||
.calendar .vc-slide-left-leave-to,
|
||||
.calendar .vc-slide-fade-enter-from.direction-right,
|
||||
.calendar .vc-slide-fade-leave-to.direction-right {
|
||||
-webkit-transform: translateX(calc(-1 * var(--vc-slide-translate)));
|
||||
transform: translateX(calc(-1 * var(--vc-slide-translate)));
|
||||
}
|
||||
.calendar .vc-slide-up-enter-from,
|
||||
.calendar .vc-slide-down-leave-to,
|
||||
.calendar .vc-slide-fade-enter-from.direction-top,
|
||||
.calendar .vc-slide-fade-leave-to.direction-top {
|
||||
-webkit-transform: translateY(var(--vc-slide-translate));
|
||||
transform: translateY(var(--vc-slide-translate));
|
||||
}
|
||||
.calendar .vc-slide-down-enter-from,
|
||||
.calendar .vc-slide-up-leave-to,
|
||||
.calendar .vc-slide-fade-enter-from.direction-bottom,
|
||||
.calendar .vc-slide-fade-leave-to.direction-bottom {
|
||||
-webkit-transform: translateY(calc(-1 * var(--vc-slide-translate)));
|
||||
transform: translateY(calc(-1 * var(--vc-slide-translate)));
|
||||
}
|
||||
/**
|
||||
* Timepicker styles
|
||||
*/
|
||||
.vc-time-picker {
|
||||
@apply flex flex-col items-center p-2;
|
||||
}
|
||||
.vc-time-picker.vc-invalid {
|
||||
@apply pointer-events-none opacity-50;
|
||||
}
|
||||
.vc-time-picker.vc-attached {
|
||||
@apply border-t border-solid border-secondary mt-2;
|
||||
}
|
||||
.vc-time-picker > * + * {
|
||||
@apply mt-1;
|
||||
}
|
||||
.vc-time-header {
|
||||
@apply flex items-center text-sm font-semibold uppercase mt-1 px-1 leading-6;
|
||||
}
|
||||
.vc-time-select-group {
|
||||
@apply inline-flex items-center px-1 rounded-md bg-primary-foreground border border-solid border-secondary;
|
||||
}
|
||||
.vc-time-select-group .vc-base-icon {
|
||||
@apply mr-1 text-primary stroke-primary;
|
||||
}
|
||||
.vc-time-select-group select {
|
||||
@apply bg-primary-foreground p-1 appearance-none outline-none text-center;
|
||||
}
|
||||
.vc-time-weekday {
|
||||
@apply text-muted-foreground tracking-wide;
|
||||
}
|
||||
.vc-time-month {
|
||||
@apply text-primary ml-2;
|
||||
}
|
||||
.vc-time-day {
|
||||
@apply text-primary ml-1;
|
||||
}
|
||||
.vc-time-year {
|
||||
@apply text-muted-foreground ml-2;
|
||||
}
|
||||
.vc-time-colon {
|
||||
@apply mb-0.5;
|
||||
}
|
||||
.vc-time-decimal {
|
||||
@apply ml-0.5;
|
||||
}
|
||||
</style>
|
||||
22
apps/www/src/lib/registry/new-york/ui/v-calendar/index.ts
Normal file
22
apps/www/src/lib/registry/new-york/ui/v-calendar/index.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
export { default as Calendar } from './Calendar.vue'
|
||||
import type { CalendarSlotName } from 'v-calendar/dist/types/src/components/Calendar/CalendarSlot.vue.d.ts'
|
||||
|
||||
export function isVCalendarSlot(slotName: string): slotName is CalendarSlotName {
|
||||
const validSlots: CalendarSlotName[] = [
|
||||
'day-content',
|
||||
'day-popover',
|
||||
'dp-footer',
|
||||
'footer',
|
||||
'header-title-wrapper',
|
||||
'header-title',
|
||||
'header-prev-button',
|
||||
'header-next-button',
|
||||
'nav',
|
||||
'nav-prev-button',
|
||||
'nav-next-button',
|
||||
'page',
|
||||
'time-header',
|
||||
]
|
||||
|
||||
return validSlots.includes(slotName as CalendarSlotName)
|
||||
}
|
||||
|
|
@ -89,4 +89,4 @@
|
|||
},
|
||||
"inlineColorsTemplate": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n",
|
||||
"cssVarsTemplate": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n \n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 224 71.4% 4.1%;\n \n --muted: 220 14.3% 95.9%;\n --muted-foreground: 220 8.9% 46.1%;\n \n --popover: 0 0% 100%;\n --popover-foreground: 224 71.4% 4.1%;\n \n --card: 0 0% 100%;\n --card-foreground: 224 71.4% 4.1%;\n \n --border: 220 13% 91%;\n --input: 220 13% 91%;\n \n --primary: 220.9 39.3% 11%;\n --primary-foreground: 210 20% 98%;\n \n --secondary: 220 14.3% 95.9%;\n --secondary-foreground: 220.9 39.3% 11%;\n \n --accent: 220 14.3% 95.9%;\n --accent-foreground: 220.9 39.3% 11%;\n \n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 20% 98%;\n \n --ring: 224 71.4% 4.1%;\n \n --radius: 0.5rem;\n }\n \n .dark {\n --background: 224 71.4% 4.1%;\n --foreground: 210 20% 98%;\n \n --muted: 215 27.9% 16.9%;\n --muted-foreground: 217.9 10.6% 64.9%;\n \n --popover: 224 71.4% 4.1%;\n --popover-foreground: 210 20% 98%;\n \n --card: 224 71.4% 4.1%;\n --card-foreground: 210 20% 98%;\n \n --border: 215 27.9% 16.9%;\n --input: 215 27.9% 16.9%;\n \n --primary: 210 20% 98%;\n --primary-foreground: 220.9 39.3% 11%;\n \n --secondary: 215 27.9% 16.9%;\n --secondary-foreground: 210 20% 98%;\n \n --accent: 215 27.9% 16.9%;\n --accent-foreground: 210 20% 98%;\n \n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 210 20% 98%;\n \n --ring: 216 12.2% 83.9%;\n }\n}\n \n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}"
|
||||
}
|
||||
}
|
||||
|
|
@ -1996,4 +1996,4 @@
|
|||
"hslChannel": "343.1 87.7% 15.9%"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user