Rework EditableItemList to support mxField
Also improves upon the general UX to be a bit friendlier for direct manipulation things.
This commit is contained in:
parent
86c49d5807
commit
2903a0e712
3 changed files with 131 additions and 156 deletions
|
@ -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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -16,47 +16,38 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_EditableItemList {
|
.mx_EditableItemList {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EditableItem {
|
.mx_EditableItem {
|
||||||
display: flex;
|
margin-bottom: 5px;
|
||||||
margin-left: 56px;
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EditableItem .mx_EditableItem_editable {
|
.mx_EditableItem_delete {
|
||||||
border: 0px;
|
margin-right: 5px;
|
||||||
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;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
vertical-align: middle;
|
||||||
visibility: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EditableItem:hover .mx_EditableItem_addButton,
|
.mx_EditableItem_email {
|
||||||
.mx_EditableItem:hover .mx_EditableItem_removeButton {
|
vertical-align: middle;
|
||||||
visibility: visible;
|
}
|
||||||
|
|
||||||
|
.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 {
|
.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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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 PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import {_t} from '../../../languageHandler.js';
|
import {_t} from '../../../languageHandler.js';
|
||||||
|
import Field from "./Field";
|
||||||
|
import AccessibleButton from "./AccessibleButton";
|
||||||
|
|
||||||
const EditableItem = React.createClass({
|
export class EditableItem extends React.Component {
|
||||||
displayName: 'EditableItem',
|
static propTypes = {
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
initialValue: PropTypes.string,
|
|
||||||
index: PropTypes.number,
|
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,
|
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,
|
onItemAdded: PropTypes.func,
|
||||||
onItemEdited: PropTypes.func,
|
|
||||||
onItemRemoved: PropTypes.func,
|
onItemRemoved: PropTypes.func,
|
||||||
|
|
||||||
canEdit: PropTypes.bool,
|
canEdit: PropTypes.bool,
|
||||||
},
|
};
|
||||||
|
|
||||||
getDefaultProps: function() {
|
_onItemAdded = (e) => {
|
||||||
return {
|
e.stopPropagation();
|
||||||
onItemAdded: () => {},
|
e.preventDefault();
|
||||||
onItemEdited: () => {},
|
|
||||||
onItemRemoved: () => {},
|
|
||||||
onNewItemChanged: () => {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onItemAdded: function(value) {
|
if (!this.refs.newItem) return;
|
||||||
this.props.onItemAdded(value);
|
|
||||||
},
|
|
||||||
|
|
||||||
onItemEdited: function(value, index) {
|
const value = this.refs.newItem.value;
|
||||||
if (value.length === 0) {
|
if (this.props.onItemAdded) this.props.onItemAdded(value);
|
||||||
this.onItemRemoved(index);
|
};
|
||||||
} else {
|
|
||||||
this.props.onItemEdited(value, index);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onItemRemoved: function(index) {
|
_onItemRemoved = (index) => {
|
||||||
this.props.onItemRemoved(index);
|
if (this.props.onItemRemoved) this.props.onItemRemoved(index);
|
||||||
},
|
};
|
||||||
|
|
||||||
onNewItemChanged: function(value) {
|
_renderNewItemField() {
|
||||||
this.props.onNewItemChanged(value);
|
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) => {
|
const editableItems = this.props.items.map((item, index) => {
|
||||||
return <EditableItem
|
return <EditableItem
|
||||||
key={index}
|
key={index}
|
||||||
index={index}
|
index={index}
|
||||||
initialValue={item}
|
value={item}
|
||||||
onChange={this.onItemEdited}
|
onRemove={this._onItemRemoved}
|
||||||
onRemove={this.onItemRemoved}
|
|
||||||
placeholder={this.props.placeholder}
|
|
||||||
/>;
|
/>;
|
||||||
});
|
});
|
||||||
|
|
||||||
const label = this.props.items.length > 0 ?
|
const label = this.props.items.length > 0 ? this.props.itemsLabel : this.props.noItemsLabel;
|
||||||
this.props.itemsLabel : this.props.noItemsLabel;
|
|
||||||
|
|
||||||
return (<div className="mx_EditableItemList">
|
return (<div className="mx_EditableItemList">
|
||||||
<div className="mx_EditableItemList_label">
|
<div className="mx_EditableItemList_label">
|
||||||
{ label }
|
{ label }
|
||||||
</div>
|
</div>
|
||||||
{ editableItems }
|
{ editableItems }
|
||||||
{ this.props.canEdit ?
|
{ this.props.canEdit ? this._renderNewItemField() : <div /> }
|
||||||
// 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 />
|
|
||||||
}
|
|
||||||
</div>);
|
</div>);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -259,21 +259,13 @@ module.exports = React.createClass({
|
||||||
<div>
|
<div>
|
||||||
{ _t("Remote addresses for this room:") }
|
{ _t("Remote addresses for this room:") }
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<ul>
|
||||||
{ this.state.remoteDomains.map((domain, i) => {
|
{ this.state.remoteDomains.map((domain, i) => {
|
||||||
return this.state.domainToAliases[domain].map(function(alias, j) {
|
return this.state.domainToAliases[domain].map((alias, j) => {
|
||||||
return (
|
return <li key={i + "_" + j}>{alias}</li>;
|
||||||
<div key={i + "_" + j}>
|
|
||||||
<EditableText
|
|
||||||
className="mx_AliasSettings_alias mx_AliasSettings_editable"
|
|
||||||
blurToCancel={false}
|
|
||||||
editable={false}
|
|
||||||
initialValue={alias} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue