feat: new calendar
This commit is contained in:
parent
d34c620055
commit
ffe9696547
|
|
@ -19,7 +19,7 @@ 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 { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||
|
||||
const range = ref({
|
||||
start: startOfToday(),
|
||||
|
|
@ -73,3 +73,4 @@ const range = ref({
|
|||
</div>
|
||||
</ThemingLayout>
|
||||
</template>
|
||||
@/lib/registry/new-york/ui/v-calendar
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ export const docsConfig: DocsConfig = {
|
|||
title: 'Calendar',
|
||||
href: '/docs/components/calendar',
|
||||
items: [],
|
||||
label: 'New',
|
||||
},
|
||||
{
|
||||
title: 'Card',
|
||||
|
|
@ -221,11 +222,6 @@ export const docsConfig: DocsConfig = {
|
|||
href: '/docs/components/data-table',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Date Picker',
|
||||
href: '/docs/components/date-picker',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Dialog',
|
||||
href: '/docs/components/dialog',
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
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",
|
||||
|
|
@ -395,41 +409,6 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/default/example/DataTableDemoColumn.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/default/example/DataTableDemoColumn.vue"],
|
||||
},
|
||||
"DatePickerDemo": {
|
||||
name: "DatePickerDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["utils","button","calendar","popover"],
|
||||
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"],
|
||||
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"],
|
||||
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"],
|
||||
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,13 +633,6 @@ 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",
|
||||
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"],
|
||||
},
|
||||
"ResizableDemo": {
|
||||
name: "ResizableDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -1081,6 +1053,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",
|
||||
|
|
@ -1292,6 +1313,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: [],
|
||||
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",
|
||||
|
|
@ -1495,41 +1530,6 @@ export const Index = {
|
|||
component: () => import("../src/lib/registry/new-york/example/DataTableDemoColumn.vue").then((m) => m.default),
|
||||
files: ["../src/lib/registry/new-york/example/DataTableDemoColumn.vue"],
|
||||
},
|
||||
"DatePickerDemo": {
|
||||
name: "DatePickerDemo",
|
||||
type: "components:example",
|
||||
registryDependencies: ["utils","button","calendar","popover"],
|
||||
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"],
|
||||
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"],
|
||||
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"],
|
||||
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",
|
||||
|
|
@ -1754,13 +1754,6 @@ 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",
|
||||
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"],
|
||||
},
|
||||
"ResizableDemo": {
|
||||
name: "ResizableDemo",
|
||||
type: "components:example",
|
||||
|
|
@ -2181,6 +2174,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",
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
"embla-carousel": "^8.0.0",
|
||||
"embla-carousel-autoplay": "^8.0.0",
|
||||
"embla-carousel-vue": "^8.0.0",
|
||||
"flat-internationalized-date": "^1.2.3",
|
||||
"lucide-vue-next": "^0.359.0",
|
||||
"magic-string": "^0.30.8",
|
||||
"radix-vue": "^1.5.3",
|
||||
|
|
|
|||
|
|
@ -2,90 +2,31 @@
|
|||
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" />
|
||||
|
||||
## 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 [flat-internationalized-date](https://github.com/epr3/flat-internationalized-date) package to handle dates.
|
||||
|
||||
If you're looking for a range calendar, check out the [Range Calendar](#asdasd) component.
|
||||
|
||||
## Installation
|
||||
|
||||
<TabPreview name="CLI">
|
||||
<template #CLI>
|
||||
|
||||
```bash
|
||||
```shell
|
||||
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="CalendarWithSelect" />
|
||||
|
||||
<<< @/lib/registry/default/ui/calendar/Calendar.vue
|
||||
|
||||
</Steps>
|
||||
|
||||
</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="CalendarForm" />
|
||||
|
|
|
|||
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: 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/
|
||||
---
|
||||
|
||||
<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 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>
|
||||
```
|
||||
|
|
@ -3,7 +3,7 @@ title: Date Picker
|
|||
description: A date picker component with range and presets.
|
||||
---
|
||||
|
||||
<ComponentPreview name="DatePickerDemo" />
|
||||
<ComponentPreview name="VDatePickerDemo" />
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ 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 { Calendar } from '@/components/ui/v-calendar'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
|
|
@ -56,24 +56,24 @@ const date = ref<Date>()
|
|||
|
||||
### Date Picker
|
||||
|
||||
<ComponentPreview name="DatePickerDemo" />
|
||||
<ComponentPreview name="VDatePickerDemo" />
|
||||
|
||||
### Date Range Picker
|
||||
|
||||
<ComponentPreview name="DatePickerWithRange" />
|
||||
<ComponentPreview name="VDatePickerWithRange" />
|
||||
|
||||
### Date Time Picker
|
||||
|
||||
<ComponentPreview name="DateTimePickerDemo" />
|
||||
<ComponentPreview name="VDateTimePickerDemo" />
|
||||
|
||||
### With Presets
|
||||
|
||||
<ComponentPreview name="DatePickerWithPresets" />
|
||||
<ComponentPreview name="VDatePickerWithPresets" />
|
||||
|
||||
### With Slot
|
||||
|
||||
<ComponentPreview name="RangePickerWithSlot" />
|
||||
<ComponentPreview name="VRangePickerWithSlot" />
|
||||
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="DatePickerForm" />
|
||||
<ComponentPreview name="VDatePickerForm" />
|
||||
|
|
@ -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/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,
|
||||
|
|
@ -49,3 +49,4 @@ const date = ref({
|
|||
</Popover>
|
||||
</div>
|
||||
</template>
|
||||
@/lib/registry/new-york/ui/v-calendar
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/lib/registry/default/ui/popover'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||
|
||||
const open = ref(false)
|
||||
|
|
@ -184,3 +184,4 @@ async function onSubmit(values: any) {
|
|||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
@/lib/registry/new-york/ui/v-calendar
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import addHours from 'date-fns/addHours'
|
|||
import format from 'date-fns/format'
|
||||
import nextSaturday from 'date-fns/nextSaturday'
|
||||
import type { Mail } from '../data/mails'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||
import { Avatar, AvatarFallback } from '@/lib/registry/new-york/ui/avatar'
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { getLocalTimeZone, today } from 'flat-internationalized-date'
|
||||
import { Calendar } from '@/lib/registry/default/ui/calendar'
|
||||
|
||||
const date = ref(new Date())
|
||||
const value = ref(today(getLocalTimeZone()))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Calendar v-model="date" class="rounded-md border" />
|
||||
<Calendar v-model="value" :weekday-format="'short'" class="rounded-md border" />
|
||||
</template>
|
||||
|
|
|
|||
95
apps/www/src/lib/registry/default/example/CalendarForm.vue
Normal file
95
apps/www/src/lib/registry/default/example/CalendarForm.vue
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<script setup lang="ts">
|
||||
import { h, ref } from 'vue'
|
||||
import { DateFormatter, type DateValue, createCalendarDate, getLocalTimeZone, parseDate, today } from 'flat-internationalized-date'
|
||||
import { useDateFormatter } 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 = useDateFormatter('en')
|
||||
const dateValue = ref<DateValue | undefined>()
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
dob: z
|
||||
.string()
|
||||
.refine(v => v, { message: 'A date of birth is required.' }),
|
||||
}))
|
||||
|
||||
const placeholder = ref()
|
||||
|
||||
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="{ 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 ? JSON.stringify(value) : "Pick a date" }} {{ JSON.stringify(value) }}</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="dateValue"
|
||||
calendar-label="Date of birth"
|
||||
initial-focus
|
||||
:min-value="createCalendarDate({
|
||||
day: 1,
|
||||
month: 2,
|
||||
year: 2024,
|
||||
})"
|
||||
:max-value="today(getLocalTimeZone())"
|
||||
@update:model-value="(v) => {
|
||||
console.log(v)
|
||||
}"
|
||||
/>
|
||||
</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,83 @@
|
|||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed, onMounted, ref, toRef } from 'vue'
|
||||
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||
import { CALENDAR, DateFormatter, getLocalTimeZone, temporalToString, toCalendar, toDate, today } from 'flat-internationalized-date'
|
||||
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading } from '@/lib/registry/default/ui/calendar'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
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 toCalendar(today(getLocalTimeZone()), CALENDAR.GREGORIAN)
|
||||
},
|
||||
weekdayFormat: 'short',
|
||||
})
|
||||
const emits = defineEmits<CalendarRootEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, placeholder: __, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
const placeholder = toRef(props.placeholder)
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CalendarRoot
|
||||
|
||||
v-slot="{ getMonths, getYears, formatter, date }"
|
||||
v-model:placeholder="placeholder"
|
||||
v-bind="forwarded"
|
||||
:class="cn('rounded-md border p-3', props.class)"
|
||||
>
|
||||
{{ JSON.stringify(placeholder) }}
|
||||
<CalendarHeader>
|
||||
<CalendarHeading class="flex w-full items-center justify-between gap-2">
|
||||
<Select
|
||||
:default-value="props.placeholder.month.toString()"
|
||||
>
|
||||
<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="temporalToString(month)" :value="month.month.toString()"
|
||||
@click="placeholder = month"
|
||||
>
|
||||
{{ formatter.custom(month, { month: 'long' }) }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
:default-value="props.placeholder.year.toString()"
|
||||
>
|
||||
<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="temporalToString(yearValue)" :value="yearValue.year.toString()"
|
||||
@click="placeholder = yearValue"
|
||||
>
|
||||
{{ yearValue.year }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</CalendarHeading>
|
||||
</CalendarHeader>
|
||||
</CalendarRoot>
|
||||
</template>
|
||||
|
|
@ -27,7 +27,7 @@ const frameworks = [
|
|||
]
|
||||
|
||||
const open = ref(false)
|
||||
const value = ref<string>('')
|
||||
const value = ref({})
|
||||
|
||||
// const filterFunction = (list: typeof frameworks, search: string) => list.filter(i => i.value.toLowerCase().includes(search.toLowerCase()))
|
||||
</script>
|
||||
|
|
|
|||
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>
|
||||
|
|
@ -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,
|
||||
|
|
@ -8,7 +8,7 @@ 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/calendar'
|
||||
import { Calendar } from '@/lib/registry/default/ui/v-calendar'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
|
|
@ -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,
|
||||
|
|
@ -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')" @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>
|
||||
<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,12 @@
|
|||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
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'
|
||||
|
|
|
|||
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,7 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||
|
||||
const date = ref(new Date())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Calendar v-model="date" class="rounded-md border" />
|
||||
<Calendar class="rounded-md border" />
|
||||
</template>
|
||||
|
|
|
|||
80
apps/www/src/lib/registry/new-york/example/CalendarForm.vue
Normal file
80
apps/www/src/lib/registry/new-york/example/CalendarForm.vue
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<script setup lang="ts">
|
||||
import { h, ref } from 'vue'
|
||||
import { DateFormatter, getLocalTimeZone, toDate, today } from 'flat-internationalized-date'
|
||||
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 { toast } from '@/lib/registry/default/ui/toast'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const df = DateFormatter('en-US')
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
date: z.date({
|
||||
required_error: 'A date of birth is required.',
|
||||
}),
|
||||
}))
|
||||
|
||||
const value = ref(today(getLocalTimeZone()))
|
||||
|
||||
const { handleSubmit } = useForm({
|
||||
validationSchema: formSchema,
|
||||
initialValues: {
|
||||
date: toDate(value.value, getLocalTimeZone()),
|
||||
},
|
||||
})
|
||||
|
||||
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>
|
||||
<Calendar v-model="value" :weekday-format="'short'" class="rounded-md border" />
|
||||
|
||||
<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 ? df(value!) : "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>
|
||||
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>
|
||||
|
|
@ -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,
|
||||
|
|
@ -8,7 +8,7 @@ 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/calendar'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/v-calendar'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
|
|
@ -5,7 +5,7 @@ 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,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')" @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>
|
||||
<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'
|
||||
|
|
|
|||
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%"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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: 89.3 80.4% 10%;\n \n --muted: 79.6 89.1% 89.2%;\n --muted-foreground: 83.7 80.5% 44.3%;\n \n --popover: 0 0% 100%;\n --popover-foreground: 89.3 80.4% 10%;\n \n --card: 0 0% 100%;\n --card-foreground: 89.3 80.4% 10%;\n \n --border: 80.9 88.5% 79.6%;\n --input: 80.9 88.5% 79.6%;\n \n --primary: 87.6 61.2% 20.2%;\n --primary-foreground: 78.3 92% 95.1%;\n \n --secondary: 79.6 89.1% 89.2%;\n --secondary-foreground: 87.6 61.2% 20.2%;\n \n --accent: 79.6 89.1% 89.2%;\n --accent-foreground: 87.6 61.2% 20.2%;\n \n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 78.3 92% 95.1%;\n \n --ring: 89.3 80.4% 10%;\n \n --radius: 0.5rem;\n }\n \n .dark {\n --background: 89.3 80.4% 10%;\n --foreground: 78.3 92% 95.1%;\n \n --muted: 86.3 69% 22.7%;\n --muted-foreground: 82.7 78% 55.5%;\n \n --popover: 89.3 80.4% 10%;\n --popover-foreground: 78.3 92% 95.1%;\n \n --card: 89.3 80.4% 10%;\n --card-foreground: 78.3 92% 95.1%;\n \n --border: 86.3 69% 22.7%;\n --input: 86.3 69% 22.7%;\n \n --primary: 78.3 92% 95.1%;\n --primary-foreground: 87.6 61.2% 20.2%;\n \n --secondary: 86.3 69% 22.7%;\n --secondary-foreground: 78.3 92% 95.1%;\n \n --accent: 86.3 69% 22.7%;\n --accent-foreground: 78.3 92% 95.1%;\n \n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 78.3 92% 95.1%;\n \n --ring: 82 84.5% 67.1%;\n }\n}\n \n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}"
|
||||
}
|
||||
}
|
||||
|
|
@ -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: 0 0% 3.9%;\n \n --muted: 0 0% 96.1%;\n --muted-foreground: 0 0% 45.1%;\n \n --popover: 0 0% 100%;\n --popover-foreground: 0 0% 3.9%;\n \n --card: 0 0% 100%;\n --card-foreground: 0 0% 3.9%;\n \n --border: 0 0% 89.8%;\n --input: 0 0% 89.8%;\n \n --primary: 0 0% 9%;\n --primary-foreground: 0 0% 98%;\n \n --secondary: 0 0% 96.1%;\n --secondary-foreground: 0 0% 9%;\n \n --accent: 0 0% 96.1%;\n --accent-foreground: 0 0% 9%;\n \n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 0 0% 98%;\n \n --ring: 0 0% 3.9%;\n \n --radius: 0.5rem;\n }\n \n .dark {\n --background: 0 0% 3.9%;\n --foreground: 0 0% 98%;\n \n --muted: 0 0% 14.9%;\n --muted-foreground: 0 0% 63.9%;\n \n --popover: 0 0% 3.9%;\n --popover-foreground: 0 0% 98%;\n \n --card: 0 0% 3.9%;\n --card-foreground: 0 0% 98%;\n \n --border: 0 0% 14.9%;\n --input: 0 0% 14.9%;\n \n --primary: 0 0% 98%;\n --primary-foreground: 0 0% 9%;\n \n --secondary: 0 0% 14.9%;\n --secondary-foreground: 0 0% 98%;\n \n --accent: 0 0% 14.9%;\n --accent-foreground: 0 0% 98%;\n \n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 0 0% 98%;\n \n --ring: 0 0% 83.1%;\n }\n}\n \n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}"
|
||||
}
|
||||
}
|
||||
|
|
@ -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: 222.2 84% 4.9%;\n \n --muted: 210 40% 96.1%;\n --muted-foreground: 215.4 16.3% 46.9%;\n \n --popover: 0 0% 100%;\n --popover-foreground: 222.2 84% 4.9%;\n \n --card: 0 0% 100%;\n --card-foreground: 222.2 84% 4.9%;\n \n --border: 214.3 31.8% 91.4%;\n --input: 214.3 31.8% 91.4%;\n \n --primary: 222.2 47.4% 11.2%;\n --primary-foreground: 210 40% 98%;\n \n --secondary: 210 40% 96.1%;\n --secondary-foreground: 222.2 47.4% 11.2%;\n \n --accent: 210 40% 96.1%;\n --accent-foreground: 222.2 47.4% 11.2%;\n \n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 40% 98%;\n \n --ring: 222.2 84% 4.9%;\n \n --radius: 0.5rem;\n }\n \n .dark {\n --background: 222.2 84% 4.9%;\n --foreground: 210 40% 98%;\n \n --muted: 217.2 32.6% 17.5%;\n --muted-foreground: 215 20.2% 65.1%;\n \n --popover: 222.2 84% 4.9%;\n --popover-foreground: 210 40% 98%;\n \n --card: 222.2 84% 4.9%;\n --card-foreground: 210 40% 98%;\n \n --border: 217.2 32.6% 17.5%;\n --input: 217.2 32.6% 17.5%;\n \n --primary: 210 40% 98%;\n --primary-foreground: 222.2 47.4% 11.2%;\n \n --secondary: 217.2 32.6% 17.5%;\n --secondary-foreground: 210 40% 98%;\n \n --accent: 217.2 32.6% 17.5%;\n --accent-foreground: 210 40% 98%;\n \n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 210 40% 98%;\n \n --ring: 212.7 26.8% 83.9%;\n }\n}\n \n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}"
|
||||
}
|
||||
}
|
||||
|
|
@ -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: 20 14.3% 4.1%;\n \n --muted: 60 4.8% 95.9%;\n --muted-foreground: 25 5.3% 44.7%;\n \n --popover: 0 0% 100%;\n --popover-foreground: 20 14.3% 4.1%;\n \n --card: 0 0% 100%;\n --card-foreground: 20 14.3% 4.1%;\n \n --border: 20 5.9% 90%;\n --input: 20 5.9% 90%;\n \n --primary: 24 9.8% 10%;\n --primary-foreground: 60 9.1% 97.8%;\n \n --secondary: 60 4.8% 95.9%;\n --secondary-foreground: 24 9.8% 10%;\n \n --accent: 60 4.8% 95.9%;\n --accent-foreground: 24 9.8% 10%;\n \n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 60 9.1% 97.8%;\n \n --ring: 20 14.3% 4.1%;\n \n --radius: 0.5rem;\n }\n \n .dark {\n --background: 20 14.3% 4.1%;\n --foreground: 60 9.1% 97.8%;\n \n --muted: 12 6.5% 15.1%;\n --muted-foreground: 24 5.4% 63.9%;\n \n --popover: 20 14.3% 4.1%;\n --popover-foreground: 60 9.1% 97.8%;\n \n --card: 20 14.3% 4.1%;\n --card-foreground: 60 9.1% 97.8%;\n \n --border: 12 6.5% 15.1%;\n --input: 12 6.5% 15.1%;\n \n --primary: 60 9.1% 97.8%;\n --primary-foreground: 24 9.8% 10%;\n \n --secondary: 12 6.5% 15.1%;\n --secondary-foreground: 60 9.1% 97.8%;\n \n --accent: 12 6.5% 15.1%;\n --accent-foreground: 60 9.1% 97.8%;\n \n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 60 9.1% 97.8%;\n \n --ring: 24 5.7% 82.9%;\n }\n}\n \n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}"
|
||||
}
|
||||
}
|
||||
|
|
@ -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: 240 10% 3.9%;\n \n --muted: 240 4.8% 95.9%;\n --muted-foreground: 240 3.8% 46.1%;\n \n --popover: 0 0% 100%;\n --popover-foreground: 240 10% 3.9%;\n \n --card: 0 0% 100%;\n --card-foreground: 240 10% 3.9%;\n \n --border: 240 5.9% 90%;\n --input: 240 5.9% 90%;\n \n --primary: 240 5.9% 10%;\n --primary-foreground: 0 0% 98%;\n \n --secondary: 240 4.8% 95.9%;\n --secondary-foreground: 240 5.9% 10%;\n \n --accent: 240 4.8% 95.9%;\n --accent-foreground: 240 5.9% 10%;\n \n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 0 0% 98%;\n \n --ring: 240 10% 3.9%;\n \n --radius: 0.5rem;\n }\n \n .dark {\n --background: 240 10% 3.9%;\n --foreground: 0 0% 98%;\n \n --muted: 240 3.7% 15.9%;\n --muted-foreground: 240 5% 64.9%;\n \n --popover: 240 10% 3.9%;\n --popover-foreground: 0 0% 98%;\n \n --card: 240 10% 3.9%;\n --card-foreground: 0 0% 98%;\n \n --border: 240 3.7% 15.9%;\n --input: 240 3.7% 15.9%;\n \n --primary: 0 0% 98%;\n --primary-foreground: 240 5.9% 10%;\n \n --secondary: 240 3.7% 15.9%;\n --secondary-foreground: 0 0% 98%;\n \n --accent: 240 3.7% 15.9%;\n --accent-foreground: 0 0% 98%;\n \n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 0 0% 98%;\n \n --ring: 240 4.9% 83.9%;\n }\n}\n \n@layer base {\n * {\n @apply border-border;\n }\n body {\n @apply bg-background text-foreground;\n }\n}"
|
||||
}
|
||||
}
|
||||
|
|
@ -117,16 +117,24 @@
|
|||
},
|
||||
{
|
||||
"name": "calendar",
|
||||
"dependencies": [
|
||||
"@vueuse/core",
|
||||
"v-calendar@next"
|
||||
],
|
||||
"dependencies": [],
|
||||
"registryDependencies": [
|
||||
"utils",
|
||||
"button"
|
||||
],
|
||||
"files": [
|
||||
"ui/calendar/Calendar.vue",
|
||||
"ui/calendar/CalendarCell.vue",
|
||||
"ui/calendar/CalendarCellTrigger.vue",
|
||||
"ui/calendar/CalendarGrid.vue",
|
||||
"ui/calendar/CalendarGridBody.vue",
|
||||
"ui/calendar/CalendarGridHead.vue",
|
||||
"ui/calendar/CalendarGridRow.vue",
|
||||
"ui/calendar/CalendarHeadCell.vue",
|
||||
"ui/calendar/CalendarHeader.vue",
|
||||
"ui/calendar/CalendarHeading.vue",
|
||||
"ui/calendar/CalendarNextButton.vue",
|
||||
"ui/calendar/CalendarPrevButton.vue",
|
||||
"ui/calendar/index.ts"
|
||||
],
|
||||
"type": "components:ui"
|
||||
|
|
@ -735,5 +743,21 @@
|
|||
"ui/tooltip/index.ts"
|
||||
],
|
||||
"type": "components:ui"
|
||||
},
|
||||
{
|
||||
"name": "v-calendar",
|
||||
"dependencies": [
|
||||
"@vueuse/core",
|
||||
"v-calendar@next"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"utils",
|
||||
"button"
|
||||
],
|
||||
"files": [
|
||||
"ui/v-calendar/Calendar.vue",
|
||||
"ui/v-calendar/index.ts"
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
@ -27,4 +27,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -48,4 +48,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -23,4 +23,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -13,4 +13,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -23,4 +23,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -39,4 +39,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -35,4 +35,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -44,4 +44,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -21,4 +21,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -48,4 +48,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -71,4 +71,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -47,4 +47,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -39,4 +39,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -67,4 +67,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -40,4 +40,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -23,4 +23,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -17,4 +17,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -71,4 +71,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -43,4 +43,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -32,4 +32,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -27,4 +27,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -23,4 +23,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -19,4 +19,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -19,4 +19,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
|
|
@ -19,4 +19,4 @@
|
|||
}
|
||||
],
|
||||
"type": "components:ui"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user