Closed8

Vuetify 3 VTab / VTabs / VTabsWindow / VWindow 変更点

redamoonredamoon

VutifiyのVTabのメモ。

https://vuetifyjs.com/en/api/v-tabs/

よく記事で紹介されている タブの本文部分は、v-windowを利用しているがVTabWindowがドキュメントにあった。
こちらは、v-tabs-window/v-tabs-window-item をサポートしている対応している。

https://vuetifyjs.com/en/api/v-tabs-window/
https://vuetifyjs.com/en/api/v-tabs-window-item/

https://github.com/vuetifyjs/vuetify/pull/19248

最初自分のIDEにインストールした環境には上記はなかった。 3.5.16
v-tabs-window-item は 3.5.16にない。
v-tabs-window も同様

3.6.0から実装されたようだ。

https://github.com/vuetifyjs/vuetify/releases/tag/v3.6.0-alpha.0

redamoonredamoon

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>
redamoonredamoon

VTabsWindow / VTabsWindowItem

タブのコンテンツを括るときは、VTabsWinodw と VTabsWindowItemを利用する。

  • Vuetify 3.6.0 以降のバージョン

アニメーションはデフォルトでスライドになっている。
変更する場合には、transitionとreverse-transitionを v-tabs-window-item に挿入する。
用意されているアニメーションは以下のドキュメントを参照する

https://vuetifyjs.com/en/styles/transitions/

  • 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

アニメーションをオフにする

デフォルトでスライドのアニメーションがついています。オフにする場合は、 transitionreverse-transitionnone を指定する必要があります。

VTab
 <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>
redamoonredamoon

ページ遷移

ページ遷移させたい場合は、 v-tab に :to を指定することで可能です。
内部的には、vue-routerを利用しています。

https://vuetifyjs.com/en/api/v-tab/#props-to

型情報としては、 RouteLocationRaw になります。
ページ遷移だけになるため、位置情報を保持する必要性がないため v-modelは不要です。
カレントの処理は routerのパスで判別していることから、自動的にカレントがつくようです。

<template>
  <v-tabs>
    <v-tab to="/">
      Home
    </v-tab>
    <v-tab to="/about">
      About
    </v-tab>
  </v-tabs>
</template>
redamoonredamoon

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の設定を行いました。

VTabsTo.stories.ts
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')
redamoonredamoon

VWindow と VTabsWindowの違い

アニメーションの違いはない。
propsがいくつか変わっているが、3.5以前まではタブの実装は VWindowで行われていた。
そのため VTabsのslotには挿入できずVTabs要素の外で、VWindowを定義してVTabsと紐づけていた。

また、VWinodow自体もブロック要素ではないため、 v-sheetなど間にいれる必要があった。

VWindowの場合
<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の子要素として配置することができる。
タブの場合、アクセシビリティの観点からも要素はまとめておいた方が良いから、この仕様になったのかなと感じた。

VTabsWindow
<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>
このスクラップは5ヶ月前にクローズされました