カレンダー
アルファ日 | 月 | 火 | 水 | 火 | 木 | 日 |
---|---|---|---|---|---|---|
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 |
<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 パッケージに依存しています。
このパッケージのドキュメントをよく読んで、その動作をしっかりと理解することをお勧めします。また、日付関連のコンポーネントを使用するには、プロジェクトにインストールする必要があります。
インストール
日付パッケージをインストールします。
$ npm add @internationalized/date
コマンドラインからコンポーネントをインストールします。
$ npm add radix-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 | ブール値 子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。 詳細については、コンポジションガイドをご覧ください。 | |
calendarLabel | 文字列 カレンダーのアクセシブルなラベル | |
defaultPlaceholder | DateValue デフォルトのプレースホルダー日付 | |
defaultValue | DateValue カレンダーのデフォルト値 | |
dir | 'ltr' | 'rtl' 該当する場合のカレンダーの読み取り方向。 | |
disabled | false | ブール値 カレンダーが無効かどうか |
fixedWeeks | false | ブール値 カレンダーに常に6週間表示するかどうか |
initialFocus | false | ブール値 trueの場合、カレンダーは、カレンダーがマウントされたときに表示されているものに応じて、選択された日、今日、または月の最初の日をフォーカスします |
isDateDisabled | マッチャー 日付が無効かどうかを返す関数 | |
isDateUnavailable | マッチャー 日付が使用できないかどうかを返す関数 | |
locale | 'en' | 文字列 日付の書式設定に使用するロケール |
maxValue | DateValue 選択できる最大日付 | |
minValue | DateValue 選択できる最小日付 | |
modelValue | DateValue | DateValue[] カレンダーの制御されたチェック状態。 | |
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 | ブール値 子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。 詳細については、コンポジションガイドをご覧ください。 |
前のボタン
カレンダーのナビゲーションボタン。現在のカレンダービューに基づいて、カレンダーを1か月/年/10年前の過去に移動します。
プロパティ | デフォルト | タイプ |
---|---|---|
as | 'button' | AsTag | コンポーネント このコンポーネントがレンダリングする要素またはコンポーネント。 |
asChild | ブール値 子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。 詳細については、コンポジションガイドをご覧ください。 | |
prevPage | ((placeholder: DateValue) => DateValue) 前のページに使用する関数。 | |
step | 'month' | 'month' | 'year' 戻るカレンダーの単位 |
データ属性 | 値 |
---|---|
[data-disabled] | 無効の場合に存在します |
次のボタン
カレンダーのナビゲーションボタン。現在のカレンダービューに基づいて、カレンダーを1か月/年/10年先の未来に移動します。
プロパティ | デフォルト | タイプ |
---|---|---|
as | 'button' | AsTag | コンポーネント このコンポーネントがレンダリングする要素またはコンポーネント。 |
asChild | ブール値 子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。 詳細については、コンポジションガイドをご覧ください。 | |
nextPage | ((placeholder: DateValue) => DateValue) 次のページに使用する関数。 | |
step | 'month' | 'month' | 'year' 進むカレンダーの単位 |
データ属性 | 値 |
---|---|
[data-disabled] | 無効の場合に存在します |
見出し
現在の月と年を表示するための見出し
プロパティ | デフォルト | タイプ |
---|---|---|
as | 'div' | AsTag | コンポーネント このコンポーネントがレンダリングする要素またはコンポーネント。 |
asChild | ブール値 子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。 詳細については、コンポジションガイドをご覧ください。 |
スロット(デフォルト) | ペイロード |
---|---|
headingValue | 文字列 現在の月と年 |
データ属性 | 値 |
---|---|
[data-disabled] | 無効の場合に存在します |
グリッド
カレンダーグリッドをラップするためのコンテナー。
プロパティ | デフォルト | タイプ |
---|---|---|
as | 'table' | AsTag | コンポーネント このコンポーネントがレンダリングする要素またはコンポーネント。 |
asChild | ブール値 子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。 詳細については、コンポジションガイドをご覧ください。 |
データ属性 | 値 |
---|---|
[data-readonly] | 読み取り専用の場合に存在します |
[data-disabled] | 無効の場合に存在します |
グリッドヘッダー
グリッドヘッダーをラップするためのコンテナー。
プロパティ | デフォルト | タイプ |
---|---|---|
as | 'thead' | AsTag | コンポーネント このコンポーネントがレンダリングする要素またはコンポーネント。 |
asChild | ブール値 子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。 詳細については、コンポジションガイドをご覧ください。 |
グリッドボディ
グリッドボディをラップするためのコンテナー。
プロパティ | デフォルト | タイプ |
---|---|---|
as | 'tbody' | AsTag | コンポーネント このコンポーネントがレンダリングする要素またはコンポーネント。 |
asChild | ブール値 子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。 詳細については、コンポジションガイドをご覧ください。 |
グリッド行
グリッド行をラップするためのコンテナー。
プロパティ | デフォルト | タイプ |
---|---|---|
as | 'tr' | AsTag | コンポーネント このコンポーネントがレンダリングする要素またはコンポーネント。 |
asChild | ブール値 子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。 詳細については、コンポジションガイドをご覧ください。 |
ヘッダーセル
ヘッダーセルをラップするためのコンテナー。曜日を表示するために使用されます。
プロパティ | デフォルト | タイプ |
---|---|---|
as | 'th' | AsTag | コンポーネント このコンポーネントがレンダリングする要素またはコンポーネント。 |
asChild | ブール値 子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。 詳細については、コンポジションガイドをご覧ください。 |
セル
カレンダーセルをラップするためのコンテナー。
プロパティ | デフォルト | タイプ |
---|---|---|
as | 'td' | AsTag | コンポーネント このコンポーネントがレンダリングする要素またはコンポーネント。 |
asChild | ブール値 子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。 詳細については、コンポジションガイドをご覧ください。 | |
date* | DateValue セルの日付値 |
データ属性 | 値 |
---|---|
[data-disabled] | 無効の場合に存在します |
セルトリガー
セル日付を表示するためのインタラクティブなコンテナー。クリックすると日付が選択されます。
プロパティ | デフォルト | タイプ |
---|---|---|
as | 'div' | AsTag | コンポーネント このコンポーネントがレンダリングする要素またはコンポーネント。 |
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] | フォーカスされている場合に表示されます |
例
年の増減が可能なカレンダー
この例では、年の増減が可能なカレンダーを紹介します。
日 | 月 | 火 | 水 | 火 | 木 | 日 |
---|---|---|---|---|---|---|
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 |
<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>
ロケールとカレンダーシステム選択が可能なカレンダー
この例では、利用可能なロケールの一部とカレンダーシステムの表示方法を紹介します。
日 | 月 | 火 | 水 | 火 | 木 | 日 |
---|---|---|---|---|---|---|
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 |
<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年単位を変更します。 |