Eu acho incrível toda essa ideia de podermos utilizar tecnologias web em diversos lugares, e é super interessante a versatilidade que essas coisas nos proporciona também.
Caso você ainda não saiba, é possível criar aplicações para dekstop com tecnologias web (html, js e css), e isso é tão comum que acaba passando despercebido por muita gente, temos apps no nosso dia a dia que são com essas tecnologias e nem parece, como é o caso do Visual Studio Code, Skype, Discord e vários outros.
Esse post foi atualizado e finalizado (a parte que falta do SQLite) e está disponível aqui.
Boa parte desses apps utilizam o Electron um framework focado em criação de apps desktop multiplataforma com tecnologias web. E como eu disse antes, todo esse ecossistema é super flexível, a ponto de conseguir usar o React junto ao electron, e é isso que vamos ver agora!
Overview do projeto
Nesse post, eu queria sintetizar algumas vontades que já tenho a um tempo e não consigo trazer: Um post de "React iniciante" e um sobre o Electron. Por isso, esse post vai ser dividido em partes e será o mais detalhado que eu conseguir.
Um aplicativo que eu tenho usado bastante ultimamente é o Microsoft To Do, então pensei que seria uma boa tentarmos criar um clone funcional dele durante essa serie de postagens.
Vamos utilizar como banco de dados o Sqlite, que é um banco SQL relacional super leve e portátil, perfeito para nosso projeto.
Iniciando com o projeto
Podemos iniciar o projeto como um projeto Electron normal e depois adicionar o React, porém, essa não é a melhor forma nem a mais performática de fazer isso.
O Electron não é exatamente elogiado pelos seus Builds enxutos ou seu baixo consumo de memória ram, então, vamos utilizar um Boilerplate, que nada mais é do que um esqueleto pronto com essa integração que precisamos e com varias configurações que vão nos ajudar e muito a poupar recursos da maquina.
Vamos iniciar clonando o repositório do boilerplate e dando um nome para o nosso projeto:
git clone --depth 1 --branch main https://github.com/electron-react-boilerplate/electron-react-boilerplate.git ms-todo-clone
Após clonado, vamos navegar até a pasta do nosso projeto e instalar as dependências dele, aqui, estou levando em conta que você já tenha um ambiente Nodejs configurado, e que já tenha o yarn
instalado, caso ainda não tenha, instale com npm install -g yarn
e agora podemos continuar:
cd ms-todo-clone
yarn
Dependências instaladas, podemos rodar o nosso projeto:
yarn start
No nosso projeto, temos a seguinte estrutura:
O que podemos observar logo de cara é que temos dois package.json
em pastas diferentes, e isso é por que o boilerplate separa dependências de desenvolvimento na raiz e dependências da aplicação dentro do app. Você pode ler detalhadamente sobre essa escolha aqui.
Outro ponto interessante e que temos o CSS Modules, React Router Dom e o testing-library já configurado no projeto.
Iniciando a UI
Para iniciar nosso projeto, vamos criar uma pasta views
, styles
e components
dentro de src/renderer
, vamos copiar as rotas dentro do arquivo App.tsx
e criar um arquivo chamado routes.tsx
e colar o conteúdo das rotas(a função que esta sendo exportada e os imports do react router dom), em seguida podemos deletar o arquivo App.tsx
, agora mova o arquivo App.global.css
para a pasta de styles. Dentro da pasta de views vamos criar uma pasta chamada Home
, dentro dela vamos criar um arquivo chamado index.tsx
e um home.module.tsx
, importe esse componente para o seu arquivo de rota e use na rota "/".
Vamos precisar fazer algumas alterações no nosso index.tsx que fica em renderer
, primeiro vamos corrigir a importação do componente App
para apontar para nossas rotas, em seguida vamos importar nosso CSS global que movemos para a pasta styles
.
Nossa estrutura ficará dessa forma:
Nosso arquivo de rotas:
Nosso arquivo index.tsx:
Com toda nossa estrutura configurada, vamos começar nossa interface, vamos começar pela sidebar, então dentro da pasta components
cria uma pasta chamada Sidebar
com os arquivos index.tsx
e Sidebar.module.css
dentro dela, vamos inicialmente adicionar o seguinte código a esse componente:
import React from 'react';
import styles from './Sidebar.module.css';
export default function Sidebar() {
return (
<div>
<a href="#">Meu dia</a>
<a href="#">Importante</a>
<a href="#">Planejado</a>
<a href="#">Trabalho</a>
</div>
);
}
Importe o componente na Home, ficando dessa forma por enquanto:
import React from 'react';
import Sidebar from '../../components/Sidebar';
export default function Home() {
return <Sidebar />;
}
Vamos criar um arquivo de tema, para centralizar nossos estilos. Na pasta styles
crie uma pasta chamada themes
e crie um arquivo chamado default.css
e nele vamos por o seguinte conteúdo:
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;300&display=swap');
:root {
--primary-color: #788cde;
--secondary-color: #323232;
--background-color: #282828;
--alternate-background-color: #1e1e1e;
--text-color: #e1e1e1;
--text-color-light: #777676bb;
--font: Roboto;
--text-cancel-color: #dd2a2c;
--link-color: #e1e1e1;
--link-color--hover: #543fd7;
}
Agora vamos estilizar nossa Sidebar, para isso abra o arquivo Sidebar.module.css
e vamos colocar o seguinte:
@import '../../styles/themes/default.css';
.sidenav {
width: 240px;
height: 100vh;
background: var(--background-color);
overflow-x: hidden;
padding-left: 10px;
}
.sidenav a {
padding: 10px;
text-decoration: none;
font-family: var(--font);
font-size: 1.1rem;
color: var(--link-color);
display: block;
}
.sidenav a:hover {
background-color: var(--alternate-background-color);
}
Agora vamos criar nosso componente de logo. Na pasta components
crie a pasta Logo
e dentro dela index.tsx
e Logo.module.css
:
import React from 'react';
import styles from './Logo.module.css';
export default function Logo() {
return <h1 className={styles.logo}>TODO Clone</h1>;
}
@import '../../styles/themes/default.css';
.logo {
color: var(--primary-color);
margin: 20px 0px;
font-family: var(--font);
font-weight: 800;
}
Importe o componente de logo na nossa Sidebar
:
import React from 'react';
import Logo from '../Logo';
import styles from './Sidebar.module.css';
export default function Sidebar() {
return (
<div className={styles.sidenav}>
<Logo />
<a href="#">Meu dia</a>
<a href="#">Importante</a>
<a href="#">Planejado</a>
<a href="#">Trabalho</a>
</div>
);
}
Como resultado, teremos o seguinte até agora:
Crie duas novas pastas em components
: TaskArea
e TaskItem
.
TaskItem é o componente que representará nossa tarefa, no arquivo index.tsx
vamos incluir o seguinte:
import React from 'react';
import { format } from 'date-fns';
import styles from './TaskItem.module.css';
export type TaskItemType = {
label: string;
date: string;
id: number;
checked: boolean;
onChange: (id: number) => void;
};
export default function TaskItem({
date,
label,
id,
checked,
onChange,
}: TaskItemType) {
function handleCheck() {
onChange(id);
}
return (
<div
className={`${styles.container} ${checked ? styles['task-finish'] : ''}`}
id={`${id}`}
>
<input
className={styles.checkbox}
type="checkbox"
checked={checked}
onChange={handleCheck}
/>
<div className="col">
<div className="row">
<p className={styles['task-label']}>{label}</p>
</div>
<div className="row">
<p className={styles['task-date']}>
{format(new Date(date), "E., dd 'de' MMM")}
</p>
</div>
</div>
</div>
);
}
Perceba que você precisará instalar o
date-fns
.
E o CSS dele fica da seguinte forma:
@import '../../styles/themes/default.css';
.container {
display: flex;
align-items: center;
background-color: var(--secondary-color);
padding: 10px 20px;
margin: 1px 0px;
color: var(--text-color);
font-family: var(--font);
border-radius: 6px;
}
.container > :nth-child(1) {
margin-right: 15px;
}
.task-label {
font-size: 0.85rem;
color: var(--text-color);
}
.task-date {
font-size: 0.85rem;
color: var(--text-cancel-color);
font-weight: bold;
}
.task-finish .task-label {
text-decoration: line-through;
}
input[type='checkbox'] {
-webkit-appearance: none;
appearance: none;
background-color: var(--alternate-background-color);
margin: 0;
font: inherit;
color: currentColor;
width: 1.35em;
height: 1.35em;
border: 0.15em solid var(--background-color);
border-radius: 50px;
transform: translateY(-0.075em);
display: grid;
place-content: center;
}
input[type='checkbox']::before {
content: '';
width: 0.55em;
height: 0.55em;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
border-radius: 50px;
transform: scale(0);
transform-origin: bottom left;
transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em var(--background-color);
background-color: var(--background-color);
}
input[type='checkbox']:checked::before {
transform: scale(1);
}
input[type='checkbox']:checked {
background-color: var(--primary-color);
}
input[type='checkbox']:focus {
outline: max(2px, 0.15em) solid currentColor;
outline-offset: max(2px, 0.15em);
}
input[type='checkbox']:disabled {
color: var(--primary-color);
cursor: not-allowed;
}
TaskArea será o container que irá gerenciar quais tasks serão exibidas. O código dele fica assim:
import React, { useState } from 'react';
import TaskItem from '../TaskItem';
import styles from './TaskArea.module.css';
export default function TaskArea() {
const [tasks, setTasks] = useState([
{
id: 1,
label: 'Teste de task',
date: new Date().toDateString(),
checked: false,
},
{
id: 2,
label: 'Teste de task',
date: new Date().toDateString(),
checked: false,
},
{
id: 3,
label: 'Teste de task',
date: new Date().toDateString(),
checked: false,
},
]);
const handleCheckTask = (id: number) => {
const newState = tasks.map((task) => {
if (task.id === id) {
return {
...task,
checked: !task.checked,
};
}
return task;
});
setTasks(newState);
};
return (
<div className={styles.container}>
{tasks.map((task) => (
<TaskItem
checked={task.checked}
date={task.date}
label={task.label}
key={task.id}
id={task.id}
onChange={handleCheckTask}
/>
))}
</div>
);
}
E o CSS:
@import '../../styles/themes/default.css';
.container {
display: flex;
flex-direction: column;
width: 100%;
padding: 10px;
background-color: var(--alternate-background-color);
}
Com isso, já podemos voltar na nossa view Home
e importar o componente TaskArea
e vamos importar os estilos dela também:
import React from 'react';
import Sidebar from '../../components/Sidebar';
import TaskArea from '../../components/TaskArea';
import styles from './Home.module.css';
export default function Home() {
return (
<div className={styles.container}>
<Sidebar />
<TaskArea />
</div>
);
}
CSS da Home:
.container {
display: flex;
flex-direction: row;
}
Com isso, já temos nossa UI exibindo as tarefas e marcando ou desmarcando como "feita".
Nosso próximo passo será:
- Criar novas tarefas
- Editar tarefas
- Deletar tarefas
- Adicionar data a tarefa
- Verificar se a tarefa está fora do prazo
- Navegar entre os menus laterais
- Conectar com a base de dados
Top comments (2)
Muito bom... gostaria de ver os próximos passos.
Show demais manooow!