@@ -7513,6 +7555,20 @@ exports[`components/calendar/toolbar return calendar, without permissions 1`] =
+
diff --git a/webapp/src/components/calendar/fullCalendar.tsx b/webapp/src/components/calendar/fullCalendar.tsx
index 930ce2a92..b6c7ec678 100644
--- a/webapp/src/components/calendar/fullCalendar.tsx
+++ b/webapp/src/components/calendar/fullCalendar.tsx
@@ -22,6 +22,11 @@ import {useHasCurrentBoardPermissions} from '../../hooks/permissions'
import CardBadges from '../cardBadges'
import './fullcalendar.scss'
+import MenuWrapper from '../../widgets/menuWrapper'
+import IconButton from '../../widgets/buttons/iconButton'
+import CardActionsMenu from '../cardActionsMenu/cardActionsMenu'
+import OptionsIcon from '../../widgets/icons/options'
+import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
const oneDay = 60 * 60 * 24 * 1000
@@ -121,17 +126,33 @@ const CalendarFullView = (props: Props): JSX.Element|null => {
const renderEventContent = (eventProps: EventContentArg): JSX.Element|null => {
const {event} = eventProps
+ const card = cards.find((o) => o.id === event.id) || cards[0]
return (
props.showCard(event.id)}
>
+ {!props.readonly &&
+
+ }/>
+ mutator.deleteBlock(card, 'delete card')}
+ onClickDuplicate={() => {
+ TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})
+ mutator.duplicateCard(card.id, board.id)
+ }}
+ />
+ }
{ event.extendedProps.icon ?
{event.extendedProps.icon}
: undefined }
{event.title || intl.formatMessage({id: 'KanbanCard.untitled', defaultMessage: 'Untitled'})}
+ >{event.title || intl.formatMessage({id: 'CalendarCard.untitled', defaultMessage: 'Untitled'})}
{visiblePropertyTemplates.map((template) => (
{
o.id === event.id) || cards[0]}
+ card={card}
propertyTemplate={template}
showEmptyPlaceholder={false}
/>
))}
{visibleBadges &&
-
o.id === event.id) || cards[0]}/> }
+ }
)
}
diff --git a/webapp/src/components/calendar/fullcalendar.scss b/webapp/src/components/calendar/fullcalendar.scss
index 161cecab4..aa5ea3296 100644
--- a/webapp/src/components/calendar/fullcalendar.scss
+++ b/webapp/src/components/calendar/fullcalendar.scss
@@ -10,6 +10,10 @@
&:hover {
background-color: unset;
+
+ .optionsMenu {
+ display: block;
+ }
}
}
@@ -19,6 +23,20 @@
align-items: flex-start;
}
+ .optionsMenu {
+ background-color: rgb(var(--center-channel-bg-rgb));
+ border-radius: var(--default-rad);
+ display: none;
+ position: absolute;
+ right: 0;
+ top: 0;
+ z-index: 30;
+
+ .IconButton {
+ background-color: rgba(var(--center-channel-color-rgb), 0.13);
+ }
+ }
+
.octo-tooltip {
display: flex;
max-width: 100%;
@@ -180,7 +198,6 @@
background-color: rgb(var(--center-channel-bg-rgb));
box-shadow: var(--elevation-1);
margin: 0 8px 10px;
- overflow: hidden;
padding: 4px 6px;
&:hover::before {
diff --git a/webapp/src/components/cardActionsMenu/__snapshots__/cardActionsMenu.test.tsx.snap b/webapp/src/components/cardActionsMenu/__snapshots__/cardActionsMenu.test.tsx.snap
new file mode 100644
index 000000000..b2296a3f6
--- /dev/null
+++ b/webapp/src/components/cardActionsMenu/__snapshots__/cardActionsMenu.test.tsx.snap
@@ -0,0 +1,300 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`components/cardActionsMenu should match snapshot 1`] = `
+
+
+
+`;
+
+exports[`components/cardActionsMenu should match snapshot w/ children prop 1`] = `
+
+
+
+`;
+
+exports[`components/cardActionsMenu should match snapshot w/ onClickDuplicate prop 1`] = `
+
+
+
+`;
diff --git a/webapp/src/components/cardActionsMenu/cardActionsMenu.test.tsx b/webapp/src/components/cardActionsMenu/cardActionsMenu.test.tsx
new file mode 100644
index 000000000..fff8b636c
--- /dev/null
+++ b/webapp/src/components/cardActionsMenu/cardActionsMenu.test.tsx
@@ -0,0 +1,96 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import '@testing-library/jest-dom'
+import {act, render} from '@testing-library/react'
+import React from 'react'
+import {Provider as ReduxProvider} from 'react-redux'
+
+import { TestBlockFactory } from '../../test/testBlockFactory'
+import {mockDOM, mockStateStore, wrapIntl} from '../../testUtils'
+
+import CardActionsMenu from './cardActionsMenu'
+
+beforeAll(() => {
+ mockDOM()
+})
+
+describe('components/cardActionsMenu', () => {
+ const board = TestBlockFactory.createBoard()
+ board.id = 'boardId'
+
+ const state = {
+ boards: {
+ current: board.id,
+ boards: {
+ [board.id]: board,
+ },
+ templates: [],
+ myBoardMemberships: {
+ [board.id]: {userId: 'user_id_1', schemeAdmin: true},
+ },
+ },
+ teams: {
+ current: {id: 'team-id'},
+ },
+ users: {
+ me: {
+ id: 'user_id_1',
+ },
+ },
+ }
+ const store = mockStateStore([], state)
+
+ test('should match snapshot', async () => {
+ let container
+ await act(async () => {
+ const result = render(wrapIntl(
+
+
+ ,
+ ))
+ container = result.container
+ })
+ expect(container).toMatchSnapshot()
+ })
+
+ test('should match snapshot w/ onClickDuplicate prop', async () => {
+ let container
+ await act(async () => {
+ const result = render(wrapIntl(
+
+
+ ,
+ ))
+ container = result.container
+ })
+ expect(container).toMatchSnapshot()
+ })
+
+ test('should match snapshot w/ children prop', async () => {
+ let container
+ await act(async () => {
+ const result = render(wrapIntl(
+
+
+
+ Test.
+
+
+ ,
+ ))
+ container = result.container
+ })
+ expect(container).toMatchSnapshot()
+ })
+})
diff --git a/webapp/src/components/cardActionsMenu/cardActionsMenu.tsx b/webapp/src/components/cardActionsMenu/cardActionsMenu.tsx
new file mode 100644
index 000000000..ee2b8c19e
--- /dev/null
+++ b/webapp/src/components/cardActionsMenu/cardActionsMenu.tsx
@@ -0,0 +1,71 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, { ReactNode } from 'react'
+import {useIntl} from 'react-intl'
+
+import DeleteIcon from '../../widgets/icons/delete'
+import Menu from '../../widgets/menu'
+import BoardPermissionGate from '../permissions/boardPermissionGate'
+import DuplicateIcon from '../../widgets/icons/duplicate'
+import LinkIcon from '../../widgets/icons/Link'
+import {Utils} from '../../utils'
+import {Permission} from '../../constants'
+import {sendFlashMessage} from '../flashMessages'
+import {IUser} from '../../user'
+import {getMe} from '../../store/users'
+import {useAppSelector} from '../../store/hooks'
+
+type Props = {
+ cardId: string
+ onClickDelete: () => void,
+ onClickDuplicate?: () => void
+ children?: ReactNode
+}
+
+export const CardActionsMenu = (props: Props): JSX.Element => {
+ const {cardId} = props
+
+ const me = useAppSelector
(getMe)
+ const intl = useIntl()
+
+ return (
+
+ )
+}
+
+export default CardActionsMenu
diff --git a/webapp/src/components/cardDialog.tsx b/webapp/src/components/cardDialog.tsx
index f03771e44..627618278 100644
--- a/webapp/src/components/cardDialog.tsx
+++ b/webapp/src/components/cardDialog.tsx
@@ -14,8 +14,6 @@ import {useAppSelector} from '../store/hooks'
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../telemetry/telemetryClient'
import {Utils} from '../utils'
import CompassIcon from '../widgets/icons/compassIcon'
-import DeleteIcon from '../widgets/icons/delete'
-import LinkIcon from '../widgets/icons/Link'
import Menu from '../widgets/menu'
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../components/confirmationDialogBox'
@@ -32,9 +30,9 @@ import BoardPermissionGate from './permissions/boardPermissionGate'
import CardDetail from './cardDetail/cardDetail'
import Dialog from './dialog'
-import {sendFlashMessage} from './flashMessages'
import './cardDialog.scss'
+import CardActionsMenu from './cardActionsMenu/cardActionsMenu'
type Props = {
board: Board
@@ -110,32 +108,10 @@ const CardDialog = (props: Props): JSX.Element => {
}
const menu = (
-
+
)
const followActionButton = (following: boolean): React.ReactNode => {
diff --git a/webapp/src/components/gallery/__snapshots__/gallery.test.tsx.snap b/webapp/src/components/gallery/__snapshots__/gallery.test.tsx.snap
index a2582d800..d4a90e6d4 100644
--- a/webapp/src/components/gallery/__snapshots__/gallery.test.tsx.snap
+++ b/webapp/src/components/gallery/__snapshots__/gallery.test.tsx.snap
@@ -282,6 +282,7 @@ exports[`src/components/gallery/Gallery should match snapshot 1`] = `
/>