├── public
├── robots.txt
├── favicon.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── manifest.json
└── index.html
├── src
├── index.js
├── Alert.js
├── DarkModeToggle.js
├── DarkModeToggle.scss
├── List.js
├── Colors.js
├── Task.js
├── context.js
├── App.js
└── index.css
├── .gitignore
├── package.json
├── LICENSE.md
└── README.md
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilyasbelaoud/react-todo-app/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilyasbelaoud/react-todo-app/HEAD/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilyasbelaoud/react-todo-app/HEAD/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilyasbelaoud/react-todo-app/HEAD/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilyasbelaoud/react-todo-app/HEAD/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ilyasbelaoud/react-todo-app/HEAD/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import { AppProvider } from "./context";
6 |
7 | ReactDOM.render(
8 |
9 |
10 |
11 |
12 | ,
13 | document.getElementById("root")
14 | );
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "To-Do List",
3 | "name": "To-Do List",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "android-chrome-192x192.png",
12 | "sizes": "192x192",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "android-chrome-512x512.png",
17 | "sizes": "512x512",
18 | "type": "image/png"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | To-Do List
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/Alert.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useGlobalContext } from "./context";
3 |
4 | const Alert = ({ msg }) => {
5 | const { tasks, refContainer, alert, showAlert } = useGlobalContext();
6 |
7 | useEffect(() => {
8 | refContainer.current.style.left = `${alert.show ? "15px" : "-100%"}`;
9 |
10 | const timeout = setTimeout(() => {
11 | refContainer.current.style.left = "-100%";
12 | showAlert(false, alert.msg);
13 | }, 4000);
14 |
15 | return () => clearTimeout(timeout);
16 | }, [alert, refContainer, showAlert, tasks]);
17 | return (
18 |
19 | {msg}
20 |
21 | );
22 | };
23 |
24 | export default Alert;
25 |
--------------------------------------------------------------------------------
/src/DarkModeToggle.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import './DarkModeToggle.scss';
3 |
4 | const getUserTheme = () => {
5 | const theme = localStorage.getItem('theme') || 'dark';
6 | return theme === 'dark' ? true : false;
7 | };
8 |
9 | const DarkModeToggle = () => {
10 | const [isDarkMode, setIsDarkMode] = useState(getUserTheme());
11 |
12 | useEffect(() => {
13 | document.documentElement.className = `${isDarkMode && 'dark'}`;
14 | localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
15 | }, [isDarkMode])
16 |
17 | return (
18 |
19 | setIsDarkMode(e.target.checked)}/>
20 |
21 |
22 | )
23 | }
24 |
25 | export default DarkModeToggle
--------------------------------------------------------------------------------
/src/DarkModeToggle.scss:
--------------------------------------------------------------------------------
1 | .toggle-btn {
2 | position: absolute;
3 | top: 0;
4 | right: 20px;
5 |
6 | input[type="checkbox"] {
7 | visibility: hidden;
8 | &:checked + label {
9 | transform: rotate(360deg);
10 | background-color: #111;
11 | &:before {
12 | transform: translateX(45px);
13 | background-color: #f8ffaf;
14 | }
15 | }
16 | }
17 |
18 | label {
19 | display: flex;
20 | width: 90px;
21 | height: 45px;
22 | border: 3px solid #111;
23 | border-radius: 99em;
24 | position: relative;
25 | transition: transform .5s ease-in-out;
26 | transform-origin: 50% 50%;
27 | cursor: pointer;
28 |
29 | &:before {
30 | transition: transform 1s ease;
31 | transition-delay: .5s;
32 | content: "";
33 | display: block;
34 | position: absolute;
35 | width: 27px;
36 | height: 27px;
37 | background-color: #111;
38 | border-radius: 50%;
39 | top: 6px;
40 | left: 6px;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo-list",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.2",
7 | "@testing-library/react": "^12.1.3",
8 | "@testing-library/user-event": "^13.5.0",
9 | "react": "^17.0.2",
10 | "react-beautiful-dnd": "^13.1.0",
11 | "react-dom": "^17.0.2",
12 | "react-icons": "^4.3.1",
13 | "react-scripts": "5.0.0",
14 | "sass": "^1.49.9",
15 | "uuidv4": "^6.2.12",
16 | "web-vitals": "^2.1.4"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "CI= react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": [
26 | "react-app",
27 | "react-app/jest"
28 | ]
29 | },
30 | "browserslist": {
31 | "production": [
32 | ">0.2%",
33 | "not dead",
34 | "not op_mini all"
35 | ],
36 | "development": [
37 | "last 1 chrome version",
38 | "last 1 firefox version",
39 | "last 1 safari version"
40 | ]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Ilyas Belaoud
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/List.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Droppable } from "react-beautiful-dnd";
3 | import { useGlobalContext } from "./context";
4 | import Task from "./Task";
5 |
6 | const List = () => {
7 | const { tasks, filter } = useGlobalContext();
8 |
9 | let filtred = [...tasks];
10 |
11 | switch (filter) {
12 | case "all":
13 | filtred = [...tasks];
14 | break;
15 | case "completed":
16 | filtred = tasks.filter((task) => task.completed);
17 | break;
18 | case "uncompleted":
19 | filtred = tasks.filter((task) => !task.completed);
20 | break;
21 | default:
22 | filtred = [...tasks];
23 | break;
24 | }
25 |
26 | return (
27 |
28 | {(provided, snapshot) => (
29 |
34 | {filtred.map((task, i) => (
35 |
36 | ))}
37 | {provided.placeholder}
38 |
39 | )}
40 |
41 | );
42 | };
43 |
44 | export default List;
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Todo App
2 |
3 | A minimalistic, customisable todo app.
4 |
5 | ## Demo
6 |
7 | - Demo hosted on Netlify: [https://react-todo-list-9.netlify.app/](https://react-todo-list-9.netlify.app/)
8 |
9 | ## Features
10 |
11 | - Dark mode
12 | - Drag n drop tasks to reorder them
13 | - Tasks saved locally
14 | - Customisable task color
15 | - Edit task
16 | - Notification Box
17 | - Tablet & mobile friendly
18 |
19 | ## Built with
20 |
21 | - React Js, Css, Sass
22 | - React Hooks : useState, useEffect, useRef, useContext
23 | - react-beautiful-dnd, react-icons, uuidv4...
24 |
25 | ## License
26 |
27 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for more details
28 |
29 | ## Author
30 |
31 | - Instagram - [@ilyasbelaoud](https://www.instagram.com/ilyasbelaoud)
32 | - Twitter - [@ilyasbelaoud](https://www.twitter.com/ilyasbelaoud)
33 |
34 | ## Instructions
35 |
36 | First clone this repository.
37 | ```bash
38 | $ git clone https://github.com/ilyasbelaoud/react-todo-app.git
39 | ```
40 |
41 | Install dependencies. Make sure you already have [`nodejs`](https://nodejs.org/en/) & [`npm`](https://www.npmjs.com/) installed in your system.
42 | ```bash
43 | $ npm install # or yarn
44 | ```
45 |
46 | Run it
47 | ```bash
48 | $ npm start # or yarn start
49 | ```
--------------------------------------------------------------------------------
/src/Colors.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import { useGlobalContext } from "./context";
3 |
4 | const Colors = () => {
5 | const { location, setIsColorsOpen, tasks, setTasks } = useGlobalContext();
6 | const colorsRef = useRef(null);
7 |
8 | useEffect(() => {
9 | const { top, right } = location;
10 | colorsRef.current.style.left = `${right + 30}px`;
11 | colorsRef.current.style.top = `${top - 20}px`;
12 | }, [location]);
13 |
14 | const changeColor = (e) => {
15 | const color = e.target.style.backgroundColor;
16 | const { id } = location;
17 | setTasks(
18 | tasks.map((task) => {
19 | return task.id === id ? { ...task, color: color } : task;
20 | })
21 | );
22 | setIsColorsOpen(false);
23 | };
24 |
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | export default Colors;
41 |
--------------------------------------------------------------------------------
/src/Task.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Draggable } from "react-beautiful-dnd";
3 | import {
4 | MdCheckBoxOutlineBlank,
5 | MdCheckBox,
6 | MdDeleteOutline,
7 | MdOutlineColorLens,
8 | } from "react-icons/md";
9 | import { FiEdit } from "react-icons/fi";
10 | import { useGlobalContext } from "./context";
11 |
12 | const Task = ({ id, name, completed, color, index }) => {
13 | const { removeTask, toggleDone, editTask, showColors } = useGlobalContext();
14 |
15 | return (
16 |
17 | {(provided, snapshot) => (
18 |
32 | {name}
33 |
36 |
39 |
42 |
45 |
46 | )}
47 |
48 | );
49 | };
50 |
51 | export default Task;
52 |
--------------------------------------------------------------------------------
/src/context.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext, useRef } from "react";
2 |
3 | const getTasks = () => {
4 | const tasks = localStorage.getItem("tasks");
5 | return tasks ? JSON.parse(tasks) : [];
6 | };
7 |
8 | const AppContext = React.createContext(null);
9 |
10 | const AppProvider = ({ children }) => {
11 | const inputRef = useRef(null);
12 | const [tasks, setTasks] = useState(getTasks());
13 | const [alert, setAlert] = useState({ show: false, msg: "" });
14 | const [isEditing, setIsEditing] = useState(false);
15 | const [editId, setEditId] = useState(null);
16 | const [name, setName] = useState("");
17 | const [filter, setFilter] = useState("all");
18 | const [isColorsOpen, setIsColorsOpen] = useState(false);
19 | const [location, setLocation] = useState({});
20 | const refContainer = useRef(null);
21 |
22 | const removeTask = (id) => {
23 | setTasks(tasks.filter((task) => task.id !== id));
24 | showAlert(true, "Task Removed.");
25 | };
26 |
27 | const toggleDone = (id) => {
28 | setTasks(
29 | tasks.map((task) =>
30 | task.id === id ? { ...task, completed: !task.completed } : task
31 | )
32 | );
33 | showAlert(true, "Task State Changed.");
34 | };
35 |
36 | const editTask = (id) => {
37 | const { name } = tasks.find((task) => task.id === id);
38 | setIsEditing(true);
39 | setEditId(id);
40 | setName(name);
41 | inputRef.current.focus();
42 | };
43 |
44 | const showAlert = (show, msg) => {
45 | setAlert({ show, msg });
46 | };
47 |
48 | const showColors = (e, id) => {
49 | const { top, right } = e.target.getBoundingClientRect();
50 | setLocation({ top, right, id });
51 | setIsColorsOpen(true);
52 | };
53 |
54 | return (
55 |
82 | {children}
83 |
84 | );
85 | };
86 |
87 | const useGlobalContext = () => {
88 | return useContext(AppContext);
89 | };
90 |
91 | export { AppContext, AppProvider, useGlobalContext };
92 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import {FaGithub} from 'react-icons/fa'
3 | import { v4 as uuid } from "uuid";
4 | import { DragDropContext } from "react-beautiful-dnd";
5 | import List from "./List";
6 | import Alert from "./Alert";
7 | import { useGlobalContext } from "./context";
8 | import Colors from "./Colors";
9 | import DarkModeToggle from './DarkModeToggle';
10 |
11 | const App = () => {
12 | const {
13 | inputRef,
14 | tasks,
15 | setTasks,
16 | alert,
17 | showAlert,
18 | isEditing,
19 | setIsEditing,
20 | editId,
21 | setEditId,
22 | name,
23 | setName,
24 | filter,
25 | setFilter,
26 | isColorsOpen,
27 | setIsColorsOpen,
28 | } = useGlobalContext();
29 |
30 | const addTask = (e) => {
31 | e.preventDefault();
32 | if (!name) {
33 | showAlert(true, "Invalid Task Name!");
34 | } else if (name && isEditing) {
35 | setTasks(
36 | tasks.map((task) => {
37 | return task.id === editId ? { ...task, name: name } : task;
38 | })
39 | );
40 | setIsEditing(false);
41 | setEditId(null);
42 | setName("");
43 | showAlert(true, "Task Edited.");
44 | } else {
45 | const newTask = {
46 | id: uuid().slice(0, 8),
47 | name: name,
48 | completed: false,
49 | color: "#009688",
50 | };
51 | setTasks([...tasks, newTask]);
52 | showAlert(true, "Task Added.");
53 | setName("");
54 | }
55 | };
56 |
57 | const filterTasks = (e) => {
58 | setFilter(e.target.dataset["filter"]);
59 | };
60 |
61 | const deleteAll = () => {
62 | setTasks([]);
63 | showAlert(true, "Your list is clear!");
64 | };
65 |
66 | useEffect(() => {
67 | inputRef.current.focus();
68 | localStorage.setItem("tasks", JSON.stringify(tasks));
69 | }, [inputRef, tasks]);
70 |
71 | const handleDragEnd = (param) => {
72 | const srcI = param.source.index;
73 | const desI = param.destination?.index;
74 | if (desI) {
75 | const reOrdered = [...tasks];
76 | reOrdered.splice(desI, 0, reOrdered.splice(srcI, 1)[0]);
77 | setTasks(reOrdered);
78 | }
79 | };
80 |
81 | const hideColorsContainer = (e) => {
82 | // body.
83 | if (e.target.classList.contains("btn-colors")) return;
84 | setIsColorsOpen(false);
85 | };
86 |
87 | return (
88 | <>
89 |
90 | {isColorsOpen &&
}
91 | {alert &&
}
92 |
102 |
103 |
110 |
117 |
124 |
125 |
126 | {tasks.length > 0 ? (
127 |
128 | ) : (
129 | Your list is clear!
130 | )}
131 |
132 | {tasks.length > 2 && (
133 |
140 | )}
141 |
142 |
143 |
144 |
147 | >
148 | );
149 | };
150 |
151 | export default App;
152 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Nunito:wght@200;400;500&display=swap");
2 |
3 | :root {
4 | --bg-color: #94d8e3;
5 | --alt-bg-color: #0000004d;
6 | --font-color: #ffffff;
7 | --alt-font-color: #222222;
8 | --primary-color: #607d8b;
9 | --secondary-color: #93B5C6;
10 | }
11 | .dark {
12 | --bg-color: #003950;
13 | --alt-bg-color: #0000004d;
14 | --font-color: #ffffff;
15 | --alt-font-color: #222222;
16 | --primary-color: #008e9f;
17 | --secondary-color: #00e4ff;
18 | }
19 | * {
20 | margin: 0;
21 | padding: 0;
22 | box-sizing: border-box;
23 | }
24 | body {
25 | min-height: 100vh;
26 | background: var(--bg-color);
27 | color: var(--font-color);
28 | font-family: "Nunito", sans-serif;
29 | padding: 80px 0;
30 | position: relative;
31 | font-size: 100%;
32 | transition: all 0.3s linear;
33 | }
34 | .container {
35 | width: 80%;
36 | max-width: 900px;
37 | padding: 30px;
38 | margin: 0 auto;
39 | background: var(--alt-bg-color);
40 | border-radius: 20px;
41 | text-align: center;
42 | box-shadow: 0 0 20px 0px #00000045;
43 | }
44 | @media (max-width: 768px) {
45 | .container {
46 | padding: 20px;
47 | width: 90%;
48 | }
49 | }
50 | .container .head {
51 | width: 100%;
52 | display: grid;
53 | grid-template-columns: 70% 29%;
54 | gap: 1%;
55 | margin-bottom: 50px;
56 | }
57 | @media (max-width: 768px) {
58 | .container .head {
59 | grid-template-columns: 1fr;
60 | gap: 5px;
61 | }
62 | }
63 | .head input {
64 | padding: 13px 20px;
65 | outline: none;
66 | border: none;
67 | background-color: #eee;
68 | border-radius: 10px;
69 | font-size: 0.9rem;
70 | font-family: inherit;
71 | }
72 | button {
73 | cursor: pointer;
74 | background-color: var(--primary-color);
75 | padding: 10px 30px;
76 | border-radius: 10px;
77 | border: none;
78 | font-size: 0.9rem;
79 | font-weight: 500;
80 | font-family: inherit;
81 | color: var(--font-color);
82 | transition: all 0.3s ease;
83 | }
84 | button:hover {
85 | opacity: 0.7;
86 | }
87 | .filter {
88 | margin: 0 auto 40px;
89 | display: flex;
90 | justify-content: center;
91 | align-items: center;
92 | gap: 5px;
93 | }
94 | @media (max-width: 576px) {
95 | .filter {
96 | flex-direction: column;
97 | }
98 | }
99 | .filter button.active {
100 | background-color: var(--secondary-color);
101 | color: #333;
102 | }
103 | .tasks-wrapper {
104 | list-style: none;
105 | padding: 0 50px;
106 | display: flex;
107 | flex-direction: column;
108 | gap: 20px;
109 | color: var(--font-color);
110 | max-height: 400px;
111 | overflow-y: auto;
112 | }
113 | @media (max-width: 768px) {
114 | .tasks-wrapper {
115 | padding: 0 5px;
116 | gap: 5px;
117 | }
118 | }
119 | .task {
120 | background-color: var(--primary-color);
121 | padding: 4px 20px 5px 30px;
122 | border-radius: 10px;
123 | display: flex;
124 | gap: 5px;
125 | align-items: center;
126 | text-align: left;
127 | transition: all 0.5s ease;
128 | }
129 | .task p {
130 | font-size: 1rem;
131 | text-transform: capitalize;
132 | flex: 1;
133 | }
134 | .task button {
135 | background-color: transparent;
136 | color: var(--alt-font-color);
137 | width: 40px;
138 | height: 40px;
139 | border-radius: 50%;
140 | padding: 0;
141 | font-weight: bold;
142 | font-size: 1rem;
143 | display: flex;
144 | align-items: center;
145 | justify-content: center;
146 | transition: 0.7s;
147 | }
148 | .task button:hover {
149 | color: var(--primary-color);
150 | background-color: var(--alt-font-color);
151 | opacity: 0.85;
152 | border-radius: 10px;
153 | }
154 | .tasks-wrapper .task-done {
155 | opacity: 0.5;
156 | }
157 | .tasks-wrapper .task-done p {
158 | text-decoration: line-through;
159 | font-style: italic;
160 | }
161 | button.btn-delete-all {
162 | margin-top: 50px;
163 | background-color: #ba0f30;
164 | }
165 | .no-tasks {
166 | color: var(--font-color);
167 | }
168 | .alert {
169 | margin-bottom: 1rem;
170 | width: 230px;
171 | height: 80px;
172 | display: grid;
173 | align-items: center;
174 | text-align: center;
175 | font-size: 0.9rem;
176 | color: var(--alt-font-color);
177 | border-radius: 0.25rem;
178 | letter-spacing: var(--spacing);
179 | text-transform: capitalize;
180 | background: #eee;
181 | position: absolute;
182 | bottom: 0;
183 | left: -100%;
184 | transition: all 0.5s cubic-bezier(0.6, -0.28, 0.74, 0.05);
185 | box-shadow: 0 0 20px 0px #00000045;
186 | }
187 | .color-container {
188 | position: absolute;
189 | width: 100px;
190 | height: 80px;
191 | display: grid;
192 | grid-template-columns: repeat(3, 1fr);
193 | background: #eee;
194 | padding: 3px;
195 | gap: 3px;
196 | border-radius: 10px;
197 | z-index: 3;
198 | }
199 | .color-container > span {
200 | border-radius: 5px;
201 | }
202 | ::-webkit-scrollbar {
203 | width: 0.5rem;
204 | border-radius: 10px;
205 | }
206 | ::-webkit-scrollbar-track {
207 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
208 | border-radius: 10px;
209 | }
210 | ::-webkit-scrollbar-thumb {
211 | background-color: #00b3b3;
212 | outline: 0px inset #7fffd4;
213 | border-radius: 10px;
214 | height: 50px;
215 | }
216 | .preventClick {
217 | pointer-events: none;
218 | }
219 | .footer {
220 | position: absolute;
221 | bottom: 0;
222 | width: 100%;
223 | text-align: center;
224 | margin: 10px auto;
225 | }
226 | .github {
227 | font-size: 2rem;
228 | color: darkcyan;
229 | }
--------------------------------------------------------------------------------