refactor: enhance tag input UI (#44)
* refactor: enhance tag input UI * style: fix tslint errors * Update tagInputSize.scss * Update tagInputSize.scss
This commit is contained in:
Родитель
d1f6f5a186
Коммит
5c9fa5de3b
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -125,6 +125,32 @@ const blueButtonPalette = {
|
|||
white: "#272b30",
|
||||
};
|
||||
|
||||
const darkThemePalette = {
|
||||
neutralLighterAlt: "#282828",
|
||||
neutralLighter: "#313131",
|
||||
neutralLight: "#3f3f3f",
|
||||
neutralQuaternaryAlt: "#484848",
|
||||
neutralQuaternary: "#4f4f4f",
|
||||
neutralTertiaryAlt: "#6d6d6d",
|
||||
neutralTertiary: "#c8c8c8",
|
||||
neutralSecondary: "#d0d0d0",
|
||||
neutralPrimaryAlt: "#dadada",
|
||||
neutralPrimary: "#ffffff",
|
||||
neutralDark: "#f4f4f4",
|
||||
black: "#f8f8f8",
|
||||
white: "#1f1f1f",
|
||||
themePrimary: "#ffffff",
|
||||
themeLighterAlt: "#020609",
|
||||
themeLighter: "#091823",
|
||||
themeLight: "#112d43",
|
||||
themeTertiary: "#235a85",
|
||||
themeSecondary: "#3385c3",
|
||||
themeDarkAlt: "#4ba0e1",
|
||||
themeDark: "#65aee6",
|
||||
themeDarker: "#8ac2ec",
|
||||
accent: "#3a96dd",
|
||||
};
|
||||
|
||||
export function getPrimaryWhiteTheme() {
|
||||
return createTheme({palette: whiteButtonPalette});
|
||||
}
|
||||
|
@ -144,3 +170,7 @@ export function getPrimaryGreyTheme() {
|
|||
export function getPrimaryBlueTheme() {
|
||||
return createTheme({palette: blueButtonPalette});
|
||||
}
|
||||
|
||||
export function getDarkTheme() {
|
||||
return createTheme({palette: darkThemePalette});
|
||||
}
|
||||
|
|
|
@ -1,217 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import React from "react";
|
||||
import { FontIcon } from "office-ui-fabric-react";
|
||||
import { Align } from "../align/align";
|
||||
import { ITag, FieldType, FieldFormat } from "../../../../models/applicationState";
|
||||
import { strings } from "../../../../common/strings";
|
||||
import "./tagContextMenu.scss";
|
||||
|
||||
/**
|
||||
* Properties for TagContextMenu
|
||||
* @member tag - ITag
|
||||
*/
|
||||
export interface ITagContextMenuProps {
|
||||
tag: ITag;
|
||||
onChange?: (oldTag: ITag, newTag: ITag) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* State for TagContextMenu
|
||||
* @member tag - ITag
|
||||
*/
|
||||
export interface ITagContextMenuState {
|
||||
tag: ITag;
|
||||
showFormat?: boolean;
|
||||
showType?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic modal that displays a message
|
||||
*/
|
||||
export default class TagContextMenu extends React.Component<ITagContextMenuProps, ITagContextMenuState> {
|
||||
|
||||
private static filterFormat(type: FieldType): FieldFormat[] {
|
||||
switch (type) {
|
||||
case FieldType.String:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.Alphanumberic,
|
||||
FieldFormat.NoWhiteSpaces,
|
||||
];
|
||||
case FieldType.Number:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.Currency,
|
||||
];
|
||||
case FieldType.Integer:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
];
|
||||
case FieldType.Date:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.DMY,
|
||||
FieldFormat.MDY,
|
||||
FieldFormat.YMD,
|
||||
];
|
||||
case FieldType.Time:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
];
|
||||
default:
|
||||
return [ FieldFormat.NotSpecified ];
|
||||
}
|
||||
}
|
||||
|
||||
public state: ITagContextMenuState = {
|
||||
tag: this.props.tag,
|
||||
showFormat: false,
|
||||
showType: false,
|
||||
};
|
||||
|
||||
private typeRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
private formatRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
public render() {
|
||||
const tag = this.state.tag;
|
||||
const types = Object.keys(FieldType);
|
||||
const formats = TagContextMenu.filterFormat(tag.type);
|
||||
const align = {
|
||||
// Align top right of source node (dropdown) with top left of target node (tag name row)
|
||||
points: ["tr", "br"],
|
||||
// Offset source node by 0px in x and 3px in y
|
||||
offset: [0, 3],
|
||||
// Auto adjust position when source node is overflowed
|
||||
overflow: {adjustX: true, adjustY: true},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className = "field-background field-background-color">
|
||||
<div className = "tag-field justify-content-start">
|
||||
<div className = "row-4 tag-field-item">
|
||||
<div
|
||||
ref={this.typeRef}
|
||||
className="field-background-container"
|
||||
onClick={this.handleTypeShow}>
|
||||
<FontIcon iconName="Link" />
|
||||
<span className="type-selected">{tag.type ? tag.type : strings.tags.toolbar.type}</span>
|
||||
<FontIcon iconName="ChevronDown" className="pr-1" />
|
||||
</div>
|
||||
<Align align={align} target={() => this.typeRef.current} monitorWindowResize={true}>
|
||||
{
|
||||
this.state.showType &&
|
||||
<div className={["tag-input-portal", "format-list", "format-items-list"].join(" ")}>
|
||||
{
|
||||
types.filter((type) => {
|
||||
return FieldType[type] !== tag.type;
|
||||
}).map((type) => {
|
||||
return (
|
||||
this.getTypeListItem(this, type)
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</Align>
|
||||
</div>
|
||||
<div className = "horizontal-line"></div>
|
||||
<div className = "row-4 tag-field-item">
|
||||
<div
|
||||
ref={this.formatRef}
|
||||
className = "field-background-container"
|
||||
onClick={this.handleFormatShow}>
|
||||
<FontIcon iconName="Link" />
|
||||
<span>{tag.format ? tag.format : strings.tags.toolbar.format}</span>
|
||||
<FontIcon iconName="ChevronDown" className="pr-1" />
|
||||
</div>
|
||||
<Align align={align} target={() => this.formatRef.current}>
|
||||
{
|
||||
this.state.showFormat &&
|
||||
<div className = {["tag-input-portal", "format-list", "format-items-list"].join(" ")}>
|
||||
{
|
||||
formats.filter((format) => {
|
||||
return format !== tag.format;
|
||||
}).map((format) => {
|
||||
return (
|
||||
this.getFormatListItem(this, format)
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</Align>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private handleTypeChange = (event) => {
|
||||
const oldTag = this.state.tag;
|
||||
const newTag: ITag = {
|
||||
...oldTag,
|
||||
type: event.target.value as FieldType,
|
||||
format: FieldFormat.NotSpecified,
|
||||
};
|
||||
this.setState({ tag: newTag, showType: false }, () => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(oldTag, newTag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleTypeShow = (e) => {
|
||||
if (e.type === "click") {
|
||||
this.setState({showType: !this.state.showType, showFormat: false});
|
||||
}
|
||||
}
|
||||
|
||||
private handleFormatShow = (e) => {
|
||||
if (e.type === "click") {
|
||||
this.setState({showFormat: !this.state.showFormat, showType: false});
|
||||
}
|
||||
}
|
||||
|
||||
private handleFormatChange = (event) => {
|
||||
const oldTag = this.state.tag;
|
||||
const newTag: ITag = {
|
||||
...oldTag,
|
||||
format: event.target.value,
|
||||
};
|
||||
|
||||
this.setState({ tag: newTag, showFormat: false }, () => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(oldTag, newTag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getTypeListItem(props, type) {
|
||||
return (
|
||||
<button type="button"
|
||||
key={type}
|
||||
onClick={props.handleTypeChange}
|
||||
value={FieldType[type]}
|
||||
className="list-items list-items-color"
|
||||
>
|
||||
{FieldType[type]}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
private getFormatListItem(props, format) {
|
||||
return(
|
||||
<button type="button"
|
||||
key={format}
|
||||
onClick={props.handleFormatChange}
|
||||
value={format}
|
||||
className="list-items list-items-color"
|
||||
>
|
||||
{format}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
@import "tagInput.scss";
|
||||
|
||||
.field-background {
|
||||
width: $tagInputWidth - $tagColorWidth;
|
||||
padding: 0px 0px 5px 0px;
|
||||
&-color {
|
||||
background-color: #32363B;
|
||||
}
|
||||
&-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: white;
|
||||
padding: 0px 0px 0px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-line {
|
||||
height: 0.1;
|
||||
width: 100%;
|
||||
border: 0.5px solid white;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
|
||||
.tag-field {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.tag-field-item {
|
||||
width: 100%;
|
||||
padding: 4px 0px 4px 0px;
|
||||
}
|
||||
|
||||
.format-list {
|
||||
width: $tagInputWidth - $tagColorWidth;
|
||||
border: white 1px;
|
||||
}
|
||||
|
||||
.format-items-hide {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.format-items-list {
|
||||
width: $tagInputWidth - $tagColorWidth;
|
||||
list-style-type: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-items: self-start;
|
||||
}
|
||||
|
||||
.list-items {
|
||||
border: 0px;
|
||||
width:100%;
|
||||
display: inline-flex;
|
||||
justify-items: flex-start;
|
||||
padding: 0px 0px 0px 20px;
|
||||
|
||||
&-color {
|
||||
background-color: #32363B;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&-color:hover {
|
||||
background-color: #2D2F31;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,13 +176,13 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-color-edit:hover {
|
||||
&-color {
|
||||
width: $tagColorWidth;
|
||||
|
||||
&-edit:hover {
|
||||
background: $darker-1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-color {
|
||||
width: $tagColorWidth;
|
||||
}
|
||||
|
||||
&-lock-icon {
|
||||
|
@ -245,6 +245,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-contextual-menu {
|
||||
width: $tagContextualMenuWidth;
|
||||
}
|
||||
}
|
||||
|
||||
&-index-span {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import React from "react";
|
||||
import { ReactWrapper, mount } from "enzyme";
|
||||
import { TagInput, ITagInputProps, ITagInputState } from "./tagInput";
|
||||
import { TagInput, TagOperationMode, ITagInputProps, ITagInputState } from "./tagInput";
|
||||
import MockFactory from "../../../../common/mockFactory";
|
||||
import { ITag } from "../../../../models/applicationState";
|
||||
import TagInputItem, { ITagInputItemProps } from "./tagInputItem";
|
||||
|
@ -43,7 +43,7 @@ describe("Tag Input Component", () => {
|
|||
const wrapper = createComponent(props);
|
||||
wrapper.find(".tag-color").first().simulate("click");
|
||||
expect(props.onTagClick).toBeCalledWith(props.tags[0]);
|
||||
expect(wrapper.state().clickedColor).toBe(true);
|
||||
expect(wrapper.state().tagOperation === TagOperationMode.ColorPicker).toBe(true);
|
||||
expect(props.onCtrlTagClick).not.toBeCalled();
|
||||
});
|
||||
|
||||
|
@ -51,19 +51,18 @@ describe("Tag Input Component", () => {
|
|||
const props = createProps();
|
||||
const wrapper = createComponent(props);
|
||||
wrapper.find("div.tag-name-container").first().simulate("click", { altKey: true } );
|
||||
expect(wrapper.state().editingTag).toEqual(props.tags[0]);
|
||||
expect(wrapper.state().selectedTag).toEqual(props.tags[0]);
|
||||
expect(wrapper.exists("input.tag-name-editor")).toBe(true);
|
||||
});
|
||||
|
||||
it("Edits tag color when alt clicked", () => {
|
||||
const props = createProps();
|
||||
const wrapper = createComponent(props);
|
||||
expect(wrapper.state().clickedColor).toBe(false);
|
||||
expect(wrapper.state().tagOperation === TagOperationMode.None).toBe(false);
|
||||
expect(wrapper.exists("div.color-picker-container")).toBe(false);
|
||||
wrapper.find("div.tag-color").first().simulate("click", { altKey: true } );
|
||||
expect(wrapper.state().clickedColor).toBe(true);
|
||||
expect(wrapper.state().showColorPicker).toBe(true);
|
||||
expect(wrapper.state().editingTag).toEqual(props.tags[0]);
|
||||
expect(wrapper.state().tagOperation === TagOperationMode.ColorPicker).toBe(true);
|
||||
expect(wrapper.state().selectedTag).toEqual(props.tags[0]);
|
||||
expect(wrapper.exists("div.color-picker-container")).toBe(true);
|
||||
// Get color picker and call onEditColor function
|
||||
const picker = wrapper.find(ColorPicker).instance() as ColorPicker;
|
||||
|
@ -85,7 +84,7 @@ describe("Tag Input Component", () => {
|
|||
const wrapper = createComponent(props);
|
||||
wrapper.find(".tag-color").first().simulate("click", { ctrlKey: true });
|
||||
expect(props.onCtrlTagClick).toBeCalledWith(props.tags[0]);
|
||||
expect(wrapper.state().clickedColor).toBe(true);
|
||||
expect(wrapper.state().tagOperation === TagOperationMode.ColorPicker).toBe(true);
|
||||
expect(props.onTagClick).not.toBeCalled();
|
||||
});
|
||||
|
||||
|
@ -167,7 +166,7 @@ describe("Tag Input Component", () => {
|
|||
const wrapper = createComponent(props);
|
||||
wrapper.find("div.tag-name-container").first().simulate("click");
|
||||
wrapper.find("div.tag-input-toolbar-item.edit").simulate("click");
|
||||
expect(wrapper.state().editingTag).toEqual(tags[0]);
|
||||
expect(wrapper.state().selectedTag).toEqual(tags[0]);
|
||||
expect(wrapper.exists("input.tag-name-editor")).toBe(true);
|
||||
});
|
||||
|
||||
|
@ -175,13 +174,12 @@ describe("Tag Input Component", () => {
|
|||
const tags = MockFactory.createTestTags();
|
||||
const props = createProps(tags);
|
||||
const wrapper = createComponent(props);
|
||||
expect(wrapper.state().clickedColor).toBe(false);
|
||||
expect(wrapper.state().tagOperation === TagOperationMode.None).toBe(false);
|
||||
expect(wrapper.exists("div.color-picker-container")).toBe(false);
|
||||
wrapper.find("div.tag-color").first().simulate("click");
|
||||
expect(wrapper.state().clickedColor).toBe(true);
|
||||
expect(wrapper.state().tagOperation === TagOperationMode.ColorPicker).toBe(true);
|
||||
wrapper.find("div.tag-input-toolbar-item.edit").simulate("click");
|
||||
expect(wrapper.state().showColorPicker).toBe(true);
|
||||
expect(wrapper.state().editingTag).toEqual(tags[0]);
|
||||
expect(wrapper.state().selectedTag).toEqual(tags[0]);
|
||||
expect(wrapper.exists("div.color-picker-container")).toBe(true);
|
||||
// Get color picker and call onEditColor function
|
||||
const picker = wrapper.find(ColorPicker).instance() as ColorPicker;
|
||||
|
|
|
@ -3,8 +3,15 @@
|
|||
|
||||
import React, { KeyboardEvent, RefObject } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { FontIcon } from "office-ui-fabric-react";
|
||||
import { Align } from "../align/align";
|
||||
import {
|
||||
ContextualMenu,
|
||||
Customizer,
|
||||
FontIcon,
|
||||
IContextualMenuItem,
|
||||
ICustomizations,
|
||||
} from "office-ui-fabric-react";
|
||||
import { strings } from "../../../../common/strings";
|
||||
import { getDarkTheme } from "../../../../common/themes";
|
||||
import { AlignPortal } from "../align/alignPortal";
|
||||
import { randomIntInRange } from "../../../../common/utils";
|
||||
import { IRegion, ITag, ILabel, FieldType, FieldFormat } from "../../../../models/applicationState";
|
||||
|
@ -14,11 +21,16 @@ import "../condensedList/condensedList.scss";
|
|||
import TagInputItem, { ITagInputItemProps, ITagClickProps } from "./tagInputItem";
|
||||
import TagInputToolbar from "./tagInputToolbar";
|
||||
import { toast } from "react-toastify";
|
||||
import { strings } from "../../../../common/strings";
|
||||
import TagContextMenu from "./tagContentMenu";
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
const tagColors = require("../../common/tagColors.json");
|
||||
|
||||
export enum TagOperationMode {
|
||||
None,
|
||||
ColorPicker,
|
||||
ContextualMenu,
|
||||
Rename,
|
||||
}
|
||||
|
||||
export interface ITagInputProps {
|
||||
/** Current list of tags */
|
||||
tags: ITag[];
|
||||
|
@ -56,48 +68,102 @@ export interface ITagInputProps {
|
|||
|
||||
export interface ITagInputState {
|
||||
tags: ITag[];
|
||||
clickedColor: boolean;
|
||||
clickedDropDown: boolean;
|
||||
showColorPicker: boolean;
|
||||
showDropDown: boolean;
|
||||
tagOperation: TagOperationMode;
|
||||
addTags: boolean;
|
||||
searchTags: boolean;
|
||||
searchQuery: string;
|
||||
selectedTag: ITag;
|
||||
editingTag: ITag;
|
||||
editingTagNode: Element;
|
||||
}
|
||||
|
||||
function defaultDOMNode(): Element {
|
||||
return document.createElement("div");
|
||||
}
|
||||
|
||||
function filterFormat(type: FieldType): FieldFormat[] {
|
||||
switch (type) {
|
||||
case FieldType.String:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.Alphanumberic,
|
||||
FieldFormat.NoWhiteSpaces,
|
||||
];
|
||||
case FieldType.Number:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.Currency,
|
||||
];
|
||||
case FieldType.Integer:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
];
|
||||
case FieldType.Date:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
FieldFormat.DMY,
|
||||
FieldFormat.MDY,
|
||||
FieldFormat.YMD,
|
||||
];
|
||||
case FieldType.Time:
|
||||
return [
|
||||
FieldFormat.NotSpecified,
|
||||
];
|
||||
default:
|
||||
return [ FieldFormat.NotSpecified ];
|
||||
}
|
||||
}
|
||||
|
||||
export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
||||
|
||||
public state: ITagInputState = {
|
||||
tags: this.props.tags || [],
|
||||
clickedColor: false,
|
||||
clickedDropDown: false,
|
||||
showColorPicker: false,
|
||||
showDropDown: false,
|
||||
tagOperation: TagOperationMode.None,
|
||||
addTags: this.props.showTagInputBox,
|
||||
searchTags: this.props.showSearchBox,
|
||||
searchQuery: "",
|
||||
selectedTag: null,
|
||||
editingTag: null,
|
||||
editingTagNode: null,
|
||||
};
|
||||
|
||||
private tagItemRefs: Map<string, TagInputItem> = new Map<string, TagInputItem>();
|
||||
|
||||
private inputRef: RefObject<HTMLInputElement>;
|
||||
private colorPickerNode = defaultDOMNode();
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.inputRef = React.createRef();
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: ITagInputProps) {
|
||||
if (prevProps.tags !== this.props.tags) {
|
||||
let selectedTag = this.state.selectedTag;
|
||||
if (selectedTag) {
|
||||
selectedTag = this.props.tags.find((tag) => this.isNameEqual(tag, selectedTag));
|
||||
}
|
||||
|
||||
this.setState({
|
||||
tags: this.props.tags,
|
||||
selectedTag,
|
||||
});
|
||||
}
|
||||
|
||||
if (prevProps.selectedRegions !== this.props.selectedRegions && this.props.selectedRegions.length > 0) {
|
||||
this.setState({
|
||||
selectedTag: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
||||
const dark: ICustomizations = {
|
||||
settings: {
|
||||
theme: getDarkTheme(),
|
||||
},
|
||||
scopedSettings: {},
|
||||
};
|
||||
|
||||
const { selectedTag } = this.state;
|
||||
const selectedTagRef = selectedTag ? this.tagItemRefs.get(selectedTag.name).getTagNameRef() : null;
|
||||
|
||||
return (
|
||||
<div className="tag-input">
|
||||
<div className="tag-input-header p-2">
|
||||
|
@ -130,10 +196,18 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
<FontIcon iconName="Search" />
|
||||
</div>
|
||||
}
|
||||
{this.getColorPickerPortal()}
|
||||
{this.getTagFieldPortal()}
|
||||
<div className="tag-input-items">
|
||||
{this.renderTagItems()}
|
||||
<Customizer {...dark}>
|
||||
<ContextualMenu
|
||||
className="tag-input-contextual-menu"
|
||||
items={this.getContextualMenuItems()}
|
||||
hidden={!selectedTagRef || this.state.tagOperation !== TagOperationMode.ContextualMenu}
|
||||
target={selectedTagRef}
|
||||
onDismiss={this.onHideContextualMenu}
|
||||
/>
|
||||
</Customizer>
|
||||
{this.getColorPickerPortal()}
|
||||
</div>
|
||||
{
|
||||
this.state.addTags &&
|
||||
|
@ -156,26 +230,6 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: ITagInputProps) {
|
||||
if (prevProps.tags !== this.props.tags) {
|
||||
let selectedTag = this.state.selectedTag;
|
||||
if (selectedTag) {
|
||||
selectedTag = this.props.tags.find((tag) => this.isNameEqual(tag, selectedTag));
|
||||
}
|
||||
|
||||
this.setState({
|
||||
tags: this.props.tags,
|
||||
selectedTag,
|
||||
});
|
||||
}
|
||||
|
||||
if (prevProps.selectedRegions !== this.props.selectedRegions && this.props.selectedRegions.length > 0) {
|
||||
this.setState({
|
||||
selectedTag: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public triggerNewTagBlur() {
|
||||
if (this.inputRef.current) {
|
||||
this.inputRef.current.blur();
|
||||
|
@ -188,17 +242,11 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
|
||||
private onEditTag = (tag: ITag) => {
|
||||
const { editingTag } = this.state;
|
||||
const newEditingTag = (editingTag && this.isNameEqual(editingTag, tag)) ? null : tag;
|
||||
const tagOperation = this.state.tagOperation === TagOperationMode.Rename
|
||||
? TagOperationMode.None : TagOperationMode.Rename;
|
||||
this.setState({
|
||||
editingTag: newEditingTag,
|
||||
editingTagNode: this.getTagNode(newEditingTag),
|
||||
tagOperation,
|
||||
});
|
||||
if (this.state.clickedColor) {
|
||||
this.setState({
|
||||
showColorPicker: !this.state.showColorPicker,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onLockTag = (tag: ITag) => {
|
||||
|
@ -232,19 +280,16 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
|
||||
private handleColorChange = (color: string) => {
|
||||
const tag = this.state.editingTag;
|
||||
const tag = this.state.selectedTag;
|
||||
const tags = this.state.tags.map((t) => {
|
||||
return (this.isNameEqual(t, tag)) ? {
|
||||
name: t.name,
|
||||
...tag,
|
||||
color,
|
||||
type: t.type,
|
||||
format: t.format,
|
||||
} : t;
|
||||
});
|
||||
this.setState({
|
||||
tags,
|
||||
editingTag: null,
|
||||
showColorPicker: false,
|
||||
tagOperation: TagOperationMode.None,
|
||||
}, () => this.props.onChange(tags));
|
||||
}
|
||||
|
||||
|
@ -288,7 +333,6 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
});
|
||||
this.setState({
|
||||
tags,
|
||||
editingTag: null,
|
||||
selectedTag: newTag,
|
||||
}, () => {
|
||||
this.props.onChange(tags);
|
||||
|
@ -303,16 +347,18 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
|
||||
private getColorPickerPortal = () => {
|
||||
const { selectedTag } = this.state;
|
||||
const showColorPicker = this.state.tagOperation === TagOperationMode.ColorPicker;
|
||||
return (
|
||||
<AlignPortal align={this.getColorAlignConfig()} target={this.getEditingTagNode}>
|
||||
<AlignPortal align={this.getColorAlignConfig()} target={this.getSelectedTagNode}>
|
||||
<div className="tag-input-portal">
|
||||
{
|
||||
this.state.showColorPicker &&
|
||||
showColorPicker &&
|
||||
<ColorPicker
|
||||
color={this.state.editingTag && this.state.editingTag.color}
|
||||
color={selectedTag && selectedTag.color}
|
||||
colors={tagColors}
|
||||
onEditColor={this.handleColorChange}
|
||||
show={this.state.showColorPicker}
|
||||
show={showColorPicker}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
@ -320,25 +366,8 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
);
|
||||
}
|
||||
|
||||
private getTagFieldPortal = () => {
|
||||
return (
|
||||
<Align align={this.getFieldAlignConfig()} target={this.getEditingTagNameNode} monitorWindowResize={true}>
|
||||
<div className="tag-input-portal">
|
||||
{
|
||||
this.state.showDropDown &&
|
||||
<TagContextMenu
|
||||
key={this.state.editingTag.name}
|
||||
tag={this.state.editingTag}
|
||||
onChange={this.props.onTagChanged}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</Align>
|
||||
);
|
||||
}
|
||||
|
||||
private getColorAlignConfig = () => {
|
||||
const coords = this.getEditingTagCoords();
|
||||
const coords = this.colorPickerNode.getBoundingClientRect();
|
||||
const isNearBottom = coords && coords.top > (window.innerHeight / 2);
|
||||
const alignCorner = isNearBottom ? "b" : "t";
|
||||
const verticalOffset = isNearBottom ? 6 : -6;
|
||||
|
@ -350,28 +379,8 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
};
|
||||
}
|
||||
|
||||
private getFieldAlignConfig = () => {
|
||||
return {
|
||||
// Align top right of source node (dropdown) with top left of target node (tag name row)
|
||||
points: ["tr", "br"],
|
||||
// Offset source node by 0px in x and 3px in y
|
||||
offset: [0, 3],
|
||||
// Auto adjust position when source node is overflowed
|
||||
overflow: {adjustX: true, adjustY: true},
|
||||
};
|
||||
}
|
||||
|
||||
private getEditingTagCoords = () => {
|
||||
const node = this.state.editingTagNode;
|
||||
return (node) ? node.getBoundingClientRect() : null;
|
||||
}
|
||||
|
||||
private getEditingTagNode = () => {
|
||||
return this.state.editingTagNode || document;
|
||||
}
|
||||
|
||||
private getEditingTagNameNode = () => {
|
||||
return TagInputItem.getNameNode(this.state.editingTagNode) || document;
|
||||
private getSelectedTagNode = () => {
|
||||
return this.getTagNode(this.state.selectedTag);
|
||||
}
|
||||
|
||||
private renderTagItems = () => {
|
||||
|
@ -385,23 +394,16 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
|
||||
return props.map((prop) =>
|
||||
<TagInputItem
|
||||
{...prop}
|
||||
key={prop.tag.name}
|
||||
labels={this.setTagLabels(prop.tag.name)}
|
||||
ref={(item) => this.setTagItemRef(item, prop.tag)}
|
||||
onLabelEnter={this.props.onLabelEnter}
|
||||
onLabelLeave={this.props.onLabelLeave}
|
||||
onTagChanged={this.props.onTagChanged}
|
||||
onCallDropDown = {this.handleTagItemDropDown}
|
||||
{...prop}
|
||||
/>);
|
||||
}
|
||||
|
||||
private handleTagItemDropDown = () => {
|
||||
this.setState((prevState) => ({
|
||||
showDropDown: !prevState.showDropDown,
|
||||
}));
|
||||
}
|
||||
|
||||
private setTagItemRef = (item: TagInputItem, tag: ITag) => {
|
||||
this.tagItemRefs.set(tag.name, item);
|
||||
return item;
|
||||
|
@ -412,19 +414,20 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
}
|
||||
|
||||
private createTagItemProps = (): ITagInputItemProps[] => {
|
||||
const tags = this.state.tags;
|
||||
const { tags, selectedTag, tagOperation } = this.state;
|
||||
const selectedRegionTagSet = this.getSelectedRegionTagSet();
|
||||
|
||||
return tags.map((tag) => (
|
||||
{
|
||||
tag,
|
||||
index: tags.findIndex((t) => this.isNameEqual(t, tag)),
|
||||
isLocked: this.props.lockedTags &&
|
||||
this.props.lockedTags.findIndex((str) => this.isNameEqualTo(tag, str)) > -1,
|
||||
isBeingEdited: this.state.editingTag && this.isNameEqual(this.state.editingTag, tag),
|
||||
isSelected: this.state.selectedTag && this.isNameEqual(this.state.selectedTag, tag),
|
||||
isLocked: this.props.lockedTags
|
||||
&& this.props.lockedTags.findIndex((str) => this.isNameEqualTo(tag, str)) > -1,
|
||||
isRenaming: selectedTag && this.isNameEqual(selectedTag, tag)
|
||||
&& tagOperation === TagOperationMode.Rename,
|
||||
isSelected: selectedTag && this.isNameEqual(this.state.selectedTag, tag),
|
||||
appliedToSelectedRegions: selectedRegionTagSet.has(tag.name),
|
||||
onClick: this.handleClick,
|
||||
onClick: this.onTagItemClick,
|
||||
onChange: this.updateTag,
|
||||
} as ITagInputItemProps
|
||||
));
|
||||
|
@ -442,60 +445,53 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
return result;
|
||||
}
|
||||
|
||||
private onAltClick = (tag: ITag) => {
|
||||
const { editingTag } = this.state;
|
||||
const newEditingTag = this.state.showDropDown && editingTag && this.isNameEqual(editingTag, tag) ? null : tag;
|
||||
this.setState({
|
||||
editingTag: newEditingTag,
|
||||
editingTagNode: this.getTagNode(newEditingTag),
|
||||
});
|
||||
}
|
||||
|
||||
private onSingleClick = (tag: ITag, clickedColor: boolean, clickedDropDown: boolean) => {
|
||||
const { editingTag, selectedTag } = this.state;
|
||||
const newEditingTag = this.state.showDropDown && editingTag && this.isNameEqual(editingTag, tag) ? null : tag;
|
||||
this.setState({
|
||||
editingTag: newEditingTag,
|
||||
editingTagNode: this.getTagNode(newEditingTag),
|
||||
clickedColor,
|
||||
clickedDropDown,
|
||||
showColorPicker: !this.state.showColorPicker && clickedColor,
|
||||
showDropDown: !this.state.showDropDown && clickedDropDown,
|
||||
selectedTag: clickedDropDown ? tag : selectedTag,
|
||||
});
|
||||
}
|
||||
|
||||
private handleClick = (tag: ITag, props: ITagClickProps) => {
|
||||
private onTagItemClick = (tag: ITag, props: ITagClickProps) => {
|
||||
if (props.ctrlKey && this.props.onCtrlTagClick) { // Lock tags
|
||||
this.props.onCtrlTagClick(tag);
|
||||
this.setState({ clickedColor: props.clickedColor, clickedDropDown: props.clickedDropDown });
|
||||
} else if (props.altKey) { // Edit tag
|
||||
this.onAltClick(tag);
|
||||
} else if (props.keyClick) {
|
||||
this.onSingleClick(tag, props.clickedColor, props.clickedDropDown);
|
||||
} else { // Select tag
|
||||
const { editingTag, selectedTag } = this.state;
|
||||
const inEditMode = editingTag && this.isNameEqual(editingTag, tag);
|
||||
const alreadySelected = selectedTag && this.isNameEqual(selectedTag, tag);
|
||||
const newEditingTag = inEditMode ? null : editingTag;
|
||||
|
||||
this.setState({
|
||||
editingTag: newEditingTag,
|
||||
editingTagNode: this.getTagNode(newEditingTag),
|
||||
selectedTag: (alreadySelected && !inEditMode) ? null : tag,
|
||||
clickedColor: props.clickedColor,
|
||||
clickedDropDown: props.clickedDropDown,
|
||||
showColorPicker: false,
|
||||
showDropDown: false,
|
||||
selectedTag: tag,
|
||||
tagOperation: TagOperationMode.Rename,
|
||||
});
|
||||
} else if (props.clickedDropDown) {
|
||||
const { selectedTag } = this.state;
|
||||
const showContextualMenu = !selectedTag || !this.isNameEqual(selectedTag, tag)
|
||||
|| this.state.tagOperation !== TagOperationMode.ContextualMenu;
|
||||
const tagOperation = showContextualMenu ? TagOperationMode.ContextualMenu : TagOperationMode.None;
|
||||
this.setState({
|
||||
selectedTag: tag,
|
||||
tagOperation,
|
||||
});
|
||||
} else if (props.clickedColor) {
|
||||
const { selectedTag, tagOperation } = this.state;
|
||||
const showColorPicker = tagOperation !== TagOperationMode.ColorPicker;
|
||||
const newTagOperation = showColorPicker ? TagOperationMode.ColorPicker : TagOperationMode.None;
|
||||
if (showColorPicker) {
|
||||
this.colorPickerNode = this.getTagNode(tag);
|
||||
}
|
||||
this.setState({
|
||||
selectedTag: showColorPicker ? tag : selectedTag,
|
||||
tagOperation: newTagOperation,
|
||||
});
|
||||
} else { // Select tag
|
||||
const { selectedTag, tagOperation: oldTagOperation } = this.state;
|
||||
const selected = selectedTag && this.isNameEqual(selectedTag, tag);
|
||||
const tagOperation = selected ? oldTagOperation : TagOperationMode.None;
|
||||
let deselect = selected && oldTagOperation === TagOperationMode.None;
|
||||
|
||||
// Only fire click event if a region is selected
|
||||
if (this.props.selectedRegions &&
|
||||
this.props.selectedRegions.length > 0 &&
|
||||
this.props.onTagClick &&
|
||||
!inEditMode) {
|
||||
this.props.onTagClick) {
|
||||
deselect = false;
|
||||
this.props.onTagClick(tag);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
selectedTag: deselect ? null : tag,
|
||||
tagOperation,
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -585,4 +581,107 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
|||
private isNameEqualTo = (tag: ITag, str: string) => {
|
||||
return tag.name.trim().toLocaleLowerCase() === str.trim().toLocaleLowerCase();
|
||||
}
|
||||
|
||||
private onHideContextualMenu = () => {
|
||||
this.setState({tagOperation: TagOperationMode.None});
|
||||
}
|
||||
|
||||
private getContextualMenuItems = (): IContextualMenuItem[] => {
|
||||
const tag = this.state.selectedTag;
|
||||
if (!tag) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const menuItems: IContextualMenuItem[] = [
|
||||
{
|
||||
key: "type",
|
||||
iconProps: {
|
||||
iconName: "Link",
|
||||
},
|
||||
text: tag.type ? tag.type : strings.tags.toolbar.type,
|
||||
subMenuProps: {
|
||||
items: this.getTypeSubMenuItems(),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "format",
|
||||
iconProps: {
|
||||
iconName: "Link",
|
||||
},
|
||||
text: tag.format ? tag.format : strings.tags.toolbar.format,
|
||||
subMenuProps: {
|
||||
items: this.getFormatSubMenuItems(),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
private getTypeSubMenuItems = (): IContextualMenuItem[] => {
|
||||
const tag = this.state.selectedTag;
|
||||
const types = Object.values(FieldType);
|
||||
|
||||
return types.map((type) => {
|
||||
return {
|
||||
key: type,
|
||||
text: type,
|
||||
canCheck: true,
|
||||
isChecked: type === tag.type,
|
||||
onClick: this.onTypeSelect,
|
||||
} as IContextualMenuItem;
|
||||
});
|
||||
}
|
||||
|
||||
private getFormatSubMenuItems = (): IContextualMenuItem[] => {
|
||||
const tag = this.state.selectedTag;
|
||||
const formats = filterFormat(tag.type);
|
||||
|
||||
return formats.map((format) => {
|
||||
return {
|
||||
key: format,
|
||||
text: format,
|
||||
canCheck: true,
|
||||
isChecked: format === tag.format,
|
||||
onClick: this.onFormatSelect,
|
||||
} as IContextualMenuItem;
|
||||
});
|
||||
}
|
||||
|
||||
private onTypeSelect = (event: React.MouseEvent<HTMLButtonElement>, item?: IContextualMenuItem): void => {
|
||||
event.preventDefault();
|
||||
const type = item.text as FieldType;
|
||||
const tag = this.state.selectedTag;
|
||||
if (type === tag.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newTag = {
|
||||
...tag,
|
||||
type,
|
||||
format: FieldFormat.NotSpecified,
|
||||
};
|
||||
|
||||
if (this.props.onTagChanged) {
|
||||
this.props.onTagChanged(tag, newTag);
|
||||
}
|
||||
}
|
||||
|
||||
private onFormatSelect = (event: React.MouseEvent<HTMLButtonElement>, item?: IContextualMenuItem): void => {
|
||||
event.preventDefault();
|
||||
const format = item.text as FieldFormat;
|
||||
const tag = this.state.selectedTag;
|
||||
if (format === tag.format) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newTag = {
|
||||
...tag,
|
||||
format,
|
||||
};
|
||||
|
||||
if (this.props.onTagChanged) {
|
||||
this.props.onTagChanged(tag, newTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ describe("Tag Input Item", () => {
|
|||
tag: MockFactory.createTestTag(),
|
||||
index: 0,
|
||||
labels: [],
|
||||
isBeingEdited: false,
|
||||
isRenaming: false,
|
||||
isLocked: false,
|
||||
isSelected: false,
|
||||
appliedToSelectedRegions: false,
|
||||
|
@ -21,7 +21,6 @@ describe("Tag Input Item", () => {
|
|||
onChange: jest.fn(),
|
||||
onLabelEnter: jest.fn(),
|
||||
onLabelLeave: jest.fn(),
|
||||
onCallDropDown: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -7,12 +7,6 @@ import { ITag, ILabel, FieldType, FieldFormat } from "../../../../models/applica
|
|||
import { strings } from "../../../../common/strings";
|
||||
import TagInputItemLabel from "./tagInputItemLabel";
|
||||
|
||||
export enum TagEditMode {
|
||||
Color = "color",
|
||||
Name = "name",
|
||||
Dropdown = "inputField",
|
||||
}
|
||||
|
||||
export interface ITagClickProps {
|
||||
ctrlKey?: boolean;
|
||||
altKey?: boolean;
|
||||
|
@ -31,8 +25,8 @@ export interface ITagInputItemProps {
|
|||
index: number;
|
||||
/** Labels owned by the tag */
|
||||
labels: ILabel[];
|
||||
/** Tag is currently being edited */
|
||||
isBeingEdited: boolean;
|
||||
/** Tag is currently renaming */
|
||||
isRenaming: boolean;
|
||||
/** Tag is currently locked for application */
|
||||
isLocked: boolean;
|
||||
/** Tag is currently selected */
|
||||
|
@ -46,39 +40,30 @@ export interface ITagInputItemProps {
|
|||
onLabelEnter: (label: ILabel) => void;
|
||||
onLabelLeave: (label: ILabel) => void;
|
||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||
onCallDropDown: () => void;
|
||||
}
|
||||
|
||||
export interface ITagInputItemState {
|
||||
/** Tag is currently being edited */
|
||||
isBeingEdited: boolean;
|
||||
/** Tag is currently renaming */
|
||||
isRenaming: boolean;
|
||||
|
||||
/** Tag is currently locked for application */
|
||||
isLocked: boolean;
|
||||
/** Mode of tag editing (text or color) */
|
||||
tagEditMode: TagEditMode;
|
||||
}
|
||||
|
||||
export default class TagInputItem extends React.Component<ITagInputItemProps, ITagInputItemState> {
|
||||
|
||||
public static getNameNode(tagNode: Element): Element | undefined {
|
||||
if (tagNode) {
|
||||
return tagNode.getElementsByClassName(TagInputItem.TAG_NAME_CLASS_NAME)[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private static TAG_NAME_CLASS_NAME = "tag-item";
|
||||
|
||||
public state: ITagInputItemState = {
|
||||
isBeingEdited: false,
|
||||
isRenaming: false,
|
||||
isLocked: false,
|
||||
tagEditMode: null,
|
||||
};
|
||||
|
||||
private itemRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
public render() {
|
||||
const style: any = {
|
||||
background: this.props.tag.color,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={"tag-item-block"}>
|
||||
<div
|
||||
|
@ -89,7 +74,10 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
<div className={"tag-item-block-2"}>
|
||||
{
|
||||
this.props.tag &&
|
||||
<div className={this.getItemClassName()} style={style}>
|
||||
<div
|
||||
ref={this.itemRef}
|
||||
className={this.getItemClassName()}
|
||||
style={style}>
|
||||
<div
|
||||
className={"tag-content pr-2"}
|
||||
onClick={this.onNameClick}>
|
||||
|
@ -108,9 +96,9 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
}
|
||||
|
||||
public componentDidUpdate(prevProps: ITagInputItemProps) {
|
||||
if (prevProps.isBeingEdited !== this.props.isBeingEdited) {
|
||||
if (prevProps.isRenaming !== this.props.isRenaming) {
|
||||
this.setState({
|
||||
isBeingEdited: this.props.isBeingEdited,
|
||||
isRenaming: this.props.isRenaming,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -121,21 +109,24 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
}
|
||||
}
|
||||
|
||||
private onInputFieldClick = (e: any) => {
|
||||
public getTagNameRef() {
|
||||
return this.itemRef;
|
||||
}
|
||||
|
||||
private onDropdownClick = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
this.setState({
|
||||
tagEditMode: TagEditMode.Dropdown,
|
||||
}, () => this.props.onClick(this.props.tag, { keyClick: true, clickedDropDown: true }));
|
||||
|
||||
const clickedDropDown = true;
|
||||
this.props.onClick(this.props.tag, { clickedDropDown });
|
||||
}
|
||||
|
||||
private onColorClick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const ctrlKey = e.ctrlKey || e.metaKey;
|
||||
const keyClick = (e.type === "click");
|
||||
this.setState({
|
||||
tagEditMode: TagEditMode.Color,
|
||||
}, () => this.props.onClick(this.props.tag, { ctrlKey, keyClick, clickedColor: true }));
|
||||
const altKey = e.altKey;
|
||||
const clickedColor = true;
|
||||
this.props.onClick(this.props.tag, { ctrlKey, altKey, clickedColor });
|
||||
}
|
||||
|
||||
private onNameClick = (e: MouseEvent) => {
|
||||
|
@ -143,13 +134,11 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
|
||||
const ctrlKey = e.ctrlKey || e.metaKey;
|
||||
const altKey = e.altKey;
|
||||
this.setState({
|
||||
tagEditMode: TagEditMode.Name,
|
||||
}, () => this.props.onClick(this.props.tag, { ctrlKey, altKey }));
|
||||
this.props.onClick(this.props.tag, { ctrlKey, altKey });
|
||||
}
|
||||
|
||||
private getItemClassName = () => {
|
||||
const classNames = [TagInputItem.TAG_NAME_CLASS_NAME];
|
||||
const classNames = ["tag-item"];
|
||||
if (this.props.isSelected) {
|
||||
classNames.push("tag-item-selected");
|
||||
}
|
||||
|
@ -169,7 +158,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
}
|
||||
<div className="tag-name-body">
|
||||
{
|
||||
(this.state.isBeingEdited && this.state.tagEditMode === TagEditMode.Name)
|
||||
this.state.isRenaming
|
||||
?
|
||||
<input
|
||||
className={`tag-name-editor ${this.getContentClassName()}`}
|
||||
|
@ -196,7 +185,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
ariaLabel={strings.tags.toolbar.contextualMenu}
|
||||
className="tag-input-toolbar-iconbutton ml-2"
|
||||
iconProps={{iconName: "ChevronDown"}}
|
||||
onClick={this.onInputFieldClick} />
|
||||
onClick={this.onDropdownClick} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -221,16 +210,13 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
});
|
||||
} else if (e.key === "Escape") {
|
||||
this.setState({
|
||||
isBeingEdited: false,
|
||||
isRenaming: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getContentClassName = () => {
|
||||
const classNames = ["tag-name-text px-2 pb-1"];
|
||||
if (this.state.isBeingEdited && this.state.tagEditMode === TagEditMode.Color) {
|
||||
classNames.push("tag-color-edit");
|
||||
}
|
||||
if (this.isTypeOrFormatSpecified()) {
|
||||
classNames.push("tag-name-text-typed");
|
||||
}
|
||||
|
@ -243,7 +229,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
|||
return (displayIndex < 10) ? displayIndex : null;
|
||||
}
|
||||
|
||||
private isTypeOrFormatSpecified() {
|
||||
private isTypeOrFormatSpecified = () => {
|
||||
const {tag} = this.props;
|
||||
return (tag.type && tag.type !== FieldType.String) ||
|
||||
(tag.format && tag.format !== FieldFormat.NotSpecified);
|
||||
|
|
|
@ -7,3 +7,4 @@ $tagLinkWidth: 22px;
|
|||
$tagItemWidth: $tagInputWidth - $tagColorWidth;
|
||||
$tagTextWidth: $tagItemWidth - 55px;
|
||||
$tagTextLinkedWidth: $tagTextWidth - $tagLinkWidth;
|
||||
$tagContextualMenuWidth: $tagItemWidth - 8px;
|
||||
|
|
|
@ -19,6 +19,7 @@ export function registerIcons() {
|
|||
Settings: "\uE713",
|
||||
Link: "\uE71B",
|
||||
Search: "\uE721",
|
||||
CheckMark: "\uE73E",
|
||||
Up: "\uE74A",
|
||||
Down: "\uE74B",
|
||||
Delete: "\uE74D",
|
||||
|
|
Загрузка…
Ссылка в новой задаче