Add click to reveal password (#624)

This commit is contained in:
Félix Malfait 2023-07-12 16:59:01 +02:00 committed by GitHub
parent daad2bab75
commit 1c3d68a537
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 15 deletions

View File

@ -1,10 +1,12 @@
import { ChangeEvent, useRef } from 'react';
import { ChangeEvent, useRef, useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
import { usePreviousHotkeysScope } from '@/hotkeys/hooks/internal/usePreviousHotkeysScope';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { IconEye, IconEyeOff } from '@/ui/icons/index';
type OwnProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
@ -50,11 +52,26 @@ const StyledInput = styled.input<{ fullWidth: boolean }>`
}
`;
const StyledIconContainer = styled.div`
align-items: center;
display: flex;
position: relative;
`;
const StyledIcon = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.light};
cursor: pointer;
position: absolute;
right: ${({ theme }) => theme.spacing(2)};
`;
export function TextInput({
label,
value,
onChange,
fullWidth,
type,
...props
}: OwnProps): JSX.Element {
const inputRef = useRef<HTMLInputElement>(null);
@ -80,23 +97,46 @@ export function TextInput({
InternalHotkeysScope.TextInput,
);
const [passwordVisible, setPasswordVisible] = useState(false);
const handleTogglePasswordVisibility = () => {
setPasswordVisible(!passwordVisible);
};
const theme = useTheme();
return (
<StyledContainer>
{label && <StyledLabel>{label}</StyledLabel>}
<StyledInput
ref={inputRef}
tabIndex={props.tabIndex ?? 0}
onFocus={handleFocus}
onBlur={handleBlur}
fullWidth={fullWidth ?? false}
value={value}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
if (onChange) {
onChange(event.target.value);
}
}}
{...props}
/>
<StyledIconContainer>
<StyledInput
ref={inputRef}
tabIndex={props.tabIndex ?? 0}
onFocus={handleFocus}
onBlur={handleBlur}
fullWidth={fullWidth ?? false}
value={value}
type={passwordVisible ? 'text' : type}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
if (onChange) {
onChange(event.target.value);
}
}}
{...props}
/>
{type === 'password' && ( // only show the icon for password inputs
<StyledIcon
onClick={handleTogglePasswordVisibility}
data-testid="reveal-password-button"
>
{passwordVisible ? (
<IconEyeOff size={theme.icon.size.md} />
) : (
<IconEye size={theme.icon.size.md} />
)}
</StyledIcon>
)}
</StyledIconContainer>
</StyledContainer>
);
}

View File

@ -63,3 +63,24 @@ export const FullWidth: Story = {
/>,
),
};
export const PasswordInput: Story = {
render: getRenderWrapperForComponent(
<TextInput
onChange={changeJestFn}
type="password"
placeholder="Password"
/>,
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByPlaceholderText('Password');
await userEvent.type(input, 'pa$$w0rd');
const revealButton = canvas.getByTestId('reveal-password-button');
await userEvent.click(revealButton);
expect(input).toHaveAttribute('type', 'text');
},
};

View File

@ -35,3 +35,5 @@ export { IconNotes } from '@tabler/icons-react';
export { IconCirclePlus } from '@tabler/icons-react';
export { IconCheckbox } from '@tabler/icons-react';
export { IconTimelineEvent } from '@tabler/icons-react';
export { IconEye } from '@tabler/icons-react';
export { IconEyeOff } from '@tabler/icons-react';