1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-19 05:09:43 +02:00

Add visibility toggle of password input. Closes #2

This commit is contained in:
Maksim Eltyshev 2019-11-15 03:45:59 +05:00
parent 7a3805e64c
commit 390d96cc19
25 changed files with 111 additions and 92 deletions

View file

@ -6161,9 +6161,9 @@
"integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==" "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ=="
}, },
"handlebars": { "handlebars": {
"version": "4.5.1", "version": "4.5.2",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.2.tgz",
"integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==", "integrity": "sha512-29Zxv/cynYB7mkT1rVWQnV7mGX6v7H/miQ6dbEpYTKq5eJBN7PsRB+ViYJlcT6JINTSu4dVB9kOqEun78h6Exg==",
"requires": { "requires": {
"neo-async": "^2.6.0", "neo-async": "^2.6.0",
"optimist": "^0.6.1", "optimist": "^0.6.1",
@ -10728,9 +10728,9 @@
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
}, },
"prettier": { "prettier": {
"version": "1.18.2", "version": "1.19.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
"integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
"dev": true "dev": true
}, },
"prettier-eslint": { "prettier-eslint": {
@ -10788,9 +10788,9 @@
} }
}, },
"core-js": { "core-js": {
"version": "3.3.6", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.3.6.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.1.tgz",
"integrity": "sha512-u4oM8SHwmDuh5mWZdDg9UwNVq5s1uqq6ZDLLIs07VY+VJU91i3h4f3K/pgFvtUQPGdeStrZ+odKyfyt4EnKHfA==", "integrity": "sha512-KX/dnuY/J8FtEwbnrzmAjUYgLqtk+cxM86hfG60LGiW3MmltIc2yAmDgBgEkfm0blZhUrdr1Zd84J2Y14mLxzg==",
"dev": true "dev": true
}, },
"eslint-scope": { "eslint-scope": {
@ -13730,9 +13730,9 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
}, },
"typescript": { "typescript": {
"version": "3.6.4", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz",
"integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==",
"dev": true "dev": true
}, },
"typescript-compare": { "typescript-compare": {

View file

@ -71,6 +71,7 @@
"eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.16.0", "eslint-plugin-react": "^7.16.0",
"eslint-plugin-react-hooks": "^1.7.0", "eslint-plugin-react-hooks": "^1.7.0",
"prettier-eslint": "^9.0.0",
"prettier-eslint-cli": "^5.0.0" "prettier-eslint-cli": "^5.0.0"
} }
} }

View file

