Skip to content
/ atomic-design Public template

Atomic Design - React in Ionic with Storybook, Typescript and Scaffdog.

Notifications You must be signed in to change notification settings

marcosvaz/atomic-design

Repository files navigation

Instalação e rodando o projeto

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.

Arquitetura

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.

Atomic

Para maiores referências: UX Collective

Vamos tomar os seguintes exemplos:

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:

Atom

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:

Molecule

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:

Organism

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:

Template

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:

Page

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>
  )
}

Documentação

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: Scaffdog - estrutura Scaffdog - Destino Scaffdog - Nomeando 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, um boolean, um number, 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')
}

[...]

About

Atomic Design - React in Ionic with Storybook, Typescript and Scaffdog.

Resources

Stars

Watchers

Forks

Packages

No packages published