Rework EditableItemList to support mxField
Also improves upon the general UX to be a bit friendlier for direct manipulation things.pull/21833/head
parent
86c49d5807
commit
2903a0e712
|
@ -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;
|
||||
}
|
|
@ -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>);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue