1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-08-09 15:35:29 +02:00

feat: Added column collapsing with card counts support

This commit is contained in:
RAR 2023-01-12 22:19:17 +01:00
parent e009ceaccd
commit 7cdac9ca9f
9 changed files with 152 additions and 18 deletions

View file

@ -10,6 +10,7 @@ import styles from './ListAdd.module.scss';
const DEFAULT_DATA = {
name: '',
isCollapsed: false,
};
const ListAdd = React.memo(({ onCreate, onClose }) => {

View file

@ -16,14 +16,22 @@ import styles from './List.module.scss';
const List = React.memo(
// eslint-disable-next-line prettier/prettier
({ id, index, name, isPersisted, isFiltered, cardIds, cardIdsFull, canEdit, onUpdate, onDelete, onCardCreate }) => {
({ id, index, name, isPersisted, isCollapsed, isFiltered, cardIds, cardIdsFull, canEdit, onUpdate, onDelete, onCardCreate }) => {
const [t] = useTranslation();
const [isAddCardOpened, setIsAddCardOpened] = useState(false);
const nameEdit = useRef(null);
const listWrapper = useRef(null);
const handleHeaderClick = useCallback(() => {
const handleToggleCollapseClick = useCallback(() => {
if (isPersisted && canEdit) {
onUpdate({
isCollapsed: !isCollapsed,
});
}
}, [isPersisted, canEdit, onUpdate, isCollapsed]);
const handleHeaderNameClick = useCallback(() => {
if (isPersisted && canEdit) {
nameEdit.current.open();
}
@ -97,6 +105,45 @@ const List = React.memo(
);
};
if (isCollapsed) {
return (
<Draggable
draggableId={`list:${id}`}
index={index}
isDragDisabled={!isPersisted || !canEdit}
>
{({ innerRef, draggableProps, dragHandleProps }) => (
<div
{...draggableProps} // eslint-disable-line react/jsx-props-no-spreading
data-drag-scroller
ref={innerRef}
className={styles.innerWrapperCollapsed}
>
<div className={styles.outerWrapper}>
<div
{...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading
className={styles.headerCollapsed}
>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */}
<div
className={classNames(
styles.headerCollapseButtonCollapsed,
canEdit && styles.headerEditable,
)}
onClick={handleToggleCollapseClick}
>
<Icon fitted name="triangle down" size="mid" />
</div>
<div className={styles.headerNameCollapsed}>{name}</div>
<div className={styles.headerCardsCountCollapsed}>{cardsCountText()}</div>
</div>
</div>
</div>
)}
</Draggable>
);
}
return (
<Draggable draggableId={`list:${id}`} index={index} isDragDisabled={!isPersisted || !canEdit}>
{({ innerRef, draggableProps, dragHandleProps }) => (
@ -107,15 +154,30 @@ const List = React.memo(
className={styles.innerWrapper}
>
<div className={styles.outerWrapper}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */}
<div
{...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading
className={classNames(styles.header, canEdit && styles.headerEditable)}
onClick={handleHeaderClick}
className={styles.header}
>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */}
<div
className={classNames(
styles.headerCollapseButton,
canEdit && styles.headerEditable,
)}
onClick={handleToggleCollapseClick}
>
<Icon fitted name="triangle right" size="mid" />
</div>
<NameEdit ref={nameEdit} defaultValue={name} onUpdate={handleNameUpdate}>
<div className={styles.headerName}>{name}</div>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */}
<div
className={classNames(styles.headerName, canEdit && styles.headerEditable)}
onClick={handleHeaderNameClick}
>
{name}
</div>
</NameEdit>
{isPersisted && canEdit && (
<ActionsPopup
@ -128,7 +190,7 @@ const List = React.memo(
</Button>
</ActionsPopup>
)}
<div className={styles.cardsCount}>{cardsCountText()}</div>
<div className={styles.headerCardsCount}>{cardsCountText()}</div>
</div>
<div
ref={listWrapper}
@ -164,6 +226,7 @@ List.propTypes = {
id: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
isCollapsed: PropTypes.bool.isRequired,
isPersisted: PropTypes.bool.isRequired,
isFiltered: PropTypes.bool.isRequired,
cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types

View file

@ -39,11 +39,6 @@
min-height: 1px;
}
.cardsCount {
color: #798d99;
font-size: 12px;
}
.cardsInnerWrapper {
max-height: calc(100vh - 268px);
overflow-x: hidden;
@ -80,7 +75,7 @@
.header {
outline: none;
padding: 6px 36px 4px 8px;
padding: 4px 36px 4px 10px;
position: relative;
&:hover .target {
@ -88,6 +83,15 @@
}
}
.headerCollapsed {
outline: none;
height: calc(100vh - 182px);
&:hover .target {
opacity: 1;
}
}
.headerEditable {
cursor: pointer;
}
@ -119,15 +123,58 @@
outline: none;
overflow: hidden;
overflow-wrap: break-word;
padding: 4px 8px;
word-break: break-word;
}
.headerNameCollapsed {
color: #17394d;
font-weight: bold;
outline: none;
overflow: hidden;
writing-mode: vertical-rl;
padding: 0px 5px 10px 5px;
text-overflow: ellipsis;
white-space: nowrap;
max-height: 85%;
}
.headerCollapseButton {
width: 10px;
height: 30px;
padding: 2px 0px 0px 2px;
position: absolute;
left: 2px;
top: 2px;
}
.headerCollapseButtonCollapsed {
height: 30px;
text-align: center;
}
.headerCardsCount {
color: #798d99;
font-size: 12px;
}
.headerCardsCountCollapsed {
color: #798d99;
font-size: 12px;
max-height: 10%;
writing-mode: vertical-rl;
padding: 0px 5px 10px 5px;
}
.innerWrapper {
margin-right: 8px;
width: 272px;
}
.innerWrapperCollapsed {
margin-right: 8px;
width: 30px;
}
.outerWrapper {
background: #dfe3e6;
border-radius: 3px;

View file

@ -11,7 +11,7 @@ const makeMapStateToProps = () => {
const selectCardIdsByListId = selectors.makeSelectCardIdsByListId();
return (state, { id, index }) => {
const { name, isPersisted } = selectListById(state, id);
const { name, isPersisted, isCollapsed } = selectListById(state, id);
const { cardIds, cardIdsFull, isFiltered } = selectCardIdsByListId(state, id);
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
@ -22,6 +22,7 @@ const makeMapStateToProps = () => {
id,
index,
name,
isCollapsed,
isPersisted,
isFiltered,
cardIds,

View file

@ -10,6 +10,7 @@ export default class extends BaseModel {
id: attr(),
position: attr(),
name: attr(),
isCollapsed: attr(),
boardId: fk({
to: 'Board',
as: 'board',

View file

@ -22,6 +22,10 @@ module.exports = {
type: 'string',
required: true,
},
isCollapsed: {
type: 'boolean',
required: true,
},
},
exits: {
@ -53,7 +57,7 @@ module.exports = {
throw Errors.NOT_ENOUGH_RIGHTS;
}
const values = _.pick(inputs, ['position', 'name']);
const values = _.pick(inputs, ['position', 'name', 'isCollapsed']);
const list = await sails.helpers.lists.createOne.with({
values: {

View file

@ -21,6 +21,9 @@ module.exports = {
type: 'string',
isNotEmptyString: true,
},
isCollapsed: {
type: 'boolean',
},
},
exits: {
@ -52,7 +55,7 @@ module.exports = {
throw Errors.NOT_ENOUGH_RIGHTS;
}
const values = _.pick(inputs, ['position', 'name']);
const values = _.pick(inputs, ['position', 'name', 'isCollapsed']);
list = await sails.helpers.lists.updateOne.with({
values,

View file

@ -19,6 +19,11 @@ module.exports = {
type: 'string',
required: true,
},
isCollapsed: {
type: 'boolean',
required: true,
columnName: 'is_collapsed',
},
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
// ║╣ ║║║╠╩╗║╣ ║║╚═╗

View file

@ -0,0 +1,9 @@
module.exports.up = (knex) =>
knex.schema.alterTable('list', (table) => {
table.boolean('is_collapsed').notNullable().defaultTo(false);
});
module.exports.down = (knex) =>
knex.schema.alterTable('list', (table) => {
table.dropColumn('is_collapsed');
});