date-fns(Vue 3)를 사용하는 캘린더 구성 요소 예제

때때로 우리는 사용자 정의 달력 보기를 구현해야 하며 date-fns를 사용하면 생각만큼 무섭지 않습니다.

먼저 몇 가지 코드를 살펴보겠습니다.

const currentDate = ref(startOfDay(new Date()));

const days = computed(() => {
  const monthStart = startOfMonth(currentDate.value);
  const dayNumInWeek = getDay(monthStart);

  const calendarStart = subDays(monthStart, dayNumInWeek !== 0 ? dayNumInWeek - 1 : 6);

  return eachDayOfInterval({
    start: calendarStart,
    end: addDays(calendarStart, 41)
  }).map((date) => {{
    return {
      isCurrent: compareAsc(currentDate.value, date) === 0,
      isCurrentMonth: date.getMonth() === currentDate.value.getMonth(),
      date: date
    }
  }});
});


이해하기 어렵지 않고 꽤 단순해 보입니다. 그러나 즉시 이해가 되지 않는다면 단계별로 설명하겠습니다.

먼저 이번 달의 시작과 끝을 가져와야 합니다.

const monthStart = startOfMonth(currentDate.value);
const monthEnd = endOfMonth(currentDate.value);


우리의 monthStart 및 monthEnd는 Date 개체에 해당 월의 시작 날짜와 종료 날짜가 포함되어 있습니다. 따라서 이번 달이 시작되는 날짜를 알 수 있습니다.

const dayNumInWeek = getDay(monthStart)


이제 이를 사용하여 캘린더의 시작 날짜를 얻을 수 있습니다.

const calendarStart = subDays(monthStart, dayNumInWeek !== 0 ? dayNumInWeek - 1 : 6);


달력에 7주를 표시하고 이번 달의 첫 번째 날은 어느 요일이든 될 수 있기 때문에 필요합니다. 때로는 주 컨텍스트에서 해당 월의 첫 번째 날이 0(일요일)일 수 있으며 이러한 일이 발생하면 6일을 줄여야 합니다.

마지막으로 우리는 한 번에 42일을 표시하고 유용한 개체로 매핑하기를 원하기 때문에 calendarStart와 calendarStart + 41 사이의 간격을 가져와야 합니다.

eachDayOfInterval({
    start: calendarStart,
    end: addDays(calendarStart, 41)
  }).map((date) => {{
    return {
      isCurrent: compareAsc(currentDate.value, date) === 0,
      isCurrentMonth: date.getMonth() === currentDate.value.getMonth(),
      date: date
    }
  }});



마지막으로 Vue 3 및 Tailwind를 사용하여 전체 예제 코드를 구현해 보겠습니다.

<template>
  <div class="w-60 bg-white  dark:border-[1px] dark:border-gray_border dark:bg-gray_800 rounded-lg shadow-lg">
    <div class="flex justify-center space-x-2 py-3 items-center rounded-lg">
      <button
          type="button"
          class="-my-1.5 flex flex-none items-center justify-center p-1.5"
          @click="toPreviousMonth"
      >
        <span class="sr-only">Previous month</span>
        <!-- Heroicon name: solid/chevron-left -->
        <svg class="h-5 w-5 icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"
             aria-hidden="true">
          <path fill-rule="evenodd"
                d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
                clip-rule="evenodd"/>
        </svg>
      </button>
      <h2 class="font-normal text-gray_800 text-sm dark:text-white font-inter">
        {{ format(currentDate, 'MMMM yyyy') }}
      </h2>
      <button
          type="button"
          class="-my-1.5 -mr-1.5 ml-2 flex flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500"
          @click="toNextMonth"
      >
        <!-- Heroicon name: solid/chevron-right -->
        <svg class="h-5 w-5 icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"
             aria-hidden="true">
          <path fill-rule="evenodd"
                d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
                clip-rule="evenodd"/>
        </svg>
      </button>
    </div>
    <div class="mt-2 grid grid-cols-7 text-center text-sm">
      <div class="text-gray_500 font-inter dark:text-white">Mo</div>
      <div class="text-gray_500 font-inter dark:text-white">Tu</div>
      <div class="text-gray_500 font-inter dark:text-white">We</div>
      <div class="text-gray_500 font-inter dark:text-white">Th</div>
      <div class="text-gray_500 font-inter dark:text-white">Fr</div>
      <div class="text-gray_500 font-inter dark:text-white">Sa</div>
      <div class="text-gray_500 font-inter dark:text-white">Su</div>
    </div>
    <div class="mt-2 grid grid-cols-7 text-sm font-inter">
      <button
          v-for="(day, index) in days" :key="`day-${index}`"
          type="button"
          class="mx-auto flex h-8 w-8 items-center justify-center rounded-lg"
          @click="currentDate = day.date"
          :class="{'bg-blue_400 justify-center rounded-lg': day.isCurrent}"
      >
        <time :class="{
          'text-number_calendar': !day.isCurrentMonth,
          'text-gray_800 dark:text-white': day.isCurrentMonth
        }">
          {{ format(day.date, 'd') }}
        </time>
      </button>
    </div>
  </div>
</template>

<script setup lang="ts">
import {
  subDays,
  addDays,
  eachDayOfInterval,
  format,
  startOfMonth,
  compareAsc,
  startOfDay,
  addMonths,
  subMonths,
  getDay,
} from 'date-fns'
import {computed, ref} from "vue";

const currentDate = ref(startOfDay(new Date()));

const days = computed(() => {
  const monthStart = startOfMonth(currentDate.value);
  const dayNumInWeek = getDay(monthStart);

  const calendarStart = subDays(monthStart, dayNumInWeek !== 0 ? dayNumInWeek - 1 : 6);

  return eachDayOfInterval({
    start: calendarStart,
    end: addDays(calendarStart, 41)
  }).map((date) => {{
    return {
      isCurrent: compareAsc(currentDate.value, date) === 0,
      isCurrentMonth: date.getMonth() === currentDate.value.getMonth(),
      date: date
    }
  }});
});

function toNextMonth() {
  currentDate.value = addMonths(startOfMonth(currentDate.value), 1)
}

function toPreviousMonth() {
  currentDate.value = subMonths(startOfMonth(currentDate.value), 1)
}
</script>

좋은 웹페이지 즐겨찾기