Storybook Cheatsheet¶
Storybook - Bauen Sie UIs in Isolation
Storybook ist ein Frontend-Workshop für den Bau von UI-Komponenten und Seiten in Isolation. Es hilft Ihnen, schwer zugängliche Zustände und Randfälle zu entwickeln und zu teilen, ohne dass Sie Ihre ganze App ausführen müssen. Tausende von Teams nutzen es für UI Entwicklung, Test und Dokumentation. < p>
Inhaltsverzeichnis¶
- [Installation](#installation
- (#getting-started)
- (#writing-stories_)
- Story Formate
- Controls & Actions
- Addons
- [Konfiguration](#configuration_
- (Theming)(#theming)
- Test
- [Dokumentation](LINK_9__
- [Bestellung](LINK_10__
- [Erweiterte Funktionen](#advanced-features
- [Performance](#performance_
- [Integration](LINK_13__
- (#troubleshooting_)
- Beste Praktiken
Installation¶
Schneller Start¶
# Initialize Storybook in existing project
npx storybook@latest init
# Create new project with Storybook
npx create-react-app my-app
cd my-app
npx storybook@latest init
# Start Storybook
npm run storybook
```_
### Manuelle Installation
```bash
# Install Storybook CLI
npm install -g @storybook/cli
# Initialize in project
sb init
# Or specify framework
sb init --type react
sb init --type vue3
sb init --type angular
```_
### Rahmenspezifisches Setup
```bash
# React
npx storybook@latest init --type react
# Vue 3
npx storybook@latest init --type vue3
# Angular
npx storybook@latest init --type angular
# Svelte
npx storybook@latest init --type svelte
# Web Components
npx storybook@latest init --type web-components
# HTML
npx storybook@latest init --type html
```_
### Projektstruktur
## Erste Schritte
### Grundlegende Geschichte
```javascript
// src/components/Button/Button.stories.js
import { Button } from './Button';
export default {
title: 'Example/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
backgroundColor: { control: 'color' },
},
};
export const Primary = {
args: {
primary: true,
label: 'Button',
},
};
export const Secondary = {
args: {
label: 'Button',
},
};
export const Large = {
args: {
size: 'large',
label: 'Button',
},
};
export const Small = {
args: {
size: 'small',
label: 'Button',
},
};
```_
### Komponentenbeispiel
```javascript
// src/components/Button/Button.js
import React from 'react';
import PropTypes from 'prop-types';
import './button.css';
export const Button = ({ primary = false, backgroundColor = null, size = 'medium', label, ...props }) => {
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
return (
<button
type="button"
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
style={{ backgroundColor }}
{...props}
>
{label}
</button>
);
};
Button.propTypes = {
primary: PropTypes.bool,
backgroundColor: PropTypes.string,
size: PropTypes.oneOf(['small', 'medium', 'large']),
label: PropTypes.string.isRequired,
onClick: PropTypes.func,
};
Button.defaultProps = {
primary: false,
onClick: undefined,
};
```_
### Laufendes Storybook
```bash
# Start development server
npm run storybook
# Build static Storybook
npm run build-storybook
# Serve built Storybook
npx http-server storybook-static
# Start with specific port
npm run storybook -- --port 9001
# Start in quiet mode
npm run storybook -- --quiet
```_
## Geschichten schreiben
### Geschichte
```javascript
// Component.stories.js
import { Component } from './Component';
// Default export - story metadata
export default {
title: 'Path/To/Component',
component: Component,
parameters: {
// Story-level parameters
},
argTypes: {
// Control definitions
},
args: {
// Default args for all stories
},
};
// Named exports - individual stories
export const Default = {};
export const WithProps = {
args: {
prop1: 'value1',
prop2: 'value2',
},
};
export const CustomStory = {
render: (args) => <Component {...args} />,
args: {
// Story-specific args
},
};
```_
### Geschichte der Geschichte
```javascript
// Organize stories with hierarchy
export default {
title: 'Design System/Components/Button',
// Creates: Design System > Components > Button
};
export default {
title: 'Pages/Dashboard/UserProfile',
// Creates: Pages > Dashboard > UserProfile
};
export default {
title: 'Example/Button',
// Creates: Example > Button
};
```_
### Mehrere Komponenten
```javascript
// stories/Forms.stories.js
import { Input } from '../components/Input';
import { Button } from '../components/Button';
import { Form } from '../components/Form';
export default {
title: 'Forms/LoginForm',
};
export const LoginForm = () => (
<Form>
<Input type="email" placeholder="Email" />
<Input type="password" placeholder="Password" />
<Button primary>Login</Button>
</Form>
);
export const SignupForm = () => (
<Form>
<Input placeholder="Full Name" />
<Input type="email" placeholder="Email" />
<Input type="password" placeholder="Password" />
<Input type="password" placeholder="Confirm Password" />
<Button primary>Sign Up</Button>
</Form>
);
```_
### Story Parameter
```javascript
export default {
title: 'Example/Button',
component: Button,
parameters: {
// Layout
layout: 'centered', // 'centered' | 'fullscreen' | 'padded'
// Backgrounds
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#333333' },
],
},
// Viewport
viewport: {
defaultViewport: 'mobile1',
},
// Documentation
docs: {
description: {
component: 'Button component for user interactions',
},
},
},
};
```_
## Geschichte Formate
### CSF 3.0 (Current)
```javascript
// Modern format with object syntax
export default {
title: 'Example/Button',
component: Button,
};
export const Primary = {
args: {
primary: true,
label: 'Button',
},
};
export const Secondary = {
args: {
primary: false,
label: 'Button',
},
};
```_
### CSF 2.0 (Legacy)
```javascript
// Function-based format
export default {
title: 'Example/Button',
component: Button,
};
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
primary: true,
label: 'Button',
};
export const Secondary = Template.bind({});
Secondary.args = {
primary: false,
label: 'Button',
};
```_
### Kundenspezifische Render Funktionen
```javascript
export const CustomRender = {
render: (args) => {
const [count, setCount] = React.useState(0);
return (
<div>
<p>Count: {count}</p>
<Button
{...args}
onClick={() => setCount(count + 1)}
/>
</div>
);
},
args: {
label: 'Increment',
},
};
```_
### Funktionen spielen
```javascript
import { userEvent, within } from '@storybook/testing-library';
export const InteractiveExample = {
args: {
label: 'Click me',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button');
// Simulate user interaction
await userEvent.click(button);
// Add assertions if needed
// expect(button).toHaveClass('clicked');
},
};
```_
## Kontrolle und Aktionen
### ArgTyps Konfiguration
```javascript
export default {
title: 'Example/Button',
component: Button,
argTypes: {
// Text input
label: {
control: 'text',
description: 'Button label text',
},
// Boolean
primary: {
control: 'boolean',
description: 'Primary button style',
},
// Select dropdown
size: {
control: 'select',
options: ['small', 'medium', 'large'],
description: 'Button size',
},
// Radio buttons
variant: {
control: 'radio',
options: ['primary', 'secondary', 'danger'],
},
// Color picker
backgroundColor: {
control: 'color',
},
// Number input
borderRadius: {
control: { type: 'number', min: 0, max: 50, step: 1 },
},
// Range slider
opacity: {
control: { type: 'range', min: 0, max: 1, step: 0.1 },
},
// Object editor
style: {
control: 'object',
},
// Array editor
items: {
control: 'object',
},
// Date picker
createdAt: {
control: 'date',
},
// Disable control
onClick: {
action: 'clicked',
control: false,
},
},
};
```_
### Maßnahmen
```javascript
import { action } from '@storybook/addon-actions';
export default {
title: 'Example/Button',
component: Button,
argTypes: {
onClick: { action: 'clicked' },
onMouseEnter: { action: 'mouse-enter' },
onMouseLeave: { action: 'mouse-leave' },
},
};
// Or in individual stories
export const WithActions = {
args: {
label: 'Button',
onClick: action('button-click'),
onMouseEnter: action('mouse-enter'),
},
};
// Multiple actions
export const MultipleActions = {
args: {
label: 'Multi Action Button',
onClick: (...args) => {
action('primary-click')(...args);
action('analytics-track')('button-clicked');
},
},
};
```_
### Erweiterte Steuerungen
```javascript
export default {
title: 'Example/Form',
component: Form,
argTypes: {
// Conditional controls
showAdvanced: {
control: 'boolean',
},
advancedOptions: {
control: 'object',
if: { arg: 'showAdvanced', truthy: true },
},
// Custom control
theme: {
control: {
type: 'select',
labels: {
light: 'Light Theme',
dark: 'Dark Theme',
auto: 'Auto (System)',
},
},
options: ['light', 'dark', 'auto'],
},
// Multi-select
features: {
control: 'multi-select',
options: ['feature1', 'feature2', 'feature3'],
},
},
};
```_
## Addons
### Grundlegende Addons
```javascript
// .storybook/main.js
module.exports = {
addons: [
'@storybook/addon-essentials', // Includes most common addons
'@storybook/addon-interactions',
'@storybook/addon-a11y',
'@storybook/addon-design-tokens',
],
};
// Individual addons (if not using essentials)
module.exports = {
addons: [
'@storybook/addon-controls',
'@storybook/addon-actions',
'@storybook/addon-viewport',
'@storybook/addon-backgrounds',
'@storybook/addon-docs',
'@storybook/addon-toolbars',
],
};
```_
### Erreichbarkeitszusatz
```javascript
// .storybook/preview.js
export const parameters = {
a11y: {
// Optional selector to inspect
element: '#storybook-root',
config: {
rules: [
{
// Disable specific rules
id: 'color-contrast',
enabled: false,
},
],
},
// Show violations in action panel
manual: true,
},
};
// In stories
export const AccessibleButton = {
args: {
label: 'Accessible Button',
},
parameters: {
a11y: {
// Story-specific a11y config
config: {
rules: [
{ id: 'button-name', enabled: true },
],
},
},
},
};
```_
### Viewport Addon
```javascript
// .storybook/preview.js
export const parameters = {
viewport: {
viewports: {
mobile: {
name: 'Mobile',
styles: {
width: '375px',
height: '667px',
},
},
tablet: {
name: 'Tablet',
styles: {
width: '768px',
height: '1024px',
},
},
desktop: {
name: 'Desktop',
styles: {
width: '1024px',
height: '768px',
},
},
},
defaultViewport: 'mobile',
},
};
```_
### Design Tokens Addon
```javascript
// design-tokens.json
{
"colors": {
"primary": "#007bff",
"secondary": "#6c757d",
"success": "#28a745",
"danger": "#dc3545"
},
"spacing": {
"xs": "4px",
"sm": "8px",
"md": "16px",
"lg": "24px",
"xl": "32px"
},
"typography": {
"fontFamily": {
"primary": "Inter, sans-serif",
"mono": "Monaco, monospace"
},
"fontSize": {
"sm": "14px",
"md": "16px",
"lg": "18px",
"xl": "24px"
}
}
}
// .storybook/main.js
module.exports = {
addons: [
{
name: '@storybook/addon-design-tokens',
options: {
designTokenGlob: '**/design-tokens.json',
},
},
],
};
```_
### Kundenspezifische Addons
```javascript
// .storybook/addons/theme-switcher/register.js
import { addons, types } from '@storybook/addons';
import { ADDON_ID, TOOL_ID } from './constants';
import { Tool } from './Tool';
addons.register(ADDON_ID, () => {
addons.add(TOOL_ID, {
type: types.TOOL,
title: 'Theme Switcher',
render: Tool,
});
});
// .storybook/addons/theme-switcher/Tool.js
import React from 'react';
import { useGlobals } from '@storybook/api';
import { IconButton } from '@storybook/components';
export const Tool = () => {
const [globals, updateGlobals] = useGlobals();
const theme = globals.theme || 'light';
const toggleTheme = () => {
updateGlobals({
theme: theme === 'light' ? 'dark' : 'light',
});
};
return (
<IconButton
key="theme-switcher"
title="Toggle theme"
onClick={toggleTheme}
>
{theme === 'light' ? '🌙' : '☀️'}
</IconButton>
);
};
```_
## Konfiguration
### Hauptkonfiguration
```javascript
// .storybook/main.js
module.exports = {
// Stories location
stories: [
'../src/**/*.stories.@(js|jsx|ts|tsx|mdx)',
'../stories/**/*.stories.@(js|jsx|ts|tsx|mdx)',
],
// Addons
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
],
// Framework
framework: {
name: '@storybook/react-vite',
options: {},
},
// Features
features: {
buildStoriesJson: true,
storyStoreV7: true,
},
// TypeScript
typescript: {
check: false,
reactDocgen: 'react-docgen-typescript',
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
},
},
// Webpack customization
webpackFinal: async (config) => {
// Add custom webpack config
config.module.rules.push({
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
});
return config;
},
// Vite customization
viteFinal: async (config) => {
// Customize Vite config
config.define = {
...config.define,
global: 'globalThis',
};
return config;
},
// Documentation
docs: {
autodocs: 'tag',
},
// Static directories
staticDirs: ['../public'],
};
```_
### Vorschau Konfigurieren
```javascript
// .storybook/preview.js
import '../src/index.css'; // Global styles
export const parameters = {
// Actions
actions: { argTypesRegex: '^on[A-Z].*' },
// Controls
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
expanded: true,
sort: 'requiredFirst',
},
// Layout
layout: 'centered',
// Backgrounds
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#333333' },
{ name: 'gray', value: '#f8f9fa' },
],
},
// Viewport
viewport: {
viewports: {
mobile1: {
name: 'Small mobile',
styles: { width: '320px', height: '568px' },
},
mobile2: {
name: 'Large mobile',
styles: { width: '414px', height: '896px' },
},
tablet: {
name: 'Tablet',
styles: { width: '768px', height: '1024px' },
},
},
},
// Documentation
docs: {
theme: themes.light,
source: {
state: 'open',
},
},
};
// Global decorators
export const decorators = [
(Story) => (
<div style={{ margin: '3em', fontFamily: 'Inter, sans-serif' }}>
<Story />
</div>
),
];
// Global args
export const args = {
// Default args for all stories
};
// Global arg types
export const argTypes = {
// Global arg types
};
```_
### Umweltvariablen
```javascript
// .storybook/main.js
module.exports = {
env: (config) => ({
...config,
API_URL: 'https://api.example.com',
FEATURE_FLAG: true,
}),
webpackFinal: async (config) => {
config.plugins.push(
new webpack.DefinePlugin({
'process.env.STORYBOOK': JSON.stringify(true),
})
);
return config;
},
};
// In components
const apiUrl = process.env.STORYBOOK
? 'https://api-mock.example.com'
: process.env.API_URL;
```_
## Theming
### Zoll Thema
```javascript
// .storybook/theme.js
import { create } from '@storybook/theming';
export default create({
base: 'light', // 'light' | 'dark'
// Brand
brandTitle: 'My Company Storybook',
brandUrl: 'https://example.com',
brandImage: 'https://example.com/logo.svg',
brandTarget: '_self',
// Colors
colorPrimary: '#FF4785',
colorSecondary: '#029CFD',
// UI
appBg: '#F6F9FC',
appContentBg: '#FFFFFF',
appBorderColor: '#E6ECF0',
appBorderRadius: 4,
// Typography
fontBase: '"Inter", sans-serif',
fontCode: 'Monaco, monospace',
// Text colors
textColor: '#2E3438',
textInverseColor: '#FFFFFF',
textMutedColor: '#798186',
// Toolbar
barTextColor: '#798186',
barSelectedColor: '#029CFD',
barBg: '#FFFFFF',
// Form
inputBg: '#FFFFFF',
inputBorder: '#E6ECF0',
inputTextColor: '#2E3438',
inputBorderRadius: 4,
});
// .storybook/manager.js
import { addons } from '@storybook/addons';
import theme from './theme';
addons.setConfig({
theme,
});
```_
### Dark Theme
```javascript
// .storybook/dark-theme.js
import { create } from '@storybook/theming';
export default create({
base: 'dark',
brandTitle: 'My Company Storybook',
brandUrl: 'https://example.com',
brandImage: 'https://example.com/logo-white.svg',
colorPrimary: '#FF4785',
colorSecondary: '#029CFD',
appBg: '#1A1A1A',
appContentBg: '#2D2D2D',
appBorderColor: '#404040',
textColor: '#FFFFFF',
textInverseColor: '#1A1A1A',
textMutedColor: '#CCCCCC',
barTextColor: '#CCCCCC',
barSelectedColor: '#029CFD',
barBg: '#2D2D2D',
inputBg: '#404040',
inputBorder: '#666666',
inputTextColor: '#FFFFFF',
});
```_
### Thema wechseln
```javascript
// .storybook/preview.js
import { themes } from '@storybook/theming';
export const parameters = {
docs: {
theme: themes.light,
},
darkMode: {
// Override the default dark theme
dark: { ...themes.dark, appBg: 'black' },
// Override the default light theme
light: { ...themes.normal, appBg: 'white' },
// Set the initial theme
current: 'light',
// Disable the addon for specific stories
stylePreview: true,
},
};
// Global decorator for theme switching
export const decorators = [
(Story, context) => {
const theme = context.globals.theme || 'light';
return (
<div className={`theme-${theme}`}>
<Story />
</div>
);
},
];
// Global types for theme switcher
export const globalTypes = {
theme: {
name: 'Theme',
description: 'Global theme for components',
defaultValue: 'light',
toolbar: {
icon: 'circlehollow',
items: [
{ value: 'light', icon: 'sun', title: 'Light' },
{ value: 'dark', icon: 'moon', title: 'Dark' },
],
showName: true,
},
},
};
```_
## Prüfung
### Visuelle Prüfung
```javascript
// .storybook/test-runner.js
const { getStoryContext } = require('@storybook/test-runner');
module.exports = {
async postRender(page, context) {
const storyContext = await getStoryContext(page, context);
// Skip visual tests for specific stories
if (storyContext.parameters?.skipVisualTest) {
return;
}
// Take screenshot
const image = await page.screenshot();
expect(image).toMatchImageSnapshot({
customSnapshotIdentifier: context.id,
});
},
};
// In stories
export const VisualTest = {
args: {
label: 'Visual Test Button',
},
parameters: {
// Skip this story in visual tests
skipVisualTest: true,
},
};
```_
### Interaction Testing
```javascript
// Button.stories.js
import { userEvent, within, expect } from '@storybook/test';
export const InteractionTest = {
args: {
label: 'Click me',
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
// Find elements
const button = canvas.getByRole('button', { name: /click me/i });
// Simulate interactions
await userEvent.click(button);
// Assertions
await expect(args.onClick).toHaveBeenCalled();
// Check DOM changes
await expect(button).toHaveClass('clicked');
// Wait for async operations
await canvas.findByText('Success!');
},
};
export const FormInteraction = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Fill form
const emailInput = canvas.getByLabelText(/email/i);
const passwordInput = canvas.getByLabelText(/password/i);
const submitButton = canvas.getByRole('button', { name: /submit/i });
await userEvent.type(emailInput, 'user@example.com');
await userEvent.type(passwordInput, 'password123');
await userEvent.click(submitButton);
// Check results
await expect(canvas.getByText(/success/i)).toBeInTheDocument();
},
};
```_
### Einheitstest mit Geschichten
```javascript
// Button.test.js
import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Button.stories';
const { Primary, Secondary, Large } = composeStories(stories);
describe('Button', () => {
test('renders primary button', () => {
render(<Primary />);
expect(screen.getByRole('button')).toHaveClass('storybook-button--primary');
});
test('renders secondary button', () => {
render(<Secondary />);
expect(screen.getByRole('button')).not.toHaveClass('storybook-button--primary');
});
test('renders large button', () => {
render(<Large />);
expect(screen.getByRole('button')).toHaveClass('storybook-button--large');
});
});
```_
### Zugänglichkeitsprüfung
```javascript
// a11y.test.js
import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Button.stories';
expect.extend(toHaveNoViolations);
const { Primary, Secondary } = composeStories(stories);
describe('Button Accessibility', () => {
test('Primary button should not have accessibility violations', async () => {
const { container } = render(<Primary />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
test('Secondary button should not have accessibility violations', async () => {
const { container } = render(<Secondary />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
```_
## Dokumentation
### Autodokumentation
```javascript
// Component with JSDoc
/**
* Primary UI component for user interaction
*/
export const Button = ({
/**
* Is this the principal call to action on the page?
*/
primary = false,
/**
* What background color to use
*/
backgroundColor,
/**
* How large should the button be?
*/
size = 'medium',
/**
* Button contents
*/
label,
/**
* Optional click handler
*/
onClick,
...props
}) => {
// Component implementation
};
// Stories with auto-generated docs
export default {
title: 'Example/Button',
component: Button,
tags: ['autodocs'], // Enable auto-documentation
parameters: {
docs: {
description: {
component: 'Button component for user interactions. Supports multiple sizes and variants.',
},
},
},
};
```_
### Kundenspezifische Dokumentation
```javascript
// Button.stories.js
export default {
title: 'Example/Button',
component: Button,
parameters: {
docs: {
description: {
component: `
# Button Component
The Button component is a fundamental UI element used throughout the application.
## Usage
\`\`\`jsx
import { Button } from './Button';
<Button primary onClick={handleClick}>
Click me
</Button>
\`\`\`
## Design Guidelines
- Use primary buttons for main actions
- Use secondary buttons for supporting actions
- Ensure sufficient color contrast for accessibility
`,
},
},
},
};
export const Primary = {
args: {
primary: true,
label: 'Button',
},
parameters: {
docs: {
description: {
story: 'Primary buttons are used for the main call-to-action on a page.',
},
},
},
};
```_
### MDX Dokumentation
__CODE_BLOCK_35_jsx
Import { Button } aus './Button';
Funktion MyComponent() {
zurück (
<= <===========================================================
<Button primär onClick={() => alert('Primary clicked!')}>
Primäraktion
</Button>
<Button onClick={() => alert('Secondary clicked!')}>
Sekundäraktion
</Button>
</div>
;
}
### Dokumentationsseiten
```javascript
// .storybook/main.js
module.exports = {
stories: [
'../src/**/*.stories.@(js|jsx|ts|tsx)',
'../docs/**/*.stories.mdx', // Documentation stories
],
};
// docs/Introduction.stories.mdx
import { Meta } from '@storybook/addon-docs';
<Meta title="Introduction" />
# Design System
Welcome to our design system documentation.
## Getting Started
This Storybook contains all the components, patterns, and guidelines for our design system.
### Installation
```bash
npm install @company/design-system
Usage¶
Principles¶
- Consistency - Maintain visual and functional consistency
- Accessibility - Ensure all components are accessible
- Performance - Optimize for speed and efficiency
- Flexibility - Support customization and theming
## Bereitstellung ### Strategischer Aufbau ```bash # Build Storybook for deployment npm run build-storybook # Output directory ls storybook-static/ # Serve locally npx http-server storybook-static ```_ ### Netlify Deployment ```bash # netlify.toml [build] command = "npm run build-storybook" publish = "storybook-static" [build.environment] NODE_VERSION = "16" # Deploy npm install -g netlify-cli netlify deploy --prod --dir=storybook-static ```_ ### Vercel Bereitstellung ```json // vercel.json { "buildCommand": "npm run build-storybook", "outputDirectory": "storybook-static", "framework": null } ```_ ### GitHub Seiten ```yaml # .github/workflows/storybook.yml name: Build and Deploy Storybook on: push: branches: [ main ] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Node uses: actions/setup-node@v3 with: node-version: '16' cache: 'npm' - name: Install dependencies run: npm ci - name: Build Storybook run: npm run build-storybook - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./storybook-static ```_ ### Einsatz von Docker ```dockerfile # Dockerfile FROM node:16-alpine as builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build-storybook FROM nginx:alpine COPY --from=builder /app/storybook-static /usr/share/nginx/html COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ```_ ### AWS S3 + CloudFront ```bash # Build and deploy to S3 npm run build-storybook aws s3 sync storybook-static/ s3://my-storybook-bucket --delete aws cloudfront create-invalidation --distribution-id ABCD1234 --paths "/*" ```_ ## Erweiterte Funktionen ### Benutzerdefinierte Webpack Konfiguration ```javascript // .storybook/main.js const path = require('path'); module.exports = { webpackFinal: async (config) => { // Add alias config.resolve.alias = { ...config.resolve.alias, '@': path.resolve(__dirname, '../src'), '@components': path.resolve(__dirname, '../src/components'), }; // Add custom loader config.module.rules.push({ test: /\.scss$/, use: [ 'style-loader', 'css-loader', { loader: 'sass-loader', options: { additionalData: `@import "@/styles/variables.scss";`, }, }, ], }); // Add plugin config.plugins.push( new webpack.DefinePlugin({ __VERSION__: JSON.stringify(process.env.npm_package_version), }) ); return config; }, }; ```_ ### Benutzerdefinierte Babel Konfiguration ```javascript // .storybook/main.js module.exports = { babel: async (options) => ({ ...options, plugins: [ ...options.plugins, ['babel-plugin-styled-components', { displayName: true }], ], }), }; ```_ ### Umwelt-spezifische Konfiguration ```javascript // .storybook/main.js const isDevelopment = process.env.NODE_ENV === 'development'; module.exports = { addons: [ '@storybook/addon-essentials', ...(isDevelopment ? ['@storybook/addon-a11y'] : []), ], features: { storyStoreV7: !isDevelopment, // Disable in development for faster builds }, webpackFinal: async (config) => { if (isDevelopment) { // Development-specific webpack config config.optimization.minimize = false; } return config; }, }; ```_ ### Personal Manager UI ```javascript // .storybook/manager.js import { addons } from '@storybook/addons'; addons.setConfig({ // Show/hide panels showPanel: true, panelPosition: 'bottom', // 'bottom' | 'right' // Sidebar showNav: true, showToolbar: true, // Initial active panel selectedPanel: 'storybook/controls/panel', // Sidebar tree expansion initialActive: 'sidebar', sidebar: { showRoots: false, collapsedRoots: ['other'], }, // Toolbar toolbar: { title: { hidden: false }, zoom: { hidden: false }, eject: { hidden: false }, copy: { hidden: false }, fullscreen: { hidden: false }, }, }); ```_ ## Leistung ### Lazy Loading ```javascript // .storybook/main.js module.exports = { features: { storyStoreV7: true, // Enable lazy loading }, stories: [ { directory: '../src/components', files: '**/*.stories.*', titlePrefix: 'Components', }, { directory: '../src/pages', files: '**/*.stories.*', titlePrefix: 'Pages', }, ], }; ```_ ### Bündeloptimierung ```javascript // .storybook/main.js module.exports = { webpackFinal: async (config) => { // Code splitting config.optimization.splitChunks = { chunks: 'all', cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all', }, }, }; // Tree shaking config.optimization.usedExports = true; config.optimization.sideEffects = false; return config; }, }; ```_ ### Speicheroptimierung ```javascript // .storybook/preview.js export const parameters = { // Reduce memory usage options: { storySort: { method: 'alphabetical', order: ['Introduction', 'Components', 'Pages'], locales: 'en-US', }, }, // Disable source code addon for better performance docs: { source: { state: 'closed', }, }, }; // Cleanup decorators export const decorators = [ (Story, context) => { // Cleanup on story change React.useEffect(() => { return () => { // Cleanup logic }; }, [context.id]); return <Story />; }, ]; ```_ ## Integration ### Integration von Werkzeugen ```javascript // Figma integration export default { title: 'Example/Button', component: Button, parameters: { design: { type: 'figma', url: 'https://www.figma.com/file/ABC123/Design-System?node-id=123%3A456', }, }, }; // Sketch integration export const SketchExample = { parameters: { design: { type: 'sketch', url: 'https://sketch.cloud/s/abc123', }, }, }; // Adobe XD integration export const XDExample = { parameters: { design: { type: 'adobe-xd', url: 'https://xd.adobe.com/view/abc123', }, }, }; ```_ ### Integration testen ```javascript // jest.config.js module.exports = { testEnvironment: 'jsdom', setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'], testPathIgnorePatterns: ['/node_modules/', '/storybook-static/'], // Test stories testMatch: [ '<rootDir>/src/**/__tests__/**/*.(js|jsx|ts|tsx)', '<rootDir>/src/**/?(*.)(spec|test).(js|jsx|ts|tsx)', '<rootDir>/src/**/*.stories.test.(js|jsx|ts|tsx)', ], }; // Chromatic integration // .github/workflows/chromatic.yml name: 'Chromatic' on: push jobs: chromatic-deployment: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - run: yarn - uses: chromaui/action@v1 with: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} ```_ ### CI/CD Integration ```yaml # .github/workflows/storybook-tests.yml name: Storybook Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '16' cache: 'npm' - run: npm ci - run: npm run build-storybook --quiet - run: npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \ "npx http-server storybook-static --port 6006 --silent" \ "npx wait-on http://127.0.0.1:6006 && npm run test-storybook" ```_ ## Fehlerbehebung ### Gemeinsame Themen ```bash # Port already in use npm run storybook -- --port 9001 # Clear cache rm -rf node_modules/.cache/storybook # Webpack issues npm run storybook -- --no-manager-cache # TypeScript issues npm run storybook -- --no-dll # Memory issues NODE_OPTIONS="--max-old-space-size=4096" npm run storybook ```_ ### Debug Mode ```bash # Enable debug logging DEBUG=storybook:* npm run storybook # Webpack debug npm run storybook -- --debug-webpack # Verbose output npm run storybook -- --verbose ```_ ### Leistungsfragen ```javascript // .storybook/main.js module.exports = { // Disable source maps in development webpackFinal: async (config) => { if (process.env.NODE_ENV === 'development') { config.devtool = false; } return config; }, // Reduce bundle size features: { storyStoreV7: true, buildStoriesJson: true, }, }; ```_ ### Probleme erstellen ```bash # Clear all caches rm -rf node_modules/.cache rm -rf storybook-static npm run build-storybook # Check for conflicting dependencies npm ls # Update Storybook npx storybook@latest upgrade
Best Practices¶
Geschichte und Geschichte¶
- **Hierarchische Struktur*: Verwenden Sie konsistente Namensmuster
- Logical Grouping: Gruppenkomponenten zusammen
- **Clear Naming*: Verwenden Sie beschreibende Geschichtennamen
- ** Aussprache**: Umfassende Dokumentation
Komponentenentwicklung¶
- Isolation: Komponenten isoliert entwickeln
- Props Interface: Design klare, konsistente Prop-Schnittstellen
- Standardwerte: Stellen Sie vernünftige Vorgaben vor
- Error Handling: Handle Edge Cases anmutig
Teststrategie¶
- **Visual Testing*: Verwenden Sie visuelle Regressionstests
- ** Interaction Testing*: Testen Sie Benutzerinteraktionen
- ** Eignungsprüfung*: Stellen Sie sicher, dass Komponenten zugänglich sind
- **Unit Testing*: Testkomponentenlogik
Leistungsoptimierung¶
- **Lazy Loading*: Verwenden Sie Story Store v7 für faules Laden
- **Bundle Splitting*: Webpack-Konfiguration optimieren
- Memory Management: Ressourcen richtig reinigen
- **Build Optimization*: Bauzeiten minimieren
Team Collaboration¶
- Design System: Erhalten Sie ein einheitliches Designsystem
- ** Aussprache**: Dokumentation bis zum Datum halten
- Code Bewertungen: Berichte und Komponenten
- Automation: Automate-Test und Bereitstellung
--
Zusammenfassung¶
Storybook ist ein wesentliches Werkzeug für die moderne Frontend-Entwicklung, das ermöglicht:
- Komponentenisolation: UI Komponenten unabhängig entwickeln und testen
- **Visuelle Dokumentation*: Erstellen Sie lebendige Dokumentation für Designsysteme
- Interaktive Entwicklung: Komponenten mit Echtzeit-Feedback erstellen
- ** Testing Integration*: Visual, Interaktion und Zugänglichkeitstests
- **Team Collaboration*: Komponenten in Teams und Stakeholdern teilen
- Design Systemmanagement: Erhalten Sie konsistente UI-Muster
Zu den wichtigsten Vorteilen gehören schnellere Entwicklungszyklen, bessere Bauteilqualität, verbesserte Teamkollaboration und umfassende Dokumentation. Storybook unterstützt alle wichtigen Frontend-Frameworks und bietet umfangreiche Anpassungsmöglichkeiten durch Addons und Konfiguration.
<= <= <= <================================================================================= Funktion copyToClipboard() {\cHFFFF} const commands = document.querySelectorAll('code'); alle Befehle = ''; Befehle. Für jede(cmd) => alle Befehle += cmd.textContent + '\n'); navigator.clipboard.writeText (allCommands); Alarm ('Alle Befehle, die in die Zwischenablage kopiert werden!'); }
Funktion generierenPDF() { Fenster.print(); }