first commit

This commit is contained in:
2026-02-06 11:16:07 +03:00
commit 9a2e09864b
13 changed files with 561 additions and 0 deletions

3
src/icons/Check.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3334 4L6.00002 11.3333L2.66669 8" stroke="#F5F5F5" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 235 B

3
src/icons/Search.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 14L11.1 11.1M12.6667 7.33333C12.6667 10.2789 10.2789 12.6667 7.33333 12.6667C4.38781 12.6667 2 10.2789 2 7.33333C2 4.38781 4.38781 2 7.33333 2C10.2789 2 12.6667 4.38781 12.6667 7.33333Z" stroke="#1E1E1E" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 388 B

70
src/index.html Normal file
View File

@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>To Do List</title>
<link rel="stylesheet" href="styles/index.css">
</head>
<body>
<div class="todo data-js-todo">
<h1 class="todo_title">To Do List</h1>
<form class="todo_form" data-js-todo-new-task-form>
<div class="todo_field field">
<label class="field_label" for="new_task">
New task title
</label>
<input class="field_input" id="new_task" placeholder=" " autocomplete="off" data-js-todo-new-task-input>
</div>
<button class="button" type="submit">Add</button>
</form>
<form class="todo_form" data-js-todo-search-task-form>
<div class="todo_field field">
<label class="field_label" for="search_task">
Search task
</label>
<input class="field_input" id="search_task" placeholder=" " autocomplete="off" type="search" data-js-todo-search-task-input>
</div>
</form>
<div class="todo_info">
<div class="todo_total-tasks">
Total Tasks: <span data-js-todo-total-tasks>0</span>
</div>
<button class="todo_delete-all-button" type="button" data-js-todo-delete-all-button>
Delete All
</button>
</div>
<ul class="todo_list" data-js-todo-list>
<li class="todo_item todo-item" data-js-todo-item>
<input class="todo_item_checkbox" type="checkbox" id="todo-1" data-js-todo-item-checkbox>
<label class="todo-item_label" for="todo-1" data-js-todo-item-label>Todo 1</label>
<button class="todo_item_delete-button" type="button" aria-label="Delete" title="Delete" data-js-todo-item-delete-button>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 5L5 15M5 5L15 15" stroke="#757575" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</button>
</li>
<li class="todo_item todo-item" data-js-todo-item>
<input class="todo_item_checkbox" type="checkbox" id="todo-1" data-js-todo-item-checkbox>
<label class="todo-item_label" for="todo-1" data-js-todo-item-label>Todo 1</label>
<button class="todo_item_delete-button" type="button" aria-label="Delete" title="Delete" data-js-todo-item-delete-button>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 5L5 15M5 5L15 15" stroke="#757575" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</button>
</li>
</ul>
<div class="todo_empty-message" data-js-todo-empty-message>
</div>
</div>
<script src="main.js" type="module"></script>
</body>
</html>

0
src/main.js Normal file
View File

View File

@@ -0,0 +1,19 @@
.button {
display: flex;
align-items: center;
height: var(--input-height);
padding: 12px;
color: var(--color-gray-1);
background-color: var(--color-dark-2);
border: 1px solid var(--color-dark-2);
border-radius: var(--border-radius);
&:hover {
color: var(--color-dark-2);
background-color: transparent;
}
&:active {
scale: 1.05;
}
}

View File

@@ -0,0 +1,48 @@
.field {
position: relative;
&:has(.field_input:not(:placeholder-shown)) .field_label {
scale: 0.7;
translate: -1.75em -2.6em;
color: var(--color-black);
}
}
.field_label {
position: absolute;
top: 50%;
left: 17px;
color: var(--color-gray-3);
translate: 0 -50%;
}
.field_input {
--fieldInputPaddingX: 16px;
--fieldSearchInputIconSize: 16px;
width: 100%;
height: var(--input-height);
padding-inline: var(--fieldInputPaddingX);
background-color: transparent;
border: var(--border);
border-radius: var(--border-radius);
&:hover, &:focus {
color: var(--color-dark-2);
}
&:focus {
background-color: var(--colorgray-1);
outline: none;
}
&[type="search"] {
&:placeholder-shown{
padding-right: calc(
var(--fieldInputPaddingX)*2+var(--fieldSearchInputIconSize)
);
background-color: url('/src/icons/Search.svg');
background-position: calc(100% - var(--fieldInputPaddingX)) 50%;
background-size: var(--fieldSearchInputIconSize);
background-repeat: no-repeat;
}
}
}

