Align tag inputs with other input boxes (#2965)
* Wrap tag input component * Postcss nested selectors * Chips has same size as in ui * Input matches with sizes/paddings of others * Adjust colours, move hint text * Added ChipInput from search in wrapper * Using InputChip Wrapper in search (#2965)
This commit is contained in:
parent
04432b2766
commit
381af547fa
@ -92,6 +92,7 @@
|
||||
"nock": "^8.0.0",
|
||||
"postcss-import": "^8.1.2",
|
||||
"postcss-loader": "^0.8.1",
|
||||
"postcss-nested": "^1.0.0",
|
||||
"postcss-simple-vars": "^3.0.0",
|
||||
"react-addons-test-utils": "^15.3.0",
|
||||
"react-copy-to-clipboard": "^4.2.3",
|
||||
|
@ -17,10 +17,8 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
import ContentSave from 'material-ui/svg-icons/content/save';
|
||||
// import ChipInput from 'material-ui-chip-input';
|
||||
import ChipInput from 'material-ui-chip-input/src/ChipInput';
|
||||
|
||||
import { Button, Form, Input, Modal } from '../../ui';
|
||||
import { Button, Form, Input, InputChip, Modal } from '../../ui';
|
||||
import { validateName } from '../../util/validation';
|
||||
|
||||
export default class EditMeta extends Component {
|
||||
@ -104,53 +102,20 @@ export default class EditMeta extends Component {
|
||||
const { tags } = meta || [];
|
||||
|
||||
return (
|
||||
<ChipInput
|
||||
ref='tagsInput'
|
||||
value={ tags }
|
||||
onRequestAdd={ this.onAddTag }
|
||||
onRequestDelete={ this.onDeleteTag }
|
||||
floatingLabelText='(optional) tags'
|
||||
hintText='press <Enter> to add a tag'
|
||||
onUpdateInput={ this.onTagsInputChange }
|
||||
floatingLabelFixed
|
||||
fullWidth
|
||||
<InputChip
|
||||
tokens={ tags }
|
||||
onTokensChange={ this.onTagsChange }
|
||||
label='(optional) tags'
|
||||
hint='press <Enter> to add a tag'
|
||||
clearOnBlur
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onAddTag = (tag) => {
|
||||
const { meta } = this.state;
|
||||
const { tags } = meta || [];
|
||||
|
||||
this.onMetaChange('tags', [].concat(tags, tag));
|
||||
}
|
||||
|
||||
onDeleteTag = (tag) => {
|
||||
const { meta } = this.state;
|
||||
const { tags } = meta || [];
|
||||
|
||||
const newTags = tags
|
||||
.filter(t => t !== tag);
|
||||
|
||||
onTagsChange = (newTags) => {
|
||||
this.onMetaChange('tags', newTags);
|
||||
}
|
||||
|
||||
onTagsInputChange = (value) => {
|
||||
const { meta } = this.state;
|
||||
const { tags = [] } = meta;
|
||||
|
||||
const tokens = value.split(/[\s,;]+/);
|
||||
|
||||
const newTokens = tokens
|
||||
.slice(0, -1)
|
||||
.filter(t => t.length > 0);
|
||||
|
||||
const inputValue = tokens.slice(-1)[0].trim();
|
||||
|
||||
this.onMetaChange('tags', [].concat(tags, newTokens));
|
||||
this.refs.tagsInput.setState({ inputValue });
|
||||
}
|
||||
|
||||
onNameChange = (name) => {
|
||||
this.setState(validateName(name));
|
||||
}
|
||||
|
@ -41,11 +41,3 @@
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.chip > svg {
|
||||
width: 1.2rem !important;
|
||||
height: 1.2rem !important;
|
||||
margin: initial !important;
|
||||
margin-right: 4px !important;
|
||||
padding: 4px 0 !important;
|
||||
}
|
||||
|
@ -15,14 +15,9 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { Chip } from 'material-ui';
|
||||
import { blue300 } from 'material-ui/styles/colors';
|
||||
// import ChipInput from 'material-ui-chip-input';
|
||||
import ChipInput from 'material-ui-chip-input/src/ChipInput';
|
||||
import ActionSearch from 'material-ui/svg-icons/action/search';
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
import { Button } from '../../';
|
||||
import { Button, InputChip } from '../../';
|
||||
|
||||
import styles from './search.css';
|
||||
|
||||
@ -74,28 +69,14 @@ export default class ActionbarSearch extends Component {
|
||||
className={ styles.searchcontainer }
|
||||
key='searchAccount'>
|
||||
<div className={ inputContainerClasses.join(' ') }>
|
||||
<ChipInput
|
||||
clearOnBlur={ false }
|
||||
<InputChip
|
||||
className={ styles.input }
|
||||
chipRenderer={ this.chipRenderer }
|
||||
hintText='Enter search input...'
|
||||
ref='searchInput'
|
||||
value={ tokens }
|
||||
hint='Enter search input...'
|
||||
tokens={ tokens }
|
||||
|
||||
onBlur={ this.handleSearchBlur }
|
||||
onRequestAdd={ this.handleTokenAdd }
|
||||
onRequestDelete={ this.handleTokenDelete }
|
||||
onUpdateInput={ this.handleInputChange }
|
||||
hintStyle={ {
|
||||
bottom: 16,
|
||||
left: 2,
|
||||
transition: 'none'
|
||||
} }
|
||||
inputStyle={ {
|
||||
marginBottom: 18
|
||||
} }
|
||||
textFieldStyle={ {
|
||||
height: 42
|
||||
} }
|
||||
onInputChange={ this.handleInputChange }
|
||||
onTokensChange={ this.handleTokensChange }
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -108,72 +89,13 @@ export default class ActionbarSearch extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
chipRenderer = (state, key) => {
|
||||
const { value, isFocused, isDisabled, handleClick, handleRequestDelete } = state;
|
||||
|
||||
return (
|
||||
<Chip
|
||||
key={ key }
|
||||
className={ styles.chip }
|
||||
style={ {
|
||||
margin: '8px 8px 0 0',
|
||||
float: 'left',
|
||||
pointerEvents: isDisabled ? 'none' : undefined,
|
||||
alignItems: 'center'
|
||||
} }
|
||||
labelStyle={ {
|
||||
paddingRight: 6,
|
||||
fontSize: '0.9rem',
|
||||
lineHeight: 'initial'
|
||||
} }
|
||||
backgroundColor={ isFocused ? blue300 : 'rgba(0, 0, 0, 0.73)' }
|
||||
onTouchTap={ handleClick }
|
||||
onRequestDelete={ handleRequestDelete }
|
||||
>
|
||||
{ value }
|
||||
</Chip>
|
||||
);
|
||||
handleTokensChange = (tokens) => {
|
||||
this.handleSearchChange(tokens);
|
||||
}
|
||||
|
||||
handleTokenAdd = (value) => {
|
||||
const { tokens } = this.props;
|
||||
const { inputValue } = this.state;
|
||||
|
||||
const newSearchTokens = uniq([].concat(tokens, value));
|
||||
|
||||
this.setState({
|
||||
inputValue: inputValue === value ? '' : inputValue
|
||||
}, () => {
|
||||
this.handleSearchChange(newSearchTokens);
|
||||
});
|
||||
}
|
||||
|
||||
handleTokenDelete = (value) => {
|
||||
const { tokens } = this.props;
|
||||
|
||||
const newSearchTokens = []
|
||||
.concat(tokens)
|
||||
.filter(v => v !== value);
|
||||
|
||||
this.handleSearchChange(newSearchTokens);
|
||||
this.refs.searchInput.focus();
|
||||
}
|
||||
|
||||
handleInputChange = (value) => {
|
||||
const splitTokens = value.split(/[\s,;]/);
|
||||
|
||||
const inputValue = (splitTokens.length <= 1)
|
||||
? value
|
||||
: splitTokens.slice(-1)[0].trim();
|
||||
|
||||
this.refs.searchInput.setState({ inputValue });
|
||||
handleInputChange = (inputValue) => {
|
||||
this.setState({ inputValue }, () => {
|
||||
if (splitTokens.length > 1) {
|
||||
const tokensToAdd = splitTokens.slice(0, -1);
|
||||
tokensToAdd.forEach(token => this.handleTokenAdd(token));
|
||||
} else {
|
||||
this.handleSearchChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -182,12 +104,10 @@ export default class ActionbarSearch extends Component {
|
||||
const { inputValue } = this.state;
|
||||
|
||||
const newSearchTokens = []
|
||||
.concat(searchTokens || tokens)
|
||||
.filter(v => v.length > 0);
|
||||
.concat(searchTokens || tokens);
|
||||
|
||||
const newSearchValues = []
|
||||
.concat(searchTokens || tokens, inputValue)
|
||||
.filter(v => v.length > 0);
|
||||
.concat(searchTokens || tokens, inputValue);
|
||||
|
||||
onChange(newSearchTokens, newSearchValues);
|
||||
}
|
||||
@ -214,19 +134,15 @@ export default class ActionbarSearch extends Component {
|
||||
}
|
||||
|
||||
handleOpenSearch = (showSearch, force) => {
|
||||
if (this.state.stateChanging && !force) return false;
|
||||
if (this.state.stateChanging && !force) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
showSearch: showSearch,
|
||||
stateChanging: true
|
||||
});
|
||||
|
||||
if (showSearch) {
|
||||
this.refs.searchInput.focus();
|
||||
} else {
|
||||
this.refs.searchInput.getInputNode().blur();
|
||||
}
|
||||
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
this.setState({ stateChanging: false });
|
||||
}, 450);
|
||||
|
17
js/src/ui/Form/InputChip/index.js
Normal file
17
js/src/ui/Form/InputChip/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './inputChip';
|
26
js/src/ui/Form/InputChip/inputChip.css
Normal file
26
js/src/ui/Form/InputChip/inputChip.css
Normal file
@ -0,0 +1,26 @@
|
||||
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.chip {
|
||||
& > svg {
|
||||
width: 1.2rem !important;
|
||||
height: 1.2rem !important;
|
||||
margin: initial !important;
|
||||
margin-right: 4px !important;
|
||||
padding: 4px 0 !important;
|
||||
}
|
||||
}
|
164
js/src/ui/Form/InputChip/inputChip.js
Normal file
164
js/src/ui/Form/InputChip/inputChip.js
Normal file
@ -0,0 +1,164 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { Chip } from 'material-ui';
|
||||
import ChipInput from 'material-ui-chip-input';
|
||||
import { blue300 } from 'material-ui/styles/colors';
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
import styles from './inputChip.css';
|
||||
|
||||
export default class InputChip extends Component {
|
||||
static propTypes = {
|
||||
tokens: PropTypes.array.isRequired,
|
||||
className: PropTypes.string,
|
||||
hint: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onTokensChange: PropTypes.func,
|
||||
onInputChange: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
clearOnBlur: PropTypes.bool
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
clearOnBlur: false
|
||||
}
|
||||
|
||||
render () {
|
||||
const { clearOnBlur, className, hint, label, tokens } = this.props;
|
||||
const classes = `${className}`;
|
||||
|
||||
return (
|
||||
<ChipInput
|
||||
className={ classes }
|
||||
ref='chipInput'
|
||||
|
||||
value={ tokens }
|
||||
clearOnBlur={ clearOnBlur }
|
||||
floatingLabelText={ label }
|
||||
hintText={ hint }
|
||||
|
||||
chipRenderer={ this.chipRenderer }
|
||||
|
||||
onBlur={ this.handleBlur }
|
||||
onRequestAdd={ this.handleTokenAdd }
|
||||
onRequestDelete={ this.handleTokenDelete }
|
||||
onUpdateInput={ this.handleInputChange }
|
||||
|
||||
floatingLabelFixed
|
||||
fullWidth
|
||||
|
||||
hintStyle={ {
|
||||
bottom: 16,
|
||||
left: 1,
|
||||
transition: 'none'
|
||||
} }
|
||||
inputStyle={ {
|
||||
marginBottom: 18
|
||||
} }
|
||||
textFieldStyle={ {
|
||||
height: 42
|
||||
} } />
|
||||
);
|
||||
}
|
||||
|
||||
chipRenderer = (state, key) => {
|
||||
const { value, isFocused, isDisabled, handleClick, handleRequestDelete } = state;
|
||||
|
||||
return (
|
||||
<Chip
|
||||
key={ key }
|
||||
className={ styles.chip }
|
||||
style={ {
|
||||
margin: '8px 8px 0 0',
|
||||
float: 'left',
|
||||
pointerEvents: isDisabled ? 'none' : undefined,
|
||||
alignItems: 'center'
|
||||
} }
|
||||
labelStyle={ {
|
||||
paddingRight: 6,
|
||||
fontSize: '0.9rem',
|
||||
lineHeight: 'initial'
|
||||
} }
|
||||
backgroundColor={ isFocused ? blue300 : 'rgba(50, 50, 50, 0.73)' }
|
||||
onTouchTap={ handleClick }
|
||||
onRequestDelete={ handleRequestDelete }
|
||||
>
|
||||
{ value }
|
||||
</Chip>
|
||||
);
|
||||
}
|
||||
|
||||
handleBlur = () => {
|
||||
const { onBlur } = this.props;
|
||||
|
||||
if (typeof onBlur === 'function') {
|
||||
onBlur();
|
||||
}
|
||||
}
|
||||
|
||||
handleTokenAdd = (value) => {
|
||||
const { tokens, onInputChange } = this.props;
|
||||
|
||||
const newTokens = uniq([].concat(tokens, value));
|
||||
|
||||
this.handleTokensChange(newTokens);
|
||||
|
||||
if (value === this.refs.chipInput.state.inputValue && typeof onInputChange === 'function') {
|
||||
onInputChange('');
|
||||
}
|
||||
}
|
||||
|
||||
handleTokenDelete = (value) => {
|
||||
const { tokens } = this.props;
|
||||
|
||||
const newTokens = uniq([]
|
||||
.concat(tokens)
|
||||
.filter(v => v !== value));
|
||||
|
||||
this.handleTokensChange(newTokens);
|
||||
this.refs.chipInput.focus();
|
||||
}
|
||||
|
||||
handleInputChange = (value) => {
|
||||
const { onInputChange } = this.props;
|
||||
|
||||
const splitTokens = value.split(/[\s,;]/);
|
||||
|
||||
const inputValue = (splitTokens.length <= 1)
|
||||
? value
|
||||
: splitTokens.slice(-1)[0].trim();
|
||||
|
||||
this.refs.chipInput.setState({ inputValue });
|
||||
|
||||
if (splitTokens.length > 1) {
|
||||
const tokensToAdd = splitTokens.slice(0, -1);
|
||||
tokensToAdd.forEach(token => this.handleTokenAdd(token));
|
||||
}
|
||||
|
||||
if (typeof onInputChange === 'function') {
|
||||
onInputChange(inputValue);
|
||||
}
|
||||
}
|
||||
|
||||
handleTokensChange = (tokens) => {
|
||||
const { onTokensChange } = this.props;
|
||||
|
||||
onTokensChange(tokens.filter(token => token && token.length > 0));
|
||||
}
|
||||
|
||||
}
|
@ -19,6 +19,7 @@ import FormWrap from './FormWrap';
|
||||
import Input from './Input';
|
||||
import InputAddress from './InputAddress';
|
||||
import InputAddressSelect from './InputAddressSelect';
|
||||
import InputChip from './InputChip';
|
||||
import InputInline from './InputInline';
|
||||
import Select from './Select';
|
||||
|
||||
@ -29,6 +30,7 @@ export {
|
||||
Input,
|
||||
InputAddress,
|
||||
InputAddressSelect,
|
||||
InputChip,
|
||||
InputInline,
|
||||
Select
|
||||
};
|
||||
|
@ -25,7 +25,7 @@ import ConfirmDialog from './ConfirmDialog';
|
||||
import Container, { Title as ContainerTitle } from './Container';
|
||||
import ContextProvider from './ContextProvider';
|
||||
import Errors from './Errors';
|
||||
import Form, { AddressSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputInline, Select } from './Form';
|
||||
import Form, { AddressSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select } from './Form';
|
||||
import IdentityIcon from './IdentityIcon';
|
||||
import IdentityName from './IdentityName';
|
||||
import MethodDecoding from './MethodDecoding';
|
||||
@ -57,6 +57,7 @@ export {
|
||||
Input,
|
||||
InputAddress,
|
||||
InputAddressSelect,
|
||||
InputChip,
|
||||
InputInline,
|
||||
Select,
|
||||
IdentityIcon,
|
||||
|
@ -17,6 +17,7 @@
|
||||
const HappyPack = require('happypack');
|
||||
const path = require('path');
|
||||
const postcssImport = require('postcss-import');
|
||||
const postcssNested = require('postcss-nested');
|
||||
const postcssVars = require('postcss-simple-vars');
|
||||
const rucksack = require('rucksack-css');
|
||||
const webpack = require('webpack');
|
||||
@ -113,6 +114,7 @@ module.exports = {
|
||||
postcssImport({
|
||||
addDependencyTo: webpack
|
||||
}),
|
||||
postcssNested({}),
|
||||
postcssVars({
|
||||
unknown: function (node, name, result) {
|
||||
node.warn(result, `Unknown variable ${name}`);
|
||||
|
Loading…
Reference in New Issue
Block a user