feat: Enhance card actions menu with separators and action bar
This commit is contained in:
@@ -114,6 +114,103 @@ const CardActionsStep = React.memo(({ cardId, defaultStep, onNameEdit, onClose }
|
||||
const [t] = useTranslation();
|
||||
const [step, openStep, handleBack] = useSteps(defaultStep || null);
|
||||
|
||||
const withActionBar = useMemo(() => {
|
||||
let menuItemsTotal = 0;
|
||||
let actionBarItemsTotal = 0;
|
||||
|
||||
if (card.type === CardTypes.PROJECT && canUseMembers) menuItemsTotal += 1;
|
||||
if (canUseLabels) menuItemsTotal += 1;
|
||||
if (card.type !== CardTypes.PROJECT && canUseMembers) menuItemsTotal += 1;
|
||||
if (card.type === CardTypes.PROJECT && canEditDueDate) menuItemsTotal += 1;
|
||||
if (card.type === CardTypes.PROJECT && canEditStopwatch) menuItemsTotal += 1;
|
||||
if (canEditName) menuItemsTotal += 1;
|
||||
if (!board.limitCardTypesToDefaultOne && canEditType) menuItemsTotal += 1;
|
||||
if (canDuplicate) menuItemsTotal += 1;
|
||||
if (canMove) menuItemsTotal += 1;
|
||||
if (prevList && canRestore) menuItemsTotal += 1;
|
||||
|
||||
if (canCopy) {
|
||||
menuItemsTotal += 1;
|
||||
actionBarItemsTotal += 1;
|
||||
}
|
||||
if (canCut) {
|
||||
menuItemsTotal += 1;
|
||||
actionBarItemsTotal += 1;
|
||||
}
|
||||
if (list.type !== ListTypes.ARCHIVE && canArchive) {
|
||||
menuItemsTotal += 1;
|
||||
actionBarItemsTotal += 1;
|
||||
}
|
||||
if (canDelete) {
|
||||
menuItemsTotal += 1;
|
||||
actionBarItemsTotal += 1;
|
||||
}
|
||||
|
||||
return menuItemsTotal > 4 && actionBarItemsTotal > 1;
|
||||
}, [
|
||||
board.limitCardTypesToDefaultOne,
|
||||
card.type,
|
||||
list.type,
|
||||
prevList,
|
||||
canEditType,
|
||||
canEditName,
|
||||
canEditDueDate,
|
||||
canEditStopwatch,
|
||||
canCopy,
|
||||
canCut,
|
||||
canDuplicate,
|
||||
canMove,
|
||||
canRestore,
|
||||
canArchive,
|
||||
canDelete,
|
||||
canUseMembers,
|
||||
canUseLabels,
|
||||
]);
|
||||
|
||||
const hasTopSection = useMemo(() => {
|
||||
return (
|
||||
(card.type === CardTypes.PROJECT && canUseMembers) ||
|
||||
canUseLabels ||
|
||||
(card.type !== CardTypes.PROJECT && canUseMembers) ||
|
||||
(card.type === CardTypes.PROJECT && canEditDueDate) ||
|
||||
(card.type === CardTypes.PROJECT && canEditStopwatch) ||
|
||||
canEditName ||
|
||||
(!board.limitCardTypesToDefaultOne && canEditType)
|
||||
);
|
||||
}, [
|
||||
board.limitCardTypesToDefaultOne,
|
||||
card.type,
|
||||
canEditType,
|
||||
canEditName,
|
||||
canEditDueDate,
|
||||
canEditStopwatch,
|
||||
canUseMembers,
|
||||
canUseLabels,
|
||||
]);
|
||||
|
||||
const hasBottomSection = useMemo(() => {
|
||||
return (
|
||||
(canCopy && !withActionBar) ||
|
||||
(canCut && !withActionBar) ||
|
||||
canDuplicate ||
|
||||
canMove ||
|
||||
(prevList && canRestore) ||
|
||||
(list.type !== ListTypes.ARCHIVE && canArchive && !withActionBar) ||
|
||||
(canDelete && !withActionBar)
|
||||
);
|
||||
}, [
|
||||
list.type,
|
||||
prevList,
|
||||
canCopy,
|
||||
canCut,
|
||||
canDuplicate,
|
||||
canMove,
|
||||
canRestore,
|
||||
canArchive,
|
||||
canDelete,
|
||||
withActionBar,
|
||||
]);
|
||||
|
||||
const handleTypeSelect = useCallback(
|
||||
(type) => {
|
||||
dispatch(
|
||||
@@ -299,22 +396,6 @@ const CardActionsStep = React.memo(({ cardId, defaultStep, onNameEdit, onClose }
|
||||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<Menu secondary vertical className={styles.menu}>
|
||||
{canEditName && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditNameClick}>
|
||||
<Icon name="edit outline" className={styles.menuItemIcon} />
|
||||
{t('action.editTitle', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{!board.limitCardTypesToDefaultOne && canEditType && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditTypeClick}>
|
||||
<Icon name="map outline" className={styles.menuItemIcon} />
|
||||
{t('action.editType', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{card.type === CardTypes.PROJECT && canUseMembers && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleMembersClick}>
|
||||
<Icon name="user outline" className={styles.menuItemIcon} />
|
||||
@@ -331,7 +412,7 @@ const CardActionsStep = React.memo(({ cardId, defaultStep, onNameEdit, onClose }
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{card.type === CardTypes.STORY && canUseMembers && (
|
||||
{card.type !== CardTypes.PROJECT && canUseMembers && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleMembersClick}>
|
||||
<Icon name="user outline" className={styles.menuItemIcon} />
|
||||
{t('common.members', {
|
||||
@@ -355,7 +436,24 @@ const CardActionsStep = React.memo(({ cardId, defaultStep, onNameEdit, onClose }
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{canCopy && (
|
||||
{canEditName && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditNameClick}>
|
||||
<Icon name="edit outline" className={styles.menuItemIcon} />
|
||||
{t('action.editTitle', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{!board.limitCardTypesToDefaultOne && canEditType && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditTypeClick}>
|
||||
<Icon name="map outline" className={styles.menuItemIcon} />
|
||||
{t('action.editType', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{hasTopSection && hasBottomSection && <hr className={styles.divider} />}
|
||||
{canCopy && !withActionBar && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleCopyClick}>
|
||||
<Icon name="copy outline" className={styles.menuItemIcon} />
|
||||
{t('action.copyCard', {
|
||||
@@ -363,7 +461,7 @@ const CardActionsStep = React.memo(({ cardId, defaultStep, onNameEdit, onClose }
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{canCut && (
|
||||
{canCut && !withActionBar && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleCutClick}>
|
||||
<Icon name="cut" className={styles.menuItemIcon} />
|
||||
{t('action.cutCard', {
|
||||
@@ -396,7 +494,7 @@ const CardActionsStep = React.memo(({ cardId, defaultStep, onNameEdit, onClose }
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{list.type !== ListTypes.ARCHIVE && canArchive && (
|
||||
{list.type !== ListTypes.ARCHIVE && canArchive && !withActionBar && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleArchiveClick}>
|
||||
<Icon name="folder open outline" className={styles.menuItemIcon} />
|
||||
{t('action.archiveCard', {
|
||||
@@ -404,7 +502,7 @@ const CardActionsStep = React.memo(({ cardId, defaultStep, onNameEdit, onClose }
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{canDelete && (
|
||||
{canDelete && !withActionBar && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
|
||||
<Icon name="trash alternate outline" className={styles.menuItemIcon} />
|
||||
{isInTrashList
|
||||
@@ -416,6 +514,69 @@ const CardActionsStep = React.memo(({ cardId, defaultStep, onNameEdit, onClose }
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{withActionBar && (
|
||||
<>
|
||||
<hr className={styles.divider} />
|
||||
<div className={styles.actionBar}>
|
||||
{canCopy && (
|
||||
/* eslint-disable-next-line jsx-a11y/anchor-is-valid,
|
||||
jsx-a11y/click-events-have-key-events,
|
||||
jsx-a11y/no-static-element-interactions */
|
||||
<a className={styles.actionBarItem} onClick={handleCopyClick}>
|
||||
<Icon fitted name="copy outline" />
|
||||
<span className={styles.actionBarItemText}>
|
||||
{t('action.copy', {
|
||||
context: 'title',
|
||||
})}
|
||||
</span>
|
||||
</a>
|
||||
)}
|
||||
{canCut && (
|
||||
/* eslint-disable-next-line jsx-a11y/anchor-is-valid,
|
||||
jsx-a11y/click-events-have-key-events,
|
||||
jsx-a11y/no-static-element-interactions */
|
||||
<a className={styles.actionBarItem} onClick={handleCutClick}>
|
||||
<Icon fitted name="cut" />
|
||||
<span className={styles.actionBarItemText}>
|
||||
{t('action.cut', {
|
||||
context: 'title',
|
||||
})}
|
||||
</span>
|
||||
</a>
|
||||
)}
|
||||
{list.type !== ListTypes.ARCHIVE && canArchive && (
|
||||
/* eslint-disable-next-line jsx-a11y/anchor-is-valid,
|
||||
jsx-a11y/click-events-have-key-events,
|
||||
jsx-a11y/no-static-element-interactions */
|
||||
<a className={styles.actionBarItem} onClick={handleArchiveClick}>
|
||||
<Icon fitted name="archive" />
|
||||
<span className={styles.actionBarItemText}>
|
||||
{t('action.archive', {
|
||||
context: 'title',
|
||||
})}
|
||||
</span>
|
||||
</a>
|
||||
)}
|
||||
{canDelete && (
|
||||
/* eslint-disable-next-line jsx-a11y/anchor-is-valid,
|
||||
jsx-a11y/click-events-have-key-events,
|
||||
jsx-a11y/no-static-element-interactions */
|
||||
<a className={styles.actionBarItem} onClick={handleDeleteClick}>
|
||||
<Icon fitted name="trash alternate outline" />
|
||||
<span className={styles.actionBarItemText}>
|
||||
{isInTrashList
|
||||
? t('action.deleteForever', {
|
||||
context: 'title',
|
||||
})
|
||||
: t('action.delete', {
|
||||
context: 'title',
|
||||
})}
|
||||
</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</Popup.Content>
|
||||
</>
|
||||
|
||||
@@ -4,6 +4,47 @@
|
||||
*/
|
||||
|
||||
:global(#app) {
|
||||
.actionBar {
|
||||
display: flex;
|
||||
margin: -7px 0;
|
||||
}
|
||||
|
||||
.actionBarItem {
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
font-size: 13px;
|
||||
padding: 6px;
|
||||
row-gap: 6px;
|
||||
user-select: none;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.actionBarItemText {
|
||||
display: -webkit-box;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.divider {
|
||||
background: #eee;
|
||||
border: 0;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
margin: 0 -12px -5px;
|
||||
width: calc(100% + 24px);
|
||||
|
||||
Reference in New Issue
Block a user