├── public
├── favicon.png
├── manifest.json
└── index.html
├── src
├── assets
│ ├── desktop-preview.jpg
│ └── images
│ │ ├── bg-desktop-dark.jpg
│ │ ├── bg-mobile-dark.jpg
│ │ ├── bg-mobile-light.jpg
│ │ ├── bg-desktop-light.jpg
│ │ ├── icon-check.svg
│ │ ├── icon-cross.svg
│ │ ├── icon-moon.svg
│ │ └── icon-sun.svg
├── index.js
├── styles
│ ├── _mixins.scss
│ ├── _vars.scss
│ ├── _themes.scss
│ └── App.scss
├── components
│ ├── Header.js
│ ├── Form.js
│ ├── Filter.js
│ ├── TodoItem.js
│ └── TodoList.js
├── data
│ └── data.json
└── App.js
├── .gitignore
├── package.json
└── README.md
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mileine/frontendmentor-todo-app-react/HEAD/public/favicon.png
--------------------------------------------------------------------------------
/src/assets/desktop-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mileine/frontendmentor-todo-app-react/HEAD/src/assets/desktop-preview.jpg
--------------------------------------------------------------------------------
/src/assets/images/bg-desktop-dark.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mileine/frontendmentor-todo-app-react/HEAD/src/assets/images/bg-desktop-dark.jpg
--------------------------------------------------------------------------------
/src/assets/images/bg-mobile-dark.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mileine/frontendmentor-todo-app-react/HEAD/src/assets/images/bg-mobile-dark.jpg
--------------------------------------------------------------------------------
/src/assets/images/bg-mobile-light.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mileine/frontendmentor-todo-app-react/HEAD/src/assets/images/bg-mobile-light.jpg
--------------------------------------------------------------------------------
/src/assets/images/bg-desktop-light.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mileine/frontendmentor-todo-app-react/HEAD/src/assets/images/bg-desktop-light.jpg
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(, document.querySelector('#root'));
--------------------------------------------------------------------------------
/src/assets/images/icon-check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [],
5 | "start_url": ".",
6 | "display": "standalone",
7 | "theme_color": "#000000",
8 | "background_color": "#ffffff"
9 | }
--------------------------------------------------------------------------------
/src/styles/_mixins.scss:
--------------------------------------------------------------------------------
1 | $mobile-width: 580px;
2 |
3 | @mixin mobile {
4 | @media (max-width: #{$mobile-width}) {
5 | @content
6 | }
7 | };
8 |
9 | @mixin tablet {
10 | @media (min-width: #{$mobile-width + 1}) {
11 | @content
12 | }
13 | }
--------------------------------------------------------------------------------
/src/assets/images/icon-cross.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/icon-moon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.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 | .eslintcache
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
--------------------------------------------------------------------------------
/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ReactComponent as LightIcon} from '../assets/images/icon-sun.svg'
3 | import { ReactComponent as DarkIcon} from '../assets/images/icon-moon.svg'
4 |
5 | const Header = ({ theme, toggleTheme }) => {
6 | return (
7 |
8 |
TODO
9 |
12 |
13 | )
14 | }
15 |
16 | export default Header
--------------------------------------------------------------------------------
/src/assets/images/icon-sun.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/data/data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "text": "Complete online JavaScript course",
5 | "checked": true
6 | },
7 | {
8 | "id": 2,
9 | "text": "Jog around the park 3x",
10 | "checked": false
11 | },
12 | {
13 | "id": 3,
14 | "text": "10 minutes meditation",
15 | "checked": false
16 | },
17 | {
18 | "id": 4,
19 | "text": "Read for 1 hour",
20 | "checked": false
21 | },
22 | {
23 | "id": 5,
24 | "text": "Pick up groceries",
25 | "checked": false
26 | },
27 | {
28 | "id": 6,
29 | "text": "Complete Todo App Frontend Mentor",
30 | "checked": false
31 | }
32 | ]
--------------------------------------------------------------------------------
/src/styles/_vars.scss:
--------------------------------------------------------------------------------
1 | $bright-blue: hsl(220, 98%, 61%);
2 | $check-background: linear-gradient(135deg, hsl(192, 100%, 67%),hsl(280, 87%, 65%));
3 |
4 | /* Light Theme */
5 |
6 | $very-light-gray: hsl(0, 0%, 98%);
7 | $very-light-grayish-blue: hsl(236, 33%, 92%);
8 | $light-grayish-blue: hsl(233, 11%, 84%);
9 | $dark-grayish-blue: hsl(236, 9%, 61%);
10 | $very-dark-grayish-blue: hsl(235, 19%, 35%);
11 |
12 | /* Dark Theme */
13 |
14 | $very-dark-blue: hsl(235, 21%, 11%);
15 | $very-dark-desaturated-blue: hsl(235, 24%, 19%);
16 | $light-grayish-blue: hsl(234, 39%, 85%);
17 | $light-grayish-blue-hover: hsl(236, 33%, 92%);
18 | $dark-grayish-blue: hsl(234, 11%, 52%);
19 | $very-dark-grayish-blue: hsl(233, 14%, 35%);
20 | $very-dark-grayish-blue-border: hsl(237, 14%, 26%);
21 |
--------------------------------------------------------------------------------
/src/components/Form.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 |
3 | const Form = ({ nextId, addItem }) => {
4 | const [text, setText] = useState('')
5 | const onSubmit = (evt) => {
6 | evt.preventDefault()
7 | const newValue = {
8 | "id": nextId,
9 | "text": text,
10 | "checked": false
11 | }
12 | addItem(newValue)
13 | setText('')
14 | }
15 |
16 | return (
17 |
23 | )
24 | }
25 |
26 | export default Form
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "node-sass": "4.14.1",
10 | "react": "^17.0.1",
11 | "react-beautiful-dnd": "^13.0.0",
12 | "react-dom": "^17.0.1",
13 | "react-scripts": "4.0.1",
14 | "web-vitals": "^0.2.4"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test",
20 | "eject": "react-scripts eject"
21 | },
22 | "eslintConfig": {
23 | "extends": [
24 | "react-app",
25 | "react-app/jest"
26 | ]
27 | },
28 | "browserslist": {
29 | "production": [
30 | ">0.2%",
31 | "not dead",
32 | "not op_mini all"
33 | ],
34 | "development": [
35 | "last 1 chrome version",
36 | "last 1 firefox version",
37 | "last 1 safari version"
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/Filter.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Filter = ({ selectedFilter, updateFilter }) => {
4 | const handleClick = (selected) => {
5 |
6 | updateFilter(selected)
7 | }
8 | return(
9 |
10 |
18 |
26 |
34 |
35 | )
36 | }
37 |
38 | export default Filter
--------------------------------------------------------------------------------
/src/components/TodoItem.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { ReactComponent as CompleteIcon } from '../assets/images/icon-cross.svg'
3 | import { Draggable } from 'react-beautiful-dnd'
4 |
5 | const TodoItem = ({ index, id, checked, updateItem, deleteItem, text }) => {
6 | const [completed, setCompleted] = useState(checked)
7 |
8 | const updateItemStatus = (id) => {
9 | updateItem(id)
10 | setCompleted(!completed)
11 | }
12 |
13 | const onKeyPress = (event, id) => {
14 | if (event.key === 'Enter') updateItem(id)
15 | setCompleted(!completed)
16 | }
17 |
18 | return (
19 |
20 | {(provided) => (
21 | onKeyPress(event, id)}
28 | >
29 |
30 |
updateItemStatus(id)}>
31 |
32 |
33 |
updateItemStatus(id)}>{text}
34 |
35 |
38 |
39 | )}
40 |
41 | )
42 | }
43 |
44 | export default TodoItem
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Todo App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/styles/_themes.scss:
--------------------------------------------------------------------------------
1 | @import 'vars';
2 |
3 | /* theme settings */
4 | $themes: (
5 | "light": (
6 | "color-bg": #fff,
7 | "color-list-bg": #fff,
8 | "color-list-text": $dark-grayish-blue,
9 | "color-text-light": $dark-grayish-blue,
10 | "color-list-text-completed": $light-grayish-blue,
11 | "color-input-placeholder": $dark-grayish-blue,
12 | "color-input": $very-dark-grayish-blue,
13 | "color-path": $very-dark-grayish-blue,
14 | "color-btn":$dark-grayish-blue,
15 | "color-btn-hover": $very-dark-grayish-blue,
16 | "color-btn-active": $bright-blue,
17 | "border": 1px solid hsl(236, 33%, 92%),
18 | "shadow": 0px 35px 50px #C2C3D680,
19 | ),
20 | "dark": (
21 | "color-bg": $very-dark-blue,
22 | "color-list-bg": $very-dark-desaturated-blue,
23 | "color-list-text": $light-grayish-blue,
24 | "color-text-light": $very-dark-grayish-blue,
25 | "color-list-text-completed": $very-dark-grayish-blue,
26 | "color-input-placeholder": $very-dark-grayish-blue,
27 | "color-input": $light-grayish-blue-hover,
28 | "color-path": $light-grayish-blue-hover,
29 | "color-btn": $dark-grayish-blue,
30 | "color-btn-hover": $light-grayish-blue-hover,
31 | "color-btn-active":$bright-blue,
32 | "border": 1px solid hsl(237, 14%, 26%),
33 | "shadow": 0px 35px 50px #00000080,
34 | )
35 | );
36 |
37 | @mixin apply-theme-color($key, $color) {
38 | @each $theme-name, $theme-color in $themes {
39 | .#{$theme-name} & {
40 | #{$key}: map-get(map-get($themes, $theme-name), $color )
41 | }
42 | }
43 | }
44 |
45 | $theme-list: light dark;
46 |
47 | @mixin backgroundImg {
48 | @each $theme in $theme-list {
49 | .#{$theme} & {
50 | background: url('/assets/images/bg-desktop-'+$theme+'.jpg');
51 | background-size: 100% auto;
52 | background-repeat: no-repeat;
53 | @include mobile {
54 | background: url('/assets/images/bg-mobile-'+$theme+'.jpg');
55 | background-size: 100% auto;
56 | background-repeat: no-repeat;
57 | }
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Frontend Mentor - Todo App
2 |
3 | This project is a challenge by [Frontend Mentor](https://www.frontendmentor.io).
4 |
5 |
6 | 
7 |
8 |
9 | ## Challenge description
10 |
11 | Users should be able to:
12 |
13 | - View the optimal layout for the app depending on their device's screen size
14 | - See hover states for all interactive elements on the page
15 | - Add new todos to the list
16 | - Mark todos as complete
17 | - Delete todos from the list
18 | - Filter by all/active/complete todos
19 | - Clear all completed todos
20 | - Toggle light and dark mode
21 | - Bonus: Drag and drop to reorder items on the list
22 |
23 | View the full description [here](https://www.frontendmentor.io/challenges/todo-app-Su1_KokOW).
24 |
25 | ## About the app
26 |
27 | ### Features
28 |
29 | All the functionalities described on *Challenge description* section were implemented.
30 |
31 | The additional behaviours below were also implemented:
32 |
33 | - All the basic functionalities are accessible on the keyboard
34 | - The task lists is saved on localstorage
35 |
36 | ### Tools
37 |
38 | - ReactJS (Create React App)
39 | - React Beautiful DnD
40 | - SASS
41 |
42 |
43 | ### Known issues
44 |
45 | The [report](https://www.frontendmentor.io/solutions/todo-app-using-reactjs-I9UzE1vG2/report) generated on Frontend Mentor website lists 15 HTML issues that were not fixed.
46 |
47 |
48 | ## How to run
49 |
50 | 1. Clone this repo.
51 |
52 | 2. Run `yarn install`.
53 |
54 | 3. Run `yarn start`.
55 |
56 |
57 | ## Demo
58 |
59 | View [demo](https://frontendmentor-todo-app-react.vercel.app/).
60 |
61 | ## References
62 |
63 | Here's a list of useful resources I learned a lot from when working on this challenge:
64 |
65 | - [Gradient Borders in CSS](https://css-tricks.com/gradient-borders-in-css/)
66 | - [How I Theme My React App With Sass](https://www.webtips.dev/how-i-theme-my-react-app-with-sass)
67 | - [Sass Essential Training](https://www.linkedin.com/learning-login/share?forceAccount=false&redirect=https%3A%2F%2Fwww.linkedin.com%2Flearning%2Fsass-essential-training%3Ftrk%3Dshare_ent_url)
68 | - [Sass mixin to have multiple theme based background images](https://medium.com/@traumastronaut/sass-mixin-to-have-multiple-theme-based-background-images-e278439299e6)
69 | - [Session Storage and Local Storage in React](https://www.robinwieruch.de/local-storage-react)
70 | - [How to Add Drag and Drop in React with React Beautiful DnD](https://www.freecodecamp.org/news/how-to-add-drag-and-drop-in-react-with-react-beautiful-dnd/)
--------------------------------------------------------------------------------
/src/components/TodoList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Filter from './Filter'
3 | import TodoItem from './TodoItem'
4 | import { DragDropContext, Droppable } from 'react-beautiful-dnd'
5 |
6 | const TodoList = ({ items, updateItem, selectedFilter, updateFilter, updateItems, deleteItem, clearCompleted }) => {
7 |
8 | const renderedList = items.map(({id, text, checked}, index) => {
9 | return (
10 |
11 | )
12 | })
13 |
14 | const renderedListActive = items.map(({id, text, checked}, index) => {
15 | if (checked) return ''
16 | return (
17 |
18 | )
19 | })
20 |
21 | const renderedListCompleted = items.map(({id, text, checked}, index) => {
22 | if (!checked) return ''
23 | return (
24 |
25 | )
26 | })
27 |
28 | const renderedFilteredList = () => {
29 | if (selectedFilter === 'active') return renderedListActive
30 | else if (selectedFilter === 'completed') return renderedListCompleted
31 | else return renderedList
32 | }
33 |
34 | const getItemsLeft = () => {
35 | let completedItems = 0
36 | let total = items.length
37 | completedItems = items.filter(item => item.checked).length
38 | return total - completedItems
39 | }
40 |
41 | const handleOnDragEnd = (result) => {
42 | if (!result.destination) return;
43 | const updatedList = Array.from(items)
44 | const [reorderedItem] = updatedList.splice(result.source.index, 1)
45 | updatedList.splice(result.destination.index, 0, reorderedItem)
46 | updateItems(updatedList)
47 | }
48 |
49 | return (
50 |
51 |
52 |
53 | {(provided) => (
54 |
55 | { renderedFilteredList() }
56 | {provided.placeholder}
57 |
58 | )}
59 |
60 |
61 |
62 | { getItemsLeft() } items left
63 |
64 |
65 |
66 |
67 | )
68 | }
69 |
70 | export default TodoList
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import './styles/App.scss';
3 | import Header from './components/Header'
4 | import Form from './components/Form'
5 | import Filter from './components/Filter'
6 | import TodoList from './components/TodoList';
7 | import todoList from './data/data.json'
8 |
9 | const App = () => {
10 | const MY_TASKS = localStorage.getItem('myTasks') ? JSON.parse(localStorage.getItem('myTasks')) : [...todoList]
11 | const [theme, setTheme] = useState('light')
12 | const [items, setItems] = useState(MY_TASKS)
13 | const [selectedFilter, setSelectedFilter] = useState('all')
14 | const [nextId, setNextId] = useState(items.length + 2)
15 |
16 | useEffect(() => {
17 | document.body.className = theme
18 | localStorage.setItem('myTasks', JSON.stringify(items))
19 | }, [theme, items])
20 |
21 | const toggleTheme = () => {
22 | setTheme(theme === 'light'? 'dark': 'light')
23 | }
24 |
25 | const addItem = (newItem) => {
26 | setItems(items => [...items, newItem])
27 | setNextId(nextId + 1)
28 | }
29 |
30 | const updateItem = (id) => {
31 | const newList = [...items]
32 |
33 | const editItem = newList.find((item => item.id === id))
34 |
35 | if (editItem) {
36 | editItem.checked = !editItem.checked
37 | }
38 |
39 | setItems(newList)
40 | }
41 |
42 | const updateFilter = (selected) => {
43 | setSelectedFilter(selected)
44 | }
45 |
46 | const updateItems = (updatedList) => {
47 | setItems(updatedList)
48 | }
49 |
50 | const deleteItem = (id) => {
51 | const newList = [...items]
52 | setItems(newList.filter(item => item.id !== id))
53 | }
54 |
55 | const clearCompleted = () => {
56 | const itemsArray = [...items]
57 | setItems(itemsArray.filter(item => !item.checked))
58 | }
59 |
60 | return (
61 |
76 | );
77 | }
78 |
79 | export default App
80 |
--------------------------------------------------------------------------------
/src/styles/App.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@400;700&display=swap');
2 | @import 'vars';
3 | @import 'mixins';
4 | @import 'themes';
5 |
6 | * {
7 | padding: 0;
8 | margin: 0;
9 | box-sizing: border-box;
10 | font-family: 'Josefin Sans', sans-serif;
11 | }
12 |
13 | ul {
14 | list-style: none;
15 | }
16 |
17 | button, li {
18 | outline-color: $bright-blue;
19 | }
20 |
21 | /* LAYOUT
22 | --------------------*/
23 | .container {
24 | min-height: 100vh;
25 | width: 100vw;
26 | @include backgroundImg;
27 | @include apply-theme-color('background-color', 'color-bg');
28 | }
29 |
30 | .main {
31 | max-width: 540px;
32 | margin: auto;
33 | display: flex;
34 | flex-direction: column;
35 | @include mobile {
36 | max-width: 90%;
37 | }
38 | }
39 |
40 | /* HEADER
41 | --------------------*/
42 |
43 | .header {
44 | color: #fff;
45 | display: flex;
46 | justify-content: space-between;
47 | margin-top: 70px;
48 | margin-bottom: 50px;
49 | .title {
50 | letter-spacing: 15px;
51 | font-size: 40px;
52 | font-weight: 700;
53 | }
54 | }
55 |
56 | .btn-switch {
57 | background-color: transparent;
58 | cursor: pointer;
59 | border: none;
60 | width: 25px;
61 | height: 25px;
62 | }
63 |
64 | .attribution {
65 | position: fixed;
66 | padding: 10px;
67 | text-align: center;
68 | font-size: 12px;
69 | width: 100%;
70 | bottom: 0;
71 | display: flex;
72 | justify-content: center;
73 | box-shadow: 0px -5px 5px rgba(68, 68, 68, 0.1);
74 | @include apply-theme-color('background-color', 'color-bg');
75 | @include apply-theme-color('color', 'color-list-text');
76 | a {
77 | color: $bright-blue;
78 | }
79 | > p {
80 | letter-spacing: .4px;
81 | opacity: 1;
82 | }
83 | .love {
84 | margin: 0 5px;
85 | }
86 | }
87 |
88 | /* Form
89 | --------------------*/
90 |
91 | .input-wrapper {
92 | width: 100%;
93 | display: flex;
94 | align-items: center;
95 | padding: 20px;
96 | border-radius: 5px;
97 | @include apply-theme-color('background', 'color-list-bg');
98 | @include apply-theme-color('box-shadow', 'shadow');
99 | input {
100 | &:focus {
101 | @include apply-theme-color('color', 'color-input');
102 | }
103 | &::placeholder {
104 | @include apply-theme-color('color', 'color-input-placeholder');
105 | }
106 | }
107 | }
108 |
109 | input {
110 | font-size: 18px;
111 | letter-spacing: -0.25px;
112 | background-color: transparent;
113 | border-color: transparent;
114 | width: 100%;
115 | position: relative;
116 | &::placeholder {
117 | color: #9495A5;
118 | }
119 | }
120 |
121 | input:focus {
122 | outline: pink;
123 | }
124 |
125 | .input-check {
126 | outline: none;
127 | display: inline-block;
128 | border-radius: 50%;
129 | height: 24px;
130 | width: 24px;
131 | margin-right: 10px;
132 | background: transparent;
133 | @include apply-theme-color('border', 'border');
134 | }
135 |
136 | /* LIST
137 | --------------------*/
138 | .todo-list-wrapper {
139 | display: flex;
140 | flex-direction: column;
141 | justify-content: space-between;
142 | max-width: 540px;
143 | margin-top: 25px;
144 | border-radius: 5px;
145 | min-height: 300px;
146 | @include apply-theme-color('background', 'color-list-bg');
147 | @include apply-theme-color('box-shadow', 'shadow');
148 | }
149 |
150 | .todo-item{
151 | position: relative;
152 | cursor: pointer;
153 | width: 100%;
154 | display: flex;
155 | align-items: center;
156 | justify-content: space-between;
157 | padding: 20px;
158 | font-size: 18px;
159 | letter-spacing: -0.25px;
160 | @include apply-theme-color('color', 'color-list-text');
161 | @include apply-theme-color('border-bottom', 'border');
162 | &.completed {
163 | @include apply-theme-color('color', 'color-list-text-completed');
164 | text-decoration: line-through;
165 | }
166 | &:first-of-type {
167 | border-top-left-radius: 5px;
168 | border-top-right-radius: 5px;
169 | }
170 | .description {
171 | display: flex;
172 | align-items: center;
173 | }
174 | .item-check-wrapper {
175 | display: flex;
176 | align-items: center;
177 | justify-content: center;
178 | width: 24px;
179 | height: 24px;
180 | border-radius: 50%;
181 | margin-right: 10px;
182 | }
183 | .item-check {
184 | display: inline-block;
185 | border-radius: 50%;
186 | height: 24px;
187 | width: 24px;
188 | background: transparent;
189 | &.completed {
190 | background: url('../assets/images/icon-check.svg'), linear-gradient(135deg, hsl(192, 100%, 67%),hsl(280, 87%, 65%));
191 | background-repeat: no-repeat;
192 | background-position: center;
193 | }
194 | }
195 | .delete-icon {
196 | transition: opacity 0.5s ease-in-out;
197 | background: transparent;
198 | border: none;
199 | opacity: 0;
200 | cursor: pointer;
201 | & > path {
202 | @include apply-theme-color('fill', 'color-path');
203 | }
204 | &:focus {
205 | opacity: 1;
206 | }
207 | }
208 | &:not(:hover) {
209 | .item-check:not(.completed) {
210 | @include apply-theme-color('border', 'border');
211 | }
212 | }
213 | &:hover,
214 | &:focus {
215 | .delete-icon{
216 | opacity: 1;
217 | }
218 | .item-check-wrapper {
219 | background: linear-gradient(135deg, hsl(192, 100%, 67%),hsl(280, 87%, 65%));
220 | .item-check {
221 | &:not(.completed) {
222 | width: 22px;
223 | height: 22px;
224 | @include apply-theme-color('background', 'color-list-bg');
225 | }
226 | }
227 | }
228 | }
229 | }
230 |
231 | .todo-list-footer {
232 | padding: 20px;
233 | display: flex;
234 | justify-content: space-between;
235 | align-items: center;
236 | .items-left {
237 | display: block;
238 | width: 100px;
239 | @include apply-theme-color('color', 'color-btn');
240 | font-size: 14px;
241 | letter-spacing: -0.25px;
242 | }
243 | .filter-wrapper {
244 | @include mobile {
245 | display: none;
246 | }
247 | }
248 | }
249 |
250 | .btn-clear {
251 | background: transparent;
252 | border: none;
253 | cursor: pointer;
254 | padding: 10px;
255 | @include apply-theme-color('color', 'color-btn');
256 | &:focus {
257 | border: none;
258 | }
259 | &:hover {
260 | @include apply-theme-color('color', 'color-btn-hover');
261 | }
262 | }
263 |
264 | /* FILTER
265 | --------------------*/
266 | .filter-wrapper {
267 | display: flex;
268 | width: 166px;
269 | justify-content: space-between;
270 | .filter-option{
271 | cursor: pointer;
272 | font-size: 14px;
273 | font-weight: bold;
274 | background: transparent;
275 | border: none;
276 | padding: 10px;
277 | @include apply-theme-color('color', 'color-text-light');
278 | &:focus {
279 | border: none;
280 | }
281 | &:hover {
282 | @include apply-theme-color('color', 'color-btn-hover');
283 | }
284 | &.selected {
285 | color: #3A7CFD;
286 | }
287 | }
288 | @include mobile {
289 | width: 100%;
290 | padding: 20px;
291 | margin-top: 15px;
292 | border-radius: 5px;
293 | .filter-option {
294 | margin-right: 20px;
295 | }
296 | justify-content: center;
297 | @include apply-theme-color('background', 'color-list-bg');
298 | @include apply-theme-color('box-shadow', 'shadow');
299 | }
300 | }
301 |
302 | .filter-mobile {
303 | @include tablet {
304 | display: none;
305 | }
306 | }
307 |
308 | /* INFO
309 | --------------------*/
310 | .dnd-info {
311 | display: block;
312 | margin: 40px auto 80px auto;
313 | @include apply-theme-color('color', 'color-text-light');
314 | }
--------------------------------------------------------------------------------