feat: Добавление канбан доски
- просмотр/создание/изменение/удаление категорий; - просмотр/создание/изменение/удаление задач; - просмотр общей информации о канбан доске
This commit is contained in:
354
src/KBBoard.js
354
src/KBBoard.js
@@ -1,29 +1,355 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
|
import './css/Board.css';
|
||||||
|
|
||||||
const KBBoard = () => {
|
const KBBoard = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const [user, setUser] = useState(null);
|
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
const [info, setInfo] = useState({});
|
||||||
|
const [categories, setCategories] = useState([]);
|
||||||
|
|
||||||
|
const [crTask, setCrTask] = useState(false);
|
||||||
|
const [crCateg, setCrCateg] = useState(false);
|
||||||
|
const [edTask, setEdTask] = useState(false);
|
||||||
|
const [edCateg, setEdCateg] = useState(false);
|
||||||
|
const [delTask, setDelTask] = useState(false);
|
||||||
|
const [delCateg, setDelCateg] = useState(false);
|
||||||
|
|
||||||
|
const [categoryTitle, setCategoryTitle] = useState('');
|
||||||
|
const [categoryPosition, setCategoryPosition] = useState(null);
|
||||||
|
const [taskTitle, setTaskTitle] = useState('');
|
||||||
|
const [taskDescription, setTaskDescription] = useState('');
|
||||||
|
const [taskPosition, setTaskPosition] = useState(null);
|
||||||
|
const [taskCategory, setTaskCategory] = useState(null);
|
||||||
|
const [taskCategori, setTaskCategori] = useState(null);
|
||||||
|
const [editedTask, setEditedTask] = useState({});
|
||||||
|
const [editedCateg, setEditedCateg] = useState({});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const loadBoardData = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setError('')
|
||||||
|
const response = await axios.post('/api/boards/load', { id });
|
||||||
|
setInfo(response.data);
|
||||||
|
setCategories(response.data.categories);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.response?.data?.message === 'Token Error' || err.response?.data?.message === 'Invalid Token') {
|
||||||
|
setError('Вы не авторизованы');
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate('/login');
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
setError('Ошибка загрузки доски');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [id, navigate]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkSession = async () => {
|
if (id) loadBoardData();
|
||||||
try {
|
}, [id, loadBoardData]);
|
||||||
const response = await axios.post('/api/boards/load', { id });
|
|
||||||
} catch {}
|
|
||||||
};
|
const modalCrTask = (categori) => () => {
|
||||||
checkSession();
|
setCrTask(!crTask);
|
||||||
}, [id]);
|
setTaskCategori(categori);
|
||||||
|
setTaskTitle('');
|
||||||
|
setTaskDescription('');
|
||||||
|
}
|
||||||
|
const modalCrCateg = () => {
|
||||||
|
setCrCateg(!crCateg);
|
||||||
|
setCategoryTitle('');
|
||||||
|
}
|
||||||
|
const modalEditTask = (task, id_categ) => () => {
|
||||||
|
setEdTask(!edTask);
|
||||||
|
setTaskCategori(id_categ);
|
||||||
|
setEditedTask(task);
|
||||||
|
setTaskTitle(task.title);
|
||||||
|
setTaskDescription(task.description);
|
||||||
|
setTaskPosition(task.position);
|
||||||
|
setTaskCategory(task.category_id);
|
||||||
|
}
|
||||||
|
const modalEditCateg = (categ) => () => {
|
||||||
|
setEdCateg(!edCateg);
|
||||||
|
setEditedCateg(categ);
|
||||||
|
setCategoryTitle(categ.title);
|
||||||
|
setCategoryPosition(categ.position);
|
||||||
|
}
|
||||||
|
const modalDelTask = () => {
|
||||||
|
setDelTask(!delTask);
|
||||||
|
}
|
||||||
|
const modalDelCateg = () => {
|
||||||
|
setDelCateg(!delCateg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const createTask = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const newTask = { category_id: taskCategori, title: taskTitle, description: taskDescription };
|
||||||
|
axios.post('/api/boards/categories/tasks/create', newTask);
|
||||||
|
await loadBoardData();
|
||||||
|
modalCrTask(null)();
|
||||||
|
};
|
||||||
|
const createCategory = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const newCategory = { board_id: id, title: categoryTitle};
|
||||||
|
await axios.post('/api/boards/categories/create', newCategory);
|
||||||
|
await loadBoardData();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.response.data.message)
|
||||||
|
} finally {
|
||||||
|
modalCrCateg();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const editTask = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
var newTask = { id: editedTask.id, update_method: "title", value: taskTitle };
|
||||||
|
await axios.put('/api/boards/categories/tasks/update', newTask);
|
||||||
|
newTask = { id: editedTask.id, update_method: "description", value: taskDescription };
|
||||||
|
await axios.put('/api/boards/categories/tasks/update', newTask);
|
||||||
|
newTask = { id: editedTask.id, update_method: "category", value: Number(taskCategory) };
|
||||||
|
await axios.put('/api/boards/categories/tasks/update', newTask);
|
||||||
|
await loadBoardData();
|
||||||
|
modalEditTask({}, null)();
|
||||||
|
};
|
||||||
|
const editCategory = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const newCategory = { id: editedCateg.id, update_method: "title", value: categoryTitle};
|
||||||
|
await axios.put('/api/boards/categories/update', newCategory);
|
||||||
|
await loadBoardData();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.response.data.message)
|
||||||
|
} finally {
|
||||||
|
modalEditCateg({})();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const deleteCategory = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await axios.delete('/api/boards/categories/delete', { data: {id: editedCateg.id} });
|
||||||
|
await loadBoardData();
|
||||||
|
modalDelCateg();
|
||||||
|
modalEditCateg({})();
|
||||||
|
}
|
||||||
|
const deleteTask = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await axios.delete('/api/boards/categories/tasks/delete', { data: { id: editedTask.id } });
|
||||||
|
await loadBoardData();
|
||||||
|
modalDelTask();
|
||||||
|
modalEditTask({},null)();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="app-container">
|
||||||
<Header />
|
<Header />
|
||||||
<div className="">
|
<div className="page-container">
|
||||||
|
{
|
||||||
|
error && <div className="error">{error}</div>
|
||||||
|
}
|
||||||
|
<div className="inf-panel" >
|
||||||
|
<div className="row">
|
||||||
|
<h3>{info.title}</h3>
|
||||||
|
<p>
|
||||||
|
<strong>Участники: </strong> В разработке
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Владелец: </strong> {" "+info.owner?.display_name}
|
||||||
|
<img className="nav-avatar" src={info.owner?.avatar_url} alt=''></img>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="row">
|
||||||
|
<p><strong>Описание: </strong> {info.description ? info.description : 'Отсутствует'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="set-panel" >
|
||||||
|
<p>SetingPanel:В разработке</p>
|
||||||
|
</div>
|
||||||
|
<div className="board-panel" >
|
||||||
|
{categories.map((category) => (
|
||||||
|
<div className="categori" key={category.position}>
|
||||||
|
<button onClick={modalEditCateg(category)}><h3>{category.title}</h3></button>
|
||||||
|
<div className="categ-h">
|
||||||
|
<p>Позиция: {category.position}</p>
|
||||||
|
<p>Задач: {category.tasks.length}</p>
|
||||||
|
</div>
|
||||||
|
<div className='task create'>
|
||||||
|
<button onClick={modalCrTask(category.id)}>
|
||||||
|
Новая задача
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='task-list'>
|
||||||
|
{category.tasks.length > 0 ? (
|
||||||
|
category.tasks.map((task) => (
|
||||||
|
<button className='task' onClick={modalEditTask(task, category.id)} key={task.id}>
|
||||||
|
<div>{task.title}</div>
|
||||||
|
<div>{task.description}</div>
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p>Нет задач</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{categories.length < 10 ? (
|
||||||
|
<div className="categori create">
|
||||||
|
<div className="bib">
|
||||||
|
<button onClick={modalCrCateg}>
|
||||||
|
<div>
|
||||||
|
+
|
||||||
|
</div>
|
||||||
|
Новая категория
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{crCateg && (
|
||||||
|
<div className="confirm-modal">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div><h3>Новая категория</h3></div>
|
||||||
|
<form onSubmit={createCategory}>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={categoryTitle}
|
||||||
|
onChange={(e) => setCategoryTitle(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Подтвердить</button>
|
||||||
|
</form>
|
||||||
|
<button onClick={modalCrCateg}>Отменить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{crTask && (
|
||||||
|
<div className="confirm-modal">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div><h3>Новая задача</h3></div>
|
||||||
|
<form onSubmit={createTask}>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={taskTitle}
|
||||||
|
onChange={(e) => setTaskTitle(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={taskDescription}
|
||||||
|
onChange={(e) => setTaskDescription(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Подтвердить</button>
|
||||||
|
</form>
|
||||||
|
<button onClick={modalCrTask(null)}>Отменить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{edCateg && (
|
||||||
|
<div className="confirm-modal">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div><h3>Изменение категории</h3></div>
|
||||||
|
<form onSubmit={editCategory}>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={categoryTitle}
|
||||||
|
onChange={(e) => setCategoryTitle(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Подтвердить</button>
|
||||||
|
</form>
|
||||||
|
<button onClick={modalEditCateg({})}>Отменить</button>
|
||||||
|
<button className="Important-button" onClick={modalDelCateg}>Удалить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{edTask && (
|
||||||
|
<div className="confirm-modal">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div><h3>Изменение задачи</h3></div>
|
||||||
|
<form onSubmit={editTask}>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={taskTitle}
|
||||||
|
onChange={(e) => setTaskTitle(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={taskDescription}
|
||||||
|
onChange={(e) => setTaskDescription(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<label >Категория:</label>
|
||||||
|
<select value={taskCategory} onChange={(e) => setTaskCategory(e.target.value)}>
|
||||||
|
{categories.map((category) => (
|
||||||
|
<option key={category.position} value={category.id}>
|
||||||
|
{category.title}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Подтвердить</button>
|
||||||
|
</form>
|
||||||
|
<button onClick={modalEditTask({},null)}>Отменить</button>
|
||||||
|
<button className="Important-button" onClick={modalDelTask}>Удалить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{delTask && (
|
||||||
|
<div className="confirm-modal">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div><h3>Удаление задачи</h3></div>
|
||||||
|
<form onSubmit={deleteTask}>
|
||||||
|
<label >Вы точно хотите удалить задачу {editedTask.title}</label>
|
||||||
|
<button onClick={modalDelTask}>Отменить</button>
|
||||||
|
<button className="Important-button" type="submit">Подтвердить</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{delCateg && (
|
||||||
|
<div className="confirm-modal">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div><h3>Удаление категории</h3></div>
|
||||||
|
<form onSubmit={deleteCategory}>
|
||||||
|
<label >Вы точно хотите удалить эту категорию</label>
|
||||||
|
<button onClick={modalDelCateg}>Отменить</button>
|
||||||
|
<button className="Important-button" type="submit">Подтвердить</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
242
src/css/Board.css
Normal file
242
src/css/Board.css
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
.inf-panel{
|
||||||
|
flex: 0 0 auto;
|
||||||
|
background-color: #2b3245;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inf-panel strong {
|
||||||
|
margin-right: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inf-panel .row{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row p, .row h3{
|
||||||
|
display: flex;
|
||||||
|
margin: 0px 0px;
|
||||||
|
word-break: break-all;
|
||||||
|
align-items: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.row+.row p {
|
||||||
|
margin: 20px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.set-panel{
|
||||||
|
flex: 0 0 auto;
|
||||||
|
background-color: #2b3245;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-panel{
|
||||||
|
flex: 1 1 auto;
|
||||||
|
background-color: #2b3245;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
gap: 15px;
|
||||||
|
padding: 5px;
|
||||||
|
border: 5px dashed #2b3245;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categori{
|
||||||
|
flex: 1 1 auto; /* занимает всё свободное место */
|
||||||
|
overflow-y: auto; /* вертикальный скролл при переполнении */
|
||||||
|
overflow-x: hidden; /* горизонтальный скролл отключён */
|
||||||
|
border: 3px solid #3d4763;
|
||||||
|
background-color: #3d4763;
|
||||||
|
min-width: 300px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categori>button{
|
||||||
|
display: flex;
|
||||||
|
background-color: #0000;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.categori>button:hover{
|
||||||
|
background-color: #0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bib {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categ-h{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list {
|
||||||
|
flex: 1 1 auto; /* занимает всё свободное место */
|
||||||
|
overflow-y: auto; /* вертикальный скролл при переполнении */
|
||||||
|
overflow-x: hidden; /* горизонтальный скролл отключён */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 300px;
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-right: -4px; /* компенсируем отступ контейнера */
|
||||||
|
padding-right: 4px; /* создаём отступ для контента */
|
||||||
|
}
|
||||||
|
|
||||||
|
.task {
|
||||||
|
text-align: left;
|
||||||
|
margin: 0px;
|
||||||
|
|
||||||
|
color: #CAD1D8;
|
||||||
|
padding: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #2b3245;
|
||||||
|
border: 2px solid #617099;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.task+.task {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task:hover {
|
||||||
|
background-color: #3d4763;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create {
|
||||||
|
margin-top: 16px;
|
||||||
|
background-color: #fff0;
|
||||||
|
border: 3px dashed #3d4763;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
overflow-y: hidden;
|
||||||
|
button{
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff0;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #3d4763;
|
||||||
|
margin: 0px;
|
||||||
|
div{
|
||||||
|
font-size: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button:hover{
|
||||||
|
background-color: #3d4763;
|
||||||
|
color: #CAD1D8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.categori .create {
|
||||||
|
margin-top: 0px;
|
||||||
|
background-color: #fff0;
|
||||||
|
border: 3px dashed #617099;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
overflow-y: unset;
|
||||||
|
button{
|
||||||
|
height: 50px;
|
||||||
|
background-color: #fff0;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #617099;
|
||||||
|
margin: 0px;
|
||||||
|
div{
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button:hover{
|
||||||
|
background-color: #617099;
|
||||||
|
color: #CAD1D8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.categori.create {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task.create {
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Стили для полосы прокрутки (опционально) */
|
||||||
|
.task-list::-webkit-scrollbar{
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-panel::-webkit-scrollbar {
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list::-webkit-scrollbar-track ,
|
||||||
|
.board-panel::-webkit-scrollbar-track {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list::-webkit-scrollbar-thumb,
|
||||||
|
.board-panel::-webkit-scrollbar-thumb {
|
||||||
|
background: #aaa;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list::-webkit-scrollbar-thumb:hover,
|
||||||
|
.board-panel::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #888;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user