Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: made a new password input component #108

Merged
merged 10 commits into from
Nov 1, 2023
Merged
19 changes: 19 additions & 0 deletions src/components/PasswordInput/PasswordInput.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@use '../variables';

$block: '.#{variables.$ns}password-input';

#{$block} {
&::-ms-reveal,
&::-ms-clear {
display: none;
}

&__additional-right-content {
display: flex;
align-items: center;
}

&__copy-button {
margin-right: 4px;
}
}
93 changes: 93 additions & 0 deletions src/components/PasswordInput/PasswordInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';

import {Eye, EyeSlash} from '@gravity-ui/icons';
import {Button, ClipboardButton, TextInput, TextInputProps, Tooltip} from '@gravity-ui/uikit';

import {block} from '../utils/cn';

import i18n from './i18n';

import './PasswordInput.scss';

const b = block('password-input');

export type PasswordInputProps = Required<Pick<TextInputProps, 'onUpdate' | 'value'>> &
Omit<TextInputProps, 'type'> & {
/** Show copy button */
showCopyButton?: boolean;
/** Show reveal button */
showRevealButton?: boolean;
/** Disable tooltip. Tooltip won't be shown */
hasTooltip?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would pick separate names here, hasCopyTooltip and hasRevealTooltip

};

export const PasswordInput: React.FC<PasswordInputProps> = (props) => {
const {
autoComplete,
value,
showCopyButton,
rightContent,
showRevealButton,
size = 'm',
hasTooltip = true,
} = props;

const [hideValue, setHideValue] = React.useState(true);

const additionalRightContent = React.useMemo(() => {
if (!showRevealButton && !showCopyButton) {
return <React.Fragment>{rightContent}</React.Fragment>;
}

const onClick = () => {
setHideValue((hideValue) => !hideValue);
};

return (
<div className={b('additional-right-content')}>
{rightContent}
{value && showCopyButton ? (
<ClipboardButton
text={value}
hasTooltip={hasTooltip}
size={16}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should map input size to button size

className={b('copy-button')}
/>
) : null}
{showRevealButton ? (
<Tooltip
disabled={!hasTooltip}
content={
hideValue ? i18n('label_show-password') : i18n('label_hide-password')
}
>
<Button
view="flat-secondary"
onClick={onClick}
size={size}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TextInput size !== Button size, take a look at clear button, maybe reuse this logic here as well

extraProps={{
'aria-label': hideValue
? i18n('label_show-password')
: i18n('label_hide-password'),
}}
>
<Button.Icon>{hideValue ? <Eye /> : <EyeSlash />}</Button.Icon>
</Button>
</Tooltip>
) : null}
</div>
);
}, [showRevealButton, showCopyButton, rightContent, value, hasTooltip, hideValue, size]);

return (
<TextInput
{...props}
type={hideValue ? 'password' : 'text'}
rightContent={additionalRightContent}
autoComplete={autoComplete ? autoComplete : 'new-password'}
controlProps={{
className: b(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now it's an opposite, you're passing root className (b()) to the control node

}}
/>
);
};
34 changes: 34 additions & 0 deletions src/components/PasswordInput/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## PasswordInput

Password Input display component

### PropTypes

Same as [TextInput component](https://github.com/gravity-ui/uikit/blob/main/src/components/controls/TextInput/README.md), with some exceptions:

- `value` is required property;
- `onUpdate` is required property;
- `type` is omitted;

| Property | Type | Required | Default | Description |
| :--------------- | :-------- | :------- | :------ | :-------------------------------------- |
| showCopyButton | `boolean` | | | Show copy button |
| showRevealButton | `boolean` | | | Show reveal button |
| hasTooltip | `boolean` | | `true` | Disable tooltip. Tooltip won't be shown |

#### Usage example

```jsx harmony
function MyComponent() {
const [value, setValue] = React.useState('');

return (
<PasswordInput
showCopyButton={true}
showVisibilityButton={true}
onUpdate={setValue}
value={value}
/>
);
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';

import {Button} from '@gravity-ui/uikit';
import type {Meta, StoryFn} from '@storybook/react';

import {cn} from '../../utils/cn';
import {PasswordInput} from '../PasswordInput';

import './PasswordInputStories.scss';

const b = cn('password-input-stories');

export default {
title: 'Components/PasswordInput',
component: PasswordInput,
} as Meta;

const DefaultTemplate: StoryFn<React.ComponentProps<typeof PasswordInput>> = () => {
const [value, setValue] = React.useState('');

return (
<PasswordInput
showCopyButton={true}
showRevealButton={true}
onUpdate={setValue}
value={value}
/>
);
};

export const Default = DefaultTemplate.bind({});

const WithGenerateRandomValueTemplate: StoryFn<React.ComponentProps<typeof PasswordInput>> = (
props: React.ComponentProps<typeof PasswordInput>,
) => {
const [value, setValue] = React.useState('');

const generateRandomValue = React.useCallback(() => {
let randomValue = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
let counter = 0;

while (counter < charactersLength) {
randomValue += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}

setValue(randomValue);
}, []);

return (
<div className={b()}>
<PasswordInput {...props} onUpdate={setValue} value={value} />
<Button onClick={generateRandomValue} className={b('button-generate-random-value')}>
Copy link

@personaljs personaljs Oct 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add prop showCopyButton and showRevealButton?

Generate random value
</Button>
</div>
);
};

export const WithGenerateRandomValue = WithGenerateRandomValueTemplate.bind({});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.password-input-stories {
display: flex;

&__button-generate-random-value {
margin-left: 8px;
}
}
4 changes: 4 additions & 0 deletions src/components/PasswordInput/i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"label_show-password": "Show password",
"label_hide-password": "Hide password"
}
8 changes: 8 additions & 0 deletions src/components/PasswordInput/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {registerKeyset} from '../../utils/registerKeyset';

import en from './en.json';
import ru from './ru.json';

const COMPONENT = 'PasswordInput';

export default registerKeyset({en, ru}, COMPONENT);
4 changes: 4 additions & 0 deletions src/components/PasswordInput/i18n/ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"label_show-password": "Показать пароль",
"label_hide-password": "Скрыть пароль"
}
1 change: 1 addition & 0 deletions src/components/PasswordInput/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './PasswordInput';
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './InfiniteScroll';
export * from './ItemSelector';
export * from './Notification';
export * from './Notifications';
export * from './PasswordInput';
export * from './PlaceholderContainer';
export * from './PromoSheet';
export * from './ActionsPanel';
Expand Down
Loading