Para rodar o projeto, será necessário possuir o Yarn instalado.
Clone ou baixe o .zip do projeto, acesse a pasta e rode o comando yarn
, isso fará com que os pacotes necessários para rodar o projeto sejam instalados.
Após a instalação, para rodar o projeto, rode o comando yarn start
.
O projeto está baseado na arquitetura Atomic Design, que é baseado na estrutura atômica para organização de seus componentes, sendo assim, organizado da seguinte maneira (da menor para a maior estrutura, respectivamente): átomos - moléculas - organismos - templates - páginas.
Para maiores referências: UX Collective
Pode-se dizer que um botão é um dos menores componentes que você terá no seu projeto, e um dos mais reutilizados, ou seja, o melhor a se fazer, é componentizar ele da forma mais genérica o possível e que supra a necessidade do projeto a ser desenvolvido.
Basicamente ele se parece assim:
E sua estrutura em código seria:
export const Button = ({
children = "Label",
onClick = () => void,
...rest
}) => {
return (
<Container onClick={onClick} {...rest}>
{children}
</Container>
)
}
Em seguida, temos um item de uma lista, que esse por sua vez, renderiza um texto como um label, e um botão, que no caso, podemos dizer que seriam dois átomos
, e assim sendo, a soma de um ou mais átomos
, forma uma molécula
:
Com sua estrutura:
export const ListItem = ({
title="Label",
button={
onClick: () => void,
text: "Label"
}
}) => {
return (
<Container>
<Text>{title}</Text>
<Button onClick={button.onClick}>{button.text}</Button>
</Container>
)
}
Em seguida temos uma lista que renderiza esses itens, no caso, a soma de diversas moléculas
resulta em um organismo
, pode-se parecer que temos apenas uma molécula
, mas isso vai depender de quantos itens iremos renderizar, ou seja, se renderizarmos 2 ou mais, já acaba sendo um organismo
de certa forma, e podemos levar em conta também que, criando esse organismo
, vamos conseguir reutilizar o map, e não será preciso realizar novamente a função, em outro componente, para criar uma lista de itens:
Por sua vez, o componente recebe os itens a serem renderizados para criar a lista:
export const List = ({
items
}) => {
return (
items.map(item =>
<ListItem
key={item.id}
title={item.title}
button={item.button}
/>
)
)
}
Em conseguinte, temos um template
de Dashboard, que vai ser a base de uma página
, em que aqui, vamos renderizar um Header no topo, uma Sidebar na lateral esquerda, e o conteúdo da página
:
Temos o seguinte código de exemplo:
export const DashboardTemplate = ({
page,
children
}) => {
return (
<Container>
<Header />
<Sidebar active={page} />
<Content>
{ children }
</Content>
</Container>
)
}
E por último, temos a renderização efetiva da página
, onde ficará realmente toda a parte de chamada das API's
e a parte de lógica da página
, que será passado aos componentes que serão renderizados, como o template
e o organismo
que renderizaremos dentro da página
a seguir:
Então o código da página seria basicamente a lógica e a renderização dos componentes específicos para aquela página, sendo da seguinte forma:
export const Dashboard = () => {
const [ isLoading, setIsLoading ] = useState(false)
const [ items, setItems ] = useState([])
const listItemsService = async () => {
setIsLoading(true);
try {
const response = await getItemsFromService();
setItems(response);
} catch (e) {
// sua mensagem de erro
throw Error(`Erro no retorno do serviço getItemsFromService [${e}]`)
} finally {
setIsLoading(false);
}
}
useEffect(() => {
listItemsService();
}, [])
return (
<DashboardTemplate page={'Dashboard'}>
<Loading loading={isLoading}>
<List items={items} />
</Loading>
</DashboardTemplate>
)
}
Para documentar o projeto, está sendo utilizado o Storybook, onde, ao criar cada componente utilizando-se do Scaffdog, é gerado um arquivo [nome-do-componente].stories.tsx
que é a documentação referente ao mesmo.
Vamos seguir um exemplo desse projeto:
Ao criar um componente, utilize o Scaffdog com o comando yarn g
:
Selecionando um tipo de componente para criar, como um átomo chamado Button
, por exemplo, veremos que ele irá criar uma pasta com o nome do componente e 3 arquivos dentro da mesma, sendo os seguintes: index.tsx
, styles.ts
e o Button.stories.tsx
.
O arquivo Button.stories.tsx
vai possuir o seguinte conteúdo:
import { Meta, Story } from '@storybook/react/types-6-0'
import React from 'react'
import { Button, IButtonProps } from '.'
export default {
title: 'Atoms/Button',
component: Button,
} as Meta
const Template: Story<IButtonProps> = args => <Button {...args} />
export const Default = Template.bind({})
Default.args = {}
Com isso podemos ver que as propriedades do componente são definidas pelo IButtonProps
que vem do arquivo index.tsx
, ou seja, ao definirmos lá, essas propriedades serão importadas para cá, e o próprio componente também, e esse arquivo cria a documentação que pode ser acessada com o comando yarn storybook
e acessando a url http://localhost:6006.
Ao definir as propriedades, no componente, em IButtonProps
através do Typescript, essas propriedades são mostradas na documentação para que você possa testar variações das mesmas, como por exemplo, se definirmos que nosso botão vai ter a propriedade children
e ela será uma string
, e uma propriedade onClick
que será uma função sem retorno (() => void
):
[...]
// interface do Typescript com a propriedade 'children' definida como uma 'string'
// e com a propriedade 'onClick' definida como uma função sem retorno '() => void'
export interface IButtonProps {
children: string
onClick: () => void
}
export const Button: React.FC<IButtonProps> = ({
children,
onClick,
...rest,
}): JSX.Element => {
return (
<Container onClick={onClick} {...rest}>
{children}
</Container>
)
}
Utilizaremos o Typescript para realizar essa definição de tipos para as propriedades, como por exemplo, uma propriedade ser uma
string
, umboolean
, umnumber
, uma função sem retorno() => void
, ou outros tipos do Typescript.Para uma maior referência sobre os tipos básicos do Typescript, clique aqui.
E definirmos no Button.stories.tsx
os argumentos padrão:
[...]
const Template: Story<IButtonProps> = args => <Button {...args} />
// Definição dos argumentos padrão para a documentação
export const Default = Template.bind({})
Default.args = {
children: 'Entrar', // Aqui poderá ser qualquer valor no lugar de 'Entrar', o componente será criado apenas para documentação
onClick: () => alert('Botão entrar') // Aqui você pode utilizar qualquer função, o componente será criado apenas para documentação
}
E para criar uma variante desse componente, na documentação do Storybook, você deve apenas adicionar um item, semelhante ao Default
, para cada uma das variações que desejar documentar:
[...]
export const Default = Template.bind({})
Default.args = {
children: 'Padrão',
onClick: () => alert('Botão padrão')
}
export const Variante1 = Template.bind({})
Variante2.args = {
children: 'Variante 1',
onClick: () => alert('Botão Variante 1')
}
export const Variante2 = Template.bind({})
Variante2.args = {
children: 'Variante 2',
onClick: () => alert('Botão Variante 2')
}
[...]