Cuaderno de historia¶
¶
¶
■h1 títulos - Construir interfaz de usuario en la solución "Clase de inscripción" Storybook es un taller de frontend para construir componentes de la interfaz de usuario y páginas en aislamiento. Le ayuda a desarrollar y compartir estados difíciles de alcanzar y casos de borde sin necesidad de ejecutar toda su aplicación. Miles de equipos lo utilizan para el desarrollo, pruebas y documentación de la UI. ▪/p] ■/div titulada
¶
########################################################################################################################################################################################################################################################## Copiar todos los comandos¶
########################################################################################################################################################################################################################################################## Generar PDF seleccionado/button¶
■/div titulada ■/div titulada
Cuadro de contenidos¶
- Instalación
- Empezar
- Writing Stories
- Story Formats
- Controles " Acciones
- Addons
- Configuración
- Theming
- Testing
- Documentación
- Deployment
- Características avanzadas
- Performance
- Integración
- Solucionando
- Las mejores prácticas
Instalación¶
Inicio rápido¶
# 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
Instalación manual¶
# 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
Configuración Marco Específico¶
# 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
Estructura del proyecto¶
my-app/
├── .storybook/
│ ├── main.js # Main configuration
│ └── preview.js # Global settings
├── src/
│ ├── components/
│ │ ├── Button/
│ │ │ ├── Button.js
│ │ │ ├── Button.css
│ │ │ └── Button.stories.js
│ │ └── Header/
│ │ ├── Header.js
│ │ └── Header.stories.js
│ └── stories/ # Example stories
├── package.json
└── README.md
Comienzo¶
Historia básica¶
// 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',
},
};
Ejemplo de componente¶
// 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,
};
Running Storybook¶
# 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
Escribir historias¶
Estructura de historia¶
// 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
},
};
Story Naming¶
// 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
};
Múltiples componentes¶
// 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>
);
Parámetros de historia¶
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',
},
},
},
};
Formatos de historia¶
CSF 3.0 (Current)¶
// 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)¶
// 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',
};
Funciones de Render personalizado¶
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',
},
};
Funciones de juego¶
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');
},
};
Controles " Acciones¶
Configuración de ArgTypes¶
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,
},
},
};
Acciones¶
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');
},
},
};
Controles avanzados¶
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¶
Complementos esenciales¶
// .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',
],
};
Accesibilidad Addon¶
// .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¶
// .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',
},
};
Diseño Tokens Addon¶
// 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',
},
},
],
};
Complementos personalizados¶
// .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>
);
};
Configuración¶
Configuración principal¶
// .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'],
};
Configuración de vista previa¶
// .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
};
Medio ambiente¶
// .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¶
Aduanas Tema¶
// .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,
});
Tema oscuro¶
// .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',
});
Cambio de tema¶
// .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,
},
},
};
Pruebas¶
Pruebas visuales¶
// .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,
},
};
Pruebas de interacción¶
// 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();
},
};
Pruebas de unidad con historias¶
// 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');
});
});
Pruebas de accesibilidad¶
// 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();
});
});
Documentación¶
Documentación automática¶
// 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.',
},
},
},
};
Documentación personalizada¶
// 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 Documentación¶
<!-- Button.stories.mdx -->
import { Meta, Story, Canvas, ArgsTable, Description } from '@storybook/addon-docs';
import { Button } from './Button';
<Meta title="Example/Button" component={Button} />
# Button
<Description of={Button} />
## Examples
### Primary Button
<Canvas>
<Story name="Primary" args={{ primary: true, label: 'Button' }}>
{(args) => <Button {...args} />}
</Story>
</Canvas>
### Secondary Button
<Canvas>
<Story name="Secondary" args={{ label: 'Button' }}>
{(args) => <Button {...args} />}
</Story>
</Canvas>
## Props
<ArgsTable of={Button} />
## Usage Guidelines
- Use primary buttons sparingly, typically one per page
- Secondary buttons can be used multiple times
- Always provide meaningful labels
- Consider accessibility when choosing colors
## Code Example
```jsx
importa { Button } de './Button';
función MyComponent() {
(Retorno)
■div
################################################################################################################################################################################################################################################################
Acción primaria
■/Button título
################################################################################################################################################################################################################################################################
Medidas secundarias
■/Button título
■/div titulada
);
}
### Páginas de documentación
```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 instalar @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
Netlify Deployment¶
# 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
Despliegue de Vercel¶
// vercel.json
{
"buildCommand": "npm run build-storybook",
"outputDirectory": "storybook-static",
"framework": null
}
```_
### Páginas de GitHub
```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
Docker Deployment¶
# 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¶
# 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 "/*"
Características avanzadas¶
Configuración de Webpack personalizado¶
// .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;
},
};
Configuración de Babel personalizada¶
// .storybook/main.js
module.exports = {
babel: async (options) => ({
...options,
plugins: [
...options.plugins,
['babel-plugin-styled-components', { displayName: true }],
],
}),
};
```_
### Configuración Medioambiental
```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;
},
};
Custom Manager UI¶
// .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 },
},
});
Ejecución¶
Lazy Cargando¶
// .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',
},
],
};
Optimización del envase¶
// .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;
},
};
Optimización de memoria¶
// .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 />;
},
];
Integración¶
Herramientas de diseño Integración¶
// 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',
},
},
};
Pruebas de integración¶
// 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¶
# .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"
Solución de problemas¶
Cuestiones comunes¶
# 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
Modo de depuración¶
# Enable debug logging
DEBUG=storybook:* npm run storybook
# Webpack debug
npm run storybook -- --debug-webpack
# Verbose output
npm run storybook -- --verbose
Cuestiones de ejecución¶
// .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,
},
};
Cuestiones de construcción¶
# 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
Buenas prácticas¶
Story Organization¶
- ** Estructura jerárquica**: Use patrones de nombres consistentes
- Logical Grouping: Los componentes relacionados con el grupo juntos
- Clear Naming: Use nombres descriptivos
- Documentación: Incluir documentación completa
Componente de desarrollo¶
- Isolación: Desarrollar componentes aislados
- ** Interfaz de Props**: Diseño de interfaces de prop claras y consistentes
- ** Valores predeterminados**: Proveer predeterminados sensibles
- Manipulación del espejo: Casos de borde del mango con gracia
Estrategia de ensayo¶
- ** Pruebas visuales**: Usar pruebas de regresión visual
- ** Pruebas de interacción**: Prueba las interacciones del usuario
- ** Pruebas de accesibilidad**: Asegurar que los componentes sean accesibles
- Unit Testing: Prueba de la lógica del componente
Optimización del rendimiento¶
- Lazy Cargando: Utilice la tienda de historias v7 para carga perezosa
- Bundle Splitting: Optimize webpack settings
- ** Gestión de memoria**: Limpiar los recursos adecuadamente
- ** Optimización del edificio**: Minimizar los tiempos de construcción
Team Collaboration¶
- ** Sistema de diseño**: Mantener un sistema de diseño coherente
- Documentación: Mantenga la documentación actualizada
- Code Reviews: Reseña de historias y componentes
- Automatización: Automatizar las pruebas y el despliegue
-...
Resumen¶
Storybook es una herramienta esencial para el desarrollo de vanguardia moderno que permite:
- Aislamiento completo: Desarrollar y probar componentes UI de forma independiente
- ** Documentación visual**: Crear documentación viva para sistemas de diseño
- ** Desarrollo interactivo**: Construir componentes con retroalimentación en tiempo real
- Testing Integration: Pruebas visuales, de interacción y accesibilidad
- Colaboración del equipo: Compartir componentes entre equipos e interesados
- ** Gestión del sistema de diseño**: Mantener patrones de interfaz de usuario consistentes
Los principales beneficios incluyen ciclos de desarrollo más rápidos, una mejor calidad de los componentes, una mejor colaboración en equipo y una documentación completa. Storybook admite todos los principales marcos de frontend y ofrece amplias opciones de personalización a través de addons y configuración.
" copia de la funciónToClipboard() {} comandos const = document.querySelectorAll('code'); que todos losCommands = '; comandos. paraCada(cmd = confianza allCommands += cmd.textContent + '\n'); navigator.clipboard.writeText(allCommands); alerta ('Todos los comandos copiados a portapapeles!'); }
función generaPDF() { ventana.print(); } ■/script título