mirror of
https://github.com/plankanban/planka.git
synced 2025-07-18 20:59:44 +02:00
Add visibility toggle of password input. Closes #2
This commit is contained in:
parent
7a3805e64c
commit
390d96cc19
25 changed files with 111 additions and 92 deletions
24
client/package-lock.json
generated
24
client/package-lock.json
generated
|
@ -6161,9 +6161,9 @@
|
|||
"integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ=="
|
||||
},
|
||||
"handlebars": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
|
||||
"integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.2.tgz",
|
||||
"integrity": "sha512-29Zxv/cynYB7mkT1rVWQnV7mGX6v7H/miQ6dbEpYTKq5eJBN7PsRB+ViYJlcT6JINTSu4dVB9kOqEun78h6Exg==",
|
||||
"requires": {
|
||||
"neo-async": "^2.6.0",
|
||||
"optimist": "^0.6.1",
|
||||
|
@ -10728,9 +10728,9 @@
|
|||
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
|
||||
},
|
||||
"prettier": {
|
||||
"version": "1.18.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
|
||||
"integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
|
||||
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier-eslint": {
|
||||
|
@ -10788,9 +10788,9 @@
|
|||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.3.6.tgz",
|
||||
"integrity": "sha512-u4oM8SHwmDuh5mWZdDg9UwNVq5s1uqq6ZDLLIs07VY+VJU91i3h4f3K/pgFvtUQPGdeStrZ+odKyfyt4EnKHfA==",
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.1.tgz",
|
||||
"integrity": "sha512-KX/dnuY/J8FtEwbnrzmAjUYgLqtk+cxM86hfG60LGiW3MmltIc2yAmDgBgEkfm0blZhUrdr1Zd84J2Y14mLxzg==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-scope": {
|
||||
|
@ -13730,9 +13730,9 @@
|
|||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.6.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz",
|
||||
"integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==",
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz",
|
||||
"integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==",
|
||||
"dev": true
|
||||
},
|
||||
"typescript-compare": {
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-react": "^7.16.0",
|
||||
"eslint-plugin-react-hooks": "^1.7.0",
|
||||
"prettier-eslint": "^9.0.0",
|
||||
"prettier-eslint-cli": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@ import React, {
|
|||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form, Message } from 'semantic-ui-react';
|
||||
import { usePrevious } from '../../lib/hooks';
|
||||
import { withPopup } from '../../lib/popup';
|
||||
import { Input, Popup } from '../../lib/custom-ui';
|
||||
|
||||
import { useForm, usePrevious } from '../../hooks';
|
||||
import { useForm } from '../../hooks';
|
||||
|
||||
import styles from './AddUserPopup.module.css';
|
||||
|
||||
|
@ -120,7 +121,7 @@ const AddUserPopup = React.memo(
|
|||
onChange={handleFieldChange}
|
||||
/>
|
||||
<div className={styles.text}>{t('common.password')}</div>
|
||||
<Input
|
||||
<Input.Password
|
||||
fluid
|
||||
ref={passwordField}
|
||||
name="password"
|
||||
|
|
|
@ -4,10 +4,9 @@ import React, {
|
|||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form, Input } from 'semantic-ui-react';
|
||||
import { useDidUpdate, useToggle } from '../../lib/hooks';
|
||||
|
||||
import {
|
||||
useClosableForm, useDidUpdate, useForm, useToggle,
|
||||
} from '../../hooks';
|
||||
import { useClosableForm, useForm } from '../../hooks';
|
||||
|
||||
import styles from './AddList.module.css';
|
||||
|
||||
|
|
|
@ -2,8 +2,7 @@ import React, { useCallback } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Progress } from 'semantic-ui-react';
|
||||
|
||||
import { useToggle } from '../../hooks';
|
||||
import { useToggle } from '../../lib/hooks';
|
||||
|
||||
import styles from './Tasks.module.css';
|
||||
|
||||
|
|
|
@ -2,8 +2,9 @@ import React, { useCallback, useRef } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
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';
|
||||
|
||||
|
|
|
@ -5,10 +5,9 @@ import PropTypes from 'prop-types';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
import { useDidUpdate, useToggle } from '../../../lib/hooks';
|
||||
|
||||
import {
|
||||
useClosableForm, useDidUpdate, useForm, useToggle,
|
||||
} from '../../../hooks';
|
||||
import { useClosableForm, useForm } from '../../../hooks';
|
||||
|
||||
import styles from './Add.module.css';
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@ import PropTypes from 'prop-types';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import { Button, Form } from 'semantic-ui-react';
|
||||
import { useDidUpdate, useToggle } from '../../lib/hooks';
|
||||
import { Input, Popup } from '../../lib/custom-ui';
|
||||
|
||||
import { useDidUpdate, useForm, useToggle } from '../../hooks';
|
||||
import { useForm } from '../../hooks';
|
||||
|
||||
import styles from './EditDueDateStep.module.css';
|
||||
|
||||
|
|
|
@ -3,9 +3,10 @@ import React, { useCallback, useEffect, useRef } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form } from 'semantic-ui-react';
|
||||
import { useToggle } from '../../lib/hooks';
|
||||
import { Input, Popup } from '../../lib/custom-ui';
|
||||
|
||||
import { useForm, useToggle } from '../../hooks';
|
||||
import { useForm } from '../../hooks';
|
||||
import {
|
||||
createTimer, getTimerParts, startTimer, stopTimer, updateTimer,
|
||||
} from '../../utils/timer';
|
||||
|
@ -114,7 +115,7 @@ const EditTimerStep = React.memo(({
|
|||
<div className={styles.fieldWrapper}>
|
||||
<div className={styles.fieldBox}>
|
||||
<div className={styles.text}>{t('common.hours')}</div>
|
||||
<Input
|
||||
<Input.Mask
|
||||
ref={hoursField}
|
||||
name="hours"
|
||||
value={data.hours}
|
||||
|
@ -126,7 +127,7 @@ const EditTimerStep = React.memo(({
|
|||
</div>
|
||||
<div className={styles.fieldBox}>
|
||||
<div className={styles.text}>{t('common.minutes')}</div>
|
||||
<Input
|
||||
<Input.Mask
|
||||
ref={minutesField}
|
||||
name="minutes"
|
||||
value={data.minutes}
|
||||
|
@ -138,7 +139,7 @@ const EditTimerStep = React.memo(({
|
|||
</div>
|
||||
<div className={styles.fieldBox}>
|
||||
<div className={styles.text}>{t('common.seconds')}</div>
|
||||
<Input
|
||||
<Input.Mask
|
||||
ref={secondsField}
|
||||
name="seconds"
|
||||
value={data.seconds}
|
||||
|
|
|
@ -5,10 +5,9 @@ import PropTypes from 'prop-types';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
import { useDidUpdate, useToggle } from '../../lib/hooks';
|
||||
|
||||
import {
|
||||
useClosableForm, useDidUpdate, useForm, useToggle,
|
||||
} from '../../hooks';
|
||||
import { useClosableForm, useForm } from '../../hooks';
|
||||
|
||||
import styles from './AddCard.module.css';
|
||||
|
||||
|
|
|
@ -8,11 +8,10 @@ import isEmail from 'validator/lib/isEmail';
|
|||
import {
|
||||
Form, Grid, Header, Message,
|
||||
} from 'semantic-ui-react';
|
||||
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
|
||||
import { Input } from '../../lib/custom-ui';
|
||||
|
||||
import {
|
||||
useDidUpdate, useForm, usePrevious, useToggle,
|
||||
} from '../../hooks';
|
||||
import { useForm } from '../../hooks';
|
||||
|
||||
import styles from './Login.module.css';
|
||||
|
||||
|
@ -156,10 +155,9 @@ const Login = React.memo(
|
|||
</div>
|
||||
<div className={styles.inputWrapper}>
|
||||
<div className={styles.inputLabel}>{t('common.password')}</div>
|
||||
<Input
|
||||
<Input.Password
|
||||
fluid
|
||||
ref={passwordField}
|
||||
type="password"
|
||||
name="password"
|
||||
value={data.password}
|
||||
readOnly={isSubmitting}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useForceUpdate, usePrevious } from '../../lib/hooks';
|
||||
|
||||
import { useForceUpdate, usePrevious } from '../../hooks';
|
||||
import { formatTimer } from '../../utils/timer';
|
||||
|
||||
import styles from './Timer.module.css';
|
||||
|
|
|
@ -5,11 +5,10 @@ import React, {
|
|||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form, Message } from 'semantic-ui-react';
|
||||
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
|
||||
import { Input, Popup } from '../../lib/custom-ui';
|
||||
|
||||
import {
|
||||
useDidUpdate, useForm, usePrevious, useToggle,
|
||||
} from '../../hooks';
|
||||
import { useForm } from '../../hooks';
|
||||
|
||||
import styles from './EditNameStep.module.css';
|
||||
|
||||
|
@ -145,10 +144,9 @@ const EditEmailStep = React.memo(
|
|||
{data.email.trim() !== email && (
|
||||
<>
|
||||
<div className={styles.text}>{t('common.currentPassword')}</div>
|
||||
<Input
|
||||
<Input.Password
|
||||
fluid
|
||||
ref={currentPasswordField}
|
||||
type="password"
|
||||
name="currentPassword"
|
||||
value={data.currentPassword}
|
||||
className={styles.field}
|
||||
|
|
|
@ -4,11 +4,10 @@ import React, {
|
|||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form, Message } from 'semantic-ui-react';
|
||||
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
|
||||
import { Input, Popup } from '../../lib/custom-ui';
|
||||
|
||||
import {
|
||||
useDidUpdate, useForm, usePrevious, useToggle,
|
||||
} from '../../hooks';
|
||||
import { useForm } from '../../hooks';
|
||||
|
||||
import styles from './EditNameStep.module.css';
|
||||
|
||||
|
@ -107,7 +106,7 @@ const EditPasswordStep = React.memo(
|
|||
)}
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<div className={styles.text}>{t('common.newPassword')}</div>
|
||||
<Input
|
||||
<Input.Password
|
||||
fluid
|
||||
ref={passwordField}
|
||||
name="password"
|
||||
|
@ -116,10 +115,9 @@ const EditPasswordStep = React.memo(
|
|||
onChange={handleFieldChange}
|
||||
/>
|
||||
<div className={styles.text}>{t('common.currentPassword')}</div>
|
||||
<Input
|
||||
<Input.Password
|
||||
fluid
|
||||
ref={currentPasswordField}
|
||||
type="password"
|
||||
name="currentPassword"
|
||||
value={data.currentPassword}
|
||||
className={styles.field}
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
import usePrevious from './use-previous';
|
||||
import useField from './use-field';
|
||||
import useForm from './use-form';
|
||||
import useSteps from './use-steps';
|
||||
import useToggle from './use-toggle';
|
||||
import useForceUpdate from './use-force-update';
|
||||
import useClosableForm from './use-closable-form';
|
||||
import useDidUpdate from './use-did-update';
|
||||
|
||||
export {
|
||||
usePrevious,
|
||||
useField,
|
||||
useForm,
|
||||
useSteps,
|
||||
useToggle,
|
||||
useForceUpdate,
|
||||
useClosableForm,
|
||||
useDidUpdate,
|
||||
useField, useForm, useSteps, useClosableForm,
|
||||
};
|
||||
|
|
|
@ -1,28 +1,11 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
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 nextProps = props;
|
||||
const Input = SemanticUIInput;
|
||||
|
||||
if (mask) {
|
||||
nextProps.input = <MaskedInput mask={mask} maskChar={maskChar} />;
|
||||
}
|
||||
Input.Password = InputPassword;
|
||||
Input.Mask = InputMask;
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return <SemanticUIInput {...nextProps} ref={ref} />;
|
||||
});
|
||||
|
||||
Input.propTypes = {
|
||||
mask: PropTypes.string,
|
||||
maskChar: PropTypes.string,
|
||||
};
|
||||
|
||||
Input.defaultProps = {
|
||||
mask: undefined,
|
||||
maskChar: undefined,
|
||||
};
|
||||
|
||||
export default React.memo(Input);
|
||||
export default Input;
|
||||
|
|
21
client/src/lib/custom-ui/components/Input/InputMask.jsx
Normal file
21
client/src/lib/custom-ui/components/Input/InputMask.jsx
Normal 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);
|
22
client/src/lib/custom-ui/components/Input/InputPassword.jsx
Normal file
22
client/src/lib/custom-ui/components/Input/InputPassword.jsx
Normal 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);
|
8
client/src/lib/hooks/index.js
Normal file
8
client/src/lib/hooks/index.js
Normal 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,
|
||||
};
|
18
server/package-lock.json
generated
18
server/package-lock.json
generated
|
@ -5367,9 +5367,9 @@
|
|||
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks="
|
||||
},
|
||||
"prettier": {
|
||||
"version": "1.18.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
|
||||
"integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
|
||||
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier-eslint": {
|
||||
|
@ -5394,9 +5394,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.3.6.tgz",
|
||||
"integrity": "sha512-u4oM8SHwmDuh5mWZdDg9UwNVq5s1uqq6ZDLLIs07VY+VJU91i3h4f3K/pgFvtUQPGdeStrZ+odKyfyt4EnKHfA==",
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.1.tgz",
|
||||
"integrity": "sha512-KX/dnuY/J8FtEwbnrzmAjUYgLqtk+cxM86hfG60LGiW3MmltIc2yAmDgBgEkfm0blZhUrdr1Zd84J2Y14mLxzg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
@ -7407,9 +7407,9 @@
|
|||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.6.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz",
|
||||
"integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==",
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz",
|
||||
"integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==",
|
||||
"dev": true
|
||||
},
|
||||
"uid-safe": {
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
"eslint-config-airbnb-base": "^14.0.0",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"nodemon": "^1.19.4",
|
||||
"prettier-eslint": "^9.0.0",
|
||||
"prettier-eslint-cli": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue