コンテンツにスキップ

カレンダー

アルファ
日付と曜日を表示し、日付関連の操作を容易にします。
2024年10月
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
イベント日、2024年10月
vue
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNext, CalendarPrev, CalendarRoot, type CalendarRootProps } from 'radix-vue'

const isDateUnavailable: CalendarRootProps['isDateUnavailable'] = (date) => {
  return date.day === 17 || date.day === 18
}
</script>

<template>
  <CalendarRoot
    v-slot="{ weekDays, grid }"
    :is-date-unavailable="isDateUnavailable"
    class="mt-6 rounded-xl bg-white p-4 shadow-md"
    fixed-weeks
  >
    <CalendarHeader class="flex items-center justify-between">
      <CalendarPrev
        class="inline-flex items-center cursor-pointer text-black justify-center rounded-[9px] bg-transparent w-8 h-8 hover:bg-black hover:text-white active:scale-98 active:transition-all focus:shadow-[0_0_0_2px] focus:shadow-black"
      >
        <Icon
          icon="radix-icons:chevron-left"
          class="w-6 h-6"
        />
      </CalendarPrev>
      <CalendarHeading class="text-[15px] text-black font-medium" />

      <CalendarNext
        class="inline-flex items-center cursor-pointer justify-center text-black rounded-[9px] bg-transparent w-8 h-8 hover:bg-black hover:text-white active:scale-98 active:transition-all focus:shadow-[0_0_0_2px] focus:shadow-black"
      >
        <Icon
          icon="radix-icons:chevron-right"
          class="w-6 h-6"
        />
      </CalendarNext>
    </CalendarHeader>
    <div
      class="flex flex-col space-y-4 pt-4 sm:flex-row sm:space-x-4 sm:space-y-0"
    >
      <CalendarGrid
        v-for="month in grid"
        :key="month.value.toString()"
        class="w-full border-collapse select-none space-y-1"
      >
        <CalendarGridHead>
          <CalendarGridRow class="mb-1 grid w-full grid-cols-7">
            <CalendarHeadCell
              v-for="day in weekDays"
              :key="day"
              class="rounded-md text-xs text-green8"
            >
              {{ 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"
              class="relative text-center text-sm"
            >
              <CalendarCellTrigger
                :day="weekDate"
                :month="month.value"
                class="relative flex items-center justify-center rounded-full whitespace-nowrap text-sm font-normal text-black w-8 h-8 outline-none focus:shadow-[0_0_0_2px] focus:shadow-black data-[disabled]:text-black/30 data-[selected]:!bg-green10 data-[selected]:text-white hover:bg-green5 data-[highlighted]:bg-green5 data-[unavailable]:pointer-events-none data-[unavailable]:text-black/30 data-[unavailable]:line-through before:absolute before:top-[5px] before:hidden before:rounded-full before:w-1 before:h-1 before:bg-white data-[today]:before:block data-[today]:before:bg-green9 "
              />
            </CalendarCell>
          </CalendarGridRow>
        </CalendarGridBody>
      </CalendarGrid>
    </div>
  </CalendarRoot>
</template>

クレジット

このコンポーネントは、melt-uiの実装からインスピレーションを得て作成されました。

機能

  • フルキーボードナビゲーション
  • 制御可能または非制御可能
  • フォーカスは完全に管理されます
  • ローカリゼーション対応
  • 高度な構成性

序文

このコンポーネントは、JavaScript での日付と時刻の操作に伴う多くの問題を解決する @internationalized/date パッケージに依存しています。

このパッケージのドキュメントをよく読んで、その動作をしっかりと理解することをお勧めします。また、日付関連のコンポーネントを使用するには、プロジェクトにインストールする必要があります。

インストール

日付パッケージをインストールします。

sh
$ npm add @internationalized/date

コマンドラインからコンポーネントをインストールします。

sh
$ npm add radix-vue

構造

すべてのパーツをインポートし、それらを組み合わせます。

vue
<script setup>
import {
  CalendarCell,
  CalendarCellTrigger,
  CalendarGrid,
  CalendarGridBody,
  CalendarGridHead,
  CalendarGridRow,
  CalendarHeadCell,
  CalendarHeader,
  CalendarHeading,

  CalendarNext,
  CalendarPrev,
  CalendarRoot
} from 'radix-vue'
</script>

<template>
  <CalendarRoot>
    <CalendarHeader>
      <CalendarPrev />
      <CalendarHeading />
      <CalendarNext />
    </CalendarHeader>
    <CalendarGrid>
      <CalendarGridHead>
        <CalendarGridRow>
          <CalendarHeadCell />
        </CalendarGridRow>
      </CalendarGridHead>
      <CalendarGridBody>
        <CalendarGridRow>
          <CalendarCell>
            <CalendarCellTrigger />
          </CalendarCell>
        </CalendarGridRow>
      </CalendarGridBody>
    </CalendarGrid>
  </CalendarRoot>
</template>

APIリファレンス

ルート

カレンダーのすべてのパーツが含まれます

プロパティデフォルトタイプ
as
'div'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

calendarLabel
文字列

カレンダーのアクセシブルなラベル

defaultPlaceholder
DateValue

デフォルトのプレースホルダー日付

defaultValue
DateValue

カレンダーのデフォルト値

dir
'ltr' | 'rtl'

該当する場合のカレンダーの読み取り方向。
省略した場合、ConfigProviderからグローバルに継承するか、LTR(左から右)の読み取りモードを想定します。

disabled
false
ブール値

カレンダーが無効かどうか

fixedWeeks
false
ブール値

カレンダーに常に6週間表示するかどうか

initialFocus
false
ブール値

trueの場合、カレンダーは、カレンダーがマウントされたときに表示されているものに応じて、選択された日、今日、または月の最初の日をフォーカスします

isDateDisabled
マッチャー

日付が無効かどうかを返す関数

isDateUnavailable
マッチャー

日付が使用できないかどうかを返す関数

locale
'en'
文字列

日付の書式設定に使用するロケール

maxValue
DateValue

選択できる最大日付

minValue
DateValue

選択できる最小日付

modelValue
DateValue | DateValue[]

カレンダーの制御されたチェック状態。v-modelとしてバインドできます。

multiple
false
ブール値

複数の日付を選択できるかどうか

nextPage
((placeholder: DateValue) => DateValue)

カレンダーの次のページを返す関数。コンポーネント内で現在のプレースホルダーを引数として受け取ります。

numberOfMonths
1
数値

一度に表示する月の数

pagedNavigation
false
ブール値

このプロパティを使用すると、前のボタンと次のボタンは、1か月ごとではなく、一度に表示される月の数で移動します

placeholder
DateValue

日付が選択されていないときにどの月を表示するかを決定するために使用されるプレースホルダー日付。これはユーザーがカレンダーを移動すると更新され、カレンダービューをプログラムで制御するために使用できます

preventDeselect
false
ブール値

ユーザーが最初に別の日付を選択せずに日付の選択を解除するのを防ぐかどうか

prevPage
((placeholder: DateValue) => DateValue)

カレンダーの前のページを返す関数。コンポーネント内で現在のプレースホルダーを引数として受け取ります。

readonly
false
ブール値

カレンダーが読み取り専用かどうか

weekdayFormat
'narrow'
'narrow' | 'short' | 'long'

weekdaysスロットプロパティを介して提供される曜日の文字列に使用する形式

weekStartsOn
0
0 | 1 | 2 | 3 | 4 | 5 | 6

カレンダーを開始する曜日

エミットペイロード
update:modelValue
[date: DateValue]

モデル値が変更されるたびに呼び出されるイベントハンドラー

update:placeholder
[date: DateValue]

プレースホルダー値が変更されるたびに呼び出されるイベントハンドラー

スロット(デフォルト)ペイロード
date
DateValue

プレースホルダーの現在の日付

grid
Grid<DateValue>[]

日付のグリッド

weekDays
string[]

曜日

weekStartsOn
0 | 1 | 2 | 3 | 4 | 5 | 6

週の開始日

locale
文字列

カレンダーのロケール

fixedWeeks
ブール値

カレンダーに常に6週間表示するかどうか

データ属性
[data-readonly]読み取り専用の場合に存在します
[data-disabled]無効の場合に存在します
[data-invalid]無効の場合に存在します

ナビゲーションボタンと見出しセグメントが含まれています。

プロパティデフォルトタイプ
as
'div'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

前のボタン

カレンダーのナビゲーションボタン。現在のカレンダービューに基づいて、カレンダーを1か月/年/10年前の過去に移動します。

プロパティデフォルトタイプ
as
'button'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

prevPage
((placeholder: DateValue) => DateValue)

前のページに使用する関数。CalendarRootで設定されたprevPage関数を上書きします。

step
'month'
'month' | 'year'

戻るカレンダーの単位

データ属性
[data-disabled]無効の場合に存在します

次のボタン

カレンダーのナビゲーションボタン。現在のカレンダービューに基づいて、カレンダーを1か月/年/10年先の未来に移動します。

プロパティデフォルトタイプ
as
'button'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

nextPage
((placeholder: DateValue) => DateValue)

次のページに使用する関数。CalendarRootで設定されたnextPage関数を上書きします。

step
'month'
'month' | 'year'

進むカレンダーの単位

データ属性
[data-disabled]無効の場合に存在します

見出し

現在の月と年を表示するための見出し

プロパティデフォルトタイプ
as
'div'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

スロット(デフォルト)ペイロード
headingValue
文字列

現在の月と年

データ属性
[data-disabled]無効の場合に存在します

グリッド

カレンダーグリッドをラップするためのコンテナー。

プロパティデフォルトタイプ
as
'table'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

データ属性
[data-readonly]読み取り専用の場合に存在します
[data-disabled]無効の場合に存在します

グリッドヘッダー

グリッドヘッダーをラップするためのコンテナー。

プロパティデフォルトタイプ
as
'thead'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

グリッドボディ

グリッドボディをラップするためのコンテナー。

プロパティデフォルトタイプ
as
'tbody'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

グリッド行

グリッド行をラップするためのコンテナー。

プロパティデフォルトタイプ
as
'tr'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

ヘッダーセル

ヘッダーセルをラップするためのコンテナー。曜日を表示するために使用されます。

プロパティデフォルトタイプ
as
'th'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

セル

カレンダーセルをラップするためのコンテナー。

プロパティデフォルトタイプ
as
'td'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

date*
DateValue

セルの日付値

データ属性
[data-disabled]無効の場合に存在します

セルトリガー

セル日付を表示するためのインタラクティブなコンテナー。クリックすると日付が選択されます。

プロパティデフォルトタイプ
as
'div'
AsTag | コンポーネント

このコンポーネントがレンダリングする要素またはコンポーネント。asChildで上書きできます

asChild
ブール値

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細については、コンポジションガイドをご覧ください。

day*
DateValue

セルトリガーに提供される日付値

month*
DateValue

セルがレンダリングされる月

スロット(デフォルト)ペイロード
dayValue
文字列

現在の日

データ属性
[data-selected]選択されている場合に存在します
[data-value]日付のISO文字列値。
[data-disabled]無効の場合に存在します
[data-unavailable]使用できない場合に存在します
[data-today]今日の場合に存在します
[data-outside-view]日付が表示されている現在の月の外にある場合に存在します。
[data-outside-visible-view]カレンダーに表示されている月以外の日付の場合に表示されます。
[data-focused]フォーカスされている場合に表示されます

年の増減が可能なカレンダー

この例では、年の増減が可能なカレンダーを紹介します。

2024年10月
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
イベント日、2024年10月
vue
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import type { DateValue } from '@internationalized/date'
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNext, CalendarPrev, CalendarRoot, type CalendarRootProps } from 'radix-vue'

const isDateUnavailable: CalendarRootProps['isDateUnavailable'] = (date) => {
  return date.day === 17 || date.day === 18
}

function pagingFunc(date: DateValue, sign: -1 | 1) {
  if (sign === -1)
    return date.subtract({ years: 1 })
  return date.add({ years: 1 })
}
</script>

<template>
  <CalendarRoot
    v-slot="{ weekDays, grid }"
    :is-date-unavailable="isDateUnavailable"
    class="mt-6 rounded-xl bg-white p-4 shadow-md"
    fixed-weeks
  >
    <CalendarHeader class="flex items-center justify-between">
      <CalendarPrev
        class="inline-flex items-center cursor-pointer text-black justify-center rounded-[9px] bg-transparent w-8 h-8 hover:bg-black hover:text-white active:scale-98 active:transition-all focus:shadow-[0_0_0_2px] focus:shadow-black"
        :prev-page="(date: DateValue) => pagingFunc(date, -1)"
      >
        <Icon
          icon="radix-icons:double-arrow-left"
          class="w-6 h-6"
        />
      </CalendarPrev>
      <CalendarPrev
        class="inline-flex items-center cursor-pointer text-black justify-center rounded-[9px] bg-transparent w-8 h-8 hover:bg-black hover:text-white active:scale-98 active:transition-all focus:shadow-[0_0_0_2px] focus:shadow-black"
      >
        <Icon
          icon="radix-icons:chevron-left"
          class="w-6 h-6"
        />
      </CalendarPrev>
      <CalendarHeading class="text-[15px] text-black font-medium" />

      <CalendarNext
        class="inline-flex items-center cursor-pointer justify-center text-black rounded-[9px] bg-transparent w-8 h-8 hover:bg-black hover:text-white active:scale-98 active:transition-all focus:shadow-[0_0_0_2px] focus:shadow-black"
      >
        <Icon
          icon="radix-icons:chevron-right"
          class="w-6 h-6"
        />
      </CalendarNext>

      <CalendarNext
        class="inline-flex items-center cursor-pointer justify-center text-black rounded-[9px] bg-transparent w-8 h-8 hover:bg-black hover:text-white active:scale-98 active:transition-all focus:shadow-[0_0_0_2px] focus:shadow-black"
        :next-page="(date: DateValue) => pagingFunc(date, 1)"
      >
        <Icon
          icon="radix-icons:double-arrow-right"
          class="w-6 h-6"
        />
      </CalendarNext>
    </CalendarHeader>
    <div
      class="flex flex-col space-y-4 pt-4 sm:flex-row sm:space-x-4 sm:space-y-0"
    >
      <CalendarGrid
        v-for="month in grid"
        :key="month.value.toString()"
        class="w-full border-collapse select-none space-y-1"
      >
        <CalendarGridHead>
          <CalendarGridRow class="mb-1 grid w-full grid-cols-7">
            <CalendarHeadCell
              v-for="day in weekDays"
              :key="day"
              class="rounded-md text-xs text-green8"
            >
              {{ 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"
              class="relative text-center text-sm"
            >
              <CalendarCellTrigger
                :day="weekDate"
                :month="month.value"
                class="relative flex items-center justify-center rounded-full whitespace-nowrap text-sm font-normal text-black w-8 h-8 outline-none focus:shadow-[0_0_0_2px] focus:shadow-black data-[disabled]:text-black/30 data-[selected]:!bg-green10 data-[selected]:text-white hover:bg-green5 data-[highlighted]:bg-green5 data-[unavailable]:pointer-events-none data-[unavailable]:text-black/30 data-[unavailable]:line-through before:absolute before:top-[5px] before:hidden before:rounded-full before:w-1 before:h-1 before:bg-white data-[today]:before:block data-[today]:before:bg-green9 "
              />
            </CalendarCell>
          </CalendarGridRow>
        </CalendarGridBody>
      </CalendarGrid>
    </div>
  </CalendarRoot>
</template>

ロケールとカレンダーシステム選択が可能なカレンダー

この例では、利用可能なロケールの一部とカレンダーシステムの表示方法を紹介します。

2024年10月
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
イベント日、2024年10月
vue
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { createCalendar, getLocalTimeZone, toCalendar, today } from '@internationalized/date'
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNext, CalendarPrev, CalendarRoot, Label, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectPortal, SelectRoot, SelectScrollUpButton, SelectTrigger, SelectValue, SelectViewport } from 'radix-vue'
import { computed, ref } from 'vue'

const preferences = [
  { locale: 'en-US', label: 'Default', ordering: 'gregory' },
  { label: 'Arabic (Algeria)', locale: 'ar-DZ', territories: 'DJ DZ EH ER IQ JO KM LB LY MA MR OM PS SD SY TD TN YE', ordering: 'gregory islamic islamic-civil islamic-tbla' },
  { label: 'Arabic (United Arab Emirates)', locale: 'ar-AE', territories: 'AE BH KW QA', ordering: 'gregory islamic-umalqura islamic islamic-civil islamic-tbla' },
  { label: 'Arabic (Egypt)', locale: 'AR-EG', territories: 'EG', ordering: 'gregory coptic islamic islamic-civil islamic-tbla' },
  { label: 'Arabic (Saudi Arabia)', locale: 'ar-SA', territories: 'SA', ordering: 'islamic-umalqura gregory islamic islamic-rgsa' },
  { label: 'Farsi (Afghanistan)', locale: 'fa-AF', territories: 'AF IR', ordering: 'persian gregory islamic islamic-civil islamic-tbla' },
  { label: 'Amharic (Ethiopia)', locale: 'am-ET', territories: 'ET', ordering: 'gregory ethiopic ethioaa' },
  { label: 'Hebrew (Israel)', locale: 'he-IL', territories: 'IL', ordering: 'gregory hebrew islamic islamic-civil islamic-tbla' },
  { label: 'Hindi (India)', locale: 'hi-IN', territories: 'IN', ordering: 'gregory indian' },
  { label: 'Japanese (Japan)', locale: 'ja-JP', territories: 'JP', ordering: 'gregory japanese' },
  { label: 'Thai (Thailand)', locale: 'th-TH', territories: 'TH', ordering: 'buddhist gregory' },
  { label: 'Chinese (Taiwan)', locale: 'zh-TW', territories: 'TW', ordering: 'gregory roc chinese' },
]

const calendars = [
  { key: 'gregory', name: 'Gregorian' },
  { key: 'japanese', name: 'Japanese' },
  { key: 'buddhist', name: 'Buddhist' },
  { key: 'roc', name: 'Taiwan' },
  { key: 'persian', name: 'Persian' },
  { key: 'indian', name: 'Indian' },
  { key: 'islamic-umalqura', name: 'Islamic (Umm al-Qura)' },
  { key: 'islamic-civil', name: 'Islamic Civil' },
  { key: 'islamic-tbla', name: 'Islamic Tabular' },
  { key: 'hebrew', name: 'Hebrew' },
  { key: 'coptic', name: 'Coptic' },
  { key: 'ethiopic', name: 'Ethiopic' },
  { key: 'ethioaa', name: 'Ethiopic (Amete Alem)' },
]

const locale = ref(preferences[0].locale)
const calendar = ref(calendars[0].key)

const pref = computed(() => preferences.find(p => p.locale === locale.value))
const preferredCalendars = computed(() => pref.value ? pref.value.ordering.split(' ').map(p => calendars.find(c => c.key === p)).filter(Boolean) : [calendars[0]])
const otherCalendars = computed(() => calendars.filter(c => !preferredCalendars.value.some(p => p!.key === c.key)))

function updateLocale(newLocale: string) {
  locale.value = newLocale
  calendar.value = pref.value!.ordering.split(' ')[0]
}
const value = computed(() => toCalendar(today(getLocalTimeZone()), createCalendar(calendar.value)))
</script>

<template>
  <div class="flex flex-col gap-4">
    <Label class="text-white">Locale</Label>
    <SelectRoot
      v-model="locale"
      @update:model-value="updateLocale"
    >
      <SelectTrigger
        class="inline-flex min-w-[160px] items-center justify-between rounded px-[15px] text-[13px] leading-none h-[35px] gap-[5px] bg-white text-grass11 shadow-[0_2px_10px] shadow-black/10 hover:bg-mauve3 focus:shadow-[0_0_0_2px] focus:shadow-black data-[placeholder]:text-green9 outline-none"
        aria-label="Select a locale"
      >
        <SelectValue placeholder="Please select a locale">
          {{ pref!.label }}
        </SelectValue>
        <Icon
          icon="radix-icons:chevron-down"
          class="h-3.5 w-3.5"
        />
      </SelectTrigger>

      <SelectPortal>
        <SelectContent
          class="min-w-[160px] bg-white rounded shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade z-[100]"
          :side-offset="5"
        >
          <SelectScrollUpButton class="flex items-center justify-center h-[25px] bg-white text-violet11 cursor-default">
            <Icon icon="radix-icons:chevron-up" />
          </SelectScrollUpButton>

          <SelectViewport class="p-[5px]">
            <SelectItem
              v-for="(option, index) in preferences"
              :key="index"
              class="text-[13px] leading-none text-grass11 rounded-[3px] flex items-center h-[25px] pr-[35px] pl-[25px] relative select-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-green9 data-[highlighted]:text-green1"
              :value="option.locale"
            >
              <SelectItemIndicator class="absolute left-0 w-[25px] inline-flex items-center justify-center">
                <Icon icon="radix-icons:check" />
              </SelectItemIndicator>
              <SelectItemText>
                {{ option.label }}
              </SelectItemText>
            </SelectItem>
          </SelectViewport>

          <SelectScrollDownButton class="flex items-center justify-center h-[25px] bg-white text-violet11 cursor-default">
            <Icon icon="radix-icons:chevron-down" />
          </SelectScrollDownButton>
        </SelectContent>
      </SelectPortal>
    </SelectRoot>
    <Label class="text-white">Calendar</Label>
    <SelectRoot v-model="calendar">
      <SelectTrigger
        class="inline-flex min-w-[160px] items-center justify-between rounded px-[15px] text-[13px] leading-none h-[35px] gap-[5px] bg-white text-grass11 shadow-[0_2px_10px] shadow-black/10 hover:bg-mauve3 focus:shadow-[0_0_0_2px] focus:shadow-black data-[placeholder]:text-green9 outline-none"
        aria-label="Select a calendar"
      >
        <SelectValue placeholder="Please select a calendar">
          {{ calendars.find(c => c.key === calendar)?.name }}
        </SelectValue>

        <Icon
          icon="radix-icons:chevron-down"
          class="h-3.5 w-3.5"
        />
      </SelectTrigger>

      <SelectPortal>
        <SelectContent
          class="min-w-[160px] bg-white rounded shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade z-[100]"
          :side-offset="5"
        >
          <SelectScrollUpButton class="flex items-center justify-center h-[25px] bg-white text-violet11 cursor-default">
            <Icon icon="radix-icons:chevron-up" />
          </SelectScrollUpButton>

          <SelectViewport class="p-[5px]">
            <SelectLabel class="px-[25px] text-xs leading-[25px] text-mauve11">
              Preferred
            </SelectLabel>
            <SelectGroup>
              <SelectItem
                v-for="(option, index) in preferredCalendars"
                :key="index"
                class="text-[13px] leading-none text-grass11 rounded-[3px] flex items-center h-[25px] pr-[35px] pl-[25px] relative select-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-green9 data-[highlighted]:text-green1"
                :value="option!.key"
              >
                <SelectItemIndicator class="absolute left-0 w-[25px] inline-flex items-center justify-center">
                  <Icon icon="radix-icons:check" />
                </SelectItemIndicator>
                <SelectItemText>
                  {{ option!.name }}
                </SelectItemText>
              </SelectItem>
            </SelectGroup>
            <SelectSeparator class="h-[1px] bg-green6 m-[5px]" />
            <SelectLabel class="px-[25px] text-xs leading-[25px] text-mauve11">
              Other
            </SelectLabel>
            <SelectGroup>
              <SelectItem
                v-for="(option, index) in otherCalendars"
                :key="index"
                class="text-[13px] leading-none text-grass11 rounded-[3px] flex items-center h-[25px] pr-[35px] pl-[25px] relative select-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-green9 data-[highlighted]:text-green1"
                :value="option.key"
              >
                <SelectItemIndicator class="absolute left-0 w-[25px] inline-flex items-center justify-center">
                  <Icon icon="radix-icons:check" />
                </SelectItemIndicator>
                <SelectItemText>
                  {{ option.name }}
                </SelectItemText>
              </SelectItem>
            </SelectGroup>
          </SelectViewport>

          <SelectScrollDownButton class="flex items-center justify-center h-[25px] bg-white text-violet11 cursor-default">
            <Icon icon="radix-icons:chevron-down" />
          </SelectScrollDownButton>
        </SelectContent>
      </SelectPortal>
    </SelectRoot>

    <CalendarRoot
      v-slot="{ weekDays, grid }"
      :model-value="value"
      :locale="locale"
      class="mt-6 rounded-xl bg-white p-4 shadow-md"
      fixed-weeks
    >
      <CalendarHeader class="flex items-center justify-between">
        <CalendarPrev
          class="inline-flex items-center cursor-pointer text-black justify-center rounded-[9px] bg-transparent w-8 h-8 hover:bg-black hover:text-white active:scale-98 active:transition-all focus:shadow-[0_0_0_2px] focus:shadow-black"
        >
          <Icon
            icon="radix-icons:chevron-left"
            class="w-6 h-6"
          />
        </CalendarPrev>
        <CalendarHeading class="text-[15px] text-black font-medium" />

        <CalendarNext
          class="inline-flex items-center cursor-pointer justify-center text-black rounded-[9px] bg-transparent w-8 h-8 hover:bg-black hover:text-white active:scale-98 active:transition-all focus:shadow-[0_0_0_2px] focus:shadow-black"
        >
          <Icon
            icon="radix-icons:chevron-right"
            class="w-6 h-6"
          />
        </CalendarNext>
      </CalendarHeader>
      <div
        class="flex flex-col space-y-4 pt-4 sm:flex-row sm:space-x-4 sm:space-y-0"
      >
        <CalendarGrid
          v-for="month in grid"
          :key="month.value.toString()"
          class="w-full border-collapse select-none space-y-1"
        >
          <CalendarGridHead>
            <CalendarGridRow class="mb-1 grid w-full grid-cols-7">
              <CalendarHeadCell
                v-for="day in weekDays"
                :key="day"
                class="rounded-md text-xs text-green8"
              >
                {{ 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"
                class="relative text-center text-sm"
              >
                <CalendarCellTrigger
                  :day="weekDate"
                  :month="month.value"
                  class="relative flex items-center justify-center rounded-full whitespace-nowrap text-sm font-normal text-black w-8 h-8 outline-none focus:shadow-[0_0_0_2px] focus:shadow-black data-[disabled]:text-black/30 data-[selected]:!bg-green10 data-[selected]:text-white hover:bg-green5 data-[highlighted]:bg-green5 data-[unavailable]:pointer-events-none data-[unavailable]:text-black/30 data-[unavailable]:line-through before:absolute before:top-[5px] before:hidden before:rounded-full before:w-1 before:h-1 before:bg-white data-[today]:before:block data-[today]:before:bg-green9 "
                />
              </CalendarCell>
            </CalendarGridRow>
          </CalendarGridBody>
        </CalendarGrid>
      </div>
    </CalendarRoot>
  </div>
</template>

アクセシビリティ

キーボード操作

キー説明
Tab
カレンダーにフォーカスが移動すると、最初のナビゲーションボタンにフォーカスします。
Space
CalendarNext または CalendarPrev にフォーカスがある場合は、カレンダーを移動します。それ以外の場合は、日付を選択します。
Enter
CalendarNext または CalendarPrev にフォーカスがある場合は、カレンダーを移動します。それ以外の場合は、日付を選択します。
ArrowLeftArrowRightArrowUpArrowDown
CalendarCellTrigger にフォーカスがある場合、日付を移動し、必要に応じて月/年/10年単位を変更します。