From f6aaa9b69b6daa3ac1b2ac5c16ee4b1ff99764ee Mon Sep 17 00:00:00 2001 From: cqh Date: Tue, 9 Jun 2026 09:35:56 +0800 Subject: [PATCH] fix: improve password visibility toggle a11y --- .../PasswordInput/PasswordInput.spec.tsx | 25 +++++++++++++++++++ .../PasswordInput/PasswordInput.tsx | 25 ++++++++++++++++--- .../__snapshots__/PasswordInput.spec.tsx.snap | 6 ++++- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/packages/fuselage/src/components/PasswordInput/PasswordInput.spec.tsx b/packages/fuselage/src/components/PasswordInput/PasswordInput.spec.tsx index 8deb7a79bb..6aeee9b8fc 100644 --- a/packages/fuselage/src/components/PasswordInput/PasswordInput.spec.tsx +++ b/packages/fuselage/src/components/PasswordInput/PasswordInput.spec.tsx @@ -1,4 +1,5 @@ import { composeStories } from '@storybook/react-webpack5'; +import userEvent from '@testing-library/user-event'; import { axe } from 'jest-axe'; import { render } from '../../testing'; @@ -19,4 +20,28 @@ describe('[PasswordInput Component]', () => { const results = await axe(container); expect(results).toHaveNoViolations(); }); + + it('toggles password visibility with an accessible button', async () => { + const { getByLabelText, getByRole } = render(); + const input = getByLabelText('password'); + const toggleButton = getByRole('button', { name: 'Show password' }); + + expect(input).toHaveAttribute('type', 'password'); + expect(toggleButton).toHaveAttribute('aria-pressed', 'false'); + + await userEvent.click(toggleButton); + + expect(input).toHaveAttribute('type', 'text'); + const hideButton = getByRole('button', { name: 'Hide password' }); + expect(hideButton).toHaveAttribute('aria-pressed', 'true'); + + hideButton.focus(); + await userEvent.keyboard('{Enter}'); + + expect(input).toHaveAttribute('type', 'password'); + expect(getByRole('button', { name: 'Show password' })).toHaveAttribute( + 'aria-pressed', + 'false', + ); + }); }); diff --git a/packages/fuselage/src/components/PasswordInput/PasswordInput.tsx b/packages/fuselage/src/components/PasswordInput/PasswordInput.tsx index cd439c4af7..59d4831d5e 100644 --- a/packages/fuselage/src/components/PasswordInput/PasswordInput.tsx +++ b/packages/fuselage/src/components/PasswordInput/PasswordInput.tsx @@ -1,30 +1,49 @@ import { useToggle } from '@rocket.chat/fuselage-hooks'; +import type { KeyboardEvent } from 'react'; import { forwardRef } from 'react'; import { Icon } from '../Icon'; import { InputBox, type InputBoxProps } from '../InputBox'; -// TODO: fix a11y issues - export type PasswordInputProps = Omit; const PasswordInput = forwardRef( - function PasswordInput(props, ref) { + function PasswordInput({ disabled, ...props }, ref) { const [hidden, toggle] = useToggle(true); const handleAddonClick = () => { + if (disabled) { + return; + } + toggle(); }; + const handleAddonKeyDown = (event: KeyboardEvent) => { + if (event.key !== 'Enter' && event.key !== ' ') { + return; + } + + event.preventDefault(); + handleAddonClick(); + }; return ( } + disabled={disabled} ref={ref} {...props} /> diff --git a/packages/fuselage/src/components/PasswordInput/__snapshots__/PasswordInput.spec.tsx.snap b/packages/fuselage/src/components/PasswordInput/__snapshots__/PasswordInput.spec.tsx.snap index 842816429d..d286e3a31c 100644 --- a/packages/fuselage/src/components/PasswordInput/__snapshots__/PasswordInput.spec.tsx.snap +++ b/packages/fuselage/src/components/PasswordInput/__snapshots__/PasswordInput.spec.tsx.snap @@ -16,8 +16,12 @@ exports[`[PasswordInput Component] renders without crashing 1`] = ` class="rcx-box rcx-box--full rcx-input-box__addon" >