Domine o Storybook no React: Guia Completo com Addons e Exemplos!

Já se deparou com a necessidade de organizar todos os seus componentes de interface de usuário em um local central no React?

Se você está começando agora com React, talvez ainda não tenha pensado nisso.

O que isso realmente significa?

Dê uma olhada nos exemplos do react-beautiful-dnd.

Esses exemplos que você viu são chamados de “histórias”, e a ferramenta usada para criá-las é o Storybook.

Agora, você provavelmente já tem uma ideia do que abordaremos neste artigo. Sem mais delongas, vamos explorar!

O que é o Storybook?

O Storybook é um ambiente de desenvolvimento de UI isolado, que oferece um espaço dedicado para seus componentes. Ele permite que você experimente com seus componentes de diversas formas, sem a necessidade de executar o aplicativo principal. Com uma configuração simples, o Storybook pode ser executado em sua própria porta.

Ele não se limita ao React, pois pode ser utilizado com a maioria dos frameworks frontend, como Vue, Angular, Mithril, Marko, Svelte e outros.

Você pode encontrar mais informações sobre o Storybook aqui.

O que é uma “história”?

Uma história define o estado renderizado de um componente. Se pensarmos em um componente comum, ele pode ser usado de diferentes maneiras, com diferentes props. Podemos escrever uma história para cada um desses estados.

Vamos imaginar que temos um componente Button.

Um botão pode ter diferentes estados, como desabilitado, carregando, primário, secundário, pequeno, grande, médio, etc. Se listarmos todos os estados, a explicação ficaria extensa. Acredito que você entendeu a ideia. Você compreenderá melhor ao começar a trabalhar com o Storybook.

Você pode ver as histórias do botão em diferentes tamanhos (Grande, Médio, Pequeno).

Configurando o Storybook em um Projeto

Vamos configurar o Storybook em um projeto React.

Vamos começar:

  • Crie um projeto React com o seguinte comando. O nome pode ser o que você preferir.
npx create-react-app storybook-demo
  • Agora, instale o Storybook em seu projeto com o seguinte comando:
npx sb init

A configuração do Storybook está concluída.

O Storybook fornece um servidor separado para nós.

Como iniciá-lo?

O Storybook adiciona automaticamente um comando no arquivo de scripts do seu projeto. Você pode verificá-lo no arquivo package.json, dentro da seção “scripts”. Por enquanto, execute o seguinte comando para iniciar o servidor do Storybook:

npm run storybook

O Storybook iniciará um novo servidor com a porta especificada na seção “scripts” do arquivo package.json. Ele abrirá o Storybook automaticamente no seu navegador padrão (assim como o servidor do React).

Você verá algumas histórias por padrão. Você pode removê-las se não desejar ou guardá-las como referência. Como discutimos anteriormente, um botão pode ter diversos estados, e você pode vê-los no Storybook (nem todos os estados que mencionamos). Escreveremos um conjunto maior de histórias para o botão na última seção deste guia.

Explore as diferentes seções do Storybook e familiarize-se com elas. Vamos cobrir algumas delas ao longo deste tutorial.

Vamos escrever nossa primeira história.

Testando o Storybook

Já vimos o Storybook em execução e alguns exemplos nele.

  • Crie uma pasta chamada “Button” dentro da pasta “src”.
  • Crie os arquivos “Button.jsx”, “Button.css” e “constants.js” dentro desta pasta.
  • Insira o código correspondente dos trechos abaixo nos arquivos.

Button.jsx

import React, { Component } from "react";
import PropTypes from "prop-types";

import "./Button.css";

import { buttonTypes, buttonVariants, buttonSizes } from "./constants";

class Button extends Component {
    static defaultProps = {
        isDisabled: false,
        type: "filled",
        variant: "oval",
        size: "medium",
        backgroundColor: "#1ea7fd",
        textColor: "#ffffff",
    };

    static buttonTypes = buttonTypes;
    static buttonVariants = buttonVariants;
    static buttonSizes = buttonSizes;

    renderButton = () => {
        const {
            text,
            isDisabled,
            type,
            variant,
            size,
            backgroundColor,
            textColor,
            onClick,
        } = this.props;
        return (
            <button
                onClick={onClick}
                className={`default ${variant} ${size} ${
                    isDisabled ? "disabled" : ""
                }`}
                style={
                    type === buttonTypes.outline
                        ? {
                              border: `1px solid ${backgroundColor}`,
                              color: "#000000",
                              backgroundColor: "transparent",
                          }
                        : {
                              backgroundColor: `${backgroundColor}`,
                              border: `1px solid ${backgroundColor}`,
                              color: textColor,
                          }
                }
                disabled={isDisabled}
            >
                {text}
            </button>
        );
    };

    render() {
        return this.renderButton();
    }
}

Button.propTypes = {
    text: PropTypes.string,
    isDisabled: PropTypes.bool,
    type: PropTypes.oneOf([buttonTypes.outline, buttonTypes.filled]),
    variant: PropTypes.oneOf([buttonVariants.oval, buttonVariants.rectangular]),
    size: PropTypes.oneOf([
        buttonSizes.small,
        buttonSizes.medium,
        buttonSizes.large,
    ]),
    backgroundColor: PropTypes.string,
    textColor: PropTypes.string,
    onClick: PropTypes.func,
};

export { Button };

Button.css

.default {
    border: none;
    cursor: pointer;
    background-color: transparent;
}

.default:focus {
    outline: none;
}

.disabled {
    opacity: 0.75;
    cursor: not-allowed;
}
.small {
    font-size: 12px;
    padding: 4px 8px;
}

.medium {
    font-size: 14px;
    padding: 8px 12px;
}

.large {
    font-size: 16px;
    padding: 12px 16px;
}

.oval {
    border-radius: 4px;
}

.rectangular {
    border-radius: 0;
}

constants.js

export const buttonTypes = {
    outline: "outline",
    filled: "filled",
};

export const buttonVariants = {
    oval: "oval",
    rectangular: "rectangular",
};

export const buttonSizes = {
    small: "small",
    medium: "medium",
    large: "large",
};

O que este código faz?

Criamos um componente Button comum que pode ser usado de diferentes maneiras. Agora, temos um componente que pode ter diversos estados.

Vamos escrever nossa primeira história seguindo os passos abaixo:

  • Crie um arquivo chamado “Button.stories.jsx”.
  • Importe React e nosso componente Button para este arquivo.
  • Agora, defina um título ou caminho para as histórias dos nossos componentes. Vamos definir isso usando o seguinte código:
export default {
   title: ‘common/Button’,
}

O código acima colocará todas as histórias no arquivo atual dentro do diretório “common/Button/”.

  • Exporte um botão com props obrigatórios da seguinte forma:
export const defaultButton = () => (
    <Button text=”Default Button” onClick={() => {}} />
);

Nossa primeira história está pronta. Execute o Storybook com o comando abaixo e veja o resultado:

npm run storybook

Vamos escrever mais histórias no final, não se preocupe.

Qual a sua utilidade no desenvolvimento Frontend?

Qual é a principal vantagem de usar o Storybook?

Imagine que estamos trabalhando em uma equipe de 10 pessoas e precisamos verificar os componentes comuns que cada um criou para o projeto atual.

Como podemos fazer isso?

Precisaríamos acessar todos os componentes para verificá-los, o que é demorado e não é a forma mais eficiente. É aí que o Storybook se torna útil.

Como podemos utilizá-lo para superar este problema?

Podemos escrever histórias para os componentes comuns (qualquer componente de UI) utilizando o Storybook. Sempre que um membro da equipe quiser verificar os componentes de outras pessoas, ele simplesmente executará o servidor do Storybook e verá todos os componentes da interface do usuário, como vimos anteriormente.

Podemos fazer muito mais com os componentes renderizados no Storybook. O Storybook tem um conceito chamado “Addons” que adiciona “superpoderes” às nossas histórias.

Por exemplo, se precisarmos verificar a responsividade dos componentes de interface no próprio Storybook, podemos usar um addon chamado “Viewport”. Aprenderemos mais sobre addons nas próximas seções.

Trabalhando com o Storybook

Nesta seção, escreveremos diferentes histórias definindo vários estados para nosso componente Button.

Escrever histórias não é muito complicado. Uma história define um estado de um componente. Se você observar as props de um componente, entenderá facilmente os diferentes casos de uso do mesmo.

Vamos escrever algumas histórias adicionando algumas props opcionais:

export const largeButton = () => (
    <Button text="Large Button" onClick={() => {}} size="large" />
);
export const outlineSmallButton = () => (
    <Button
        text="Outline Small Button"
        onClick={() => {}}
        size="small"
        type="outline"
    />
);
export const rectangularLargeButton = () => (
    <Button
        text="Rectangular Large Button"
        onClick={() => {}}
        size="large"
        variant="rectangular"
    />
);


