Aplicação Front-end desenvolvida em ReactJS para clone do app Pipefy web, que é voltado para organização de tarefas, permitindo interação de arraste (drag n'drop) entre os cards nas listas de tarefas.
- Components
- Server API fake
- react-dnd
- useDrag
- react-dnd-html5-backend
- DndProvider
- useDrop
- useRef
- createContext
- useContext
- useState
- Immer
- react-icons
- styled-components
- Multiple CSS properties
- npm install
- npm run start / npm start
- Dê um fork nesse repositório
- Crie a sua branch com a feature
- git checkout -b my-feature
- Commit a sua contribuição
- git commit -m 'feat: My feature'
- Push a sua branch
- git push origin my-feature
import React from 'react';
import { DndProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import GlobalStyle from './styles/global';
import Header from './components/Header';
import Board from './components/Board';
function App() {
return (
// API dnd do html5
<DndProvider backend={ HTML5Backend }>
<Header />
<Board />
<GlobalStyle />
</DndProvider>
)
};
export default App;
import React, { useState } from 'react';
import produce from 'immer';
import { loadLists } from '../../services/api';
import BoardContext from './context';
import List from '../List';
import { Container } from './styles';
const data = loadLists();
export default function Board() {
const [lists, setLists] = useState(data);
function move(fromList, toList, from, to) {
setLists(produce(lists, draft => {
const dragged = draft[fromList].cards[from];
draft[fromList].cards.splice(from, 1);
draft[toList].cards.splice(to, 0, dragged);
}));
}
return (
<BoardContext.Provider value={{ lists, move }}>
<Container>
{ lists.map((list, index) =>
<List key={ list.title }
index={ index }
data={ list } />)
}
</Container>
</BoardContext.Provider>
);
};
import React from 'react';
import { MdAdd } from 'react-icons/md';
import { Container } from './styles';
import Card from '../Card';
export default function List({ data, index: listIndex }) {
return (
<Container done={ data.done }>
<header>
<h2>{ data.title }</h2>
{ data.creatable && (
<button type='button'>
<MdAdd size={24} color="#FFF" />
</button>
)}
</header>
<ul>
{ data.cards.map((card, index) => (
<Card key={ card.id }
listIndex={ listIndex }
index={ index }
data={ card }
/>
))}
</ul>
</Container>
);
};
import React, { useRef, useContext } from 'react';
import { useDrag, useDrop } from 'react-dnd';
import BoardContext from '../Board/context';
import { Container, Label } from './styles';
export default function Card({ data, index, listIndex }) {
const ref = useRef();
const { move } = useContext(BoardContext);
// Drag
const [{ isDragging }, dragRef] = useDrag({
item: { type: 'CARD', index, listIndex },
collect: monitor => ({
isDragging: monitor.isDragging()
})
})
// Drop
const [, dropRef] = useDrop({
accept: 'CARD',
hover(item, monitor) {
const draggedListIndex = item.listIndex;
const targetListIndex = listIndex;
// item selecionado
const draggedIndex = item.index;
// alvo destino
const targetIndex = index;
// Não modificar na mesma posição do card e lista
if(draggedIndex === targetIndex && draggedListIndex === targetListIndex) {
return;
}
// tamanho elemento
const targetSize = ref.current.getBoundingClientRect();
// calc ponto central
const targetCenter = (targetSize.bottom - targetSize.top) / 2;
// distância dos pontos (quanto do item foi arrastado encima)
const draggedOffset = monitor.getClientOffset();
// calc ((topo dos pontos) - (topo elemento))
const draggedTop = draggedOffset.y - targetSize.top;
// Não modificar posição de um item que já anteceda ao outro
if (draggedIndex < targetIndex && draggedTop < targetCenter) {
return;
}
// Não modificar posição de um item que já preceda ao outro
if (draggedIndex > targetIndex && draggedTop > targetCenter) {
return;
}
move(draggedListIndex, targetListIndex, draggedIndex, targetIndex);
item.index = targetIndex;
item.listIndex = targetListIndex;
}
})
dragRef(dropRef(ref));
return (
<Container ref={ ref } isDragging={ isDragging }>
<header>
{ data.labels.map(label => <Label key={ label } color={ label } />) }
</header>
<p>{ data.content }</p>
{ data.user && <img src={ data.user } alt='avatar' /> }
</Container>
);
};