Vuetify 3 VTab / VTabs / VTabsWindow / VWindow 変更点
VutifiyのVTabのメモ。
よく記事で紹介されている タブの本文部分は、v-windowを利用しているがVTabWindowがドキュメントにあった。
こちらは、v-tabs-window/v-tabs-window-item をサポートしている対応している。
最初自分のIDEにインストールした環境には上記はなかった。 3.5.16
v-tabs-window-item は 3.5.16にない。
v-tabs-window も同様
3.6.0から実装されたようだ。
VTabs / VTab
タブの実装には、VTabs と VTab を組み合わせたHTMLを作成する
v-model で定義した tabs は refとして定義し 表示するコンテンツとカレント(現在のタブ)が挿入する変数を用意しておく。
<template>
<v-tabs v-model="tabs">
<v-tab value="1">コンテンツ1</v-tab>
<v-tab value="2">コンテンツ2</v-tab>
</v-tabs>
</template>
VTabsWindow / VTabsWindowItem
タブのコンテンツを括るときは、VTabsWinodw と VTabsWindowItemを利用する。
- Vuetify 3.6.0 以降のバージョン
アニメーションはデフォルトでスライドになっている。
変更する場合には、transitionとreverse-transitionを v-tabs-window-item
に挿入する。
用意されているアニメーションは以下のドキュメントを参照する
- transition アニメーションの開始設定
- reverse-transition アニメーションの逆設定
属性に指定する場合は、v-
から始まる接頭辞は不要です。
タグとしてくくる場合は、ドキュメントの記載があるタグを 接頭辞 v-
をつける。
<template>
<v-tabs v-model="tabs">
<v-tab value="1">
コンテンツ1
</v-tab>
<v-tab value="2">
コンテンツ2
</v-tab>
<template #window>
<v-tabs-window-item value="1" transition="fab-transition" reverse-transition="fab-transition">
<v-card variant="flat">
<v-card-title>コンテンツ1</v-card-title>
<v-card-text>
コンテンツ1です。
</v-card-text>
</v-card>
</v-tabs-window-item>
<v-tabs-window-item value="2" transition="fab-transition" reverse-transition="fab-transition">
<v-card variant="flat">
<v-card-title>コンテンツ2</v-card-title>
<v-card-text>
コンテンツ2です。
</v-card-text>
</v-card>
</v-tabs-window-item>
</template>
</v-tabs>
</template>
アニメーションの種類
- v-expand-transition
- v-fab-transition
- v-fade-transition
- v-scale-transition
- v-scroll-x-transition
- v-scroll-y-transition
- v-slide-x-reverse-transition
- v-slide-x-transition
- v-slide-y-reverse-transition
- v-slide-y-transition
- v-tab-reverse-transition
- v-tab-transition
- v-toggle-slide-x-reverse-transition
- v-toggle-slide-x-transition
- v-toggle-slide-y-reverse-transition
- v-toggle-slide-y-transition
アニメーションをオフにする
デフォルトでスライドのアニメーションがついています。オフにする場合は、 transition
と reverse-transition
に none
を指定する必要があります。
<template>
<v-tabs v-model="tabs">
<v-tab value="1">
コンテンツ1
</v-tab>
<v-tab value="2">
コンテンツ2
</v-tab>
<template #window>
- <v-tabs-window-item value="1" transition="fab-transition" reverse-transition="fab-transition">
+ <v-tabs-window-item value="1" transition="none" reverse-transition="none">
<v-card variant="flat">
<v-card-title>コンテンツ1</v-card-title>
<v-card-text>
コンテンツ1です。
</v-card-text>
</v-card>
</v-tabs-window-item>
- <v-tabs-window-item value="2" transition="fab-transition" reverse-transition="fab-transition">
+ <v-tabs-window-item value="2" transition="none" reverse-transition="none">
<v-card variant="flat">
<v-card-title>コンテンツ2</v-card-title>
<v-card-text>
コンテンツ2です。
</v-card-text>
</v-card>
</v-tabs-window-item>
</template>
</v-tabs>
</template>
ページ遷移
ページ遷移させたい場合は、 v-tab に :to
を指定することで可能です。
内部的には、vue-routerを利用しています。
型情報としては、 RouteLocationRaw
になります。
ページ遷移だけになるため、位置情報を保持する必要性がないため v-modelは不要です。
カレントの処理は routerのパスで判別していることから、自動的にカレントがつくようです。
<template>
<v-tabs>
<v-tab to="/">
Home
</v-tab>
<v-tab to="/about">
About
</v-tab>
</v-tabs>
</template>
Storybook上で Vue Routerを動かす
Nuxt の利用環境では、ファイルベースルーティングになるため別途StorybookにはVue Router の設定が必要です。
viteで使ってる場合はrouterの設定をまとめておくと良いですが、ここでは直接 .storybook/preview.ts
にroutes の設定を加えました。
非同期コンポーネントとして component: () => import('../pages/index.vue')
書いてる理由はビルドしたタイミングでpageのコンポーネントを取り出せないためこのような書き方にしました。
import type { Preview } from "@storybook/vue3";
import { setup } from '@storybook/vue3'
import * as VueRouter from "vue-router";
// Routes
const routes: VueRouter.RouteRecordRaw[] = [
{
path: '/',
name: 'Home',
component: () => import('../pages/index.vue')
},
{
path: '/about',
name: 'About',
component: () => import('../pages/about.vue')
}
]
const createRouter = (type: 'memory' | 'history') => {
const history = type === 'memory' ? VueRouter.createMemoryHistory() : VueRouter.createWebHistory();
return VueRouter.createRouter({ history, routes });
};
const router = createRouter('memory');
// Styles
import vuetify from "../utils/vuetify";
import VueApexCharts from 'vue3-apexcharts'
setup((app) => {
if (app) {
app.use(vuetify)
app.use(VueApexCharts)
app.use(router)
}
})
export const decorators = [
(story: any) => ({
components: { story },
template: '<v-app><story /></v-app>',
}),
]
/** @type { import('@storybook/vue3').Preview } */
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;
VTabsTo.stories.ts は以下の様にrouterの設定を行いました。
import type { Meta, StoryObj } from '@storybook/vue3'
import { useRouter } from 'vue-router'
import VTabsTo from './VTabsTo.vue'
type Story = StoryObj<typeof VTabsTo>;
function createPageStory (name: string): StoryObj {
return {
render: () => ({
setup () {
const pageLoaded = ref(false)
const router = useRouter()
router.push({ name }).then(() => {
pageLoaded.value = true
})
return { pageLoaded }
},
template: '<template v-if="pageLoaded"><router-view /></template>'
})
}
}
const meta: Meta<typeof VTabsTo> = {
title: 'Features/VTabsTo',
component: VTabsTo,
tags: ['autodocs'],
argTypes: {},
render: args => ({
components: { VTabsTo },
setup () {
return { args }
},
template: '<VTabsTo v-bind="args" />'
})
}
export const Default: Story = {
args: {}
}
export default meta
export const HomePage = createPageStory('Home')
export const AboutPage = createPageStory('About')
ページ遷移のサンプルをStorybook上に載せた。
VWindow と VTabsWindowの違い
アニメーションの違いはない。
propsがいくつか変わっているが、3.5以前まではタブの実装は VWindowで行われていた。
そのため VTabsのslotには挿入できずVTabs要素の外で、VWindowを定義してVTabsと紐づけていた。
また、VWinodow自体もブロック要素ではないため、 v-sheetなど間にいれる必要があった。
<script setup lang="ts">
import { ref } from 'vue'
const props = withDefaults(defineProps<{
transition?: string
reverseTransition?: string
}>(), {
transition: undefined,
reverseTransition: undefined
})
const tabs = ref(null)
</script>
<template>
<v-tabs v-model="tabs">
<v-tab value="1">
コンテンツ1
</v-tab>
<v-tab value="2">
コンテンツ2
</v-tab>
</v-tabs>
<v-window v-model="tabs">
<v-window-item value="1" :transition="props.transition" :reverse-transition="props.transition">
<v-sheet
tile
>
<v-card variant="flat">
<v-card-title>コンテンツ1</v-card-title>
<v-card-text>
コンテンツ1です。
</v-card-text>
</v-card>
</v-sheet>
</v-window-item>
<v-tabs-window-item value="2" :transition="props.transition" :reverse-transition="props.transition">
<v-sheet
tile
>
<v-card variant="flat">
<v-card-title>コンテンツ2</v-card-title>
<v-card-text>
コンテンツ2です。
</v-card-text>
</v-card>
</v-sheet>
</v-tabs-window-item>
<v-tabs-window-item value="3" :transition="transition" :reverse-transition="transition">
<v-sheet
tile
>
<v-card variant="flat">
<v-card-title>コンテンツ3</v-card-title>
<v-card-text>
コンテンツ3です。
</v-card-text>
</v-card>
</v-sheet>
</v-tabs-window-item>
</v-window>
</template>
基本的にVuetify 3.6以降はVTabsWindowが実装されたため、VTabsWindow と VTabsItemを組み合わせて v-tabsの子要素として配置することができる。
タブの場合、アクセシビリティの観点からも要素はまとめておいた方が良いから、この仕様になったのかなと感じた。
<script setup lang="ts">
import { ref } from 'vue'
const props = withDefaults(defineProps<{
transition?: string
reverseTransition?: string
}>(), {
transition: undefined,
reverseTransition: undefined
})
const tabs = ref(null)
</script>
<template>
<v-tabs v-model="tabs">
<v-tab value="1">
コンテンツ1
</v-tab>
<v-tab value="2">
コンテンツ2
</v-tab>
<template #window>
<v-tabs-window-item value="1" :transition="props.transition" :reverse-transition="props.transition">
<v-card variant="flat">
<v-card-title>コンテンツ1</v-card-title>
<v-card-text>
コンテンツ1です。
</v-card-text>
</v-card>
</v-tabs-window-item>
<v-tabs-window-item value="2" :transition="props.transition" :reverse-transition="props.transition">
<v-card variant="flat">
<v-card-title>コンテンツ2</v-card-title>
<v-card-text>
コンテンツ2です。
</v-card-text>
</v-card>
</v-tabs-window-item>
<v-tabs-window-item value="3" :transition="transition" :reverse-transition="transition">
<v-card variant="flat">
<v-card-title>コンテンツ3</v-card-title>
<v-card-text>
コンテンツ3です。
</v-card-text>
</v-card>
</v-tabs-window-item>
</template>
</v-tabs>
</template>