Skip to content

Instantly share code, notes, and snippets.

@markcheno
Created September 13, 2018 02:51
Show Gist options
  • Save markcheno/b4798dc3560e34d64cc81b1b094f7787 to your computer and use it in GitHub Desktop.
Save markcheno/b4798dc3560e34d64cc81b1b094f7787 to your computer and use it in GitHub Desktop.
React Chips component
%h2#title React Chips!
#react-mount
const update = React.addons.update;
const Chips = React.createClass({
propTypes: {
chips: React.PropTypes.array,
max: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.string
]),
maxlength: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.string
]),
placeholder: React.PropTypes.string
},
getDefaultProps() {
return {
placeholder: 'Add a chip...',
maxlength: 20
};
},
getInitialState() {
return {
chips: [],
KEY: {
backspace: 8,
tab: 9,
enter: 13
},
// only allow letters, numbers and spaces inbetween words
INVALID_CHARS: /[^a-zA-Z0-9 ]/g
};
},
componentDidMount() {
this.setChips(this.props.chips);
},
componentWillReceiveProps(nextProps) {
this.setChips(nextProps.chips);
},
setChips(chips) {
if (chips && chips.length) this.setState({ chips });
},
onKeyDown(event) {
let keyPressed = event.which;
if (keyPressed === this.state.KEY.enter ||
(keyPressed === this.state.KEY.tab && event.target.value)) {
event.preventDefault();
this.updateChips(event);
} else if (keyPressed === this.state.KEY.backspace) {
let chips = this.state.chips;
if (!event.target.value && chips.length) {
this.deleteChip(chips[chips.length - 1]);
}
}
},
clearInvalidChars(event) {
let value = event.target.value;
if (this.state.INVALID_CHARS.test(value)) {
event.target.value = value.replace(this.state.INVALID_CHARS, '');
} else if (value.length > this.props.maxlength) {
event.target.value = value.substr(0, this.props.maxlength);
}
},
updateChips(event) {
if (!this.props.max ||
this.state.chips.length < this.props.max) {
let value = event.target.value;
if (!value) return;
let chip = value.trim().toLowerCase();
if (chip && this.state.chips.indexOf(chip) < 0) {
this.setState({
chips: update(
this.state.chips,
{
$push: [chip]
}
)
});
}
}
event.target.value = '';
},
deleteChip(chip) {
let index = this.state.chips.indexOf(chip);
if (index >= 0) {
this.setState({
chips: update(
this.state.chips,
{
$splice: [[index, 1]]
}
)
});
}
},
focusInput(event) {
let children = event.target.children;
if (children.length) children[children.length - 1].focus();
},
render() {
let chips = this.state.chips.map((chip, index) => {
return (
<span className="chip" key={index}>
<span className="chip-value">{chip}</span>
<button type="button" className="chip-delete-button" onClick={this.deleteChip.bind(null, chip)}>x</button>
</span>
);
});
let placeholder = !this.props.max || chips.length < this.props.max ? this.props.placeholder : '';
return (
<div className="chips" onClick={this.focusInput}>
{chips}
<input type="text" className="chips-input" placeholder={placeholder} onKeyDown={this.onKeyDown} onKeyUp={this.clearInvalidChars} />
</div>
);
}
});
ReactDOM.render(
<Chips chips={['react', 'javascript', 'scss']} placeholder="Add a tag..." max="10" />,
document.getElementById('react-mount')
);
<script src="//cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-with-addons.min.js"></script>
$chip-y-spacing: 15px;
$chip-x-spacing: 5px;
$chip-button-width: $chip-y-spacing + $chip-x-spacing * 2;
$chip-border-radius: 15px;
$chip-background: #555;
$chip-color: #fff;
$chip-min-height: 36px;
body {
font-family: Arial, sans-serif;
}
#title {
letter-spacing: 1px;
}
.chips {
min-height: $chip-min-height;
border-bottom: 2px solid blue;
line-height: 1;
font-size: 1em;
}
.chips-input {
display: inline-block;
width: 33%;
min-height: $chip-min-height;
margin-bottom: $chip-x-spacing;
margin-left: $chip-x-spacing * 2;
border: 0;
outline: none;
font-size: 0.9rem;
}
.chip {
display: inline-block;
margin-top: $chip-x-spacing;
margin-bottom: $chip-x-spacing;
margin-left: $chip-x-spacing;
margin-right: $chip-button-width;
position: relative;
.chip-value {
display: inline-block;
padding: $chip-x-spacing;
padding-left: $chip-y-spacing;
padding-right: $chip-y-spacing / 2;
background: $chip-background;
color: $chip-color;
font-weight: bold;
border-radius: $chip-border-radius 0 0 $chip-border-radius;
}
.chip-delete-button {
background: $chip-background;
color: $chip-color;
border: 0;
border-radius: 0 $chip-border-radius $chip-border-radius 0;
padding: $chip-x-spacing $chip-x-spacing * 2;
cursor: pointer;
position: absolute;
top: 0;
bottom: 0;
right: -$chip-button-width;
line-height: 0.5;
font-weight: bold;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment