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
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} {
&__additional-right-content {
display: flex;
}

&__button {
--yc-button-background-color-hover: transparent;
Copy link
Contributor

Choose a reason for hiding this comment

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

Technically this is not part of public API. Moreover this var will be removed in the next major

}

&__input {
::-ms-reveal {
display: none;
}
}
}
82 changes: 82 additions & 0 deletions src/components/PasswordInput/PasswordInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react';

import {Copy, CopyCheck, Eye, EyeSlash} from '@gravity-ui/icons';
import {
Button,
CopyToClipboard,
CopyToClipboardStatus,
Icon,
TextInput,
TextInputProps,
} from '@gravity-ui/uikit';

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

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 visibility button */
showVisibilityButton?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

Lets call it showRevealButton

};

export const PasswordInput: React.FC<PasswordInputProps> = (props) => {
const {autoComplete, value, showCopyButton, rightContent, showVisibilityButton, className} =
props;

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

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

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

return (
<div className={b('additional-right-content')}>
{rightContent}
{value && showCopyButton ? (
<CopyToClipboard text={String(value)} timeout={500}>
Copy link
Contributor

Choose a reason for hiding this comment

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

Can be replaced with ClipboardButton component, which have tooltip functionality

{(state) => (
<Button view="flat-secondary" className={b('button')} size="s">
<Icon
size={14}
data={
state === CopyToClipboardStatus.Pending ? Copy : CopyCheck
}
/>
</Button>
)}
</CopyToClipboard>
) : null}
{showVisibilityButton ? (
<Button
view="flat-secondary"
Copy link
Contributor

Choose a reason for hiding this comment

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

aria-label prop would be nice here with the texts: Show password/Hide password

onClick={onClick}
className={b('button')}
size="s"
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 use different sizes for each TextInput size

>
<Icon data={hideValue ? Eye : EyeSlash} size={14} />
</Button>
) : null}
</div>
);
}, [rightContent, value, showCopyButton, showVisibilityButton, hideValue]);

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

Choose a reason for hiding this comment

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

That's not the input's className, you should pass it to the controlProps prop

/>
);
};
33 changes: 33 additions & 0 deletions src/components/PasswordInput/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## 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 |
| showVisibilityButton | `boolean` | | | Show visibility button |

#### 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}
showVisibilityButton={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;
}
}
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