View File

@@ -0,0 +1,99 @@
.todo_item {
display: flex;
align-items: center;
column-gap: 12px;
padding-left: 10px;
border: var(--border);
border-radius: var(--border-radius);
transition-duration: var(--transition-duration);
&:hover {
background-color: var(--color-gray-1);
}
&.is-disappearing {
opacity: 0;
translate: 0 -75%;
transition-duration: calc(var(--transition-duration)*2);
pointer-events: none;
& ~ .todo_item {
translate: 0 calc((100% + var(--todoListRowGap)) * -1);
transition-delay: var(--transition-duration);
transition-duration: var(--transition-duration);
pointer-events: none;
}
}
}
.todo_item_checkbox {
flex-shrink: 0;
appearance: none;
position: relative;
width: 20px;
height: 20px;
margin: 0;
border: 1px solid var(--color-gray-4);
border-radius: 4px;
&:not(:checked) {
&::after {
opacity: 0;
}
}
&:checked {
background-color: var(--color-dark-2);
border-color: var(--color-dark-2);
& + .todo-item_label {
color: var(--color-gray-4);
text-decoration: line-through;
}
}
&:after {
content: '';
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
width: 16px;
height: 16px;
background: url('/src/icons/Check.svg') center/contain no-repeat;
}
}
.todo-item_label {
width: 100%;
pointer-events: none;
}
.todo_item_delete-button {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
margin-left: auto;
padding: 0;
color: var(--color-gray-4);
background-color: transparent;
border: none;
border-radius: inherit;
&:hover {
color: var(--color-white);
background-color: var(--color-dark-1);
}
&:active {
scale: 1.05;
}
* {
pointer-events: none;
}
}

View File

@@ -0,0 +1,61 @@
.todo {
display: flex;
flex-direction: column;
row-gap: 24px;
width: 100%;
max-width: 404px;
min-height: 429px;
padding: 24px;
background-color: var(--color-white);
border: var(--border);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
}
.todo_title {
text-align: center;
font-size: 24px;
line-height: 1.2;
font-weight: 600;
letter-spacing: -2%;
}
.todo_form {
display: flex;
column-gap: 16px;
}
.todo_field {
width: 100%;
}
.todo_info {
display: flex;
justify-content: space-between;
align-items: center;
column-gap: 16px;
font-weight: 600;
}
.todo_delete-all-button {
padding: 0;
background-color: transparent;
border: none;
&:hover {
color: var(--color-gray-4);
}
&:not(.is-visiable) {
opacity: 0;
visibility: hidden;
}
}
.todo_list, .todo_empty-message {
&:empty {
display: none;
}
}
.todo_list {
--todoListRowGap: 8px;
display: grid;
row-gap: var(--todoListRowGap);
}
.todo_empty-message {
text-align: center;
color: var(--color-gray-4);
}

1
src/styles/fonts.css Normal file
View File

@@ -0,0 +1 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,100..900&display=swap');

17
src/styles/globals.css Normal file
View File

@@ -0,0 +1,17 @@
body {
display: flex;
justify-content: center;
align-items: start;
min-height: 100vh;
padding: 100px 30px;
font-family: "Inter", sans-serif;
background-color: var(--color-gray-1);
}
input, label, button {
transition-duration: var(--transition-duration);
}
:focus-visible {
outline: 2px dashed var(--color-black);
outline-offset: 4px;
transition-duration: 0s !important;
}

9
src/styles/index.css Normal file
View File

@@ -0,0 +1,9 @@
@import "normalize.css";
@import "fonts.css";
@import "veriables.css";
@import "globals.css";
@import "components/button.css";
@import "components/field.css";
@import "components/todo-item.css";
@import "components/todo.css";

210
src/styles/normalize.css vendored Normal file
View File

