Merge branch 'showable-password' into 'develop'

Create ShowablePassword component

See merge request soapbox-pub/soapbox-fe!916
This commit is contained in:
Alex Gleason 2021-12-15 15:07:33 +00:00
commit 7f3d7fbc29
10 changed files with 210 additions and 37 deletions

View File

@ -0,0 +1,64 @@
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl } from 'react-intl';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import IconButton from 'soapbox/components/icon_button';
import { FormPropTypes, InputContainer, LabelInputContainer } from 'soapbox/features/forms';
const messages = defineMessages({
showPassword: { id: 'forms.show_password', defaultMessage: 'Show password' },
hidePassword: { id: 'forms.hide_password', defaultMessage: 'Hide password' },
});
export default @injectIntl
class ShowablePassword extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
label: FormPropTypes.label,
className: PropTypes.string,
hint: PropTypes.node,
error: PropTypes.bool,
}
state = {
revealed: false,
}
toggleReveal = () => {
if (this.props.onToggleVisibility) {
this.props.onToggleVisibility();
} else {
this.setState({ revealed: !this.state.revealed });
}
}
render() {
const { intl, hint, error, label, className, ...props } = this.props;
const { revealed } = this.state;
const revealButton = (
<IconButton
src={revealed ? require('@tabler/icons/icons/eye.svg') : require('@tabler/icons/icons/eye-off.svg')}
onClick={this.toggleReveal}
title={intl.formatMessage(revealed ? messages.hidePassword : messages.showPassword)}
/>
);
return (
<InputContainer {...this.props} extraClass={classNames('showable-password', className)}>
{label ? (
<LabelInputContainer label={label}>
<input {...props} type={revealed ? 'text' : 'password'} />
{revealButton}
</LabelInputContainer>
) : (<>
<input {...props} type={revealed ? 'text' : 'password'} />
{revealButton}
</>)}
</InputContainer>
);
}
}

View File

@ -25,19 +25,48 @@ exports[`<LoginForm /> renders for Mastodon 1`] = `
/> />
</div> </div>
<div <div
className="input password user_password" className="input required showable-password password user_password"
> >
<input <input
aria-label="Password" aria-label="Password"
autoCapitalize="off" autoCapitalize="off"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
className="password"
name="password" name="password"
placeholder="Password" placeholder="Password"
required={true} required={true}
type="password" type="password"
/> />
<button
aria-label="Show password"
className="icon-button"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
tabIndex="0"
title="Show password"
>
<div
style={Object {}}
>
<div
className="svg-icon"
>
<svg
id={
Object {
"process": [Function],
}
}
/>
</div>
</div>
</button>
</div> </div>
<p <p
className="hint subtle-hint" className="hint subtle-hint"
@ -89,19 +118,48 @@ exports[`<LoginForm /> renders for Pleroma 1`] = `
/> />
</div> </div>
<div <div
className="input password user_password" className="input required showable-password password user_password"
> >
<input <input
aria-label="Password" aria-label="Password"
autoCapitalize="off" autoCapitalize="off"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
className="password"
name="password" name="password"
placeholder="Password" placeholder="Password"
required={true} required={true}
type="password" type="password"
/> />
<button
aria-label="Show password"
className="icon-button"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
tabIndex="0"
title="Show password"
>
<div
style={Object {}}
>
<div
className="svg-icon"
>
<svg
id={
Object {
"process": [Function],
}
}
/>
</div>
</div>
</button>
</div> </div>
<p <p
className="hint subtle-hint" className="hint subtle-hint"

View File

@ -28,19 +28,48 @@ exports[`<LoginPage /> renders correctly on load 1`] = `
/> />
</div> </div>
<div <div
className="input password user_password" className="input required showable-password password user_password"
> >
<input <input
aria-label="Password" aria-label="Password"
autoCapitalize="off" autoCapitalize="off"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
className="password"
name="password" name="password"
placeholder="Password" placeholder="Password"
required={true} required={true}
type="password" type="password"
/> />
<button
aria-label="Show password"
className="icon-button"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
tabIndex="0"
title="Show password"
>
<div
style={Object {}}
>
<div
className="svg-icon"
>
<svg
id={
Object {
"process": [Function],
}
}
/>
</div>
</div>
</button>
</div> </div>
<p <p
className="hint subtle-hint" className="hint subtle-hint"

View File

@ -5,6 +5,7 @@ import { Link } from 'react-router-dom';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { getFeatures } from 'soapbox/utils/features'; import { getFeatures } from 'soapbox/utils/features';
import { getBaseURL } from 'soapbox/utils/state'; import { getBaseURL } from 'soapbox/utils/state';
import ShowablePassword from 'soapbox/components/showable_password';
const messages = defineMessages({ const messages = defineMessages({
username: { id: 'login.fields.username_placeholder', defaultMessage: 'Username' }, username: { id: 'login.fields.username_placeholder', defaultMessage: 'Username' },
@ -45,19 +46,20 @@ class LoginForm extends ImmutablePureComponent {
required required
/> />
</div> </div>
<div className='input password user_password'> <ShowablePassword
<input
aria-label={intl.formatMessage(messages.password)} aria-label={intl.formatMessage(messages.password)}
className='password' className='password user_password'
placeholder={intl.formatMessage(messages.password)} placeholder={intl.formatMessage(messages.password)}
type='password'
name='password' name='password'
autoComplete='off' autoComplete='off'
autoCorrect='off' autoCorrect='off'
autoCapitalize='off' autoCapitalize='off'
required required
/> />
</div> {/* <div className='input password user_password'>
<input
/>
</div> */}
<p className='hint subtle-hint'> <p className='hint subtle-hint'>
{hasResetPasswordAPI ? ( {hasResetPasswordAPI ? (
<Link to='/auth/reset_password'> <Link to='/auth/reset_password'>

View File

@ -5,6 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl'; import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import ShowablePassword from 'soapbox/components/showable_password';
import { import {
SimpleForm, SimpleForm,
SimpleInput, SimpleInput,
@ -231,10 +232,9 @@ class RegistrationForm extends ImmutablePureComponent {
<FormattedMessage id='registration.password_mismatch' defaultMessage="Passwords don't match." /> <FormattedMessage id='registration.password_mismatch' defaultMessage="Passwords don't match." />
</div> </div>
)} )}
<SimpleInput <ShowablePassword
placeholder={intl.formatMessage(messages.password)} placeholder={intl.formatMessage(messages.password)}
name='password' name='password'
type='password'
autoComplete='off' autoComplete='off'
autoCorrect='off' autoCorrect='off'
autoCapitalize='off' autoCapitalize='off'
@ -243,10 +243,9 @@ class RegistrationForm extends ImmutablePureComponent {
error={passwordMismatch === true} error={passwordMismatch === true}
required required
/> />
<SimpleInput <ShowablePassword
placeholder={intl.formatMessage(messages.confirm)} placeholder={intl.formatMessage(messages.confirm)}
name='password_confirmation' name='password_confirmation'
type='password'
autoComplete='off' autoComplete='off'
autoCorrect='off' autoCorrect='off'
autoCapitalize='off' autoCapitalize='off'

View File

@ -6,9 +6,9 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
import Button from 'soapbox/components/button'; import Button from 'soapbox/components/button';
import ShowablePassword from 'soapbox/components/showable_password';
import { import {
SimpleForm, SimpleForm,
SimpleInput,
FieldsGroup, FieldsGroup,
TextInput, TextInput,
} from 'soapbox/features/forms'; } from 'soapbox/features/forms';
@ -141,8 +141,7 @@ class ChangeEmailForm extends ImmutablePureComponent {
onChange={this.handleInputChange} onChange={this.handleInputChange}
value={this.state.email} value={this.state.email}
/> />
<SimpleInput <ShowablePassword
type='password'
label={intl.formatMessage(messages.passwordFieldLabel)} label={intl.formatMessage(messages.passwordFieldLabel)}
name='password' name='password'
onChange={this.handleInputChange} onChange={this.handleInputChange}
@ -208,22 +207,19 @@ class ChangePasswordForm extends ImmutablePureComponent {
<h2>{intl.formatMessage(messages.passwordHeader)}</h2> <h2>{intl.formatMessage(messages.passwordHeader)}</h2>
<fieldset disabled={this.state.isLoading}> <fieldset disabled={this.state.isLoading}>
<FieldsGroup> <FieldsGroup>
<SimpleInput <ShowablePassword
type='password'
label={intl.formatMessage(messages.oldPasswordFieldLabel)} label={intl.formatMessage(messages.oldPasswordFieldLabel)}
name='oldPassword' name='oldPassword'
onChange={this.handleInputChange} onChange={this.handleInputChange}
value={this.state.oldPassword} value={this.state.oldPassword}
/> />
<SimpleInput <ShowablePassword
type='password'
label={intl.formatMessage(messages.newPasswordFieldLabel)} label={intl.formatMessage(messages.newPasswordFieldLabel)}
name='newPassword' name='newPassword'
onChange={this.handleInputChange} onChange={this.handleInputChange}
value={this.state.newPassword} value={this.state.newPassword}
/> />
<SimpleInput <ShowablePassword
type='password'
label={intl.formatMessage(messages.confirmationFieldLabel)} label={intl.formatMessage(messages.confirmationFieldLabel)}
name='confirmation' name='confirmation'
onChange={this.handleInputChange} onChange={this.handleInputChange}
@ -392,8 +388,7 @@ class DeactivateAccount extends ImmutablePureComponent {
</p> </p>
<fieldset disabled={this.state.isLoading}> <fieldset disabled={this.state.isLoading}>
<FieldsGroup> <FieldsGroup>
<SimpleInput <ShowablePassword
type='password'
label={intl.formatMessage(messages.passwordFieldLabel)} label={intl.formatMessage(messages.passwordFieldLabel)}
name='password' name='password'
onChange={this.handleInputChange} onChange={this.handleInputChange}

View File

@ -11,9 +11,9 @@ import LoadingIndicator from 'soapbox/components/loading_indicator';
import Button from 'soapbox/components/button'; import Button from 'soapbox/components/button';
import { changeSetting, getSettings } from 'soapbox/actions/settings'; import { changeSetting, getSettings } from 'soapbox/actions/settings';
import snackbar from 'soapbox/actions/snackbar'; import snackbar from 'soapbox/actions/snackbar';
import ShowablePassword from 'soapbox/components/showable_password';
import { import {
SimpleForm, SimpleForm,
SimpleInput,
FieldsGroup, FieldsGroup,
TextInput, TextInput,
} from 'soapbox/features/forms'; } from 'soapbox/features/forms';
@ -144,8 +144,7 @@ class DisableOtpForm extends ImmutablePureComponent {
</h1> </h1>
<div><FormattedMessage id='mfa.otp_enabled_description' defaultMessage='You have enabled two-factor authentication via OTP.' /></div> <div><FormattedMessage id='mfa.otp_enabled_description' defaultMessage='You have enabled two-factor authentication via OTP.' /></div>
<div><FormattedMessage id='mfa.mfa_disable_enter_password' defaultMessage='Enter your current password to disable two-factor auth:' /></div> <div><FormattedMessage id='mfa.mfa_disable_enter_password' defaultMessage='Enter your current password to disable two-factor auth:' /></div>
<SimpleInput <ShowablePassword
type='password'
name='password' name='password'
onChange={this.handleInputChange} onChange={this.handleInputChange}
/> />
@ -313,8 +312,7 @@ class OtpConfirmForm extends ImmutablePureComponent {
/> />
<div><FormattedMessage id='mfa.mfa_setup_enter_password' defaultMessage='Enter your current password to confirm your identity:' /></div> <div><FormattedMessage id='mfa.mfa_setup_enter_password' defaultMessage='Enter your current password to confirm your identity:' /></div>
<SimpleInput <ShowablePassword
type='password'
name='password' name='password'
onChange={this.handleInputChange} onChange={this.handleInputChange}
/> />

View File

@ -424,6 +424,8 @@
"follow_request.authorize": "Autoryzuj", "follow_request.authorize": "Autoryzuj",
"follow_request.reject": "Odrzuć", "follow_request.reject": "Odrzuć",
"forms.copy": "Kopiuj", "forms.copy": "Kopiuj",
"forms.hide_password": "Ukryj hasło",
"forms.show_password": "Pokaż hasło",
"getting_started.open_source_notice": "{code_name} jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitLabie tutaj: {code_link} (v{code_version}).", "getting_started.open_source_notice": "{code_name} jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitLabie tutaj: {code_link} (v{code_version}).",
"group.detail.archived_group": "Zarchiwizowana grupa", "group.detail.archived_group": "Zarchiwizowana grupa",
"group.members.empty": "Ta grupa nie ma żadnych członków.", "group.members.empty": "Ta grupa nie ma żadnych członków.",

View File

@ -97,6 +97,7 @@ $fluid-breakpoint: $maximum-width + 20px;
} }
.input { .input {
flex: 1;
margin-bottom: 0; margin-bottom: 0;
margin-right: 10px; margin-right: 10px;

View File

@ -608,6 +608,31 @@ code {
margin-bottom: 14px; margin-bottom: 14px;
font-weight: bold; font-weight: bold;
} }
.showable-password {
position: relative;
input {
padding-right: 36px;
}
.icon-button {
position: absolute;
top: 0;
right: 0;
height: 41px;
width: 36px;
padding: 0;
margin: 0;
background: transparent;
color: var(--primary-text-color);
.svg-icon {
height: 20px;
width: 20px;
}
}
}
} }
.block-icon { .block-icon {