All files / components/common PasswordField.tsx

57.14% Statements 4/7
50% Branches 4/8
25% Functions 1/4
66.66% Lines 4/6

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50                        526x 526x     526x                                     526x                            
import {type ReactElement, useState} from 'react';
import {IconButton, InputAdornment, TextField, type TextFieldProps} from '@mui/material';
import {Visibility, VisibilityOff} from '@mui/icons-material';
 
/**
 * A password TextField with a built-in show/hide toggle (FSC-131).
 *
 * Accepts every standard MUI TextField prop; the `type` is managed internally
 * and flips between `password` and `text` when the user clicks the eye icon.
 * Existing `slotProps` (e.g. `htmlInput` constraints) are preserved.
 */
export default function PasswordField(props: Omit<TextFieldProps, 'type'>): ReactElement {
	const [show, setShow] = useState(false);
	const {slotProps, ...rest} = props;
 
	const toggle = (
		<InputAdornment position="end">
			<IconButton
				aria-label={show ? 'Hide password' : 'Show password'}
				onClick={(): void => {
					setShow((prev) => !prev);
				}}
				onMouseDown={(e): void => {
					// Keep focus in the field when toggling.
					e.preventDefault();
				}}
				edge="end"
				// Skip in tab order so keyboard flow goes field -> submit, not field -> eye.
				tabIndex={-1}
			>
				{show ? <VisibilityOff/> : <Visibility/>}
			</IconButton>
		</InputAdornment>
	);
 
	return (
		<TextField
			{...rest}
			type={show ? 'text' : 'password'}
			slotProps={{
				...slotProps,
				input: {
					...(typeof slotProps?.input === 'object' ? slotProps.input : {}),
					endAdornment: toggle
				}
			}}
		/>
	);
}