From 381af547faf83e272c01e48479ea4b14132db9f3 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 31 Oct 2016 23:22:22 +0100 Subject: [PATCH] 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) --- js/package.json | 1 + js/src/modals/EditMeta/editMeta.js | 51 ++------ js/src/ui/Actionbar/Search/search.css | 8 -- js/src/ui/Actionbar/Search/search.js | 116 +++-------------- js/src/ui/Form/InputChip/index.js | 17 +++ js/src/ui/Form/InputChip/inputChip.css | 26 ++++ js/src/ui/Form/InputChip/inputChip.js | 164 +++++++++++++++++++++++++ js/src/ui/Form/index.js | 2 + js/src/ui/index.js | 3 +- js/webpack.config.js | 2 + 10 files changed, 238 insertions(+), 152 deletions(-) create mode 100644 js/src/ui/Form/InputChip/index.js create mode 100644 js/src/ui/Form/InputChip/inputChip.css create mode 100644 js/src/ui/Form/InputChip/inputChip.js diff --git a/js/package.json b/js/package.json index 959d470c7..8f6f85721 100644 --- a/js/package.json +++ b/js/package.json @@ -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", diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index afcf9b127..f00418194 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -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 ( - ); } - 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)); } diff --git a/js/src/ui/Actionbar/Search/search.css b/js/src/ui/Actionbar/Search/search.css index 58365d03b..956a9e2e3 100644 --- a/js/src/ui/Actionbar/Search/search.css +++ b/js/src/ui/Actionbar/Search/search.css @@ -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; -} diff --git a/js/src/ui/Actionbar/Search/search.js b/js/src/ui/Actionbar/Search/search.js index b25ccf5fb..4f2b7a5c0 100644 --- a/js/src/ui/Actionbar/Search/search.js +++ b/js/src/ui/Actionbar/Search/search.js @@ -15,14 +15,9 @@ // along with Parity. If not, see . 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'>
-
@@ -108,72 +89,13 @@ export default class ActionbarSearch extends Component { ); } - chipRenderer = (state, key) => { - const { value, isFocused, isDisabled, handleClick, handleRequestDelete } = state; - - return ( - - { value } - - ); + 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(); - } + 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); diff --git a/js/src/ui/Form/InputChip/index.js b/js/src/ui/Form/InputChip/index.js new file mode 100644 index 000000000..74bf603fe --- /dev/null +++ b/js/src/ui/Form/InputChip/index.js @@ -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 . + +export default from './inputChip'; diff --git a/js/src/ui/Form/InputChip/inputChip.css b/js/src/ui/Form/InputChip/inputChip.css new file mode 100644 index 000000000..b9427f42d --- /dev/null +++ b/js/src/ui/Form/InputChip/inputChip.css @@ -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 . +*/ + +.chip { + & > svg { + width: 1.2rem !important; + height: 1.2rem !important; + margin: initial !important; + margin-right: 4px !important; + padding: 4px 0 !important; + } +} diff --git a/js/src/ui/Form/InputChip/inputChip.js b/js/src/ui/Form/InputChip/inputChip.js new file mode 100644 index 000000000..d73596f29 --- /dev/null +++ b/js/src/ui/Form/InputChip/inputChip.js @@ -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 . + +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 ( + + ); + } + + chipRenderer = (state, key) => { + const { value, isFocused, isDisabled, handleClick, handleRequestDelete } = state; + + return ( + + { value } + + ); + } + + 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)); + } + +} diff --git a/js/src/ui/Form/index.js b/js/src/ui/Form/index.js index 30dba8d14..061cdddd0 100644 --- a/js/src/ui/Form/index.js +++ b/js/src/ui/Form/index.js @@ -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 }; diff --git a/js/src/ui/index.js b/js/src/ui/index.js index 72ad058e0..b766df9c3 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -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, diff --git a/js/webpack.config.js b/js/webpack.config.js index 299e24dbb..37aeb25dd 100644 --- a/js/webpack.config.js +++ b/js/webpack.config.js @@ -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}`);