Rework EditableItemList to support mxField

Also improves upon the general UX to be a bit friendlier for direct manipulation things.
pull/21833/head
Travis Ralston 2019-02-08 09:11:30 -07:00
parent 86c49d5807
commit 2903a0e712
3 changed files with 131 additions and 156 deletions

View File

@ -1,5 +1,5 @@
/*
Copyright 2017 New Vector Ltd.
Copyright 2017, 2019 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,47 +16,38 @@ limitations under the License.
.mx_EditableItemList {
margin-top: 12px;
margin-bottom: 0px;
margin-bottom: 10px;
}
.mx_EditableItem {
display: flex;
margin-left: 56px;
margin-bottom: 5px;
margin-left: 15px;
}
.mx_EditableItem .mx_EditableItem_editable {
border: 0px;
border-bottom: 1px solid $strong-input-border-color;
padding: 0px;
min-width: 240px;
max-width: 400px;
margin-bottom: 16px;
}
.mx_EditableItem .mx_EditableItem_editable:focus {
border-bottom: 1px solid $accent-color;
outline: none;
box-shadow: none;
}
.mx_EditableItem .mx_EditableItem_editablePlaceholder {
color: $settings-grey-fg-color;
}
.mx_EditableItem .mx_EditableItem_addButton,
.mx_EditableItem .mx_EditableItem_removeButton {
padding-left: 0.5em;
position: relative;
.mx_EditableItem_delete {
margin-right: 5px;
cursor: pointer;
visibility: hidden;
vertical-align: middle;
}
.mx_EditableItem:hover .mx_EditableItem_addButton,
.mx_EditableItem:hover .mx_EditableItem_removeButton {
visibility: visible;
.mx_EditableItem_email {
vertical-align: middle;
}
.mx_EditableItem_promptText {
margin-right: 10px;
}
.mx_EditableItem_confirmBtn {
margin-right: 5px;
}
.mx_EditableItemList_newItem .mx_Field input {
// Use 100% of the space available for the input, but don't let the 10px
// padding on either side of the input to push it out of alignment.
width: calc(100% - 20px);
}
.mx_EditableItemList_label {
margin-bottom: 8px;
}
margin-bottom: 5px;
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2017 New Vector Ltd.
Copyright 2017, 2019 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -18,140 +18,132 @@ import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import {_t} from '../../../languageHandler.js';
import Field from "./Field";
import AccessibleButton from "./AccessibleButton";
const EditableItem = React.createClass({
displayName: 'EditableItem',
propTypes: {
initialValue: PropTypes.string,
export class EditableItem extends React.Component {
static propTypes = {
index: PropTypes.number,
value: PropTypes.string,
onRemove: PropTypes.func,
};
constructor() {
super();
this.state = {
verifyRemove: false,
};
}
_onRemove = (e) => {
e.stopPropagation();
e.preventDefault();
this.setState({verifyRemove: true});
};
_onDontRemove = (e) => {
e.stopPropagation();
e.preventDefault();
this.setState({verifyRemove: false});
};
_onActuallyRemove = (e) => {
e.stopPropagation();
e.preventDefault();
if (this.props.onRemove) this.props.onRemove(this.props.index);
this.setState({verifyRemove: false});
};
render() {if (this.state.verifyRemove) {
return (
<div className="mx_EditableItem">
<span className="mx_EditableItem_promptText">
{_t("Are you sure?")}
</span>
<AccessibleButton onClick={this._onActuallyRemove} kind="primary_sm"
className="mx_EditableItem_confirmBtn">
{_t("Yes")}
</AccessibleButton>
<AccessibleButton onClick={this._onDontRemove} kind="danger_sm"
className="mx_EditableItem_confirmBtn">
{_t("No")}
</AccessibleButton>
</div>
);
}
return (
<div className="mx_EditableItem">
<img src={require("../../../../res/img/feather-icons/cancel.svg")} width={14} height={14}
onClick={this._onRemove} className="mx_EditableItem_delete" alt={_t("Remove")} />
<span className="mx_EditableItem_item">{this.props.value}</span>
</div>
);
}
}
export default class EditableItemList extends React.Component{
static propTypes = {
items: PropTypes.arrayOf(PropTypes.string).isRequired,
itemsLabel: PropTypes.string,
noItemsLabel: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func,
onRemove: PropTypes.func,
onAdd: PropTypes.func,
addOnChange: PropTypes.bool,
},
onChange: function(value) {
this.setState({ value });
if (this.props.onChange) this.props.onChange(value, this.props.index);
if (this.props.addOnChange && this.props.onAdd) this.props.onAdd(value);
},
onRemove: function() {
if (this.props.onRemove) this.props.onRemove(this.props.index);
},
onAdd: function() {
if (this.props.onAdd) this.props.onAdd(this.state.value);
},
render: function() {
const EditableText = sdk.getComponent('elements.EditableText');
return <div className="mx_EditableItem">
<EditableText
className="mx_EditableItem_editable"
placeholderClassName="mx_EditableItem_editablePlaceholder"
placeholder={this.props.placeholder}
blurToCancel={false}
editable={true}
initialValue={this.props.initialValue}
onValueChanged={this.onChange} />
{ this.props.onAdd ?
<div className="mx_EditableItem_addButton">
<img className="mx_filterFlipColor"
src={require("../../../../res/img/plus.svg")} width="14" height="14"
alt={_t("Add")} onClick={this.onAdd} />
</div>
:
<div className="mx_EditableItem_removeButton">
<img className="mx_filterFlipColor"
src={require("../../../../res/img/cancel-small.svg")} width="14" height="14"
alt={_t("Delete")} onClick={this.onRemove} />
</div>
}
</div>;
},
});
// TODO: Make this use the new Field element
module.exports = React.createClass({
displayName: 'EditableItemList',
propTypes: {
items: PropTypes.arrayOf(PropTypes.string).isRequired,
onNewItemChanged: PropTypes.func,
onItemAdded: PropTypes.func,
onItemEdited: PropTypes.func,
onItemRemoved: PropTypes.func,
canEdit: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
onItemAdded: () => {},
onItemEdited: () => {},
onItemRemoved: () => {},
onNewItemChanged: () => {},
};
},
_onItemAdded = (e) => {
e.stopPropagation();
e.preventDefault();
onItemAdded: function(value) {
this.props.onItemAdded(value);
},
if (!this.refs.newItem) return;
onItemEdited: function(value, index) {
if (value.length === 0) {
this.onItemRemoved(index);
} else {
this.props.onItemEdited(value, index);
}
},
const value = this.refs.newItem.value;
if (this.props.onItemAdded) this.props.onItemAdded(value);
};
onItemRemoved: function(index) {
this.props.onItemRemoved(index);
},
_onItemRemoved = (index) => {
if (this.props.onItemRemoved) this.props.onItemRemoved(index);
};
onNewItemChanged: function(value) {
this.props.onNewItemChanged(value);
},
_renderNewItemField() {
return (
<form onSubmit={this._onAddClick} autoComplete={false}
noValidate={true} className="mx_EditableItemList_newItem">
<Field id="newEmailAddress" ref="newItem" label={this.props.placeholder}
type="text" autoComplete="off" />
<AccessibleButton onClick={this._onItemAdded} kind="primary">
{_t("Add")}
</AccessibleButton>
</form>
)
}
render: function() {
render() {
const editableItems = this.props.items.map((item, index) => {
return <EditableItem
key={index}
index={index}
initialValue={item}
onChange={this.onItemEdited}
onRemove={this.onItemRemoved}
placeholder={this.props.placeholder}
value={item}
onRemove={this._onItemRemoved}
/>;
});
const label = this.props.items.length > 0 ?
this.props.itemsLabel : this.props.noItemsLabel;
const label = this.props.items.length > 0 ? this.props.itemsLabel : this.props.noItemsLabel;
return (<div className="mx_EditableItemList">
<div className="mx_EditableItemList_label">
{ label }
</div>
{ editableItems }
{ this.props.canEdit ?
// This is slightly evil; we want a new instance of
// EditableItem when the list grows. To make sure it's
// reset to its initial state.
<EditableItem
key={editableItems.length}
initialValue={this.props.newItem}
onAdd={this.onItemAdded}
onChange={this.onNewItemChanged}
addOnChange={true}
placeholder={this.props.placeholder}
/> : <div />
}
{ this.props.canEdit ? this._renderNewItemField() : <div /> }
</div>);
},
});
}
}

View File

@ -259,21 +259,13 @@ module.exports = React.createClass({
<div>
{ _t("Remote addresses for this room:") }
</div>
<div>
<ul>
{ this.state.remoteDomains.map((domain, i) => {
return this.state.domainToAliases[domain].map(function(alias, j) {
return (
<div key={i + "_" + j}>
<EditableText
className="mx_AliasSettings_alias mx_AliasSettings_editable"
blurToCancel={false}
editable={false}
initialValue={alias} />
</div>
);
return this.state.domainToAliases[domain].map((alias, j) => {
return <li key={i + "_" + j}>{alias}</li>;
});
}) }
</div>
</ul>
</div>
);
}