@@ -0,0 +1,210 @@
/**
Нормализация блочной модели
*/
*,
::before,
::after {
box-sizing: border-box;
}
/**
Убираем внутренние отступы слева тегам списков,
у которых есть атрибут class
*/
:where(ul, ol):where([class]) {
padding-left: 0;
}
/**
Убираем внешние отступы body и двум другим тегам,
у которых есть атрибут class
*/
body,
:where(blockquote, figure, fieldset):where([class]) {
margin: 0;
}
/**
Убираем внешние отступы вертикали нужным тегам,
у которых есть атрибут class
*/
:where(
h1,
h2,
h3,
h4,
h5,
h6,
p,
ul,
ol,
dl
):where([class]) {
margin-block: 0;
}
:where(dd[class]) {
margin-left: 0;
}
:where(fieldset[class]) {
padding: 0;
border: none;
}
/**
Убираем стандартный маркер маркированному списку,
у которого есть атрибут class
*/
:where(ul[class]) {
list-style: none;
}
:where(address[class]) {
font-style: normal;
}
/**
Обнуляем вертикальные внешние отступы параграфа,
объявляем локальную переменную для внешнего отступа вниз,
чтобы избежать взаимодействие с более сложным селектором
*/
p {
--paragraphMarginBottom: 24px;
margin-block: 0;
}
/**
Внешний отступ вниз для параграфа без атрибута class,
который расположен не последним среди своих соседних элементов
*/
p:where(:not([class]):not(:last-child)) {
margin-bottom: var(--paragraphMarginBottom);
}
/**
Упрощаем работу с изображениями и видео
*/
img,
video {
display: block;
max-width: 100%;
height: auto;
}
/**
Наследуем свойства шрифт для полей ввода
*/
input,
textarea,
select,
button {
font: inherit;
}
html {
/**
Пригодится в большинстве ситуаций
(когда, например, нужно будет "прижать" футер к низу сайта)
*/
height: 100%;
/**
Убираем скачок интерфейса по горизонтали
при появлении / исчезновении скроллбара
*/
scrollbar-gutter: stable;
/**
Плавный скролл
*/
scroll-behavior: smooth;
}
body {
/**
Пригодится в большинстве ситуаций
(когда, например, нужно будет "прижать" футер к низу сайта)
*/
min-height: 100%;
/**
Унифицированный интерлиньяж
*/
line-height: 1.5;
}
/**
Нормализация высоты элемента ссылки при его инспектировании в DevTools
*/
a:where([class]) {
display: inline-flex;
}
/**
Курсор-рука при наведении на элемент
*/
button,
label {
cursor: pointer;
}
/**
Убирает серую подсветку при тапе на мобильных устройствах (iOS/Android)
*/
button {
-webkit-tap-highlight-color: transparent;
}
/**
Приводим к единому цвету svg-элементы
(за исключением тех, у которых уже указан
атрибут fill со значением 'none' или начинается с 'url')
*/
:where([fill]:not(
[fill="none"],
[fill^="url"]
)) {
fill: currentColor;
}
/**
Приводим к единому цвету svg-элементы
(за исключением тех, у которых уже указан
атрибут stroke со значением 'none')
*/
:where([stroke]:not(
[stroke="none"],
[stroke^="url"]
)) {
stroke: currentColor;
}
/**
Чиним баг задержки смены цвета при взаимодействии с svg-элементами
*/
svg * {
transition-property: fill, stroke;
}
/**
Приведение рамок таблиц в классический 'collapse' вид
*/
:where(table) {
border-collapse: collapse;
border-color: currentColor;
}
/**
Удаляем все анимации и переходы для людей,
которые предпочитают их не использовать
*/
@media (prefers-reduced-motion: reduce) {
*,
::before,
::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

21
src/styles/veriables.css Normal file
View File

@@ -0,0 +1,21 @@
:root {
--color-white: #ffffff;
--color-black: #000000;
--color-dark-1: #1E1E1E;
--color-dark-2: #2C2C2C;
--color-gray-1: #F5F5F5;
--color-gray-2: #D9D9D9;
--color-gray-3: #B3B3B3;
--color-gray-4: #757575;
--border: 1px solid var(--color-gray-2);
--border-radius: 8px;
--box-shadow: 0px 4px 4px -4px rgba(12, 12, 13, 0.05), 0px 16px 32px -4px rgba(12, 12, 13, 0.1);
--input-height: 40px;
--transition-duration: 0.2s
}