ref: Remove board types, refactoring

This commit is contained in:
Maksim Eltyshev
2022-12-26 21:10:50 +01:00
parent d39da61295
commit 5cd025ffb7
182 changed files with 1573 additions and 1239 deletions

View File

@@ -0,0 +1,178 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import { closePopup } from '../../lib/popup';
import DroppableTypes from '../../constants/DroppableTypes';
import ListContainer from '../../containers/ListContainer';
import CardModalContainer from '../../containers/CardModalContainer';
import ListAdd from './ListAdd';
import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-icon.svg';
import styles from './Board.module.scss';
const parseDndId = (dndId) => dndId.split(':')[1];
const Board = React.memo(
({ listIds, isCardModalOpened, canEdit, onListCreate, onListMove, onCardMove }) => {
const [t] = useTranslation();
const [isListAddOpened, setIsListAddOpened] = useState(false);
const wrapper = useRef(null);
const prevPosition = useRef(null);
const handleAddListClick = useCallback(() => {
setIsListAddOpened(true);
}, []);
const handleAddListClose = useCallback(() => {
setIsListAddOpened(false);
}, []);
const handleDragStart = useCallback(() => {
closePopup();
}, []);
const handleDragEnd = useCallback(
({ draggableId, type, source, destination }) => {
if (
!destination ||
(source.droppableId === destination.droppableId && source.index === destination.index)
) {
return;
}
const id = parseDndId(draggableId);
switch (type) {
case DroppableTypes.LIST:
onListMove(id, destination.index);
break;
case DroppableTypes.CARD:
onCardMove(id, parseDndId(destination.droppableId), destination.index);
break;
default:
}
},
[onListMove, onCardMove],
);
const handleMouseDown = useCallback(
(event) => {
if (event.target !== wrapper.current && !event.target.dataset.dragScroller) {
return;
}
prevPosition.current = event.clientX;
},
[wrapper],
);
const handleWindowMouseMove = useCallback(
(event) => {
if (!prevPosition.current) {
return;
}
event.preventDefault();
window.scrollBy({
left: prevPosition.current - event.clientX,
});
prevPosition.current = event.clientX;
},
[prevPosition],
);
const handleWindowMouseUp = useCallback(() => {
prevPosition.current = null;
}, [prevPosition]);
useEffect(() => {
document.body.style.overflowX = 'auto';
return () => {
document.body.style.overflowX = null;
};
}, []);
useEffect(() => {
if (isListAddOpened) {
window.scroll(document.body.scrollWidth, 0);
}
}, [listIds, isListAddOpened]);
useEffect(() => {
window.addEventListener('mouseup', handleWindowMouseUp);
window.addEventListener('mousemove', handleWindowMouseMove);
return () => {
window.removeEventListener('mouseup', handleWindowMouseUp);
window.removeEventListener('mousemove', handleWindowMouseMove);
};
}, [handleWindowMouseUp, handleWindowMouseMove]);
return (
<>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div ref={wrapper} className={styles.wrapper} onMouseDown={handleMouseDown}>
<div>
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<Droppable droppableId="board" type={DroppableTypes.LIST} direction="horizontal">
{({ innerRef, droppableProps, placeholder }) => (
<div
{...droppableProps} // eslint-disable-line react/jsx-props-no-spreading
data-drag-scroller
ref={innerRef}
className={styles.lists}
>
{listIds.map((listId, index) => (
<ListContainer key={listId} id={listId} index={index} />
))}
{placeholder}
{canEdit && (
<div data-drag-scroller className={styles.list}>
{isListAddOpened ? (
<ListAdd onCreate={onListCreate} onClose={handleAddListClose} />
) : (
<button
type="button"
className={styles.addListButton}
onClick={handleAddListClick}
>
<PlusMathIcon className={styles.addListButtonIcon} />
<span className={styles.addListButtonText}>
{listIds.length > 0
? t('action.addAnotherList')
: t('action.addList')}
</span>
</button>
)}
</div>
)}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</div>
{isCardModalOpened && <CardModalContainer />}
</>
);
},
);
Board.propTypes = {
listIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
isCardModalOpened: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
onListCreate: PropTypes.func.isRequired,
onListMove: PropTypes.func.isRequired,
onCardMove: PropTypes.func.isRequired,
};
export default Board;

View File

@@ -0,0 +1,64 @@
:global(#app) {
.addListButton {
background: rgba(0, 0, 0, 0.24);
border: none;
border-radius: 3px;
color: rgba(255, 255, 255, 0.72);
cursor: pointer;
display: block;
fill: rgba(255, 255, 255, 0.72);
font-weight: normal;
height: 42px;
padding: 11px;
text-align: left;
transition: background 85ms ease-in, opacity 40ms ease-in,
border-color 85ms ease-in;
width: 100%;
&:active {
outline: none;
}
&:hover {
background: rgba(0, 0, 0, 0.32);
}
}
.addListButtonIcon {
height: 20px;
padding: 0.64px;
width: 20px;
}
.addListButtonText {
display: inline-block;
font-size: 14px;
line-height: 20px;
vertical-align: top;
}
.list {
margin-right: 20px;
width: 272px;
}
.lists {
display: inline-flex;
height: 100%;
min-width: 100%;
}
.panel {
align-items: center;
display: flex;
margin-bottom: 20px;
}
.panelItem {
margin-right: 20px;
}
.wrapper {
margin: 0 20px;
}
}

View File

@@ -0,0 +1,89 @@
import React, { useCallback, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Form, Input } from 'semantic-ui-react';
import { useDidUpdate, useToggle } from '../../lib/hooks';
import { useClosableForm, useForm } from '../../hooks';
import styles from './ListAdd.module.scss';
const DEFAULT_DATA = {
name: '',
};
const ListAdd = React.memo(({ onCreate, onClose }) => {
const [t] = useTranslation();
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
const [focusNameFieldState, focusNameField] = useToggle();
const nameField = useRef(null);
const handleFieldKeyDown = useCallback(
(event) => {
if (event.key === 'Escape') {
onClose();
}
},
[onClose],
);
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(onClose);
const handleSubmit = useCallback(() => {
const cleanData = {
...data,
name: data.name.trim(),
};
if (!cleanData.name) {
nameField.current.select();
return;
}
onCreate(cleanData);
setData(DEFAULT_DATA);
focusNameField();
}, [onCreate, data, setData, focusNameField]);
useEffect(() => {
nameField.current.focus();
}, []);
useDidUpdate(() => {
nameField.current.focus();
}, [focusNameFieldState]);
return (
<Form className={styles.wrapper} onSubmit={handleSubmit}>
<Input
ref={nameField}
name="name"
value={data.name}
placeholder={t('common.enterListTitle')}
className={styles.field}
onKeyDown={handleFieldKeyDown}
onChange={handleFieldChange}
onBlur={handleFieldBlur}
/>
<div className={styles.controls}>
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
<Button
positive
content={t('action.addList')}
className={styles.button}
onMouseOver={handleControlMouseOver}
onMouseOut={handleControlMouseOut}
/>
</div>
</Form>
);
});
ListAdd.propTypes = {
onCreate: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
export default ListAdd;

View File

@@ -0,0 +1,33 @@
:global(#app) {
.button {
min-height: 30px;
vertical-align: top;
}
.controls {
margin-top: 4px;
}
.field {
border: none;
border-radius: 3px;
box-shadow: 0 1px 0 #ccc;
color: #333;
outline: none;
overflow: hidden;
width: 100%;
&:focus {
border-color: #298fca;
box-shadow: 0 0 2px #298fca;
}
}
.wrapper {
background: #e2e4e6;
border-radius: 3px;
padding: 4px;
transition: opacity 40ms ease-in;
width: 272px;
}
}

View File

@@ -0,0 +1,3 @@
import Board from './Board';
export default Board;