├── .gitattributes ├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── src ├── components │ ├── card │ │ ├── card.scss │ │ └── index.jsx │ └── kanban │ │ ├── kanban.scss │ │ └── index.jsx ├── index.js ├── App.scss ├── App.jsx └── mockData.js ├── .gitignore ├── README.md └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-kanban-ui/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-kanban-ui/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-kanban-ui/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/components/card/card.scss: -------------------------------------------------------------------------------- 1 | .card { 2 | padding: 30px; 3 | background-color: #212121; 4 | border-radius: 10px; 5 | } -------------------------------------------------------------------------------- /src/components/card/index.jsx: -------------------------------------------------------------------------------- 1 | import './card.scss' 2 | 3 | const Card = props => { 4 | return ( 5 |
6 | {props.children} 7 |
8 | ) 9 | } 10 | 11 | export default Card -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700;900&display=swap'); 2 | 3 | * { 4 | padding: 0; 5 | margin: 0; 6 | box-sizing: border-box; 7 | } 8 | 9 | body { 10 | background-color: #212121; 11 | color: #E4E4E4; 12 | font-family: 'Lato', sans-serif; 13 | line-height: 1.5; 14 | } -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import './App.scss' 2 | import Kanban from './components/kanban' 3 | 4 | function App() { 5 | return ( 6 |
7 |

8 | Kanban UI 9 |

10 | 11 |
12 | ) 13 | } 14 | 15 | export default App 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Video tutorial 2 | 3 | https://youtu.be/AL7IJVhuGeA 4 | 5 | # Reference 6 | 7 | - Create react app: https://create-react-app.dev/ 8 | - SASS: https://sass-lang.com/ 9 | - Boxicons: https://boxicons.com/ 10 | - Google font: https://fonts.google.com/ 11 | - React beautiful dnd: https://github.com/atlassian/react-beautiful-dnd/ 12 | 13 | # Preview 14 | 15 | !["React Draggable Kanban Board"](https://user-images.githubusercontent.com/67447840/155845190-bd85601d-d2a0-4419-82bf-b8361f33075a.gif "React Draggable Kanban Board") -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/components/kanban/kanban.scss: -------------------------------------------------------------------------------- 1 | .kanban { 2 | display: flex; 3 | align-items: flex-start; 4 | 5 | &__section { 6 | width: 400px; 7 | background-color: #383838; 8 | padding: 10px; 9 | border-radius: 10px; 10 | 11 | & ~ & { 12 | margin-left: 10px; 13 | } 14 | 15 | &__title { 16 | font-size: 1.2rem; 17 | font-weight: 700; 18 | margin: 10px 0 20px; 19 | } 20 | 21 | &__content { 22 | & > * ~ * { 23 | margin-top: 10px; 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-kanban-ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.14.1", 7 | "@testing-library/react": "^12.0.0", 8 | "@testing-library/user-event": "^13.2.1", 9 | "react": "^17.0.2", 10 | "react-beautiful-dnd": "^13.1.0", 11 | "react-dom": "^17.0.2", 12 | "react-scripts": "5.0.0", 13 | "sass": "^1.49.9", 14 | "web-vitals": "^2.1.0" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/mockData.js: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid' 2 | 3 | const mockData = [ 4 | { 5 | id: uuidv4(), 6 | title: ' 📃 To do', 7 | tasks: [ 8 | { 9 | id: uuidv4(), 10 | title: 'Learn JavaScript' 11 | }, 12 | { 13 | id: uuidv4(), 14 | title: 'Learn Git' 15 | }, 16 | { 17 | id: uuidv4(), 18 | title: 'Learn Python' 19 | }, 20 | ] 21 | }, 22 | { 23 | id: uuidv4(), 24 | title: ' ✏️ In progress', 25 | tasks: [ 26 | { 27 | id: uuidv4(), 28 | title: 'Learn CSS' 29 | }, 30 | { 31 | id: uuidv4(), 32 | title: 'Learn Golang' 33 | } 34 | ] 35 | }, 36 | { 37 | id: uuidv4(), 38 | title: ' ✔️ Completed', 39 | tasks: [ 40 | { 41 | id: uuidv4(), 42 | title: 'Learn HTML' 43 | } 44 | ] 45 | } 46 | ] 47 | 48 | export default mockData -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/kanban/index.jsx: -------------------------------------------------------------------------------- 1 | import './kanban.scss' 2 | import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd' 3 | import mockData from '../../mockData' 4 | import { useState } from 'react' 5 | import Card from '../card' 6 | 7 | const Kanban = () => { 8 | const [data, setData] = useState(mockData) 9 | 10 | const onDragEnd = result => { 11 | if (!result.destination) return 12 | const { source, destination } = result 13 | 14 | if (source.droppableId !== destination.droppableId) { 15 | const sourceColIndex = data.findIndex(e => e.id === source.droppableId) 16 | const destinationColIndex = data.findIndex(e => e.id === destination.droppableId) 17 | 18 | const sourceCol = data[sourceColIndex] 19 | const destinationCol = data[destinationColIndex] 20 | 21 | const sourceTask = [...sourceCol.tasks] 22 | const destinationTask = [...destinationCol.tasks] 23 | 24 | const [removed] = sourceTask.splice(source.index, 1) 25 | destinationTask.splice(destination.index, 0, removed) 26 | 27 | data[sourceColIndex].tasks = sourceTask 28 | data[destinationColIndex].tasks = destinationTask 29 | 30 | setData(data) 31 | } 32 | } 33 | 34 | return ( 35 | 36 |
37 | { 38 | data.map(section => ( 39 | 43 | {(provided) => ( 44 |
49 |
50 | {section.title} 51 |
52 |
53 | { 54 | section.tasks.map((task, index) => ( 55 | 60 | {(provided, snapshot) => ( 61 |
70 | 71 | {task.title} 72 | 73 |
74 | )} 75 |
76 | )) 77 | } 78 | {provided.placeholder} 79 |
80 |
81 | )} 82 |
83 | )) 84 | } 85 |
86 |
87 | ) 88 | } 89 | 90 | export default Kanban --------------------------------------------------------------------------------