Make columns itself scrollable, fix action creation when moving card, little refactoring

This commit is contained in:
Maksim Eltyshev
2020-05-16 04:09:46 +05:00
parent 66c570f234
commit 3a1929efba
44 changed files with 549 additions and 438 deletions

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
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';
@@ -12,48 +12,23 @@ const DEFAULT_DATA = {
name: '',
};
const AddList = React.forwardRef(({ children, onCreate }, ref) => {
const AddList = React.memo(({ onCreate, onClose }) => {
const [t] = useTranslation();
const [isOpened, setIsOpened] = useState(false);
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
const [selectNameFieldState, selectNameField] = useToggle();
const nameField = useRef(null);
const open = useCallback(() => {
setIsOpened(true);
}, []);
const close = useCallback(() => {
setIsOpened(false);
}, []);
useImperativeHandle(
ref,
() => ({
open,
close,
}),
[open, close],
);
const handleChildrenClick = useCallback(() => {
open();
}, [open]);
const handleFieldKeyDown = useCallback(
(event) => {
if (event.key === 'Escape') {
close();
onClose();
}
},
[close],
[onClose],
);
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
isOpened,
close,
);
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(onClose);
const handleSubmit = useCallback(() => {
const cleanData = {
@@ -73,21 +48,13 @@ const AddList = React.forwardRef(({ children, onCreate }, ref) => {
}, [onCreate, data, setData, selectNameField]);
useEffect(() => {
if (isOpened) {
nameField.current.select();
}
}, [isOpened]);
nameField.current.select();
}, []);
useDidUpdate(() => {
nameField.current.select();
}, [selectNameFieldState]);
if (!isOpened) {
return React.cloneElement(children, {
onClick: handleChildrenClick,
});
}
return (
<Form className={styles.wrapper} onSubmit={handleSubmit}>
<Input
@@ -115,8 +82,8 @@ const AddList = React.forwardRef(({ children, onCreate }, ref) => {
});
AddList.propTypes = {
children: PropTypes.element.isRequired,
onCreate: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
export default React.memo(AddList);
export default AddList;

View File

@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
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';
@@ -35,6 +35,18 @@ const Board = React.memo(
onLabelDelete,
}) => {
const [t] = useTranslation();
const [isAddListOpened, setIsAddListOpened] = useState(false);
const wrapper = useRef(null);
const prevPosition = useRef(null);
const handleAddListClick = useCallback(() => {
setIsAddListOpened(true);
}, []);
const handleAddListClose = useCallback(() => {
setIsAddListOpened(false);
}, []);
const handleDragStart = useCallback(() => {
closePopup();
@@ -66,45 +78,110 @@ const Board = React.memo(
[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(() => {
if (isAddListOpened) {
window.scroll(document.body.scrollWidth, 0);
}
}, [listIds, isAddListOpened]);
useEffect(() => {
window.addEventListener('mouseup', handleWindowMouseUp);
window.addEventListener('mousemove', handleWindowMouseMove);
return () => {
window.removeEventListener('mouseup', handleWindowMouseUp);
window.removeEventListener('mousemove', handleWindowMouseMove);
};
}, [handleWindowMouseUp, handleWindowMouseMove]);
return (
<>
<Filter
users={filterUsers}
labels={filterLabels}
allProjectMemberships={allProjectMemberships}
allLabels={allLabels}
onUserAdd={onUserToFilterAdd}
onUserRemove={onUserFromFilterRemove}
onLabelAdd={onLabelToFilterAdd}
onLabelRemove={onLabelFromFilterRemove}
onLabelCreate={onLabelCreate}
onLabelUpdate={onLabelUpdate}
onLabelDelete={onLabelDelete}
/>
<div className={styles.wrapper}>
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<Droppable droppableId="board" type={DroppableTypes.LIST} direction="horizontal">
{({ innerRef, droppableProps, placeholder }) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<div {...droppableProps} data-drag-scroller ref={innerRef} className={styles.lists}>
{listIds.map((listId, index) => (
<ListContainer key={listId} id={listId} index={index} />
))}
{placeholder}
<div data-drag-scroller className={styles.list}>
<AddList onCreate={onListCreate}>
<button type="button" className={styles.addListButton}>
<PlusMathIcon className={styles.addListButtonIcon} />
<span className={styles.addListButtonText}>
{listIds.length > 0 ? t('action.addAnotherList') : t('action.addList')}
</span>
</button>
</AddList>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div ref={wrapper} className={styles.wrapper} onMouseDown={handleMouseDown}>
<Filter
users={filterUsers}
labels={filterLabels}
allProjectMemberships={allProjectMemberships}
allLabels={allLabels}
onUserAdd={onUserToFilterAdd}
onUserRemove={onUserFromFilterRemove}
onLabelAdd={onLabelToFilterAdd}
onLabelRemove={onLabelFromFilterRemove}
onLabelCreate={onLabelCreate}
onLabelUpdate={onLabelUpdate}
onLabelDelete={onLabelDelete}
/>
<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}
<div data-drag-scroller className={styles.list}>
{isAddListOpened ? (
<AddList
isOpened={isAddListOpened}
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>
</div>
)}
</Droppable>
</DragDropContext>
)}
</Droppable>
</DragDropContext>
</div>
</div>
{isCardModalOpened && <CardModalContainer />}
</>

View File

@@ -48,5 +48,5 @@
}
.wrapper {
height: 100%;
margin: 0 20px;
}

View File

@@ -25,14 +25,14 @@ const Filter = React.memo(
}) => {
const [t] = useTranslation();
const handleUserRemoveClick = useCallback(
const handleRemoveUserClick = useCallback(
(id) => {
onUserRemove(id);
},
[onUserRemove],
);
const handleLabelRemoveClick = useCallback(
const handleRemoveLabelClick = useCallback(
(id) => {
onLabelRemove(id);
},
@@ -62,7 +62,7 @@ const Filter = React.memo(
name={user.name}
avatarUrl={user.avatarUrl}
size="tiny"
onClick={() => handleUserRemoveClick(user.id)}
onClick={() => handleRemoveUserClick(user.id)}
/>
</span>
))}
@@ -91,7 +91,7 @@ const Filter = React.memo(
name={label.name}
color={label.color}
size="small"
onClick={() => handleLabelRemoveClick(label.id)}
onClick={() => handleRemoveLabelClick(label.id)}
/>
</span>
))}