├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── jest.config.js ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── styles │ │ └── tailwind.postcss │ ├── undraw_completed.svg │ ├── undraw_creative_team.svg │ ├── undraw_done.svg │ ├── undraw_files_sent.svg │ ├── undraw_laravel_and_vue.svg │ ├── undraw_marketing.svg │ ├── undraw_task.svg │ └── undraw_team_work.svg ├── components │ └── AppButton.vue ├── default-boards.js ├── main.js ├── plugins │ └── AppIcon.js ├── router.js ├── store.js ├── utils.js └── views │ ├── Board.vue │ ├── Home.vue │ ├── Pricing.vue │ ├── Task.vue │ └── Workspace.vue ├── tailwind.config.js ├── tests └── unit │ ├── .eslintrc.js │ └── example.spec.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kanban Board App 2 | 3 | This is a kanban board prototype created with Vue.js to provide people the opportunity practice applying techniques and best practices for creating components. 4 | 5 | ## Setup 6 | 7 | ### Requirement 8 | 9 | - [Git](https://git-scm.com/) 10 | - [Node.js](https://nodejs.org/) 11 | 12 | ### Optional 13 | 14 | - [yarn](https://yarnpkg.com/) 15 | 16 | ### Instructions 17 | 18 | > If you want to keep a copy of your work, make sure to fork the repository first before cloning and replace the repo path in the first instruction. 19 | 20 | ```bash 21 | # Clone repository 22 | git clone git@github.com:ridiculously-reusable-components/kanban-board-app.git 23 | 24 | # Change directories to workshop resources 25 | cd kanban-board-app 26 | 27 | # Install dependencies 28 | npm install 29 | 30 | # Run local web server with hot-module reloading 31 | npm run serve 32 | 33 | # Compile a production build 34 | npm run build 35 | 36 | # Lint and fix files 37 | npm run lint 38 | 39 | # Run tests 40 | npm run test 41 | 42 | ### Run unit tests 43 | npm run test:unit 44 | ``` 45 | 46 | ## Author 47 | 48 | - Damian Dulisz: [GitHub](https://www.github.com/shentao) / [Twitter](https://twitter.com/DamianDulisz) 49 | - Ben Hong: [GitHub](https://www.github.com/bencodezen) / [Twitter](https://twitter.com/bencodezen) 50 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 11 | '^.+\\.jsx?$': 'babel-jest' 12 | }, 13 | moduleNameMapper: { 14 | '^@/(.*)$': '/src/$1' 15 | }, 16 | snapshotSerializers: [ 17 | 'jest-serializer-vue' 18 | ], 19 | testMatch: [ 20 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 21 | ], 22 | testURL: 'http://localhost/' 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reusable-components-workshop", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint", 10 | "test:unit": "vue-cli-service test:unit" 11 | }, 12 | "dependencies": { 13 | "@fortawesome/fontawesome-svg-core": "^1.2.6", 14 | "@fortawesome/free-solid-svg-icons": "^5.4.1", 15 | "@fortawesome/vue-fontawesome": "^0.1.1", 16 | "vue": "^2.5.17", 17 | "vue-router": "^3.0.1", 18 | "vuex": "^3.0.1" 19 | }, 20 | "devDependencies": { 21 | "@fullhuman/postcss-purgecss": "^1.0.1", 22 | "@ky-is/vue-cli-plugin-tailwind": "^1.4.0", 23 | "@vue/cli-plugin-babel": "^3.0.4", 24 | "@vue/cli-plugin-eslint": "^3.0.4", 25 | "@vue/cli-plugin-unit-jest": "^3.0.4", 26 | "@vue/cli-service": "^3.0.4", 27 | "@vue/eslint-config-standard": "^4.0.0", 28 | "@vue/test-utils": "^1.0.0-beta.20", 29 | "babel-core": "7.0.0-bridge.0", 30 | "babel-eslint": "^10.0.1", 31 | "babel-jest": "^23.6.0", 32 | "eslint": "^5.8.0", 33 | "eslint-plugin-vue": "^5.0.0-0", 34 | "node-sass": "^4.11.0", 35 | "postcss-preset-env": "^5.3.0", 36 | "sass-loader": "^7.1.0", 37 | "tailwindcss": "^0.6.5", 38 | "vue-cli-plugin-fontawesome": "^0.1.0", 39 | "vue-template-compiler": "^2.5.17" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const isHotReloaded = process.argv.includes('serve') 2 | 3 | class TailwindVueExtractor { 4 | static extract (content) { 5 | const contentWithoutStyleBlocks = content.replace(//gi, '') 6 | return contentWithoutStyleBlocks.match(/[A-Za-z0-9-_:/]+/g) || [] 7 | } 8 | } 9 | 10 | const extensionsUsingCSS = [ 'vue', 'html' ] 11 | const extensionsOfCSS = [ 'css', 'less', 'pcss', 'postcss', 'sass', 'scss', 'styl' ] 12 | 13 | module.exports = { 14 | plugins: [ 15 | require('postcss-preset-env')({ stage: 2 }), 16 | require('tailwindcss')('./tailwind.config.js'), 17 | !isHotReloaded && require('@fullhuman/postcss-purgecss')({ 18 | content: [ `./@(public|src)/**/*.@(${extensionsUsingCSS.join('|')})` ], 19 | css: [ `./src/**/*.@(${extensionsOfCSS.join('|')})` ], 20 | extractors: [ 21 | { 22 | extractor: TailwindVueExtractor, 23 | extensions: extensionsUsingCSS, 24 | }, 25 | ], 26 | }), 27 | require('autoprefixer')(), 28 | ], 29 | } 30 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ridiculously-reusable-components/kanban-board-app/e941b635ad10fc6d2155d99d2eb7b163ba6e4b5b/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Kanban Board Vue.js App 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 18 | 19 | 38 | -------------------------------------------------------------------------------- /src/assets/styles/tailwind.postcss: -------------------------------------------------------------------------------- 1 | /** 2 | * This injects Tailwind's base styles, which is a combination of 3 | * Normalize.css and some additional base styles. 4 | * 5 | * You can see the styles here: 6 | * https://github.com/tailwindcss/tailwindcss/blob/master/css/preflight.css 7 | */ 8 | @import '~tailwindcss/preflight.css'; 9 | 10 | /** 11 | * This injects any component classes registered by plugins. 12 | */ 13 | @import '~tailwindcss/components.css'; 14 | 15 | /** 16 | * Here you would add any of your custom component classes; stuff that you'd 17 | * want loaded *before* the utilities so that the utilities could still 18 | * override them. 19 | * 20 | * Example: 21 | * @import 'components/buttons'; 22 | */ 23 | 24 | /** 25 | * This injects all of Tailwind's utility classes, generated based on your 26 | * config file. 27 | */ 28 | @import '~tailwindcss/utilities.css'; 29 | 30 | /** 31 | * Here you would add any custom utilities you need that don't come out of the 32 | * box with Tailwind. 33 | * 34 | * Example: 35 | * @import 'utilities/background-patterns'; 36 | */ 37 | -------------------------------------------------------------------------------- /src/assets/undraw_completed.svg: -------------------------------------------------------------------------------- 1 | completed -------------------------------------------------------------------------------- /src/assets/undraw_creative_team.svg: -------------------------------------------------------------------------------- 1 | creative team -------------------------------------------------------------------------------- /src/assets/undraw_done.svg: -------------------------------------------------------------------------------- 1 | done -------------------------------------------------------------------------------- /src/assets/undraw_files_sent.svg: -------------------------------------------------------------------------------- 1 | files sent -------------------------------------------------------------------------------- /src/assets/undraw_laravel_and_vue.svg: -------------------------------------------------------------------------------- 1 | laravel and vue -------------------------------------------------------------------------------- /src/assets/undraw_marketing.svg: -------------------------------------------------------------------------------- 1 | marketing -------------------------------------------------------------------------------- /src/assets/undraw_task.svg: -------------------------------------------------------------------------------- 1 | task -------------------------------------------------------------------------------- /src/assets/undraw_team_work.svg: -------------------------------------------------------------------------------- 1 | team work -------------------------------------------------------------------------------- /src/components/AppButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 26 | 27 | 32 | -------------------------------------------------------------------------------- /src/default-boards.js: -------------------------------------------------------------------------------- 1 | import { uuid } from './utils' 2 | 3 | export default [ 4 | { 5 | name: 'workshop', 6 | columns: [ 7 | { 8 | name: 'todo', 9 | tasks: [ 10 | { 11 | description: '', 12 | name: 'first task', 13 | id: uuid() 14 | }, 15 | { 16 | description: '', 17 | name: 'second task', 18 | id: uuid() 19 | }, 20 | { 21 | description: '', 22 | name: 'and thrid', 23 | id: uuid() 24 | } 25 | ] 26 | }, 27 | { 28 | name: 'in-progress', 29 | tasks: [ 30 | { 31 | description: '', 32 | name: 'first task', 33 | id: uuid() 34 | } 35 | ] 36 | }, 37 | { 38 | name: 'done', 39 | tasks: [ 40 | { 41 | description: '', 42 | name: 'first task', 43 | id: uuid() 44 | } 45 | ] 46 | } 47 | ] 48 | }, 49 | { 50 | name: 'private', 51 | columns: [ 52 | { 53 | name: 'todo', 54 | tasks: [ 55 | { 56 | description: '', 57 | name: 'first task', 58 | id: uuid() 59 | } 60 | ] 61 | }, 62 | { 63 | name: 'in-progress', 64 | tasks: [ 65 | { 66 | description: '', 67 | name: 'first task', 68 | id: uuid() 69 | } 70 | ] 71 | }, 72 | { 73 | name: 'done', 74 | tasks: [ 75 | { 76 | description: '', 77 | name: 'first task', 78 | id: uuid() 79 | } 80 | ] 81 | } 82 | ] 83 | } 84 | ] 85 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import './plugins/AppIcon' 3 | import App from './App.vue' 4 | import router from './router' 5 | import store from './store' 6 | import AppButton from './components/AppButton' 7 | 8 | Vue.config.productionTip = false 9 | Vue.component('AppButton', AppButton) 10 | 11 | new Vue({ 12 | router, 13 | store, 14 | render: h => h(App) 15 | }).$mount('#app') 16 | -------------------------------------------------------------------------------- /src/plugins/AppIcon.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import { library } from '@fortawesome/fontawesome-svg-core' 4 | import { fas } from '@fortawesome/free-solid-svg-icons' 5 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' 6 | 7 | library.add(fas) 8 | 9 | Vue.component('AppIcon', FontAwesomeIcon) 10 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Task from './views/Task.vue' 4 | import Home from './views/Home.vue' 5 | import Board from './views/Board.vue' 6 | import Workspace from './views/Workspace.vue' 7 | import Pricing from './views/Pricing.vue' 8 | 9 | Vue.use(Router) 10 | 11 | export default new Router({ 12 | routes: [ 13 | { 14 | path: '/', 15 | name: 'home', 16 | component: Home 17 | }, 18 | { 19 | path: '/board/:name', 20 | name: 'board', 21 | component: Board, 22 | children: [ 23 | { 24 | path: 'task/:id', 25 | name: 'task', 26 | component: Task 27 | } 28 | ] 29 | }, 30 | { 31 | path: '/workspace', 32 | name: 'workspace', 33 | component: Workspace 34 | }, 35 | { 36 | path: '/pricing', 37 | name: 'pricing', 38 | component: Pricing 39 | } 40 | ] 41 | }) 42 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { uuid, saveStatePlugin } from './utils' 4 | import defaultBoards from './default-boards' 5 | 6 | Vue.use(Vuex) 7 | 8 | const boards = JSON.parse(localStorage.getItem('boards')) || defaultBoards 9 | 10 | export default new Vuex.Store({ 11 | plugins: [saveStatePlugin], 12 | state: { 13 | boards 14 | }, 15 | mutations: { 16 | MOVE_TASK (state, { sourceList, targetList, from, to }) { 17 | const taskToMove = sourceList.splice(from, 1)[0] 18 | targetList.splice(to, 0, taskToMove) 19 | }, 20 | MOVE_COLUMN (state, { from, to, columnList }) { 21 | const columnToMove = columnList.splice(from, 1)[0] 22 | columnList.splice(to, 0, columnToMove) 23 | }, 24 | CREATE_TASK (state, { tasks, name }) { 25 | tasks.push({ 26 | name, 27 | id: uuid(), 28 | description: '' 29 | }) 30 | }, 31 | UPDATE_TASK (state, { task, key, value }) { 32 | Vue.set(task, key, value) 33 | }, 34 | UPDATE_COLUMN_NAME (state, { column, name }) { 35 | column.name = name 36 | }, 37 | CREATE_NEW_COLUMN (state, { board, name }) { 38 | board.columns.push({ 39 | name, 40 | tasks: [] 41 | }) 42 | }, 43 | REMOVE_COLUMN (state, { columnIndex, board }) { 44 | board.columns.splice(columnIndex, 1) 45 | }, 46 | REMOVE_TASK (state, { index, tasksList }) { 47 | tasksList.splice(index, 1) 48 | } 49 | }, 50 | actions: { 51 | 52 | } 53 | }) 54 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export function uuid () { 2 | return Math.random().toString(16).slice(2) 3 | } 4 | 5 | export function saveStatePlugin (store) { 6 | store.subscribe( 7 | (mutation, state) => localStorage.setItem('boards', JSON.stringify(state.boards)) 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/views/Board.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 170 | 171 | 190 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 122 | 123 | 290 | -------------------------------------------------------------------------------- /src/views/Pricing.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 144 | 145 | 330 | -------------------------------------------------------------------------------- /src/views/Task.vue: -------------------------------------------------------------------------------- 1 |