diff --git a/webapp/src/components/editable.tsx b/webapp/src/components/editable.tsx deleted file mode 100644 index f8da882ab..000000000 --- a/webapp/src/components/editable.tsx +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react' - -import {Utils} from '../utils' - -type Props = { - onChanged: (text: string) => void - text?: string - placeholderText?: string - className?: string - style?: React.CSSProperties - isMarkdown: boolean - isMultiline: boolean - allowEmpty: boolean - readonly?: boolean - - onFocus?: () => void - onBlur?: () => void - onKeyDown?: (e: React.KeyboardEvent) => void -} - -class Editable extends React.PureComponent { - static defaultProps = { - text: '', - isMarkdown: false, - isMultiline: false, - allowEmpty: true, - } - - private privateText = '' - get text(): string { - return this.privateText - } - set text(value: string) { - if (!this.elementRef.current) { - Utils.assertFailure('elementRef.current') - return - } - - const {isMarkdown} = this.props - - if (value) { - this.elementRef.current.innerHTML = isMarkdown ? Utils.htmlFromMarkdown(value) : Utils.htmlEncode(value) - } else { - this.elementRef.current.innerText = '' - } - - this.privateText = value || '' - } - - private elementRef = React.createRef() - - constructor(props: Props) { - super(props) - this.privateText = props.text || '' - } - - componentDidUpdate(): void { - this.privateText = this.props.text || '' - } - - focus(): void { - this.elementRef.current?.focus() - - // Put cursor at end - document.execCommand('selectAll', false, undefined) - document.getSelection()?.collapseToEnd() - } - - blur(): void { - this.elementRef.current?.blur() - } - - render(): JSX.Element { - const {text, style, placeholderText, isMarkdown, isMultiline, onFocus, onBlur, onKeyDown, onChanged} = this.props - - const initialStyle = {...this.props.style} - - let html: string - if (text) { - html = isMarkdown ? Utils.htmlFromMarkdown(text) : Utils.htmlEncode(text) - } else { - html = '' - } - - let className = 'octo-editable' - if (this.props.className) { - className += ' ' + this.props.className - } - - const element = ( -
{ - if (this.props.readonly) { - return - } - - if (this.elementRef.current) { - this.elementRef.current.innerText = this.text - this.elementRef.current.style.color = style?.color || '' - this.elementRef.current.classList.add('active') - } - - if (onFocus) { - onFocus() - } - }} - - onBlur={async () => { - if (this.props.readonly) { - return - } - - if (this.elementRef.current) { - const newText = this.elementRef.current.innerText - const oldText = this.props.text || '' - if (this.props.allowEmpty || newText) { - if (newText !== oldText && onChanged) { - onChanged(newText) - } - - this.text = newText - } else { - this.text = oldText // Reset text - } - - this.elementRef.current.classList.remove('active') - } - - if (onBlur) { - onBlur() - } - }} - - onKeyDown={(e) => { - if (this.props.readonly) { - return - } - - if (e.keyCode === 27 && !(e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) { // ESC - e.stopPropagation() - this.elementRef.current?.blur() - } else if (!isMultiline && e.keyCode === 13 && !(e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) { // Return - e.stopPropagation() - this.elementRef.current?.blur() - } - - if (onKeyDown) { - onKeyDown(e) - } - }} - />) - - return element - } -} - -export default Editable diff --git a/webapp/src/components/kanban/kanban.tsx b/webapp/src/components/kanban/kanban.tsx index 3a8fc901e..a72250eaa 100644 --- a/webapp/src/components/kanban/kanban.tsx +++ b/webapp/src/components/kanban/kanban.tsx @@ -177,7 +177,7 @@ class Kanban extends React.Component { ) } - private async propertyNameChanged(option: IPropertyOption, text: string): Promise { + private propertyNameChanged = async (option: IPropertyOption, text: string): Promise => { const {boardTree} = this.props await mutator.changePropertyOptionValue(boardTree, boardTree.groupByProperty!, option, text) diff --git a/webapp/src/components/kanban/kanbanColumnHeader.tsx b/webapp/src/components/kanban/kanbanColumnHeader.tsx index b069c3d79..26c253474 100644 --- a/webapp/src/components/kanban/kanbanColumnHeader.tsx +++ b/webapp/src/components/kanban/kanbanColumnHeader.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. /* eslint-disable max-lines */ -import React from 'react' +import React, {useState, useEffect} from 'react' import {FormattedMessage, IntlShape} from 'react-intl' import {Constants} from '../../constants' @@ -16,8 +16,7 @@ import HideIcon from '../../widgets/icons/hide' import OptionsIcon from '../../widgets/icons/options' import Menu from '../../widgets/menu' import MenuWrapper from '../../widgets/menuWrapper' - -import Editable from '../editable' +import Editable from '../../widgets/editable' type Props = { boardTree: BoardTree @@ -33,86 +32,16 @@ type Props = { export default function KanbanColumnHeader(props: Props): JSX.Element { const {boardTree, intl, group} = props const {activeView} = boardTree + const [groupTitle, setGroupTitle] = useState(group.option.value) - if (!group.option.id) { - // Empty group - const ref = React.createRef() - return ( -
{ - props.setDraggedHeaderOption(group.option) - }} - onDragEnd={() => { - props.setDraggedHeaderOption(undefined) - }} - - onDragOver={(e) => { - ref.current?.classList.add('dragover') - e.preventDefault() - }} - onDragEnter={(e) => { - ref.current?.classList.add('dragover') - e.preventDefault() - }} - onDragLeave={(e) => { - ref.current?.classList.remove('dragover') - e.preventDefault() - }} - onDrop={(e) => { - ref.current?.classList.remove('dragover') - e.preventDefault() - props.onDropToColumn(group.option) - }} - > -
- -
- -
- {!props.readonly && - <> - - }/> - - } - name={intl.formatMessage({id: 'BoardComponent.hide', defaultMessage: 'Hide'})} - onClick={() => mutator.hideViewColumn(activeView, '')} - /> - - - } - onClick={() => props.addCard(undefined)} - /> - - } -
- ) - } + useEffect(() => { + setGroupTitle(group.option.value) + }, [group.option.value]) const ref = React.createRef() return (
- { - props.propertyNameChanged(group.option, text) - }} - readonly={props.readonly} - /> + {!group.option.id && +
+ +
} + {group.option.id && + { + if(groupTitle.trim() === '') { + setGroupTitle(group.option.value) + } + props.propertyNameChanged(group.option, groupTitle) + }} + onCancel={() => { + setGroupTitle(group.option.value) + }} + readonly={props.readonly} + />}
{!props.readonly && @@ -163,23 +115,26 @@ export default function KanbanColumnHeader(props: Props): JSX.Element { id='hide' icon={} name={intl.formatMessage({id: 'BoardComponent.hide', defaultMessage: 'Hide'})} - onClick={() => mutator.hideViewColumn(activeView, group.option.id)} + onClick={() => mutator.hideViewColumn(activeView, group.option.id || '')} /> - } - name={intl.formatMessage({id: 'BoardComponent.delete', defaultMessage: 'Delete'})} - onClick={() => mutator.deletePropertyOption(boardTree, boardTree.groupByProperty!, group.option)} - /> - - {Constants.menuColors.map((color) => ( - mutator.changePropertyOptionColor(boardTree.board, boardTree.groupByProperty!, group.option, color.id)} - /> - ))} + {group.option.id && + <> + } + name={intl.formatMessage({id: 'BoardComponent.delete', defaultMessage: 'Delete'})} + onClick={() => mutator.deletePropertyOption(boardTree, boardTree.groupByProperty!, group.option)} + /> + + {Constants.menuColors.map((color) => ( + mutator.changePropertyOptionColor(boardTree.board, boardTree.groupByProperty!, group.option, color.id)} + /> + ))} + } { const {boardTree, showView, withGroupBy} = props const {board, activeView} = boardTree + const [viewTitle, setViewTitle] = useState(activeView.title) + + useEffect(() => { + setViewTitle(activeView.title) + }, [activeView.title]) + const hasFilter = activeView.filter && activeView.filter.filters?.length > 0 return (
{ - mutator.changeTitle(activeView, text) + onSave={(): void => { + mutator.changeTitle(activeView, viewTitle) }} + onCancel={(): void => { + setViewTitle(activeView.title) + }} + onChange={setViewTitle} readonly={props.readonly} /> diff --git a/webapp/src/components/viewHeader/viewHeaderSearch.tsx b/webapp/src/components/viewHeader/viewHeaderSearch.tsx index f0014be94..ddbd6936d 100644 --- a/webapp/src/components/viewHeader/viewHeaderSearch.tsx +++ b/webapp/src/components/viewHeader/viewHeaderSearch.tsx @@ -5,8 +5,7 @@ import {FormattedMessage, injectIntl, IntlShape} from 'react-intl' import {BoardTree} from '../../viewModel/boardTree' import Button from '../../widgets/buttons/button' - -import Editable from '../editable' +import Editable from '../../widgets/editable' type Props = { boardTree: BoardTree @@ -15,41 +14,37 @@ type Props = { } const ViewHeaderSearch = React.memo((props: Props) => { + const {boardTree, intl} = props + const searchFieldRef = useRef(null) const [isSearching, setIsSearching] = useState(Boolean(props.boardTree.getSearchText())) + const [searchValue, setSearchValue] = useState(boardTree.getSearchText()) useEffect(() => { searchFieldRef.current?.focus() }, [isSearching]) - const onSearchKeyDown = (e: React.KeyboardEvent) => { - if (e.keyCode === 27) { // ESC: Clear search - if (searchFieldRef.current) { - searchFieldRef.current.text = '' - } - setIsSearching(false) - props.setSearchText(undefined) - e.preventDefault() - } - if (e.keyCode === 13 && searchFieldRef.current?.text.trim() === '') { // ENTER: with empty string clear search - setIsSearching(false) - props.setSearchText(undefined) - e.preventDefault() - } - } - - const {boardTree, intl} = props + useEffect(() => { + setSearchValue(boardTree.getSearchText()) + }, [boardTree]) if (isSearching) { return ( { - onSearchKeyDown(e) + onChange={setSearchValue} + onCancel={() => { + setSearchValue('') + setIsSearching(false) + props.setSearchText('') + }} + onSave={() => { + if (searchValue === '') { + setIsSearching(false) + } + props.setSearchText(searchValue) }} /> ) diff --git a/webapp/src/widgets/buttons/button.scss b/webapp/src/widgets/buttons/button.scss index a98da8947..6fd0fea6a 100644 --- a/webapp/src/widgets/buttons/button.scss +++ b/webapp/src/widgets/buttons/button.scss @@ -5,7 +5,6 @@ text-align: center; justify-content: center; border-radius: var(--default-rad); - padding: 4px 8px; min-width: 20px; cursor: pointer; overflow: hidden; @@ -36,6 +35,6 @@ .Icon { width: 14px; height: 14px; - margin-right: 5px; + margin-right: 0px; } }