export const disabledButton = () => (
    <Button text="Disabled Button" onClick={() => {}} isDisabled={true} />
);


export const warningButton = () => (
    <Button
        text="Warning Button"
        onClick={() => {}}
        backgroundColor="orange"
    />
);

As três histórias acima definem diferentes casos de uso para o nosso componente Button. Agora, é sua vez de adicionar outros casos de histórias para o nosso componente comum. Tente adicionar “disabledSmallRectangularButton”, “hazardButton”, “successDisabledButton”, etc.

Não darei o código para estes casos. Você precisará escrevê-los para entender melhor. Você pode ver o código completo das histórias que escrevemos até agora:

import React from "react";

import { Button } from "./Button";

export default {
    title: "src/common/Button",
};

export const defaultButton = () => (
    <Button text="Default Button" onClick={() => {}} />
);

export const largeButton = () => (
    <Button text="Large Button" onClick={() => {}} size="large" />
);

export const outlineSmallButton = () => (
    <Button
        text="Outline Small Button"
        onClick={() => {}}
        size="small"
        type="outline"
    />
);

export const rectangularLargeButton = () => (
    <Button
        text="Rectangular Large Button"
        onClick={() => {}}
        size="large"
        variant="rectangular"
    />
);

export const disabledButton = () => (
    <Button text="Disabled Button" onClick={() => {}} isDisabled={true} />
);

export const warningButton = () => (
    <Button
        text="Disabled Button"
        onClick={() => {}}
        backgroundColor="orange"
    />
);

Agora, você tem total controle sobre como escrever histórias para um componente.

Vamos para a próxima seção, onde aprenderemos sobre addons e como eles impulsionam nossas histórias.

Addons do Storybook

Teremos vários addons disponíveis por padrão. Nesta seção, exploraremos os addons mais úteis para o nosso desenvolvimento.

Vamos impulsionar as histórias do nosso Button.

Controles

Os controles adicionam um recurso que permite fornecer props personalizadas para o componente dentro do próprio Storybook. Para o nosso componente Button, podemos adicionar controles para modificar as diferentes props no Storybook.

Imagine que precisamos descobrir a melhor cor para o fundo do botão. Seria demorado se testássemos a cor de fundo uma por uma no componente. Em vez disso, podemos adicionar um controle que nos permita escolher uma cor diferente diretamente no Storybook, facilitando os testes.

Vamos ver como adicionar controles às nossas histórias de botão.

Primeiro, precisamos definir todas as props abaixo do título, da seguinte forma:

export default {
    title: "src/common/Button",
    argTypes: {
        text: { control: "text" },
        backgroundColor: { control: "color" },
        isDisabled: { control: "boolean" },
        size: {
            control: { type: "select", options: ["small", "medium", "large"] },
        },
        type: {
            control: { type: "select", options: ["filled", "outline"] },
        },
        variant: {
            control: { type: "select", options: ["oval", "rectangular"] },
        },
    },
};

Em seguida, separe as props do componente e forneça-as como argumentos da seguinte forma:

export const outlineSmallButton = (args) => (
    <Button {...args} onClick={() => {}} />
);
outlineSmallButton.args = {
    text: "Outline Small Button",
    size: "small",
    type: "outline",
};

Você pode ver os controles na parte inferior da janela de visualização do componente.

Você pode ver a aba “Controles” na parte inferior da janela de visualização do componente. Experimente!.

Atualize todas as histórias como acima. É mais como aprender a sintaxe dos addons do Storybook. Em “argTypes”, usamos diferentes tipos de controles. Você pode encontrar todos os controles presentes no Storybook aqui.

As histórias de botão atualizadas ficarão assim:

import React from "react";

import { Button } from "./Button";

export default {
    title: "src/common/Button",
    argTypes: {
        text: { control: "text" },
        backgroundColor: { control: "color" },
        isDisabled: { control: "boolean" },
        size: {
            control: { type: "select", options: ["small", "medium", "large"] },
        },
        type: {
            control: { type: "select", options: ["filled", "outline"] },
        },
        variant: {
            control: { type: "select", options: ["oval", "rectangular"] },
        },
    },
};

export const defaultButton = (args) => <Button {...args} onClick={() => {}} />;
defaultButton.args = {
    text: "Default Button",
};

export const largeButton = (args) => (
    <Button {...args} onClick={() => {}} size="large" />
);
largeButton.args = {
    text: "Large Button",
};

