mirror of
https://github.com/plankanban/planka.git
synced 2025-07-19 13:19:44 +02:00
Make columns itself scrollable, fix action creation when moving card, little refactoring
This commit is contained in:
parent
c5b44598f9
commit
746e2fe790
44 changed files with 549 additions and 438 deletions
|
@ -1,26 +1,29 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import HeaderContainer from '../containers/HeaderContainer';
|
||||
import ProjectsContainer from '../containers/ProjectsContainer';
|
||||
import ModalTypes from '../constants/ModalTypes';
|
||||
import FixedWrapperContainer from '../containers/FixedWrapperContainer';
|
||||
import StaticWrapperContainer from '../containers/StaticWrapperContainer';
|
||||
import UsersModalContainer from '../containers/UsersModalContainer';
|
||||
import UserSettingsModalContainer from '../containers/UserSettingsModalContainer';
|
||||
import AddProjectModalContainer from '../containers/AddProjectModalContainer';
|
||||
|
||||
const App = ({ isUsersModalOpened, isUserSettingsModalOpened, isAddProjectModalOpened }) => (
|
||||
const App = ({ currentModal }) => (
|
||||
<>
|
||||
<HeaderContainer />
|
||||
<ProjectsContainer />
|
||||
{isUsersModalOpened && <UsersModalContainer />}
|
||||
{isUserSettingsModalOpened && <UserSettingsModalContainer />}
|
||||
{isAddProjectModalOpened && <AddProjectModalContainer />}
|
||||
<FixedWrapperContainer />
|
||||
<StaticWrapperContainer />
|
||||
{currentModal === ModalTypes.USERS && <UsersModalContainer />}
|
||||
{currentModal === ModalTypes.USER_SETTINGS && <UserSettingsModalContainer />}
|
||||
{currentModal === ModalTypes.ADD_PROJECT && <AddProjectModalContainer />}
|
||||
</>
|
||||
);
|
||||
|
||||
App.propTypes = {
|
||||
isUsersModalOpened: PropTypes.bool.isRequired,
|
||||
isUserSettingsModalOpened: PropTypes.bool.isRequired,
|
||||
isAddProjectModalOpened: PropTypes.bool.isRequired,
|
||||
currentModal: PropTypes.oneOf(Object.values(ModalTypes)),
|
||||
};
|
||||
|
||||
App.defaultProps = {
|
||||
currentModal: undefined,
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -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]);
|
||||
}, []);
|
||||
|
||||
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;
|
||||
|
|
|
@ -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,8 +78,58 @@ 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 (
|
||||
<>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div ref={wrapper} className={styles.wrapper} onMouseDown={handleMouseDown}>
|
||||
<Filter
|
||||
users={filterUsers}
|
||||
labels={filterLabels}
|
||||
|
@ -81,31 +143,46 @@ const Board = React.memo(
|
|||
onLabelUpdate={onLabelUpdate}
|
||||
onLabelDelete={onLabelDelete}
|
||||
/>
|
||||
<div className={styles.wrapper}>
|
||||
<div>
|
||||
<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}>
|
||||
<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}>
|
||||
<AddList onCreate={onListCreate}>
|
||||
<button type="button" className={styles.addListButton}>
|
||||
{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>
|
||||
</AddList>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</div>
|
||||
{isCardModalOpened && <CardModalContainer />}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -48,5 +48,5 @@
|
|||
}
|
||||
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
@ -2,16 +2,13 @@ import pick from 'lodash/pick';
|
|||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
||||
import { Button, Icon } from 'semantic-ui-react';
|
||||
import { closePopup } from '../../lib/popup';
|
||||
import { DragScroller } from '../../lib/custom-ui';
|
||||
|
||||
import Paths from '../../constants/Paths';
|
||||
import DroppableTypes from '../../constants/DroppableTypes';
|
||||
import BoardWrapperContainer from '../../containers/BoardWrapperContainer';
|
||||
import AddPopup from './AddPopup';
|
||||
import EditPopup from './EditPopup';
|
||||
|
||||
|
@ -19,8 +16,6 @@ import styles from './Boards.module.css';
|
|||
|
||||
const Boards = React.memo(
|
||||
({ items, currentId, isEditable, onCreate, onUpdate, onMove, onDelete }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const handleDragStart = useCallback(() => {
|
||||
closePopup();
|
||||
}, []);
|
||||
|
@ -139,28 +134,6 @@ const Boards = React.memo(
|
|||
) : (
|
||||
<div className={styles.tabs}>{renderItems(items)}</div>
|
||||
)}
|
||||
<DragScroller className={styles.board}>
|
||||
{currentId ? (
|
||||
<BoardWrapperContainer />
|
||||
) : (
|
||||
<div className={styles.message}>
|
||||
<Icon
|
||||
inverted
|
||||
name="hand point up outline"
|
||||
size="huge"
|
||||
className={styles.messageIcon}
|
||||
/>
|
||||
<h1 className={styles.messageTitle}>
|
||||
{t('common.openBoard', {
|
||||
context: 'title',
|
||||
})}
|
||||
</h1>
|
||||
<div className={styles.messageContent}>
|
||||
<Trans i18nKey="common.createNewOneOrSelectExistingOne" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DragScroller>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -9,15 +9,6 @@
|
|||
background: rgba(34, 36, 38, 0.3) !important;
|
||||
}
|
||||
|
||||
.board {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
margin: 0 -20px 8px;
|
||||
overflow: auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.editButton {
|
||||
background: transparent !important;
|
||||
color: #fff !important;
|
||||
|
@ -45,32 +36,6 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.message {
|
||||
align-content: space-between;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.messageIcon {
|
||||
margin-top: -84px;
|
||||
}
|
||||
|
||||
.messageTitle {
|
||||
font-size: 32px;
|
||||
margin: 24px 0 8px;
|
||||
}
|
||||
|
||||
.messageContent {
|
||||
font-size: 18px;
|
||||
line-height: 1.4;
|
||||
margin: 4px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border-radius: 3px 3px 0 0;
|
||||
min-width: 100px;
|
||||
|
@ -104,7 +69,6 @@
|
|||
display: flex;
|
||||
height: 38px;
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ const ActionsStep = React.memo(
|
|||
const [t] = useTranslation();
|
||||
const [step, openStep, handleBack] = useSteps();
|
||||
|
||||
const handleNameEditClick = useCallback(() => {
|
||||
const handleEditNameClick = useCallback(() => {
|
||||
onNameEdit();
|
||||
onClose();
|
||||
}, [onNameEdit, onClose]);
|
||||
|
@ -178,7 +178,7 @@ const ActionsStep = React.memo(
|
|||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<Menu secondary vertical className={styles.menu}>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleNameEditClick}>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditNameClick}>
|
||||
{t('action.editTitle', {
|
||||
context: 'title',
|
||||
})}
|
||||
|
|
|
@ -69,8 +69,8 @@ const EditName = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
|||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
||||
isOpened,
|
||||
close,
|
||||
isOpened,
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
|
|
|
@ -66,8 +66,8 @@ const EditComment = React.forwardRef(({ children, defaultData, onUpdate }, ref)
|
|||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
||||
isOpened,
|
||||
close,
|
||||
isOpened,
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
|
|
|
@ -117,7 +117,7 @@ const CardModal = React.memo(
|
|||
[onUpdate],
|
||||
);
|
||||
|
||||
const handleToggleSubscribeClick = useCallback(() => {
|
||||
const handleToggleSubscriptionClick = useCallback(() => {
|
||||
onUpdate({
|
||||
isSubscribed: !isSubscribed,
|
||||
});
|
||||
|
@ -359,7 +359,7 @@ const CardModal = React.memo(
|
|||
<Button
|
||||
fluid
|
||||
className={styles.actionButton}
|
||||
onClick={handleToggleSubscribeClick}
|
||||
onClick={handleToggleSubscriptionClick}
|
||||
>
|
||||
<Icon name="paper plane outline" className={styles.actionIcon} />
|
||||
{isSubscribed ? t('action.unsubscribe') : t('action.subscribe')}
|
||||
|
@ -378,7 +378,7 @@ const CardModal = React.memo(
|
|||
<Button
|
||||
fluid
|
||||
className={styles.actionButton}
|
||||
onClick={handleToggleSubscribeClick}
|
||||
onClick={handleToggleSubscriptionClick}
|
||||
>
|
||||
<Icon name="share square outline" className={styles.actionIcon} />
|
||||
{t('action.move')}
|
||||
|
|
|
@ -56,8 +56,8 @@ const EditDescription = React.forwardRef(({ children, defaultValue, onUpdate },
|
|||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
||||
isOpened,
|
||||
close,
|
||||
isOpened,
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
|
|
|
@ -18,7 +18,7 @@ const ActionsStep = React.memo(({ onNameEdit, onDelete, onClose }) => {
|
|||
const [t] = useTranslation();
|
||||
const [step, openStep, handleBack] = useSteps();
|
||||
|
||||
const handleNameEditClick = useCallback(() => {
|
||||
const handleEditNameClick = useCallback(() => {
|
||||
onNameEdit();
|
||||
onClose();
|
||||
}, [onNameEdit, onClose]);
|
||||
|
@ -50,7 +50,7 @@ const ActionsStep = React.memo(({ onNameEdit, onDelete, onClose }) => {
|
|||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<Menu secondary vertical className={styles.menu}>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleNameEditClick}>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditNameClick}>
|
||||
{t('action.editDescription', {
|
||||
context: 'title',
|
||||
})}
|
||||
|
|
|
@ -71,8 +71,8 @@ const Add = React.forwardRef(({ children, onCreate }, ref) => {
|
|||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
||||
isOpened,
|
||||
close,
|
||||
isOpened,
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
|
|
|
@ -61,8 +61,8 @@ const EditName = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
|||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
||||
isOpened,
|
||||
close,
|
||||
isOpened,
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
|
|
24
client/src/components/FixedWrapper/FixedWrapper.jsx
Normal file
24
client/src/components/FixedWrapper/FixedWrapper.jsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import HeaderContainer from '../../containers/HeaderContainer';
|
||||
import ProjectContainer from '../../containers/ProjectContainer';
|
||||
|
||||
import styles from './FixedWrapper.module.css';
|
||||
|
||||
const FixedWrapper = ({ projectId }) => (
|
||||
<div className={styles.wrapper}>
|
||||
<HeaderContainer />
|
||||
{projectId && <ProjectContainer />}
|
||||
</div>
|
||||
);
|
||||
|
||||
FixedWrapper.propTypes = {
|
||||
projectId: PropTypes.string,
|
||||
};
|
||||
|
||||
FixedWrapper.defaultProps = {
|
||||
projectId: undefined,
|
||||
};
|
||||
|
||||
export default FixedWrapper;
|
|
@ -0,0 +1,5 @@
|
|||
.wrapper {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
3
client/src/components/FixedWrapper/index.js
Normal file
3
client/src/components/FixedWrapper/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import FixedWrapper from './FixedWrapper';
|
||||
|
||||
export default FixedWrapper;
|
|
@ -111,4 +111,6 @@ NotificationsStep.propTypes = {
|
|||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withPopup(NotificationsStep);
|
||||
export default withPopup(NotificationsStep, {
|
||||
position: 'bottom right',
|
||||
});
|
||||
|
|
|
@ -18,12 +18,12 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
|
|||
const [t] = useTranslation();
|
||||
const [step, openStep, handleBack] = useSteps();
|
||||
|
||||
const handleNameEditClick = useCallback(() => {
|
||||
const handleEditNameClick = useCallback(() => {
|
||||
onNameEdit();
|
||||
onClose();
|
||||
}, [onNameEdit, onClose]);
|
||||
|
||||
const handleCardAddClick = useCallback(() => {
|
||||
const handleAddCardClick = useCallback(() => {
|
||||
onCardAdd();
|
||||
onClose();
|
||||
}, [onCardAdd, onClose]);
|
||||
|
@ -55,12 +55,12 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
|
|||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<Menu secondary vertical className={styles.menu}>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleNameEditClick}>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditNameClick}>
|
||||
{t('action.editTitle', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleCardAddClick}>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleAddCardClick}>
|
||||
{t('action.addCard', {
|
||||
context: 'title',
|
||||
})}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
|
@ -13,22 +14,13 @@ const DEFAULT_DATA = {
|
|||
name: '',
|
||||
};
|
||||
|
||||
const AddCard = React.forwardRef(({ children, onCreate }, ref) => {
|
||||
const AddCard = React.memo(({ isOpened, 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);
|
||||
}, []);
|
||||
|
||||
const submit = useCallback(() => {
|
||||
const cleanData = {
|
||||
...data,
|
||||
|
@ -46,19 +38,6 @@ const AddCard = React.forwardRef(({ children, onCreate }, ref) => {
|
|||
selectNameField();
|
||||
}, [onCreate, data, setData, selectNameField]);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
open,
|
||||
close,
|
||||
}),
|
||||
[open, close],
|
||||
);
|
||||
|
||||
const handleChildrenClick = useCallback(() => {
|
||||
open();
|
||||
}, [open]);
|
||||
|
||||
const handleFieldKeyDown = useCallback(
|
||||
(event) => {
|
||||
switch (event.key) {
|
||||
|
@ -69,19 +48,16 @@ const AddCard = React.forwardRef(({ children, onCreate }, ref) => {
|
|||
|
||||
break;
|
||||
case 'Escape':
|
||||
close();
|
||||
onClose();
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
[close, submit],
|
||||
[onClose, submit],
|
||||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
||||
isOpened,
|
||||
close,
|
||||
);
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(onClose);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
submit();
|
||||
|
@ -97,14 +73,11 @@ const AddCard = React.forwardRef(({ children, onCreate }, ref) => {
|
|||
nameField.current.ref.current.select();
|
||||
}, [selectNameFieldState]);
|
||||
|
||||
if (!isOpened) {
|
||||
return React.cloneElement(children, {
|
||||
onClick: handleChildrenClick,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Form className={styles.wrapper} onSubmit={handleSubmit}>
|
||||
<Form
|
||||
className={classNames(styles.wrapper, !isOpened && styles.wrapperClosed)}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className={styles.fieldWrapper}>
|
||||
<TextArea
|
||||
ref={nameField}
|
||||
|
@ -135,8 +108,9 @@ const AddCard = React.forwardRef(({ children, onCreate }, ref) => {
|
|||
});
|
||||
|
||||
AddCard.propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
isOpened: PropTypes.bool.isRequired,
|
||||
onCreate: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(AddCard);
|
||||
export default AddCard;
|
||||
|
|
|
@ -24,3 +24,7 @@
|
|||
.wrapper {
|
||||
padding-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.wrapperClosed {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -17,9 +17,10 @@ import styles from './List.module.css';
|
|||
const List = React.memo(
|
||||
({ id, index, name, isPersisted, cardIds, onUpdate, onDelete, onCardCreate }) => {
|
||||
const [t] = useTranslation();
|
||||
const [isAddCardOpened, setIsAddCardOpened] = useState(false);
|
||||
|
||||
const addCard = useRef(null);
|
||||
const editName = useRef(null);
|
||||
const listWrapper = useRef(null);
|
||||
|
||||
const handleHeaderClick = useCallback(() => {
|
||||
if (isPersisted) {
|
||||
|
@ -36,14 +37,28 @@ const List = React.memo(
|
|||
[onUpdate],
|
||||
);
|
||||
|
||||
const handleAddCardClick = useCallback(() => {
|
||||
setIsAddCardOpened(true);
|
||||
}, []);
|
||||
|
||||
const handleAddCardClose = useCallback(() => {
|
||||
setIsAddCardOpened(false);
|
||||
}, []);
|
||||
|
||||
const handleNameEdit = useCallback(() => {
|
||||
editName.current.open();
|
||||
}, []);
|
||||
|
||||
const handleCardAdd = useCallback(() => {
|
||||
addCard.current.open();
|
||||
setIsAddCardOpened(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAddCardOpened) {
|
||||
listWrapper.current.scrollTop = listWrapper.current.scrollHeight;
|
||||
}
|
||||
}, [cardIds, isAddCardOpened]);
|
||||
|
||||
const cardsNode = (
|
||||
<Droppable
|
||||
droppableId={`list:${id}`}
|
||||
|
@ -58,15 +73,12 @@ const List = React.memo(
|
|||
<CardContainer key={cardId} id={cardId} index={cardIndex} />
|
||||
))}
|
||||
{placeholder}
|
||||
<AddCard
|
||||
isOpened={isAddCardOpened}
|
||||
onCreate={onCardCreate}
|
||||
onClose={handleAddCardClose}
|
||||
/>
|
||||
</div>
|
||||
<AddCard ref={addCard} onCreate={onCardCreate}>
|
||||
<button type="button" disabled={!isPersisted} className={styles.addCardButton}>
|
||||
<PlusMathIcon className={styles.addCardButtonIcon} />
|
||||
<span className={styles.addCardButtonText}>
|
||||
{cardIds.length > 0 ? t('action.addAnotherCard') : t('action.addCard')}
|
||||
</span>
|
||||
</button>
|
||||
</AddCard>
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
|
@ -99,8 +111,26 @@ const List = React.memo(
|
|||
</ActionsPopup>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
ref={listWrapper}
|
||||
className={classNames(styles.listWrapper, isAddCardOpened && styles.listWrapperFull)}
|
||||
>
|
||||
<div className={styles.list}>{cardsNode}</div>
|
||||
</div>
|
||||
{!isAddCardOpened && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={!isPersisted}
|
||||
className={styles.addCardButton}
|
||||
onClick={handleAddCardClick}
|
||||
>
|
||||
<PlusMathIcon className={styles.addCardButtonIcon} />
|
||||
<span className={styles.addCardButtonText}>
|
||||
{cardIds.length > 0 ? t('action.addAnotherCard') : t('action.addCard')}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
.addCardButton {
|
||||
background: none !important;
|
||||
background: #dfe3e6 !important;
|
||||
border: none !important;
|
||||
border-radius: 0 0 3px 3px !important;
|
||||
color: #6b808c !important;
|
||||
cursor: pointer;
|
||||
display: block !important;
|
||||
|
@ -8,18 +9,14 @@
|
|||
flex: 0 0 auto;
|
||||
font-weight: normal !important;
|
||||
height: 36px !important;
|
||||
margin: 0 -8px !important;
|
||||
outline: none !important;
|
||||
padding: 8px !important;
|
||||
text-align: left !important;
|
||||
width: calc(100% + 16px);
|
||||
}
|
||||
|
||||
.addCardButton:active {
|
||||
outline: none !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.addCardButton:hover {
|
||||
background-color: #092d4221 !important;
|
||||
background-color: #c3cbd0 !important;
|
||||
color: #17394d !important;
|
||||
fill: #17394d !important;
|
||||
}
|
||||
|
@ -40,6 +37,7 @@
|
|||
|
||||
.cards {
|
||||
flex: 1 1 auto;
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.header {
|
||||
|
@ -96,18 +94,48 @@
|
|||
|
||||
.list {
|
||||
background: #dfe3e6 !important;
|
||||
border-radius: 0 0 3px 3px !important;
|
||||
box-sizing: border-box !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 8px !important;
|
||||
position: relative !important;
|
||||
white-space: normal !important;
|
||||
width: 272px;
|
||||
}
|
||||
|
||||
.listWrapper {
|
||||
background: #dfe3e6;
|
||||
max-height: calc(100vh - 300px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 290px;
|
||||
}
|
||||
|
||||
.listWrapper:hover {
|
||||
width: 272px;
|
||||
}
|
||||
|
||||
.listWrapper::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.listWrapper::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.listWrapper::-webkit-scrollbar-thumb {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.listWrapperFull {
|
||||
max-height: calc(100vh - 264px);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
border-radius: 3px;
|
||||
flex: 0 0 auto;
|
||||
margin: 0 4px !important;
|
||||
overflow: hidden;
|
||||
vertical-align: top !important;
|
||||
width: 272px !important;
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProjectContainer from '../containers/ProjectContainer';
|
||||
|
||||
const ProjectWrapper = React.memo(({ isProjectNotFound, isBoardNotFound, isCardNotFound }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
if (isCardNotFound) {
|
||||
return (
|
||||
<h1>
|
||||
{t('common.cardNotFound', {
|
||||
context: 'title',
|
||||
})}
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
if (isBoardNotFound) {
|
||||
return (
|
||||
<h1>
|
||||
{t('common.boardNotFound', {
|
||||
context: 'title',
|
||||
})}
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
if (isProjectNotFound) {
|
||||
return (
|
||||
<h1>
|
||||
{t('common.projectNotFound', {
|
||||
context: 'title',
|
||||
})}
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
return <ProjectContainer />;
|
||||
});
|
||||
|
||||
ProjectWrapper.propTypes = {
|
||||
isProjectNotFound: PropTypes.bool.isRequired,
|
||||
isBoardNotFound: PropTypes.bool.isRequired,
|
||||
isCardNotFound: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default ProjectWrapper;
|
|
@ -1,4 +1,3 @@
|
|||
import isUndefined from 'lodash/isUndefined';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
@ -7,15 +6,13 @@ import { Link } from 'react-router-dom';
|
|||
import { Container, Grid } from 'semantic-ui-react';
|
||||
|
||||
import Paths from '../../constants/Paths';
|
||||
import ProjectWrapperContainer from '../../containers/ProjectWrapperContainer';
|
||||
import { ReactComponent as PlusIcon } from '../../assets/images/plus-icon.svg';
|
||||
|
||||
import styles from './Projects.module.css';
|
||||
|
||||
const Projects = React.memo(({ items, currentId, isEditable, onAdd }) => {
|
||||
const Projects = React.memo(({ items, isEditable, onAdd }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
if (isUndefined(currentId)) {
|
||||
return (
|
||||
<Container className={styles.cardsWrapper}>
|
||||
<Grid className={styles.gridFix}>
|
||||
|
@ -53,26 +50,12 @@ const Projects = React.memo(({ items, currentId, isEditable, onAdd }) => {
|
|||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.project}>
|
||||
<ProjectWrapperContainer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Projects.propTypes = {
|
||||
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
currentId: PropTypes.string,
|
||||
isEditable: PropTypes.bool.isRequired,
|
||||
onAdd: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
Projects.defaultProps = {
|
||||
currentId: undefined,
|
||||
};
|
||||
|
||||
export default Projects;
|
||||
|
|
106
client/src/components/StaticWrapper/StaticWrapper.jsx
Normal file
106
client/src/components/StaticWrapper/StaticWrapper.jsx
Normal file
|
@ -0,0 +1,106 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import { Icon } from 'semantic-ui-react';
|
||||
|
||||
import ProjectsContainer from '../../containers/ProjectsContainer';
|
||||
import BoardWrapperContainer from '../../containers/BoardWrapperContainer';
|
||||
|
||||
import styles from './StaticWrapper.module.css';
|
||||
|
||||
const StaticWrapper = ({ cardId, boardId, projectId }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.overflowX = 'auto'; // TODO: only for board
|
||||
}, []);
|
||||
|
||||
if (projectId === undefined) {
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<ProjectsContainer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (cardId === null) {
|
||||
return (
|
||||
<div className={classNames(styles.root, styles.flex)}>
|
||||
<div className={styles.message}>
|
||||
<h1>
|
||||
{t('common.cardNotFound', {
|
||||
context: 'title',
|
||||
})}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (boardId === null) {
|
||||
return (
|
||||
<div className={classNames(styles.root, styles.flex)}>
|
||||
<div className={styles.message}>
|
||||
<h1>
|
||||
{t('common.boardNotFound', {
|
||||
context: 'title',
|
||||
})}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (projectId === null) {
|
||||
return (
|
||||
<div className={classNames(styles.root, styles.flex)}>
|
||||
<div className={styles.message}>
|
||||
<h1>
|
||||
{t('common.projectNotFound', {
|
||||
context: 'title',
|
||||
})}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (boardId === undefined) {
|
||||
return (
|
||||
<div className={classNames(styles.board, styles.flex)}>
|
||||
<div className={styles.message}>
|
||||
<Icon inverted name="hand point up outline" size="huge" className={styles.messageIcon} />
|
||||
<h1 className={styles.messageTitle}>
|
||||
{t('common.openBoard', {
|
||||
context: 'title',
|
||||
})}
|
||||
</h1>
|
||||
<div className={styles.messageContent}>
|
||||
<Trans i18nKey="common.createNewOneOrSelectExistingOne" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.board, styles.flex)}>
|
||||
<BoardWrapperContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
StaticWrapper.propTypes = {
|
||||
cardId: PropTypes.string,
|
||||
boardId: PropTypes.string,
|
||||
projectId: PropTypes.string,
|
||||
};
|
||||
|
||||
StaticWrapper.defaultProps = {
|
||||
cardId: undefined,
|
||||
boardId: undefined,
|
||||
projectId: undefined,
|
||||
};
|
||||
|
||||
export default StaticWrapper;
|
38
client/src/components/StaticWrapper/StaticWrapper.module.css
Normal file
38
client/src/components/StaticWrapper/StaticWrapper.module.css
Normal file
|
@ -0,0 +1,38 @@
|
|||
.board {
|
||||
margin-top: 168px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.message {
|
||||
align-content: space-between;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.messageIcon {
|
||||
margin-top: -84px;
|
||||
}
|
||||
|
||||
.messageTitle {
|
||||
font-size: 32px;
|
||||
margin: 24px 0 8px;
|
||||
}
|
||||
|
||||
.messageContent {
|
||||
font-size: 18px;
|
||||
line-height: 1.4;
|
||||
margin: 4px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.root {
|
||||
margin-top: 50px;
|
||||
}
|
3
client/src/components/StaticWrapper/index.js
Normal file
3
client/src/components/StaticWrapper/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import StaticWrapper from './StaticWrapper';
|
||||
|
||||
export default StaticWrapper;
|
|
@ -46,4 +46,6 @@ UserStep.propTypes = {
|
|||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withPopup(UserStep);
|
||||
export default withPopup(UserStep, {
|
||||
position: 'bottom right',
|
||||
});
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
const USERS = 'USERS';
|
||||
|
||||
const USER_SETTINGS = 'USER_SETTINGS';
|
||||
|
||||
const ADD_PROJECT = 'ADD_PROJECT';
|
||||
|
||||
export default {
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { currentModalSelector } from '../selectors';
|
||||
import ModalTypes from '../constants/ModalTypes';
|
||||
import App from '../components/App';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const currentModal = currentModalSelector(state);
|
||||
|
||||
return {
|
||||
isUsersModalOpened: currentModal === ModalTypes.USERS,
|
||||
isUserSettingsModalOpened: currentModal === ModalTypes.USER_SETTINGS,
|
||||
isAddProjectModalOpened: currentModal === ModalTypes.ADD_PROJECT,
|
||||
currentModal,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
14
client/src/containers/FixedWrapperContainer.js
Normal file
14
client/src/containers/FixedWrapperContainer.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { pathSelector } from '../selectors';
|
||||
import FixedWrapper from '../components/FixedWrapper';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { projectId } = pathSelector(state);
|
||||
|
||||
return {
|
||||
projectId,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(FixedWrapper);
|
|
@ -1,16 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { pathSelector } from '../selectors';
|
||||
import ProjectWrapper from '../components/ProjectWrapper';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { cardId, boardId, projectId } = pathSelector(state);
|
||||
|
||||
return {
|
||||
isProjectNotFound: projectId === null,
|
||||
isBoardNotFound: boardId === null,
|
||||
isCardNotFound: cardId === null,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(ProjectWrapper);
|
|
@ -1,18 +1,16 @@
|
|||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { currentUserSelector, pathSelector, projectsForCurrentUserSelector } from '../selectors';
|
||||
import { currentUserSelector, projectsForCurrentUserSelector } from '../selectors';
|
||||
import { openAddProjectModal } from '../actions/entry';
|
||||
import Projects from '../components/Projects';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { projectId } = pathSelector(state);
|
||||
const { isAdmin } = currentUserSelector(state);
|
||||
const projects = projectsForCurrentUserSelector(state);
|
||||
|
||||
return {
|
||||
items: projects,
|
||||
currentId: projectId,
|
||||
isEditable: isAdmin,
|
||||
};
|
||||
};
|
||||
|
|
16
client/src/containers/StaticWrapperContainer.js
Normal file
16
client/src/containers/StaticWrapperContainer.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { pathSelector } from '../selectors';
|
||||
import StaticWrapper from '../components/StaticWrapper';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { cardId, boardId, projectId } = pathSelector(state);
|
||||
|
||||
return {
|
||||
cardId,
|
||||
boardId,
|
||||
projectId,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(StaticWrapper);
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
export default (isOpened, close) => {
|
||||
export default (close, isOpened = true) => {
|
||||
const isClosable = useRef(null);
|
||||
|
||||
const handleFieldBlur = useCallback(() => {
|
||||
|
|
|
@ -5,7 +5,7 @@ body {
|
|||
#root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.react-datepicker {
|
||||
|
@ -100,3 +100,30 @@ body {
|
|||
top: 0;
|
||||
margin: 9px 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
-webkit-transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:window-inactive {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(128, 135, 139, 0.8);
|
||||
}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const DragScroller = React.memo(({ children, ...props }) => {
|
||||
const wrapper = useRef(null);
|
||||
const prevPosition = useRef(null);
|
||||
|
||||
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();
|
||||
|
||||
const position = event.clientX;
|
||||
|
||||
wrapper.current.scrollLeft -= -prevPosition.current + position;
|
||||
prevPosition.current = position;
|
||||
},
|
||||
[wrapper, prevPosition],
|
||||
);
|
||||
|
||||
const handleWindowMouseUp = useCallback(() => {
|
||||
prevPosition.current = null;
|
||||
}, [prevPosition]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('mouseup', handleWindowMouseUp);
|
||||
window.addEventListener('mousemove', handleWindowMouseMove);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('mouseup', handleWindowMouseUp);
|
||||
window.removeEventListener('mousemove', handleWindowMouseMove);
|
||||
};
|
||||
}, [handleWindowMouseUp, handleWindowMouseMove]);
|
||||
|
||||
return (
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions, react/jsx-props-no-spreading */
|
||||
<div {...props} ref={wrapper} onMouseDown={handleMouseDown}>
|
||||
{/* eslint-enable jsx-a11y/no-static-element-interactions, react/jsx-props-no-spreading */}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
DragScroller.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default DragScroller;
|
|
@ -1,3 +0,0 @@
|
|||
import DragScroller from './DragScroller';
|
||||
|
||||
export default DragScroller;
|
8
client/src/lib/custom-ui/index.css
vendored
8
client/src/lib/custom-ui/index.css
vendored
|
@ -827,7 +827,7 @@ input::selection {
|
|||
|
||||
/* Force Simple Scrollbars */
|
||||
|
||||
body ::-webkit-scrollbar {
|
||||
/* body ::-webkit-scrollbar {
|
||||
-webkit-appearance: none;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
@ -852,11 +852,11 @@ body ::-webkit-scrollbar-thumb:window-inactive {
|
|||
|
||||
body ::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(128, 135, 139, 0.8);
|
||||
}
|
||||
} */
|
||||
|
||||
/* Inverted UI */
|
||||
|
||||
body .ui.inverted::-webkit-scrollbar-track {
|
||||
/* body .ui.inverted::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
|
@ -870,7 +870,7 @@ body .ui.inverted::-webkit-scrollbar-thumb:window-inactive {
|
|||
|
||||
body .ui.inverted::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.35);
|
||||
}
|
||||
} */
|
||||
|
||||
/*******************************
|
||||
Global Overrides
|
||||
|
|
|
@ -2,6 +2,5 @@ import Input from './components/Input';
|
|||
import Popup from './components/Popup';
|
||||
import Markdown from './components/Markdown';
|
||||
import FilePicker from './components/FilePicker';
|
||||
import DragScroller from './components/DragScroller';
|
||||
|
||||
export { Input, Popup, Markdown, FilePicker, DragScroller };
|
||||
export { Input, Popup, Markdown, FilePicker };
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Button, Popup as SemanticUIPopup } from 'semantic-ui-react';
|
|||
|
||||
import styles from './Popup.module.css';
|
||||
|
||||
export default (WrappedComponent) => {
|
||||
export default (WrappedComponent, defaultProps) => {
|
||||
const Popup = React.memo(({ children, ...props }) => {
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
|
||||
|
@ -49,11 +49,17 @@ export default (WrappedComponent) => {
|
|||
on="click"
|
||||
open={isOpened}
|
||||
position="bottom left"
|
||||
popperModifiers={{
|
||||
preventOverflow: {
|
||||
boundariesElement: 'window',
|
||||
},
|
||||
}}
|
||||
className={styles.wrapper}
|
||||
onOpen={handleOpen}
|
||||
onClose={handleClose}
|
||||
onMouseDown={handleMouseDown}
|
||||
onClick={handleClick}
|
||||
{...defaultProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
>
|
||||
<Button icon="close" onClick={handleClose} className={styles.closeButton} />
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
|
|
|
@ -53,7 +53,7 @@ module.exports = {
|
|||
}
|
||||
|
||||
if (inputs.toBoard.id === inputs.board.id) {
|
||||
delete inputs.toList; // eslint-disable-line no-param-reassign
|
||||
delete inputs.toBoard; // eslint-disable-line no-param-reassign
|
||||
} else {
|
||||
values.boardId = inputs.toBoard.id;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue