/ common / components / TogglablePassword.tsx
TogglablePassword.tsx
  1  // Either self contained, or controlled component for having a password field
  2  // with a toggle to turn it into a visible text field.
  3  // Pass `isVisible` and `handleToggleVisibility` to control the visibility
  4  // yourself, otherwise all visibiility changes are managed in internal state.
  5  import React from 'react';
  6  
  7  import { Input, TextArea } from 'components/ui';
  8  import './TogglablePassword.scss';
  9  
 10  interface Props {
 11    // Shared props
 12    className?: string;
 13    value: string;
 14    placeholder?: string;
 15    name?: string;
 16    disabled?: boolean;
 17    ariaLabel?: string;
 18    toggleAriaLabel?: string;
 19    isValid?: boolean;
 20    isVisible?: boolean;
 21    readOnly?: boolean;
 22  
 23    // Textarea-only props
 24    isTextareaWhenVisible?: boolean;
 25    rows?: number;
 26    onEnter?(): void;
 27  
 28    // Shared callbacks
 29    onChange?(ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>): void;
 30    onFocus?(ev: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>): void;
 31    onBlur?(ev: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>): void;
 32    handleToggleVisibility?(): void;
 33  }
 34  
 35  interface State {
 36    isVisible: boolean;
 37  }
 38  
 39  export default class TogglablePassword extends React.PureComponent<Props, State> {
 40    public state: State = {
 41      isVisible: !!this.props.isVisible
 42    };
 43  
 44    public UNSAFE_componentWillReceiveProps(nextProps: Props) {
 45      if (this.props.isVisible !== nextProps.isVisible) {
 46        this.setState({ isVisible: !!nextProps.isVisible });
 47      }
 48    }
 49  
 50    public render() {
 51      const {
 52        className,
 53        value,
 54        placeholder,
 55        name,
 56        disabled,
 57        ariaLabel,
 58        toggleAriaLabel,
 59        isTextareaWhenVisible,
 60        isValid,
 61        onChange,
 62        onFocus,
 63        onBlur,
 64        handleToggleVisibility,
 65        readOnly
 66      } = this.props;
 67      const { isVisible } = this.state;
 68  
 69      return (
 70        <div className={`TogglablePassword input-group input-group-inline`}>
 71          {isTextareaWhenVisible && isVisible ? (
 72            <TextArea
 73              isValid={!!isValid}
 74              className={className}
 75              value={value}
 76              name={name}
 77              disabled={disabled}
 78              onChange={onChange}
 79              onKeyDown={this.handleTextareaKeyDown}
 80              onFocus={onFocus}
 81              onBlur={onBlur}
 82              placeholder={placeholder}
 83              rows={this.props.rows || 3}
 84              aria-label={ariaLabel}
 85              readOnly={readOnly}
 86            />
 87          ) : (
 88            <Input
 89              isValid={!!isValid}
 90              value={value}
 91              name={name}
 92              disabled={disabled}
 93              type={isVisible ? 'text' : 'password'}
 94              className={`${className} border-rad-right-0`}
 95              placeholder={placeholder}
 96              onChange={onChange}
 97              onFocus={onFocus}
 98              onBlur={onBlur}
 99              aria-label={ariaLabel}
100              readOnly={readOnly}
101            />
102          )}
103          <span
104            onClick={handleToggleVisibility || this.toggleVisibility}
105            aria-label={toggleAriaLabel}
106            role="button"
107            className="TogglablePassword-toggle input-group-addon"
108          >
109            <i className={`fa fa-${isVisible ? 'eye-slash' : 'eye'}`} />
110          </span>
111        </div>
112      );
113    }
114  
115    private toggleVisibility = () => {
116      this.setState({ isVisible: !this.state.isVisible });
117    };
118  
119    private handleTextareaKeyDown = (ev: React.KeyboardEvent<HTMLTextAreaElement>) => {
120      if (this.props.onEnter && ev.keyCode === 13) {
121        ev.preventDefault();
122        this.props.onEnter();
123      }
124    };
125  }