Skip to content

Commit

Permalink
Introduce <PasswordInput>
Browse files Browse the repository at this point in the history
  • Loading branch information
Kmaschta committed Jul 9, 2019
1 parent fdc7d19 commit b502f17
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 7 deletions.
23 changes: 23 additions & 0 deletions docs/Inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,29 @@ You can customize the `step` props (which defaults to "any"):
<NumberInput source="nb_views" step={1} />
```
## `<PasswordInput>`
`<PasswordInput>` works like the [`<TextInput>`](#textinput) but overwrites its `type` prop to `password` or `text` in accordance with a visibility button, hidden by default.
```jsx
import { PasswordInput } from 'react-admin';

<PasswordInput source="password" />
```
![Password Input](./img/password-input.png)
It is possible to change the default behavior and display the value by default via the `initiallyVisible` prop:
```jsx
import { PasswordInput } from 'react-admin';

<PasswordInput source="password" initiallyVisible />
```
![Password Input (visible)](./img/password-input-visible.png)
## `<RadioButtonGroupInput>`
If you want to let the user choose a value among a list of possible values by showing them all (instead of hiding them behind a dropdown list, as in [`<SelectInput>`](#selectinput)), `<RadioButtonGroupInput>` is the right component. Set the `choices` attribute to determine the options (with `id`, `name` tuples):
Expand Down
5 changes: 5 additions & 0 deletions docs/_layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,11 @@
<code>&lt;NumberInput&gt;</code>
</a>
</li>
<li class="chapter">
<a href="#passwordinput">
<code>&lt;PasswordInput&gt;</code>
</a>
</li>
<li class="chapter">
<a href="#radiobuttongroupinput">
<code>&lt;RadioButtonGroupInput&gt;</code>
Expand Down
Binary file added docs/img/password-input-visible.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/password-input.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/data-generator/src/customers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default (db, { serializeDate }) =>
const last_name = name.lastName();
const email = internet.email(first_name, last_name);
const birthday = has_ordered ? date.past(60) : null;

return {
id,
first_name,
Expand Down
6 changes: 6 additions & 0 deletions examples/demo/src/i18n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,16 @@ export default {
orders: 'Orders',
reviews: 'Reviews',
stats: 'Stats',
password: 'Password',
change_password: 'Change Password',
},
page: {
delete: 'Delete Customer',
},
errors: {
password_mismatch:
'The password confirmation is not the same as the first password.',
},
},
commands: {
name: 'Order |||| Orders',
Expand Down
2 changes: 2 additions & 0 deletions examples/demo/src/i18n/fr.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export default {
orders: 'Commandes',
reviews: 'Commentaires',
stats: 'Statistiques',
password: 'Mot de passe',
change_password: 'Changer le mot de passe',
},
page: {
delete: 'Supprimer le client',
Expand Down
46 changes: 43 additions & 3 deletions examples/demo/src/visitors/VisitorCreate.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ import {
LongTextInput,
TabbedForm,
TextInput,
PasswordInput,
translate as withTranslation,
} from 'react-admin';
import { makeStyles } from '@material-ui/core/styles';

export const styles = {
first_name: { display: 'inline-block' },
last_name: { display: 'inline-block', marginLeft: 32 },
email: { width: 544 },
birthday: { display: 'inline-block' },
password: { display: 'inline-block' },
confirm_password: { display: 'inline-block', marginLeft: 32 },
address: { maxWidth: 544 },
zipcode: { display: 'inline-block' },
city: { display: 'inline-block', marginLeft: 32 },
Expand All @@ -24,13 +29,29 @@ export const styles = {
},
};

export const validatePasswords = translate => ({
password,
confirm_password,
}) => {
const errors = {};

if (password && confirm_password && password !== confirm_password) {
errors.confirm_password = [
translate('resources.customers.errors.password_mismatch'),
];
}

return errors;
};

const useStyles = makeStyles(styles);

const VisitorCreate = props => {
const VisitorCreate = ({ translate, ...props }) => {
const classes = useStyles();

return (
<Create {...props}>
<TabbedForm>
<TabbedForm validate={validatePasswords(translate)}>
<FormTab label="resources.customers.tabs.identity">
<TextInput
autoFocus
Expand All @@ -49,6 +70,10 @@ const VisitorCreate = props => {
formClassName={classes.email}
/>
<DateInput source="birthday" />
<PasswordInput
source="password"
formClassName={classes.password}
/>
</FormTab>
<FormTab
label="resources.customers.tabs.address"
Expand All @@ -64,9 +89,24 @@ const VisitorCreate = props => {
/>
<TextInput source="city" formClassName={classes.city} />
</FormTab>
<FormTab
label="resources.customers.tabs.password"
path="password"
>
<PasswordInput
source="password"
formClassName={classes.password}
required
/>
<PasswordInput
source="confirm_password"
formClassName={classes.confirm_password}
required
/>
</FormTab>
</TabbedForm>
</Create>
);
};

export default VisitorCreate;
export default withTranslation(VisitorCreate);
24 changes: 20 additions & 4 deletions examples/demo/src/visitors/VisitorEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import {
LongTextInput,
NullableBooleanInput,
NumberField,
PasswordInput,
ReferenceManyField,
TabbedForm,
TextField,
TextInput,
translate as withTranslation,
} from 'react-admin';
import { makeStyles } from '@material-ui/core/styles';

Expand All @@ -21,18 +23,19 @@ import ProductReferenceField from '../products/ProductReferenceField';
import StarRatingField from '../reviews/StarRatingField';
import FullNameField from './FullNameField';
import SegmentsInput from './SegmentsInput';
import { styles } from './VisitorCreate';
import { styles, validatePasswords } from './VisitorCreate';

const useStyles = makeStyles(styles);

const VisitorTitle = ({ record }) =>
record ? <FullNameField record={record} size={32} /> : null;

const VisitorEdit = props => {
const VisitorEdit = ({ translate, ...props }) => {
const classes = useStyles();

return (
<Edit title={<VisitorTitle />} {...props}>
<TabbedForm>
<TabbedForm validate={validatePasswords(translate)}>
<FormTab label="resources.customers.tabs.identity">
<TextInput
source="first_name"
Expand Down Expand Up @@ -123,9 +126,22 @@ const VisitorEdit = props => {
style={{ width: 128, display: 'inline-block' }}
/>
</FormTab>
<FormTab
label="resources.customers.tabs.change_password"
path="password"
>
<PasswordInput
source="password"
formClassName={classes.password}
/>
<PasswordInput
source="confirm_password"
formClassName={classes.confirm_password}
/>
</FormTab>
</TabbedForm>
</Edit>
);
};

export default VisitorEdit;
export default withTranslation(VisitorEdit);
3 changes: 3 additions & 0 deletions packages/ra-language-english/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ module.exports = {
single_missing:
'Associated reference no longer appears to be available.',
},
password: {
toggle_visibility: 'Toggle visibility',
},
},
message: {
about: 'About',
Expand Down
3 changes: 3 additions & 0 deletions packages/ra-language-french/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ module.exports = {
single_missing:
'La référence associée ne semble plus disponible.',
},
password: {
toggle_visibility: 'Afficher / Cacher',
},
},
message: {
about: 'Au sujet de',
Expand Down
50 changes: 50 additions & 0 deletions packages/ra-ui-materialui/src/input/PasswordInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import compose from 'recompose/compose';
import { addField, translate } from 'ra-core';

import IconButton from '@material-ui/core/IconButton';
import InputAdornment from '@material-ui/core/InputAdornment';
import Visibility from '@material-ui/icons/Visibility';
import VisibilityOff from '@material-ui/icons/VisibilityOff';

import TextInput from './TextInput';

const PasswordInput = ({ translate, initiallyVisible, ...rest }) => {
const [visible, setVisible] = useState(initiallyVisible);

return (
<TextInput
{...rest}
type={visible ? 'text' : 'password'}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label={translate(
'ra.input.password.toggle_visibility'
)}
onClick={() => setVisible(!visible)}
>
{visible ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
),
}}
/>
);
};

PasswordInput.propTypes = {
translate: PropTypes.func.isRequired,
initiallyVisible: PropTypes.bool,
};

PasswordInput.defaultProps = {
initiallyVisible: false,
};

export default compose(
translate,
addField
)(PasswordInput);
2 changes: 2 additions & 0 deletions packages/ra-ui-materialui/src/input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Labeled from './Labeled';
import LongTextInput from './LongTextInput';
import NullableBooleanInput from './NullableBooleanInput';
import NumberInput from './NumberInput';
import PasswordInput from './PasswordInput';
import RadioButtonGroupInput from './RadioButtonGroupInput';
import ReferenceArrayInput from './ReferenceArrayInput';
import ReferenceInput from './ReferenceInput';
Expand All @@ -38,6 +39,7 @@ export {
LongTextInput,
NullableBooleanInput,
NumberInput,
PasswordInput,
RadioButtonGroupInput,
ReferenceArrayInput,
ReferenceInput,
Expand Down

0 comments on commit b502f17

Please sign in to comment.