Skip to content

Commit

Permalink
feat: add tags field
Browse files Browse the repository at this point in the history
  • Loading branch information
dackmin committed Oct 24, 2018
1 parent d3653d1 commit 7b24965
Show file tree
Hide file tree
Showing 8 changed files with 388 additions and 0 deletions.
2 changes: 2 additions & 0 deletions examples/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import SliderPage from '../containers/SliderPage';
import SwitchPage from '../containers/SwitchPage';
import DateFieldPage from '../containers/DateFieldPage';
import ButtonPage from '../containers/ButtonPage';
import TagsFieldPage from '../containers/TagsFieldPage';

import Toolbox from '../containers/Toolbox';

Expand Down Expand Up @@ -50,6 +51,7 @@ class App extends React.Component {
<Route exact path="/switch" component={SwitchPage} />
<Route exact path="/date-field" component={DateFieldPage} />
<Route exact path="/button" component={ButtonPage} />
<Route exact path="/tags-field" component={TagsFieldPage} />
</Switch>
</Router>

Expand Down
1 change: 1 addition & 0 deletions examples/components/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const Home = () => (
<li><Link to="/switch">Switch</Link></li>
<li><Link to="/date-field">DateField</Link></li>
<li><Link to="/button">Button</Link></li>
<li><Link to="/tags-field">TagsField</Link></li>
</ul>
</div>
);
Expand Down
48 changes: 48 additions & 0 deletions examples/components/TagsFieldPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { Link } from 'react-router-dom';

import { TagsField } from '@poool/junipero';

class TagsFieldPage extends React.Component {

constructor(props) {
super(props);
this.state = {
default: {},
};
}

onChange(name, field) {
this.setState({ [name]: field });
}

render() {
return (
<div className="container">
<p><Link to="/">Back</Link></p>
<h1>TagsField example</h1>

<h2 className="mt-5">Default</h2>
<div className="row mt-5">
<div className="col-6">
<TagsField
label="Label"
disabled={this.props.disabled}
boxed={this.props.boxed}
error={this.props.error}
placeholder={this.props.placeholder}
onChange={this.onChange.bind(this, 'default')}
/>
</div>
<div className="col-6">
<p>Current state :</p>
<pre>{ JSON.stringify(this.state.default, null, 2)}</pre>
</div>
</div>
</div>
);
}

}

export default TagsFieldPage;
15 changes: 15 additions & 0 deletions examples/containers/TagsFieldPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { connect } from 'react-redux';

import TagsFieldPage from '../components/TagsFieldPage';

const mapStateToProps = (state, ownProps) => {
return {
...ownProps,
disabled: state.tools.disabled,
error: state.tools.error,
placeholder: state.tools.placeholder,
boxed: state.tools.boxed,
};
};

export default connect(mapStateToProps)(TagsFieldPage);
276 changes: 276 additions & 0 deletions src/components/TagsField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
import React from 'react';
import PropTypes from 'prop-types';

import '../theme/components/TagsField.styl';

const propTypes = {
className: PropTypes.string,
value: PropTypes.array,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
deleteComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
placeholder: PropTypes.string,
disabled: PropTypes.bool,
required: PropTypes.bool,
boxed: PropTypes.bool,
prefix: PropTypes.object,
suffix: PropTypes.object,
forceValue: PropTypes.bool,
titleKey: PropTypes.string,
valueKey: PropTypes.string,
tabIndex: PropTypes.number,
onChange: PropTypes.func,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
};

const defaultProps = {
className: null,
value: [],
label: '',
deleteComponent: (<i className="close-icon" />),
placeholder: '',
disabled: false,
required: false,
boxed: false,
prefix: null,
suffix: null,
forceValue: false,
titleKey: 'title',
valueKey: 'value',
tabIndex: 0,
onChange: () => {},
onFocus: () => {},
onBlur: () => {},
};

class TagsField extends React.Component {

constructor(props) {
super(props);

this.state = {
value: this.props.value,
input: '',
focused: false,
selected: -1,
};
}

onClick(e) {
if (this.props.disabled) {
return false;
}

e.preventDefault();
this.input?.focus();
return false;
}

onFocus(e) {
if (this.props.disabled) {
return false;
}

this.props.onFocus(e);

if (
e.defaultPrevented ||
(e.isImmediatePropagationStopped && e.isImmediatePropagationStopped()) ||
(e.isPropagationStopped && e.isPropagationStopped())
) {
return false;
}

this.setState({ focused: true });
return true;
}

onBlur(e) {
if (this.props.disabled) {
return false;
}

this.props.onFocus(e);

if (
e.defaultPrevented ||
(e.isImmediatePropagationStopped && e.isImmediatePropagationStopped()) ||
(e.isPropagationStopped && e.isPropagationStopped())
) {
return false;
}

this.setState({ focused: false });
return true;
}

onInputChange(e) {
if (this.props.disabled) {
return false;
}

const input = e.target.value;

this.setState({
input,
selected: -1,
});

return true;
}

onKeyDown(e) {
if (this.props.disabled) {
return false;
}

const {selected, input, value} = this.state;

if (
(e.which === 8 || e.keyCode === 8) && // BACKSPACE
input.trim() === '' &&
value.length > 0
) {
if (selected < 0) {
this.selectItem();
} else {
this.remove(selected);
}
return false;
} else if (e.which === 27 || e.keyCode === 27) { // ESC
this.unselectItem();
}

return true;
}

onKeyPress(e) {
if (this.props.disabled) {
return false;
}

if (e.which === 13 || e.keyCode === 13) {
this.add(this.state.input);
return false;
}

return true;
}

add(item) {
if (!item || item.trim() === '') {
return;
}

this.state.value.push(item.trim());

this.setState({
value: this.state.value,
input: '',
}, () => {

this.props.onChange({
value: this.state.value,
});
});
}

remove(index) {
this.state.value.splice(index, 1);

this.setState({
value: this.state.value,
selected: -1,
}, () => {

this.props.onChange({
value: this.state.value,
});
});
}

selectItem() {
this.setState({
selected: this.state.value.length - 1,
});
}

unselectItem() {
this.setState({
selected: -1,
});
}

render() {
return (
<div
className={[
'junipero',
'junipero-field',
'tags-field',
this.state.focused ? 'focused' : null,
this.state.input || this.state.value.length ? 'dirty' : null,
this.props.disabled ? 'disabled' : null,
this.state.opened ? 'opened' : null,
this.props.required ? 'required' : null,
this.props.boxed ? 'boxed' : null,
this.props.className,
].join(' ')}
role="textbox"
tabIndex={this.props.tabIndex}
onClick={this.onClick.bind(this)}
>

<div className="field-wrapper">
{ this.props.prefix && (
<div className="field-prefix">{ this.props.prefix }</div>
) }

<div className="field-inner">

{ this.state.value.map((item, index) => (
<span
key={index}
className={[
'tag',
this.state.selected === index ? 'active' : null,
].join(' ')}
>
{ item }
</span>
)) }
<input
ref={(ref) => this.input = ref}
className="field"
type="text"
disabled={this.props.disabled}
required={this.props.required}
placeholder={this.props.placeholder}
value={this.state.input}
rows={this.props.rows}
onFocus={this.onFocus.bind(this)}
onBlur={this.onBlur.bind(this)}
onChange={this.onInputChange.bind(this)}
onKeyPress={this.onKeyPress.bind(this)}
onKeyDown={this.onKeyDown.bind(this)}
/>

{ this.props.label && (
<span className="label">{ this.props.label }</span>
) }
</div>

{ this.props.suffix && (
<div className="field-suffix">{ this.props.suffix }</div>
) }
</div>

</div>
);
}
}

TagsField.propTypes = propTypes;
TagsField.defaultProps = defaultProps;

export default TagsField;
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Slider from './components/Slider';
import Switch from './components/Switch';
import DateField from './components/DateField';
import Button from './components/Button';
import TagsField from './components/TagsField';

import './theme/index.styl';

Expand All @@ -16,4 +17,5 @@ export {
Switch,
DateField,
Button,
TagsField,
};
Loading

0 comments on commit 7b24965

Please sign in to comment.