@ -5,10 +5,11 @@ import React, {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Form, Message } from 'semantic-ui-react'; import { Button, Form, Message } from 'semantic-ui-react';
import { usePrevious } from '../../lib/hooks';
import { withPopup } from '../../lib/popup'; import { withPopup } from '../../lib/popup';
import { Input, Popup } from '../../lib/custom-ui'; import { Input, Popup } from '../../lib/custom-ui';
import { useForm, usePrevious } from '../../hooks'; import { useForm } from '../../hooks';
import styles from './AddUserPopup.module.css'; import styles from './AddUserPopup.module.css';
@ -120,7 +121,7 @@ const AddUserPopup = React.memo(
onChange={handleFieldChange} onChange={handleFieldChange}
/> />
<div className={styles.text}>{t('common.password')}</div> <div className={styles.text}>{t('common.password')}</div>
<Input <Input.Password
fluid fluid
ref={passwordField} ref={passwordField}
name="password" name="password"

View file

@ -4,10 +4,9 @@ import React, {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Form, Input } from 'semantic-ui-react'; import { Button, Form, Input } from 'semantic-ui-react';
import { useDidUpdate, useToggle } from '../../lib/hooks';
import { import { useClosableForm, useForm } from '../../hooks';
useClosableForm, useDidUpdate, useForm, useToggle,
} from '../../hooks';
import styles from './AddList.module.css'; import styles from './AddList.module.css';

View file

@ -2,8 +2,7 @@ import React, { useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { Progress } from 'semantic-ui-react'; import { Progress } from 'semantic-ui-react';
import { useToggle } from '../../lib/hooks';
import { useToggle } from '../../hooks';
import styles from './Tasks.module.css'; import styles from './Tasks.module.css';

View file

@ -2,8 +2,9 @@ import React, { useCallback, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import { TextArea } from 'semantic-ui-react'; import { TextArea } from 'semantic-ui-react';
import { useDidUpdate, usePrevious } from '../../lib/hooks';
import { useDidUpdate, useField, usePrevious } from '../../hooks'; import { useField } from '../../hooks';
import styles from './NameField.module.css'; import styles from './NameField.module.css';

View file

@ -5,10 +5,9 @@ import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react'; import { Button, Form, TextArea } from 'semantic-ui-react';
import { useDidUpdate, useToggle } from '../../../lib/hooks';
import { import { useClosableForm, useForm } from '../../../hooks';
useClosableForm, useDidUpdate, useForm, useToggle,
} from '../../../hooks';
import styles from './Add.module.css'; import styles from './Add.module.css';

View file

@ -5,9 +5,10 @@ import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import DatePicker from 'react-datepicker'; import DatePicker from 'react-datepicker';
import { Button, Form } from 'semantic-ui-react'; import { Button, Form } from 'semantic-ui-react';
import { useDidUpdate, useToggle } from '../../lib/hooks';
import { Input, Popup } from '../../lib/custom-ui'; import { Input, Popup } from '../../lib/custom-ui';
import { useDidUpdate, useForm, useToggle } from '../../hooks'; import { useForm } from '../../hooks';
import styles from './EditDueDateStep.module.css'; import styles from './EditDueDateStep.module.css';

View file

@ -3,9 +3,10 @@ import React, { useCallback, useEffect, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Form } from 'semantic-ui-react'; import { Button, Form } from 'semantic-ui-react';
import { useToggle } from '../../lib/hooks';
import { Input, Popup } from '../../lib/custom-ui'; import { Input, Popup } from '../../lib/custom-ui';
import { useForm, useToggle } from '../../hooks'; import { useForm } from '../../hooks';
import { import {
createTimer, getTimerParts, startTimer, stopTimer, updateTimer, createTimer, getTimerParts, startTimer, stopTimer, updateTimer,
} from '../../utils/timer'; } from '../../utils/timer';
@ -114,7 +115,7 @@ const EditTimerStep = React.memo(({
<div className={styles.fieldWrapper}> <div className={styles.fieldWrapper}>
<div className={styles.fieldBox}> <div className={styles.fieldBox}>
<div className={styles.text}>{t('common.hours')}</div> <div className={styles.text}>{t('common.hours')}</div>
<Input <Input.Mask
ref={hoursField} ref={hoursField}
name="hours" name="hours"
value={data.hours} value={data.hours}
@ -126,7 +127,7 @@ const EditTimerStep = React.memo(({
</div> </div>
<div className={styles.fieldBox}> <div className={styles.fieldBox}>
<div className={styles.text}>{t('common.minutes')}</div> <div className={styles.text}>{t('common.minutes')}</div>
<Input <Input.Mask
ref={minutesField} ref={minutesField}
name="minutes" name="minutes"
value={data.minutes} value={data.minutes}
@ -138,7 +139,7 @@ const EditTimerStep = React.memo(({
</div> </div>
<div className={styles.fieldBox}> <div className={styles.fieldBox}>
<div className={styles.text}>{t('common.seconds')}</div> <div className={styles.text}>{t('common.seconds')}</div>
<Input <Input.Mask
ref={secondsField} ref={secondsField}
name="seconds" name="seconds"
value={data.seconds} value={data.seconds}

View file

@ -5,10 +5,9 @@ import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react'; import { Button, Form, TextArea } from 'semantic-ui-react';
import { useDidUpdate, useToggle } from '../../lib/hooks';
import { import { useClosableForm, useForm } from '../../hooks';
useClosableForm, useDidUpdate, useForm, useToggle,
} from '../../hooks';
import styles from './AddCard.module.css'; import styles from './AddCard.module.css';

View file

@ -8,11 +8,10 @@ import isEmail from 'validator/lib/isEmail';
import { import {
Form, Grid, Header, Message, Form, Grid, Header, Message,
} from 'semantic-ui-react'; } from 'semantic-ui-react';
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
import { Input } from '../../lib/custom-ui'; import { Input } from '../../lib/custom-ui';
import { import { useForm } from '../../hooks';
useDidUpdate, useForm, usePrevious, useToggle,
} from '../../hooks';
import styles from './Login.module.css'; import styles from './Login.module.css';
@ -156,10 +155,9 @@ const Login = React.memo(
</div> </div>
<div className={styles.inputWrapper}> <div className={styles.inputWrapper}>
<div className={styles.inputLabel}>{t('common.password')}</div> <div className={styles.inputLabel}>{t('common.password')}</div>
<Input <Input.Password
fluid fluid
ref={passwordField} ref={passwordField}
type="password"
name="password" name="password"
value={data.password} value={data.password}
readOnly={isSubmitting} readOnly={isSubmitting}

View file

@ -1,8 +1,8 @@
import React, { useCallback, useEffect, useRef } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { useForceUpdate, usePrevious } from '../../lib/hooks';
import { useForceUpdate, usePrevious } from '../../hooks';
import { formatTimer } from '../../utils/timer'; import { formatTimer } from '../../utils/timer';
import styles from './Timer.module.css'; import styles from './Timer.module.css';

View file

@ -5,11 +5,10 @@ import React, {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Form, Message } from 'semantic-ui-react'; import { Button, Form, Message } from 'semantic-ui-react';
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
import { Input, Popup } from '../../lib/custom-ui'; import { Input, Popup } from '../../lib/custom-ui';
import { import { useForm } from '../../hooks';
useDidUpdate, useForm, usePrevious, useToggle,
} from '../../hooks';
import styles from './EditNameStep.module.css'; import styles from './EditNameStep.module.css';
@ -145,10 +144,9 @@ const EditEmailStep = React.memo(
{data.email.trim() !== email && ( {data.email.trim() !== email && (
<> <>
<div className={styles.text}>{t('common.currentPassword')}</div> <div className={styles.text}>{t('common.currentPassword')}</div>
<Input <Input.Password
fluid fluid
ref={currentPasswordField} ref={currentPasswordField}
type="password"
name="currentPassword" name="currentPassword"
value={data.currentPassword} value={data.currentPassword}
className={styles.field} className={styles.field}

View file

@ -4,11 +4,10 @@ import React, {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Form, Message } from 'semantic-ui-react'; import { Button, Form, Message } from 'semantic-ui-react';
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
import { Input, Popup } from '../../lib/custom-ui'; import { Input, Popup } from '../../lib/custom-ui';
import { import { useForm } from '../../hooks';
useDidUpdate, useForm, usePrevious, useToggle,
} from '../../hooks';
import styles from './EditNameStep.module.css'; import styles from './EditNameStep.module.css';
@ -107,7 +106,7 @@ const EditPasswordStep = React.memo(
)} )}
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<div className={styles.text}>{t('common.newPassword')}</div> <div className={styles.text}>{t('common.newPassword')}</div>
<Input <Input.Password
fluid fluid
ref={passwordField} ref={passwordField}
name="password" name="password"
@ -116,10 +115,9 @@ const EditPasswordStep = React.memo(
onChange={handleFieldChange} onChange={handleFieldChange}
/> />
<div className={styles.text}>{t('common.currentPassword')}</div> <div className={styles.text}>{t('common.currentPassword')}</div>
<Input <Input.Password
fluid fluid
ref={currentPasswordField} ref={currentPasswordField}
type="password"
name="currentPassword" name="currentPassword"
value={data.currentPassword} value={data.currentPassword}
className={styles.field} className={styles.field}

View file

@ -1,19 +1,8 @@
import usePrevious from './use-previous';
import useField from './use-field'; import useField from './use-field';
import useForm from './use-form'; import useForm from './use-form';
import useSteps from './use-steps'; import useSteps from './use-steps';
import useToggle from './use-toggle';
import useForceUpdate from './use-force-update';
import useClosableForm from './use-closable-form'; import useClosableForm from './use-closable-form';
import useDidUpdate from './use-did-update';
export { export {
usePrevious, useField, useForm, useSteps, useClosableForm,
useField,
useForm,
useSteps,
useToggle,
useForceUpdate,
useClosableForm,
useDidUpdate,
}; };

View file

@ -1,28 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Input as SemanticUIInput } from 'semantic-ui-react'; import { Input as SemanticUIInput } from 'semantic-ui-react';
import MaskedInput from './MaskedInput'; import InputPassword from './InputPassword';
import InputMask from './InputMask';
const Input = React.forwardRef(({ mask, maskChar, ...props }, ref) => { const Input = SemanticUIInput;
const nextProps = props;
if (mask) { Input.Password = InputPassword;
nextProps.input = <MaskedInput mask={mask} maskChar={maskChar} />; Input.Mask = InputMask;
}
// eslint-disable-next-line react/jsx-props-no-spreading export default Input;
return <SemanticUIInput {...nextProps} ref={ref} />;
});
Input.propTypes = {
mask: PropTypes.string,
maskChar: PropTypes.string,
};
Input.defaultProps = {
mask: undefined,
maskChar: undefined,
};
export default React.memo(Input);

View file

@ -0,0 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Input } from 'semantic-ui-react';
import MaskedInput from './MaskedInput';
const InputMask = React.forwardRef(({ mask, maskChar, ...props }, ref) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<Input {...props} ref={ref} input={<MaskedInput mask={mask} maskChar={maskChar} />} />
));
InputMask.propTypes = {
mask: PropTypes.string.isRequired,
maskChar: PropTypes.string,
};
InputMask.defaultProps = {
maskChar: undefined,
};
export default React.memo(InputMask);

View file

@ -0,0 +1,22 @@
import React, { useCallback } from 'react';
import { Icon, Input } from 'semantic-ui-react';
import { useToggle } from '../../../hooks';
const InputPassword = React.forwardRef((props, ref) => {
const [isHidden, toggleHidden] = useToggle(true);
const handleToggleClick = useCallback(() => {
toggleHidden();
}, [toggleHidden]);
return (
<Input
{...props} // eslint-disable-line react/jsx-props-no-spreading
ref={ref}
type={isHidden ? 'password' : 'text'}
icon={<Icon link name={isHidden ? 'eye slash' : 'eye'} onClick={handleToggleClick} />}
/>
);
});
export default React.memo(InputPassword);

View file

@ -0,0 +1,8 @@
import usePrevious from './use-previous';
import useToggle from './use-toggle';
import useForceUpdate from './use-force-update';
import useDidUpdate from './use-did-update';
export {
usePrevious, useToggle, useForceUpdate, useDidUpdate,
};

View file

@ -5367,9 +5367,9 @@
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks="
}, },
"prettier": { "prettier": {
"version": "1.18.2", "version": "1.19.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
"integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
"dev": true "dev": true
}, },
"prettier-eslint": { "prettier-eslint": {
@ -5394,9 +5394,9 @@
}, },
"dependencies": { "dependencies": {
"core-js": { "core-js": {
"version": "3.3.6", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.3.6.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.1.tgz",
"integrity": "sha512-u4oM8SHwmDuh5mWZdDg9UwNVq5s1uqq6ZDLLIs07VY+VJU91i3h4f3K/pgFvtUQPGdeStrZ+odKyfyt4EnKHfA==", "integrity": "sha512-KX/dnuY/J8FtEwbnrzmAjUYgLqtk+cxM86hfG60LGiW3MmltIc2yAmDgBgEkfm0blZhUrdr1Zd84J2Y14mLxzg==",
"dev": true "dev": true
} }
} }
@ -7407,9 +7407,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "3.6.4", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz",
"integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==",
"dev": true "dev": true
}, },
"uid-safe": { "uid-safe": {

View file

@ -46,6 +46,7 @@
"eslint-config-airbnb-base": "^14.0.0", "eslint-config-airbnb-base": "^14.0.0",
"eslint-plugin-import": "^2.18.2", "eslint-plugin-import": "^2.18.2",
"nodemon": "^1.19.4", "nodemon": "^1.19.4",
"prettier-eslint": "^9.0.0",
"prettier-eslint-cli": "^5.0.0" "prettier-eslint-cli": "^5.0.0"
}, },
"engines": { "engines": {