Storybook — это инструмент для разработки UI-компонентов в изоляции. Он позволяет визуализировать компоненты, тестировать их состояния и создавать библиотеку интерфейсных элементов. Для интеграции с Vuetify необходимо установить базовые пакеты:
npx sb init
npm install @storybook/vue3 --save-dev
npm install vuetify@next --save
Далее создается файл конфигурации
.storybook/main.js:
module.exports = {
stories: ['../src/**/*.stories.@(js|ts|vue)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
],
framework: '@storybook/vue3',
};
Чтобы Vuetify корректно работал в Storybook, создается файл
.storybook/preview.js:
import { app } from '@storybook/vue3';
import { createVuetify } from 'vuetify';
import 'vuetify/styles';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';
const vuetify = createVuetify({
components,
directives,
});
app.use(vuetify);
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
};
Истории обычно создаются рядом с компонентами в папке
src/components. Рекомендуется использовать формат
.stories.js или .stories.ts. Пример
структуры:
src/
components/
Button/
BaseButton.vue
BaseButton.stories.js
Файл истории для BaseButton.vue:
import BaseButton from './BaseButton.vue';
export default {
title: 'Components/BaseButton',
component: BaseButton,
argTypes: {
color: { control: 'color' },
disabled: { control: 'boolean' },
},
};
const Template = (args) => ({
components: { BaseButton },
setup() { return { args }; },
template: '<BaseButton v-bind="args" />',
});
export const Primary = Template.bind({});
Primary.args = {
color: 'primary',
disabled: false,
};
export const Disabled = Template.bind({});
Disabled.args = {
color: 'secondary',
disabled: true,
};
Args позволяют динамически управлять свойствами компонента. Для
компонентов Vuetify особенно важно использовать v-model и
props, чтобы изменения в Storybook отражались в реальном времени.
Пример компонента с v-model:
<template>
<v-text-field v-model="text" label="Введите текст" />
</template>
<script>
export default {
props: {
modelValue: String,
},
emits: ['update:modelValue'],
data() {
return { text: this.modelValue };
},
watch: {
text(newVal) {
this.$emit('update:modelValue', newVal);
},
},
};
</script>
История для этого компонента:
import TextField from './TextField.vue';
export default {
title: 'Components/TextField',
component: TextField,
argTypes: {
modelValue: { control: 'text' },
},
};
const Template = (args) => ({
components: { TextField },
setup() { return { args }; },
template: '<TextField v-bind="args" />',
});
export const Default = Template.bind({});
Default.args = {
modelValue: '',
};
Декораторы позволяют оборачивать все истории в общий контекст, что особенно полезно для Vuetify, чтобы единообразно применять темы, сетки и провайдеры.
import { app } from '@storybook/vue3';
import { createVuetify } from 'vuetify';
import 'vuetify/styles';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';
const vuetify = createVuetify({
components,
directives,
});
export const decorators = [
(story) => {
app.use(vuetify);
return { components: { story }, template: '<story />' };
},
];
@storybook/addon-docs позволяет автоматически
генерировать документацию компонентов Vuetify. Используется синтаксис
MDX для комбинирования текста и визуализации:
import { Meta, Story, ArgsTable } from '@storybook/addon-docs';
import BaseButton from './BaseButton.vue';
<Meta title="Components/BaseButton" component={BaseButton} />
# BaseButton
Компонент кнопки с поддержкой цветов Vuetify и состояний disabled.
<ArgsTable story="Primary" />
<Story name="Primary">
<BaseButton color="primary">Нажми меня</BaseButton>
</Story>
Storybook интегрируется с @storybook/addon-interactions
и визуальными тестами, что позволяет проверять компоненты Vuetify в
различных состояниях:
import { within, userEvent } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
export const ClickTest = Template.bind({});
ClickTest.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByText('Нажми меня');
await userEvent.click(button);
expect(button).toBeEnabled();
};
Для демонстрации разных тем в Storybook можно использовать декораторы и глобальные параметры:
import { createVuetify } from 'vuetify';
import { app } from '@storybook/vue3';
import 'vuetify/styles';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';
const lightTheme = {
dark: false,
colors: { primary: '#1976D2', secondary: '#424242' },
};
const darkTheme = {
dark: true,
colors: { primary: '#BB86FC', secondary: '#03DAC6' },
};
export const decorators = [
(story, context) => {
const vuetify = createVuetify({
components,
directives,
theme: context.globals.theme === 'dark' ? darkTheme : lightTheme,
});
app.use(vuetify);
return { components: { story }, template: '<story />' };
},
];
export const globalTypes = {
theme: {
name: 'Theme',
description: 'Выбор темы',
defaultValue: 'light',
toolbar: { icon: 'circlehollow', items: ['light', 'dark'] },
},
};
Для компонентов, зависящих от маршрутизации или состояния, Storybook позволяет подключать полноценный контекст:
import { app } from '@storybook/vue3';
import { createRouter, createWebHistory } from 'vue-router';
import { createPinia } from 'pinia';
const router = createRouter({
history: createWebHistory(),
routes: [{ path: '/', component: { template: '<div>Главная</div>' } }],
});
const pinia = createPinia();
app.use(router);
app.use(pinia);
Это позволяет демонстрировать Vuetify-компоненты в условиях, максимально приближенных к реальному приложению, с поддержкой маршрутов, состояния и глобальных провайдеров.
Вся структура Storybook с Vuetify строится на комбинации Args, декораторов, тем и интеграций, что делает возможным создание полной библиотеки интерфейсных компонентов с визуальными тестами и документацией.