Skip to content

Commit

Permalink
feat: made a new password input component (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
NasgulNexus authored Nov 1, 2023
1 parent aa95cda commit 5b1c15d
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 0 deletions.
21 changes: 21 additions & 0 deletions src/components/PasswordInput/PasswordInput.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@use '../variables';

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

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

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

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

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

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

import i18n from './i18n';
import {getCopyButtonSizeAndIconSize} from './utils';

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 the tooltip for the copy button. The tooltip will not be displayed */
hasCopyTooltip?: boolean;
/** Disable the tooltip for the reveal button. The tooltip will not be displayed */
hasRevealTooltip?: boolean;
};

export const PasswordInput: React.FC<PasswordInputProps> = (props) => {
const {
autoComplete,
value,
showCopyButton,
rightContent,
showRevealButton,
size = 'm',
hasCopyTooltip = true,
hasRevealTooltip = true,
controlProps,
} = 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);
};

const {copyButtonSize, iconSize} = getCopyButtonSizeAndIconSize(size);

return (
<div className={b('additional-right-content')}>
{rightContent}
{value && showCopyButton ? (
<ClipboardButton
text={value}
hasTooltip={hasRevealTooltip}
size={iconSize}
className={b('copy-button')}
/>
) : null}
{showRevealButton ? (
<Tooltip
disabled={!hasCopyTooltip}
content={
hideValue ? i18n('label_show-password') : i18n('label_hide-password')
}
>
<Button
view="flat-secondary"
onClick={onClick}
size={copyButtonSize}
extraProps={{
'aria-label': hideValue
? i18n('label_show-password')
: i18n('label_hide-password'),
}}
>
<Icon data={hideValue ? Eye : EyeSlash} size={iconSize} />
</Button>
</Tooltip>
) : null}
</div>
);
}, [
showRevealButton,
showCopyButton,
rightContent,
value,
hasRevealTooltip,
hasCopyTooltip,
hideValue,
size,
]);

return (
<TextInput
{...props}
type={hideValue ? 'password' : 'text'}
rightContent={additionalRightContent}
autoComplete={autoComplete ? autoComplete : 'new-password'}
controlProps={{
...controlProps,
className: b('input-control', controlProps?.className),
}}
/>
);
};
35 changes: 35 additions & 0 deletions src/components/PasswordInput/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## 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 |
| hasCopyTooltip | `boolean` | | `true` | Disable the tooltip for the copy button. The tooltip will not be displayed |
| hasRevealTooltip | `boolean` | | `true` | Disable the tooltip for the reveal button. The tooltip will not be displayed |

#### Usage example

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

return (
<PasswordInput
showCopyButton={true}
showRevealButton={true}
onUpdate={setValue}
value={value}
/>
);
}
```
57 changes: 57 additions & 0 deletions src/components/PasswordInput/__stories__/PasswordInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';

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

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

import './PasswordInputStories.scss';

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

export default {
title: 'Components/PasswordInput',
component: PasswordInput,
args: {
showCopyButton: true,
showRevealButton: true,
},
} as Meta;

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

return <PasswordInput {...args} onUpdate={setValue} value={value} />;
};

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

const WithGenerateRandomValueTemplate: StoryFn<PasswordInputProps> = (args) => {
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 {...args} onUpdate={setValue} value={value} />
<Button onClick={generateRandomValue} className={b('button-generate-random-value')}>
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';
26 changes: 26 additions & 0 deletions src/components/PasswordInput/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type {ButtonSize, InputControlSize} from '@gravity-ui/uikit';

export const getCopyButtonSizeAndIconSize = (
textInputSize: InputControlSize,
): {copyButtonSize: ButtonSize; iconSize: number} => {
let copyButtonSize: ButtonSize = 's';
let iconSize = 16;

switch (textInputSize) {
case 's': {
copyButtonSize = 'xs';
iconSize = 12;
break;
}
case 'l': {
copyButtonSize = 'm';
break;
}
case 'xl': {
copyButtonSize = 'l';
iconSize = 20;
}
}

return {copyButtonSize, iconSize};
};
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

0 comments on commit 5b1c15d

Please sign in to comment.