diff --git a/front/src/modules/ui/components/inputs/TextInput.tsx b/front/src/modules/ui/components/inputs/TextInput.tsx index 1fbb0d68d..01a87764f 100644 --- a/front/src/modules/ui/components/inputs/TextInput.tsx +++ b/front/src/modules/ui/components/inputs/TextInput.tsx @@ -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, @@ -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(null); @@ -80,23 +97,46 @@ export function TextInput({ InternalHotkeysScope.TextInput, ); + const [passwordVisible, setPasswordVisible] = useState(false); + + const handleTogglePasswordVisibility = () => { + setPasswordVisible(!passwordVisible); + }; + + const theme = useTheme(); + return ( {label && {label}} - ) => { - if (onChange) { - onChange(event.target.value); - } - }} - {...props} - /> + + ) => { + if (onChange) { + onChange(event.target.value); + } + }} + {...props} + /> + {type === 'password' && ( // only show the icon for password inputs + + {passwordVisible ? ( + + ) : ( + + )} + + )} + ); } diff --git a/front/src/modules/ui/components/inputs/__stories__/TextInput.stories.tsx b/front/src/modules/ui/components/inputs/__stories__/TextInput.stories.tsx index e1f29d057..5a1aace61 100644 --- a/front/src/modules/ui/components/inputs/__stories__/TextInput.stories.tsx +++ b/front/src/modules/ui/components/inputs/__stories__/TextInput.stories.tsx @@ -63,3 +63,24 @@ export const FullWidth: Story = { />, ), }; + +export const PasswordInput: Story = { + render: getRenderWrapperForComponent( + , + ), + 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'); + }, +}; diff --git a/front/src/modules/ui/icons/index.ts b/front/src/modules/ui/icons/index.ts index 82aa56949..643dba895 100644 --- a/front/src/modules/ui/icons/index.ts +++ b/front/src/modules/ui/icons/index.ts @@ -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';