Neste artigo vou mostrar um dos projetos que construí na formação React da Rocketseat, um projeto de duas páginas/telas, onde uma tela contém o timer, e a outra tela contém o histórico dos ciclos realizados.
Já que uma das melhores maneiras de se aprender é ensinando, resolvi criar esta postagem para revisitar e fixar melhor os fundamentos da biblioteca React.
Caso queira adquirir os cursos da Rocketseat com o meu cupom de desconto Acesse esse link
Nesta primeira parte iremos cobrir a estrutura da aplicação, a criação do projeto e a utilização da biblioteca Styled Components
para estilização da nossa aplicação.
Links úteis:
Capítulos:
Criação do projeto
1 - Usando o Vite
Vamos criar o projeto usando uma ferramenta chamada Vite que consegue fazer toda a configuração de um projeto frontend, configurando, por exemplo, o bundler e o compiler da nossa aplicação, nos poupando bastante tempo.
$ npm create vite@latest
Após executar o comando, dê um nome para a aplicação, após selecione React e TypeScript.
commit: feat: 🎉 add initial structure $ npm create vite@latest
2 - Instalando as dependências
Após o projeto ter sido criado, na raiz do projeto, instale as dependências com o comando:
$ npm i
commit: build: 🔧 install dependencies $ npm i
3 - (Opcional) Removendo arquivos desnecessários
commit: refactor: 🔥 remove vite unused files
Styled Components
1 - Instalando o pacote styled-components
Execute o comando na raiz do projeto:
$ npm i styled-components
commit: build: ➕ add styled-components $ npm i styled-components
2 - Instalação da tipagem
O pacote styled-components
era um daqueles pacotes que não traziam a tipagem do TypeScript junto dele:
desta forma, era preciso instalar a tipagem separadamente com esse comando:
# NÃO É MAIS PRECISO INSTALAR
$ npm i @types/styled-components -D
-D
é uma abreviatura de--save-dev
que significa que será instalado apenas para desenvolvimento, ou seja, essa biblioteca não fará parte dobuild
final da aplicação em produção. Isso porque o código TypeScript é convertido todo para JavaScript, então os tipos não serão usados no código final em produção.
Hoje em dia nas versões mais atuais, isso não é mais preciso, pois a tipagem já vem junto dele:
Você pode ignorar os commits abaixo, pois um deles possue a instalação dos tipos e o outro a desinstalação. Isso ocorreu, pois as aulas do Rocketseat estavam desatualizadas no momento. Mas como eu instalei a versão mais recente, pude desinstalar sem problema o pacote dos tipos.
commit: build: ➕ add types of styled-components $ npm i @types/styled-components -D
commit: build: ➖ remove @types/styled-components
with $ npm uninstall @types/styled-components
3 - Instale a extensão no VSCode
Essa extensão habilita o Syntax highlighting
, ou seja, deixa o código CSS colorido dentro do Tagged templates
4 - Entendendo como funciona o styled-components
Vamos criar um componente de botão para exemplificar o funcionamento do styled-components
.
Crie o arquivo src/components/Button.tsx
com o código:
export function Button() {
return <button>Enviar</button>
}
Em src/App.tsx
vamos repetir várias vezes o botão em tela:
import { Button } from "./components/Button";
export function App() {
return (
<>
<Button />
<Button />
<Button />
<Button />
</>
)
}
Os sinais
<>
e</>
chamadoFragment
é usado quando precisamos colocar vários components dentro sem precisar de uma tagdiv
Vamos agora adicionar uma propriedade para mudar a cor do botão.
Em src/components/Button.tsx
adicionamos uma tipagem das cores aceitas para cada botão:
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger' | 'success';
}
export function Button(props: ButtonProps) {
return <button>Enviar</button>
}
Em src/App.tsx
, para cada botão, vamos adicionar a propriedade de cor variant
:
import { Button } from "./components/Button";
export function App() {
return (
<>
<Button variant="primary" />
<Button variant="secondary" />
<Button variant="success" />
<Button variant="danger" />
<Button />
</>
)
}
Quando estamos utilizando o CSS tradicional, para estilizar precisamos utilizar as classes. Para isso, vamos utilizar o css-modules apenas para este exemplo, pois, mais adiante, vamos utilizar apenas o styled-components
.
Criando o arquivo de css-modules
em src/components/Button.module.css
com as classes:
.button {
width: 100px;
height: 40px;
}
vamos importar os estilos e colocar o className
em src/components/Button.tsx
import styles from "./Button.module.css";
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger' | 'success';
}
export function Button(props: ButtonProps) {
return <button className={styles.button}>Enviar</button>
}
e o resultado agora são botões com tamanhos maiores:
e com as cores no mesmo arquivo src/components/Button.tsx
:
import styles from "./Button.module.css";
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger' | 'success';
}
export function Button({ variant = 'primary' }: ButtonProps) {
return <button className={`${styles.button} ${styles[color]}`}>Enviar</button>
}
Agora falta criar as classes para as cores. No arquivo de CSS src/components/Button.module.css
:
.button {
width: 100px;
height: 40px;
}
.primary {
background-color: purple;
}
.secondary {
background-color: orange;
}
.danger {
background-color: red;
}
.success {
background-color: green;
}
e o resultado das cores é esse:
Refatorando usando Styled Components
Vamos renomear o arquivo de CSS src/components/Button.module.css
para ➡️ src/components/Button.styles.ts
terminando exatamente com .ts
e o conteúdo:
import styled from "styled-components";
export const ButtonContainer = styled.button`
width: 100px;
height: 40px;
`;
e voltando no arquivo src/components/Button.tsx
vamos trocar a tag button
para ButtonContainer
:
import { ButtonContainer } from "./Button.styles";
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger' | 'success';
}
export function Button({ variant = "primary" }: ButtonProps) {
return <ButtonContainer>Enviar</ButtonContainer>;
}
Como resultado temos os botões com os mesmos tamanhos, pois definimos o height
e o width
no ButtonContainer
:
Podemos definir a cor adicionando variant={variant}
no mesmo arquivo src/components/Button.tsx
:
import { ButtonContainer } from "./Button.styles";
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger' | 'success';
}
export function Button({ variant = "primary" }: ButtonProps) {
return <ButtonContainer variant={variant}>Enviar</ButtonContainer>;
}
e no arquivo de estilização src/components/Button.styles.ts
:
import styled from "styled-components";
interface ButtonContainerProps {
variant: "primary" | "secondary" | "danger" | "success";
}
export const ButtonContainer = styled.button`
width: 100px;
height: 40px;
`;
nesse mesmo arquivo vamos criar um tipo para variant:
import styled from "styled-components";
export type ButtonVariant = "primary" | "secondary" | "danger" | "success";
interface ButtonContainerProps {
variant: ButtonVariant;
}
export const ButtonContainer = styled.button`
width: 100px;
height: 40px;
`;
E no arquivo src/components/Button.tsx
vamos importar e usar essa tipagem:
import { ButtonContainer, ButtonVariant } from "./Button.styles";
interface ButtonProps {
variant?: ButtonVariant;
}
export function Button({ variant = "primary" }: ButtonProps) {
return <ButtonContainer variant={variant}>Enviar</ButtonContainer>;
}
Para completar a tipagem e fazer uso dela, vamos adicionar <ButtonContainerProps>
import styled from "styled-components";
export type ButtonVariant = "primary" | "secondary" | "danger" | "success";
interface ButtonContainerProps {
variant: ButtonVariant;
}
export const ButtonContainer = styled.button<ButtonContainerProps>`
width: 100px;
height: 40px;
`;
Tipagem feita, agora precisamos setar as cores no mesmo arquivo de estilização src/components/Button.styles.ts
adicionando a constante buttonVariants
:
import styled from "styled-components";
export type ButtonVariant = "primary" | "secondary" | "danger" | "success";
interface ButtonContainerProps {
variant: ButtonVariant;
}
const buttonVariants = {
primary: "purple",
secondary: "orange",
danger: "red",
success: "green",
};
export const ButtonContainer = styled.button<ButtonContainerProps>`
width: 100px;
height: 40px;
`;
e fazendo uma interpolação de strings ${}
, o Styled Components
envia todas as propriedades automaticamente para a função:
import styled from "styled-components";
export type ButtonVariant = "primary" | "secondary" | "danger" | "success";
interface ButtonContainerProps {
variant: ButtonVariant;
}
const buttonVariants = {
primary: "purple",
secondary: "orange",
danger: "red",
success: "green",
};
export const ButtonContainer = styled.button<ButtonContainerProps>`
width: 100px;
height: 40px;
${props => {
return `background-color: ${buttonVariants[props.variant]}`
}}
`;
o props.variant
é o valor que vem automaticamente do arquivo do componente do botão.
E o resultado com as cores permanece identicando:
Conclusão do Styled Components: A vantagem é não precisar usar multiplas classes para a estilização e mantendo um scopo de componente.
Apenas uma melhoria na questão de Syntax highlighting
:
Para melhorar podemos importar o css e acrescentar a palavra css na frente da template literals:
commit: feat: ✨ add button component to understand about styled-components
5 - Configurando Temas
Crie o arquivo src/styles/themes/default.ts
com o seguinte código:
export const defaultTheme = {
white: "#FFF",
primary: "#8257e6",
secondary: "orange",
};
No arquivo src/App.tsx
vamos importar o defaultTheme
e vamos passar como valor a propriedade theme
do ThemeProvider
:
import { ThemeProvider } from "styled-components";
import { Button } from "./components/Button";
import { defaultTheme } from "./styles/themes/default";
export function App() {
return (
<ThemeProvider theme={defaultTheme}>
<Button variant="primary" />
<Button variant="secondary" />
<Button variant="success" />
<Button variant="danger" />
<Button />
</ThemeProvider>
);
}
Se tivessemos mais de um tema, poderíamos trocar o valor
defaultTheme
paralightTheme
por exemplo:theme={lightTheme}
Podemos acessar as cores do tema usando props.theme.nomeDaPropriedade
:
`
background-color: ${(props) => props.theme.primary};
`
No arquivo do botão src/components/Button.styles.ts
:
...
export const ButtonContainer = styled.button<ButtonContainerProps>`
width: 100px;
height: 40px;
border-radius: 4px;
border: 0;
margin: 8px;
background-color: ${(props) => props.theme.primary};
color: ${(props) => props.theme.white};
`;
e temos esse resultado:
commit: feat: ✨ add themes
6 - Tipagem de Temas
Temos um problema de autocomplete ao digitar props.theme.
o VSCode não nos fornece as opções disponíveis, isso acontece por falta de configuração de tipagem:
Para corrigir isso, vamos criar um arquivo no diretório src/@types/styled.d.ts
.
A nomenclatura @types
foi usada nesse caso simplismente para permanecer no topo da árvore de pastas. Além de ficar com o ícone do TypeScript.
Já a nomenclatura do arquivo styled.d.ts
terminando com d.ts
, indica que esse arquivo possuí apenas código de tipagem do TypeScript, chamado de arquivo de definição de tipos.
Nesse arquivo src/@types/styled.d.ts
vamos adicionar duas importações:
import "styled-components";
import { defaultTheme } from "../styles/themes/default";
A imagem acima mostra o ponteiro do mouse descansando em cima da const defaultTheme
, indicando que a devolução do valor é do tipo objeto, com as propriedades do tipo string, ou seja, mostra a tipagem do defaultTheme
. Isso acontece pois o TypeScript cria a tipagem de forma automática pra gente, mas existe uma forma de nós acessarmos isso e guardar em uma variável com o seguinte código:
type ThemeType = typeof defaultTheme;
// ThemeType poderia ter qualquer nome
Na imagem acima podemos perceber que o tipo gerado (inferência de tipo) para o defaultTheme
foi guardado dentro de ThemeType
, isso graças ao typeof
que capturou a tipagem do defaultTheme
.
No arquivo src/@types/styled.d.ts
vamos fazer o uso do declare module
:
import "styled-components";
import { defaultTheme } from "../styles/themes/default";
type ThemeType = typeof defaultTheme;
declare module "styled-components" {
// Aqui vamos subscrever a tipagem do DefaultTheme
}
O declare module
cria uma tipagem para o módulo styled-components
do NPM, e toda vez que o styled-components
for importado, a tipagem, a definição de tipos que vai ser puxada, vai ser o que foi definido dentro do scopo do declare module
.
Como queremos apenas subscrever algo de dentro do declare module
, e não criar toda uma tipagem nova, nós tivemos que fazer a importação import "styled-components";
e fazer a declaração com o declare module "styled-components"
. Sem a importação estaríamos criando tudo do zero a definição de tipos do pacote styled-components
.
Na imagem acima, mostra o arquivo src/components/Button.styles.ts
, e nele o ponteiro do mouse está parado em cima da palavra theme
. Se clicar em cima de theme
segurando a tecla ctrl
ou command
, é direcionado para o arquivo de tipos do styled-components
:
Agora clicando em cima de DefaultTheme
em laranja, é direcionado para a exportação da tipagem do DefaultTheme
:
Na imagem acima, podemos ver que o objeto {}
do DefaultTheme
exportado está vazio, pois esse objeto foi feito para ser subscrito pelas nossas tipagens.
Então o código final do arquivo src/@types/styled.d.ts
ficará assim:
import "styled-components";
import { defaultTheme } from "../styles/themes/default";
type ThemeType = typeof defaultTheme;
// subescreve a tipagem do DefaultTheme
declare module "styled-components" {
export interface DefaultTheme extends ThemeType {}
}
Agora no arquivo src/components/Button.styles.ts
podemos ver a tipagem funcionando:
e se tentar colocar uma propriedade que não existe, um erro será acusado:
commit: feat: ✨ add type to themes and override DefaultTheme
7 - Estilos Globais
Criando o arquivo no diretório src/styles/global.ts
. Mais uma vez terminando com .ts
pois estamos utilizando styled-components
. Todo CSS global vamos colocar nesse arquivo:
import { createGlobalStyle } from "styled-components";
export const GlobalStyle = createGlobalStyle`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #333;
color: #FFF;
}
`;
Vamos agora importar no arquivo src/App.tsx
e utilizar:
import { ThemeProvider } from "styled-components";
import { Button } from "./components/Button";
import { GlobalStyle } from "./styles/global";
import { defaultTheme } from "./styles/themes/default";
export function App() {
return (
<ThemeProvider theme={defaultTheme}>
<Button variant="primary" />
<Button variant="secondary" />
<Button variant="success" />
<Button variant="danger" />
<Button />
{/* tanto faz a posição do <GlobalStyle /> dentro do ThemeProvider */}
<GlobalStyle />
</ThemeProvider>
);
}
Tanto faz a posição do <GlobalStyle />
dentro do ThemeProvider
, o importante é que <GlobalStyle />
esteja dentro do scopo do ThemeProvider
, senão o <GlobalStyle />
não terá acesso as variáveis do nosso tema.
Acessando a aplicação, podemos ver os estilos globais sendo aplicados, como o background na cor cinza:
commit: feat: ✨ add global styles
Cores e Fonte
Para este projeto foi escolhido a fonte Roboto e a fonte Roboto Mono
A fonte Roboto Mono
é uma fonte tipográfica monoespaçada, cujas letras e caracteres ocupam o mesmo espaço horizontal. Ela será ideal para ser usada no timer.
Adicionamos as fontes no arquivo index.html
:
<!doctype html>
<html lang="pt">
<head>
<meta charset="UTF-8" />
<!-- adicione essas duas linhas -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- o ideal é deixar os dois links acima por primeiro para carregar mais rápido as fontes -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- e adicione também esta linha -->
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,700;1,700&family=Roboto:wght@400;700&display=swap" rel="stylesheet" />
<title>Ignite Timer</title>
</head>
No arquivo de estilos globais src/styles/global.ts
:
import { createGlobalStyle } from "styled-components";
export const GlobalStyle = createGlobalStyle`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #333;
color: #FFF;
}
/* adicione */
body, input, textarea, button {
font-family: 'Roboto', sans-serif;
font-weight: 400;
font-size: 1rem;
}
`;
Fonte tipográfica definida, agora vamos definir as cores no nosso tema, no arquivo src/styles/themes/default.ts
:
export const defaultTheme = {
white: "#FFF",
"gray-100": "#E1E1E6",
"gray-300": "#C4C4CC",
"gray-400": "#8D8D99",
"gray-500": "#7C7C8A",
"gray-600": "#323238",
"gray-700": "#29292E",
"gray-800": "#202024",
"gray-900": "#121214",
"green-300": "#00B37E",
"green-500": "#00875F",
"green-700": "#015F43",
"red-500": "#AB222E",
"red-700": "#7A1921",
"yellow-500": "#FBA94C",
};
Vamos utilizar as cores no arquivo src/styles/global.ts
:
import { createGlobalStyle } from "styled-components";
export const GlobalStyle = createGlobalStyle`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:focus {
outline: none;
box-shadow: 0 0 0 2px ${(props) => props.theme["green-500"]};
}
body {
background: ${(props) => props.theme["gray-900"]};
color: ${(props) => props.theme["gray-300"]};
}
body, input, textarea, button {
font-family: 'Roboto', sans-serif;
font-weight: 400;
font-size: 1rem;
}
`;
Ao digitar props.theme.
, por causa da tipagem, temos todas as cores disponiveis no autocomplete:
Pra finalizar essa parte de cores, precisamos arrumar o erro no arquivo de estilos do botão src/components/Button.styles.ts
:
Trocamos de props.theme.primary
para props.theme["green-500"]
.
Feito isso já podemos fisualizar o novo estilo:
commit: feat: 💄 add fonts and colors
Top comments (0)