]> Untitled Git - lemmy-ui.git/blob - src/shared/components/common/password-input.tsx
Add show/hide button to password fields (#1861)
[lemmy-ui.git] / src / shared / components / common / password-input.tsx
1 import { Options, passwordStrength } from "check-password-strength";
2 import classNames from "classnames";
3 import { NoOptionI18nKeys } from "i18next";
4 import { Component, FormEventHandler, linkEvent } from "inferno";
5 import { NavLink } from "inferno-router";
6 import { I18NextService } from "../../services";
7 import { Icon } from "./icon";
8
9 interface PasswordInputProps {
10   id: string;
11   value?: string;
12   onInput: FormEventHandler<HTMLInputElement>;
13   className?: string;
14   showStrength?: boolean;
15   label?: string | null;
16   showForgotLink?: boolean;
17 }
18
19 interface PasswordInputState {
20   show: boolean;
21 }
22
23 const passwordStrengthOptions: Options<string> = [
24   {
25     id: 0,
26     value: "very_weak",
27     minDiversity: 0,
28     minLength: 0,
29   },
30   {
31     id: 1,
32     value: "weak",
33     minDiversity: 2,
34     minLength: 10,
35   },
36   {
37     id: 2,
38     value: "medium",
39     minDiversity: 3,
40     minLength: 12,
41   },
42   {
43     id: 3,
44     value: "strong",
45     minDiversity: 4,
46     minLength: 14,
47   },
48 ];
49
50 function handleToggleShow(i: PasswordInput) {
51   i.setState(prev => ({
52     ...prev,
53     show: !prev.show,
54   }));
55 }
56
57 class PasswordInput extends Component<PasswordInputProps, PasswordInputState> {
58   state: PasswordInputState = {
59     show: false,
60   };
61
62   constructor(props: PasswordInputProps, context: any) {
63     super(props, context);
64   }
65
66   render() {
67     const {
68       props: {
69         id,
70         value,
71         onInput,
72         className,
73         showStrength,
74         label,
75         showForgotLink,
76       },
77       state: { show },
78     } = this;
79
80     return (
81       <>
82         <div className={classNames("row", className)}>
83           {label && (
84             <label className="col-sm-2 col-form-label" htmlFor={id}>
85               {label}
86             </label>
87           )}
88           <div className={`col-sm-${label ? 10 : 12}`}>
89             <div className="input-group">
90               <input
91                 type={show ? "text" : "password"}
92                 className="form-control"
93                 aria-describedby={id}
94                 autoComplete="on"
95                 onInput={onInput}
96                 value={value}
97                 required
98                 maxLength={60}
99                 minLength={10}
100               />
101               <button
102                 className="btn btn-outline-dark"
103                 type="button"
104                 id={id}
105                 onClick={linkEvent(this, handleToggleShow)}
106                 aria-label={I18NextService.i18n.t(
107                   `${show ? "show" : "hide"}_password`
108                 )}
109                 data-tippy-content={I18NextService.i18n.t(
110                   `${show ? "show" : "hide"}_password`
111                 )}
112               >
113                 <Icon icon={`eye${show ? "-slash" : ""}`} inline />
114               </button>
115             </div>
116             {showStrength && value && (
117               <div className={this.passwordColorClass}>
118                 {I18NextService.i18n.t(
119                   this.passwordStrength as NoOptionI18nKeys
120                 )}
121               </div>
122             )}
123             {showForgotLink && (
124               <NavLink
125                 className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold pointer-events not-allowed"
126                 to="/login_reset"
127               >
128                 {I18NextService.i18n.t("forgot_password")}
129               </NavLink>
130             )}
131           </div>
132         </div>
133       </>
134     );
135   }
136
137   get passwordStrength(): string | undefined {
138     const password = this.props.value;
139     return password
140       ? passwordStrength(password, passwordStrengthOptions).value
141       : undefined;
142   }
143
144   get passwordColorClass(): string {
145     const strength = this.passwordStrength;
146
147     if (strength && ["weak", "medium"].includes(strength)) {
148       return "text-warning";
149     } else if (strength == "strong") {
150       return "text-success";
151     } else {
152       return "text-danger";
153     }
154   }
155 }
156
157 export default PasswordInput;