├── .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 |
+ Add Event
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
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 |
23 |
27 |
28 |
29 |
30 |
38 |
39 |
40 |
41 |
43 |
44 |
53 |
54 |
56 |
58 | Save
60 |
61 |
62 |
63 |
64 |
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 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
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 |
14 |
15 |
32 |
33 |
34 |
37 |
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 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
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 | Load More
20 |
21 |
22 |
23 |
24 |
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 | + Create Task
17 |
18 |
19 |
20 |
Backlog
21 |
23 |
24 |
25 |
26 |
In Progress
27 |
29 |
30 |
35 |
40 |
41 |
42 |
48 |
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 |
34 |
52 |
53 |
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 |
16 |
22 |
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 |
21 |
22 |
23 |
24 |
25 |
27 |
28 |
29 |
31 |
33 |
34 |
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 | };
--------------------------------------------------------------------------------