Skip to content

Commit d744c3e

Browse files
fix(Calendar): create yearRange once (#1548)
* docs(components): date-picker example with month and year dropdowns * fix: create default yearRange once * chore: update calendar demos to use layout prop * fix: add fallback value for defaultPlaceholder * chore: update * chore: update deps * chore: remove installation tabs and add note about popover and calendar installation instructions * chore: build registry --------- Co-authored-by: Sadegh Barati <sadeghbaratiwork@gmail.com>
1 parent 95313d0 commit d744c3e

File tree

12 files changed

+495
-288
lines changed

12 files changed

+495
-288
lines changed

apps/v4/components/demo/CalendarDateBirth.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const date = ref(today(getLocalTimeZone())) as Ref<DateValue>
3333
<PopoverContent class="w-auto overflow-hidden p-0" align="start">
3434
<Calendar
3535
:model-value="date"
36+
layout="month-and-year"
3637
@update:model-value="(value) => {
3738
if (value) {
3839
date = value

apps/v4/components/demo/CalendarDemo.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ const date = ref(fromDate(new Date(), getLocalTimeZone())) as Ref<DateValue>
1010
<Calendar
1111
v-model="date"
1212
class="rounded-md border shadow-sm"
13+
layout="month-and-year"
1314
/>
1415
</template>

apps/v4/components/demo/CalendarPersianDemo.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import { Calendar } from '@/registry/new-york-v4/ui/calendar'
88
99
const date = ref(today(getLocalTimeZone())) as Ref<DateValue>
1010
const placeholder = ref(toCalendar(today(getLocalTimeZone()), new PersianCalendar())) as Ref<DateValue>
11+
// or
12+
const defaultPlaceholder = toCalendar(today(getLocalTimeZone()), new PersianCalendar())
13+
1114
const formatter = useDateFormatter('fa')
1215
</script>
1316

apps/v4/components/demo/DatePickerDemo.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import type { DateValue } from '@internationalized/date'
3-
import { DateFormatter, getLocalTimeZone } from '@internationalized/date'
3+
import { DateFormatter, getLocalTimeZone, today } from '@internationalized/date'
44
55
import { CalendarIcon } from 'lucide-vue-next'
66
import { cn } from '@/lib/utils'
@@ -12,6 +12,7 @@ import {
1212
PopoverTrigger,
1313
} from '@/registry/new-york-v4/ui/popover'
1414
15+
const defaultPlaceholder = today(getLocalTimeZone())
1516
const date = ref() as Ref<DateValue>
1617
1718
const df = new DateFormatter('en-US', {
@@ -33,6 +34,8 @@ const df = new DateFormatter('en-US', {
3334
<PopoverContent class="w-auto p-0" align="start">
3435
<Calendar
3536
v-model="date"
37+
:default-placeholder="defaultPlaceholder"
38+
layout="month-and-year"
3639
initial-focus
3740
@update:model-value="close"
3841
/>

apps/v4/content/docs/components/date-picker.md

Lines changed: 10 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,56 +16,19 @@ description: A date picker component.
1616

1717
## Installation
1818

19-
::code-tabs
19+
The Date Picker is built using a composition of the `<Popover />` and the `<Calendar />` components.
2020

21-
::tabs-list
21+
See installation instructions for the [Popover](/docs/components/popover) and the [Calendar](/docs/components/calendar) components.
2222

23-
::tabs-trigger{value="cli"}
24-
CLI
25-
::
2623

27-
::tabs-trigger{value="manual"}
28-
Manual
29-
::
30-
31-
::
32-
33-
::tabs-content{value="cli"}
34-
35-
```bash
36-
npx shadcn-vue@latest add date-picker
37-
```
38-
39-
::
40-
41-
::tabs-content{value="manual"}
42-
::steps
43-
::step
44-
Install the following dependencies:
45-
::
46-
47-
```bash
48-
npm install reka-ui
49-
```
50-
51-
::step
52-
Copy and paste the [GitHub source code](https://github.com/unovue/shadcn-vue/tree/dev/apps/v4/registry/new-york-v4/ui/date-picker) into your project.
53-
::
54-
55-
::step
56-
Update the import paths to match your project setup.
57-
::
58-
::
59-
::
60-
61-
::
6224

6325
## Usage
6426

6527
```vue showLineNumbers
6628
<script setup lang="ts">
6729
import { ref } from 'vue'
6830
import { CalendarIcon } from 'lucide-vue-next'
31+
import { DateFormatter, getLocalTimeZone, today } from '@internationalized/date'
6932
import { cn } from '@/lib/utils'
7033
import { Button } from '@/components/ui/button'
7134
import { Calendar } from '@/components/ui/calendar'
@@ -76,6 +39,7 @@ import {
7639
} from '@/components/ui/popover'
7740
7841
const date = ref<Date>()
42+
const defaultPlaceholder = today(getLocalTimeZone())
7943
</script>
8044
8145
<template>
@@ -93,7 +57,12 @@ const date = ref<Date>()
9357
</Button>
9458
</PopoverTrigger>
9559
<PopoverContent class="w-auto p-0">
96-
<Calendar v-model="date" :initial-focus="true" />
60+
<Calendar
61+
v-model="date"
62+
:initial-focus="true"
63+
:default-placeholder="defaultPlaceholder"
64+
layout="month-and-year"
65+
/>
9766
</PopoverContent>
9867
</Popover>
9968
</template>

apps/v4/nuxt.config.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ export default defineNuxtConfig({
5959
vite: {
6060
plugins: [tailwindcss()],
6161
},
62-
colorMode: {
63-
classSuffix: '',
64-
},
6562
build: {
6663
transpile: ['vee-validate', 'vue-sonner'],
6764
},

apps/v4/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@
1515
"dependencies": {
1616
"@dnd-kit/abstract": "^0.1.21",
1717
"@internationalized/date": "catalog:",
18-
"@nuxt/content": "^3.8.0",
19-
"@nuxt/fonts": "0.11.4",
18+
"@nuxt/content": "^3.8.2",
19+
"@nuxt/fonts": "0.12.1",
2020
"@nuxt/image": "2.0.0",
2121
"@tabler/icons-vue": "^3.35.0",
2222
"@tailwindcss/vite": "^4.1.17",
23-
"@tanstack/vue-form": "^1.23.8",
23+
"@tanstack/vue-form": "^1.25.0",
2424
"@tanstack/vue-table": "^8.21.3",
2525
"@unhead/vue": "^2.0.19",
26-
"@unovis/ts": "^1.6.1",
27-
"@unovis/vue": "^1.6.1",
26+
"@unovis/ts": "^1.6.2",
27+
"@unovis/vue": "^1.6.2",
2828
"@vee-validate/zod": "catalog:",
2929
"@vueuse/core": "catalog:",
3030
"chrono-node": "^2.9.0",
@@ -51,7 +51,7 @@
5151
"zod": "catalog:"
5252
},
5353
"devDependencies": {
54-
"@nuxtjs/color-mode": "^3.5.2",
54+
"@nuxtjs/color-mode": "^4.0.0",
5555
"nuxt-shiki": "^0.3.1",
5656
"oxc-parser": "catalog:",
5757
"pathe": "catalog:",

apps/v4/public/r/styles/new-york-v4/calendar.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"files": [
1414
{
1515
"path": "registry/new-york-v4/ui/calendar/Calendar.vue",
16-
"content": "<script lang=\"ts\" setup>\nimport type { CalendarRootEmits, CalendarRootProps, DateValue } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport type { LayoutTypes } from \".\"\nimport { createReusableTemplate, reactiveOmit, useVModel } from \"@vueuse/core\"\nimport { CalendarRoot, useDateFormatter, useForwardPropsEmits } from \"reka-ui\"\nimport { createYear, createYearRange, toDate } from \"reka-ui/date\"\nimport { cn } from \"@/lib/utils\"\nimport { NativeSelect, NativeSelectOption } from \"@/registry/new-york-v4/ui/native-select\"\nimport { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNextButton, CalendarPrevButton } from \".\"\n\nconst props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes[\"class\"], layout?: LayoutTypes, yearRange?: DateValue[] }>(), {\n modelValue: undefined,\n layout: undefined,\n})\nconst emits = defineEmits<CalendarRootEmits>()\n\nconst delegatedProps = reactiveOmit(props, \"class\", \"layout\", \"placeholder\")\n\nconst placeholder = useVModel(props, \"placeholder\", emits, {\n passive: true,\n defaultValue: props.defaultPlaceholder,\n}) as Ref<DateValue>\n\nconst formatter = useDateFormatter(props.locale ?? \"en\")\n\nconst [DefineMonthTemplate, ReuseMonthTemplate] = createReusableTemplate<{ date: DateValue }>()\nconst [DefineYearTemplate, ReuseYearTemplate] = createReusableTemplate<{ date: DateValue }>()\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <DefineMonthTemplate v-slot=\"{ date }\">\n <div class=\"**:data-[slot=native-select-icon]:right-1\">\n <div class=\"relative\">\n <div class=\"absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none\">\n {{ formatter.custom(toDate(date), { month: 'short' }) }}\n </div>\n <NativeSelect\n class=\"text-xs h-8 pr-6 pl-2 text-transparent relative\"\n @change=\"(e: Event) => {\n placeholder = placeholder.set({\n month: Number((e?.target as any)?.value),\n })\n }\"\n >\n <NativeSelectOption v-for=\"(month) in createYear({ dateObj: date })\" :key=\"month.toString()\" :value=\"month.month\" :selected=\"date.month === month.month\">\n {{ formatter.custom(toDate(month), { month: 'short' }) }}\n </NativeSelectOption>\n </NativeSelect>\n </div>\n </div>\n </DefineMonthTemplate>\n\n <DefineYearTemplate v-slot=\"{ date }\">\n <div class=\"**:data-[slot=native-select-icon]:right-1\">\n <div class=\"relative\">\n <div class=\"absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none\">\n {{ formatter.custom(toDate(date), { year: 'numeric' }) }}\n </div>\n <NativeSelect\n class=\"text-xs h-8 pr-6 pl-2 text-transparent relative\"\n @change=\"(e: Event) => {\n placeholder = placeholder.set({\n year: Number((e?.target as any)?.value),\n })\n }\"\n >\n <NativeSelectOption v-for=\"(year) in yearRange ?? createYearRange({ start: date.cycle('year', -100), end: date })\" :key=\"year.toString()\" :value=\"year.year\" :selected=\"date.year === year.year\">\n {{ formatter.custom(toDate(year), { year: 'numeric' }) }}\n </NativeSelectOption>\n </NativeSelect>\n </div>\n </div>\n </DefineYearTemplate>\n\n <CalendarRoot\n v-slot=\"{ grid, weekDays, date }\"\n v-bind=\"forwarded\"\n v-model:placeholder=\"placeholder\"\n data-slot=\"calendar\"\n :class=\"cn('p-3', props.class)\"\n >\n <CalendarHeader class=\"pt-0\">\n <nav class=\"flex items-center gap-1 absolute top-0 inset-x-0 justify-between\">\n <CalendarPrevButton>\n <slot name=\"calendar-prev-icon\" />\n </CalendarPrevButton>\n <CalendarNextButton>\n <slot name=\"calendar-next-icon\" />\n </CalendarNextButton>\n </nav>\n\n <slot name=\"calendar-heading\" :date=\"date\" :month=\"ReuseMonthTemplate\" :year=\"ReuseYearTemplate\">\n <template v-if=\"layout === 'month-and-year'\">\n <div class=\"flex items-center justify-center gap-1\">\n <ReuseMonthTemplate :date=\"date\" />\n <ReuseYearTemplate :date=\"date\" />\n </div>\n </template>\n <template v-else-if=\"layout === 'month-only'\">\n <div class=\"flex items-center justify-center gap-1\">\n <ReuseMonthTemplate :date=\"date\" />\n {{ formatter.custom(toDate(date), { year: 'numeric' }) }}\n </div>\n </template>\n <template v-else-if=\"layout === 'year-only'\">\n <div class=\"flex items-center justify-center gap-1\">\n {{ formatter.custom(toDate(date), { month: 'short' }) }}\n <ReuseYearTemplate :date=\"date\" />\n </div>\n </template>\n <template v-else>\n <CalendarHeading />\n </template>\n </slot>\n </CalendarHeader>\n\n <div class=\"flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0\">\n <CalendarGrid v-for=\"month in grid\" :key=\"month.value.toString()\">\n <CalendarGridHead>\n <CalendarGridRow>\n <CalendarHeadCell\n v-for=\"day in weekDays\" :key=\"day\"\n >\n {{ day }}\n </CalendarHeadCell>\n </CalendarGridRow>\n </CalendarGridHead>\n <CalendarGridBody>\n <CalendarGridRow v-for=\"(weekDates, index) in month.rows\" :key=\"`weekDate-${index}`\" class=\"mt-2 w-full\">\n <CalendarCell\n v-for=\"weekDate in weekDates\"\n :key=\"weekDate.toString()\"\n :date=\"weekDate\"\n >\n <CalendarCellTrigger\n :day=\"weekDate\"\n :month=\"month.value\"\n />\n </CalendarCell>\n </CalendarGridRow>\n </CalendarGridBody>\n </CalendarGrid>\n </div>\n </CalendarRoot>\n</template>\n",
16+
"content": "<script lang=\"ts\" setup>\nimport type { CalendarRootEmits, CalendarRootProps, DateValue } from \"reka-ui\"\nimport type { HTMLAttributes } from \"vue\"\nimport type { LayoutTypes } from \".\"\nimport { getLocalTimeZone, today } from \"@internationalized/date\"\nimport { createReusableTemplate, reactiveOmit, useVModel } from \"@vueuse/core\"\nimport { CalendarRoot, useDateFormatter, useForwardPropsEmits } from \"reka-ui\"\nimport { createYear, createYearRange, toDate } from \"reka-ui/date\"\nimport { cn } from \"@/lib/utils\"\nimport { NativeSelect, NativeSelectOption } from \"@/registry/new-york-v4/ui/native-select\"\nimport { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNextButton, CalendarPrevButton } from \".\"\n\nconst props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes[\"class\"], layout?: LayoutTypes, yearRange?: DateValue[] }>(), {\n modelValue: undefined,\n layout: undefined,\n})\nconst emits = defineEmits<CalendarRootEmits>()\n\nconst delegatedProps = reactiveOmit(props, \"class\", \"layout\", \"placeholder\")\n\nconst placeholder = useVModel(props, \"placeholder\", emits, {\n passive: true,\n defaultValue: props.defaultPlaceholder ?? today(getLocalTimeZone()),\n}) as Ref<DateValue>\n\nconst formatter = useDateFormatter(props.locale ?? \"en\")\n\nconst yearRange = computed(() => {\n return props.yearRange ?? createYearRange({\n start: (toRaw(props.placeholder) ?? props.defaultPlaceholder ?? today(getLocalTimeZone()))\n .cycle(\"year\", -100),\n\n end: today(getLocalTimeZone()),\n })\n})\n\nconst [DefineMonthTemplate, ReuseMonthTemplate] = createReusableTemplate<{ date: DateValue }>()\nconst [DefineYearTemplate, ReuseYearTemplate] = createReusableTemplate<{ date: DateValue }>()\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <DefineMonthTemplate v-slot=\"{ date }\">\n <div class=\"**:data-[slot=native-select-icon]:right-1\">\n <div class=\"relative\">\n <div class=\"absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none\">\n {{ formatter.custom(toDate(date), { month: 'short' }) }}\n </div>\n <NativeSelect\n class=\"text-xs h-8 pr-6 pl-2 text-transparent relative\"\n @change=\"(e: Event) => {\n placeholder = placeholder.set({\n month: Number((e?.target as any)?.value),\n })\n }\"\n >\n <NativeSelectOption v-for=\"(month) in createYear({ dateObj: date })\" :key=\"month.toString()\" :value=\"month.month\" :selected=\"date.month === month.month\">\n {{ formatter.custom(toDate(month), { month: 'short' }) }}\n </NativeSelectOption>\n </NativeSelect>\n </div>\n </div>\n </DefineMonthTemplate>\n\n <DefineYearTemplate v-slot=\"{ date }\">\n <div class=\"**:data-[slot=native-select-icon]:right-1\">\n <div class=\"relative\">\n <div class=\"absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none\">\n {{ formatter.custom(toDate(date), { year: 'numeric' }) }}\n </div>\n <NativeSelect\n class=\"text-xs h-8 pr-6 pl-2 text-transparent relative\"\n @change=\"(e: Event) => {\n placeholder = placeholder.set({\n year: Number((e?.target as any)?.value),\n })\n }\"\n >\n <NativeSelectOption v-for=\"(year) in yearRange\" :key=\"year.toString()\" :value=\"year.year\" :selected=\"date.year === year.year\">\n {{ formatter.custom(toDate(year), { year: 'numeric' }) }}\n </NativeSelectOption>\n </NativeSelect>\n </div>\n </div>\n </DefineYearTemplate>\n\n <CalendarRoot\n v-slot=\"{ grid, weekDays, date }\"\n v-bind=\"forwarded\"\n v-model:placeholder=\"placeholder\"\n data-slot=\"calendar\"\n :class=\"cn('p-3', props.class)\"\n >\n <CalendarHeader class=\"pt-0\">\n <nav class=\"flex items-center gap-1 absolute top-0 inset-x-0 justify-between\">\n <CalendarPrevButton>\n <slot name=\"calendar-prev-icon\" />\n </CalendarPrevButton>\n <CalendarNextButton>\n <slot name=\"calendar-next-icon\" />\n </CalendarNextButton>\n </nav>\n\n <slot name=\"calendar-heading\" :date=\"date\" :month=\"ReuseMonthTemplate\" :year=\"ReuseYearTemplate\">\n <template v-if=\"layout === 'month-and-year'\">\n <div class=\"flex items-center justify-center gap-1\">\n <ReuseMonthTemplate :date=\"date\" />\n <ReuseYearTemplate :date=\"date\" />\n </div>\n </template>\n <template v-else-if=\"layout === 'month-only'\">\n <div class=\"flex items-center justify-center gap-1\">\n <ReuseMonthTemplate :date=\"date\" />\n {{ formatter.custom(toDate(date), { year: 'numeric' }) }}\n </div>\n </template>\n <template v-else-if=\"layout === 'year-only'\">\n <div class=\"flex items-center justify-center gap-1\">\n {{ formatter.custom(toDate(date), { month: 'short' }) }}\n <ReuseYearTemplate :date=\"date\" />\n </div>\n </template>\n <template v-else>\n <CalendarHeading />\n </template>\n </slot>\n </CalendarHeader>\n\n <div class=\"flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0\">\n <CalendarGrid v-for=\"month in grid\" :key=\"month.value.toString()\">\n <CalendarGridHead>\n <CalendarGridRow>\n <CalendarHeadCell\n v-for=\"day in weekDays\" :key=\"day\"\n >\n {{ day }}\n </CalendarHeadCell>\n </CalendarGridRow>\n </CalendarGridHead>\n <CalendarGridBody>\n <CalendarGridRow v-for=\"(weekDates, index) in month.rows\" :key=\"`weekDate-${index}`\" class=\"mt-2 w-full\">\n <CalendarCell\n v-for=\"weekDate in weekDates\"\n :key=\"weekDate.toString()\"\n :date=\"weekDate\"\n >\n <CalendarCellTrigger\n :day=\"weekDate\"\n :month=\"month.value\"\n />\n </CalendarCell>\n </CalendarGridRow>\n </CalendarGridBody>\n </CalendarGrid>\n </div>\n </CalendarRoot>\n</template>\n",
1717
"type": "registry:ui"
1818
},
1919
{

apps/v4/registry/new-york-v4/ui/calendar/Calendar.vue

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import type { CalendarRootEmits, CalendarRootProps, DateValue } from "reka-ui"
33
import type { HTMLAttributes } from "vue"
44
import type { LayoutTypes } from "."
5+
import { getLocalTimeZone, today } from "@internationalized/date"
56
import { createReusableTemplate, reactiveOmit, useVModel } from "@vueuse/core"
67
import { CalendarRoot, useDateFormatter, useForwardPropsEmits } from "reka-ui"
78
import { createYear, createYearRange, toDate } from "reka-ui/date"
@@ -19,11 +20,20 @@ const delegatedProps = reactiveOmit(props, "class", "layout", "placeholder")
1920
2021
const placeholder = useVModel(props, "placeholder", emits, {
2122
passive: true,
22-
defaultValue: props.defaultPlaceholder,
23+
defaultValue: props.defaultPlaceholder ?? today(getLocalTimeZone()),
2324
}) as Ref<DateValue>
2425
2526
const formatter = useDateFormatter(props.locale ?? "en")
2627
28+
const yearRange = computed(() => {
29+
return props.yearRange ?? createYearRange({
30+
start: (toRaw(props.placeholder) ?? props.defaultPlaceholder ?? today(getLocalTimeZone()))
31+
.cycle("year", -100),
32+
33+
end: today(getLocalTimeZone()),
34+
})
35+
})
36+
2737
const [DefineMonthTemplate, ReuseMonthTemplate] = createReusableTemplate<{ date: DateValue }>()
2838
const [DefineYearTemplate, ReuseYearTemplate] = createReusableTemplate<{ date: DateValue }>()
2939
@@ -67,7 +77,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
6777
})
6878
}"
6979
>
70-
<NativeSelectOption v-for="(year) in yearRange ?? createYearRange({ start: date.cycle('year', -100), end: date })" :key="year.toString()" :value="year.year" :selected="date.year === year.year">
80+
<NativeSelectOption v-for="(year) in yearRange" :key="year.toString()" :value="year.year" :selected="date.year === year.year">
7181
{{ formatter.custom(toDate(year), { year: 'numeric' }) }}
7282
</NativeSelectOption>
7383
</NativeSelect>

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,11 @@
6868
"esbuild",
6969
"maplibre-gl",
7070
"msw",
71+
"sharp",
7172
"simple-git-hooks",
7273
"unrs-resolver",
73-
"vue-demi"
74+
"vue-demi",
75+
"workerd"
7476
]
7577
},
7678
"simple-git-hooks": {

0 commit comments

Comments
 (0)