Loëck Vézien
7 years ago
committed by
GitHub
6 changed files with 181 additions and 1 deletions
@ -0,0 +1,124 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import styled from 'styled-components' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import zxcvbn from 'zxcvbn' |
||||
|
|
||||
|
import type { T } from 'types/common' |
||||
|
|
||||
|
import debounce from 'lodash/debounce' |
||||
|
import noop from 'lodash/noop' |
||||
|
|
||||
|
import Box from 'components/base/Box' |
||||
|
import Input from 'components/base/Input' |
||||
|
|
||||
|
import IconEye from 'icons/Eye' |
||||
|
|
||||
|
const InputRight = styled(Box).attrs({ |
||||
|
color: 'grey', |
||||
|
justifyContent: 'center', |
||||
|
pr: 3, |
||||
|
})` |
||||
|
cursor: pointer; |
||||
|
` |
||||
|
|
||||
|
const Strength = styled(Box).attrs({ |
||||
|
bg: p => (p.activated ? (p.warning ? 'alertRed' : 'positiveGreen') : 'fog'), |
||||
|
grow: true, |
||||
|
})` |
||||
|
border-radius: 13px; |
||||
|
height: 4px; |
||||
|
` |
||||
|
|
||||
|
const Warning = styled(Box).attrs({ |
||||
|
alignItems: 'flex-end', |
||||
|
color: p => (p.passwordStrength <= 1 ? 'alertRed' : 'positiveGreen'), |
||||
|
ff: 'Open Sans|SemiBold', |
||||
|
fontSize: 3, |
||||
|
})`` |
||||
|
|
||||
|
const getPasswordStrength = (v: string) => zxcvbn(v).score |
||||
|
|
||||
|
type State = { |
||||
|
inputType: 'text' | 'password', |
||||
|
passwordStrength: number, |
||||
|
} |
||||
|
|
||||
|
type Props = { |
||||
|
maxLength: number, |
||||
|
onChange: Function, |
||||
|
t: T, |
||||
|
value: string, |
||||
|
} |
||||
|
|
||||
|
class InputPassword extends PureComponent<Props, State> { |
||||
|
static defaultProps = { |
||||
|
onChange: noop, |
||||
|
value: '', |
||||
|
maxLength: 20, |
||||
|
} |
||||
|
|
||||
|
state = { |
||||
|
passwordStrength: getPasswordStrength(this.props.value), |
||||
|
inputType: 'password', |
||||
|
} |
||||
|
|
||||
|
toggleInputType = () => |
||||
|
this.setState(prev => ({ |
||||
|
inputType: prev.inputType === 'text' ? 'password' : 'text', |
||||
|
})) |
||||
|
|
||||
|
debouncePasswordStrength = debounce( |
||||
|
v => |
||||
|
this.setState({ |
||||
|
passwordStrength: getPasswordStrength(v), |
||||
|
}), |
||||
|
150, |
||||
|
) |
||||
|
|
||||
|
handleChange = (v: string) => { |
||||
|
const { onChange } = this.props |
||||
|
onChange(v) |
||||
|
this.debouncePasswordStrength(v) |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { t, value, maxLength } = this.props |
||||
|
const { passwordStrength, inputType } = this.state |
||||
|
|
||||
|
const hasValue = value.trim() !== '' |
||||
|
|
||||
|
return ( |
||||
|
<Box flow={1}> |
||||
|
<Input |
||||
|
{...this.props} |
||||
|
type={inputType} |
||||
|
maxLength={maxLength} |
||||
|
onChange={this.handleChange} |
||||
|
renderRight={ |
||||
|
<InputRight onClick={this.toggleInputType}> |
||||
|
<IconEye size={16} /> |
||||
|
</InputRight> |
||||
|
} |
||||
|
/> |
||||
|
<Box flow={1} horizontal> |
||||
|
{[0, 1, 2, 3, 4].map(v => ( |
||||
|
<Strength |
||||
|
key={v} |
||||
|
warning={passwordStrength <= 1} |
||||
|
activated={hasValue && passwordStrength >= v} |
||||
|
/> |
||||
|
))} |
||||
|
</Box> |
||||
|
{hasValue && ( |
||||
|
<Warning passwordStrength={passwordStrength}> |
||||
|
{t(`password:warning_${passwordStrength}`)} |
||||
|
</Warning> |
||||
|
)} |
||||
|
</Box> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()(InputPassword) |
@ -0,0 +1,34 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Component } from 'react' |
||||
|
import { storiesOf } from '@storybook/react' |
||||
|
import { action } from '@storybook/addon-actions' |
||||
|
|
||||
|
import InputPassword from 'components/base/InputPassword' |
||||
|
|
||||
|
const stories = storiesOf('Components', module) |
||||
|
|
||||
|
class Wrapper extends Component<any, any> { |
||||
|
state = { |
||||
|
value: 'p@ssw0rd!', |
||||
|
} |
||||
|
|
||||
|
handleChange = value => { |
||||
|
action('onChange')(value) |
||||
|
this.setState({ value }) |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { render } = this.props |
||||
|
const { value } = this.state |
||||
|
|
||||
|
return render({ |
||||
|
onChange: this.handleChange, |
||||
|
value, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
stories.add('InputPassword', () => ( |
||||
|
<Wrapper render={({ value, onChange }) => <InputPassword value={value} onChange={onChange} />} /> |
||||
|
)) |
@ -0,0 +1,12 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React from 'react' |
||||
|
|
||||
|
export default ({ size, ...p }: { size: number }) => ( |
||||
|
<svg viewBox="0 0 16 16" height={size} width={size} {...p}> |
||||
|
<path |
||||
|
fill="currentColor" |
||||
|
d="M2.502 8.393c.335.494.731.99 1.184 1.45C4.953 11.128 6.399 11.888 8 11.888s3.047-.76 4.314-2.047A10.368 10.368 0 0 0 13.751 8a10.368 10.368 0 0 0-1.437-1.842C11.047 4.87 9.601 4.11 8 4.11s-3.047.76-4.314 2.047A10.368 10.368 0 0 0 2.249 8c.073.12.158.253.253.393zm-1.44-.641a8.35 8.35 0 0 1 .46-.748c.37-.547.809-1.094 1.313-1.606C4.302 3.907 6.028 3 8 3s3.698.907 5.165 2.398c.504.512.942 1.059 1.313 1.606.225.33.378.591.46.748a.532.532 0 0 1 0 .496 8.35 8.35 0 0 1-.46.748 11.477 11.477 0 0 1-1.313 1.606C11.698 12.093 9.972 13 8 13s-3.698-.907-5.165-2.398a11.477 11.477 0 0 1-1.313-1.606 8.35 8.35 0 0 1-.46-.748.532.532 0 0 1 0-.496zM8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0-1a1 1 0 1 1 0-2 1 1 0 0 1 0 2z" |
||||
|
/> |
||||
|
</svg> |
||||
|
) |
@ -0,0 +1,5 @@ |
|||||
|
warning_0: Warning 0 |
||||
|
warning_1: Warning 1 |
||||
|
warning_2: Warning 2 |
||||
|
warning_3: Warning 3 |
||||
|
warning_4: Warning 4 |
Loading…
Reference in new issue