calendar
Custom navigation with year view
Custom navigation with year view
Загрузка превью…
src/calendar/c-calendar-13.vue
<!--
АДАПТАЦИЯ (docs/PORTING.md §4a/§5): оригинал подменяет внутренние
`react-day-picker`-компоненты `CaptionLabel`/`MonthGrid`, накладывая
поверх сетки дней отдельный слой выбора года/месяца. `reka-ui`
`CalendarRoot` не даёт способа подменить `MonthGrid` изнутри — здесь тот
же оверлей (год → месяц, `ScrollArea`, кнопки) собран вокруг `CCalendar`
как обычный `position: absolute` слой поверх неё, тем же приёмом, что и
в оригинале (`bg-background absolute inset-0 z-20`). Логика переключения
(клик по подписи месяца → список лет → список месяцев) перенесена без
изменений; месяц/сегодня зафиксированы явно, без `new Date()`.
-->
<script setup lang="ts">
import { ref, computed } from "vue"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Calendar as CCalendar } from "@/components/ui/calendar"
const today = new Date(2026, 0, 15)
const month = ref<Date>(today)
const date = ref<Date | undefined>(today)
const isYearView = ref(false)
const selectedYear = ref<number | null>(null)
const startYear = today.getFullYear() - 10
const endYear = today.getFullYear() + 10
const years = Array.from({ length: endYear - startYear + 1 }, (_, i) => startYear + i)
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
const caption = computed(() =>
// en-US явно: react-day-picker форматирует подписи на английском
// независимо от локали окружения (см. Calendar.vue (@revueui/ui)/effectiveLocale).
new Intl.DateTimeFormat("en-US", { month: "long", year: "numeric" }).format(month.value)
)
function toggleYearView() {
isYearView.value = !isYearView.value
if (!isYearView.value) selectedYear.value = null
}
function pickYear(y: number) {
selectedYear.value = y
}
function pickMonth(mIndex: number) {
if (selectedYear.value === null) return
month.value = new Date(selectedYear.value, mIndex, 1)
isYearView.value = false
selectedYear.value = null
}
function isMonthDisabled(y: number, mIndex: number): boolean {
const d = new Date(y, mIndex, 1)
return d < new Date(startYear, 0, 1) || d > new Date(endYear, 11, 31)
}
</script>
<template>
<Card class="p-0">
<CardContent class="p-0">
<div class="relative">
<CCalendar
mode="single"
v-model="date"
:month="month"
@update:month="month = $event"
caption-class="ms-2.5 justify-start"
nav-class="flex items-center w-full absolute inset-x-0 justify-end pointer-events-none [&>button]:pointer-events-auto"
>
<template #caption>
<div class="flex h-(--cell-size) w-full items-center px-(--cell-size) ms-2.5 justify-start">
<Button
class="-ms-2 flex items-center gap-2 text-sm font-medium hover:bg-transparent"
:data-state="isYearView ? 'open' : 'closed'"
size="sm"
variant="ghost"
@click="toggleYearView"
>
{{ caption }}
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" :class="['text-muted-foreground/80 shrink-0 transition-transform duration-200', isYearView && 'rotate-180']">
<path d="m6 9 6 6 6-6" />
</svg>
</Button>
</div>
</template>
</CCalendar>
<div v-if="isYearView" class="bg-background absolute inset-0 z-20 -m-2">
<div class="h-full">
<ScrollArea class="h-full">
<div class="px-3 pt-1 pb-3">
<div v-if="selectedYear === null" class="grid grid-cols-4 gap-2">
<Button
v-for="y in years"
:key="y"
:variant="y === month.getFullYear() ? 'default' : 'outline'"
size="sm"
class="h-8"
@click="pickYear(y)"
>
{{ y }}
</Button>
</div>
<div v-else class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<Button variant="ghost" size="sm" class="px-2" @click="selectedYear = null">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-1 size-4 rotate-90">
<path d="m6 9 6 6 6-6" />
</svg>
{{ selectedYear }}
</Button>
</div>
<div class="grid grid-cols-3 gap-2">
<Button
v-for="(m, mi) in monthNames"
:key="m"
:variant="mi === month.getMonth() && selectedYear === month.getFullYear() ? 'default' : 'outline'"
size="sm"
class="h-8"
:disabled="isMonthDisabled(selectedYear, mi)"
@click="pickMonth(mi)"
>
{{ m }}
</Button>
</div>
</div>
</div>
</ScrollArea>
</div>
</div>
</div>
</CardContent>
</Card>
</template>
Установка
npx shadcn-vue@latest add https://revueui.rootapi.dev/r/c-calendar-13.jsonЗависимости реестра
Источник: порт из ReUI (Keenthemes, MIT)