Adding menu options in sidebar, and improving order (#3713)

* Adding category and board options at the top

* Updating UI

* Updating test

* Updating test

* Updating icon

* Fixing bug and translation

* Updating createCategory component

* Removing unused vars

* Removing unused vars

* Updating UI

* fixed tests

* fixed tests

* fixed tests

* Updating test

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com>
This commit is contained in:
Asaad Mahmood 2022-08-25 18:09:09 +05:00 committed by GitHub
parent 61a8af8f34
commit 62ffa9c39a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 355 additions and 246 deletions

View file

@ -23,7 +23,7 @@ exports[`components/boardSelector renders with no results 1`] = `
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >
@ -126,7 +126,7 @@ exports[`components/boardSelector renders with some results 1`] = `
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >
@ -327,7 +327,7 @@ exports[`components/boardSelector renders without start searching 1`] = `
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >

View file

@ -23,15 +23,8 @@
position: relative; position: relative;
width: 600px; width: 600px;
height: 450px; height: 450px;
.toolbar {
flex-direction: row-reverse;
padding: 0;
position: absolute;
right: 18px;
top: 18px;
}
} }
.confirmation-dialog-box { .confirmation-dialog-box {
.dialog { .dialog {
position: fixed; position: fixed;

View file

@ -242,6 +242,7 @@
"Sidebar.untitled-view": "(Untitled View)", "Sidebar.untitled-view": "(Untitled View)",
"SidebarCategories.BlocksMenu.Move": "Move To...", "SidebarCategories.BlocksMenu.Move": "Move To...",
"SidebarCategories.CategoryMenu.CreateNew": "Create New Category", "SidebarCategories.CategoryMenu.CreateNew": "Create New Category",
"SidebarCategories.CategoryMenu.CreateBoard": "Create New Board",
"SidebarCategories.CategoryMenu.Delete": "Delete Category", "SidebarCategories.CategoryMenu.Delete": "Delete Category",
"SidebarCategories.CategoryMenu.DeleteModal.Body": "Boards in <b>{categoryName}</b> will move back to the Boards categories. You're not removed from any boards.", "SidebarCategories.CategoryMenu.DeleteModal.Body": "Boards in <b>{categoryName}</b> will move back to the Boards categories. You're not removed from any boards.",
"SidebarCategories.CategoryMenu.DeleteModal.Title": "Delete this category?", "SidebarCategories.CategoryMenu.DeleteModal.Title": "Delete this category?",
@ -400,4 +401,4 @@
"tutorial_tip.ok": "Next", "tutorial_tip.ok": "Next",
"tutorial_tip.out": "Opt out of these tips.", "tutorial_tip.out": "Opt out of these tips.",
"tutorial_tip.seen": "Seen this before?" "tutorial_tip.seen": "Seen this before?"
} }

View file

@ -20,7 +20,7 @@ exports[`/components/confirmationDialogBox confirmDialog should match snapshot 1
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >
@ -95,7 +95,7 @@ exports[`/components/confirmationDialogBox confirmDialog with Confirm Button Tex
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >

View file

@ -20,7 +20,7 @@ exports[`components/dialog should match snapshot 1`] = `
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >
@ -61,7 +61,7 @@ exports[`components/dialog should return dialog and click on cancel button 1`] =
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >

View file

@ -9,8 +9,8 @@
background-color: rgba(var(--sidebar-text-rgb), 0.08); background-color: rgba(var(--sidebar-text-rgb), 0.08);
color: rgba(var(--sidebar-text-rgb), 0.56); color: rgba(var(--sidebar-text-rgb), 0.56);
flex: 1; flex: 1;
padding: 6px 8px; padding: 6px 8px 6px 4px;
gap: 6px; gap: 4px;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
height: 28px; height: 28px;
@ -28,9 +28,17 @@
} }
.add-board-icon { .add-board-icon {
margin-left: 4px; border-radius: 28px;
margin-left: 8px;
width: 28px; width: 28px;
height: 28px; height: 28px;
flex: 0 0 28px; flex: 0 0 28px;
background-color: rgba(var(--sidebar-text-rgb), 0.08);
color: rgba(var(--sidebar-text-rgb), 0.72);
&:hover {
background-color: rgba(var(--sidebar-text-rgb), 0.16);
color: var(--sidebar-text);
}
} }
} }

View file

@ -2,17 +2,19 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useEffect, useState} from 'react' import React, {useEffect, useState} from 'react'
import {useIntl} from 'react-intl' import {FormattedMessage, useIntl} from 'react-intl'
import MenuWrapper from '../../widgets/menuWrapper'
import CompassIcon from '../../widgets/icons/compassIcon'
import Menu from '../../widgets/menu'
import Search from '../../widgets/icons/search' import Search from '../../widgets/icons/search'
import CreateCategory from '../createCategory/createCategory'
import {useAppSelector} from '../../store/hooks' import {useAppSelector} from '../../store/hooks'
import { import {
getOnboardingTourCategory, getOnboardingTourCategory,
getOnboardingTourStep, getOnboardingTourStep,
} from '../../store/users' } from '../../store/users'
import {getCurrentCard} from '../../store/cards' import {getCurrentCard} from '../../store/cards'
import './boardsSwitcher.scss' import './boardsSwitcher.scss'
@ -26,7 +28,7 @@ import IconButton from '../../widgets/buttons/iconButton'
import SearchForBoardsTourStep from '../../components/onboardingTour/searchForBoards/searchForBoards' import SearchForBoardsTourStep from '../../components/onboardingTour/searchForBoards/searchForBoards'
type Props = { type Props = {
onBoardTemplateSelectorOpen?: () => void, onBoardTemplateSelectorOpen: () => void,
userIsGuest?: boolean, userIsGuest?: boolean,
} }
@ -35,11 +37,11 @@ const BoardsSwitcher = (props: Props): JSX.Element => {
const [showSwitcher, setShowSwitcher] = useState<boolean>(false) const [showSwitcher, setShowSwitcher] = useState<boolean>(false)
const onboardingTourCategory = useAppSelector(getOnboardingTourCategory) const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)
const [showCreateCategoryModal, setShowCreateCategoryModal] = useState(false)
const onboardingTourStep = useAppSelector(getOnboardingTourStep) const onboardingTourStep = useAppSelector(getOnboardingTourStep)
const currentCard = useAppSelector(getCurrentCard) const currentCard = useAppSelector(getCurrentCard)
const noCardOpen = !currentCard const noCardOpen = !currentCard
const shouldViewSearchForBoardsTour = noCardOpen && const shouldViewSearchForBoardsTour = noCardOpen &&
onboardingTourCategory === TOUR_SIDEBAR && onboardingTourCategory === TOUR_SIDEBAR &&
onboardingTourStep === SidebarTourSteps.SEARCH_FOR_BOARDS.toString() onboardingTourStep === SidebarTourSteps.SEARCH_FOR_BOARDS.toString()
@ -68,6 +70,10 @@ const BoardsSwitcher = (props: Props): JSX.Element => {
} }
} }
const handleCreateNewCategory = () => {
setShowCreateCategoryModal(true)
}
useEffect(() => { useEffect(() => {
document.addEventListener('keydown', handleQuickSwitchKeyPress) document.addEventListener('keydown', handleQuickSwitchKeyPress)
document.addEventListener('keydown', handleEscKeyPress) document.addEventListener('keydown', handleEscKeyPress)
@ -95,19 +101,53 @@ const BoardsSwitcher = (props: Props): JSX.Element => {
{shouldViewSearchForBoardsTour && <div><SearchForBoardsTourStep/></div>} {shouldViewSearchForBoardsTour && <div><SearchForBoardsTourStep/></div>}
{ {
Utils.isFocalboardPlugin() && !props.userIsGuest && Utils.isFocalboardPlugin() && !props.userIsGuest &&
<IconButton <MenuWrapper>
size='small' <IconButton
inverted={true} size='small'
className='add-board-icon' inverted={true}
onClick={props.onBoardTemplateSelectorOpen} className='add-board-icon'
icon={<AddIcon/>} icon={<AddIcon/>}
/> />
<Menu>
<Menu.Text
id='create-new-board-option'
icon={<CompassIcon icon='plus' />}
onClick={props.onBoardTemplateSelectorOpen}
name='Create new board'
/>
<Menu.Text
id='createNewCategory'
name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.CreateNew', defaultMessage: 'Create New Category'})}
icon={
<CompassIcon
icon='folder-plus-outline'
className='CreateNewFolderIcon'
/>
}
onClick={handleCreateNewCategory}
/>
</Menu>
</MenuWrapper>
} }
{ {
showSwitcher && showSwitcher &&
<BoardSwitcherDialog onClose={() => setShowSwitcher(false)} /> <BoardSwitcherDialog onClose={() => setShowSwitcher(false)} />
} }
{
showCreateCategoryModal && (
<CreateCategory
onClose={() => setShowCreateCategoryModal(false)}
title={(
<FormattedMessage
id='SidebarCategories.CategoryMenu.CreateNew'
defaultMessage='Create New Category'
/>
)}
/>
)
}
</div> </div>
) )
} }

View file

@ -20,7 +20,7 @@ exports[`component/BoardSwitcherDialog base case 1`] = `
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >

View file

@ -10,10 +10,6 @@
color: rgba(var(--center-channel-color-rgb), 0.56); color: rgba(var(--center-channel-color-rgb), 0.56);
} }
.toolbar {
padding: 4px;
}
span { span {
display: inline-block; display: inline-block;
height: 100%; height: 100%;

View file

@ -20,7 +20,7 @@ exports[`components/createCategory/CreateCategory base case should match snapsho
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >
@ -35,7 +35,9 @@ exports[`components/createCategory/CreateCategory base case should match snapsho
<div <div
class="CreateCategory" class="CreateCategory"
> >
<h3> <h3
class="dialog-title"
>
<span> <span>
title title
</span> </span>

View file

@ -4,20 +4,14 @@
.wrapper { .wrapper {
.dialog { .dialog {
width: 600px; width: 600px;
height: 260px; height: auto;
}
.toolbar {
flex-direction: row-reverse;
padding: 2px 6px;
} }
.CreateCategory { .CreateCategory {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0 40px 30px; padding: 0 32px 24px;
gap: 12px; gap: 24px;
flex: 1;
.inputWrapper { .inputWrapper {
position: relative; position: relative;
@ -38,18 +32,20 @@
width: 100%; width: 100%;
} }
h3 {
margin-top: 0;
}
input { input {
height: 48px;
font-size: 16px; font-size: 16px;
padding: 6px 12px;
border-radius: 4px; border-radius: 4px;
border: 2px solid rgba(var(--center-channel-color-rgb), 0.16); border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
background: var(--center-channel-bg);
color: var(--center-channel-color);
padding: 0 16px;
flex: 1;
transition: border 0.15s ease-in;
&:focus { &:focus {
border-color: var(--button-bg); border-color: var(--button-bg);
box-shadow: inset 0 0 0 1px var(--button-bg);
} }
} }

View file

@ -7,20 +7,59 @@ import {render} from "@testing-library/react"
import userEvent from "@testing-library/user-event" import userEvent from "@testing-library/user-event"
import {wrapIntl} from "../../testUtils" import thunk from "redux-thunk"
import {Provider as ReduxProvider} from 'react-redux'
import {mocked} from "jest-mock"
import {mockStateStore, wrapIntl} from "../../testUtils"
import {IUser} from "../../user"
import mutator from "../../mutator"
import CreateCategory from "./createCategory" import CreateCategory from "./createCategory"
jest.mock('../../mutator')
const mockedMutator = mocked(mutator, true)
describe('components/createCategory/CreateCategory', () => { describe('components/createCategory/CreateCategory', () => {
const me: IUser = {
id: 'user-id-1',
username: 'username_1',
email: '',
nickname: '',
firstname: '',
lastname: '',
props: {},
create_at: 0,
update_at: 0,
is_bot: false,
roles: 'system_user',
is_guest: false,
}
const state = {
teams: {
current: {id: 'team-id', title: 'Test Team'},
},
users: {
me,
},
}
const store = mockStateStore([thunk], state)
it('base case should match snapshot', () => { it('base case should match snapshot', () => {
const component = wrapIntl( const component = wrapIntl(
<CreateCategory <ReduxProvider store={store}>
onClose={jest.fn()} <CreateCategory
onCreate={jest.fn()} onClose={jest.fn()}
title={ title={
<span>{'title'}</span> <span>{'title'}</span>
} }
/> />
</ReduxProvider>
) )
const {container} = render(component) const {container} = render(component)
@ -30,13 +69,14 @@ describe('components/createCategory/CreateCategory', () => {
it('should call onClose on being closed', () => { it('should call onClose on being closed', () => {
const onCloseHandler = jest.fn() const onCloseHandler = jest.fn()
const component = wrapIntl( const component = wrapIntl(
<CreateCategory <ReduxProvider store={store}>
onClose={onCloseHandler} <CreateCategory
onCreate={jest.fn()} onClose={onCloseHandler}
title={ title={
<span>{'title'}</span> <span>{'title'}</span>
} }
/> />
</ReduxProvider>
) )
const {container} = render(component) const {container} = render(component)
@ -52,35 +92,39 @@ describe('components/createCategory/CreateCategory', () => {
}) })
it('should call onCreate on pressing enter', () => { it('should call onCreate on pressing enter', () => {
const onCreateHandler = jest.fn()
const component = wrapIntl( const component = wrapIntl(
<CreateCategory <ReduxProvider store={store}>
onClose={jest.fn()} <CreateCategory
onCreate={onCreateHandler} onClose={jest.fn()}
title={ title={
<span>{'title'}</span> <span>{'title'}</span>
} }
/> />
</ReduxProvider>
) )
const {container} = render(component) const {container} = render(component)
const inputField = container.querySelector('.categoryNameInput') const inputField = container.querySelector('.categoryNameInput')
expect(inputField).toBeTruthy() expect(inputField).toBeTruthy()
userEvent.type(inputField as Element, 'category name{enter}') userEvent.type(inputField as Element, 'category name{enter}')
expect(onCreateHandler).toBeCalledWith('category name') expect(mockedMutator.createCategory).toBeCalledWith({
name: "category name",
teamID: "team-id",
userID: "user-id-1",
})
}) })
it('should show initial value', () => { it('should show initial value', () => {
const onCreateHandler = jest.fn()
const component = wrapIntl( const component = wrapIntl(
<CreateCategory <ReduxProvider store={store}>
initialValue='Dwight prank ideas' <CreateCategory
onClose={jest.fn()} initialValue='Dwight prank ideas'
onCreate={onCreateHandler} onClose={jest.fn()}
title={ title={
<span>{'title'}</span> <span>{'title'}</span>
} }
/> />
</ReduxProvider>
) )
const {container} = render(component) const {container} = render(component)
@ -90,16 +134,16 @@ describe('components/createCategory/CreateCategory', () => {
}) })
it('should clear input field on clicking clear icon', () => { it('should clear input field on clicking clear icon', () => {
const onCreateHandler = jest.fn()
const component = wrapIntl( const component = wrapIntl(
<CreateCategory <ReduxProvider store={store}>
initialValue='Dunder Mifflin' <CreateCategory
onClose={jest.fn()} initialValue='Dunder Mifflin'
onCreate={onCreateHandler} onClose={jest.fn()}
title={ title={
<span>{'title'}</span> <span>{'title'}</span>
} }
/> />
</ReduxProvider>
) )
const {container} = render(component) const {container} = render(component)

View file

@ -5,6 +5,17 @@ import React, {useState, KeyboardEvent} from 'react'
import {useIntl} from 'react-intl' import {useIntl} from 'react-intl'
import {IUser} from '../../user'
import {Category} from '../../store/sidebar'
import {getCurrentTeam} from '../../store/teams'
import mutator from '../../mutator'
import {useAppSelector} from '../../store/hooks'
import {
getMe,
} from '../../store/users'
import {Utils} from '../../utils'
import Dialog from '../dialog' import Dialog from '../dialog'
import Button from '../../widgets/buttons/button' import Button from '../../widgets/buttons/button'
@ -12,15 +23,18 @@ import './createCategory.scss'
import CloseCircle from "../../widgets/icons/closeCircle" import CloseCircle from "../../widgets/icons/closeCircle"
type Props = { type Props = {
boardCategoryId?: string
renameModal?: boolean
initialValue?: string initialValue?: string
onClose: () => void onClose: () => void
onCreate: (name: string) => void
title: JSX.Element title: JSX.Element
} }
const CreateCategory = (props: Props): JSX.Element => { const CreateCategory = (props: Props): JSX.Element => {
const intl = useIntl() const intl = useIntl()
const me = useAppSelector<IUser|null>(getMe)
const team = useAppSelector(getCurrentTeam)
const teamID = team?.id || ''
const placeholder = intl.formatMessage({id: 'Categories.CreateCategoryDialog.Placeholder', defaultMessage: 'Name your category' }) const placeholder = intl.formatMessage({id: 'Categories.CreateCategoryDialog.Placeholder', defaultMessage: 'Name your category' })
const cancelText = intl.formatMessage({id: 'Categories.CreateCategoryDialog.CancelText', defaultMessage: 'Cancel' }) const cancelText = intl.formatMessage({id: 'Categories.CreateCategoryDialog.CancelText', defaultMessage: 'Cancel' })
const createText = intl.formatMessage({id: 'Categories.CreateCategoryDialog.CreateText', defaultMessage: 'Create' }) const createText = intl.formatMessage({id: 'Categories.CreateCategoryDialog.CreateText', defaultMessage: 'Create' })
@ -30,17 +44,45 @@ const CreateCategory = (props: Props): JSX.Element => {
const handleKeypress = (e: KeyboardEvent) => { const handleKeypress = (e: KeyboardEvent) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
props.onCreate(name) onCreate(name)
} }
} }
const onCreate = async (categoryName: string) => {
if (!me) {
Utils.logError('me not initialized')
return
}
if (props.renameModal) {
const category: Category = {
name: categoryName,
id: props.boardCategoryId,
userID: me.id,
teamID,
} as Category
await mutator.updateCategory(category)
} else {
const category: Category = {
name: categoryName,
userID: me.id,
teamID,
} as Category
await mutator.createCategory(category)
}
props.onClose()
}
return ( return (
<Dialog <Dialog
className='CreateCategoryModal' className='CreateCategoryModal'
onClose={props.onClose} onClose={props.onClose}
> >
<div className='CreateCategory'> <div className='CreateCategory'>
<h3>{props.title}</h3> <h3 className='dialog-title'>{props.title}</h3>
<div className='inputWrapper'> <div className='inputWrapper'>
<input <input
className='categoryNameInput' className='categoryNameInput'
@ -70,7 +112,7 @@ const CreateCategory = (props: Props): JSX.Element => {
<Button <Button
size={'medium'} size={'medium'}
filled={Boolean(name.trim())} filled={Boolean(name.trim())}
onClick={() => props.onCreate(name.trim())} onClick={() => onCreate(name.trim())}
disabled={!(name.trim())} disabled={!(name.trim())}
> >
{props.initialValue ? updateText : createText} {props.initialValue ? updateText : createText}

View file

@ -10,6 +10,19 @@
bottom: 0; bottom: 0;
} }
.dialog-title {
margin-top: 24px;
font-weight: 600;
font-size: 22px;
line-height: 28px;
}
.dialog__close {
top: 18px;
right: 18px;
position: absolute;
}
.backdrop { .backdrop {
@include z-index(dialog-backdrop); @include z-index(dialog-backdrop);
position: fixed; position: fixed;
@ -26,14 +39,8 @@
justify-content: center; justify-content: center;
} }
.toolbar {
display: flex;
flex-direction: row;
padding: 16px;
justify-content: space-between;
}
.dialog { .dialog {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: rgb(var(--center-channel-bg-rgb)); background-color: rgb(var(--center-channel-bg-rgb));
@ -55,6 +62,7 @@
display: none !important; display: none !important;
} }
} }
@media screen and (max-width: 975px) { @media screen and (max-width: 975px) {
position: fixed; position: fixed;
top: 0; top: 0;
@ -63,26 +71,33 @@
bottom: 0; bottom: 0;
} }
> * { >* {
flex-shrink: 0; flex-shrink: 0;
} }
> .banner { >.banner {
background-color: rgba(230, 220, 192, 0.9); background-color: rgba(230, 220, 192, 0.9);
text-align: center; text-align: center;
padding: 10px; padding: 10px;
color: #222; color: #222;
} }
> .banner.error { >.banner.error {
background-color: rgba(230, 192, 192, 0.9); background-color: rgba(230, 192, 192, 0.9);
} }
> .toolbar { .IconButton {
display: flex; color: rgba(var(--center-channel-color-rgb), 0.56);
flex-direction: row;
padding: 24px; &:hover {
justify-content: space-between; 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--right { .toolbar--right {
@ -90,7 +105,7 @@
gap: 8px; gap: 8px;
} }
> .content { >.content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
@ -98,12 +113,13 @@
@media not screen and (max-width: 975px) { @media not screen and (max-width: 975px) {
padding: 10px 126px; padding: 10px 126px;
} }
@media screen and (max-width: 975px) { @media screen and (max-width: 975px) {
padding: 10px; padding: 10px;
} }
} }
> .content.fullwidth { >.content.fullwidth {
padding-left: 78px; padding-left: 78px;
} }
} }

View file

@ -45,7 +45,7 @@ const Dialog = (props: Props) => {
} }
isBackdropClickedRef.current = false isBackdropClickedRef.current = false
props.onClose() props.onClose()
}} }}
onMouseDown={(e) => { onMouseDown={(e) => {
if(e.target === e.currentTarget){ if(e.target === e.currentTarget){
@ -58,10 +58,11 @@ const Dialog = (props: Props) => {
className='dialog' className='dialog'
> >
<div className='toolbar'> <div className='toolbar'>
{title && <h1 className='text-heading5 mt-2'>{title}</h1>} {title && <h1 className='dialog-title'>{title}</h1>}
{ {
!props.hideCloseButton && !props.hideCloseButton &&
<IconButton <IconButton
className='dialog__close'
onClick={props.onClose} onClick={props.onClose}
icon={<CloseIcon/>} icon={<CloseIcon/>}
title={closeDialogText} title={closeDialogText}

View file

@ -8,18 +8,6 @@
} }
} }
.wrapper {
.dialog {
.toolbar {
flex-direction: row-reverse;
padding: 0;
position: absolute;
right: 18px;
top: 18px;
}
}
}
.wrapper { .wrapper {
.dialog { .dialog {
position: relative; position: relative;
@ -58,7 +46,7 @@
padding: 0 32px; padding: 0 32px;
cursor: pointer; cursor: pointer;
overflow: hidden; overflow: hidden;
&.freesize { &.freesize {
height: unset; height: unset;
} }

View file

@ -107,33 +107,6 @@ exports[`components/sidebarBoardItem sidebar board item 1`] = `
<div <div
class="menu-options" class="menu-options"
> >
<div>
<div
aria-label="Delete board"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<i
class="CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Delete board
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
<div> <div>
<div <div
class="MenuOption SubMenuOption menu-option boardMoveToCategorySubmenu" class="MenuOption SubMenuOption menu-option boardMoveToCategorySubmenu"
@ -239,6 +212,33 @@ exports[`components/sidebarBoardItem sidebar board item 1`] = `
/> />
</div> </div>
</div> </div>
<div>
<div
aria-label="Delete board"
class="MenuOption TextOption menu-option text-danger"
role="button"
>
<div
class="d-flex"
>
<i
class="CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Delete board
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
</div> </div>
<div <div
class="menu-spacer hideOnWidescreen" class="menu-spacer hideOnWidescreen"
@ -349,33 +349,6 @@ exports[`components/sidebarBoardItem sidebar board item for guest 1`] = `
<div <div
class="menu-options" class="menu-options"
> >
<div>
<div
aria-label="Delete board"
class="MenuOption TextOption menu-option"
role="button"
>
<div
class="d-flex"
>
<i
class="CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Delete board
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
<div> <div>
<div <div
class="MenuOption SubMenuOption menu-option boardMoveToCategorySubmenu" class="MenuOption SubMenuOption menu-option boardMoveToCategorySubmenu"
@ -429,6 +402,33 @@ exports[`components/sidebarBoardItem sidebar board item for guest 1`] = `
/> />
</div> </div>
</div> </div>
<div>
<div
aria-label="Delete board"
class="MenuOption TextOption menu-option text-danger"
role="button"
>
<div
class="d-flex"
>
<i
class="CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline"
/>
</div>
<div
class="menu-option__content"
>
<div
class="menu-name"
>
Delete board
</div>
</div>
<div
class="noicon"
/>
</div>
</div>
</div> </div>
<div <div
class="menu-spacer hideOnWidescreen" class="menu-spacer hideOnWidescreen"

View file

@ -69,11 +69,12 @@ describe('components/sidebarSidebar', () => {
}) })
const history = createMemoryHistory() const history = createMemoryHistory()
const onBoardTemplateSelectorOpen = jest.fn()
const component = wrapIntl( const component = wrapIntl(
<ReduxProvider store={store}> <ReduxProvider store={store}>
<Router history={history}> <Router history={history}>
<Sidebar/> <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>
</Router> </Router>
</ReduxProvider>, </ReduxProvider>,
) )
@ -131,11 +132,12 @@ describe('components/sidebarSidebar', () => {
}) })
const history = createMemoryHistory() const history = createMemoryHistory()
const onBoardTemplateSelectorOpen = jest.fn()
const component = wrapIntl( const component = wrapIntl(
<ReduxProvider store={store}> <ReduxProvider store={store}>
<Router history={history}> <Router history={history}>
<Sidebar/> <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>
</Router> </Router>
</ReduxProvider>, </ReduxProvider>,
) )
@ -192,11 +194,12 @@ describe('components/sidebarSidebar', () => {
}) })
const history = createMemoryHistory() const history = createMemoryHistory()
const onBoardTemplateSelectorOpen = jest.fn()
const component = wrapIntl( const component = wrapIntl(
<ReduxProvider store={store}> <ReduxProvider store={store}>
<Router history={history}> <Router history={history}>
<Sidebar/> <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>
</Router> </Router>
</ReduxProvider>, </ReduxProvider>,
) )
@ -254,11 +257,12 @@ describe('components/sidebarSidebar', () => {
}) })
const history = createMemoryHistory() const history = createMemoryHistory()
const onBoardTemplateSelectorOpen = jest.fn()
const component = wrapIntl( const component = wrapIntl(
<ReduxProvider store={store}> <ReduxProvider store={store}>
<Router history={history}> <Router history={history}>
<Sidebar/> <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>
</Router> </Router>
</ReduxProvider>, </ReduxProvider>,
) )
@ -305,7 +309,7 @@ describe('components/sidebarSidebar', () => {
// const component = wrapIntl( // const component = wrapIntl(
// <ReduxProvider store={store}> // <ReduxProvider store={store}>
// <Router history={history}> // <Router history={history}>
// <Sidebar/> // <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>
// </Router> // </Router>
// </ReduxProvider>, // </ReduxProvider>,
// ) // )

View file

@ -40,7 +40,7 @@ import {addMissingItems} from './utils'
type Props = { type Props = {
activeBoardId?: string activeBoardId?: string
onBoardTemplateSelectorOpen?: () => void onBoardTemplateSelectorOpen: () => void
} }
function getWindowDimensions() { function getWindowDimensions() {

View file

@ -235,20 +235,6 @@ const SidebarBoardItem = (props: Props) => {
position='auto' position='auto'
parentRef={boardItemRef} parentRef={boardItemRef}
> >
<BoardPermissionGate
boardId={board.id}
permissions={[Permission.DeleteBoard]}
>
<Menu.Text
key={`deleteBlock-${board.id}`}
id='deleteBlock'
name={intl.formatMessage({id: 'Sidebar.delete-board', defaultMessage: 'Delete board'})}
icon={<DeleteIcon/>}
onClick={() => {
props.onDeleteRequest(board)
}}
/>
</BoardPermissionGate>
<Menu.SubMenu <Menu.SubMenu
key={`moveBlock-${board.id}`} key={`moveBlock-${board.id}`}
id='moveBlock' id='moveBlock'
@ -279,6 +265,21 @@ const SidebarBoardItem = (props: Props) => {
icon={<CloseIcon/>} icon={<CloseIcon/>}
onClick={() => handleHideBoard()} onClick={() => handleHideBoard()}
/> />
<BoardPermissionGate
boardId={board.id}
permissions={[Permission.DeleteBoard]}
>
<Menu.Text
key={`deleteBlock-${board.id}`}
id='deleteBlock'
className='text-danger'
name={intl.formatMessage({id: 'Sidebar.delete-board', defaultMessage: 'Delete board'})}
icon={<DeleteIcon/>}
onClick={() => {
props.onDeleteRequest(board)
}}
/>
</BoardPermissionGate>
</Menu> </Menu>
</MenuWrapper> </MenuWrapper>
</div> </div>

View file

@ -10,6 +10,7 @@ import {Board} from '../../blocks/board'
import mutator from '../../mutator' import mutator from '../../mutator'
import IconButton from '../../widgets/buttons/iconButton' import IconButton from '../../widgets/buttons/iconButton'
import DeleteIcon from '../../widgets/icons/delete' import DeleteIcon from '../../widgets/icons/delete'
import CompassIcon from '../../widgets/icons/compassIcon'
import OptionsIcon from '../../widgets/icons/options' import OptionsIcon from '../../widgets/icons/options'
import Menu from '../../widgets/menu' import Menu from '../../widgets/menu'
import MenuWrapper from '../../widgets/menuWrapper' import MenuWrapper from '../../widgets/menuWrapper'
@ -30,7 +31,6 @@ import {
import {getCurrentCard} from '../../store/cards' import {getCurrentCard} from '../../store/cards'
import {Utils} from '../../utils' import {Utils} from '../../utils'
import Update from '../../widgets/icons/update'
import { TOUR_SIDEBAR, SidebarTourSteps, TOUR_BOARD, FINISHED } from '../../components/onboardingTour/index' import { TOUR_SIDEBAR, SidebarTourSteps, TOUR_BOARD, FINISHED } from '../../components/onboardingTour/index'
import telemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient' import telemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
@ -244,6 +244,12 @@ const SidebarCategory = (props: Props) => {
{ {
props.categoryBoards.id !== '' && props.categoryBoards.id !== '' &&
<React.Fragment> <React.Fragment>
<Menu.Text
id='updateCategory'
name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.Update', defaultMessage: 'Rename Category'})}
icon={<CompassIcon icon='pencil-outline'/>}
onClick={handleUpdateCategory}
/>
<Menu.Text <Menu.Text
id='deleteCategory' id='deleteCategory'
className='text-danger' className='text-danger'
@ -251,11 +257,12 @@ const SidebarCategory = (props: Props) => {
icon={<DeleteIcon/>} icon={<DeleteIcon/>}
onClick={() => setShowDeleteCategoryDialog(true)} onClick={() => setShowDeleteCategoryDialog(true)}
/> />
<Menu.Separator/>
<Menu.Text <Menu.Text
id='updateCategory' id='createNewCategory'
name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.Update', defaultMessage: 'Rename Category'})} name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.CreateNew', defaultMessage: 'Create New Category'})}
icon={<Update/>} icon={<CreateNewFolder/>}
onClick={handleUpdateCategory} onClick={handleCreateNewCategory}
/> />
</React.Fragment> </React.Fragment>
} }
@ -315,21 +322,6 @@ const SidebarCategory = (props: Props) => {
defaultMessage='Create New Category' defaultMessage='Create New Category'
/> />
)} )}
onCreate={async (name) => {
if (!me) {
Utils.logError('me not initialized')
return
}
const category: Category = {
name,
userID: me.id,
teamID,
} as Category
await mutator.createCategory(category)
setShowCreateCategoryModal(false)
}}
/> />
) )
} }
@ -345,22 +337,6 @@ const SidebarCategory = (props: Props) => {
/> />
)} )}
onClose={() => setShowUpdateCategoryModal(false)} onClose={() => setShowUpdateCategoryModal(false)}
onCreate={async (name) => {
if (!me) {
Utils.logError('me not initialized')
return
}
const category: Category = {
name,
id: props.categoryBoards.id,
userID: me.id,
teamID,
} as Category
await mutator.updateCategory(category)
setShowUpdateCategoryModal(false)
}}
/> />
) )
} }

View file

@ -20,7 +20,7 @@ exports[`components/viewLimitDialog/ViewLiimitDialog show notify upgrade button
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >
@ -96,7 +96,7 @@ exports[`components/viewLimitDialog/ViewLiimitDialog show upgrade button for sys
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >

View file

@ -20,7 +20,7 @@ exports[`components/viewLimitDialog/ViewL]imitDialog show notify admin confirmat
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >
@ -130,7 +130,7 @@ exports[`components/viewLimitDialog/ViewL]imitDialog show view limit dialog 1`]
> >
<button <button
aria-label="Close dialog" aria-label="Close dialog"
class="IconButton size--medium" class="IconButton dialog__close size--medium"
title="Close dialog" title="Close dialog"
type="button" type="button"
> >

View file

@ -61,6 +61,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: flex-start;
position: relative; position: relative;
font-size: 14px; font-size: 14px;
line-height: 24px; line-height: 24px;