calendar

Appointment calendar

Appointment calendar

Загрузка превью…

src/calendar/c-calendar-19.vue

<script setup lang="ts">
import { ref } from "vue"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Calendar as CCalendar } from "@/components/ui/calendar"

const fixedDate = new Date(2026, 0, 15)
const date = ref<Date | undefined>(fixedDate)
const selectedTime = ref<string | null>("10:00")

const timeSlots = Array.from({ length: 37 }, (_, i) => {
  const totalMinutes = i * 15
  const hour = Math.floor(totalMinutes / 60) + 9
  const minute = totalMinutes % 60
  return `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`
})

const bookedDates = [0, 1, 2].map((i) => new Date(fixedDate.getFullYear(), fixedDate.getMonth(), fixedDate.getDate() + i))

function isBooked(d: Date): boolean {
  return bookedDates.some((b) => b.getFullYear() === d.getFullYear() && b.getMonth() === d.getMonth() && b.getDate() === d.getDate())
}

const bookingText = new Intl.DateTimeFormat("en-US", { weekday: "long", day: "numeric", month: "long", timeZone: "UTC" }).format(
  new Date(Date.UTC(fixedDate.getFullYear(), fixedDate.getMonth(), fixedDate.getDate()))
)
</script>

<template>
  <Card class="gap-0 p-0">
    <CardHeader class="flex h-max items-center justify-start border-b px-4! py-3!">
      <CardTitle>Book your appointment</CardTitle>
    </CardHeader>
    <CardContent class="relative p-0 md:pr-48">
      <div class="p-4">
        <CCalendar mode="single" v-model="date" :month="fixedDate" :disabled-fn="isBooked" />
      </div>
      <div class="inset-y-0 right-0 flex w-full flex-col gap-4 border-t max-md:h-60 md:absolute md:w-48 md:border-t-0 md:border-l">
        <ScrollArea class="h-full">
          <div class="flex flex-col gap-2 p-4">
            <Button
              v-for="time in timeSlots"
              :key="time"
              :variant="selectedTime === time ? 'default' : 'outline'"
              class="w-full shadow-none"
              @click="selectedTime = time"
            >
              {{ time }}
            </Button>
          </div>
        </ScrollArea>
      </div>
    </CardContent>
    <CardFooter class="flex flex-col gap-4 border-t px-4 py-3! md:flex-row">
      <div class="flex max-w-64 items-center gap-2 text-sm">
        <template v-if="date && selectedTime">
          <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="size-4 shrink-0">
            <circle cx="12" cy="12" r="10" />
            <path d="m9 12 2 2 4-4" />
          </svg>
          <span class="text-sm">
            Your meeting is booked for
            <span class="font-medium"> {{ bookingText }} </span>
            at <span class="font-medium">{{ selectedTime }}</span>
          </span>
        </template>
        <template v-else>Select a date and time for your meeting.</template>
      </div>
      <Button :disabled="!date || !selectedTime" class="w-full md:ml-auto md:w-auto" variant="outline">Confirm</Button>
    </CardFooter>
  </Card>
</template>

Установка

npx shadcn-vue@latest add https://revueui.rootapi.dev/r/c-calendar-19.json

Зависимости реестра

Источник: порт из ReUI (Keenthemes, MIT)