export const outlineSmallButton = (args) => (
    <Button {...args} onClick={() => {}} />
);
outlineSmallButton.args = {
    text: "Outline Small Button",
    size: "small",
    type: "outline",
};

export const rectangularLargeButton = (args) => (
    <Button {...args} onClick={() => {}} />
);
rectangularLargeButton.args = {
    text: "Rectangular Large Button",
    size: "large",
    variant: "rectangular",
};

export const disabledButton = (args) => <Button {...args} onClick={() => {}} />;
disabledButton.args = {
    text: "Disabled Button",
    isDisabled: true,
};

export const warningButton = (args) => <Button {...args} onClick={() => {}} />;
warningButton.args = {
    text: "Warning Button",
    backgroundColor: "orange",
};

Ações

Ações são eventos em JavaScript. Podemos clicar em um botão, que é um evento em JavaScript. Podemos executar ações ao clicar no botão utilizando o addon “Actions”.

Com as ações, podemos testar se os eventos estão funcionando corretamente ou não. Um botão desabilitado não deve poder ser clicado, e um botão habilitado deve ser clicável. Podemos garantir isso usando as ações.

Vamos ver como adicionar ações ao evento de clique do botão:

  • Importe a ação do addon do Storybook com a seguinte instrução:
import { action } from "@storybook/addon-actions";
  • Substitua todos os () => {} pela seguinte instrução:
action("Button is clicked!")

Agora, abra o Storybook e clique em um botão. Você verá a mensagem exibida na aba “Actions”, ao lado da aba “Controls”. A mensagem não será exibida se você clicar em um botão desabilitado, pois ele está inativo.

Podemos usar a ação para diferentes eventos como “onChange”, “onMouseOver”, “onMouseOut”, etc., para garantir que eles estejam funcionando corretamente. Tente implementar o mesmo para “onChange” em um elemento de entrada.

Consulte a documentação para ações aqui.

Plano de Fundo

Podemos alterar o plano de fundo da janela de visualização usando o addon “Background”. Não precisamos escrever nenhum código. Basta alterá-lo no Storybook. Veja o GIF abaixo:

Viewport

Também podemos testar a responsividade de nossos componentes diretamente no Storybook. Veja o GIF abaixo para mais informações sobre as opções de viewport:

Documentos

Podemos documentar nossos componentes no Storybook utilizando o addon “Docs”. Isso é muito útil quando trabalhamos em equipe, pois permite que os desenvolvedores compreendam os componentes diretamente. Isso economiza tempo de todos os envolvidos.

Na janela de visualização dos componentes no Storybook, você pode encontrar a aba “Docs” no canto superior direito da aba “Canvas”. Ela contém toda a documentação de todas as histórias de um componente. Se quisermos documentar o componente incluindo marcação e renderização, precisamos usar “Button.stories.mdx”. Basta inserir um pouco de código extra de markdown dentro desse arquivo, juntamente com as histórias do componente.

Agora, escreveremos um documento para as nossas histórias. O código inclui marcação e renderização de componentes. É apenas uma questão de aprender a sintaxe e você entenderá rapidamente.

Vamos ver o código do documento “Button.stories.mdx”:

<!--- Button.stories.mdx -->

import {
    Meta,
    Story,
    Preview,
    ArgsTable
} from '@storybook/addon-docs/blocks';

import { Button } from './Button';

<Meta title="MDX/Button" component={Button} />

# Button Documentation

With `MDX` we can define a story for `Button` right in the middle of our
Markdown documentation.

<ArgsTable of={Button} />

export const Template = (args) => <Button {...args} />

## Default Button
We can write the documentation related to the Default Button
<Preview>
    <Story name="Default Button" args={{
        text: 'Default Button'
    }}>
    {Template.bind({})}
   </Story>
</Preview>

## Large Button
We are writing sample docs for two stories, you can write rest of them
<Preview>
    <Story name="Large Button" args={{
        text: "Large Button",
        }}>
        {Template.bind({})}
    </Story>
</Preview>

Saiba mais sobre os componentes de documentação aqui.

Você pode explorar mais sobre os addons aqui.

Conclusão

Espero que tenha gostado deste guia e que tenha aprendido bastante sobre o Storybook. Utilize-o de forma eficaz em sua equipe para aumentar a produtividade.

Está começando com React? Confira estes recursos de aprendizagem:

Boa codificação! 🙂