Compare commits
41 commits
main
...
status-pro
Author | SHA1 | Date | |
---|---|---|---|
|
57298943bf | ||
|
9c52c2e5c0 | ||
|
c03bf1446d | ||
|
ecb2d445ed | ||
|
c03539f33c | ||
|
b89bb99424 | ||
|
92ca9d9975 | ||
|
e5bc177345 | ||
|
e2ce4b76c8 | ||
|
b686e80dda | ||
|
f9286dc53b | ||
|
ec6ea0497b | ||
|
b469c9678c | ||
|
cd6db61930 | ||
|
b32757ff6f | ||
|
6fb01601d5 | ||
|
f987a6b99e | ||
|
cdf0926090 | ||
|
30471005c0 | ||
|
75639e20fc | ||
|
1e3d151ba4 | ||
|
69910d8329 | ||
|
d009f80d7c | ||
|
4112db4626 | ||
|
68986e181a | ||
|
684c1b09a7 | ||
|
1be39c0ff4 | ||
|
a7b94e345e | ||
|
9f5db533ac | ||
|
51e2b0b3f4 | ||
|
57b7295f2a | ||
|
6922539159 | ||
|
f3d4477245 | ||
|
33e2039dda | ||
|
ac98daa792 | ||
|
a05ec7d6eb | ||
|
9d5a8ea6c7 | ||
|
d0bf7cff4a | ||
|
adcc541cc2 | ||
|
a3bb9247f6 | ||
|
18d9f77837 |
34 changed files with 3241 additions and 163 deletions
|
@ -6,7 +6,7 @@ exports[`components/boardSelector escape button should unmount the component 1`]
|
|||
class="focalboard-body"
|
||||
>
|
||||
<div
|
||||
class="Dialog dialog-back BoardSelector size--medium"
|
||||
class="ActionDialog Dialog dialog-back BoardSelector size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -15,7 +15,7 @@ exports[`components/boardSelector escape button should unmount the component 1`]
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -111,7 +111,7 @@ exports[`components/boardSelector renders with no results 1`] = `
|
|||
class="focalboard-body"
|
||||
>
|
||||
<div
|
||||
class="Dialog dialog-back BoardSelector size--medium"
|
||||
class="ActionDialog Dialog dialog-back BoardSelector size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -120,7 +120,7 @@ exports[`components/boardSelector renders with no results 1`] = `
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -217,7 +217,7 @@ exports[`components/boardSelector renders with some results 1`] = `
|
|||
class="focalboard-body"
|
||||
>
|
||||
<div
|
||||
class="Dialog dialog-back BoardSelector size--medium"
|
||||
class="ActionDialog Dialog dialog-back BoardSelector size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -226,7 +226,7 @@ exports[`components/boardSelector renders with some results 1`] = `
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -421,7 +421,7 @@ exports[`components/boardSelector renders without start searching 1`] = `
|
|||
class="focalboard-body"
|
||||
>
|
||||
<div
|
||||
class="Dialog dialog-back BoardSelector size--medium"
|
||||
class="ActionDialog Dialog dialog-back BoardSelector size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -430,7 +430,7 @@ exports[`components/boardSelector renders without start searching 1`] = `
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"rules": {
|
||||
"indentation": 4,
|
||||
"selector-class-pattern": "[a-zA-Z_-]+",
|
||||
"max-nesting-depth": 4,
|
||||
"max-nesting-depth": null,
|
||||
"selector-max-compound-selectors": 6,
|
||||
"selector-max-id": 1,
|
||||
"selector-no-qualifying-type": null,
|
||||
|
|
|
@ -448,5 +448,12 @@
|
|||
"tutorial_tip.got_it": "Got it",
|
||||
"tutorial_tip.ok": "Next",
|
||||
"tutorial_tip.out": "Opt out of these tips.",
|
||||
"tutorial_tip.seen": "Seen this before?"
|
||||
"tutorial_tip.seen": "Seen this before?",
|
||||
"statusProperty.configDialog.title": "Edit Statuses",
|
||||
"statusProperty.configDialog.todo.emptyText": "Drag statuses here to consider tasks with these statuses “Not Started”",
|
||||
"statusProperty.configDialog.inProgress.emptyText": "Drag statuses here to consider tasks with these statuses “In Progress”",
|
||||
"statusProperty.configDialog.complete.emptyText": "Drag statuses here to consider tasks with these statuses ”Done”",
|
||||
"statusProperty.configDialog.subTitle": "Categorise your status values to represent what each value represents",
|
||||
"generic.cancel": "Cancel",
|
||||
"generic.save": "Save"
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`components/cardDialog already following card 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back cardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back cardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -12,7 +12,7 @@ exports[`components/cardDialog already following card 1`] = `
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -215,7 +215,7 @@ exports[`components/cardDialog already following card 1`] = `
|
|||
exports[`components/cardDialog limited card shows hidden view (no toolbar) 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back cardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back cardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -224,7 +224,7 @@ exports[`components/cardDialog limited card shows hidden view (no toolbar) 1`] =
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -461,7 +461,7 @@ exports[`components/cardDialog limited card shows hidden view (no toolbar) 1`] =
|
|||
exports[`components/cardDialog return a cardDialog readonly 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back cardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back cardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -470,7 +470,7 @@ exports[`components/cardDialog return a cardDialog readonly 1`] = `
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -592,7 +592,7 @@ exports[`components/cardDialog return a cardDialog readonly 1`] = `
|
|||
exports[`components/cardDialog return cardDialog menu content 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back cardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back cardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -601,7 +601,7 @@ exports[`components/cardDialog return cardDialog menu content 1`] = `
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -941,7 +941,7 @@ exports[`components/cardDialog return cardDialog menu content 1`] = `
|
|||
exports[`components/cardDialog return cardDialog menu content and cancel delete confirmation do nothing 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back cardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back cardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -950,7 +950,7 @@ exports[`components/cardDialog return cardDialog menu content and cancel delete
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -1153,7 +1153,7 @@ exports[`components/cardDialog return cardDialog menu content and cancel delete
|
|||
exports[`components/cardDialog should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back cardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back cardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -1162,7 +1162,7 @@ exports[`components/cardDialog should match snapshot 1`] = `
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -1365,7 +1365,7 @@ exports[`components/cardDialog should match snapshot 1`] = `
|
|||
exports[`components/cardDialog should match snapshot without permissions 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back cardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back cardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -1374,7 +1374,7 @@ exports[`components/cardDialog should match snapshot without permissions 1`] = `
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`/components/confirmAddUserForNotifications should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back confirmation-dialog-box size--small"
|
||||
class="ActionDialog Dialog dialog-back confirmation-dialog-box size--small"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -12,7 +12,7 @@ exports[`/components/confirmAddUserForNotifications should match snapshot 1`] =
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`/components/confirmationDialogBox confirmDialog should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back confirmation-dialog-box size--small"
|
||||
class="ActionDialog Dialog dialog-back confirmation-dialog-box size--small"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -12,7 +12,7 @@ exports[`/components/confirmationDialogBox confirmDialog should match snapshot 1
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -84,7 +84,7 @@ exports[`/components/confirmationDialogBox confirmDialog should match snapshot 1
|
|||
exports[`/components/confirmationDialogBox confirmDialog with Confirm Button Text should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back confirmation-dialog-box size--small"
|
||||
class="ActionDialog Dialog dialog-back confirmation-dialog-box size--small"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -93,7 +93,7 @@ exports[`/components/confirmationDialogBox confirmDialog with Confirm Button Tex
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`components/dialog should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back undefined size--medium"
|
||||
class="ActionDialog Dialog dialog-back undefined size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -12,7 +12,7 @@ exports[`components/dialog should match snapshot 1`] = `
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -50,7 +50,7 @@ exports[`components/dialog should match snapshot 1`] = `
|
|||
exports[`components/dialog should return dialog and click on cancel button 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back undefined size--medium"
|
||||
class="ActionDialog Dialog dialog-back undefined size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -59,7 +59,7 @@ exports[`components/dialog should return dialog and click on cancel button 1`] =
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
135
webapp/src/components/actionDialog/actionDialog.scss
Normal file
135
webapp/src/components/actionDialog/actionDialog.scss
Normal file
|
@ -0,0 +1,135 @@
|
|||
@import '../../styles/z-index';
|
||||
|
||||
.Dialog {
|
||||
&.dialog-back {
|
||||
@include z-index(dialog-back);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&.size--small {
|
||||
.dialog {
|
||||
max-width: 512px;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.dialog-subtitle {
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
@include z-index(dialog-backdrop);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(var(--center-channel-color-rgb), 0.5);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: rgb(var(--center-channel-bg-rgb));
|
||||
box-shadow: rgba(var(--center-channel-color-rgb), 0.1) 0 0 0 1px,
|
||||
rgba(var(--center-channel-color-rgb), 0.1) 0 2px 4px;
|
||||
border-radius: var(--modal-rad);
|
||||
padding: 0;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
&.footerHidden {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
@media not screen and (max-width: 975px) {
|
||||
margin: 72px auto;
|
||||
max-width: 975px;
|
||||
height: calc(100% - 144px);
|
||||
|
||||
.hideOnWidescreen {
|
||||
/* Hide elements on larger screens */
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
> .banner {
|
||||
background-color: rgba(230, 220, 192, 0.9);
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
> .banner.error {
|
||||
background-color: rgba(230, 192, 192, 0.9);
|
||||
}
|
||||
|
||||
.IconButton {
|
||||
color: rgba(var(--center-channel-color-rgb), 0.56);
|
||||
|
||||
&:hover {
|
||||
color: rgba(var(--center-channel-color-rgb), 0.72);
|
||||
background-color: rgba(var(--center-channel-color-rgb), 0.08);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: rgba(var(--button-bg-rgb), 0.08);
|
||||
color: rgba(var(--button-bg-rgb), 1);
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 24px 0;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.toolbar--right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.cardToolbar {
|
||||
width: 100%;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
|
||||
.footer {
|
||||
padding: 24px 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
133
webapp/src/components/actionDialog/actionDialog.tsx
Normal file
133
webapp/src/components/actionDialog/actionDialog.tsx
Normal file
|
@ -0,0 +1,133 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useRef} from 'react'
|
||||
import {FormattedMessage, useIntl} from 'react-intl'
|
||||
import {useHotkeys} from 'react-hotkeys-hook'
|
||||
|
||||
import IconButton from '../../widgets/buttons/iconButton'
|
||||
import CloseIcon from '../../widgets/icons/close'
|
||||
import OptionsIcon from '../../widgets/icons/options'
|
||||
import MenuWrapper from '../../widgets/menuWrapper'
|
||||
import './actionDialog.scss'
|
||||
import Button from '../../widgets/buttons/button'
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
size?: string
|
||||
toolsMenu?: React.ReactNode // some dialogs may not require a toolmenu
|
||||
toolbar?: React.ReactNode
|
||||
hideCloseButton?: boolean
|
||||
className?: string
|
||||
title?: JSX.Element
|
||||
subtitle?: JSX.Element
|
||||
onClose: () => void
|
||||
confirmButtonText?: string
|
||||
onConfirm?: () => void
|
||||
cancelButtonText?: string
|
||||
onCancel?: () => void
|
||||
hideFooter?: boolean
|
||||
}
|
||||
|
||||
const ActionDialog = (props: Props) => {
|
||||
const {toolsMenu, toolbar, title, subtitle, size} = props
|
||||
const intl = useIntl()
|
||||
|
||||
const closeDialogText = intl.formatMessage({
|
||||
id: 'Dialog.closeDialog',
|
||||
defaultMessage: 'Close dialog',
|
||||
})
|
||||
|
||||
useHotkeys('esc', () => props.onClose())
|
||||
|
||||
const isBackdropClickedRef = useRef(false)
|
||||
|
||||
return (
|
||||
<div className={`ActionDialog Dialog dialog-back ${props.className} size--${size || 'medium'}`}>
|
||||
<div className='backdrop'/>
|
||||
<div
|
||||
className='wrapper'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (!isBackdropClickedRef.current) {
|
||||
return
|
||||
}
|
||||
isBackdropClickedRef.current = false
|
||||
props.onClose()
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
isBackdropClickedRef.current = true
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
role='dialog'
|
||||
className={`dialog${props.hideFooter ? ' footerHidden' : ''}`}
|
||||
>
|
||||
<div className='toolbar'>
|
||||
<div>
|
||||
{<h1 className='dialog-title'>{title || ''}</h1>}
|
||||
{subtitle && <h5 className='dialog-subtitle'>{subtitle}</h5>}
|
||||
</div>
|
||||
<div className='toolbar--right'>
|
||||
{toolbar && <div className='d-flex'>{toolbar}</div>}
|
||||
{toolsMenu && <MenuWrapper>
|
||||
<IconButton
|
||||
size='medium'
|
||||
icon={<OptionsIcon/>}
|
||||
/>
|
||||
{toolsMenu}
|
||||
</MenuWrapper>
|
||||
}
|
||||
{
|
||||
!props.hideCloseButton &&
|
||||
<IconButton
|
||||
className='dialog__close'
|
||||
onClick={props.onClose}
|
||||
icon={<CloseIcon/>}
|
||||
title={closeDialogText}
|
||||
size='medium'
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{props.children}
|
||||
{
|
||||
!props.hideFooter &&
|
||||
<div className='footer'>
|
||||
<Button
|
||||
onClick={props.onCancel}
|
||||
emphasis='tertiary'
|
||||
size='medium'
|
||||
>
|
||||
{
|
||||
props.cancelButtonText ||
|
||||
<FormattedMessage
|
||||
id='generic.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={props.onConfirm}
|
||||
emphasis='primary'
|
||||
filled={true}
|
||||
size='medium'
|
||||
>
|
||||
{
|
||||
props.confirmButtonText ||
|
||||
<FormattedMessage
|
||||
id='generic.save'
|
||||
defaultMessage='Save'
|
||||
/>
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ActionDialog)
|
|
@ -143,7 +143,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelectorItem should trigg
|
|||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back DeleteBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back DeleteBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -152,7 +152,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelectorItem should trigg
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`component/BoardSwitcherDialog base case 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back BoardSwitcherDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back BoardSwitcherDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -12,7 +12,7 @@ exports[`component/BoardSwitcherDialog base case 1`] = `
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`components/createCategory/CreateCategory base case should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back CreateCategoryModal size--medium"
|
||||
class="ActionDialog Dialog dialog-back CreateCategoryModal size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -12,7 +12,7 @@ exports[`components/createCategory/CreateCategory base case should match snapsho
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useRef} from 'react'
|
||||
import {useIntl} from 'react-intl'
|
||||
import {useHotkeys} from 'react-hotkeys-hook'
|
||||
import React from 'react'
|
||||
|
||||
import IconButton from '../widgets/buttons/iconButton'
|
||||
import CloseIcon from '../widgets/icons/close'
|
||||
import OptionsIcon from '../widgets/icons/options'
|
||||
import MenuWrapper from '../widgets/menuWrapper'
|
||||
import './dialog.scss'
|
||||
import ActionDialog from './actionDialog/actionDialog'
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
|
@ -23,72 +18,12 @@ type Props = {
|
|||
}
|
||||
|
||||
const Dialog = (props: Props) => {
|
||||
const {toolsMenu, toolbar, title, subtitle, size} = props
|
||||
const intl = useIntl()
|
||||
|
||||
const closeDialogText = intl.formatMessage({
|
||||
id: 'Dialog.closeDialog',
|
||||
defaultMessage: 'Close dialog',
|
||||
})
|
||||
|
||||
useHotkeys('esc', () => props.onClose())
|
||||
|
||||
const isBackdropClickedRef = useRef(false)
|
||||
|
||||
// A dialog is just a ActionDialog without any footer action buttons
|
||||
return (
|
||||
<div className={`Dialog dialog-back ${props.className} size--${size || 'medium'}`}>
|
||||
<div className='backdrop'/>
|
||||
<div
|
||||
className='wrapper'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (!isBackdropClickedRef.current) {
|
||||
return
|
||||
}
|
||||
isBackdropClickedRef.current = false
|
||||
props.onClose()
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
isBackdropClickedRef.current = true
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
role='dialog'
|
||||
className='dialog'
|
||||
>
|
||||
<div className='toolbar'>
|
||||
<div>
|
||||
{<h1 className='dialog-title'>{title || ''}</h1>}
|
||||
{subtitle && <h5 className='dialog-subtitle'>{subtitle}</h5>}
|
||||
</div>
|
||||
<div className='toolbar--right'>
|
||||
{toolbar && <div className='d-flex'>{toolbar}</div>}
|
||||
{toolsMenu && <MenuWrapper>
|
||||
<IconButton
|
||||
size='medium'
|
||||
icon={<OptionsIcon/>}
|
||||
<ActionDialog
|
||||
hideFooter={true}
|
||||
{...props}
|
||||
/>
|
||||
{toolsMenu}
|
||||
</MenuWrapper>
|
||||
}
|
||||
{
|
||||
!props.hideCloseButton &&
|
||||
<IconButton
|
||||
className='dialog__close'
|
||||
onClick={props.onClose}
|
||||
icon={<CloseIcon/>}
|
||||
title={closeDialogText}
|
||||
size='medium'
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -905,7 +905,7 @@ exports[`src/components/gallery/GalleryCard without block content return Gallery
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="Dialog dialog-back confirmation-dialog-box size--small"
|
||||
class="ActionDialog Dialog dialog-back confirmation-dialog-box size--small"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -914,7 +914,7 @@ exports[`src/components/gallery/GalleryCard without block content return Gallery
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`src/components/shareBoard/shareBoard confirm unlinking linked channel 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -12,7 +12,7 @@ exports[`src/components/shareBoard/shareBoard confirm unlinking linked channel 1
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -253,7 +253,7 @@ exports[`src/components/shareBoard/shareBoard confirm unlinking linked channel 1
|
|||
exports[`src/components/shareBoard/shareBoard return shareBoard and click Copy link 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -262,7 +262,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Copy l
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -462,7 +462,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Copy l
|
|||
exports[`src/components/shareBoard/shareBoard return shareBoard and click Copy link 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -471,7 +471,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Copy l
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -671,7 +671,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Copy l
|
|||
exports[`src/components/shareBoard/shareBoard return shareBoard and click Regenerate token 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -680,7 +680,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Regene
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -903,7 +903,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Regene
|
|||
exports[`src/components/shareBoard/shareBoard return shareBoard and click Select 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -912,7 +912,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Select
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -1139,7 +1139,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Select
|
|||
exports[`src/components/shareBoard/shareBoard return shareBoard and click Select 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -1148,7 +1148,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Select
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -1607,7 +1607,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Select
|
|||
exports[`src/components/shareBoard/shareBoard return shareBoard and click Select, non-plugin mode 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -1616,7 +1616,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Select
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -1843,7 +1843,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Select
|
|||
exports[`src/components/shareBoard/shareBoard return shareBoard and click Select, non-plugin mode 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -1852,7 +1852,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Select
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -2329,7 +2329,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Select
|
|||
exports[`src/components/shareBoard/shareBoard return shareBoard template and click Select 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -2338,7 +2338,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard template and cli
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -2514,7 +2514,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard template and cli
|
|||
exports[`src/components/shareBoard/shareBoard return shareBoard template and click Select 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -2523,7 +2523,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard template and cli
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -2835,7 +2835,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard template and cli
|
|||
exports[`src/components/shareBoard/shareBoard return shareBoard, and click switch 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -2844,7 +2844,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard, and click switc
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -3067,7 +3067,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoard, and click switc
|
|||
exports[`src/components/shareBoard/shareBoard return shareBoardComponent and click Switch without sharing 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -3076,7 +3076,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoardComponent and cli
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -3299,7 +3299,7 @@ exports[`src/components/shareBoard/shareBoard return shareBoardComponent and cli
|
|||
exports[`src/components/shareBoard/shareBoard should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -3308,7 +3308,7 @@ exports[`src/components/shareBoard/shareBoard should match snapshot 1`] = `
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -3508,7 +3508,7 @@ exports[`src/components/shareBoard/shareBoard should match snapshot 1`] = `
|
|||
exports[`src/components/shareBoard/shareBoard should match snapshot with sharing 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -3517,7 +3517,7 @@ exports[`src/components/shareBoard/shareBoard should match snapshot with sharing
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -3717,7 +3717,7 @@ exports[`src/components/shareBoard/shareBoard should match snapshot with sharing
|
|||
exports[`src/components/shareBoard/shareBoard should match snapshot with sharing and subpath 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -3726,7 +3726,7 @@ exports[`src/components/shareBoard/shareBoard should match snapshot with sharing
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -3926,7 +3926,7 @@ exports[`src/components/shareBoard/shareBoard should match snapshot with sharing
|
|||
exports[`src/components/shareBoard/shareBoard should match snapshot with sharing and without workspaceId and subpath 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -3935,7 +3935,7 @@ exports[`src/components/shareBoard/shareBoard should match snapshot with sharing
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -4135,7 +4135,7 @@ exports[`src/components/shareBoard/shareBoard should match snapshot with sharing
|
|||
exports[`src/components/shareBoard/shareBoard should match snapshot, with template 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ShareBoardDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -4144,7 +4144,7 @@ exports[`src/components/shareBoard/shareBoard should match snapshot, with templa
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,176 @@
|
|||
.StatusPropertyConfigrationDialog {
|
||||
.wrapper {
|
||||
.dialog {
|
||||
width: 832px;
|
||||
height: 540px;
|
||||
padding: 0 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-75 {
|
||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.categorySwimlanes {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 36px;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
.categorySwimlane {
|
||||
flex: 1 1 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.categorySwimlane_Header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
||||
gap: 9px;
|
||||
|
||||
.text-heading1 {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.addBtnWrapper {
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
background: rgba(var(--center-channel-color-rgb), 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.octo-tooltip.tooltip-bottom:hover::after {
|
||||
width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.categorySwimlane_ValueArea {
|
||||
flex: 1;
|
||||
border: 2px dashed rgba(var(--center-channel-color-rgb), 0.25);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
padding: 14px 3px !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.overflowWrapper {
|
||||
overflow-y: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.Label {
|
||||
width: max-content;
|
||||
margin-top: 0;
|
||||
max-width: 150px;
|
||||
margin-right: auto;
|
||||
|
||||
span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.categorySwimlane_Value {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
|
||||
.colorEditor,
|
||||
.contextMenu {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Menu {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.colorEditor {
|
||||
margin-right: 4px;
|
||||
margin-left: 4px;
|
||||
|
||||
&.editing {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.contextMenu {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(var(--center-channel-color-rgb), 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background: rgba(var(--center-channel-color-rgb), 0.1);
|
||||
border-radius: 4px;
|
||||
|
||||
.colorEditor,
|
||||
.contextMenu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.dragHandleWrapper {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.colorEditor {
|
||||
width: 24px;
|
||||
min-width: 24px;
|
||||
max-width: 24px;
|
||||
height: 24px;
|
||||
border: 1px solid #80808052;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.emptyColumnPlaceholder {
|
||||
margin: 0 16px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 6px 0;
|
||||
|
||||
.icon-wrapper {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin: 0 auto;
|
||||
padding-top: 7px;
|
||||
|
||||
.CompassIcon {
|
||||
font-size: 18px;
|
||||
opacity: 1;
|
||||
display: block;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import {render} from '@testing-library/react'
|
||||
|
||||
import CheckIcon from '../../../widgets/icons/check'
|
||||
import ClockOutline from '../../../widgets/icons/clockOutline'
|
||||
import {wrapIntl, wrapRBDNDContext} from '../../../testUtils'
|
||||
|
||||
import BlackCheckboxOutline from '../../../widgets/icons/blackCheckboxOutline'
|
||||
|
||||
import EditStatusPropertyDialog, {StatusCategory} from './editStatusDialog'
|
||||
|
||||
describe('components/standardProperties/statusProperty/EditStatusPropertyDialog', () => {
|
||||
test('should match snapshot', () => {
|
||||
const initialValueCategoryValue: StatusCategory[] = [
|
||||
{
|
||||
id: 'category_id_1',
|
||||
title: 'Not Started',
|
||||
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: 'In progress',
|
||||
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: 'Completed',
|
||||
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”',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const component = wrapRBDNDContext(
|
||||
wrapIntl(
|
||||
<EditStatusPropertyDialog
|
||||
valueCategories={initialValueCategoryValue}
|
||||
onClose={() => {}}
|
||||
onUpdate={() => {}}
|
||||
/>,
|
||||
))
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,271 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {FormattedMessage, useIntl} from 'react-intl'
|
||||
|
||||
import {DragDropContext, Droppable, DropResult} from 'react-beautiful-dnd'
|
||||
|
||||
import {cloneDeep} from 'lodash'
|
||||
|
||||
import Tooltip from '../../../widgets/tooltip'
|
||||
|
||||
import PlusIcon from '../../../widgets/icons/plus'
|
||||
|
||||
import InfoIcon from '../../../widgets/icons/info'
|
||||
|
||||
import './editStatusDialog.scss'
|
||||
import ActionDialog from '../../actionDialog/actionDialog'
|
||||
import {IPropertyOption} from '../../../blocks/board'
|
||||
|
||||
import {IDType, Utils} from '../../../utils'
|
||||
|
||||
import ValueRow from './valueRow'
|
||||
|
||||
export type StatusCategoryEmptyState = {
|
||||
icon: JSX.Element
|
||||
color: string
|
||||
text: {
|
||||
id: string
|
||||
defaultMessage: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EditablePropertyOption = IPropertyOption & {
|
||||
editing?: boolean
|
||||
focused?: boolean
|
||||
}
|
||||
|
||||
export type StatusCategory = {
|
||||
id: string
|
||||
title: string
|
||||
options: EditablePropertyOption[]
|
||||
emptyState: StatusCategoryEmptyState
|
||||
}
|
||||
|
||||
type Props = {
|
||||
valueCategories: StatusCategory[]
|
||||
onClose: () => void
|
||||
onUpdate: (updatedValueCategories: StatusCategory[]) => void
|
||||
}
|
||||
|
||||
const EditStatusPropertyDialog = (props: Props): JSX.Element => {
|
||||
const [valueCategories, setValueCategories] = useState<StatusCategory[]>([])
|
||||
const [focusedValueID, setFocusedValueID] = useState<string>()
|
||||
const intl = useIntl()
|
||||
|
||||
useEffect(() => {
|
||||
// we save a deel copy of props as user actions like
|
||||
// DND and adding, editing, changing coloror deleting the values
|
||||
// affect this local copy first and only if the user clicks the
|
||||
// "Save" button, are the changes propogated to the prop change callback function/
|
||||
// Direcxtly modifying the props value can cause the value of variable
|
||||
// from the parent component to be affected as well in some cases
|
||||
setValueCategories(cloneDeep(props.valueCategories))
|
||||
}, [props.valueCategories])
|
||||
|
||||
const title = (
|
||||
<FormattedMessage
|
||||
id='statusProperty.configDialog.title'
|
||||
defaultMessage='Edit Statuses'
|
||||
/>
|
||||
)
|
||||
|
||||
const handleUpdateValue = (statusCategoryID: string, newOptionValue: IPropertyOption): void => {
|
||||
newOptionValue.value = newOptionValue.value.trim()
|
||||
|
||||
const categoryIndex = valueCategories.findIndex((valueCategory) => valueCategory.id === statusCategoryID)
|
||||
if (categoryIndex < 0) {
|
||||
Utils.logError(`category with ID: ${statusCategoryID} not found`)
|
||||
return
|
||||
}
|
||||
|
||||
const valueIndex = valueCategories[categoryIndex].options.findIndex((option) => option.id === newOptionValue.id)
|
||||
if (valueIndex < 0) {
|
||||
Utils.logError(`Value with ID ${newOptionValue.id} not found`)
|
||||
return
|
||||
}
|
||||
|
||||
const updatedValueCategories = [...valueCategories]
|
||||
const oldOptionvalue = updatedValueCategories[categoryIndex].options[valueIndex]
|
||||
|
||||
if (oldOptionvalue.value === newOptionValue.value && newOptionValue.value === '') {
|
||||
// if used add a new value, but then leaves it empty by, for example,
|
||||
// clicking outside, we delete that value as its the fasted way for the user
|
||||
// to remove an accidently added value
|
||||
updatedValueCategories[categoryIndex].options.splice(valueIndex, 1)
|
||||
} else {
|
||||
updatedValueCategories[categoryIndex].options[valueIndex] = newOptionValue
|
||||
}
|
||||
|
||||
setFocusedValueID('')
|
||||
setValueCategories(updatedValueCategories)
|
||||
}
|
||||
|
||||
const handleDeleteValue = (statusCategoryID: string, optionID: string) => {
|
||||
const categoryIndex = valueCategories.findIndex((valueCategory) => valueCategory.id === statusCategoryID)
|
||||
if (categoryIndex < 0) {
|
||||
Utils.logError(`category with ID: ${statusCategoryID} not found`)
|
||||
return
|
||||
}
|
||||
|
||||
const valueIndex = valueCategories[categoryIndex].options.findIndex((option) => option.id === optionID)
|
||||
if (valueIndex < 0) {
|
||||
Utils.logError(`Value with ID ${optionID} not found`)
|
||||
return
|
||||
}
|
||||
|
||||
const updatedValueCategories = Array.from(valueCategories)
|
||||
updatedValueCategories[categoryIndex].options.splice(valueIndex, 1)
|
||||
setValueCategories(updatedValueCategories)
|
||||
}
|
||||
|
||||
const handleAddCategoryValue = (categoryID: string) => {
|
||||
const categoryIndex = valueCategories.findIndex((valueCategory) => valueCategory.id === categoryID)
|
||||
if (categoryIndex < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const newOption: EditablePropertyOption = {
|
||||
id: Utils.createGuid(IDType.None),
|
||||
value: '',
|
||||
color: 'propColorOrange',
|
||||
}
|
||||
|
||||
const updatedValueCategories = [...valueCategories]
|
||||
updatedValueCategories[categoryIndex].options.unshift(newOption)
|
||||
|
||||
setFocusedValueID(newOption.id)
|
||||
setValueCategories(updatedValueCategories)
|
||||
}
|
||||
|
||||
const onDragEndHandler = (result: DropResult) => {
|
||||
const {destination, source, type} = result
|
||||
|
||||
if (type !== 'statusCategory' || !destination) {
|
||||
return
|
||||
}
|
||||
|
||||
const updatedValues = Array.from(valueCategories)
|
||||
|
||||
const sourceCategoryIndex = updatedValues.findIndex((valueCategory) => valueCategory.id === source.droppableId)
|
||||
const destinationCategoryIndex = updatedValues.findIndex((valueCategory) => valueCategory.id === destination.droppableId)
|
||||
const draggedObject = valueCategories[sourceCategoryIndex].options[source.index]
|
||||
|
||||
updatedValues[sourceCategoryIndex].options.splice(source.index, 1)
|
||||
updatedValues[destinationCategoryIndex].options.splice(destination.index, 0, draggedObject)
|
||||
setValueCategories(updatedValues)
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
onClose={props.onClose}
|
||||
title={title}
|
||||
className='StatusPropertyConfigrationDialog'
|
||||
onConfirm={() => {
|
||||
props.onUpdate(valueCategories)
|
||||
props.onClose()
|
||||
}}
|
||||
onCancel={props.onClose}
|
||||
>
|
||||
<div className='text-heading5'/>
|
||||
<div className='text-75'>
|
||||
<FormattedMessage
|
||||
id='statusProperty.configDialog.subTitle'
|
||||
defaultMessage='Categorise your status values to represent what each value represents'
|
||||
/>
|
||||
</div>
|
||||
<div className='categorySwimlanes'>
|
||||
<DragDropContext onDragEnd={onDragEndHandler}>
|
||||
{valueCategories.map((valueCategory) => {
|
||||
return (
|
||||
<div
|
||||
key={valueCategory.id}
|
||||
className='categorySwimlane'
|
||||
>
|
||||
<div className='categorySwimlane_Header'>
|
||||
<div className='text-heading1'>
|
||||
{valueCategory.title}
|
||||
</div>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id: valueCategory.emptyState.text.id,
|
||||
defaultMessage: valueCategory.emptyState.text.defaultMessage,
|
||||
})}
|
||||
placement='bottom'
|
||||
wrap={true}
|
||||
>
|
||||
<InfoIcon/>
|
||||
</Tooltip>
|
||||
<div
|
||||
className='addBtnWrapper'
|
||||
onClick={() => handleAddCategoryValue(valueCategory.id)}
|
||||
>
|
||||
<PlusIcon/>
|
||||
</div>
|
||||
</div>
|
||||
<Droppable
|
||||
type='statusCategory'
|
||||
droppableId={valueCategory.id}
|
||||
>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
className='categorySwimlane_ValueArea'
|
||||
>
|
||||
<div className='overflowWrapper'>
|
||||
{
|
||||
valueCategory.options.length === 0 &&
|
||||
<div className='emptyColumnPlaceholder'>
|
||||
<div
|
||||
className='icon-wrapper'
|
||||
style={{
|
||||
backgroundColor: `rgba(var(${valueCategory.emptyState.color}), 0.2)`,
|
||||
color: `rgba(var(${valueCategory.emptyState.color}), 1)`,
|
||||
}}
|
||||
>
|
||||
{valueCategory.emptyState.icon}
|
||||
</div>
|
||||
<div className='placeholderText text-75'>
|
||||
{
|
||||
<FormattedMessage
|
||||
id={valueCategory.emptyState.text.id}
|
||||
defaultMessage={valueCategory.emptyState.text.defaultMessage}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
valueCategory.options.length > 0 &&
|
||||
valueCategory.options.map(
|
||||
(option: EditablePropertyOption, index) =>
|
||||
(
|
||||
<ValueRow
|
||||
key={option.id}
|
||||
option={option}
|
||||
index={index}
|
||||
editing={option.id === focusedValueID}
|
||||
onUpdate={handleUpdateValue}
|
||||
onDelete={handleDeleteValue}
|
||||
valueCategoryID={valueCategory.id}
|
||||
/>
|
||||
),
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</ActionDialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditStatusPropertyDialog
|
|
@ -0,0 +1,10 @@
|
|||
.EditableLabel {
|
||||
input {
|
||||
border: 0;
|
||||
background: none;
|
||||
border-bottom: 1px solid;
|
||||
border-radius: 0;
|
||||
max-width: 100%;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useState} from 'react'
|
||||
|
||||
import Label from '../../../../widgets/label'
|
||||
|
||||
import {IPropertyOption} from '../../../../blocks/board'
|
||||
|
||||
import './editableLabel.scss'
|
||||
|
||||
type Props = {
|
||||
option: IPropertyOption
|
||||
editing?: boolean
|
||||
onBlur?: (newOptionValue: IPropertyOption) => void
|
||||
}
|
||||
|
||||
const EditableLabel = (props: Props): JSX.Element => {
|
||||
const [value, setValue] = useState<string>(props.option.value)
|
||||
|
||||
const handleOnBlur = () => {
|
||||
const newOptionValue = {
|
||||
...props.option,
|
||||
value,
|
||||
}
|
||||
|
||||
if (props.onBlur) {
|
||||
props.onBlur(newOptionValue)
|
||||
}
|
||||
}
|
||||
|
||||
const displayValue = (<span>{props.option.value}</span>)
|
||||
const editValue = (
|
||||
<input
|
||||
defaultValue={props.option.value}
|
||||
autoFocus={true}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onBlur={handleOnBlur}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<Label
|
||||
key={props.option.id}
|
||||
className='EditableLabel'
|
||||
color={props.option.color}
|
||||
title={props.option.value}
|
||||
>
|
||||
{ props.editing ? editValue : displayValue }
|
||||
</Label>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(EditableLabel)
|
|
@ -0,0 +1,149 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useRef, useState} from 'react'
|
||||
import {Draggable} from 'react-beautiful-dnd'
|
||||
|
||||
import DeleteIcon from '../../../widgets/icons/delete'
|
||||
|
||||
import {Constants} from '../../../constants'
|
||||
|
||||
import DragHandle from '../../../widgets/icons/dragHandle'
|
||||
import EditIcon from '../../../widgets/icons/edit'
|
||||
import Menu from '../../../widgets/menu'
|
||||
import MenuWrapper from '../../../widgets/menuWrapper'
|
||||
|
||||
import {IPropertyOption} from '../../../blocks/board'
|
||||
|
||||
import OptionsIcon from '../../../widgets/icons/options'
|
||||
|
||||
import EditableLabel from './editableLabel/editableLabel'
|
||||
|
||||
type Props = {
|
||||
option: IPropertyOption
|
||||
index: number
|
||||
editing: boolean
|
||||
onUpdate: (statusCategoryID: string, newOptionValue: IPropertyOption) => void
|
||||
onDelete: (statusCategoryID: string, optionID: string) => void
|
||||
valueCategoryID: string
|
||||
}
|
||||
|
||||
const ValueRow = (props: Props) => {
|
||||
const colorEditorRef = useRef(null)
|
||||
const menuRef = useRef(null)
|
||||
const [isActive, setIsActive] = useState<boolean>(false)
|
||||
const [isEditing, setIsEditing] = useState<boolean>(false)
|
||||
const [menuOpen, setMenuOpen] = useState<boolean>(false)
|
||||
|
||||
const handleOnColorChange = useCallback((newColor: string) => {
|
||||
const newValue: IPropertyOption = {
|
||||
...props.option,
|
||||
color: newColor,
|
||||
}
|
||||
|
||||
props.onUpdate(props.valueCategoryID, newValue)
|
||||
}, [props.valueCategoryID, props.option.value, props.option.color])
|
||||
|
||||
const handleEditButtonClick = useCallback(() => {
|
||||
setIsEditing((value) => !value)
|
||||
}, [])
|
||||
|
||||
const handleDeleteButtonClick = useCallback(() => {
|
||||
props.onDelete(props.valueCategoryID, props.option.id)
|
||||
}, [props.valueCategoryID, props.option.id])
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
draggableId={props.option.id}
|
||||
index={props.index}
|
||||
key={props.option.id}
|
||||
>
|
||||
{(draggableProvided) => (
|
||||
<div
|
||||
{...draggableProvided.draggableProps}
|
||||
ref={draggableProvided.innerRef}
|
||||
key={props.option.id}
|
||||
className={`categorySwimlane_Value ${isActive || props.editing || isEditing || menuOpen ? 'active' : ''}`}
|
||||
onBlur={() => {
|
||||
setIsActive(false)
|
||||
setIsEditing(false)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
{...draggableProvided.dragHandleProps}
|
||||
className='dragHandleWrapper'
|
||||
>
|
||||
<DragHandle/>
|
||||
</div>
|
||||
|
||||
<EditableLabel
|
||||
option={props.option}
|
||||
editing={props.editing || isEditing}
|
||||
onBlur={(newOptionValue: IPropertyOption) => props.onUpdate(props.valueCategoryID, newOptionValue)}
|
||||
/>
|
||||
<MenuWrapper
|
||||
stopPropagationOnToggle={true}
|
||||
onToggle={(isOpen: boolean) => setIsActive(isOpen)}
|
||||
>
|
||||
<div
|
||||
ref={colorEditorRef}
|
||||
className={`colorEditor ${props.option.color} ${props.editing ? 'editing' : ''}`}
|
||||
/>
|
||||
<Menu
|
||||
position='auto'
|
||||
parentRef={colorEditorRef}
|
||||
menuMargin={30}
|
||||
fixed={true}
|
||||
>
|
||||
{
|
||||
Object.entries(Constants.menuColors).map(
|
||||
([key, color]: [string, string]) => (
|
||||
<Menu.Color
|
||||
key={key}
|
||||
id={key}
|
||||
name={color}
|
||||
onClick={() => handleOnColorChange(key)}
|
||||
/>
|
||||
),
|
||||
)
|
||||
}
|
||||
</Menu>
|
||||
</MenuWrapper>
|
||||
<MenuWrapper
|
||||
stopPropagationOnToggle={true}
|
||||
onToggle={(open) => setMenuOpen(open)}
|
||||
>
|
||||
<div
|
||||
ref={menuRef}
|
||||
className='contextMenu'
|
||||
>
|
||||
<OptionsIcon/>
|
||||
</div>
|
||||
<Menu
|
||||
position='auto'
|
||||
parentRef={menuRef}
|
||||
menuMargin={30}
|
||||
fixed={true}
|
||||
>
|
||||
<Menu.Text
|
||||
id='editText'
|
||||
name={'Edit'}
|
||||
icon={<EditIcon/>}
|
||||
onClick={handleEditButtonClick}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='deleteOption'
|
||||
name={'Delete'}
|
||||
icon={<DeleteIcon/>}
|
||||
onClick={handleDeleteButtonClick}
|
||||
className='text-danger'
|
||||
/>
|
||||
</Menu>
|
||||
</MenuWrapper>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ValueRow)
|
|
@ -3,7 +3,7 @@
|
|||
exports[`components/viewLimitDialog/ViewLiimitDialog show notify upgrade button for non sys admin user 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ViewLimitDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ViewLimitDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -12,7 +12,7 @@ exports[`components/viewLimitDialog/ViewLiimitDialog show notify upgrade button
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -85,7 +85,7 @@ exports[`components/viewLimitDialog/ViewLiimitDialog show notify upgrade button
|
|||
exports[`components/viewLimitDialog/ViewLiimitDialog show upgrade button for sys admin user 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ViewLimitDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ViewLimitDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -94,7 +94,7 @@ exports[`components/viewLimitDialog/ViewLiimitDialog show upgrade button for sys
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`components/viewLimitDialog/ViewL]imitDialog show notify admin confirmation msg 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ViewLimitDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ViewLimitDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -12,7 +12,7 @@ exports[`components/viewLimitDialog/ViewL]imitDialog show notify admin confirmat
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
@ -119,7 +119,7 @@ exports[`components/viewLimitDialog/ViewL]imitDialog show notify admin confirmat
|
|||
exports[`components/viewLimitDialog/ViewL]imitDialog show view limit dialog 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ViewLimitDialog size--medium"
|
||||
class="ActionDialog Dialog dialog-back ViewLimitDialog size--medium"
|
||||
>
|
||||
<div
|
||||
class="backdrop"
|
||||
|
@ -128,7 +128,7 @@ exports[`components/viewLimitDialog/ViewL]imitDialog show view limit dialog 1`]
|
|||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
class="dialog footerHidden"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -5,6 +5,9 @@ import React from 'react'
|
|||
import './button.scss'
|
||||
import {Utils} from '../../utils'
|
||||
|
||||
export type ButtonSize = 'xsmall' | 'small' | 'medium' | 'large'
|
||||
export type Emphasis = 'primary' | 'secondary' | 'tertiary' | 'quaternary' | 'gray' | 'danger' | 'default' | 'link'
|
||||
|
||||
type Props = {
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
onMouseOver?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
|
@ -16,8 +19,8 @@ type Props = {
|
|||
filled?: boolean
|
||||
active?: boolean
|
||||
submit?: boolean
|
||||
emphasis?: string
|
||||
size?: string
|
||||
emphasis?: Emphasis
|
||||
size?: ButtonSize
|
||||
danger?: boolean
|
||||
className?: string
|
||||
rightIcon?: boolean
|
||||
|
|
15
webapp/src/widgets/icons/blackCheckboxOutline.tsx
Normal file
15
webapp/src/widgets/icons/blackCheckboxOutline.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import CompassIcon from './compassIcon'
|
||||
|
||||
export default function BlackCheckboxOutline(): JSX.Element {
|
||||
return (
|
||||
<CompassIcon
|
||||
icon='checkbox-blank-outline'
|
||||
className='BlackCheckboxOutline'
|
||||
/>
|
||||
)
|
||||
}
|
15
webapp/src/widgets/icons/clockOutline.tsx
Normal file
15
webapp/src/widgets/icons/clockOutline.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import CompassIcon from './compassIcon'
|
||||
|
||||
export default function ClockOutline(): JSX.Element {
|
||||
return (
|
||||
<CompassIcon
|
||||
icon='clock-outline'
|
||||
className='ClockOutline'
|
||||
/>
|
||||
)
|
||||
}
|
15
webapp/src/widgets/icons/dragHandle.tsx
Normal file
15
webapp/src/widgets/icons/dragHandle.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import CompassIcon from './compassIcon'
|
||||
|
||||
export default function DragHandle(): JSX.Element {
|
||||
return (
|
||||
<CompassIcon
|
||||
icon='drag-vertical'
|
||||
className='DragHandle'
|
||||
/>
|
||||
)
|
||||
}
|
15
webapp/src/widgets/icons/info.tsx
Normal file
15
webapp/src/widgets/icons/info.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import CompassIcon from './compassIcon'
|
||||
|
||||
export default function InfoIcon(): JSX.Element {
|
||||
return (
|
||||
<CompassIcon
|
||||
icon='information-outline'
|
||||
className='InfoIcon'
|
||||
/>
|
||||
)
|
||||
}
|
15
webapp/src/widgets/icons/plus.tsx
Normal file
15
webapp/src/widgets/icons/plus.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import CompassIcon from './compassIcon'
|
||||
|
||||
export default function PlusIcon(): JSX.Element {
|
||||
return (
|
||||
<CompassIcon
|
||||
icon='plus'
|
||||
className='PlusIcon'
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -11,14 +11,15 @@ type Props = {
|
|||
title?: string
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
editing?: boolean
|
||||
}
|
||||
|
||||
// Switch is an on-off style switch / checkbox
|
||||
function Label(props: Props): JSX.Element {
|
||||
let color = 'empty'
|
||||
if (props.color && props.color in Constants.menuColors) {
|
||||
color = props.color
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`Label ${color} ${props.className ? props.className : ''}`}
|
||||
|
|
|
@ -18,6 +18,7 @@ type Props = {
|
|||
position?: 'top' | 'bottom' | 'left' | 'right' | 'auto'
|
||||
fixed?: boolean
|
||||
parentRef?: React.RefObject<any>
|
||||
menuMargin?: number
|
||||
}
|
||||
|
||||
export default class Menu extends React.PureComponent<Props> {
|
||||
|
@ -48,7 +49,7 @@ export default class Menu extends React.PureComponent<Props> {
|
|||
let style: CSSProperties = {}
|
||||
if (this.props.parentRef) {
|
||||
const forceBottom = position ? ['bottom', 'left', 'right'].includes(position) : false
|
||||
style = MenuUtil.openUp(this.props.parentRef, forceBottom).style
|
||||
style = MenuUtil.openUp(this.props.parentRef, forceBottom, this.props.menuMargin).style
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -47,10 +47,19 @@ $tooltop-vertical-offset: 2px;
|
|||
position: relative;
|
||||
// Arrow gets added before content
|
||||
&:hover::before {
|
||||
@extend %hover-tooltip-arrow; }
|
||||
@extend %hover-tooltip-arrow;
|
||||
}
|
||||
// Tooltip message gets added after content
|
||||
&:hover::after {
|
||||
@extend %hover-tooltip-body; }
|
||||
@extend %hover-tooltip-body;
|
||||
}
|
||||
|
||||
&.wrap {
|
||||
&:hover::after {
|
||||
word-break: break-word;
|
||||
white-space: unset;
|
||||
}
|
||||
}
|
||||
|
||||
// Top tooltip arrow style
|
||||
&.tooltip-top:hover::before {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import './tooltip.scss'
|
||||
|
@ -8,6 +9,7 @@ type Props = {
|
|||
title: string
|
||||
children: React.ReactNode
|
||||
placement?: 'top'|'left'|'right'|'bottom'
|
||||
wrap?: boolean
|
||||
}
|
||||
|
||||
// Adds tooltip div over children elements, the popup will
|
||||
|
@ -15,7 +17,7 @@ type Props = {
|
|||
// Default position is 'top'
|
||||
function Tooltip(props: Props): JSX.Element {
|
||||
const placement = props.placement || 'top'
|
||||
const className = `octo-tooltip tooltip-${placement}`
|
||||
const className = `octo-tooltip tooltip-${placement}${props.wrap ? ' wrap' : ''}`
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
|
|
Loading…
Reference in a new issue