UNPKG

react-rails-ux-password-field

Version:

A UX forward password field for react-js using react-rails

292 lines (239 loc) 7.29 kB
'use strict'; var React = require('react'), RP = React.PropTypes, config = require('./js/config'), debounce = require('lodash.debounce'); var InputPassword = React.createClass({ /*========== VALIDATE ==========*/ propTypes: { infoBar: RP.bool, statusColor: RP.string, statusInactiveColor: RP.string, minScore: RP.number, changeCb: RP.func, toggleMask: RP.bool, unMaskTime: RP.number, minLength: RP.number, strengthLang:RP.array, id: RP.string, }, /*========== DEFAULTS ==========*/ getDefaultProps() { return { infoBar: true, statusColor: config.statusColor, statusInactiveColor: config.statusInactiveColor, zxcvbn: config.zxcvbnSrc, minScore: 0, toggleMask: true, unMaskTime: config.unMaskTime, strengthLang: config.strengthLang, id: 'input', name: '', className: 'form-control', placeholder: 'Password', required: true }; }, getInitialState() { return { value: '', score: 0, entropy: 0, isPassword: true, isValid: false } }, /*========== STYLES ==========*/ getMeterStyle(score) { var width = this.state.value === '' ? 0 : 24 * score + 4; return { width: this.props.zxcvbn ? width + '%' : '100%', maxWidth: '85%', opacity: this.props.zxcvbn ? width * .01 + .5 : '1', background: this.state.isValid ? this.props.statusColor : this.props.statusInactiveColor, height: 3, transition: 'all 400ms linear', display: 'inline-block', marginRight: '1%', marginLeft: '5px' }; }, unMaskStyle: { color: config.unMaskColor }, infoStyle: { width: '100%', overflow: 'hidden', height: '20px' }, iconStyle: { display: 'inline-block', opacity: .25, position: 'relative', top: 2, width: '3%' }, strengthLangStyle: { fontSize: 12, position: 'relative', top: 2, }, /*========== METHODS ==========*/ addPasswordType() { this.setState({ isPassword: true }); }, /*========== HANDLERS ==========*/ handleInputType() { this.setState({ isPassword: !this.state.isPassword }); }, handleChange(e) { e.preventDefault(); var native_target = e.nativeEvent.target; var val = e.target.value; var score; this.setState({ value: val, isValid: e.target.validity.valid, selectionStart : native_target.selectionStart, selectionEnd : native_target.selectionEnd, }); if (this.props.toggleMask) { this.handleToggleMask(); } if (this.props.zxcvbn) { score = this.handleZxcvbn(val); } else { score = this.state.score; } // call onChange prop passed from parent if (this.props.onChange) { this.props.onChange(val, this.state.isValid, score); } if (this.props.minLength) { this.handleMinLength(e.target.value.length) } }, handleToggleMask() { // display password, then this.setState({ isPassword: false }); // debounce remasking password this.maskPassword(); }, handleToggleMaskIcon() { this.setState({ isPassword: !this.state.isPassword }); }, handleZxcvbn(val) { if (typeof zxcvbn === 'undefined' && typeof window.zxcvbn === 'undefined') { return; } var stats = zxcvbn(val), currentScore = stats.score; this.setState({ score: currentScore, entropy: stats.entropy }); if (currentScore < this.props.minScore) { this.setState({ isValid: false }); } // if score changed and callback provided if (this.props.changeCb && this.state.score !== currentScore) { this.props.changeCb(this.state.score, currentScore, val) } if (this.props.zxcvbn === 'debug') { console.debug(stats); } return currentScore; }, handleMinLength(len) { if (len <= this.props.minLength) { this.setState({ isValid: false }) } }, componentWillMount() { var zxcvbnSrc; // Load zxcvbn async if its enabled and doesn't already exist if (this.props.zxcvbn && typeof zxcvbn === 'undefined') { zxcvbnSrc = this.props.zxcvbn !== 'debug' ? this.props.zxcvbn : config.zxcvbnSrc; // snippet to async load zxcvbn if enabled (function(){var a;a=function(){var a,b;b=document.createElement("script");b.src=zxcvbnSrc;b.type="text/javascript";b.async=!0;a=document.getElementsByTagName("head")[0];return a.parentNode.insertBefore(b,a)};null!=window.attachEvent?window.attachEvent("onload",a):window.addEventListener("load",a,!1)}).call(this); } // set debouncer for password // if (this.props.toggleMask) { this.maskPassword = debounce(this.addPasswordType, this.props.unMaskTime); // } }, componentWillUnmount() { // cancel the debouncer when component is not used anymore. This to avoid // setting the state unnecessarily, see issue #24 if (this.maskPassword) { this.maskPassword.cancel() } }, render() { var infoBar; if (this.props.infoBar) { infoBar = <div className="passwordField__info" style={this.infoStyle}> <span style={this.iconStyle} className="passwordField__icon"> <img src={require('./img/lock.png')} height="10" width="10" /> </span> <span style={this.getMeterStyle(this.state.score)} className="passwordField__meter" /> <span style={this.strengthLangStyle} className="passwordField__strength"> {this.props.zxcvbn && this.state.value.length > 0 && this.props.strengthLang.length > 0 ? this.props.strengthLang[this.state.score] : null} </span> </div> } // allow onChange to be passed from parent and not override default prop var {onChange, ...props} = this.props; // overcome problem with firefox resetting the input selection point var that = this; setTimeout(function() { if (!/Firefox/.test(navigator.userAgent)) return; var elem = that.refs[that.props.id].getDOMNode(); elem.selectionStart = that.state.selectionStart; elem.selectionEnd = that.state.selectionEnd; }, 1); return ( <div style={{position: 'relative', display: 'block'}} className="passwordField" data-valid={this.state.isValid} data-score={this.state.score} data-entropy={this.state.entropy} > <input ref={this.props.id} className={this.props.className} type={this.state.isPassword ? 'password' : 'text'} name={this.props.name} value={this.state.value} style={this.state.isPassword ? null : this.unMaskStyle} onChange={this.handleChange} placeholder={this.props.placeholder} {...props} /> <img src={require('./img/eye.png')} className={this.state.isPassword ? 'passwordField__mask-icon' : 'active passwordField__mask-icon'} onClick={this.handleToggleMaskIcon} /> {this.state.value.length > 0 ? <div>{infoBar}</div> : null } </div> ); } }); module.exports = InputPassword;