Introdução
English version: React and typescript components lib, part 5: documentation of components using storybook
Na parte cinco, será adicionada a documentação dos componentes usando storybook, com o objetivo de mostrar a partir dela como funciona os componentes e as diferentes customizações possíveis para eles. A ideia é mostrar a parte prática da adição do storybook nesse artigo e como realizar a documentação, para uma referência de como funciona a lib segue o artigo que escrevi anteriormente de referência: Documentação de componentes em React com Typescript usando Storybook (versão 8).
Setup
Primeiro será realizado o setup com as configurações inicias do storybook, a partir da execução no terminal: npx storybook init
.
Esse comando vai detectar as dependências do projeto e apresentar três passos:
- informar qual versão do
storybook
vai ser adicionada - perguntar se o projeto usa
webpack
ouvite
- perguntar se vai usar o
storybook
para documentação ou teste
Quanto ao primeiro ponto é só prosseguir. Do segundo ponto, o projeto usa rollup
, então não seria na prática nenhuma das duas opções, mas como o storybook
não tem integração direta com rollup
, será escolhida a opção vite
, que vai ser utilizado para rodar o storybook
. Da última pergunta, é informar que vai ser usado para documentação.
Ao finalizar o processo vão ser adicionadas automaticamente libs que permitirão executar o storybook
.
No momento desse artigo gerou as seguintes versões:
"@storybook/addon-essentials": "^8.6.12",
"@storybook/addon-interactions": "^8.6.12",
"@storybook/addon-onboarding": "^8.6.12",
"@storybook/blocks": "^8.6.12",
"@storybook/react": "^8.6.12",
"@storybook/react-vite": "^8.6.12",
"@storybook/test": "^8.6.12",
"eslint-plugin-storybook": "^0.12.0",
"storybook": "^8.6.12",
E em package.json
serão adicionados automaticamente dois scripts:
"scripts": {
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
O primeiro executa o storybook localmente, o segundo faz o build do storybook.
E vai gerar uma pasta .storybook
, com dois arquivos configurados para permitir a execução do storybook: main.ts
e preview.ts
.
Além de uma pasta stories
, que possui alguns exemplos de documentação prontos, mas como não vamos utilizar vai ser removida essa pasta.
Dessa forma estava já configurado com sucesso o storybook caso a app utilizasse o vite. Mas como utiliza rollup vai ser necessária adicionar duas libs para permitir a execução com sucesso:
yarn add vite @storybook/builder-vite --dev
No momento desse artigo gerou as seguintes versões:
"@storybook/builder-vite": "^8.6.12",
"vite": "^6.3.5"
Modificação componentes
Antes de seguir com a documentação, vai ser adaptado os componentes da lib para definição das propriedades default e ajustes.
Começando pelo Tag.tsx
:
- Definição dos valores de props default e remoção de
textWeight
(pois já tem uma propriedade que permite mexer de forma customizável com o font weight do texto:textFontWeight
):
// ...
const Tag = ({
text = "Tag",
type = "default",
textColor = "#fff",
textFontWeight = 600,
textFontSize,
textFontFamily,
backgroundColor,
format = "semiRounded",
borderRadius,
size = "medium",
padding,
}: TagProps) => {
- Ajuste da definição de types do componente, uma vez que
textWeight
não é mais uma props presente no componente e que oformat
default agora é semiRounded:
// ...
export interface TagProps {
type?: "default" | "success" | "alert" | "error";
text: string;
textColor?: string;
textFontWeight?: number;
textFontSize?: string;
textFontFamily?: string;
backgroundColor?: string;
format?: "square" | "semiRounded" | "rounded";
borderRadius?: string;
size?: "small" | "medium" | "large";
padding?: string;
}
export interface StyledTagProps {
$type?: "default" | "success" | "alert" | "error";
$textColor?: string;
$textFontWeight?: number;
$textFontSize?: string;
$textFontFamily?: string;
$backgroundColor?: string;
$format?: "square" | "semiRounded" | "rounded";
$borderRadius?: string;
$size?: "small" | "medium" | "large";
$padding?: string;
}
- Ajuste na definição de propriedades a partir do styled-components para levar em consideração de forma mais clara o
size
small, otype
default e oformat
square:
// ...
export const StyledTag = styled.div<StyledTagProps>`
border: none;
padding: ${(props) =>
props.$padding
? props.$padding
: props.$size === "large"
? "15px 20px"
: props.$size === "medium"
? "10px 12px"
: props.$size === "small" && "7px"};
background-color: ${(props) =>
props.$backgroundColor
? props.$backgroundColor
: props.$type === "error"
? "#e97451"
: props.$type === "alert"
? "#f8de7e"
: props.$type === "success"
? "#50c878"
: props.$type === "default" && "#d3d3d3"};
pointer-events: none;
border-radius: ${(props) =>
props.$borderRadius
? props.$borderRadius
: props.$format === "rounded"
? "30px"
: props.$format === "semiRounded"
? "5px"
: props.$format === "square" && "0"};
width: fit-content;
`;
- Ajuste na renderização do componente, definindo o font size do texto com base na props
textFontSize
ousize
:
// ...
}: TagProps) => {
const fontSize = textFontSize
? textFontSize
: size === "large"
? "18px"
: size === "small"
? "14px"
: "16px";
return (
<StyledTag
data-testid="tag"
$type={type}
$backgroundColor={backgroundColor}
$format={format}
$borderRadius={borderRadius}
$size={size}
$padding={padding}
>
<Text
color={textColor}
fontWeight={textFontWeight}
fontSize={fontSize}
fontFamily={textFontFamily}
>
{text}
</Text>
</StyledTag>
);
};
Como foi mexida nas propriedades default do componente de Tag
, vai ser necessário mexer no arquivo de teste dele Tag.test.tsx
:
- Ajuste dos
expect
do primeiro teste para validar as propriedades default:
it("should render component with default properties", () => {
render(<Tag text="Tag" />);
const element = screen.getByTestId("tag");
expect(element).toBeInTheDocument();
expect(element).toHaveStyleRule("background-color", "#d3d3d3");
expect(element).toHaveStyleRule("border-radius", "0");
expect(element).toHaveStyleRule("padding", "7px");
expect(within(element).getByText("Tag")).toHaveStyleRule("color", "#fff");
});
- Remoção do teste
should render component with bold text font weight
, uma vez que a props que ele considerava foi removida.
Agora referente a Text.tsx
:
- Definição dos valores de props default:
// ...
const Text = ({
children,
color="#000",
weight="normal",
fontWeight,
fontSize="16px",
fontFamily,
}: TextProps) => (
- Ajuste na definição de propriedades a partir do styled-components, para em algumas situações só definir a propriedade se chegar a props relacionada:
// ...
export const StyledText = styled.span<StyledTextProps>`
${(props) => props.$color && `color: ${props.$color};`}
${(props) => props.$fontSize && `font-size: ${props.$fontSize};`}
font-weight: ${(props) =>
props.$fontWeight
? props.$fontWeight
: props.$weight
? props.$weight
: "normal"};
${(props) => props.$fontFamily && `font-family: ${props.$fontFamily};`}
`;
Documentação dos componentes
Para a documentação dos componentes, vão ser criados dois tipos de arquivos para cada um deles:
- stories: onde será definido os cenários de customização a serem mostrados na documentação
- mdx: a documentação do componente, onde vão ser definidos os textos e incorporado os cenários presentes no arquivo de stories
Antes de partir para a documentação, será criado um componente responsável por centralizar os componentes renderizados na documentação e colocar um pequeno espaço entre eles, criando uma pasta StorybookContainer
dentro de src/components
:
- arquivo
StorybookContainer.tsx
:
import React from "react";
import styled from "styled-components";
export interface StorybookContainerProps {
children: React.ReactNode;
}
export const StyledStorybookContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
`;
const StorybookContainer = ({ children }: StorybookContainerProps) => (
<StyledStorybookContainer>
{children}
</StyledStorybookContainer>
);
export default StorybookContainer;
- arquivo
index.ts
:
export { default } from "./StorybookContainer";
Agora, podemos começar com a documentação do componente de Tag, criando dentro da pasta src/components/Tag
:
- Tag.stories.tsx
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import Tag from "./Tag";
import StorybookContainer from "../StorybookContainer/StorybookContainer";
const meta: Meta<typeof Tag> = {
title: "Tag",
component: Tag,
argTypes: {},
};
export default meta;
type Story = StoryObj<typeof Tag>;
export const Default: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
</StorybookContainer>
),
};
export const PredefinedType: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
<Tag {...args} type="success" />
<Tag {...args} type="alert" />
<Tag {...args} type="error" />
</StorybookContainer>
),
};
export const PredefinedFormat: Story = {
args: {
format: "square",
},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
<Tag {...args} format="semiRounded" />
<Tag {...args} format="rounded" />
</StorybookContainer>
),
};
export const PredefinedSize: Story = {
args: {
size: "small",
},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
<Tag {...args} size="medium" />
<Tag {...args} size="large" />
</StorybookContainer>
),
};
export const Text: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
<Tag {...args} text="Example" />
</StorybookContainer>
),
};
export const BackgroundColor: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
<Tag {...args} backgroundColor="#3b82f6" />
</StorybookContainer>
),
};
export const CustomFormat: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
<Tag {...args} borderRadius="15px 2px" />
</StorybookContainer>
),
};
export const CustomSize: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
<Tag {...args} padding="20px 40px" />
</StorybookContainer>
),
};
export const TextColor: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
<Tag {...args} textColor="#000" />
</StorybookContainer>
),
};
export const TextFontWeight: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
<Tag {...args} textFontWeight={100} />
</StorybookContainer>
),
};
export const TextFontFamily: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
<Tag {...args} textFontFamily="Arial" />
</StorybookContainer>
),
};
export const TextFontSize: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Tag {...args} />
<Tag {...args} textFontSize="20px" />
</StorybookContainer>
),
};
Nele em Meta
é definido o componente que está sendo documentado e o título que vai aparecer no menu lateral do storybook. Em cada export const
é definido um cenário que estará presente na documentação. Citando dois exemplos de cenário, o primeiro Default
vai trazer o componente com as propriedades default dele, o segundo PredefinedType
vai mostrar como o componente se comporta com a variação da props type
.
- Tag.mdx
import { Canvas, Controls, Meta } from "@storybook/blocks";
import * as Stories from "./Tag.stories";
<Meta of={Stories} />
# Tag
Tag base component.
<Canvas of={Stories.Default} withToolbar />
<Controls of={Stories.Default} />
## Predefined properties
### Type
There are four type predefined properties: default, success, alert and error.
<Canvas of={Stories.PredefinedType} withToolbar />
### Format
There are three format predefined properties: square, semiRounded (default) and rounded.
<Canvas of={Stories.PredefinedFormat} withToolbar />
### Size
There are three size predefined properties: small, medium (default) and large.
<Canvas of={Stories.PredefinedSize} withToolbar />
## Custom properties
### Text
Tag text can be modified.
<Canvas of={Stories.Text} withToolbar />
### Background Color
Tag background color can be modified.
<Canvas of={Stories.BackgroundColor} withToolbar />
### Format
Tag format can be modified by border radius definition.
<Canvas of={Stories.CustomFormat} withToolbar />
### Size
Tag size can be modified by padding definition.
<Canvas of={Stories.CustomSize} withToolbar />
### Text Color
Tag text color can be modified.
<Canvas of={Stories.TextColor} withToolbar />
### Text Font Weight
Tag text font weight can be modified.
<Canvas of={Stories.TextFontWeight} withToolbar />
### Text Font Family
Tag text font family can be modified.
<Canvas of={Stories.TextFontFamily} withToolbar />
### Text Font Size
Tag text font size can be modified.
<Canvas of={Stories.TextFontSize} withToolbar />
No arquivo acima, em <Canvas of={Stories.Default} withToolbar />
é renderizado o componente com o cenário Default
dele. Logo abaixo em <Controls of={Stories.Default} />
é renderizada uma tabela com todas as props do componente disponíveis para customização, permitindo que ao mexer nessas props seja possível ver em tempo real o impacto no componente acima da tabela.
Em Predefined properties
são mostrados os cenários em que, com base nas props modificadas, a própria app define o css associado.
Em Custom properties
são mostrados os cenários em que as props modificadas impactam diretamente no css definido.
Rodando localmente o storybook com yarn storybook
:
Agora, será realizada a documentação do componente de Text, criando dentro da pasta src/components/Text
:
- Text.stories.tsx
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import Text from "./Text";
import StorybookContainer from "../StorybookContainer/StorybookContainer";
const meta: Meta<typeof Text> = {
title: "Text",
component: Text,
argTypes: {},
};
export default meta;
type Story = StoryObj<typeof Text>;
export const Default: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Text {...args}>Text</Text>
</StorybookContainer>
),
};
export const PredefinedFontWeight: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Text {...args}>Text</Text>
<Text {...args} weight="bold">
Text
</Text>
</StorybookContainer>
),
};
export const Color: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Text {...args}>Text</Text>
<Text {...args} color="#800080">
Text
</Text>
</StorybookContainer>
),
};
export const CustomFontWeight: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Text {...args}>Text</Text>
<Text {...args} fontWeight={900}>
Text
</Text>
</StorybookContainer>
),
};
export const FontSize: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Text {...args}>Text</Text>
<Text {...args} fontSize="30px">
Text
</Text>
</StorybookContainer>
),
};
export const FontFamily: Story = {
args: {},
render: (args) => (
<StorybookContainer>
<Text {...args}>Text</Text>
<Text {...args} fontFamily="Arial">
Text
</Text>
</StorybookContainer>
),
};
- Text.mdx
import { Canvas, Controls, Meta } from "@storybook/blocks";
import * as Stories from "./Text.stories";
<Meta of={Stories} />
# Text
Text base component.
<Canvas of={Stories.Default} withToolbar />
<Controls of={Stories.Default} />
## Predefined properties
### Font Weight
There are two font weight predefined properties: normal(default) and bold.
<Canvas of={Stories.PredefinedFontWeight} withToolbar />
## Custom properties
### Color
Text color can be modified.
<Canvas of={Stories.Color} withToolbar />
### Font Weight
Text font weight can be modified.
<Canvas of={Stories.CustomFontWeight} withToolbar />
### Font Size
Text font size can be modified.
<Canvas of={Stories.FontSize} withToolbar />
### Font Family
Text font family can be modified.
<Canvas of={Stories.FontFamily} withToolbar />
Ficando da seguinte forma:
Deploy storybook
Agora temos os dois componentes documentados e rodando com sucesso o storybook localmente. Mas a ideia não é a documentação ser para uso de quem está desenvolvendo a lib, mas sim estar disponível para mostrar os componentes que a lib oferece para quem está pensando em adicionar ela. Para isso vai ser usado o chromatic para fazer o deploy da documentação e disponibilizar ela, que é um serviço de publicação criado pelos responsáveis do storybook.
Primeiro será adicionada a lib:
yarn add chromatic --dev
No momento desse artigo gerou a seguinte versão:
"chromatic": "^12.0.0",
Agora será necessário seguir os seguintes passos para realizar o deploy:
- Acessar página de cadastro
- Selecionar
Connect with GitHub
para acessar (assumindo que seu repositório está lá) - Depois do cadastro, selecionar
Choose GitHub repo
- Selecionar o repositório da lib presente no seu github
- Copiar e guardar o comando presente em
Publish your Storybook
, nesse ponto não dá para seguir sem executar no terminal o comando, mas antes vamos fazer o build do storybook - No terminal executar
yarn build-storybook
- Depois no terminal executar o comando passado na página do chromatic (toda vez que precisar fazer o deploy, esse exato comando precisará ser executado depois do build)
Por fim executando com sucesso, vai avisar no terminal que o storybook foi publicado com sucesso e passado a url onde é possível acessar ele em View your Storybook at "url"
.
Acessando a url vai ser possível ver a documentação dos componentes.
README.md
Agora que o storybook foi publicado, no README da app vai ser passada a url em que ele se encontra (aqui vou colocar a minha url gerada, no seu caso seria colocar a sua lá):
Arquivo .gitignore
No arquivo .gitignore será colocado para ignorar a pasta storybook-static
, que é gerada quando se executa o yarn build-storybook
:
dist
node_modules
*storybook.log
storybook-static
package.json
Será mudada a versão dentro de package.json
para 0.5.0, uma vez que uma nova versão da lib será disponibilizada:
{
"name": "react-example-lib",
"version": "0.5.0",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/griseduardo/react-example-lib.git"
},
"scripts": {
"build": "rollup -c --bundleConfigAsCjs",
"lint-src": "eslint src",
"lint-src-fix": "eslint src --fix",
"lint-fix": "eslint --fix",
"format-src": "prettier src --check",
"format-src-fix": "prettier src --write",
"format-fix": "prettier --write",
"test": "jest",
"prepare": "husky",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"lint-staged": {
"src/components/**/*.{ts,tsx}": [
"yarn lint-fix",
"yarn format-fix"
],
"src/components/**/*.tsx": "yarn test --findRelatedTests --bail"
},
"devDependencies": {
"@babel/core": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"@eslint/js": "^9.19.0",
"@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "11.1.6",
"@storybook/addon-essentials": "^8.6.12",
"@storybook/addon-interactions": "^8.6.12",
"@storybook/addon-onboarding": "^8.6.12",
"@storybook/blocks": "^8.6.12",
"@storybook/builder-vite": "^8.6.12",
"@storybook/react": "^8.6.12",
"@storybook/react-vite": "^8.6.12",
"@storybook/test": "^8.6.12",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@types/jest": "^29.5.14",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"chromatic": "^12.0.0",
"eslint": "^9.19.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-storybook": "^0.12.0",
"husky": "^9.1.7",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-styled-components": "^7.2.0",
"lint-staged": "^15.5.0",
"prettier": "^3.4.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"rollup": "^4.30.1",
"rollup-plugin-dts": "^6.1.1",
"rollup-plugin-peer-deps-external": "^2.2.4",
"storybook": "^8.6.12",
"styled-components": "^6.1.14",
"typescript": "^5.7.3",
"typescript-eslint": "^8.23.0",
"vite": "^6.3.5"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"styled-components": "^6.1.14"
},
"eslintConfig": {
"extends": [
"plugin:storybook/recommended"
]
}
}
Arquivo CHANGELOG
Como uma nova versão será disponibilizada, será adicionado em CHANGELOG.md
sobre o que foi modificado:
## 0.5.0
_May. 29, 2025_
- change Tag and Text default behavior
- add storybook
- add Tag and Text storybook docs
## 0.4.0
_Abr. 29, 2025_
- setup husky and lint-staged
- define pre-commit actions
## 0.3.0
_Mar. 24, 2025_
- setup jest and testing-library
- add components tests
## 0.2.0
_Fev. 24, 2025_
- setup typescript-eslint and prettier
- add custom rules
## 0.1.0
_Jan. 29, 2025_
- initial config
Estrutura de pastas
A estrutura de pastas ficará da seguinte forma:
Publicação nova versão
Primeiro passo a ser realizado é ver se a execução do rollup ocorre com sucesso. Para isso será executado o yarn build
no terminal, que foi definido em package.json
.
Executando com sucesso, é realizar a publicação da nova versão da lib: npm publish --access public
Conclusão
A ideia desse artigo foi fazer o setup do storybook, criar a documentação dos componentes presentes na app e fazer o deploy dela, uma vez que a documentação é importante para mostrar os componentes que a lib está disponibilizando.
Segue o repositório no github e a lib no npmjs com as novas modificações.
Top comments (0)