Loëck Vézien
7 years ago
6 changed files with 174 additions and 1 deletions
@ -0,0 +1,118 @@ |
|||
// @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, maxLength } = this.props |
|||
const { passwordStrength, inputType } = this.state |
|||
|
|||
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> |
|||
{[1, 2, 3, 4].map(v => ( |
|||
<Strength key={v} warning={passwordStrength <= 1} activated={passwordStrength >= v} /> |
|||
))} |
|||
</Box> |
|||
{passwordStrength > 0 && ( |
|||
<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,4 @@ |
|||
warning_1: Warning 1 |
|||
warning_2: Warning 2 |
|||
warning_3: Warning 3 |
|||
warning_4: Warning 4 |
Loading…
Reference in new issue