1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-11-24 08:22:29 +02:00

Added tests

This commit is contained in:
Harshil Sharma 2023-02-06 15:35:52 +05:30
parent e2ce4b76c8
commit e5bc177345
40 changed files with 458 additions and 240 deletions

View File

@ -19,6 +19,7 @@ import {Permission} from '../../../../webapp/src/constants'
import './rhsChannelBoardItem.scss'
import BoardPermissionGate from '../../../../webapp/src/components/permissions/boardPermissionGate'
import {MenuText} from '../../../../webapp/src/widgets/menu/menu'
const windowAny = (window as SuiteWindow)
@ -66,7 +67,7 @@ const RHSChannelBoardItem = (props: Props) => {
teamId={team.id}
permissions={[Permission.ManageBoardRoles]}
>
<Menu.Text
<MenuText
key={`unlinkBoard-${board.id}`}
id='unlinkBoard'
name={intl.formatMessage({id: 'rhs-boards.unlink-board', defaultMessage: 'Unlink board'})}
@ -82,7 +83,7 @@ const RHSChannelBoardItem = (props: Props) => {
permissions={[Permission.ManageBoardRoles]}
invert={true}
>
<Menu.Text
<MenuText
key={`unlinkBoard-${board.id}`}
id='unlinkBoard'
disabled={true}

View File

@ -4,12 +4,13 @@
import React from 'react'
import {useIntl} from 'react-intl'
import {MenuText} from '../widgets/menu/menu'
import {BlockTypes, Block} from '../blocks/block'
import {Card} from '../blocks/card'
import mutator from '../mutator'
import octoClient from '../octoClient'
import {Utils} from '../utils'
import Menu from '../widgets/menu'
import {contentRegistry} from './content/contentRegistry'
@ -31,7 +32,7 @@ const AddContentMenuItem = (props: Props): JSX.Element => {
}
return (
<Menu.Text
<MenuText
key={type}
id={type}
name={handler.getDisplayText(intl)}

View File

@ -26,6 +26,7 @@ import {TOUR_SIDEBAR, SidebarTourSteps} from '../../components/onboardingTour'
import IconButton from '../../widgets/buttons/iconButton'
import SearchForBoardsTourStep from '../../components/onboardingTour/searchForBoards/searchForBoards'
import {MenuText} from '../../widgets/menu/menu'
type Props = {
onBoardTemplateSelectorOpen: () => void
@ -109,13 +110,13 @@ const BoardsSwitcher = (props: Props): JSX.Element => {
icon={<AddIcon/>}
/>
<Menu>
<Menu.Text
<MenuText
id='create-new-board-option'
icon={<CompassIcon icon='plus'/>}
onClick={props.onBoardTemplateSelectorOpen}
name='Create new board'
/>
<Menu.Text
<MenuText
id='createNewCategory'
name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.CreateNew', defaultMessage: 'Create New Category'})}
icon={

View File

@ -16,6 +16,7 @@ import {IUser} from '../../user'
import {getMe} from '../../store/users'
import {useAppSelector} from '../../store/hooks'
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
import {MenuText} from '../../widgets/menu/menu'
type Props = {
cardId: string
@ -46,14 +47,14 @@ export const CardActionsMenu = (props: Props): JSX.Element => {
return (
<Menu position='left'>
<BoardPermissionGate permissions={[Permission.ManageBoardCards]}>
<Menu.Text
<MenuText
icon={<DeleteIcon/>}
id='delete'
name={intl.formatMessage({id: 'CardActionsMenu.delete', defaultMessage: 'Delete'})}
onClick={handleDeleteCard}
/>
{props.onClickDuplicate &&
<Menu.Text
<MenuText
icon={<DuplicateIcon/>}
id='duplicate'
name={intl.formatMessage({id: 'CardActionsMenu.duplicate', defaultMessage: 'Duplicate'})}
@ -61,7 +62,7 @@ export const CardActionsMenu = (props: Props): JSX.Element => {
/>}
</BoardPermissionGate>
{me?.id !== 'single-user' &&
<Menu.Text
<MenuText
icon={<LinkIcon/>}
id='copy'
name={intl.formatMessage({id: 'CardActionsMenu.copyLink', defaultMessage: 'Copy link'})}

View File

@ -7,6 +7,7 @@ import {BlockTypes} from '../../blocks/block'
import {Utils} from '../../utils'
import Button from '../../widgets/buttons/button'
import Menu from '../../widgets/menu'
import {MenuText} from '../../widgets/menu/menu'
import MenuWrapper from '../../widgets/menuWrapper'
import {contentRegistry} from '../content/contentRegistry'
@ -27,7 +28,7 @@ function addContentMenu(intl: IntlShape, type: BlockTypes): JSX.Element {
}, [cardDetail, handler])
return (
<Menu.Text
<MenuText
key={type}
id={type}
name={handler.getDisplayText(intl)}

View File

@ -17,6 +17,7 @@ import Tooltip from '../../widgets/tooltip'
import GuestBadge from '../../widgets/guestBadge'
import './comment.scss'
import {MenuText} from '../../widgets/menu/menu'
type Props = {
comment: Block
@ -55,7 +56,7 @@ const Comment: FC<Props> = (props: Props) => {
<MenuWrapper>
<IconButton icon={<OptionsIcon/>}/>
<Menu position='left'>
<Menu.Text
<MenuText
icon={<DeleteIcon/>}
id='delete'
name={intl.formatMessage({id: 'Comment.delete', defaultMessage: 'Delete'})}

View File

@ -16,7 +16,6 @@ import {getCardAttachments, updateAttachments, updateUploadPrecent} from '../sto
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../telemetry/telemetryClient'
import {Utils} from '../utils'
import CompassIcon from '../widgets/icons/compassIcon'
import Menu from '../widgets/menu'
import {sendFlashMessage} from '../components/flashMessages'
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../components/confirmationDialogBox'
@ -32,6 +31,8 @@ import {Permission} from '../constants'
import {Block, createBlock} from '../blocks/block'
import {AttachmentBlock, createAttachmentBlock} from '../blocks/attachmentBlock'
import {MenuText} from '../widgets/menu/menu'
import BoardPermissionGate from './permissions/boardPermissionGate'
import CardDetail from './cardDetail/cardDetail'
@ -125,7 +126,7 @@ const CardDialog = (props: Props): JSX.Element => {
>
{!isTemplate &&
<BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>
<Menu.Text
<MenuText
id='makeTemplate'
icon={
<CompassIcon

View File

@ -17,14 +17,15 @@ import {getUploadPercent} from '../../store/attachments'
import {useAppSelector} from '../../store/hooks'
import {Permission} from '../../constants'
import ArchivedFile from './archivedFile/archivedFile'
import './attachmentElement.scss'
import CompassIcon from './../../widgets/icons/compassIcon'
import MenuWrapper from './../../widgets/menuWrapper'
import IconButton from './../../widgets/buttons/iconButton'
import Menu from './../../widgets/menu'
import Tooltip from './../../widgets/tooltip'
import {MenuText} from '../../widgets/menu/menu'
import CompassIcon from '../../widgets/icons/compassIcon'
import MenuWrapper from '../../widgets/menuWrapper'
import IconButton from '../../widgets/buttons/iconButton'
import Menu from '../../widgets/menu'
import Tooltip from '../../widgets/tooltip'
import ArchivedFile from './archivedFile/archivedFile'
type Props = {
block: AttachmentBlock
@ -171,7 +172,7 @@ const AttachmentElement = (props: Props): JSX.Element|null => {
/>
<div className='delete-menu'>
<Menu position='left'>
<Menu.Text
<MenuText
id='makeTemplate'
icon={
<CompassIcon

View File

@ -19,6 +19,7 @@ import Menu from '../widgets/menu'
import MenuWrapper from '../widgets/menuWrapper'
import {useSortableWithGrip} from '../hooks/sortable'
import {Position} from '../components/cardDetail/cardDetailContents'
import {MenuSubMenu, MenuText} from '../widgets/menu/menu'
import ContentElement from './content/contentElement'
import AddContentMenuItem from './addContentMenuItem'
@ -76,7 +77,7 @@ const ContentBlock = (props: Props): JSX.Element => {
<IconButton icon={<OptionsIcon/>}/>
<Menu>
{index > 0 &&
<Menu.Text
<MenuText
id='moveUp'
name={intl.formatMessage({id: 'ContentBlock.moveUp', defaultMessage: 'Move up'})}
icon={<SortUpIcon/>}
@ -86,7 +87,7 @@ const ContentBlock = (props: Props): JSX.Element => {
}}
/>}
{index < (contentOrder.length - 1) &&
<Menu.Text
<MenuText
id='moveDown'
name={intl.formatMessage({id: 'ContentBlock.moveDown', defaultMessage: 'Move down'})}
icon={<SortDownIcon/>}
@ -95,7 +96,7 @@ const ContentBlock = (props: Props): JSX.Element => {
mutator.changeCardContentOrder(props.card.boardId, card.id, card.fields.contentOrder, contentOrder)
}}
/>}
<Menu.SubMenu
<MenuSubMenu
id='insertAbove'
name={intl.formatMessage({id: 'ContentBlock.insertAbove', defaultMessage: 'Insert above'})}
icon={<AddIcon/>}
@ -109,8 +110,8 @@ const ContentBlock = (props: Props): JSX.Element => {
cords={cords}
/>
))}
</Menu.SubMenu>
<Menu.Text
</MenuSubMenu>
<MenuText
icon={<DeleteIcon/>}
id='delete'
name={intl.formatMessage({id: 'ContentBlock.Delete', defaultMessage: 'Delete'})}

View File

@ -14,6 +14,8 @@ import Menu from '../widgets/menu'
import OptionsIcon from '../widgets/icons/options'
import {MenuText} from '../widgets/menu/menu'
import Dialog from './dialog'
describe('components/dialog', () => {
@ -48,7 +50,7 @@ describe('components/dialog', () => {
onClose={onCloseMethod}
>
<Menu position='left'>
<Menu.Text
<MenuText
id='test'
icon={<OptionsIcon/>}
name='Test'
@ -70,7 +72,7 @@ describe('components/dialog', () => {
<Dialog
onClose={jest.fn()}
toolsMenu={<Menu position='left'>
<Menu.Text
<MenuText
id='test'
icon={<OptionsIcon/>}
name='Test'
@ -94,7 +96,7 @@ describe('components/dialog', () => {
<Dialog
onClose={jest.fn()}
toolsMenu={<Menu position='left'>
<Menu.Text
<MenuText
id='test'
icon={<OptionsIcon/>}
name='Test'

View File

@ -21,6 +21,7 @@ import {Constants} from '../../constants'
import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../telemetry/telemetryClient'
import './globalHeaderSettingsMenu.scss'
import {MenuSubMenu, MenuText, MenuSwitch} from '../../widgets/menu/menu'
type Props = {
history: History<unknown>
@ -45,12 +46,12 @@ const GlobalHeaderSettingsMenu = (props: Props) => {
<SettingsIcon/>
</div>
<Menu position='left'>
<Menu.SubMenu
<MenuSubMenu
id='import'
name={intl.formatMessage({id: 'Sidebar.import', defaultMessage: 'Import'})}
position='left-bottom'
>
<Menu.Text
<MenuText
id='import_archive'
name={intl.formatMessage({id: 'Sidebar.import-archive', defaultMessage: 'Import archive'})}
onClick={async () => {
@ -60,7 +61,7 @@ const GlobalHeaderSettingsMenu = (props: Props) => {
/>
{
Constants.imports.map((i) => (
<Menu.Text
<MenuText
key={`${i.id}-import`}
id={`${i.id}-import`}
name={i.displayName}
@ -71,15 +72,15 @@ const GlobalHeaderSettingsMenu = (props: Props) => {
/>
))
}
</Menu.SubMenu>
<Menu.SubMenu
</MenuSubMenu>
<MenuSubMenu
id='lang'
name={intl.formatMessage({id: 'Sidebar.set-language', defaultMessage: 'Set language'})}
position='left-bottom'
>
{
Constants.languages.map((language) => (
<Menu.Text
<MenuText
key={language.code}
id={`${language.name}-lang`}
name={language.displayName}
@ -88,8 +89,8 @@ const GlobalHeaderSettingsMenu = (props: Props) => {
/>
))
}
</Menu.SubMenu>
<Menu.Switch
</MenuSubMenu>
<MenuSwitch
id='random-icons'
name={intl.formatMessage({id: 'Sidebar.random-icons', defaultMessage: 'Random icons'})}
isOn={randomIcons}
@ -97,7 +98,7 @@ const GlobalHeaderSettingsMenu = (props: Props) => {
suppressItemClicked={true}
/>
{me?.is_guest !== true &&
<Menu.Text
<MenuText
id='product-tour'
className='product-tour'
name={intl.formatMessage({id: 'Sidebar.product-tour', defaultMessage: 'Product tour'})}

View File

@ -10,6 +10,7 @@ import EmojiIcon from '../widgets/icons/emoji'
import Menu from '../widgets/menu'
import MenuWrapper from '../widgets/menuWrapper'
import './iconSelector.scss'
import {MenuText, MenuSubMenu} from '../widgets/menu/menu'
type Props = {
readonly?: boolean
@ -29,20 +30,20 @@ const IconSelector = React.memo((props: Props) => {
<MenuWrapper>
{props.iconElement}
<Menu>
<Menu.Text
<MenuText
id='random'
icon={<RandomIcon/>}
name={intl.formatMessage({id: 'ViewTitle.random-icon', defaultMessage: 'Random'})}
onClick={props.onAddRandomIcon}
/>
<Menu.SubMenu
<MenuSubMenu
id='pick'
icon={<EmojiIcon/>}
name={intl.formatMessage({id: 'ViewTitle.pick-icon', defaultMessage: 'Pick icon'})}
>
<EmojiPicker onSelect={props.onSelectEmoji}/>
</Menu.SubMenu>
<Menu.Text
</MenuSubMenu>
<MenuText
id='remove'
icon={<DeleteIcon/>}
name={intl.formatMessage({id: 'ViewTitle.remove-icon', defaultMessage: 'Remove icon'})}

View File

@ -23,6 +23,8 @@ import {useHasCurrentBoardPermissions} from '../../hooks/permissions'
import BoardPermissionGate from '../permissions/boardPermissionGate'
import {MenuText, MenuSeparator, MenuColor} from '../../widgets/menu/menu'
import {KanbanCalculation} from './calculation/calculation'
type Props = {
@ -164,7 +166,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
<MenuWrapper>
<IconButton icon={<OptionsIcon/>}/>
<Menu>
<Menu.Text
<MenuText
id='hide'
icon={<HideIcon/>}
name={intl.formatMessage({id: 'BoardComponent.hide', defaultMessage: 'Hide'})}
@ -172,15 +174,15 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
/>
{canEditOption &&
<>
<Menu.Text
<MenuText
id='delete'
icon={<DeleteIcon/>}
name={intl.formatMessage({id: 'BoardComponent.delete', defaultMessage: 'Delete'})}
onClick={() => mutator.deletePropertyOption(board.id, board.cardProperties, groupByProperty!, group.option)}
/>
<Menu.Separator/>
<MenuSeparator/>
{Object.entries(Constants.menuColors).map(([key, color]) => (
<Menu.Color
<MenuColor
key={key}
id={key}
name={color}

View File

@ -15,6 +15,7 @@ import {BoardGroup} from '../../blocks/board'
import {BoardView} from '../../blocks/boardView'
import Button from '../../widgets/buttons/button'
import {MenuText} from '../../widgets/menu/menu'
type Props = {
activeView: BoardView
@ -59,7 +60,7 @@ export default function KanbanHiddenColumnItem(props: Props): JSX.Element {
{group.option.value}
</Label>
<Menu>
<Menu.Text
<MenuText
id='show'
icon={<ShowIcon/>}
name={intl.formatMessage({id: 'BoardComponent.show', defaultMessage: 'Show'})}

View File

@ -24,6 +24,7 @@ import CompassIcon from '../../widgets/icons/compassIcon'
import ConfirmationDialogBox from '../confirmationDialogBox'
import BoardPermissionGate from '../permissions/boardPermissionGate'
import {MenuText} from '../../widgets/menu/menu'
type Props = {
teammateNameDisplay?: string
@ -129,7 +130,7 @@ const ChannelPermissionsRow = (props: Props): JSX.Element => {
/>
</button>
<Menu position='left'>
<Menu.Text
<MenuText
id='Unlink'
icon={<DeleteIcon/>}
name={intl.formatMessage({id: 'BoardMember.unlinkChannel', defaultMessage: 'Unlink'})}

View File

@ -21,6 +21,7 @@ import BoardPermissionGate from '../permissions/boardPermissionGate'
import ConfirmationDialogBox from '../confirmationDialogBox'
import mutator from '../../mutator'
import {MenuText} from '../../widgets/menu/menu'
async function updateBoardType(board: Board, newType: string, newMinimumRole: MemberRole) {
if (board.type === newType && board.minimumRole === newMinimumRole) {
@ -110,7 +111,7 @@ const TeamPermissionsRow = (): JSX.Element => {
</button>
<Menu position='left'>
{!board.isTemplate &&
<Menu.Text
<MenuText
id={MemberRole.Editor}
check={board.minimumRole === undefined || board.minimumRole === MemberRole.Editor}
icon={board.type === BoardTypeOpen && board.minimumRole === MemberRole.Editor ? <CheckIcon/> : <div className='empty-icon'/>}
@ -118,21 +119,21 @@ const TeamPermissionsRow = (): JSX.Element => {
onClick={() => setChangeRoleConfirmation(MemberRole.Editor)}
/>}
{!board.isTemplate &&
<Menu.Text
<MenuText
id={MemberRole.Commenter}
check={board.minimumRole === MemberRole.Commenter}
icon={board.type === BoardTypeOpen && board.minimumRole === MemberRole.Commenter ? <CheckIcon/> : <div className='empty-icon'/>}
name={intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})}
onClick={() => setChangeRoleConfirmation(MemberRole.Commenter)}
/>}
<Menu.Text
<MenuText
id={MemberRole.Viewer}
check={board.minimumRole === MemberRole.Viewer}
icon={board.type === BoardTypeOpen && board.minimumRole === MemberRole.Viewer ? <CheckIcon/> : <div className='empty-icon'/>}
name={intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})}
onClick={() => updateBoardType(board, BoardTypeOpen, MemberRole.Viewer)}
/>
<Menu.Text
<MenuText
id={MemberRole.None}
check={true}
icon={board.type === BoardTypePrivate ? <CheckIcon/> : <div className='empty-icon'/>}

View File

@ -19,6 +19,7 @@ import {useAppSelector} from '../../store/hooks'
import {getCurrentBoard} from '../../store/boards'
import BoardPermissionGate from '../permissions/boardPermissionGate'
import {MenuText, MenuSeparator} from '../../widgets/menu/menu'
type Props = {
user: IUser
@ -82,7 +83,7 @@ const UserPermissionsRow = (props: Props): JSX.Element => {
parentRef={menuWrapperRef}
>
{(board.minimumRole === MemberRole.Viewer || board.minimumRole === MemberRole.None) &&
<Menu.Text
<MenuText
id={MemberRole.Viewer}
check={true}
icon={currentRole === MemberRole.Viewer ? <CheckIcon/> : <div className='empty-icon'/>}
@ -90,14 +91,14 @@ const UserPermissionsRow = (props: Props): JSX.Element => {
onClick={() => props.onUpdateBoardMember(member, MemberRole.Viewer)}
/>}
{!board.isTemplate && (board.minimumRole === MemberRole.None || board.minimumRole === MemberRole.Commenter || board.minimumRole === MemberRole.Viewer) &&
<Menu.Text
<MenuText
id={MemberRole.Commenter}
check={true}
icon={currentRole === MemberRole.Commenter ? <CheckIcon/> : <div className='empty-icon'/>}
name={intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})}
onClick={() => props.onUpdateBoardMember(member, MemberRole.Commenter)}
/>}
<Menu.Text
<MenuText
id={MemberRole.Editor}
check={true}
icon={currentRole === MemberRole.Editor ? <CheckIcon/> : <div className='empty-icon'/>}
@ -105,15 +106,15 @@ const UserPermissionsRow = (props: Props): JSX.Element => {
onClick={() => props.onUpdateBoardMember(member, MemberRole.Editor)}
/>
{user.is_guest !== true &&
<Menu.Text
<MenuText
id={MemberRole.Admin}
check={true}
icon={currentRole === MemberRole.Admin ? <CheckIcon/> : <div className='empty-icon'/>}
name={intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'})}
onClick={() => props.onUpdateBoardMember(member, MemberRole.Admin)}
/>}
<Menu.Separator/>
<Menu.Text
<MenuSeparator/>
<MenuText
id='Remove'
name={intl.formatMessage({id: 'ShareBoard.userPermissionsRemoveMemberText', defaultMessage: 'Remove member'})}
onClick={() => props.onDeleteBoardMember(member)}

View File

@ -40,6 +40,7 @@ import octoClient from '../../octoClient'
import {getCurrentBoardId} from '../../store/boards'
import {UserSettings} from '../../userSettings'
import {Archiver} from '../../archiver'
import {MenuText, MenuSubMenu} from '../../widgets/menu/menu'
const iconForViewType = (viewType: IViewType): JSX.Element => {
switch (viewType) {
@ -82,7 +83,7 @@ const SidebarBoardItem = (props: Props) => {
const generateMoveToCategoryOptions = (boardID: string) => {
return props.allCategories.map((category) => (
<Menu.Text
<MenuText
key={category.id}
id={category.id}
name={category.name}
@ -237,7 +238,7 @@ const SidebarBoardItem = (props: Props) => {
position='auto'
parentRef={boardItemRef}
>
<Menu.SubMenu
<MenuSubMenu
key={`moveBlock-${board.id}`}
id='moveBlock'
className='boardMoveToCategorySubmenu'
@ -246,28 +247,28 @@ const SidebarBoardItem = (props: Props) => {
position='auto'
>
{generateMoveToCategoryOptions(board.id)}
</Menu.SubMenu>
</MenuSubMenu>
{!me?.is_guest &&
<Menu.Text
<MenuText
id='duplicateBoard'
name={intl.formatMessage({id: 'Sidebar.duplicate-board', defaultMessage: 'Duplicate board'})}
icon={<DuplicateIcon/>}
onClick={() => handleDuplicateBoard(board.isTemplate)}
/>}
{!me?.is_guest &&
<Menu.Text
<MenuText
id='templateFromBoard'
name={intl.formatMessage({id: 'Sidebar.template-from-board', defaultMessage: 'New template from board'})}
icon={<AddIcon/>}
onClick={() => handleDuplicateBoard(true)}
/>}
<Menu.Text
<MenuText
id='exportBoardArchive'
name={intl.formatMessage({id: 'ViewHeader.export-board-archive', defaultMessage: 'Export board archive'})}
icon={<CompassIcon icon='export-variant'/>}
onClick={() => Archiver.exportBoardArchive(board)}
/>
<Menu.Text
<MenuText
id='hideBoard'
name={intl.formatMessage({id: 'HideBoard.MenuOption', defaultMessage: 'Hide board'})}
icon={<CloseIcon/>}
@ -277,7 +278,7 @@ const SidebarBoardItem = (props: Props) => {
boardId={board.id}
permissions={[Permission.DeleteBoard]}
>
<Menu.Text
<MenuText
key={`deleteBlock-${board.id}`}
id='deleteBlock'
className='text-danger'

View File

@ -44,6 +44,8 @@ import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmation
import SidebarCategoriesTourStep from '../../components/onboardingTour/sidebarCategories/sidebarCategories'
import ManageCategoriesTourStep from '../../components/onboardingTour/manageCategories/manageCategories'
import {MenuText, MenuSeparator} from '../../widgets/menu/menu'
import DeleteBoardDialog from './deleteBoardDialog'
import SidebarBoardItem from './sidebarBoardItem'
@ -318,23 +320,23 @@ const SidebarCategory = (props: Props) => {
{
props.categoryBoards.type === 'custom' &&
<React.Fragment>
<Menu.Text
<MenuText
id='updateCategory'
name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.Update', defaultMessage: 'Rename Category'})}
icon={<CompassIcon icon='pencil-outline'/>}
onClick={handleUpdateCategory}
/>
<Menu.Text
<MenuText
id='deleteCategory'
className='text-danger'
name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.Delete', defaultMessage: 'Delete Category'})}
icon={<DeleteIcon/>}
onClick={() => setShowDeleteCategoryDialog(true)}
/>
<Menu.Separator/>
<MenuSeparator/>
</React.Fragment>
}
<Menu.Text
<MenuText
id='createNewCategory'
name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.CreateNew', defaultMessage: 'Create New Category'})}
icon={<CreateNewFolder/>}

View File

@ -26,6 +26,7 @@ import CheckIcon from '../../widgets/icons/check'
import {Constants} from '../../constants'
import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../telemetry/telemetryClient'
import {MenuSubMenu, MenuText, MenuSwitch} from '../../widgets/menu/menu'
type Props = {
activeTheme: string
@ -85,12 +86,12 @@ const SidebarSettingsMenu = (props: Props) => {
/>
</div>
<Menu position='top'>
<Menu.SubMenu
<MenuSubMenu
id='import'
name={intl.formatMessage({id: 'Sidebar.import', defaultMessage: 'Import'})}
position='top'
>
<Menu.Text
<MenuText
id='import_archive'
name={intl.formatMessage({id: 'Sidebar.import-archive', defaultMessage: 'Import archive'})}
onClick={async () => {
@ -100,7 +101,7 @@ const SidebarSettingsMenu = (props: Props) => {
/>
{
Constants.imports.map((i) => (
<Menu.Text
<MenuText
key={`${i.id}-import`}
id={`${i.id}-import`}
name={i.displayName}
@ -111,8 +112,8 @@ const SidebarSettingsMenu = (props: Props) => {
/>
))
}
</Menu.SubMenu>
<Menu.Text
</MenuSubMenu>
<MenuText
id='export'
name={intl.formatMessage({id: 'Sidebar.export-archive', defaultMessage: 'Export archive'})}
onClick={async () => {
@ -122,14 +123,14 @@ const SidebarSettingsMenu = (props: Props) => {
}
}}
/>
<Menu.SubMenu
<MenuSubMenu
id='lang'
name={intl.formatMessage({id: 'Sidebar.set-language', defaultMessage: 'Set language'})}
position='top'
>
{
Constants.languages.map((language) => (
<Menu.Text
<MenuText
key={language.code}
id={`${language.name}-lang`}
name={language.displayName}
@ -138,8 +139,8 @@ const SidebarSettingsMenu = (props: Props) => {
/>
))
}
</Menu.SubMenu>
<Menu.SubMenu
</MenuSubMenu>
<MenuSubMenu
id='theme'
name={intl.formatMessage({id: 'Sidebar.set-theme', defaultMessage: 'Set theme'})}
position='top'
@ -147,7 +148,7 @@ const SidebarSettingsMenu = (props: Props) => {
{
themes.map((theme) =>
(
<Menu.Text
<MenuText
key={theme.id}
id={theme.id}
name={intl.formatMessage({id: `Sidebar.${theme.id}`, defaultMessage: theme.displayName})}
@ -157,8 +158,8 @@ const SidebarSettingsMenu = (props: Props) => {
),
)
}
</Menu.SubMenu>
<Menu.Switch
</MenuSubMenu>
<MenuSwitch
id='random-icons'
name={intl.formatMessage({id: 'Sidebar.random-icons', defaultMessage: 'Random icons'})}
isOn={randomIcons}

View File

@ -19,6 +19,8 @@ import ModalWrapper from '../modalWrapper'
import {IAppWindow} from '../../types'
import {MenuLabel, MenuText, MenuSeparator} from '../../widgets/menu/menu'
import RegistrationLink from './registrationLink'
import './sidebarUserMenu.scss'
@ -55,8 +57,8 @@ const SidebarUserMenu = () => {
</div>
<Menu>
{user && user.username !== 'single-user' && <>
<Menu.Label><b>{user.username}</b></Menu.Label>
<Menu.Text
<MenuLabel><b>{user.username}</b></MenuLabel>
<MenuText
id='logout'
name={intl.formatMessage({id: 'Sidebar.logout', defaultMessage: 'Log out'})}
onClick={async () => {
@ -65,14 +67,14 @@ const SidebarUserMenu = () => {
history.push('/login')
}}
/>
<Menu.Text
<MenuText
id='changePassword'
name={intl.formatMessage({id: 'Sidebar.changePassword', defaultMessage: 'Change password'})}
onClick={async () => {
history.push('/change_password')
}}
/>
<Menu.Text
<MenuText
id='invite'
name={intl.formatMessage({id: 'Sidebar.invite-users', defaultMessage: 'Invite users'})}
onClick={async () => {
@ -80,10 +82,10 @@ const SidebarUserMenu = () => {
}}
/>
<Menu.Separator/>
<MenuSeparator/>
</>}
<Menu.Text
<MenuText
id='about'
name={intl.formatMessage({id: 'Sidebar.about', defaultMessage: 'About Focalboard'})}
onClick={async () => {

View File

@ -82,4 +82,175 @@ describe('components/standardProperties/statusProperty/EditStatusPropertyDialog'
const {container} = render(component)
expect(container).toMatchSnapshot()
})
test('no value in any column', () => {
const noValueConfig: StatusCategory[] = [
{
id: 'category_id_1',
title: 'Not Started',
options: [],
emptyState: {
icon: (<BlackCheckboxOutline/>),
color: '--sys-dnd-indicator-rgb',
text: {
id: 'statusProperty.configDialog.todo.emptyText',
defaultMessage: 'Drag statuses here to consider tasks with these statuses “Not Started”',
},
},
},
{
id: 'category_id_2',
title: 'In progress',
options: [],
emptyState: {
icon: (<ClockOutline/>),
color: '--away-indicator-rgb',
text: {
id: 'statusProperty.configDialog.inProgress.emptyText',
defaultMessage: 'Drag statuses here to consider tasks with these statuses “in progress”',
},
},
},
{
id: 'category_id_3',
title: 'Completed',
options: [],
emptyState: {
icon: (<CheckIcon/>),
color: '--online-indicator-rgb',
text: {
id: 'statusProperty.configDialog.complete.emptyText',
defaultMessage: 'Drag statuses here to consider tasks with these statuses ”Done”',
},
},
},
]
const component = wrapRBDNDContext(
wrapIntl(
<EditStatusPropertyDialog
valueCategories={noValueConfig}
onClose={() => {}}
onUpdate={() => {}}
/>,
))
const {container} = render(component)
expect(container).toMatchSnapshot()
})
test('5 columns', () => {
const initialValueCategoryValue: StatusCategory[] = [
{
id: 'category_id_1',
title: 'Column 1',
options: [
{id: 'status_id_1', value: 'Pending Design', color: 'propColorPurple'},
{id: 'status_id_2', value: 'TODO', color: 'propColorYellow'},
{id: 'status_id_3', value: 'Pending Specs', color: 'propColorGray'},
],
emptyState: {
icon: (<BlackCheckboxOutline/>),
color: '--sys-dnd-indicator-rgb',
text: {
id: 'statusProperty.configDialog.todo.emptyText',
defaultMessage: 'Drag statuses here to consider tasks with these statuses “Not Started”',
},
},
},
{
id: 'category_id_2',
title: 'Column 2',
options: [
{id: 'status_id_4', value: 'In Progress', color: 'propColorBrown'},
{id: 'status_id_5', value: 'In Review', color: 'propColorRed'},
{id: 'status_id_6', value: 'In QA', color: 'propColorPink'},
{id: 'status_id_7', value: 'Awaiting Cherrypick', color: 'propColorOrange'},
],
emptyState: {
icon: (<ClockOutline/>),
color: '--away-indicator-rgb',
text: {
id: 'statusProperty.configDialog.inProgress.emptyText',
defaultMessage: 'Drag statuses here to consider tasks with these statuses “in progress”',
},
},
},
{
id: 'category_id_3',
title: 'Column 3',
options: [
{id: 'status_id_20', value: 'Done', color: 'propColorPink'},
{id: 'status_id_21', value: 'Branch Cut', color: 'propColorGreen'},
{id: 'status_id_22', value: 'Released', color: 'propColorDefault'},
],
emptyState: {
icon: (<CheckIcon/>),
color: '--online-indicator-rgb',
text: {
id: 'statusProperty.configDialog.complete.emptyText',
defaultMessage: 'Drag statuses here to consider tasks with these statuses ”Done”',
},
},
},
{
id: 'category_id_2',
title: 'Column 4',
options: [
{id: 'status_id_54', value: 'Michael Scott', color: 'propColorOrange'},
],
emptyState: {
icon: (<ClockOutline/>),
color: '--away-indicator-rgb',
text: {
id: 'statusProperty.configDialog.inProgress.emptyText',
defaultMessage: 'Drag statuses here to consider tasks with these statuses “in progress”',
},
},
},
{
id: 'category_id_3',
title: 'Column 5',
options: [
{id: 'status_id_22', value: 'Jim Halpert', color: 'propColorDefault'},
],
emptyState: {
icon: (<CheckIcon/>),
color: '--online-indicator-rgb',
text: {
id: 'statusProperty.configDialog.complete.emptyText',
defaultMessage: 'Drag statuses here to consider tasks with these statuses ”Done”',
},
},
},
]
const component = wrapRBDNDContext(
wrapIntl(
<EditStatusPropertyDialog
valueCategories={initialValueCategoryValue}
onClose={() => {}}
onUpdate={() => {}}
/>,
))
const {container} = render(component)
expect(container).toMatchSnapshot()
})
test('0 columns', () => {
const initialValueCategoryValue: StatusCategory[] = []
const component = wrapRBDNDContext(
wrapIntl(
<EditStatusPropertyDialog
valueCategories={initialValueCategoryValue}
onClose={() => {}}
onUpdate={() => {}}
/>,
))
const {container} = render(component)
expect(container).toMatchSnapshot()
})
})

View File

@ -10,7 +10,7 @@ import {Constants} from '../../../constants'
import DragHandle from '../../../widgets/icons/dragHandle'
import EditIcon from '../../../widgets/icons/edit'
import Menu from '../../../widgets/menu/menu'
import Menu, {MenuColor, MenuText} from '../../../widgets/menu/menu'
import MenuWrapper from '../../../widgets/menuWrapper'
import {IPropertyOption} from '../../../blocks/board'
@ -98,7 +98,7 @@ const ValueRow = (props: Props) => {
{
Object.entries(Constants.menuColors).map(
([key, color]: [string, string]) => (
<Menu.Color
<MenuColor
key={key}
id={key}
name={color}
@ -125,13 +125,13 @@ const ValueRow = (props: Props) => {
menuMargin={30}
fixed={true}
>
<Menu.Text
<MenuText
id='editText'
name={'Edit'}
icon={<EditIcon/>}
onClick={handleEditButtonClick}
/>
<Menu.Text
<MenuText
id='deleteOption'
name={'Delete'}
icon={<DeleteIcon/>}

View File

@ -21,6 +21,8 @@ import MenuWrapper from '../../widgets/menuWrapper'
import Editable from '../../widgets/editable'
import Label from '../../widgets/label'
import {MenuText, MenuSeparator, MenuColor} from '../../widgets/menu/menu'
import {useColumnResize} from './tableColumnResizeContext'
type Props = {
@ -122,7 +124,7 @@ const TableGroupHeaderRow = (props: Props): JSX.Element => {
<MenuWrapper>
<IconButton icon={<OptionsIcon/>}/>
<Menu>
<Menu.Text
<MenuText
id='hide'
icon={<HideIcon/>}
name={intl.formatMessage({id: 'BoardComponent.hide', defaultMessage: 'Hide'})}
@ -130,15 +132,15 @@ const TableGroupHeaderRow = (props: Props): JSX.Element => {
/>
{canEditOption &&
<>
<Menu.Text
<MenuText
id='delete'
icon={<DeleteIcon/>}
name={intl.formatMessage({id: 'BoardComponent.delete', defaultMessage: 'Delete'})}
onClick={() => mutator.deletePropertyOption(board.id, board.cardProperties, groupByProperty!, group.option)}
/>
<Menu.Separator/>
<MenuSeparator/>
{Object.entries(Constants.menuColors).map(([key, color]) => (
<Menu.Color
<MenuColor
key={key}
id={key}
name={color}

View File

@ -10,6 +10,7 @@ import {BoardView} from '../../blocks/boardView'
import {Card} from '../../blocks/card'
import mutator from '../../mutator'
import Menu from '../../widgets/menu'
import {MenuText} from '../../widgets/menu/menu'
type Props = {
templateId: string
@ -24,17 +25,17 @@ const TableHeaderMenu: FC<Props> = (props: Props): JSX.Element => {
const intl = useIntl()
return (
<Menu>
<Menu.Text
<MenuText
id='sortAscending'
name={intl.formatMessage({id: 'TableHeaderMenu.sort-ascending', defaultMessage: 'Sort ascending'})}
onClick={() => mutator.changeViewSortOptions(board.id, activeView.id, activeView.fields.sortOptions, [{propertyId: templateId, reversed: false}])}
/>
<Menu.Text
<MenuText
id='sortDescending'
name={intl.formatMessage({id: 'TableHeaderMenu.sort-descending', defaultMessage: 'Sort descending'})}
onClick={() => mutator.changeViewSortOptions(board.id, activeView.id, activeView.fields.sortOptions, [{propertyId: templateId, reversed: true}])}
/>
<Menu.Text
<MenuText
id='insertLeft'
name={intl.formatMessage({id: 'TableHeaderMenu.insert-left', defaultMessage: 'Insert left'})}
onClick={() => {
@ -47,7 +48,7 @@ const TableHeaderMenu: FC<Props> = (props: Props): JSX.Element => {
}
}}
/>
<Menu.Text
<MenuText
id='insertRight'
name={intl.formatMessage({id: 'TableHeaderMenu.insert-right', defaultMessage: 'Insert right'})}
onClick={() => {
@ -62,17 +63,17 @@ const TableHeaderMenu: FC<Props> = (props: Props): JSX.Element => {
/>
{props.templateId !== Constants.titleColumnId &&
<>
<Menu.Text
<MenuText
id='hide'
name={intl.formatMessage({id: 'TableHeaderMenu.hide', defaultMessage: 'Hide'})}
onClick={() => mutator.changeViewVisibleProperties(board.id, activeView.id, activeView.fields.visiblePropertyIds, activeView.fields.visiblePropertyIds.filter((o: string) => o !== templateId))}
/>
<Menu.Text
<MenuText
id='duplicate'
name={intl.formatMessage({id: 'TableHeaderMenu.duplicate', defaultMessage: 'Duplicate'})}
onClick={() => mutator.duplicatePropertyTemplate(board, activeView, templateId)}
/>
<Menu.Text
<MenuText
id='delete'
name={intl.formatMessage({id: 'TableHeaderMenu.delete', defaultMessage: 'Delete'})}
onClick={() => mutator.deleteProperty(board, views, cards, templateId)}

View File

@ -16,6 +16,7 @@ import mutator from '../../mutator'
import {useAppSelector} from '../../store/hooks'
import {getCurrentView} from '../../store/views'
import {getCurrentBoardId} from '../../store/boards'
import {MenuText} from '../../widgets/menu/menu'
type Props = {
addCard: () => void
@ -27,7 +28,7 @@ const EmptyCardButton = (props: Props) => {
const intl = useIntl()
return (
<Menu.Text
<MenuText
icon={<CardIcon/>}
id='empty-template'
name={intl.formatMessage({id: 'ViewHeader.empty-card', defaultMessage: 'Empty card'})}
@ -39,7 +40,7 @@ const EmptyCardButton = (props: Props) => {
<MenuWrapper stopPropagationOnToggle={true}>
<IconButton icon={<OptionsIcon/>}/>
<Menu position='left'>
<Menu.Text
<MenuText
icon={<CheckIcon/>}
id='default'
name={intl.formatMessage({

View File

@ -14,6 +14,7 @@ import Button from '../../widgets/buttons/button'
import Menu from '../../widgets/menu'
import MenuWrapper from '../../widgets/menuWrapper'
import propsRegistry from '../../properties'
import {MenuText} from '../../widgets/menu/menu'
import FilterValue from './filterValue'
@ -46,7 +47,7 @@ const FilterEntry = (props: Props): JSX.Element => {
<MenuWrapper>
<Button>{propertyName}</Button>
<Menu>
<Menu.Text
<MenuText
key={'title'}
id={'title'}
name={'Title'}
@ -64,7 +65,7 @@ const FilterEntry = (props: Props): JSX.Element => {
}}
/>
{board.cardProperties.filter((o: IPropertyTemplate) => propsRegistry.get(o.type).canFilter).map((o: IPropertyTemplate) => (
<Menu.Text
<MenuText
key={o.id}
id={o.id}
name={o.name}
@ -89,22 +90,22 @@ const FilterEntry = (props: Props): JSX.Element => {
<Menu>
{propertyType.filterValueType === 'options' &&
<>
<Menu.Text
<MenuText
id='includes'
name={intl.formatMessage({id: 'Filter.includes', defaultMessage: 'includes'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='notIncludes'
name={intl.formatMessage({id: 'Filter.not-includes', defaultMessage: 'doesn\'t include'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='isEmpty'
name={intl.formatMessage({id: 'Filter.is-empty', defaultMessage: 'is empty'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='isNotEmpty'
name={intl.formatMessage({id: 'Filter.is-not-empty', defaultMessage: 'is not empty'})}
onClick={(id) => props.conditionClicked(id, filter)}
@ -112,12 +113,12 @@ const FilterEntry = (props: Props): JSX.Element => {
</>}
{propertyType.filterValueType === 'person' &&
<>
<Menu.Text
<MenuText
id='includes'
name={intl.formatMessage({id: 'Filter.includes', defaultMessage: 'includes'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='notIncludes'
name={intl.formatMessage({id: 'Filter.not-includes', defaultMessage: 'doesn\'t include'})}
onClick={(id) => props.conditionClicked(id, filter)}
@ -125,12 +126,12 @@ const FilterEntry = (props: Props): JSX.Element => {
</>}
{(propertyType.type === 'person' || propertyType.type === 'multiPerson') &&
<>
<Menu.Text
<MenuText
id='isEmpty'
name={intl.formatMessage({id: 'Filter.is-empty', defaultMessage: 'is empty'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='isNotEmpty'
name={intl.formatMessage({id: 'Filter.is-not-empty', defaultMessage: 'is not empty'})}
onClick={(id) => props.conditionClicked(id, filter)}
@ -138,12 +139,12 @@ const FilterEntry = (props: Props): JSX.Element => {
</>}
{propertyType.filterValueType === 'boolean' &&
<>
<Menu.Text
<MenuText
id='isSet'
name={intl.formatMessage({id: 'Filter.is-set', defaultMessage: 'is set'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='isNotSet'
name={intl.formatMessage({id: 'Filter.is-not-set', defaultMessage: 'is not set'})}
onClick={(id) => props.conditionClicked(id, filter)}
@ -151,37 +152,37 @@ const FilterEntry = (props: Props): JSX.Element => {
</>}
{propertyType.filterValueType === 'text' &&
<>
<Menu.Text
<MenuText
id='is'
name={intl.formatMessage({id: 'Filter.is', defaultMessage: 'is'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='contains'
name={intl.formatMessage({id: 'Filter.contains', defaultMessage: 'contains'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='notContains'
name={intl.formatMessage({id: 'Filter.not-contains', defaultMessage: 'doesn\'t contain'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='startsWith'
name={intl.formatMessage({id: 'Filter.starts-with', defaultMessage: 'starts with'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='notStartsWith'
name={intl.formatMessage({id: 'Filter.not-starts-with', defaultMessage: 'doesn\'t start with'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='endsWith'
name={intl.formatMessage({id: 'Filter.ends-with', defaultMessage: 'ends with'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='notEndsWith'
name={intl.formatMessage({id: 'Filter.not-ends-with', defaultMessage: 'doesn\'t end with'})}
onClick={(id) => props.conditionClicked(id, filter)}
@ -189,17 +190,17 @@ const FilterEntry = (props: Props): JSX.Element => {
</>}
{propertyType.filterValueType === 'date' &&
<>
<Menu.Text
<MenuText
id='is'
name={intl.formatMessage({id: 'Filter.is', defaultMessage: 'is'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='isBefore'
name={intl.formatMessage({id: 'Filter.isbefore', defaultMessage: 'is before'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='isAfter'
name={intl.formatMessage({id: 'Filter.isafter', defaultMessage: 'is after'})}
onClick={(id) => props.conditionClicked(id, filter)}
@ -207,12 +208,12 @@ const FilterEntry = (props: Props): JSX.Element => {
</>}
{propertyType.type === 'date' &&
<>
<Menu.Text
<MenuText
id='isSet'
name={intl.formatMessage({id: 'Filter.is-set', defaultMessage: 'is set'})}
onClick={(id) => props.conditionClicked(id, filter)}
/>
<Menu.Text
<MenuText
id='isNotSet'
name={intl.formatMessage({id: 'Filter.is-not-set', defaultMessage: 'is not set'})}
onClick={(id) => props.conditionClicked(id, filter)}

View File

@ -16,6 +16,8 @@ import Menu from '../../widgets/menu'
import Editable from '../../widgets/editable'
import MenuWrapper from '../../widgets/menuWrapper'
import {MenuSwitch} from '../../widgets/menu/menu'
import DateFilter from './dateFilter'
import './filterValue.scss'
@ -103,7 +105,7 @@ const filterValue = (props: Props): JSX.Element|null => {
<Menu>
{template?.options.map((o) => (
<Menu.Switch
<MenuSwitch
key={o.id}
id={o.id}
name={o.value}

View File

@ -12,6 +12,8 @@ import {useAppSelector} from '../../store/hooks'
import {getCurrentBoardTemplates} from '../../store/cards'
import {getCurrentView} from '../../store/views'
import {MenuLabel, MenuSeparator, MenuText} from '../../widgets/menu/menu'
import NewCardButtonTemplateItem from './newCardButtonTemplateItem'
import EmptyCardButton from './emptyCardButton'
@ -46,16 +48,16 @@ const NewCardButton = (props: Props): JSX.Element => {
>
<Menu position='left'>
{cardTemplates.length > 0 && <>
<Menu.Label>
<MenuLabel>
<b>
<FormattedMessage
id='ViewHeader.select-a-template'
defaultMessage='Select a template'
/>
</b>
</Menu.Label>
</MenuLabel>
<Menu.Separator/>
<MenuSeparator/>
</>}
{cardTemplates.map((cardTemplate) => {
@ -76,7 +78,7 @@ const NewCardButton = (props: Props): JSX.Element => {
addCard={props.addCard}
/>
<Menu.Text
<MenuText
icon={<AddIcon/>}
id='add-template'
name={intl.formatMessage({id: 'ViewHeader.add-template', defaultMessage: 'New template'})}

View File

@ -16,6 +16,7 @@ import CheckIcon from '../../widgets/icons/check'
import {useAppSelector} from '../../store/hooks'
import {getCurrentView} from '../../store/views'
import {getCurrentBoardId} from '../../store/boards'
import {MenuText} from '../../widgets/menu/menu'
type Props = {
cardTemplate: Card
@ -32,7 +33,7 @@ const NewCardButtonTemplateItem = (props: Props) => {
const boardId = useAppSelector(getCurrentBoardId)
return (
<Menu.Text
<MenuText
key={cardTemplate.id}
id={cardTemplate.id}
name={displayName}
@ -45,7 +46,7 @@ const NewCardButtonTemplateItem = (props: Props) => {
<MenuWrapper stopPropagationOnToggle={true}>
<IconButton icon={<OptionsIcon/>}/>
<Menu position='left'>
<Menu.Text
<MenuText
icon={<CheckIcon/>}
id='default'
name={intl.formatMessage({id: 'ViewHeader.set-default-template', defaultMessage: 'Set as default'})}
@ -53,7 +54,7 @@ const NewCardButtonTemplateItem = (props: Props) => {
await mutator.setDefaultTemplate(boardId, currentView.id, currentView.fields.defaultTemplateId, cardTemplate.id)
}}
/>
<Menu.Text
<MenuText
icon={<EditIcon/>}
id='edit'
name={intl.formatMessage({id: 'ViewHeader.edit-template', defaultMessage: 'Edit'})}
@ -61,7 +62,7 @@ const NewCardButtonTemplateItem = (props: Props) => {
props.editCardTemplate(cardTemplate.id)
}}
/>
<Menu.Text
<MenuText
icon={<DeleteIcon/>}
id='delete'
name={intl.formatMessage({id: 'ViewHeader.delete-template', defaultMessage: 'Delete'})}

View File

@ -16,6 +16,7 @@ import {Utils} from '../../utils'
import ModalWrapper from '../modalWrapper'
import {sendFlashMessage} from '../flashMessages'
import {MenuText} from '../../widgets/menu/menu'
type Props = {
board: Board
@ -103,35 +104,35 @@ const ViewHeaderActionsMenu = (props: Props) => {
<MenuWrapper label={intl.formatMessage({id: 'ViewHeader.view-header-menu', defaultMessage: 'View header menu'})}>
<IconButton icon={<OptionsIcon/>}/>
<Menu position='left'>
<Menu.Text
<MenuText
id='exportCsv'
name={intl.formatMessage({id: 'ViewHeader.export-csv', defaultMessage: 'Export to CSV'})}
onClick={() => onExportCsvTrigger(board, activeView, cards, intl)}
/>
<Menu.Text
<MenuText
id='exportBoardArchive'
name={intl.formatMessage({id: 'ViewHeader.export-board-archive', defaultMessage: 'Export board archive'})}
onClick={() => Archiver.exportBoardArchive(board)}
/>
{/*
<Menu.Separator/>
<MenuSeparator/>
<Menu.Text
<MenuText
id='testAdd100Cards'
name={intl.formatMessage({id: 'ViewHeader.test-add-100-cards', defaultMessage: 'TEST: Add 100 cards'})}
onClick={() => testAddCards(board, activeView, cards.length, 100)}
/>
<Menu.Text
<MenuText
id='testAdd1000Cards'
name={intl.formatMessage({id: 'ViewHeader.test-add-1000-cards', defaultMessage: 'TEST: Add 1,000 cards'})}
onClick={() => testAddCards(board, activeView, cards.length, 1000)}
/>
<Menu.Text
<MenuText
id='testDistributeCards'
name={intl.formatMessage({id: 'ViewHeader.test-distribute-cards', defaultMessage: 'TEST: Distribute cards'})}
onClick={() => testDistributeCards()}
/>
<Menu.Text
<MenuText
id='testRandomizeIcons'
name={intl.formatMessage({id: 'ViewHeader.test-randomize-icons', defaultMessage: 'TEST: Randomize icons'})}
onClick={() => testRandomizeIcons()}

View File

@ -15,6 +15,7 @@ import MenuWrapper from '../../widgets/menuWrapper'
import CheckIcon from '../../widgets/icons/check'
import propsRegistry from '../../properties'
import {MenuText} from '../../widgets/menu/menu'
type Props = {
properties: readonly IPropertyTemplate[]
@ -52,7 +53,7 @@ const ViewHeaderDisplayByMenu = (props: Props) => {
</Button>
<Menu>
{getDateProperties().length > 0 && getDateProperties().map((date: IPropertyTemplate) => (
<Menu.Text
<MenuText
key={date.id}
id={date.id}
name={date.name}
@ -66,7 +67,7 @@ const ViewHeaderDisplayByMenu = (props: Props) => {
/>
))}
{getDateProperties().length === 0 &&
<Menu.Text
<MenuText
key={'createdDate'}
id={'createdDate'}
name={createdDateName}

View File

@ -17,6 +17,7 @@ import {useAppSelector} from '../../store/hooks'
import {getCurrentViewCardsSortedFilteredAndGrouped} from '../../store/cards'
import {getVisibleAndHiddenGroups} from '../../boardUtils'
import propsRegistry from '../../properties'
import {MenuText, MenuSeparator} from '../../widgets/menu/menu'
type Props = {
properties: readonly IPropertyTemplate[]
@ -69,7 +70,7 @@ const ViewHeaderGroupByMenu = (props: Props) => {
{activeView.fields.viewType === 'table' && activeView.fields.groupById &&
<>
{emptyVisibleGroupsCount > 0 &&
<Menu.Text
<MenuText
key={'hideEmptyGroups'}
id={'hideEmptyGroups'}
name={intl.formatMessage({id: 'GroupBy.hideEmptyGroups', defaultMessage: 'Hide {count} empty groups'}, {count: emptyVisibleGroupsCount})}
@ -77,14 +78,14 @@ const ViewHeaderGroupByMenu = (props: Props) => {
onClick={() => handleToggleGroups(false)}
/>}
{hiddenGroupsCount > 0 &&
<Menu.Text
<MenuText
key={'showHiddenGroups'}
id={'showHiddenGroups'}
name={intl.formatMessage({id: 'GroupBy.showHiddenGroups', defaultMessage: 'Show {count} hidden groups'}, {count: hiddenGroupsCount})}
rightIcon={<ShowIcon/>}
onClick={() => handleToggleGroups(true)}
/>}
<Menu.Text
<MenuText
key={'ungroup'}
id={''}
name={intl.formatMessage({id: 'GroupBy.ungroup', defaultMessage: 'Ungroup'})}
@ -96,10 +97,10 @@ const ViewHeaderGroupByMenu = (props: Props) => {
mutator.changeViewGroupById(activeView.boardId, activeView.id, activeView.fields.groupById, id)
}}
/>
<Menu.Separator/>
<MenuSeparator/>
</>}
{properties?.filter((o: IPropertyTemplate) => propsRegistry.get(o.type).canGroup).map((option: IPropertyTemplate) => (
<Menu.Text
<MenuText
key={option.id}
id={option.id}
name={option.name}

View File

@ -10,6 +10,7 @@ import mutator from '../../mutator'
import Button from '../../widgets/buttons/button'
import Menu from '../../widgets/menu'
import MenuWrapper from '../../widgets/menuWrapper'
import {MenuSwitch} from '../../widgets/menu/menu'
type Props = {
properties: readonly IPropertyTemplate[]
@ -41,7 +42,7 @@ const ViewHeaderPropertiesMenu = (props: Props) => {
</Button>
<Menu>
{activeView.fields.viewType === 'gallery' &&
<Menu.Switch
<MenuSwitch
key={Constants.titleColumnId}
id={Constants.titleColumnId}
name={intl.formatMessage({id: 'default-properties.title', defaultMessage: 'Title'})}
@ -50,7 +51,7 @@ const ViewHeaderPropertiesMenu = (props: Props) => {
onClick={toggleVisibility}
/>}
{properties?.map((option: IPropertyTemplate) => (
<Menu.Switch
<MenuSwitch
key={option.id}
id={option.id}
name={option.name}
@ -60,7 +61,7 @@ const ViewHeaderPropertiesMenu = (props: Props) => {
/>
))}
{canShowBadges &&
<Menu.Switch
<MenuSwitch
key={Constants.badgesColumnId}
id={Constants.badgesColumnId}
name={intl.formatMessage({id: 'default-properties.badges', defaultMessage: 'Comments and description'})}

View File

@ -13,6 +13,7 @@ import Menu from '../../widgets/menu'
import MenuWrapper from '../../widgets/menuWrapper'
import SortDownIcon from '../../widgets/icons/sortDown'
import SortUpIcon from '../../widgets/icons/sortUp'
import {MenuText, MenuSeparator} from '../../widgets/menu/menu'
type Props = {
properties: readonly IPropertyTemplate[]
@ -64,19 +65,19 @@ const ViewHeaderSortMenu = (props: Props) => {
<Menu>
{(activeView.fields.sortOptions?.length > 0) &&
<>
<Menu.Text
<MenuText
id='manual'
name='Manual'
onClick={onManualSort}
/>
<Menu.Text
<MenuText
id='revert'
name='Revert'
onClick={onRevertSort}
/>
<Menu.Separator/>
<MenuSeparator/>
</>
}
@ -89,7 +90,7 @@ const ViewHeaderSortMenu = (props: Props) => {
}
}
return (
<Menu.Text
<MenuText
key={option.id}
id={option.id}
name={option.name}

View File

@ -19,8 +19,10 @@ import DuplicateIcon from '../widgets/icons/duplicate'
import GalleryIcon from '../widgets/icons/gallery'
import TableIcon from '../widgets/icons/table'
import Menu from '../widgets/menu'
import {MenuText, MenuSeparator, MenuSubMenu} from '../widgets/menu/menu'
import BoardPermissionGate from './permissions/boardPermissionGate'
import './viewMenu.scss'
type Props = {
@ -273,7 +275,7 @@ const ViewMenu = (props: Props) => {
<Menu>
<div className='view-list'>
{views.map((view: BoardView) => (
<Menu.Text
<MenuText
key={view.id}
id={view.id}
name={view.title}
@ -282,11 +284,11 @@ const ViewMenu = (props: Props) => {
/>))}
</div>
<BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>
<Menu.Separator/>
<MenuSeparator/>
</BoardPermissionGate>
{!props.readonly &&
<BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>
<Menu.Text
<MenuText
id='__duplicateView'
name={duplicateViewText}
icon={<DuplicateIcon/>}
@ -296,7 +298,7 @@ const ViewMenu = (props: Props) => {
}
{!props.readonly && views.length > 1 &&
<BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>
<Menu.Text
<MenuText
id='__deleteView'
name={deleteViewText}
icon={<DeleteIcon/>}
@ -306,38 +308,38 @@ const ViewMenu = (props: Props) => {
}
{!props.readonly &&
<BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>
<Menu.SubMenu
<MenuSubMenu
id='__addView'
name={addViewText}
icon={<AddIcon/>}
>
<div className='subMenu'>
<Menu.Text
<MenuText
id='board'
name={boardText}
icon={<BoardIcon/>}
onClick={handleAddViewBoard}
/>
<Menu.Text
<MenuText
id='table'
name={tableText}
icon={<TableIcon/>}
onClick={handleAddViewTable}
/>
<Menu.Text
<MenuText
id='gallery'
name={galleryText}
icon={<GalleryIcon/>}
onClick={handleAddViewGallery}
/>
<Menu.Text
<MenuText
id='calendar'
name='Calendar'
icon={<CalendarIcon/>}
onClick={handleAddViewCalendar}
/>
</div>
</Menu.SubMenu>
</MenuSubMenu>
</BoardPermissionGate>
}
</Menu>

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {CSSProperties} from 'react'
import React, {CSSProperties, useCallback, useEffect, useRef, useState} from 'react'
import SeparatorOption from './separatorOption'
import SwitchOption from './switchOption'
@ -21,71 +21,74 @@ type Props = {
menuMargin?: number
}
export default class Menu extends React.PureComponent<Props> {
static Color = ColorOption
static SubMenu = SubMenuOption
static Switch = SwitchOption
static Separator = SeparatorOption
static Text = TextOption
static TextInput = textInputOption
static Label = LabelOption
const MenuColor = ColorOption
const MenuSubMenu = SubMenuOption
const MenuSwitch = SwitchOption
const MenuSeparator = SeparatorOption
const MenuText = TextOption
const MenuTextInput = textInputOption
const MenuLabel = LabelOption
menuRef: React.RefObject<HTMLDivElement>
const Menu = (props: Props): JSX.Element => {
const menuRef = useRef<HTMLDivElement>(null)
const [hovering, setHovering] = useState<React.ReactNode>(null)
const [menuStyle, setMenuStyle] = useState<CSSProperties>({})
constructor(props: Props) {
super(props)
this.menuRef = React.createRef<HTMLDivElement>()
}
public state = {
hovering: null,
menuStyle: {},
}
public render(): JSX.Element {
const {position, fixed, children} = this.props
const {position, menuMargin, fixed, children} = props
useEffect(() => {
let style: CSSProperties = {}
if (this.props.parentRef) {
if (props.parentRef) {
const forceBottom = position ? ['bottom', 'left', 'right'].includes(position) : false
style = MenuUtil.openUp(this.props.parentRef, forceBottom, this.props.menuMargin).style
style = MenuUtil.openUp(props.parentRef, forceBottom, menuMargin).style
}
setMenuStyle(style)
}, [props.parentRef, props.position, props.menuMargin])
return (
<div
className={`Menu noselect ${position || 'bottom'} ${fixed ? ' fixed' : ''}`}
style={style}
ref={this.menuRef}
>
<div className='menu-contents'>
<div className='menu-options'>
{React.Children.map(children, (child) => (
<div
onMouseEnter={() => this.setState({hovering: child})}
>
<HoveringContext.Provider value={child === this.state.hovering}>
{child}
</HoveringContext.Provider>
</div>))}
</div>
const onCancel = useCallback(() => {
// No need to do anything, as click bubbled up to MenuWrapper, which closes
}, [])
<div className='menu-spacer hideOnWidescreen'/>
return (
<div
className={`Menu noselect ${position || 'bottom'} ${fixed ? ' fixed' : ''}`}
style={menuStyle}
ref={menuRef}
>
<div className='menu-contents'>
<div className='menu-options'>
{React.Children.map(children, (child) => (
<div
onMouseEnter={() => setHovering(child)}
>
<HoveringContext.Provider value={child === hovering}>
{child}
</HoveringContext.Provider>
</div>))}
</div>
<div className='menu-options hideOnWidescreen'>
<Menu.Text
id='menu-cancel'
name={'Cancel'}
className='menu-cancel'
onClick={this.onCancel}
/>
</div>
<div className='menu-spacer hideOnWidescreen'/>
<div className='menu-options hideOnWidescreen'>
<MenuText
id='menu-cancel'
name={'Cancel'}
className='menu-cancel'
onClick={onCancel}
/>
</div>
</div>
)
}
private onCancel = () => {
// No need to do anything, as click bubbled up to MenuWrapper, which closes
}
</div>
)
}
export default Menu
export {
MenuColor,
MenuSubMenu,
MenuSwitch,
MenuSeparator,
MenuText,
MenuTextInput,
MenuLabel,
}

View File

@ -6,9 +6,8 @@ import CompassIcon from '../../widgets/icons/compassIcon'
import MenuUtil from './menuUtil'
import Menu from '.'
import './subMenuOption.scss'
import {MenuText} from './menu'
export const HoveringContext = React.createContext(false)
@ -77,7 +76,7 @@ function SubMenuOption(props: SubMenuOptionProps): JSX.Element {
<div className='menu-spacer hideOnWidescreen'/>
<div className='menu-options hideOnWidescreen'>
<Menu.Text
<MenuText
id='menu-cancel'
name={'Cancel'}
className='menu-cancel'

View File

@ -6,7 +6,9 @@ import {useIntl, IntlShape} from 'react-intl'
import Menu from '../widgets/menu'
import propsRegistry from '../properties'
import {PropertyType} from '../properties/types'
import './propertyMenu.scss'
import {MenuLabel, MenuSeparator, MenuText, MenuTextInput, MenuSubMenu} from './menu/menu'
type Props = {
propertyId: string
@ -29,15 +31,15 @@ export const PropertyTypes = (props: TypesProps): JSX.Element => {
const intl = useIntl()
return (
<>
<Menu.Label>
<MenuLabel>
<b>{props.label}</b>
</Menu.Label>
</MenuLabel>
<Menu.Separator/>
<MenuSeparator/>
{
propsRegistry.list().map((p) => (
<Menu.Text
<MenuText
key={p.type}
id={p.type}
name={p.displayName(intl)}
@ -60,7 +62,7 @@ const PropertyMenu = (props: Props) => {
return (
<Menu>
<Menu.TextInput
<MenuTextInput
initialValue={props.propertyName}
onConfirmValue={(n) => {
props.onTypeAndNameChanged(props.propertyType, n)
@ -70,7 +72,7 @@ const PropertyMenu = (props: Props) => {
currentPropertyName = n
}}
/>
<Menu.SubMenu
<MenuSubMenu
id='type'
name={typeMenuTitle(intl, props.propertyType)}
>
@ -78,8 +80,8 @@ const PropertyMenu = (props: Props) => {
label={intl.formatMessage({id: 'PropertyMenu.changeType', defaultMessage: 'Change property type'})}
onTypeSelected={(type: PropertyType) => props.onTypeAndNameChanged(type, currentPropertyName)}
/>
</Menu.SubMenu>
<Menu.Text
</MenuSubMenu>
<MenuText
id='delete'
name={deleteText}
onClick={() => props.onDelete(props.propertyId)}

View File

@ -22,6 +22,7 @@ import CloseIcon from './icons/close'
import Label from './label'
import './valueSelector.scss'
import {MenuText, MenuSeparator, MenuColor} from './menu/menu'
type Props = {
options: IPropertyOption[]
@ -84,15 +85,15 @@ const ValueSelectorLabel = (props: LabelProps): JSX.Element => {
icon={<OptionsIcon/>}
/>
<Menu position='left'>
<Menu.Text
<MenuText
id='delete'
icon={<DeleteIcon/>}
name={intl.formatMessage({id: 'BoardComponent.delete', defaultMessage: 'Delete'})}
onClick={() => props.onDeleteOption(option)}
/>
<Menu.Separator/>
<MenuSeparator/>
{Object.entries(Constants.menuColors).map(([key, color]: [string, string]) => (
<Menu.Color
<MenuColor
key={key}
id={key}
name={color}