├── .gitignore ├── README.md ├── calendar v2 ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── controller.js │ │ ├── date-utils.js │ │ └── model.js │ ├── assets │ │ └── .gitkeep │ ├── index.js │ └── style.scss ├── webpack.dev.js └── webpack.prod.js ├── calendar ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── dates.js │ │ ├── layout.js │ │ ├── overlay.js │ │ └── store.js │ ├── assets │ │ └── .gitkeep │ ├── index.js │ └── style.scss ├── tailwind.config.js ├── webpack.dev.js └── webpack.prod.js ├── carousel ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── app │ │ ├── .gitkeep │ │ └── carousel.js │ ├── assets │ │ ├── .gitkeep │ │ ├── img-1.jpg │ │ ├── img-2.jpg │ │ ├── img-3.jpg │ │ └── img-4.jpg │ ├── index.js │ └── style.scss ├── tailwind.config.js ├── webpack.dev.js └── webpack.prod.js ├── comment-widget ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── comment.js │ │ ├── store.js │ │ └── views.js │ ├── assets │ │ └── .gitkeep │ ├── index.js │ └── style.scss ├── tailwind.config.js ├── webpack.dev.js └── webpack.prod.js ├── data-binding ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── app │ │ ├── .gitkeep │ │ └── views.js │ ├── assets │ │ └── .gitkeep │ ├── index.js │ └── style.scss ├── tailwind.config.js ├── webpack.dev.js └── webpack.prod.js ├── email ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── http-client.js │ │ ├── renderer.js │ │ └── view.js │ ├── assets │ │ └── .gitkeep │ ├── index.js │ └── style.scss ├── webpack.dev.js └── webpack.prod.js ├── giphy ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── http-client.js │ │ ├── store.js │ │ ├── view.js │ │ └── viewport-observer.js │ ├── assets │ │ └── .gitkeep │ ├── index.js │ └── style.scss ├── tailwind.config.js ├── webpack.dev.js └── webpack.prod.js ├── scaffold ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── app │ │ └── .gitkeep │ ├── assets │ │ └── .gitkeep │ ├── index.js │ └── style.scss ├── webpack.dev.js └── webpack.prod.js ├── task-tracker ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── drag.js │ │ ├── layout.js │ │ ├── sidebar.js │ │ └── store.js │ ├── assets │ │ └── .gitkeep │ ├── index.js │ └── style.scss ├── tailwind.config.js ├── webpack.dev.js └── webpack.prod.js ├── tictactoe ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── controller.js │ │ ├── store.js │ │ └── views.js │ ├── assets │ │ └── .gitkeep │ ├── index.js │ └── style.scss ├── tailwind.config.js ├── webpack.dev.js └── webpack.prod.js ├── todo v2 ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── store.js │ │ └── views.js │ ├── assets │ │ └── .gitkeep │ ├── index.js │ └── style.scss ├── webpack.dev.js └── webpack.prod.js ├── todo ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── http-client.js │ │ ├── layout.js │ │ ├── store.js │ │ └── todo-input.js │ ├── assets │ │ ├── delete.svg │ │ └── done.svg │ ├── index.js │ └── style.scss ├── tailwind.config.js ├── webpack.dev.js └── webpack.prod.js └── vanlla-spa ├── index.html ├── package-lock.json ├── package.json ├── src ├── app │ ├── .gitkeep │ ├── router.js │ └── views │ │ ├── About.js │ │ ├── BaseView.js │ │ ├── Home.js │ │ └── Settings.js ├── assets │ └── .gitkeep ├── index.js └── style.scss ├── webpack.dev.js └── webpack.prod.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ui-bootcamp -------------------------------------------------------------------------------- /calendar v2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Calendar v2 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 |
16 |
17 |
18 |

Planned Events

19 | 20 |
21 | 22 |
23 | 24 | 25 |
26 | 27 | 33 | 34 | 39 | 42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /calendar v2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "sass-loader": "^11.0.1", 21 | "style-loader": "^2.0.0", 22 | "webpack": "^5.36.2", 23 | "webpack-cli": "^4.7.0", 24 | "webpack-dev-server": "^3.11.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /calendar v2/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/calendar v2/src/app/.gitkeep -------------------------------------------------------------------------------- /calendar v2/src/app/controller.js: -------------------------------------------------------------------------------- 1 | import { DAYS, getCurrentDate, getDay, getDaysInMonth, MONTHS } from "./date-utils"; 2 | 3 | export default class Controller { 4 | 5 | constructor() { 6 | this.baseCell = document.getElementById('cell'); 7 | this.baseLabel = document.getElementById('label'); 8 | this.wrapper = document.getElementById('calendar-grid'); 9 | this.selectWrapper = document.getElementById('date-select'); 10 | this.eventWrapper = document.getElementById('event-wrapper'); 11 | this.eventTemplate = document.getElementById('event'); 12 | 13 | this.initialize(); 14 | 15 | this.activeCell = null; 16 | } 17 | 18 | initialize() { 19 | this.createLabels(); 20 | 21 | const [date, month, year] = getCurrentDate(); 22 | 23 | this.createCells(month, year); 24 | 25 | this.initSelect(month, year); 26 | 27 | } 28 | 29 | createLabels() { 30 | const fragment = document.createDocumentFragment(); 31 | DAYS.forEach(day => { 32 | const label = this.baseLabel.content.cloneNode(true); 33 | label.querySelector('.label').textContent = day; 34 | fragment.appendChild(label); 35 | }) 36 | 37 | this.wrapper.appendChild(fragment); 38 | 39 | } 40 | 41 | createCells(month, year) { 42 | const fragment = document.createDocumentFragment(); 43 | 44 | const days = getDaysInMonth(year, month + 1); 45 | 46 | const offset = getDay(1, month, year); 47 | 48 | let i = offset % 7; 49 | 50 | while(i--) { 51 | const cell = this.baseCell.content.cloneNode(true); 52 | cell.querySelector('.cell').classList.add('cell-offset') 53 | fragment.appendChild(cell); 54 | } 55 | 56 | i = days; 57 | 58 | const [currentDate, currentMonth, currentYear] = getCurrentDate(); 59 | 60 | while(i--) { 61 | const cell = this.baseCell.content.cloneNode(true); 62 | cell.querySelector('.cell').classList.add('cell-main') 63 | cell.querySelector('.cell').id = `${days - i}-${month + 1}-${year}`; 64 | cell.querySelector('.date').textContent = days - i; 65 | 66 | if(currentMonth === month && currentYear === year) { 67 | (currentDate === days - i) && cell.querySelector('.date').classList.add('today') 68 | } 69 | fragment.appendChild(cell); 70 | } 71 | 72 | this.wrapper.appendChild(fragment); 73 | 74 | } 75 | 76 | destroyLayout() { 77 | while(this.wrapper.childElementCount) { 78 | this.wrapper.removeChild(this.wrapper.firstElementChild); 79 | } 80 | } 81 | 82 | initSelect(month, year) { 83 | const monthDropdown = this.selectWrapper.querySelector('select[name=month]'); 84 | 85 | const monthOptions = new DocumentFragment(); 86 | 87 | MONTHS.forEach((month, i) => { 88 | const option = document.createElement('option'); 89 | option.value = i + 1; 90 | option.textContent = month; 91 | monthOptions.appendChild(option); 92 | }) 93 | 94 | monthDropdown.appendChild(monthOptions); 95 | 96 | monthDropdown.value = month + 1; 97 | 98 | const yearDropdown = this.selectWrapper.querySelector('select[name=year]'); 99 | 100 | const yearOptions = new DocumentFragment(); 101 | 102 | for(let i = 2000; i <= 2050; i++) { 103 | const option = document.createElement('option'); 104 | option.value = i; 105 | option.textContent = i; 106 | yearOptions.appendChild(option); 107 | } 108 | 109 | yearDropdown.appendChild(yearOptions); 110 | 111 | yearDropdown.value = year; 112 | 113 | this.onSelectChange() 114 | } 115 | 116 | onDateClick(callback) { 117 | this.wrapper.addEventListener('click', e => { 118 | if(e.target.classList.contains('cell-main')) { 119 | 120 | this.activeCell = e.target.id; 121 | 122 | const cells = e.target.parentElement.children; 123 | 124 | Array.from(cells).forEach(el => { 125 | if(el.classList.contains('cell-active')) { 126 | el.classList.remove('cell-active'); 127 | } 128 | }) 129 | 130 | document.getElementById(this.activeCell).classList.add('cell-active'); 131 | 132 | callback(this.activeCell); 133 | } 134 | }) 135 | } 136 | 137 | onSelectChange() { 138 | const yearDropdown = this.selectWrapper.querySelector('select[name=year]'); 139 | const monthDropdown = this.selectWrapper.querySelector('select[name=month]'); 140 | 141 | let currentYear = yearDropdown.value; 142 | let currentMonth = monthDropdown.value; 143 | 144 | monthDropdown.addEventListener('change', e => { 145 | currentMonth = e.target.value; 146 | this.destroyLayout(); 147 | this.createLabels(); 148 | this.createCells(currentMonth - 1, parseInt(currentYear)); 149 | }); 150 | 151 | yearDropdown.addEventListener('change', e => { 152 | currentYear = e.target.value; 153 | this.destroyLayout(); 154 | this.createLabels(); 155 | this.createCells(currentMonth - 1, parseInt(currentYear)); 156 | }); 157 | } 158 | 159 | renderEvents(events) { 160 | const fragment = new DocumentFragment(); 161 | 162 | events.forEach(ev => { 163 | const eventCell = this.eventTemplate.content.cloneNode(true); 164 | eventCell.querySelector('.title').textContent = ev.name; 165 | eventCell.querySelector('.date').textContent = ev.id; 166 | 167 | fragment.appendChild(eventCell); 168 | }) 169 | 170 | this.eventWrapper.appendChild(fragment); 171 | } 172 | 173 | destroyEvents() { 174 | if(this.eventWrapper.childElementCount) { 175 | while(this.eventWrapper.childElementCount) { 176 | this.eventWrapper.removeChild(this.eventWrapper.firstElementChild); 177 | } 178 | } 179 | } 180 | 181 | } -------------------------------------------------------------------------------- /calendar v2/src/app/date-utils.js: -------------------------------------------------------------------------------- 1 | 2 | export const getDaysInMonth = (year, month) => { 3 | return new Date(year, month, 0).getDate(); 4 | } 5 | 6 | export const getCurrentDate = () => { 7 | const date = new Date(); 8 | return [date.getDate(), date.getMonth(), date.getFullYear()]; 9 | } 10 | 11 | export const getDay = (d, m, y) => { 12 | return new Date(y, m, d).getDay(); 13 | } 14 | 15 | export const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 16 | export const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] -------------------------------------------------------------------------------- /calendar v2/src/app/model.js: -------------------------------------------------------------------------------- 1 | export default class Model { 2 | constructor() { 3 | this._events = [] 4 | 5 | this.getLocalStore(); 6 | } 7 | 8 | 9 | get events() { 10 | return this._events; 11 | } 12 | 13 | 14 | setLocalStore() { 15 | if (this._events.length) { 16 | localStorage.setItem('events', JSON.stringify(this._events)) 17 | } 18 | } 19 | 20 | getEventsById(id) { 21 | return this._events.find(ev => ev.id === id)?.events; 22 | } 23 | 24 | getLocalStore() { 25 | const events = localStorage.getItem('events'); 26 | 27 | try { 28 | if (events) { 29 | this._events = JSON.parse(events); 30 | } 31 | } catch { 32 | console.error(e); 33 | } 34 | } 35 | 36 | setEvent({ id, name, desc }) { 37 | const eventsOnId = this.getEventsById(id); 38 | 39 | if (eventsOnId) { 40 | eventsOnId.events.push({ name, desc }) 41 | } else { 42 | this._events.push({ id, events: [{ name, desc }] }); 43 | } 44 | 45 | this.setLocalStore() 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /calendar v2/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/calendar v2/src/assets/.gitkeep -------------------------------------------------------------------------------- /calendar v2/src/index.js: -------------------------------------------------------------------------------- 1 | import Controller from './app/controller'; 2 | import Model from './app/model'; 3 | import './style.scss' 4 | 5 | export const render = () => { 6 | 7 | const controller = new Controller(); 8 | 9 | const model = new Model(); 10 | 11 | 12 | 13 | controller.onDateClick(id => { 14 | 15 | // model.setEvent({ id, name: 'Lorem ipsum', desc: 'test test test test'}); 16 | 17 | const events = model.getEventsById(id); 18 | 19 | console.log(events) 20 | 21 | controller.destroyEvents(); 22 | 23 | if(events) { 24 | controller.renderEvents(events.map(ev => ({...ev, id}))); 25 | } 26 | console.log(id) 27 | }) 28 | } 29 | 30 | render(); -------------------------------------------------------------------------------- /calendar v2/src/style.scss: -------------------------------------------------------------------------------- 1 | /* Basic reset */ 2 | 3 | $gray: #F9FAFB; 4 | $white: #FFF; 5 | $blue: #3B82F6; 6 | $red: #EF4444; 7 | 8 | html { 9 | font-size: 16px; 10 | } 11 | 12 | html, 13 | body { 14 | margin: 0; 15 | padding: 0; 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 21 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 22 | font-size: 1rem; 23 | font-weight: 400; 24 | min-height: 100vh; 25 | width: 100%; 26 | background-color: $gray; 27 | padding: 2rem; 28 | } 29 | 30 | *, 31 | *::after, 32 | *::before { 33 | box-sizing: inherit; 34 | margin: 0; 35 | padding: 0; 36 | } 37 | 38 | .flex { 39 | display: flex; 40 | } 41 | 42 | .calendar { 43 | display: grid; 44 | margin-top: 3rem; 45 | // grid-template-rows: repeat(7, minmax(0, 1fr)); 46 | grid-template-columns: repeat(7, minmax(0, 1fr)); 47 | flex: 0 0 80%; 48 | 49 | .cell { 50 | font-weight: 600; 51 | width: 100%; 52 | padding: 0.5rem; 53 | border: 1px solid darken($gray, 5); 54 | text-align: right; 55 | padding-bottom: 2rem; 56 | transition: 0.3s; 57 | .date { 58 | font-size: 2rem; 59 | pointer-events: none; 60 | display: inline-block; 61 | padding: 0.2rem; 62 | 63 | &.today { 64 | background-color: $red; 65 | border-radius: 50%; 66 | color: white; 67 | padding: 0.2rem 0.8rem; 68 | 69 | } 70 | } 71 | 72 | &-main { 73 | cursor: pointer; 74 | 75 | &:hover { 76 | background-color: darken($gray, 5); 77 | } 78 | } 79 | 80 | &-offset { 81 | .date { 82 | display: none; 83 | } 84 | } 85 | 86 | &-active { 87 | border: 1px solid rgba($blue, 0.4); 88 | box-shadow: inset 0 0 1rem rgba($blue, 0.2); 89 | } 90 | } 91 | 92 | .label { 93 | font-weight: 600; 94 | text-transform: uppercase; 95 | color: $blue; 96 | text-align: right; 97 | padding: 0.5rem; 98 | border: 1px solid darken($gray, 10); 99 | } 100 | 101 | .col { 102 | grid-column: span 1 / span 1; 103 | } 104 | } 105 | 106 | .events { 107 | flex: 0 0 20%; 108 | padding: 1rem; 109 | flex-direction: column; 110 | gap: 1rem; 111 | 112 | .title { 113 | font-weight: 600; 114 | } 115 | 116 | .wrapper { 117 | flex-direction: column; 118 | gap: 1rem; 119 | } 120 | 121 | .event-card { 122 | padding: 0.5rem; 123 | border: 1px solid darken($gray, 5); 124 | border-radius: 0.4rem; 125 | display: flex; 126 | flex-direction: column; 127 | gap: 0.4rem; 128 | cursor: pointer; 129 | 130 | .date { 131 | color: darken($gray, 50); 132 | font-size: 0.85rem; 133 | } 134 | } 135 | 136 | .add-event { 137 | padding: 0.5rem; 138 | width: 100%; 139 | background-color: rgba($color: $blue, $alpha: 0.1); 140 | color: $blue; 141 | font-size: 1rem; 142 | font-weight: 600; 143 | outline: none; 144 | border: none; 145 | border-radius: 0.3rem; 146 | transition: 0.2s; 147 | cursor: pointer; 148 | 149 | &:hover { 150 | background-color: rgba($color: $blue, $alpha: 0.2); 151 | } 152 | } 153 | } 154 | 155 | .date-select { 156 | gap: 1rem; 157 | select { 158 | padding: 0.5rem 1rem; 159 | font-size: 1.5rem; 160 | font-weight: 600; 161 | outline: none; 162 | border: none; 163 | border-radius: 0.3rem; 164 | background-color: transparent; 165 | transition: 0.2s; 166 | &:hover { 167 | background-color: darken($gray, 5); 168 | } 169 | } 170 | } -------------------------------------------------------------------------------- /calendar v2/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'Calendar - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /calendar v2/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'Calendar - Production', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /calendar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Calendar 9 | 10 | 11 | 12 |
13 | 16 |
17 | 18 |
19 | 20 |
21 | 22 | 28 | 29 | 39 | 40 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /calendar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "postcss-cli": "^8.3.1", 21 | "postcss-loader": "^5.2.0", 22 | "sass-loader": "^11.0.1", 23 | "style-loader": "^2.0.0", 24 | "tailwindcss": "^2.1.2", 25 | "webpack": "^5.36.2", 26 | "webpack-cli": "^4.7.0", 27 | "webpack-dev-server": "^3.11.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /calendar/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | module.exports = { 3 | plugins: [ 4 | tailwindcss('./tailwind.config.js'), 5 | require('autoprefixer'), 6 | ], 7 | }; -------------------------------------------------------------------------------- /calendar/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/calendar/src/app/.gitkeep -------------------------------------------------------------------------------- /calendar/src/app/dates.js: -------------------------------------------------------------------------------- 1 | export default class DateService { 2 | 3 | constructor() {} 4 | 5 | getCurrentDate() { 6 | return new Date(); 7 | } 8 | 9 | getDaysinMonth(year, month) { 10 | return new Date(year, month, 0).getDate(); 11 | } 12 | 13 | 14 | } 15 | 16 | export const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 17 | 'July', 'August', 'September', 'October', 'November', 'December' 18 | ]; 19 | 20 | export const days = ['Sunday','Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; -------------------------------------------------------------------------------- /calendar/src/app/layout.js: -------------------------------------------------------------------------------- 1 | import { days } from './dates'; 2 | import EventOverlay from './overlay'; 3 | export default class Layout { 4 | 5 | colors = ['#F87171', '#FBBF24', '#34D399', '#60A5FA', '#818CF8', '#A78BFA', '#F472B6']; 6 | 7 | constructor() { 8 | this.baseNode = document.querySelector('#date-tile'); 9 | this.dayNode = document.querySelector('#day-tile'); 10 | this.monthWrapper = document.querySelector('#month-view'); 11 | this.monthSelect = document.querySelector('#month-picker'); 12 | this.overlayService = new EventOverlay(); 13 | } 14 | 15 | initMonthLayout(days, month, year, saveEventCb, events) { 16 | for (let i = 1; i <= days; i++) { 17 | const node = this.baseNode.content.cloneNode(true); 18 | const date = new Date(year, month, i, 5, 30); 19 | 20 | if (i === 1) { 21 | this.setDayHeader(date.getDay()) 22 | } 23 | 24 | const hasEvent = events.find(ev => ev.timestamp.slice(0, 10) === date.toISOString().slice(0, 10)); 25 | const eventLabel = node.querySelector('.date-event'); 26 | 27 | if (hasEvent) { 28 | eventLabel.classList.remove('hidden'); 29 | } 30 | 31 | node.querySelector('.date-tile').addEventListener('click', async () => { 32 | const result = await this.overlayService.initOverlay(date, saveEventCb, hasEvent) 33 | result && eventLabel.classList.remove('hidden'); 34 | }) 35 | 36 | node.querySelector('.date-label span').textContent = i; 37 | 38 | eventLabel.style.backgroundColor = 39 | this.colors[parseInt(Math.random() * this.colors.length, 10)]; 40 | 41 | 42 | if (new Date().toISOString().slice(0, 10) === date.toISOString().slice(0, 10)) { 43 | node.querySelector('.date-label span').classList.add('bg-blue-500', 'text-white'); 44 | } 45 | 46 | this.monthWrapper.appendChild(node); 47 | } 48 | } 49 | 50 | disposeMonthLayout() { 51 | this.monthWrapper.innerHTML = ''; 52 | } 53 | 54 | initMonthDropdown(months, year, listenerCb) { 55 | let fragment = new DocumentFragment(); 56 | 57 | months.forEach((month, i) => { 58 | const node = document.createElement('option'); 59 | node.value = i + 1; 60 | node.textContent = `${month} ${year}`; 61 | fragment.appendChild(node); 62 | }); 63 | 64 | this.monthSelect.appendChild(fragment); 65 | 66 | this.setMonthListener(listenerCb); 67 | } 68 | 69 | 70 | setMonth(month) { 71 | this.monthSelect.value = month; 72 | } 73 | 74 | 75 | setMonthListener(callback) { 76 | this.monthSelect.addEventListener('change', e => callback(e)) 77 | } 78 | 79 | setDayHeader(startDay) { 80 | for (let i = 0; i < days.length; i++) { 81 | const node = this.dayNode.content.cloneNode(true); 82 | node.querySelector('.day-label').textContent = days[(startDay + i) % 7] 83 | this.monthWrapper.appendChild(node); 84 | } 85 | } 86 | 87 | 88 | } -------------------------------------------------------------------------------- /calendar/src/app/overlay.js: -------------------------------------------------------------------------------- 1 | import { monthNames } from './dates'; 2 | 3 | export default class EventOverlay { 4 | 5 | current; 6 | 7 | constructor() { 8 | this.base = document.querySelector('#event-dialog'); 9 | } 10 | 11 | async initOverlay(date, saveEventCb, event) { 12 | 13 | return new Promise((res) => { 14 | const node = this.base.content.cloneNode(true); 15 | node.querySelector('.event-details').textContent = `${date.getDate()} ${monthNames[date.getMonth()]}, ${date.getFullYear()}`; 16 | 17 | node.querySelector('.close').addEventListener('click', e => { 18 | this.disposeOverlay(); 19 | res(false); 20 | }); 21 | 22 | 23 | if (event) { 24 | const title = node.querySelector('.event-title'); 25 | const desc = node.querySelector('.event-desc'); 26 | 27 | title.disabled = true; 28 | desc.disabled = true; 29 | 30 | title.value = event.title; 31 | desc.value = event.description; 32 | 33 | node.querySelector('.save-event').classList.add('hidden'); 34 | res(false); 35 | 36 | } else { 37 | node.querySelector('.save-event').addEventListener('click', e => { 38 | const title = e.target.parentNode.querySelector('.event-title'); 39 | const desc = e.target.parentNode.querySelector('.event-desc'); 40 | 41 | if (!desc.value.trim() || !title.value.trim()) { 42 | alert('Please enter a valid value'); 43 | } else { 44 | saveEventCb(date.toISOString(), title.value.trim(), desc.value.trim()); 45 | this.disposeOverlay(); 46 | res(true); 47 | } 48 | }); 49 | } 50 | 51 | this.current = node.querySelector('.event-dialog'); 52 | document.body.appendChild(node); 53 | 54 | }); 55 | } 56 | 57 | disposeOverlay() { 58 | document.body.removeChild(this.current); 59 | this.current = null; 60 | } 61 | 62 | 63 | } -------------------------------------------------------------------------------- /calendar/src/app/store.js: -------------------------------------------------------------------------------- 1 | export default class Store { 2 | 3 | constructor() { 4 | this.events = []; 5 | this.getEventStore(); 6 | } 7 | 8 | getEventStore() { 9 | if (window.localStorage.events) { 10 | this.events = JSON.parse(window.localStorage.events); 11 | } 12 | } 13 | 14 | setStore() { 15 | window.localStorage.events = JSON.stringify(this.events); 16 | } 17 | 18 | saveEvent(timestamp, title, description) { 19 | this.events.push({ timestamp, title, description }); 20 | this.setStore(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /calendar/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/calendar/src/assets/.gitkeep -------------------------------------------------------------------------------- /calendar/src/index.js: -------------------------------------------------------------------------------- 1 | import DateService, { monthNames } from './app/dates'; 2 | import Layout from './app/layout'; 3 | import Store from './app/store'; 4 | import './style.scss' 5 | 6 | export const loadApp = () => { 7 | 8 | const layoutService = new Layout(); 9 | const dateService = new DateService(); 10 | const store = new Store(); 11 | 12 | let date = dateService.getCurrentDate(); 13 | 14 | const saveEvent = (date, title, desc) => { 15 | store.saveEvent(date, title, desc); 16 | } 17 | 18 | layoutService.initMonthLayout(dateService.getDaysinMonth(date.getFullYear(), date.getMonth() + 1), date.getMonth(), date.getFullYear(), saveEvent, store.events); 19 | 20 | const changeMonth = (e) => { 21 | layoutService.disposeMonthLayout(); 22 | layoutService.initMonthLayout(dateService.getDaysinMonth(date.getFullYear(), parseInt(e.target.value, 10)), parseInt(e.target.value, 10) - 1, date.getFullYear(), saveEvent, store.events); 23 | }; 24 | 25 | layoutService.initMonthDropdown(monthNames, date.getFullYear(), changeMonth); 26 | 27 | layoutService.setMonth(date.getMonth() + 1); 28 | 29 | } 30 | 31 | loadApp(); -------------------------------------------------------------------------------- /calendar/src/style.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | /* Basic reset */ 7 | 8 | html { 9 | font-size: 16px; 10 | } 11 | 12 | html, 13 | body { 14 | margin: 0; 15 | padding: 0; 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 21 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 22 | font-size: 1rem; 23 | font-weight: 400; 24 | } 25 | 26 | *, 27 | *::after, 28 | *::before { 29 | box-sizing: inherit; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | -------------------------------------------------------------------------------- /calendar/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | -------------------------------------------------------------------------------- /calendar/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'Calendar - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /calendar/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', 'postcss-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'Calendar - Production', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /carousel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Carousel Demo

12 | 13 | 27 | 28 | -------------------------------------------------------------------------------- /carousel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "postcss-cli": "^8.3.1", 21 | "postcss-loader": "^5.2.0", 22 | "sass-loader": "^11.0.1", 23 | "style-loader": "^2.0.0", 24 | "tailwindcss": "^2.1.2", 25 | "webpack": "^5.36.2", 26 | "webpack-cli": "^4.7.0", 27 | "webpack-dev-server": "^3.11.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /carousel/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | module.exports = { 3 | plugins: [ 4 | tailwindcss('./tailwind.config.js'), 5 | require('autoprefixer'), 6 | ], 7 | }; -------------------------------------------------------------------------------- /carousel/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/carousel/src/app/.gitkeep -------------------------------------------------------------------------------- /carousel/src/app/carousel.js: -------------------------------------------------------------------------------- 1 | export default class Carousel { 2 | 3 | defaultOptions = { 4 | autoplay: true, 5 | duration: 2000, 6 | dots: false, 7 | pauseOnHover: false 8 | }; 9 | 10 | constructor(options = this.defaultOptions) { 11 | this.baseWrapper = document.getElementById('carousel'); 12 | 13 | this.validateCarousel(options); 14 | 15 | this.initCarousel(options); 16 | } 17 | 18 | validateCarousel(options) { 19 | if (!this.baseWrapper) { 20 | throw new Error('Cannot find a valid element'); 21 | } 22 | 23 | if (typeof options !== 'object') { 24 | throw new Error('Invalid options provided'); 25 | } 26 | 27 | const { autoplay, duration, dots } = options; 28 | 29 | // if(typeof autoplay) 30 | } 31 | 32 | initCarousel({ autoplay, duration, dots, pauseOnHover }) { 33 | let slides = this.baseWrapper.querySelectorAll('.slide'); 34 | 35 | slides = Array.from(slides); 36 | 37 | if (!slides.length) { 38 | throw new Error('No slides to show'); 39 | } 40 | 41 | const innerFragment = new DocumentFragment(); 42 | 43 | const innerDiv = document.createElement('div'); 44 | 45 | const width = this.baseWrapper.clientWidth; 46 | innerDiv.style.width = `${slides.length * width}px`; 47 | innerDiv.style.transition = `0.2s`; 48 | innerDiv.style.display = `flex`; 49 | 50 | innerFragment.appendChild(innerDiv); 51 | 52 | slides.forEach(slide => { 53 | slide.querySelector('img').style.userSelect = 'none'; 54 | innerDiv.appendChild(slide) 55 | }); 56 | 57 | this.baseWrapper.appendChild(innerFragment); 58 | 59 | let slide = { current: 1 }; 60 | let timer; 61 | 62 | if (autoplay) { 63 | timer = this.autoplayCarousel(duration, innerDiv, slide, width, slides); 64 | } 65 | 66 | if (pauseOnHover) { 67 | this.initPauseOnHover((type) => { 68 | if (type === 'mouseenter') { 69 | clearTimeout(timer); 70 | } else { 71 | timer = this.autoplayCarousel(duration, innerDiv, slide, width, slides); 72 | } 73 | }) 74 | } 75 | 76 | } 77 | 78 | autoplayCarousel(duration, innerDiv, slide, width, slides) { 79 | return setInterval(() => { 80 | innerDiv.style.transform = `translateX(-${slide.current * width}px)`; 81 | slide.current = (slide.current + 1) % slides.length; 82 | }, duration); 83 | } 84 | 85 | initPauseOnHover(callback) { 86 | this.baseWrapper.addEventListener('mouseenter', () => { 87 | callback('mouseenter'); 88 | }); 89 | 90 | this.baseWrapper.addEventListener('mouseleave', () => { 91 | callback('mouseleave'); 92 | }); 93 | } 94 | 95 | 96 | } -------------------------------------------------------------------------------- /carousel/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/carousel/src/assets/.gitkeep -------------------------------------------------------------------------------- /carousel/src/assets/img-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/carousel/src/assets/img-1.jpg -------------------------------------------------------------------------------- /carousel/src/assets/img-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/carousel/src/assets/img-2.jpg -------------------------------------------------------------------------------- /carousel/src/assets/img-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/carousel/src/assets/img-3.jpg -------------------------------------------------------------------------------- /carousel/src/assets/img-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/carousel/src/assets/img-4.jpg -------------------------------------------------------------------------------- /carousel/src/index.js: -------------------------------------------------------------------------------- 1 | import Carousel from './app/carousel'; 2 | import './style.scss' 3 | 4 | export const loadApp = () => { 5 | 6 | new Carousel({ 7 | autoplay: true, 8 | duration: 1000, 9 | pauseOnHover: true 10 | }); 11 | } 12 | 13 | loadApp(); -------------------------------------------------------------------------------- /carousel/src/style.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | /* Basic reset */ 7 | 8 | html { 9 | font-size: 16px; 10 | } 11 | 12 | html, 13 | body { 14 | margin: 0; 15 | padding: 0; 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 21 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 22 | font-size: 1rem; 23 | font-weight: 400; 24 | } 25 | 26 | *, 27 | *::after, 28 | *::before { 29 | box-sizing: inherit; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | -------------------------------------------------------------------------------- /carousel/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | './src/**/*.html', 4 | './src/**/*.js' 5 | ], 6 | darkMode: false, // or 'media' or 'class' 7 | theme: { 8 | extend: {}, 9 | }, 10 | variants: { 11 | extend: {}, 12 | }, 13 | plugins: [], 14 | } 15 | -------------------------------------------------------------------------------- /carousel/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'Carousel - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /carousel/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', 'postcss-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'Carousel Demo', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /comment-widget/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 32 | 33 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /comment-widget/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "postcss-cli": "^8.3.1", 21 | "postcss-loader": "^5.2.0", 22 | "sass-loader": "^11.0.1", 23 | "style-loader": "^2.0.0", 24 | "tailwindcss": "^2.1.2", 25 | "webpack": "^5.36.2", 26 | "webpack-cli": "^4.7.0", 27 | "webpack-dev-server": "^3.11.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /comment-widget/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | module.exports = { 3 | plugins: [ 4 | tailwindcss('./tailwind.config.js'), 5 | require('autoprefixer'), 6 | ], 7 | }; -------------------------------------------------------------------------------- /comment-widget/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/comment-widget/src/app/.gitkeep -------------------------------------------------------------------------------- /comment-widget/src/app/comment.js: -------------------------------------------------------------------------------- 1 | export default class Comment { 2 | 3 | constructor(title, level, parent) { 4 | this.title = title; 5 | this.level = level; 6 | this.replies = []; 7 | this.id = parseInt(Math.random() * 100000, 10); 8 | this.parent = parent; 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /comment-widget/src/app/store.js: -------------------------------------------------------------------------------- 1 | import Comment from "./comment"; 2 | 3 | export default class Store { 4 | constructor() { 5 | this._store = []; 6 | this.getLocalStore(); 7 | } 8 | 9 | get comments() { 10 | return this._store; 11 | } 12 | 13 | saveComment(comment, id = 0) { 14 | let newNode; 15 | 16 | if (id) { 17 | const parent = this.findComment(id); 18 | newNode = new Comment(comment, parent.level + 1, id); 19 | parent.replies.push(newNode); 20 | } else { 21 | newNode = new Comment(comment, 0, ''); 22 | this._store.push(newNode); 23 | } 24 | 25 | this.setLocalStore(); 26 | return newNode; 27 | } 28 | 29 | deleteComment(id) { 30 | const parent = this.findComment(id).parent; 31 | 32 | if (parent) { 33 | const comments = this.findComment(parent); 34 | const idx = comments.replies.findIndex(el => el.id === id); 35 | comments.replies.splice(idx, 1); 36 | } else { 37 | const idx = this.comments.findIndex(el => el.id === id); 38 | this.comments.splice(idx, 1); 39 | } 40 | 41 | this.setLocalStore(); 42 | } 43 | 44 | findComment(id, comments = this.comments) { 45 | 46 | for (let comment of comments) { 47 | if (comment.replies.length) { 48 | const result = this.findComment(id, comment.replies); 49 | if (result !== -1) { 50 | return result; 51 | } 52 | } 53 | if (comment.id === id) { 54 | return comment; 55 | } 56 | } 57 | return -1; 58 | } 59 | 60 | getLocalStore() { 61 | if (window.localStorage.comments) { 62 | this._store = JSON.parse(window.localStorage.comments); 63 | } 64 | } 65 | 66 | setLocalStore() { 67 | window.localStorage.comments = JSON.stringify(this._store); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /comment-widget/src/app/views.js: -------------------------------------------------------------------------------- 1 | export default class Views { 2 | 3 | colors = ['#F87171', '#FBBF24', '#34D399', '#60A5FA', '#818CF8', '#A78BFA', '#F472B6']; 4 | 5 | constructor() { 6 | this.commentNode = document.getElementById('comment'); 7 | this.baseWrapper = document.getElementById('comment-wrapper'); 8 | this.input = document.getElementById('input'); 9 | 10 | this.renderCommentInput(); 11 | this.initReplyListener(); 12 | } 13 | 14 | renderCommentNode(comment, parentId) { 15 | const node = this.createCommentNode(comment); 16 | 17 | if (comment.level === 0) { 18 | this.baseWrapper.appendChild(node); 19 | } else { 20 | document.getElementById(parentId).querySelector(`.nesting`).appendChild(node); 21 | } 22 | } 23 | 24 | createCommentNode(comment) { 25 | const node = this.commentNode.content.cloneNode(true); 26 | node.querySelector('.comment').id = comment.id; 27 | node.querySelector('.comment').classList.add(`level-${comment.level}`); 28 | node.querySelector('.comment .content').style.backgroundColor = `${this.colors[comment.level % 7]}20`; 29 | node.querySelector('.content').textContent = comment.title; 30 | 31 | this.renderCommentInput(node.querySelector('.comment-box')); 32 | 33 | return node; 34 | } 35 | 36 | renderAllComments(comments, baseWrapper = this.baseWrapper) { 37 | for (let comment of comments) { 38 | const node = this.createCommentNode(comment); 39 | 40 | if (comment.replies) { 41 | this.renderAllComments(comment.replies, node.querySelector('.nesting')); 42 | } 43 | 44 | baseWrapper.appendChild(node); 45 | } 46 | } 47 | 48 | renderCommentInput(baseWrapper = this.baseWrapper) { 49 | const node = this.input.content.cloneNode(true); 50 | baseWrapper.appendChild(node); 51 | } 52 | 53 | initCommentListener(callback) { 54 | document.addEventListener('keyup', e => { 55 | if (e.target.classList.contains('comment-input') && e.code === 'Enter') { 56 | const id = parseInt(e.target.parentNode.parentNode.id, 10); 57 | callback(e.target.value, id); 58 | e.target.value = ''; 59 | if(e.target.parentNode.id !== 'comment-wrapper') { 60 | e.target.parentNode.classList.add('hidden'); 61 | } 62 | } 63 | }) 64 | } 65 | 66 | initReplyListener() { 67 | document.addEventListener('click', e => { 68 | if (e.target.classList.contains('reply')) { 69 | const commentBox = e.target.parentNode.parentNode.querySelector('.comment-box'); 70 | commentBox.classList.contains('hidden') ? commentBox.classList.remove('hidden') : commentBox.classList.add('hidden'); 71 | } 72 | }); 73 | } 74 | 75 | initDeleteListener(callback) { 76 | document.addEventListener('click', e => { 77 | if (e.target.classList.contains('delete')) { 78 | e.target.parentNode.parentNode.remove(); 79 | callback(parseInt(e.target.parentNode.parentNode.id, 10)); 80 | } 81 | }); 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /comment-widget/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/comment-widget/src/assets/.gitkeep -------------------------------------------------------------------------------- /comment-widget/src/index.js: -------------------------------------------------------------------------------- 1 | import Store from './app/store'; 2 | import Views from './app/views'; 3 | import './style.scss' 4 | 5 | export const loadApp = () => { 6 | 7 | const store = new Store(); 8 | const viewService = new Views(); 9 | 10 | viewService.renderAllComments(store.comments); 11 | 12 | viewService.initCommentListener((comment, id) => { 13 | const newComment = store.saveComment(comment, id || 0); 14 | viewService.renderCommentNode(newComment, id); 15 | }); 16 | 17 | viewService.initDeleteListener((id) => { 18 | store.deleteComment(id); 19 | }) 20 | } 21 | 22 | 23 | loadApp(); -------------------------------------------------------------------------------- /comment-widget/src/style.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | /* Basic reset */ 7 | 8 | html { 9 | font-size: 16px; 10 | } 11 | 12 | html, 13 | body { 14 | margin: 0; 15 | padding: 0; 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 21 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 22 | font-size: 1rem; 23 | font-weight: 400; 24 | } 25 | 26 | *, 27 | *::after, 28 | *::before { 29 | box-sizing: inherit; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | -------------------------------------------------------------------------------- /comment-widget/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | './src/**/*.html', 4 | './src/**/*.js' 5 | ], 6 | darkMode: false, // or 'media' or 'class' 7 | theme: { 8 | extend: {}, 9 | }, 10 | variants: { 11 | extend: {}, 12 | }, 13 | plugins: [], 14 | } 15 | -------------------------------------------------------------------------------- /comment-widget/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'Comment Integration - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /comment-widget/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', 'postcss-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'Comment Integration', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /data-binding/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 14 | -------------------------------------------------------------------------------- /data-binding/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "postcss-cli": "^8.3.1", 21 | "postcss-loader": "^5.2.0", 22 | "sass-loader": "^11.0.1", 23 | "style-loader": "^2.0.0", 24 | "tailwindcss": "^2.1.2", 25 | "webpack": "^5.36.2", 26 | "webpack-cli": "^4.7.0", 27 | "webpack-dev-server": "^3.11.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /data-binding/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | module.exports = { 3 | plugins: [ 4 | tailwindcss('./tailwind.config.js'), 5 | require('autoprefixer'), 6 | ], 7 | }; -------------------------------------------------------------------------------- /data-binding/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/data-binding/src/app/.gitkeep -------------------------------------------------------------------------------- /data-binding/src/app/views.js: -------------------------------------------------------------------------------- 1 | export default class View { 2 | constructor() { 3 | this.input = document.getElementById('input'); 4 | this.result = document.getElementById('result'); 5 | } 6 | 7 | initInputListener(callback) { 8 | this.input.addEventListener('keyup', callback); 9 | } 10 | 11 | outputResult(state) { 12 | this.result.textContent = state.result; 13 | } 14 | 15 | 16 | } -------------------------------------------------------------------------------- /data-binding/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/data-binding/src/assets/.gitkeep -------------------------------------------------------------------------------- /data-binding/src/index.js: -------------------------------------------------------------------------------- 1 | import View from './app/views' 2 | import './style.scss' 3 | 4 | export const loadApp = () => { 5 | 6 | const viewService = new View(); 7 | 8 | const createState = (state) => new Proxy(state, { 9 | set: (state, prop, value) => { 10 | state[prop] = value; 11 | 12 | viewService.outputResult(state); 13 | 14 | return true; 15 | } 16 | }); 17 | 18 | 19 | const state = createState({ 20 | result: '' 21 | }); 22 | 23 | viewService.initInputListener(e => { 24 | state.result = e.target.value; 25 | }); 26 | 27 | 28 | } 29 | 30 | loadApp(); -------------------------------------------------------------------------------- /data-binding/src/style.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | /* Basic reset */ 7 | 8 | html { 9 | font-size: 16px; 10 | } 11 | 12 | html, 13 | body { 14 | margin: 0; 15 | padding: 0; 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 21 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 22 | font-size: 1rem; 23 | font-weight: 400; 24 | } 25 | 26 | *, 27 | *::after, 28 | *::before { 29 | box-sizing: inherit; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | -------------------------------------------------------------------------------- /data-binding/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | './src/**/*.html', 4 | './src/**/*.js' 5 | ], 6 | darkMode: false, // or 'media' or 'class' 7 | theme: { 8 | extend: {}, 9 | }, 10 | variants: { 11 | extend: {}, 12 | }, 13 | plugins: [], 14 | } 15 | -------------------------------------------------------------------------------- /data-binding/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: '2 way data binding - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /data-binding/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', 'postcss-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: '2 way data binding', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /email/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Email 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 | 16 | 21 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /email/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "sass-loader": "^11.0.1", 21 | "style-loader": "^2.0.0", 22 | "webpack": "^5.36.2", 23 | "webpack-cli": "^4.7.0", 24 | "webpack-dev-server": "^3.11.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /email/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/email/src/app/.gitkeep -------------------------------------------------------------------------------- /email/src/app/http-client.js: -------------------------------------------------------------------------------- 1 | export default class HttpClient { 2 | BASE_URL = 'https://jsonplaceholder.typicode.com' 3 | 4 | constructor() { } 5 | 6 | async getEmail() { 7 | try { 8 | const emailResponse = await fetch(`${this.BASE_URL}/posts`); 9 | return await emailResponse.json(); 10 | } catch (e) { 11 | return await e; 12 | } 13 | 14 | } 15 | 16 | 17 | } -------------------------------------------------------------------------------- /email/src/app/renderer.js: -------------------------------------------------------------------------------- 1 | import View from "./view"; 2 | 3 | export default class Renderer { 4 | constructor() { 5 | this.viewService = new View(); 6 | 7 | const render = this.viewService.debounceRenderView(100); 8 | 9 | this.store = new Proxy([], { 10 | set: (target, prop, val) => { 11 | target[prop] = val; 12 | render(target); 13 | return true; 14 | } 15 | }); 16 | 17 | this.viewService.onListItemClick((id) => { 18 | const activeIdx = this.store.findIndex((el) => { 19 | return el.id === parseInt(id.split('-').pop(), 10) 20 | }) 21 | render(this.store, activeIdx); 22 | }) 23 | 24 | } 25 | 26 | setState(items) { 27 | this.store.push(...items); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /email/src/app/view.js: -------------------------------------------------------------------------------- 1 | export default class View { 2 | constructor() { 3 | this.bodyTemplate = document.getElementById('email-body'); 4 | this.wrapper = document.getElementById('wrapper'); 5 | this.listItem = document.getElementById('email-list-item'); 6 | } 7 | 8 | onListItemClick(callback) { 9 | document.addEventListener('click', (e) => { 10 | if(e.target.classList.contains('email-list-item')) { 11 | callback(e.target.id) 12 | } 13 | }); 14 | } 15 | 16 | renderEmail(email) { 17 | const { title, body } = email; 18 | 19 | const template = this.bodyTemplate.content.cloneNode(true); 20 | 21 | template.querySelector('.title').textContent = title; 22 | template.querySelector('.content').textContent = body; 23 | 24 | this.wrapper.querySelector('.email-body').innerHTML = '' 25 | this.wrapper.querySelector('.email-body').appendChild(template); 26 | } 27 | 28 | renderListItem(email, fragment, isActive) { 29 | const { title, id } = email; 30 | 31 | const listItem = this.listItem.content.cloneNode(true); 32 | 33 | listItem.querySelector('.title').textContent = title; 34 | listItem.querySelector('.email-list-item').id = `list-item-${id}`; 35 | isActive && listItem.querySelector('.email-list-item').classList.add(`email-list-item-active`); 36 | 37 | fragment.appendChild(listItem); 38 | } 39 | 40 | renderView(state, activeIdx = 0) { 41 | const listFragment = document.createDocumentFragment(); 42 | 43 | state.forEach((item, i) => { 44 | this.renderListItem(item, listFragment, activeIdx === i) 45 | }) 46 | 47 | this.wrapper.querySelector('.email-list').innerHTML = '' 48 | this.wrapper.querySelector('.email-list').appendChild(listFragment); 49 | 50 | const activeEl = state[activeIdx]; 51 | 52 | this.renderEmail(activeEl); 53 | 54 | } 55 | 56 | debounceRenderView(wait) { 57 | let timer; 58 | return (...args) => { 59 | clearTimeout(timer); 60 | 61 | timer = setTimeout(() => { 62 | this.renderView(...args); 63 | console.log('rendered') 64 | }, wait); 65 | } 66 | } 67 | 68 | 69 | 70 | } -------------------------------------------------------------------------------- /email/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/email/src/assets/.gitkeep -------------------------------------------------------------------------------- /email/src/index.js: -------------------------------------------------------------------------------- 1 | import HttpClient from './app/http-client'; 2 | import Renderer from './app/renderer'; 3 | import './style.scss' 4 | 5 | export const loadApp = async () => { 6 | const renderer = new Renderer(); 7 | const httpService = new HttpClient(); 8 | 9 | const emails = await httpService.getEmail(); 10 | 11 | renderer.setState(emails); 12 | } 13 | 14 | loadApp(); -------------------------------------------------------------------------------- /email/src/style.scss: -------------------------------------------------------------------------------- 1 | /* Basic reset */ 2 | 3 | $white: #fff; 4 | $gray: #f3f4f6; 5 | $blue: #3B82F6; 6 | 7 | html { 8 | font-size: 16px; 9 | } 10 | 11 | *, 12 | *::after, 13 | *::before { 14 | box-sizing: inherit; 15 | margin: 0; 16 | padding: 0; 17 | } 18 | 19 | html, 20 | body { 21 | margin: 0; 22 | padding: 0; 23 | box-sizing: border-box; 24 | } 25 | 26 | body { 27 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 28 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 29 | font-size: 1rem; 30 | font-weight: 400; 31 | min-height: 100vh; 32 | width: 100%; 33 | background-color: $gray; 34 | } 35 | 36 | 37 | .email { 38 | display: flex; 39 | 40 | &-list { 41 | flex: 0 0 60%; 42 | display: flex; 43 | flex-direction: column; 44 | padding-left: 1rem; 45 | padding-top: 2rem; 46 | padding-bottom: 1rem; 47 | 48 | &-item { 49 | padding: 1rem; 50 | font-weight: 500; 51 | margin-bottom: 0.5rem; 52 | transition: 0.1s; 53 | border-radius: 0.5rem 0 0 0.5rem; 54 | cursor: pointer; 55 | 56 | .title { 57 | pointer-events: none; 58 | } 59 | 60 | &:hover { 61 | background-color: $white; 62 | } 63 | 64 | &-active { 65 | background-color: $white; 66 | color: $blue; 67 | } 68 | } 69 | } 70 | 71 | &-body { 72 | flex: 0 0 40%; 73 | padding: 1.5rem; 74 | background-color: $white; 75 | padding-top: 2rem; 76 | position: sticky; 77 | top: 0; 78 | max-height: 100vh; 79 | 80 | .title { 81 | font-weight: 600; 82 | margin-bottom: 1rem; 83 | } 84 | 85 | .content { 86 | font-size: 1.2rem; 87 | line-height: 1.2; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /email/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'Email - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /email/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'Email - Production', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /giphy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Giphy - Search Engine 9 | 10 | 11 | 12 | 15 | 16 |
17 | 18 |
19 | 20 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /giphy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "postcss-cli": "^8.3.1", 21 | "postcss-loader": "^5.2.0", 22 | "sass-loader": "^11.0.1", 23 | "style-loader": "^2.0.0", 24 | "tailwindcss": "^2.1.2", 25 | "webpack": "^5.36.2", 26 | "webpack-cli": "^4.7.0", 27 | "webpack-dev-server": "^3.11.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /giphy/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | module.exports = { 3 | plugins: [ 4 | tailwindcss('./tailwind.config.js'), 5 | require('autoprefixer'), 6 | ], 7 | }; -------------------------------------------------------------------------------- /giphy/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/giphy/src/app/.gitkeep -------------------------------------------------------------------------------- /giphy/src/app/http-client.js: -------------------------------------------------------------------------------- 1 | export default class HttpClient { 2 | constructor() { 3 | this.searchEndpoint = 'https://api.giphy.com/v1/gifs/search'; 4 | this._pagination = { 5 | count: 20, 6 | offset: 0, 7 | total_count: Infinity 8 | }; 9 | } 10 | 11 | async getSearchResults(query, offset = 0, limit = 20) { 12 | const response = await fetch(`${this.searchEndpoint}?q=${query}&offset=${offset}&limit=${limit}&api_key=${API_KEY}`); 13 | const data = await response.json(); 14 | return data; 15 | } 16 | 17 | 18 | 19 | } 20 | 21 | const API_KEY = '3RxA3kiBwdUQ5S2X6IzXpLJoLACjzhr1'; -------------------------------------------------------------------------------- /giphy/src/app/store.js: -------------------------------------------------------------------------------- 1 | export default class Store { 2 | constructor() { 3 | this._store = []; 4 | } 5 | 6 | get storeCount() { 7 | return this._store.length; 8 | } 9 | 10 | getStore() { 11 | return this._store; 12 | } 13 | 14 | clearStore() { 15 | this._store = []; 16 | } 17 | 18 | setStore(data) { 19 | this._store.push(...data); 20 | } 21 | 22 | transformData(data) { 23 | return data.map(el => { 24 | return { 25 | url: el.images.fixed_height.url, 26 | id: el.id, 27 | link: el.url, 28 | title: el.title 29 | } 30 | }); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /giphy/src/app/view.js: -------------------------------------------------------------------------------- 1 | export default class View { 2 | constructor() { 3 | this.resultWrapper = document.getElementById('results-wrapper'); 4 | this.resultNode = document.getElementById('result'); 5 | this.searchIp = document.getElementById('search'); 6 | this.loadMore = document.getElementById('load-more'); 7 | this.loader = document.getElementById('loader'); 8 | } 9 | 10 | renderResult(data) { 11 | const node = this.resultNode.content.cloneNode(true); 12 | node.querySelector('img').src = data.url; 13 | node.querySelector('img').alt = data.title; 14 | node.querySelector('img').title = data.title; 15 | node.querySelector('.result').id = data.id; 16 | node.querySelector('.result').href = data.link; 17 | 18 | this.resultWrapper.appendChild(node); 19 | } 20 | 21 | clearView() { 22 | this.resultWrapper.innerHTML = ''; 23 | } 24 | 25 | clearSearch() { 26 | this.searchIp.value = ''; 27 | } 28 | 29 | initSearchListener(callback) { 30 | const deboncedSearchCb = this.debounceSearch(callback, 1000); 31 | this.searchIp.addEventListener('keyup', e => { 32 | this.showLoader(); 33 | deboncedSearchCb(e); 34 | }) 35 | } 36 | 37 | debounceSearch(fn, delay) { 38 | let timer; 39 | 40 | return (...args) => { 41 | clearTimeout(timer); 42 | timer = setTimeout(() => fn.apply(this, args), delay); 43 | } 44 | 45 | } 46 | 47 | hideLoadBtn() { 48 | this.loadMore.classList.add('hidden'); 49 | } 50 | 51 | showLoadBtn() { 52 | this.loadMore.classList.remove('hidden'); 53 | } 54 | 55 | hideLoader() { 56 | this.loader.classList.add('hidden'); 57 | } 58 | 59 | showLoader() { 60 | this.loader.classList.remove('hidden'); 61 | } 62 | 63 | initLoadMoreListener(callback) { 64 | this.loadMore.addEventListener('click', callback); 65 | } 66 | 67 | 68 | } -------------------------------------------------------------------------------- /giphy/src/app/viewport-observer.js: -------------------------------------------------------------------------------- 1 | export default class ViewportObserver { 2 | 3 | initObserver(node, callback) { 4 | const observer = new IntersectionObserver(callback, { threshold: 0.9 }) 5 | observer.observe(node); 6 | }; 7 | 8 | } -------------------------------------------------------------------------------- /giphy/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/giphy/src/assets/.gitkeep -------------------------------------------------------------------------------- /giphy/src/index.js: -------------------------------------------------------------------------------- 1 | import HttpClient from './app/http-client'; 2 | import Store from './app/store'; 3 | import View from './app/view'; 4 | import ViewportObserver from './app/viewport-observer'; 5 | import './style.scss' 6 | 7 | export const loadApp = () => { 8 | 9 | const viewService = new View(); 10 | const httpService = new HttpClient(); 11 | const storeService = new Store(); 12 | const viewportObserver = new ViewportObserver(); 13 | 14 | let searchTerm = ''; 15 | let resultCount = 0; 16 | 17 | const resetView = () => { 18 | viewService.clearView(); 19 | viewService.clearSearch(); 20 | storeService.clearStore(); 21 | viewService.hideLoadBtn(); 22 | viewService.hideLoader(); 23 | resultCount = 0; 24 | }; 25 | 26 | const fetchResults = async (offset = 0) => { 27 | try { 28 | let results = await httpService.getSearchResults(searchTerm, offset); 29 | 30 | resultCount = results.pagination.total_count; 31 | results = storeService.transformData(results.data); 32 | 33 | if (resultCount > storeService.storeCount) { 34 | viewService.showLoadBtn(); 35 | } else { 36 | viewService.hideLoadBtn(); 37 | } 38 | 39 | storeService.setStore(results); 40 | 41 | results.forEach(result => { 42 | viewService.renderResult(result); 43 | }); 44 | 45 | viewService.hideLoader(); 46 | 47 | } catch (e) { 48 | console.error(e); 49 | alert('Failed to fetch results'); 50 | resetView(); 51 | } 52 | } 53 | 54 | viewportObserver.initObserver(viewService.loadMore, (e) => { 55 | if(e.some(el => el.isIntersecting)) { 56 | viewService.showLoader(); 57 | storeService.storeCount && fetchResults(storeService.storeCount); 58 | } 59 | }) 60 | 61 | 62 | viewService.initSearchListener(async e => { 63 | if (searchTerm) { 64 | viewService.clearView(); 65 | storeService.clearStore(); 66 | } 67 | 68 | searchTerm = e.target.value?.trim(); 69 | 70 | if (searchTerm) { 71 | await fetchResults(); 72 | } else { 73 | resetView(); 74 | } 75 | }); 76 | 77 | } 78 | 79 | loadApp(); -------------------------------------------------------------------------------- /giphy/src/style.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | /* Basic reset */ 7 | 8 | html { 9 | font-size: 16px; 10 | } 11 | 12 | html, 13 | body { 14 | margin: 0; 15 | padding: 0; 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 21 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 22 | font-size: 1rem; 23 | font-weight: 400; 24 | } 25 | 26 | *, 27 | *::after, 28 | *::before { 29 | box-sizing: inherit; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | 34 | @keyframes loadingInfinite { 35 | 0% { transform: scaleX(0.2) translateX(0)} 36 | 50% { transform: scaleX(0.8) translateX(100%)} 37 | 100% { transform: scaleX(0.2) translateX(0)} 38 | } 39 | 40 | .loader { 41 | transform-origin: left; 42 | animation: loadingInfinite 2000ms ease-in 0s infinite alternate; 43 | } 44 | -------------------------------------------------------------------------------- /giphy/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | './src/**/*.html', 4 | './src/**/*.js' 5 | ], 6 | darkMode: false, // or 'media' or 'class' 7 | theme: { 8 | extend: {}, 9 | }, 10 | variants: { 11 | extend: {}, 12 | }, 13 | plugins: [], 14 | } 15 | -------------------------------------------------------------------------------- /giphy/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'Giphy - Search Engine - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /giphy/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', 'postcss-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'Giphy - Search Engine', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /scaffold/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Webpack Scaffold 9 | 10 | 11 | 12 |

TESSTTT

13 | 14 | -------------------------------------------------------------------------------- /scaffold/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "sass-loader": "^11.0.1", 21 | "style-loader": "^2.0.0", 22 | "webpack": "^5.36.2", 23 | "webpack-cli": "^4.7.0", 24 | "webpack-dev-server": "^3.11.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scaffold/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/scaffold/src/app/.gitkeep -------------------------------------------------------------------------------- /scaffold/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/scaffold/src/assets/.gitkeep -------------------------------------------------------------------------------- /scaffold/src/index.js: -------------------------------------------------------------------------------- 1 | import './style.scss' 2 | 3 | export const loadApp = () => { 4 | 5 | } 6 | 7 | loadApp(); -------------------------------------------------------------------------------- /scaffold/src/style.scss: -------------------------------------------------------------------------------- 1 | /* Basic reset */ 2 | 3 | html { 4 | font-size: 16px; 5 | } 6 | 7 | html, 8 | body { 9 | margin: 0; 10 | padding: 0; 11 | box-sizing: border-box; 12 | } 13 | 14 | body { 15 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 16 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 17 | font-size: 1rem; 18 | font-weight: 400; 19 | min-height: 100vh; 20 | width: 100%; 21 | } 22 | 23 | *, 24 | *::after, 25 | *::before { 26 | box-sizing: inherit; 27 | margin: 0; 28 | padding: 0; 29 | } 30 | -------------------------------------------------------------------------------- /scaffold/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'Scaffold - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /scaffold/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'Scafold - Production', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /task-tracker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Task Tracker 9 | 10 | 11 | 12 |
13 |

Sprint #4

14 | 17 |
18 |
19 |
20 |

Backlog

21 |
23 |
24 |
25 |
26 |

In Progress

27 |
29 |
30 |
31 |

In Review

32 |
34 |
35 |
36 |

Done

37 |
39 |
40 |
41 | 49 | 50 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /task-tracker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "postcss-cli": "^8.3.1", 21 | "postcss-loader": "^5.2.0", 22 | "sass-loader": "^11.0.1", 23 | "style-loader": "^2.0.0", 24 | "tailwindcss": "^2.1.2", 25 | "webpack": "^5.36.2", 26 | "webpack-cli": "^4.7.0", 27 | "webpack-dev-server": "^3.11.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /task-tracker/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | module.exports = { 3 | plugins: [ 4 | tailwindcss('./tailwind.config.js'), 5 | require('autoprefixer'), 6 | ], 7 | }; -------------------------------------------------------------------------------- /task-tracker/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/task-tracker/src/app/.gitkeep -------------------------------------------------------------------------------- /task-tracker/src/app/drag.js: -------------------------------------------------------------------------------- 1 | export default class Drag { 2 | constructor() { 3 | this.dragged = null; 4 | } 5 | 6 | dropZoneClasses = ['bg-blue-50', 'border-gray-200'] 7 | 8 | initDragListener(onDrag) { 9 | 10 | document.addEventListener('dragstart', (e) => { 11 | this.dragged = e.target; 12 | this.dragged.style.opacity = 0; 13 | }); 14 | 15 | document.addEventListener('dragover', (e) => { 16 | e.preventDefault(); 17 | }) 18 | 19 | document.addEventListener('dragenter', e => { 20 | if (e.target.classList.contains('dropzone')) { 21 | e.target.classList.add(...this.dropZoneClasses) 22 | } 23 | }) 24 | 25 | document.addEventListener('dragleave', e => { 26 | if (e.target.classList.contains('dropzone')) { 27 | e.target.classList.remove(...this.dropZoneClasses) 28 | } 29 | }) 30 | 31 | document.addEventListener('drop', (e) => { 32 | this.dragged.style.opacity = 1; 33 | if (e.target.classList.contains('dropzone')) { 34 | this.dragged.parentNode.removeChild(this.dragged); 35 | e.target.appendChild(this.dragged); 36 | e.target.classList.remove(...this.dropZoneClasses) 37 | const zone = parseInt(e.target.id?.split('-').pop(), 10); 38 | const taskId = parseInt(this.dragged.id,10) 39 | onDrag(zone, taskId); 40 | } 41 | }); 42 | 43 | } 44 | 45 | 46 | 47 | } -------------------------------------------------------------------------------- /task-tracker/src/app/layout.js: -------------------------------------------------------------------------------- 1 | import Sidebar from "./sidebar"; 2 | 3 | export default class Layout { 4 | 5 | constructor() { 6 | this.taskNode = document.getElementById('task'); 7 | this.baseWrappers = document.querySelectorAll('.dropzone'); 8 | this.createBtn = document.getElementById('create-task'); 9 | } 10 | 11 | 12 | createTaskLayout({ title, description, id, category, status }) { 13 | const node = this.taskNode.content.cloneNode(true); 14 | node.querySelector('.task').id = id; 15 | node.querySelector('.title').textContent = title; 16 | node.querySelector('.description').textContent = description; 17 | node.querySelector('.category').textContent = category; 18 | node.querySelector('.category').style.color = colorClasses[parseInt(Math.random() * colorClasses.length, 10)]; 19 | 20 | node.querySelector('.task').addEventListener('click', (e) => { 21 | const sidebar = new Sidebar(); 22 | sidebar.initSidebar({ title, description, id, category, status }); 23 | },); 24 | 25 | this.baseWrappers[status || 0].appendChild(node); 26 | 27 | return true; 28 | } 29 | 30 | createBtnListener(saveCallback) { 31 | this.createBtn.addEventListener('click', async (e) => { 32 | const sidebar = new Sidebar(); 33 | const result = await sidebar.initSidebar(); 34 | saveCallback(result); 35 | }); 36 | } 37 | 38 | } 39 | 40 | export const colorClasses = ['#F59E0B', '#F472B6', '#FBBF24', '#F87171']; -------------------------------------------------------------------------------- /task-tracker/src/app/sidebar.js: -------------------------------------------------------------------------------- 1 | export default class Sidebar { 2 | constructor() { 3 | this.sidebar = document.getElementById('sidebar'); 4 | } 5 | 6 | async initSidebar(data = null) { 7 | return new Promise(res => { 8 | const node = this.sidebar.content.cloneNode(true); 9 | 10 | this.initCloseListener(node, res); 11 | 12 | if (data) { 13 | node.getElementById('save').classList.add('hidden'); 14 | node.getElementById('title').value = data.title; 15 | node.getElementById('title').disabled = true; 16 | node.getElementById('description').value = data.description; 17 | node.getElementById('description').disabled = true; 18 | node.getElementById('category').value = data.category; 19 | node.getElementById('category').disabled = true; 20 | } else { 21 | node.getElementById('save').addEventListener('click', e => { 22 | res({ title: title.value, description: description.value, category: category.value, status: 0 }); 23 | this.disposeSidebar(); 24 | }); 25 | } 26 | 27 | document.body.appendChild(node); 28 | }); 29 | } 30 | 31 | disposeSidebar(cb) { 32 | document.body.removeChild(document.getElementById('task-sidebar')); 33 | cb && cb(false); 34 | } 35 | 36 | initCloseListener(node, cb) { 37 | node.getElementById('close').addEventListener('click', e => { 38 | this.disposeSidebar(cb); 39 | }); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /task-tracker/src/app/store.js: -------------------------------------------------------------------------------- 1 | export default class Store { 2 | 3 | constructor() { 4 | this._store = []; 5 | this.getLocalStore(); 6 | } 7 | 8 | saveToStore(data) { 9 | this._store.push({ ...data, id: parseInt(Math.random() * 10000, 10), status: 0 }); 10 | this.setLocalStore(); 11 | } 12 | 13 | getLocalStore() { 14 | if (window.localStorage.getItem('tasks')) { 15 | this._store = JSON.parse(window.localStorage.getItem('tasks')); 16 | } 17 | } 18 | 19 | setLocalStore() { 20 | window.localStorage.setItem('tasks', JSON.stringify(this._store)); 21 | } 22 | 23 | getAllTasks() { 24 | return this._store; 25 | } 26 | 27 | getTaskById(id) { 28 | return this._store.find(el => el.id === id); 29 | } 30 | 31 | patchStatus(id, status) { 32 | const node = this.getTaskById(id); 33 | node ? node.status = status : ''; 34 | this.setLocalStore(); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /task-tracker/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/task-tracker/src/assets/.gitkeep -------------------------------------------------------------------------------- /task-tracker/src/index.js: -------------------------------------------------------------------------------- 1 | import Drag from './app/drag'; 2 | import Layout from './app/layout'; 3 | import Store from './app/store'; 4 | import './style.scss' 5 | 6 | export const loadApp = () => { 7 | 8 | const layoutService = new Layout(); 9 | const storeService = new Store(); 10 | const dragService = new Drag(); 11 | 12 | let store = storeService.getAllTasks(); 13 | 14 | store.forEach(item => { 15 | layoutService.createTaskLayout(item); 16 | }); 17 | 18 | layoutService.createBtnListener((data) => { 19 | if (data) { 20 | storeService.saveToStore(data); 21 | layoutService.createTaskLayout(data); 22 | } 23 | }); 24 | 25 | dragService.initDragListener((dropzone, id) => { 26 | storeService.patchStatus(id, dropzone); 27 | }); 28 | 29 | } 30 | 31 | loadApp(); -------------------------------------------------------------------------------- /task-tracker/src/style.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* Basic reset */ 6 | 7 | html { 8 | font-size: 16px; 9 | } 10 | 11 | html, 12 | body { 13 | margin: 0; 14 | padding: 0; 15 | box-sizing: border-box; 16 | } 17 | 18 | body { 19 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 20 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 21 | font-size: 1rem; 22 | font-weight: 400; 23 | } 24 | 25 | *, 26 | *::after, 27 | *::before { 28 | box-sizing: inherit; 29 | margin: 0; 30 | padding: 0; 31 | } 32 | 33 | .dropzone { 34 | height: calc(100vh - 12rem); 35 | } 36 | 37 | .task { 38 | min-height: 7rem; 39 | } 40 | 41 | @keyframes slideIn { 42 | from { 43 | transform: translateX(100%); 44 | } 45 | to { 46 | transform: translateX(0%); 47 | } 48 | } 49 | 50 | .sidebar-wrapper { 51 | animation: slideIn 400ms ease-out; 52 | } 53 | -------------------------------------------------------------------------------- /task-tracker/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | './src/**/*.html', 4 | './src/**/*.js' 5 | ], 6 | darkMode: false, // or 'media' or 'class' 7 | theme: { 8 | extend: {}, 9 | }, 10 | variants: { 11 | extend: {}, 12 | }, 13 | plugins: [], 14 | } 15 | -------------------------------------------------------------------------------- /task-tracker/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'Task Tracker - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /task-tracker/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', 'postcss-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'Task Tracker - Production', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /tictactoe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 20 |

21 | 22 |

23 | 24 |
32 | 33 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /tictactoe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "postcss-cli": "^8.3.1", 21 | "postcss-loader": "^5.2.0", 22 | "sass-loader": "^11.0.1", 23 | "style-loader": "^2.0.0", 24 | "tailwindcss": "^2.1.2", 25 | "webpack": "^5.36.2", 26 | "webpack-cli": "^4.7.0", 27 | "webpack-dev-server": "^3.11.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tictactoe/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | module.exports = { 3 | plugins: [ 4 | tailwindcss('./tailwind.config.js'), 5 | require('autoprefixer'), 6 | ], 7 | }; -------------------------------------------------------------------------------- /tictactoe/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/tictactoe/src/app/.gitkeep -------------------------------------------------------------------------------- /tictactoe/src/app/controller.js: -------------------------------------------------------------------------------- 1 | export default class Controller { 2 | options = ['🟢', '❌']; 3 | 4 | constructor() { 5 | this.currentControl = 0; 6 | } 7 | 8 | getOption(current) { 9 | return this.options[current]; 10 | } 11 | 12 | switchControl() { 13 | this.currentControl = Math.abs(this.currentControl - 1); 14 | } 15 | 16 | getActiveControl() { 17 | return this.currentControl; 18 | } 19 | 20 | 21 | checkWin(grid, current) { 22 | const checkRow = (row) => row.every(element => element === current); 23 | const checkCol = (grid, j) => [grid[0][j], grid[1][j], grid[2][j]].every(element => element === current); 24 | const checkDiag = (grid) => { 25 | return (grid[0][0] === current && grid[0][0] === grid[1][1] && grid[1][1] === grid[2][2]) 26 | || (grid[0][2] === current && grid[0][2] === grid[1][1] && grid[1][1] === grid[2][0]) 27 | }; 28 | 29 | let j = 0; 30 | 31 | for (let row of grid) { 32 | if (checkRow(row)) return true; 33 | if (checkCol(grid, j)) return true; 34 | if (checkDiag(grid)) return true; 35 | j++; 36 | } 37 | 38 | return false; 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /tictactoe/src/app/store.js: -------------------------------------------------------------------------------- 1 | export default class Store { 2 | 3 | store; 4 | 5 | constructor() { 6 | this.initStore(); 7 | } 8 | 9 | getStore() { 10 | return this.store; 11 | } 12 | 13 | setItem(cell, value) { 14 | let row = cell <= 3 ? 0 : cell <= 6 ? 1 : 2; 15 | let col = (cell - 1) % 3; 16 | this.store[row][col] = value; 17 | } 18 | 19 | getItem(cell) { 20 | let row = cell <= 3 ? 0 : cell <= 6 ? 1 : 2; 21 | let col = (cell - 1) % 3; 22 | return this.store[row][col] 23 | } 24 | 25 | initStore() { 26 | this.store = new Array(3).fill(null).map(el => new Array(3).fill(null)); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /tictactoe/src/app/views.js: -------------------------------------------------------------------------------- 1 | export default class View { 2 | 3 | constructor() { 4 | this.gridWrapper = document.getElementById('grid'); 5 | this.cellTemplate = document.getElementById('cell'); 6 | this.turnHeading = document.getElementById('turn'); 7 | 8 | this.constructGrid(); 9 | } 10 | 11 | constructGrid() { 12 | for(let i = 0; i < 9; i++) { 13 | const node = this.cellTemplate.content.cloneNode(true); 14 | node.querySelector('.content').id = `content-${i+1}`; 15 | this.gridWrapper.appendChild(node); 16 | } 17 | } 18 | 19 | initGridListener(callback) { 20 | this.gridWrapper.addEventListener('click' ,e => { 21 | callback(parseInt(e.target.id.split('-').pop(), 10)); 22 | }); 23 | } 24 | 25 | addCellValue(cell, value) { 26 | const node = this.gridWrapper.querySelector(`#content-${cell}`); 27 | node.textContent = value; 28 | node.classList.remove('cursor-pointer'); 29 | } 30 | 31 | setTurn(content) { 32 | this.turnHeading.textContent = content; 33 | } 34 | 35 | alertWinner(message) { 36 | setTimeout(() => { 37 | alert(message) 38 | }, 0); 39 | } 40 | 41 | reset() { 42 | Array.from(this.gridWrapper.children).forEach(node => this.gridWrapper.removeChild(node)); 43 | this.constructGrid(); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /tictactoe/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/tictactoe/src/assets/.gitkeep -------------------------------------------------------------------------------- /tictactoe/src/index.js: -------------------------------------------------------------------------------- 1 | import Controller from './app/controller'; 2 | import Store from './app/store'; 3 | import View from './app/views'; 4 | import './style.scss' 5 | 6 | export const loadApp = () => { 7 | 8 | const viewService = new View(); 9 | const storeService = new Store(); 10 | const controller = new Controller(); 11 | 12 | viewService.setTurn(`Player ${controller.getActiveControl() + 1} - ${controller.getOption(controller.getActiveControl())}`); 13 | 14 | let turns = 1; 15 | 16 | const alertAndReset = (message) => { 17 | viewService.alertWinner(message); 18 | setTimeout(() => { 19 | viewService.reset(); 20 | storeService.initStore(); 21 | turns = 1; 22 | }, 100); 23 | } 24 | 25 | viewService.initGridListener((cell) => { 26 | 27 | const existingValue = storeService.getItem(cell); 28 | 29 | if (existingValue === null) { 30 | const active = controller.getActiveControl(); 31 | const value = controller.getOption(active); 32 | 33 | storeService.setItem(cell, active); 34 | viewService.addCellValue(cell, value); 35 | 36 | const winner = controller.checkWin(storeService.getStore(), active); 37 | 38 | if (winner) { 39 | alertAndReset(`Player ${active + 1} wins!`); 40 | return; 41 | } 42 | 43 | if (turns === 9) { 44 | alertAndReset(`It's a tie`); 45 | return; 46 | } 47 | 48 | turns++; 49 | 50 | controller.switchControl(); 51 | viewService.setTurn(`Player ${controller.getActiveControl() + 1} - ${controller.getOption(controller.getActiveControl())}`); 52 | } 53 | 54 | }); 55 | 56 | 57 | 58 | } 59 | 60 | loadApp(); -------------------------------------------------------------------------------- /tictactoe/src/style.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | /* Basic reset */ 7 | 8 | html { 9 | font-size: 16px; 10 | } 11 | 12 | html, 13 | body { 14 | margin: 0; 15 | padding: 0; 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 21 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 22 | font-size: 1rem; 23 | font-weight: 400; 24 | } 25 | 26 | *, 27 | *::after, 28 | *::before { 29 | box-sizing: inherit; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | 34 | 35 | .maze { 36 | width: 30rem; 37 | height: 30rem; 38 | max-width: 100%; 39 | max-height: 100%; 40 | } -------------------------------------------------------------------------------- /tictactoe/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | './src/**/*.html', 4 | './src/**/*.js' 5 | ], 6 | darkMode: false, // or 'media' or 'class' 7 | theme: { 8 | extend: {}, 9 | }, 10 | variants: { 11 | extend: {}, 12 | }, 13 | plugins: [], 14 | } 15 | -------------------------------------------------------------------------------- /tictactoe/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'TicTacToe - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /tictactoe/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', 'postcss-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'TicTacToe', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /todo v2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Todo v2 9 | 10 | 11 | 12 | 13 |
14 | 15 | 23 | 24 | -------------------------------------------------------------------------------- /todo v2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "sass-loader": "^11.0.1", 21 | "style-loader": "^2.0.0", 22 | "webpack": "^5.36.2", 23 | "webpack-cli": "^4.7.0", 24 | "webpack-dev-server": "^3.11.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /todo v2/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/todo v2/src/app/.gitkeep -------------------------------------------------------------------------------- /todo v2/src/app/store.js: -------------------------------------------------------------------------------- 1 | export default class Store { 2 | constructor() { 3 | this._store = []; 4 | this.getLocalStore(); 5 | } 6 | 7 | get allNotes() { 8 | return this._store; 9 | } 10 | 11 | setStore(state) { 12 | this._store = state; 13 | } 14 | 15 | addNote(title) { 16 | const note = { 17 | title, 18 | id: Math.round(Math.random() * 100000), 19 | done: false 20 | } 21 | 22 | this._store.unshift(note); 23 | this.setLocalStore(); 24 | 25 | return note; 26 | } 27 | 28 | patchNote(value) { 29 | const { title, id, done } = value; 30 | 31 | const note = this._store.find(el => el.id === id); 32 | 33 | if (note) { 34 | if(title !== undefined) { 35 | note.title = title; 36 | } 37 | if(done !== undefined) { 38 | note.done = done; 39 | } 40 | 41 | this.setLocalStore(); 42 | return true; 43 | } 44 | 45 | return false 46 | } 47 | 48 | getLocalStore() { 49 | const localStore = localStorage.getItem('todos'); 50 | 51 | try { 52 | if (localStore) { 53 | const data = JSON.parse(localStore) 54 | this.setStore(data); 55 | } 56 | } catch (e) { 57 | console.error(e) 58 | } 59 | } 60 | 61 | setLocalStore() { 62 | if (this._store.length) { 63 | localStorage.setItem('todos', JSON.stringify(this._store)); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /todo v2/src/app/views.js: -------------------------------------------------------------------------------- 1 | const BASE_INPUT = 'base-input'; 2 | const TASK_INPUT = 'task-input'; 3 | 4 | export default class Views { 5 | 6 | constructor() { 7 | this.baseTodo = document.getElementById('todo'); 8 | this.wrapper = document.getElementById('wrapper'); 9 | 10 | this.initView(); 11 | } 12 | 13 | initView() { 14 | const notesInput = this.baseTodo.content.cloneNode(true); 15 | 16 | notesInput.querySelector('.todo input[type=text]').classList.add(BASE_INPUT); 17 | const checkNode = notesInput.querySelector('.todo .check'); 18 | notesInput.querySelector('.todo').removeChild(checkNode) 19 | 20 | this.wrapper.appendChild(notesInput); 21 | } 22 | 23 | onEnterPress(callback) { 24 | document.addEventListener('keyup', e => { 25 | if (e.target.classList.contains(BASE_INPUT)) { 26 | if (e.code === 'Enter' && e.target.value.trim()) { 27 | callback(e.target.value); 28 | e.target.value = ''; 29 | } 30 | } 31 | }); 32 | } 33 | 34 | onTaskChange(callback) { 35 | let timer; 36 | 37 | document.addEventListener('keyup', (e) => { 38 | clearTimeout(timer); 39 | 40 | timer = setTimeout(() => { 41 | if (e.target.classList.contains(TASK_INPUT)) { 42 | const parent = e.target.closest('.todo-task'); 43 | callback({ id: parseInt(parent.id, 10), title: e.target.value }) 44 | } 45 | }, 500); 46 | }); 47 | 48 | document.addEventListener('click', (e) => { 49 | if (e.target.type === 'checkbox') { 50 | const parent = e.target.closest('.todo-task'); 51 | callback({ id: parseInt(parent.id, 10), done: e.target.checked }) 52 | } 53 | }); 54 | 55 | } 56 | 57 | renderNote(note, first = false) { 58 | const { id, title, done } = note; 59 | 60 | const notesInput = this.baseTodo.content.cloneNode(true); 61 | 62 | notesInput.querySelector('.todo input[type=text]').classList.add(TASK_INPUT); 63 | notesInput.querySelector('.todo input[type=text]').value = title; 64 | notesInput.querySelector('.todo').id = id; 65 | notesInput.querySelector('.todo').classList.add('todo-task'); 66 | const checkNode = notesInput.querySelector('.todo .check'); 67 | 68 | if (done) { 69 | checkNode.querySelector('input[type=checkbox]').checked = true; 70 | } 71 | 72 | 73 | if (first) { 74 | this.wrapper.insertBefore(notesInput, this.wrapper.firstElementChild.nextElementSibling) 75 | } else { 76 | this.wrapper.appendChild(notesInput) 77 | } 78 | 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /todo v2/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/todo v2/src/assets/.gitkeep -------------------------------------------------------------------------------- /todo v2/src/index.js: -------------------------------------------------------------------------------- 1 | import Store from './app/store'; 2 | import Views from './app/views'; 3 | import './style.scss' 4 | 5 | export const render = () => { 6 | const viewService = new Views(); 7 | const storeService = new Store(); 8 | 9 | const state = storeService.allNotes; 10 | 11 | state.forEach((note) => { 12 | viewService.renderNote(note); 13 | }) 14 | 15 | 16 | viewService.onEnterPress((value) => { 17 | const note = storeService.addNote(value); 18 | 19 | viewService.renderNote(note, true); 20 | }); 21 | 22 | 23 | viewService.onTaskChange(({ id, title, done }) => { 24 | storeService.patchNote({ id, title, done }); 25 | }); 26 | 27 | } 28 | 29 | render(); -------------------------------------------------------------------------------- /todo v2/src/style.scss: -------------------------------------------------------------------------------- 1 | /* Basic reset */ 2 | 3 | html { 4 | font-size: 16px; 5 | } 6 | 7 | html, 8 | body { 9 | margin: 0; 10 | padding: 0; 11 | box-sizing: border-box; 12 | } 13 | 14 | body { 15 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 16 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 17 | font-size: 1rem; 18 | font-weight: 400; 19 | min-height: 100vh; 20 | width: 100%; 21 | padding: 2rem; 22 | } 23 | 24 | *, 25 | *::after, 26 | *::before { 27 | box-sizing: inherit; 28 | margin: 0; 29 | padding: 0; 30 | } 31 | 32 | #wrapper { 33 | display: flex; 34 | justify-content: center; 35 | flex-direction: column; 36 | gap: 1rem; 37 | } 38 | 39 | .todo { 40 | min-width: 50%; 41 | height: max-content; 42 | border-radius: 0.5rem; 43 | box-shadow: 0 0 0.5rem rgba($color: #000000, $alpha: 0.1); 44 | display: flex; 45 | position: relative; 46 | transition: 0.2s; 47 | 48 | .check { 49 | position: absolute; 50 | height: 100%; 51 | width: 2rem; 52 | left: 0.25rem; 53 | input[type="checkbox"] { 54 | height: 100%; 55 | width: 100%; 56 | } 57 | } 58 | 59 | input[type="text"] { 60 | border: none; 61 | padding: 1rem; 62 | width: 100%; 63 | font-size: 1.2rem; 64 | font-weight: 500; 65 | 66 | &:focus { 67 | outline: none; 68 | } 69 | } 70 | 71 | &-task { 72 | input[type="text"] { 73 | padding-left: 2.5rem; 74 | } 75 | } 76 | 77 | &-done { 78 | opacity: 0.5; 79 | input[type="text"] { 80 | text-decoration: line-through; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /todo v2/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'Todo v2 - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /todo v2/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'Todo v2 - Production', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

To Do

12 |
13 | 15 |
16 | 17 |
18 | 19 |
20 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /todo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "postcss-cli": "^8.3.1", 21 | "postcss-loader": "^5.2.0", 22 | "sass-loader": "^11.0.1", 23 | "style-loader": "^2.0.0", 24 | "tailwindcss": "^2.1.2", 25 | "webpack": "^5.36.2", 26 | "webpack-cli": "^4.7.0", 27 | "webpack-dev-server": "^3.11.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /todo/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | module.exports = { 3 | plugins: [ 4 | tailwindcss('./tailwind.config.js'), 5 | require('autoprefixer'), 6 | ], 7 | }; -------------------------------------------------------------------------------- /todo/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/todo/src/app/.gitkeep -------------------------------------------------------------------------------- /todo/src/app/http-client.js: -------------------------------------------------------------------------------- 1 | export default class HttpClient { 2 | getUrl = 'https://jsonplaceholder.typicode.com/todos'; 3 | 4 | constructor() {} 5 | 6 | async getAllTodos() { 7 | try { 8 | const response = await fetch(this.getUrl); 9 | const data = await response.json(); 10 | return data.map(({ id, title, completed }) => { 11 | return { 12 | id, 13 | value: title, 14 | done: completed 15 | } 16 | }) 17 | } catch (e) { 18 | console.error('Failed to fetch', e) 19 | } 20 | 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /todo/src/app/layout.js: -------------------------------------------------------------------------------- 1 | export default class Layout { 2 | constructor(stateService) { 3 | this.baseNode = document.querySelector('#task'); 4 | this.wrapperRef = document.querySelector('#tasks-wrapper'); 5 | this.stateService = stateService; 6 | 7 | this.initLayout(); 8 | } 9 | 10 | initLayout() { 11 | const data = this.stateService.getAllTodo(); 12 | if (data.length) { 13 | data.forEach(el => this.addLayout(el)); 14 | } 15 | } 16 | 17 | addLayout(data) { 18 | const node = this.baseNode.content.cloneNode(true); 19 | 20 | const taskContainer = node.querySelector('.task-container'); 21 | const input = node.querySelector('.task-content'); 22 | const deleteBtn = node.querySelector('.task-delete'); 23 | const checkboxWrapper = node.querySelector('.task-check'); 24 | const checkboxInput = checkboxWrapper.querySelector('input'); 25 | const checkImg = checkboxWrapper.querySelector('img'); 26 | 27 | checkboxInput.checked = data.done; 28 | 29 | if (checkboxInput.checked) { 30 | this.markAsDone(input, checkImg); 31 | } 32 | 33 | taskContainer.id = `task-${data.id}`; 34 | 35 | const patchTodo = this.debounceInput(this.stateService.patchTodo.bind(this.stateService), 500); 36 | 37 | input.addEventListener('keyup', (e) => patchTodo(data.id, e.target.value)); 38 | input.value = data.value; 39 | 40 | checkboxInput.addEventListener('change', (e) => { 41 | e.preventDefault(); 42 | checkboxInput.checked = !data.done; 43 | if (checkboxInput.checked) { 44 | this.markAsDone(input, checkImg); 45 | } else { 46 | this.unmarkAsDone(input, checkImg); 47 | } 48 | this.stateService.changeStatus(data.id, checkboxInput.checked); 49 | }); 50 | 51 | deleteBtn.addEventListener('click', () => { 52 | this.stateService.deleteTodo(data.id); 53 | this.removeLayout(data.id); 54 | }); 55 | 56 | this.wrapperRef.insertBefore( 57 | node, 58 | this.wrapperRef.querySelector('.task-container') 59 | ); 60 | } 61 | 62 | removeLayout(id) { 63 | const node = this.wrapperRef.querySelector(`#task-${id}`); 64 | this.wrapperRef.removeChild(node); 65 | } 66 | 67 | markAsDone(input, img) { 68 | input.classList.add('line-through', 'opacity-50'); 69 | img.classList.remove('hidden'); 70 | } 71 | 72 | unmarkAsDone(input, img) { 73 | input.classList.remove('line-through', 'opacity-50'); 74 | img.classList.add('hidden'); 75 | } 76 | 77 | debounceInput(fn, delay) { 78 | let timer; 79 | return (...args) => { 80 | clearTimeout(timer); 81 | timer = setTimeout(() => fn(...args), delay); 82 | }; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /todo/src/app/store.js: -------------------------------------------------------------------------------- 1 | export default class Store { 2 | constructor() { 3 | this._store = []; 4 | } 5 | 6 | getTodoById(id) { 7 | return this._store.find((el) => el.id === id); 8 | } 9 | 10 | getAllTodo() { 11 | return this._store; 12 | } 13 | 14 | deleteTodo(id) { 15 | const deleted = this.getTodoById(id); 16 | if (deleted) { 17 | this._store.splice(this._store.indexOf(deleted), 1); 18 | } 19 | this.setLocalStore(); 20 | } 21 | 22 | patchTodo(id, value) { 23 | const node = this.getTodoById(id); 24 | node.value = value; 25 | this.setLocalStore(); 26 | } 27 | 28 | changeStatus(id, status) { 29 | const node = this.getTodoById(id); 30 | node.done = status; 31 | this.setLocalStore(); 32 | } 33 | 34 | addTodo(value) { 35 | this._store.push({ 36 | id: parseInt(Math.random() * 100000, 10), 37 | value, 38 | done: false, 39 | }); 40 | this.setLocalStore(); 41 | return this._store[this._store.length - 1]; 42 | } 43 | 44 | getLocalStore() { 45 | this._store = JSON.parse(window.localStorage.todo); 46 | } 47 | 48 | setLocalStore() { 49 | window.localStorage.todo = JSON.stringify(this._store); 50 | } 51 | 52 | setStore(data) { 53 | this._store = data; 54 | this.setLocalStore(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /todo/src/app/todo-input.js: -------------------------------------------------------------------------------- 1 | export default class TodoInput { 2 | constructor(stateService, layoutService) { 3 | this.input = document.querySelector('#todo-input'); 4 | this.bindListener(this.input); 5 | this.stateService = stateService; 6 | this.layoutService = layoutService; 7 | } 8 | 9 | bindListener(input) { 10 | input.addEventListener('keydown', (e) => { 11 | if (e.code === 'Enter') { 12 | this.addTodo(e.target.value); 13 | } 14 | }); 15 | } 16 | 17 | clearInput() { 18 | this.input.value = ''; 19 | } 20 | 21 | addTodo(value) { 22 | if (value.trim()) { 23 | const data = this.stateService.addTodo(value); 24 | this.layoutService.addLayout(data); 25 | this.clearInput(); 26 | } else { 27 | alert('Please enter a valid Task'); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /todo/src/assets/delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /todo/src/assets/done.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /todo/src/index.js: -------------------------------------------------------------------------------- 1 | import HttpClient from './app/http-client'; 2 | import Layout from './app/layout'; 3 | import Store from './app/store'; 4 | import TodoInput from './app/todo-input'; 5 | import './style.scss'; 6 | 7 | export const loadApp = async () => { 8 | const httpService = new HttpClient(); 9 | const stateService = new Store(); 10 | 11 | if (window.localStorage.todo) { 12 | stateService.getLocalStore(); 13 | } else { 14 | const data = await httpService.getAllTodos(); 15 | stateService.setStore(data); 16 | } 17 | 18 | const layoutService = new Layout(stateService); 19 | const todoInputService = new TodoInput(stateService, layoutService); 20 | }; 21 | 22 | 23 | loadApp(); 24 | -------------------------------------------------------------------------------- /todo/src/style.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* Basic reset */ 6 | 7 | html { 8 | font-size: 16px; 9 | } 10 | 11 | html, 12 | body { 13 | margin: 0; 14 | padding: 0; 15 | box-sizing: border-box; 16 | } 17 | 18 | body { 19 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 20 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 21 | font-display: swap; 22 | font-size: 1rem; 23 | font-weight: 400; 24 | } 25 | 26 | *, 27 | *::after, 28 | *::before { 29 | box-sizing: inherit; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | 34 | .task-check { 35 | input:checked + label { 36 | @apply bg-green-400; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /todo/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | './src/**/*.html', 4 | './src/**/*.js' 5 | ], 6 | darkMode: false, 7 | theme: { 8 | extend: {}, 9 | }, 10 | variants: { 11 | extend: {}, 12 | }, 13 | plugins: [], 14 | } 15 | -------------------------------------------------------------------------------- /todo/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'Todo - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /todo/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', 'postcss-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'Todo - Production', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; -------------------------------------------------------------------------------- /vanlla-spa/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Webpack Scaffold 9 | 10 | 11 | 12 | 17 | 18 |
19 | 20 | -------------------------------------------------------------------------------- /vanlla-spa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "webpack --config webpack.prod.js", 8 | "start": "webpack serve --open --config webpack.dev.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "autoprefixer": "^10.2.5", 15 | "css-loader": "^5.2.4", 16 | "html-loader": "^2.1.2", 17 | "html-webpack-plugin": "^5.3.1", 18 | "mini-css-extract-plugin": "^1.6.0", 19 | "node-sass": "^6.0.0", 20 | "sass-loader": "^11.0.1", 21 | "style-loader": "^2.0.0", 22 | "webpack": "^5.36.2", 23 | "webpack-cli": "^4.7.0", 24 | "webpack-dev-server": "^3.11.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vanlla-spa/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/vanlla-spa/src/app/.gitkeep -------------------------------------------------------------------------------- /vanlla-spa/src/app/router.js: -------------------------------------------------------------------------------- 1 | export default class Router { 2 | 3 | constructor(routes) { 4 | this.routes = routes; 5 | this.outlet = document.getElementById('router-outlet'); 6 | 7 | this.initRouteHandler(); 8 | } 9 | 10 | initRouteHandler() { 11 | this.navigate(); 12 | 13 | document.body.addEventListener('click', (e) => { 14 | if (e.target.classList.contains('nav-link')) { 15 | e.preventDefault(); 16 | history.pushState(null, null, e.target.href); 17 | this.navigate(); 18 | } 19 | }); 20 | 21 | window.addEventListener('popstate', this.navigate.bind(this)) 22 | } 23 | 24 | navigate() { 25 | let matchRoute = this.routes.find(route => route.url === location.pathname); 26 | if (!matchRoute) { 27 | matchRoute = this.routes[0]; 28 | } 29 | 30 | const view = new matchRoute.view(); 31 | 32 | this.outlet.innerHTML = view.getHTML(); 33 | 34 | return matchRoute.url; 35 | } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /vanlla-spa/src/app/views/About.js: -------------------------------------------------------------------------------- 1 | import BaseView from "./BaseView"; 2 | 3 | export default class About extends BaseView { 4 | constructor() { 5 | super(); 6 | this.setTitle('About'); 7 | } 8 | 9 | getHTML() { 10 | return `

About

11 |

Lorem ipsum dolor sit, amet consectetur adipisicing elit. Nulla facere obcaecati dolore. 12 | Et dolorum laboriosam iure, praesentium, molestiae atque fuga quod sapiente inventore vero numquam optio 13 | ipsa exercitationem aspernatur tempore.

14 | ` 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /vanlla-spa/src/app/views/BaseView.js: -------------------------------------------------------------------------------- 1 | export default class BaseView { 2 | constructor() { } 3 | 4 | setTitle(title) { 5 | document.title = title; 6 | } 7 | 8 | getHTML() { 9 | return `
` 10 | } 11 | 12 | 13 | } -------------------------------------------------------------------------------- /vanlla-spa/src/app/views/Home.js: -------------------------------------------------------------------------------- 1 | import BaseView from "./BaseView"; 2 | 3 | export default class Home extends BaseView { 4 | constructor() { 5 | super(); 6 | this.setTitle('Home'); 7 | } 8 | 9 | getHTML() { 10 | return `

Home

11 |

Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dolorum corporis voluptatum earum reprehenderit. 12 | Eum, blanditiis illo ab culpa sequi nisi similique debitis adipisci accusamus? 13 | Ad tenetur culpa voluptatem quae a!

14 | ` 15 | } 16 | } -------------------------------------------------------------------------------- /vanlla-spa/src/app/views/Settings.js: -------------------------------------------------------------------------------- 1 | import BaseView from "./BaseView"; 2 | 3 | export default class Settings extends BaseView { 4 | constructor() { 5 | super(); 6 | this.setTitle('Settings'); 7 | } 8 | 9 | getHTML() { 10 | return `

Settings

` 11 | } 12 | } -------------------------------------------------------------------------------- /vanlla-spa/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayush013/ui-bootcamp/84fa564a869b4f6ea5ddffcdf4fc1bcab58985c2/vanlla-spa/src/assets/.gitkeep -------------------------------------------------------------------------------- /vanlla-spa/src/index.js: -------------------------------------------------------------------------------- 1 | import Router from './app/router'; 2 | import About from './app/views/About'; 3 | import Home from './app/views/Home'; 4 | import Settings from './app/views/Settings'; 5 | import './style.scss' 6 | 7 | const ROUTES = [ 8 | { url: '/', view: Home }, 9 | { url: '/about', view: About }, 10 | { url: '/settings', view: Settings }, 11 | ]; 12 | 13 | 14 | export const loadApp = () => { 15 | 16 | new Router(ROUTES); 17 | 18 | } 19 | 20 | loadApp(); -------------------------------------------------------------------------------- /vanlla-spa/src/style.scss: -------------------------------------------------------------------------------- 1 | /* Basic reset */ 2 | 3 | $dark: #1F2937; 4 | $white: #fff; 5 | 6 | html { 7 | font-size: 16px; 8 | } 9 | 10 | html, 11 | body { 12 | margin: 0; 13 | padding: 0; 14 | box-sizing: border-box; 15 | } 16 | 17 | body { 18 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 19 | "Ubuntu", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 20 | font-size: 1rem; 21 | font-weight: 400; 22 | min-height: 100vh; 23 | width: 100%; 24 | } 25 | 26 | *, 27 | *::after, 28 | *::before { 29 | box-sizing: inherit; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | 34 | nav.header { 35 | width: 100%; 36 | background-color: rgba($color: $dark, $alpha: 0.9); 37 | padding: 1rem; 38 | display: flex; 39 | align-items: center; 40 | gap: 1rem; 41 | 42 | a.nav-link { 43 | color: $white; 44 | font-size: 1.2rem; 45 | font-weight: 600; 46 | } 47 | } 48 | 49 | .router { 50 | padding: 2rem; 51 | } 52 | 53 | .mt-4 { 54 | margin-top: 1rem; 55 | } -------------------------------------------------------------------------------- /vanlla-spa/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | devServer: { 14 | contentBase: './dist', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: ['style-loader', 'css-loader', 'sass-loader'], 29 | } 30 | ], 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | title: 'Vanilla SPA - Development', 35 | template: 'index.html' 36 | }), 37 | ], 38 | }; -------------------------------------------------------------------------------- /vanlla-spa/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: 'production', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist'), 11 | clean: true, 12 | }, 13 | optimization: { 14 | runtimeChunk: 'single', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.html$/, 20 | loader: 'html-loader', 21 | }, 22 | { 23 | test: /\.(svg|png|jpg|gif)$/, 24 | type: 'asset/resource' 25 | }, 26 | { 27 | test: /\.(scss|css)$/, 28 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }), 34 | new HtmlWebpackPlugin({ 35 | title: 'Vanilla SPA - Production', 36 | template: 'index.html' 37 | }), 38 | ], 39 | }; --------------------------------------------------------------------------------