From 2e7f2c731c8cb2fd08993fc30ffed8b06a5f0ea2 Mon Sep 17 00:00:00 2001 From: Egor Mostovoy Date: Fri, 15 Nov 2024 14:13:24 +0100 Subject: [PATCH] feat(PasswordInput): add component (#1745) Co-authored-by: Evgeny Alaev --- .../controls/PasswordInput/PasswordInput.scss | 16 +++ .../controls/PasswordInput/PasswordInput.tsx | 115 ++++++++++++++++++ .../controls/PasswordInput/README.md | 64 ++++++++++ ...nder-story-Default-dark-chromium-linux.png | Bin 0 -> 3004 bytes ...render-story-Default-dark-webkit-linux.png | Bin 0 -> 2755 bytes ...der-story-Default-light-chromium-linux.png | Bin 0 -> 2770 bytes ...ender-story-Default-light-webkit-linux.png | Bin 0 -> 2542 bytes .../PasswordInput/__stories__/Docs.mdx | 7 ++ .../__stories__/PasswordInput.stories.tsx | 55 +++++++++ .../__tests__/PasswordInput.visual.test.tsx | 13 ++ .../__tests__/helpersPlaywright.ts | 5 + .../controls/PasswordInput/i18n/en.json | 4 + .../controls/PasswordInput/i18n/index.ts | 8 ++ .../controls/PasswordInput/i18n/ru.json | 4 + .../controls/PasswordInput/index.ts | 1 + .../controls/PasswordInput/utils.ts | 26 ++++ src/components/controls/index.ts | 1 + 17 files changed, 319 insertions(+) create mode 100644 src/components/controls/PasswordInput/PasswordInput.scss create mode 100644 src/components/controls/PasswordInput/PasswordInput.tsx create mode 100644 src/components/controls/PasswordInput/README.md create mode 100644 src/components/controls/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/PasswordInput-render-story-Default-dark-chromium-linux.png create mode 100644 src/components/controls/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/PasswordInput-render-story-Default-dark-webkit-linux.png create mode 100644 src/components/controls/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/PasswordInput-render-story-Default-light-chromium-linux.png create mode 100644 src/components/controls/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/PasswordInput-render-story-Default-light-webkit-linux.png create mode 100644 src/components/controls/PasswordInput/__stories__/Docs.mdx create mode 100644 src/components/controls/PasswordInput/__stories__/PasswordInput.stories.tsx create mode 100644 src/components/controls/PasswordInput/__tests__/PasswordInput.visual.test.tsx create mode 100644 src/components/controls/PasswordInput/__tests__/helpersPlaywright.ts create mode 100644 src/components/controls/PasswordInput/i18n/en.json create mode 100644 src/components/controls/PasswordInput/i18n/index.ts create mode 100644 src/components/controls/PasswordInput/i18n/ru.json create mode 100644 src/components/controls/PasswordInput/index.ts create mode 100644 src/components/controls/PasswordInput/utils.ts diff --git a/src/components/controls/PasswordInput/PasswordInput.scss b/src/components/controls/PasswordInput/PasswordInput.scss new file mode 100644 index 0000000000..9e8f8c231b --- /dev/null +++ b/src/components/controls/PasswordInput/PasswordInput.scss @@ -0,0 +1,16 @@ +@use '../../variables'; + +$block: '.#{variables.$ns}password-input'; + +#{$block} { + &__input-control { + &::-ms-reveal, + &::-ms-clear { + display: none; + } + } + + &__copy-button { + margin-inline-end: 4px; + } +} diff --git a/src/components/controls/PasswordInput/PasswordInput.tsx b/src/components/controls/PasswordInput/PasswordInput.tsx new file mode 100644 index 0000000000..e86adc8bc5 --- /dev/null +++ b/src/components/controls/PasswordInput/PasswordInput.tsx @@ -0,0 +1,115 @@ +'use client'; + +import React from 'react'; + +import {Eye, EyeSlash} from '@gravity-ui/icons'; + +import {useControlledState} from '../../../hooks'; +import {ActionTooltip} from '../../ActionTooltip'; +import {Button} from '../../Button'; +import {ClipboardButton} from '../../ClipboardButton'; +import {Icon} from '../../Icon'; +import {block} from '../../utils/cn'; +import {TextInput} from '../TextInput'; +import type {TextInputProps} from '../TextInput'; + +import {i18n} from './i18n'; +import {getActionButtonSizeAndIconSize} from './utils'; + +import './PasswordInput.scss'; + +const b = block('password-input'); + +export type PasswordInputProps = Omit & { + /** Hide copy button */ + hideCopyButton?: boolean; + /** Hide reveal button */ + hideRevealButton?: boolean; + /** Determines whether to display the tooltip for the copy button */ + showCopyTooltip?: boolean; + /** Determines whether to display the tooltip for the reveal button */ + showRevealTooltip?: boolean; + /** Determines the visibility state of the password input field */ + revealValue?: boolean; + /** A callback function that is invoked whenever the revealValue state changes */ + onRevealValueUpdate?: (value: boolean) => void; +}; + +export const PasswordInput = (props: PasswordInputProps) => { + const { + autoComplete, + controlProps, + endContent, + rightContent, + hideCopyButton = false, + hideRevealButton = false, + showCopyTooltip = false, + showRevealTooltip = false, + size = 'm', + } = props; + + const [inputValue, setInputValue] = useControlledState( + props.value, + props.defaultValue ?? '', + props.onUpdate, + ); + + const [revealValue, setRevealValue] = useControlledState( + props.revealValue, + false, + props.onRevealValueUpdate, + ); + + const {actionButtonSize, iconSize} = getActionButtonSizeAndIconSize(size); + + const additionalEndContent = ( + + {endContent || rightContent} + {inputValue && !hideCopyButton && !props.disabled ? ( + + ) : null} + {hideRevealButton ? null : ( + + + + )} + + ); + + return ( + + ); +}; diff --git a/src/components/controls/PasswordInput/README.md b/src/components/controls/PasswordInput/README.md new file mode 100644 index 0000000000..f0964937d3 --- /dev/null +++ b/src/components/controls/PasswordInput/README.md @@ -0,0 +1,64 @@ + + +## Password Input + + + +```tsx +import {PasswordInput} from '@gravity-ui/uikit'; +``` + +`TextInput` for typing passwords and other sensitive information. It can be rendered with copy and reveal buttons for more convinient usage. + +### Copy button + +This button allows users to easily copy the input value to their clipboard. You can hide this button with `hideCopyButton` boolean prop. + + + +### Reveal button + +The `hideRevealButton` prop allows users to toggle the visibility of the password. + + + +### Properties + +`TextInput` [properties](https://github.com/gravity-ui/uikit/blob/main/src/components/controls/TextInput/README.md#properties), with some exceptions and additions: + +- `type` is omitted; + +| Name | Description | Type | Default | +| :------------------ | :------------------------------------------------------------------------- | :--------: | :-----: | +| hideCopyButton | Show copy button | `boolean` | `false` | +| hideRevealButton | Show reveal button | `boolean` | `false` | +| showCopyTooltip | Determines whether to display the tooltip for the copy button | `boolean` | `false` | +| showRevealTooltip | Determines whether to display the tooltip for the reveal button | `boolean` | `false` | +| revealValue | Determines the visibility state of the password input field | `boolean` | `false` | +| onRevealValueUpdate | A callback function that is invoked whenever the revealValue state changes | `function` | | + + + +#### Usage example + +```tsx +function MyComponent() { + const [value, setValue] = React.useState(''); + + return ; +} +``` + + diff --git a/src/components/controls/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/PasswordInput-render-story-Default-dark-chromium-linux.png b/src/components/controls/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/PasswordInput-render-story-Default-dark-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..ace4d75b98cbcdb999d77a6e6e183b8eb851471a GIT binary patch literal 3004 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 literal 0 HcmV?d00001 diff --git a/src/components/controls/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/PasswordInput-render-story-Default-dark-webkit-linux.png b/src/components/controls/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/PasswordInput-render-story-Default-dark-webkit-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..4091f4dc080b312e21a3d44f9a8c1834d44f464a GIT binary patch literal 2755 zcmd5;X*AneAO2I*g;7muO%Y5OrYe?FB`77fR<$ptN|MG>dr4^`K_zVsm1C=#N~01> z8>Z977GkU!loUnnYeX4MHP))FMBdE%W#;4ian3#G-ru?RIp?0|+~0kkhkV7&NnTb( z761Tw7iYLT07x}S>W2rVCG*xfs;gw#?|;z=4v7C61&t-y03b_nfuHvz+!JjZg%!kE0o$2F%}2 zWe7bxkMXp=di&nu7AzU3q&&$vk=gbvqR&>dAYLC($;!qbS61$tt6aL5V*19r1YDs= z{@L%lp|GJ2%M78CT-yc4Oe}q=Q0iT~(YFJp2s4FvhvM0X-(~e| zY;2T5QIHu}q)v9|Loovg`7tLDhr^A55b)ENO=^*D%Y?TCC*~q z#p1v)HF6KOJs_&p2+3tU1%^XILpyNDR-+kX8s|whW1#9SdxITTo!fB(n=WN*(s|?b zP}rIA<3og*7f>3=5&|imjYc25`zX400T!uwWW}T?iH()qpJHD6EL`&J6dRk4-jw6x z{U@sKL)`V1aPoldnx2yEXAj4!cd<+lxZdOaah0;Rtd)UP*mdET{L8@*-MjKD1BFpk zx#1Du@17Yxa!OZfOFYouf4V%pprodxq=d^}93H+3@YJ$~Su3YBvrKwtkzL`)> zwf~TBHZBIIu5OX%s*~7Uo&Df`vo8WM!H$?X42F5?Jq`*A(kZ=sLz(5%zOCo1d-sx} zY*I$Pd6W1LqSngi)O*dow+IBna#Q2krg~!{ZZiM3F?|WFy8U>Tl!=i_PxEX@Fic}GJ8TOE(#8V=i z`hPI?RZyH-CfDIl?+t3svwfp*%a|nsX(6Gj5`F`RTklFmXQ09OJj$yp@6CG_5j7^4 zO+@=dIe{mVvs`sF46UvUWLM%;TNGk|GHsRg47$pCeQj0Mmofgim*{&VRd4qlINVUN znO<6I0P47Ln$O=O#>LgK8|M}lZo+7L(+dYAymJE6>FT17y&7|Cu~2c&`s^#kKia>M zzE@!qK%>zhl?@HM^Jp8UO>mmyvtThch9JIWs&eMifN6J#Da^>IJp9&(gnTvCCCSc@ z7)iuO<9}3=^=V#W@@gnt?RHueLapeImcvSe$qeqafM{ApMYslOEM9WByX)gO7uV59RgRg3@%*1)-DOu~{_keQ zi0%VnlGfU~{bXj9keE++^@sm0(=?zltwn(907K_#5az zQY;O)-j}$eu(8NjuDTC}LKE#1@ynCzy5EtVE3P$c^PiRq*~tj|!iR#`7oK6!{+~NJ zWrO^z%(egxx~QDVyab;-wmHe~WdaaWlXH!R@=;QMsg+ITDIaqoM@( z?D$8^d+$+V1CLtd-Y%m%zKBlrHPo&)QU1m1IBfVY+pETzouh>{T|YZ^mUJ zj@`J)-E_=I+xiP9u(1)ZG}qOgtW#}?fB;XY_ZoNdcjvMtB2jV86&|y(JUPZ$n;$4) zu~-s?s!X*p#7&M3)iQ$-r>U>5kz~4+++`$AelJ?k=0YXHz6Scj=c4 z45~&@5b;T$^+Udl$IL%0I`m?R_$J5iT_n)Ciw!|S;E38?Cj8RCoq1g;UwIpFHJD5y zjrV)&QQ*&i7%cjq@QdHp=YybCjN5G}P*w`K?+gHRcd;Y3PipyuTr8P|LHwO=iaSWa zHQlbtg%lL?swgd_aLq>$9cgjopcUtoI?!P2<>jS$`1)NqwHmUWYk6|Mb&EhP$bX{KWg;yb_2S!N9M|sckc~D3FV_%P~2a zmrp;{4RrGHh3jps2+Nxf?4gBrh3}^0Eu`MHRK9{k`lk8}Ak-4-opWWeR&ff3kXmFZ z(m=OPG3NE*3BQpj)Cg2b_VIC6RK!aoCsRq>&Q9;@D$zno%3hqj@jkR|7f z<_E-yjn^5-j=5-w({I`W(+=j$eSh2FzcM=gf0=H>=I0@D!q$Svw~|x}xH!1MOYMB` F{}=Q!B8mV2 literal 0 HcmV?d00001 diff --git a/src/components/controls/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/PasswordInput-render-story-Default-light-chromium-linux.png b/src/components/controls/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/PasswordInput-render-story-Default-light-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..a2becc8a8cd8180b6eeb88e9f11043b0d600c226 GIT binary patch literal 2770 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 Fe*p?d+^YZp literal 0 HcmV?d00001 diff --git a/src/components/controls/PasswordInput/__stories__/Docs.mdx b/src/components/controls/PasswordInput/__stories__/Docs.mdx new file mode 100644 index 0000000000..343d63b894 --- /dev/null +++ b/src/components/controls/PasswordInput/__stories__/Docs.mdx @@ -0,0 +1,7 @@ +import {Meta, Markdown} from '@storybook/addon-docs'; +import * as Stories from './PasswordInput.stories'; +import Readme from '../README.md?raw'; + + + +{Readme} diff --git a/src/components/controls/PasswordInput/__stories__/PasswordInput.stories.tsx b/src/components/controls/PasswordInput/__stories__/PasswordInput.stories.tsx new file mode 100644 index 0000000000..575b1d736a --- /dev/null +++ b/src/components/controls/PasswordInput/__stories__/PasswordInput.stories.tsx @@ -0,0 +1,55 @@ +import React from 'react'; + +import type {Meta, StoryFn} from '@storybook/react'; + +import {Button} from '../../../Button'; +import {Flex, spacing} from '../../../layout'; +import type {PasswordInputProps} from '../PasswordInput'; +import {PasswordInput} from '../PasswordInput'; + +export default { + title: 'Components/Inputs/PasswordInput', + component: PasswordInput, + args: { + controlProps: { + 'aria-label': 'Password', + }, + }, +} as Meta; + +const DefaultTemplate: StoryFn = (args) => { + const [value, setValue] = React.useState(''); + + return ; +}; + +export const Default = DefaultTemplate.bind({}); + +const WithGenerateRandomValueTemplate: StoryFn = (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 ( + + + + + + + ); +}; + +export const WithGenerateRandomValue = WithGenerateRandomValueTemplate.bind({}); diff --git a/src/components/controls/PasswordInput/__tests__/PasswordInput.visual.test.tsx b/src/components/controls/PasswordInput/__tests__/PasswordInput.visual.test.tsx new file mode 100644 index 0000000000..f6ab293e36 --- /dev/null +++ b/src/components/controls/PasswordInput/__tests__/PasswordInput.visual.test.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import {test} from '~playwright/core'; + +import {PasswordInputStories} from './helpersPlaywright'; + +test.describe('PasswordInput', () => { + test('render story: ', async ({mount, expectScreenshot}) => { + await mount(); + + await expectScreenshot(); + }); +}); diff --git a/src/components/controls/PasswordInput/__tests__/helpersPlaywright.ts b/src/components/controls/PasswordInput/__tests__/helpersPlaywright.ts new file mode 100644 index 0000000000..7c6a1d906b --- /dev/null +++ b/src/components/controls/PasswordInput/__tests__/helpersPlaywright.ts @@ -0,0 +1,5 @@ +import {composeStories} from '@storybook/react'; + +import * as DefaultPasswordInputStories from '../__stories__/PasswordInput.stories'; + +export const PasswordInputStories = composeStories(DefaultPasswordInputStories); diff --git a/src/components/controls/PasswordInput/i18n/en.json b/src/components/controls/PasswordInput/i18n/en.json new file mode 100644 index 0000000000..130afcee1b --- /dev/null +++ b/src/components/controls/PasswordInput/i18n/en.json @@ -0,0 +1,4 @@ +{ + "label_show-password": "Show password", + "label_hide-password": "Hide password" +} diff --git a/src/components/controls/PasswordInput/i18n/index.ts b/src/components/controls/PasswordInput/i18n/index.ts new file mode 100644 index 0000000000..5a8bb879a3 --- /dev/null +++ b/src/components/controls/PasswordInput/i18n/index.ts @@ -0,0 +1,8 @@ +import {addComponentKeysets} from '../../../../i18n'; + +import en from './en.json'; +import ru from './ru.json'; + +const COMPONENT = 'PasswordInput'; + +export const i18n = addComponentKeysets({en, ru}, COMPONENT); diff --git a/src/components/controls/PasswordInput/i18n/ru.json b/src/components/controls/PasswordInput/i18n/ru.json new file mode 100644 index 0000000000..838beedfba --- /dev/null +++ b/src/components/controls/PasswordInput/i18n/ru.json @@ -0,0 +1,4 @@ +{ + "label_show-password": "Показать пароль", + "label_hide-password": "Скрыть пароль" +} diff --git a/src/components/controls/PasswordInput/index.ts b/src/components/controls/PasswordInput/index.ts new file mode 100644 index 0000000000..1eed3611f8 --- /dev/null +++ b/src/components/controls/PasswordInput/index.ts @@ -0,0 +1 @@ +export * from './PasswordInput'; diff --git a/src/components/controls/PasswordInput/utils.ts b/src/components/controls/PasswordInput/utils.ts new file mode 100644 index 0000000000..3fbea684c2 --- /dev/null +++ b/src/components/controls/PasswordInput/utils.ts @@ -0,0 +1,26 @@ +import type {ButtonSize} from '../../Button'; +import type {InputControlSize} from '../types'; + +export const getActionButtonSizeAndIconSize = ( + textInputSize: InputControlSize, +): {actionButtonSize: ButtonSize; iconSize: number} => { + let actionButtonSize: ButtonSize = 's'; + let iconSize = 16; + + switch (textInputSize) { + case 's': { + actionButtonSize = 'xs'; + iconSize = 12; + break; + } + case 'l': { + actionButtonSize = 'm'; + break; + } + case 'xl': { + actionButtonSize = 'l'; + } + } + + return {actionButtonSize, iconSize}; +}; diff --git a/src/components/controls/index.ts b/src/components/controls/index.ts index 211411bace..7319aa37d2 100644 --- a/src/components/controls/index.ts +++ b/src/components/controls/index.ts @@ -1,3 +1,4 @@ export * from './TextArea'; export * from './TextInput'; +export * from './PasswordInput'; export type {InputControlPin, InputControlSize, InputControlState, InputControlView} from './types';