diff --git a/.changeset/user-button-a11y.md b/.changeset/user-button-a11y.md new file mode 100644 index 00000000000..39b9a4cb99c --- /dev/null +++ b/.changeset/user-button-a11y.md @@ -0,0 +1,7 @@ +--- +'@clerk/ui': patch +'@clerk/shared': patch +'@clerk/localizations': patch +--- + +Fix UserButton popover accessibility: use `role="dialog"` with grouped actions instead of `role="menu"` with `menuitem` children, fix focus management via floating-ui's interaction system, and add identity-first trigger labels diff --git a/integration/tests/astro/components.test.ts b/integration/tests/astro/components.test.ts index 4919fa96ec8..17f87251d2e 100644 --- a/integration/tests/astro/components.test.ts +++ b/integration/tests/astro/components.test.ts @@ -93,7 +93,7 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f await u.po.userButton.toHaveVisibleMenuItems([/Custom link/i, /Custom action/i, /Custom click/i]); // Click custom action and check for custom page availbility - await u.page.getByRole('menuitem', { name: /Custom action/i }).click(); + await u.page.getByRole('button', { name: /Custom action/i }).click(); await u.po.userProfile.waitForUserProfileModal(); await expect(u.page.getByRole('heading', { name: 'Custom Terms Page' })).toBeVisible(); @@ -114,7 +114,7 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f ); }); }); - await u.page.getByRole('menuitem', { name: /Custom click/i }).click(); + await u.page.getByRole('button', { name: /Custom click/i }).click(); expect(await eventPromise).toBe('custom_click'); // Trigger the popover again @@ -122,7 +122,7 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f await u.po.userButton.waitForPopover(); // Click custom link and check navigation - await u.page.getByRole('menuitem', { name: /Custom link/i }).click(); + await u.page.getByRole('button', { name: /Custom link/i }).click(); await u.page.waitForAppUrl('/user'); }); @@ -139,7 +139,7 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f await u.po.userButton.waitForPopover(); // First item should now be the sign out button - await u.page.getByRole('menuitem').first().click(); + await u.page.getByRole('button').first().click(); await u.po.expect.toBeSignedOut(); }); diff --git a/integration/tests/sign-out-smoke.test.ts b/integration/tests/sign-out-smoke.test.ts index 6b040080bd5..df5624c3fe4 100644 --- a/integration/tests/sign-out-smoke.test.ts +++ b/integration/tests/sign-out-smoke.test.ts @@ -86,7 +86,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('sign out await u.page.getByRole('link', { name: 'Home' }).click(); await u.page.getByRole('button', { name: 'Open user menu' }).click(); - await u.page.getByRole('menuitem', { name: 'Sign out' }).click(); + await u.page.getByRole('button', { name: 'Sign out' }).click(); await u.po.expect.toBeSignedOut(); await u.page.getByRole('link', { name: 'Protected', exact: true }).click(); await u.page.waitForURL(url => url.href.includes('/sign-in?redirect_url')); diff --git a/integration/tests/vue/components.test.ts b/integration/tests/vue/components.test.ts index c7966a53d34..7e3b9844f18 100644 --- a/integration/tests/vue/components.test.ts +++ b/integration/tests/vue/components.test.ts @@ -80,7 +80,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te await u.po.userButton.toHaveVisibleMenuItems([/Custom link/i, /Custom page/i, /Custom action/i]); // Click custom action - await u.page.getByRole('menuitem', { name: /Custom action/i }).click(); + await u.page.getByRole('button', { name: /Custom action/i }).click(); await expect(u.page.getByText('Is action clicked: true')).toBeVisible(); // Trigger the popover again @@ -88,7 +88,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te await u.po.userButton.waitForPopover(); // Click custom action and check for custom page availbility - await u.page.getByRole('menuitem', { name: /Custom page/i }).click(); + await u.page.getByRole('button', { name: /Custom page/i }).click(); await u.po.userProfile.waitForUserProfileModal(); await expect(u.page.getByRole('heading', { name: 'Custom Terms Page' })).toBeVisible(); @@ -98,7 +98,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te await u.po.userButton.waitForPopover(); // Click custom link and check navigation - await u.page.getByRole('menuitem', { name: /Custom link/i }).click(); + await u.page.getByRole('button', { name: /Custom link/i }).click(); await u.page.waitForAppUrl('/profile'); }); @@ -115,7 +115,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te await u.po.userButton.waitForPopover(); // First item should now be the sign out button - await u.page.getByRole('menuitem').first().click(); + await u.page.getByRole('button').first().click(); await u.po.expect.toBeSignedOut(); }); @@ -132,7 +132,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te // Open UserProfile modal through UserButton await u.po.userButton.toggleTrigger(); await u.po.userButton.waitForPopover(); - await u.page.getByRole('menuitem', { name: /Manage account/i }).click(); + await u.page.getByRole('button', { name: /Manage account/i }).click(); await u.po.userProfile.waitForUserProfileModal(); // Verify custom pages and links are visible in the UserProfile diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index a9a83381088..6dfac4082ef 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -1454,11 +1454,14 @@ export const enUS: LocalizationResource = { }, userButton: { action__addAccount: 'Add account', - action__closeUserMenu: 'Close user menu', + action__closeUserMenu: '{{name}} - Close account panel', action__manageAccount: 'Manage account', - action__openUserMenu: 'Open user menu', + action__openUserMenu: '{{name}} - Open account panel', action__signOut: 'Sign out', action__signOutAll: 'Sign out of all accounts', + label__userButtonPopover: 'Account panel', + label__accountActions: 'Account actions', + label__activeSessions: 'Active sessions', }, userProfile: { apiKeysPage: { diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index db7df988428..9077f4c7d5d 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -995,8 +995,11 @@ export type __internal_LocalizationResource = { action__signOut: LocalizationValue; action__signOutAll: LocalizationValue; action__addAccount: LocalizationValue; - action__openUserMenu: LocalizationValue; - action__closeUserMenu: LocalizationValue; + action__openUserMenu: LocalizationValue<'name'>; + action__closeUserMenu: LocalizationValue<'name'>; + label__userButtonPopover?: LocalizationValue; + label__accountActions?: LocalizationValue; + label__activeSessions?: LocalizationValue; }; organizationSwitcher: { personalWorkspace: LocalizationValue; diff --git a/packages/testing/src/playwright/unstable/page-objects/userButton.ts b/packages/testing/src/playwright/unstable/page-objects/userButton.ts index 69804cc4ef3..e64acd36edf 100644 --- a/packages/testing/src/playwright/unstable/page-objects/userButton.ts +++ b/packages/testing/src/playwright/unstable/page-objects/userButton.ts @@ -23,14 +23,14 @@ export const createUserButtonPageObject = (testArgs: { page: EnhancedPage }) => menuItems = [menuItems]; } for (const menuItem of menuItems) { - await expect(page.getByRole('menuitem', { name: menuItem })).toBeVisible(); + await expect(page.getByRole('button', { name: menuItem })).toBeVisible(); } }, triggerSignOut: () => { - return page.getByRole('menuitem', { name: /Sign out$/i }).click(); + return page.getByRole('button', { name: /Sign out$/i }).click(); }, triggerManageAccount: () => { - return page.getByRole('menuitem', { name: /Manage account/i }).click(); + return page.getByRole('button', { name: /Manage account/i }).click(); }, switchAccount: (emailAddress: string) => { return page.getByText(emailAddress).click(); diff --git a/packages/ui/src/components/OrganizationList/OrganizationListPage.tsx b/packages/ui/src/components/OrganizationList/OrganizationListPage.tsx index db8937232d2..0efb4ec7e23 100644 --- a/packages/ui/src/components/OrganizationList/OrganizationListPage.tsx +++ b/packages/ui/src/components/OrganizationList/OrganizationListPage.tsx @@ -155,7 +155,7 @@ export const OrganizationListPageList = (props: { onCreateOrganizationClick: () ({ margin: `${t.space.$none} ${t.space.$5}` })}>{card.error} - + {(userMemberships.count || 0) > 0 && userMemberships.data?.map(inv => { diff --git a/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx b/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx index 78356967931..a355535300b 100644 --- a/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx +++ b/packages/ui/src/components/OrganizationList/__tests__/OrganizationList.test.tsx @@ -31,7 +31,7 @@ describe('OrganizationList', () => { expect(queryByText('to continue to TestApp')).toBeInTheDocument(); // expect(queryByText('Personal account')).toBeInTheDocument(); - expect(queryByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument(); + expect(queryByRole('button', { name: 'Create organization' })).toBeInTheDocument(); }); }); @@ -78,7 +78,7 @@ describe('OrganizationList', () => { // Display membership expect(queryByText('Org1')).toBeInTheDocument(); - expect(queryByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument(); + expect(queryByRole('button', { name: 'Create organization' })).toBeInTheDocument(); }); }); @@ -236,7 +236,7 @@ describe('OrganizationList', () => { expect(queryByRole('button', { name: 'Upload' })).not.toBeInTheDocument(); expect(queryByLabelText(/name/i)).not.toBeInTheDocument(); expect(queryByLabelText(/slug/i)).not.toBeInTheDocument(); - await userEvent.click(getByRole('menuitem', { name: 'Create organization' })); + await userEvent.click(getByRole('button', { name: 'Create organization' })); // Header expect(queryByRole('heading', { name: /Create organization/i })).toBeInTheDocument(); // Form fields of CreateOrganizationForm @@ -266,9 +266,9 @@ describe('OrganizationList', () => { }, ); await waitFor(async () => - expect(await findByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument(), + expect(await findByRole('button', { name: 'Create organization' })).toBeInTheDocument(), ); - await userEvent.click(getByRole('menuitem', { name: 'Create organization' })); + await userEvent.click(getByRole('button', { name: 'Create organization' })); await waitFor(async () => expect(await findByLabelText(/name/i)).toBeInTheDocument()); await userEvent.type(getByLabelText(/name/i), 'new org'); await userEvent.click(getByRole('button', { name: /create organization/i })); @@ -290,9 +290,9 @@ describe('OrganizationList', () => { const { findByRole, getByRole, userEvent, queryByLabelText } = render(, { wrapper }); await waitFor(async () => - expect(await findByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument(), + expect(await findByRole('button', { name: 'Create organization' })).toBeInTheDocument(), ); - await userEvent.click(getByRole('menuitem', { name: 'Create organization' })); + await userEvent.click(getByRole('button', { name: 'Create organization' })); expect(queryByLabelText(/Name/i)).toBeInTheDocument(); expect(queryByLabelText(/Slug/i)).not.toBeInTheDocument(); }); @@ -310,9 +310,9 @@ describe('OrganizationList', () => { const { findByRole, getByRole, userEvent, queryByLabelText } = render(, { wrapper }); await waitFor(async () => - expect(await findByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument(), + expect(await findByRole('button', { name: 'Create organization' })).toBeInTheDocument(), ); - await userEvent.click(getByRole('menuitem', { name: 'Create organization' })); + await userEvent.click(getByRole('button', { name: 'Create organization' })); expect(queryByLabelText(/Name/i)).toBeInTheDocument(); expect(queryByLabelText(/Slug/i)).toBeInTheDocument(); }); @@ -445,9 +445,9 @@ describe('OrganizationList', () => { fixtures.clerk.setActive.mockReturnValue(Promise.resolve()); await waitFor(async () => - expect(await findByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument(), + expect(await findByRole('button', { name: 'Create organization' })).toBeInTheDocument(), ); - await userEvent.click(getByRole('menuitem', { name: 'Create organization' })); + await userEvent.click(getByRole('button', { name: 'Create organization' })); await waitFor(async () => expect(await findByLabelText(/name/i)).toBeInTheDocument()); await userEvent.type(getByLabelText(/name/i), 'new org'); await userEvent.click(getByRole('button', { name: /create organization/i })); diff --git a/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx b/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx index 52c43d01821..2f6aeeab5c0 100644 --- a/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx +++ b/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx @@ -127,7 +127,7 @@ export const OrganizationSwitcherPopover = React.forwardRef - {manageOrganizationButton} + {manageOrganizationButton} ); @@ -142,10 +142,7 @@ export const OrganizationSwitcherPopover = React.forwardRef - + {currentOrg ? selectedOrganizationPreview(currentOrg) : !hidePersonal && ( diff --git a/packages/ui/src/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx b/packages/ui/src/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx index 337bd35c83d..b0e75dc3529 100644 --- a/packages/ui/src/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx +++ b/packages/ui/src/components/OrganizationSwitcher/UserInvitationSuggestionList.tsx @@ -180,7 +180,6 @@ const InvitationPreview = withCardStateProvider( elementDescriptor={descriptors.organizationSwitcherPreviewButton} icon={SwitchArrowRight} onClick={acceptedOrganization ? () => onOrganizationClick(acceptedOrganization) : undefined} - role='menuitem' > & { showBorder: boolean }) => { const { showBorder, ...restProps } = props; - return ( - - ); + return ; }; export const SuggestionPreview = withCardStateProvider((props: OrganizationSuggestionResource) => { diff --git a/packages/ui/src/components/OrganizationSwitcher/UserMembershipList.tsx b/packages/ui/src/components/OrganizationSwitcher/UserMembershipList.tsx index 35f814b4782..9dd88ab1aae 100644 --- a/packages/ui/src/components/OrganizationSwitcher/UserMembershipList.tsx +++ b/packages/ui/src/components/OrganizationSwitcher/UserMembershipList.tsx @@ -83,7 +83,6 @@ export const UserMembershipList = (props: UserMembershipListProps) => { elementId={descriptors.organizationSwitcherPreviewButton.setId('personal')} icon={SwitchArrowRight} onClick={onPersonalWorkspaceClick} - role='menuitem' > { elementId={descriptors.organizationSwitcherPreviewButton.setId('organization')} icon={SwitchArrowRight} onClick={() => onOrganizationClick(organization)} - role='menuitem' sx={t => ({ border: `0 solid ${t.colors.$borderAlpha100}`, })} diff --git a/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx b/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx index 8e1009061d7..ffc55f53b79 100644 --- a/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx +++ b/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx @@ -358,7 +358,7 @@ describe('OrganizationSwitcher', () => { props.setProps({ hidePersonal: true }); const { getByRole, userEvent } = render(, { wrapper }); await userEvent.click(getByRole('button')); - await userEvent.click(getByRole('menuitem')); + await userEvent.click(getByRole('button', { name: /manage/i })); expect(fixtures.clerk.openOrganizationProfile).toHaveBeenCalled(); }); @@ -375,7 +375,7 @@ describe('OrganizationSwitcher', () => { props.setProps({ hidePersonal: true }); const { getByRole, userEvent } = render(, { wrapper }); await userEvent.click(getByRole('button', { name: 'Open organization switcher' })); - await userEvent.click(getByRole('menuitem', { name: 'Create organization' })); + await userEvent.click(getByRole('button', { name: 'Create organization' })); expect(fixtures.clerk.openCreateOrganization).toHaveBeenCalled(); }); @@ -391,7 +391,7 @@ describe('OrganizationSwitcher', () => { const { getByRole, queryByLabelText, userEvent } = render(, { wrapper }); await userEvent.click(getByRole('button', { name: 'Open organization switcher' })); - await userEvent.click(getByRole('menuitem', { name: 'Create organization' })); + await userEvent.click(getByRole('button', { name: 'Create organization' })); expect(fixtures.clerk.openCreateOrganization).toHaveBeenCalled(); expect(queryByLabelText(/Slug/i)).not.toBeInTheDocument(); }); @@ -619,7 +619,7 @@ describe('OrganizationSwitcher', () => { ); await userEvent.click(getByRole('button')); - const manageButton = await waitFor(() => screen.getByRole('menuitem', { name: /manage/i })); + const manageButton = await waitFor(() => screen.getByRole('button', { name: /manage/i })); await userEvent.click(manageButton); expect(fixtures.clerk.openOrganizationProfile).toHaveBeenCalledWith(expect.objectContaining({ getContainer })); @@ -649,7 +649,7 @@ describe('OrganizationSwitcher', () => { ); await userEvent.click(getByRole('button', { name: 'Open organization switcher' })); - const createButton = await waitFor(() => screen.getByRole('menuitem', { name: 'Create organization' })); + const createButton = await waitFor(() => screen.getByRole('button', { name: 'Create organization' })); await userEvent.click(createButton); expect(fixtures.clerk.openCreateOrganization).toHaveBeenCalledWith(expect.objectContaining({ getContainer })); diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx index 972fb3682ed..3e4aed265ba 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx @@ -62,7 +62,7 @@ export const ChooseOrganizationScreen = (props: ChooseOrganizationScreenProps) = ({ margin: `${t.space.$none} ${t.space.$8}` })}>{card.error} - + {(userMemberships.count || 0) > 0 && userMemberships.data?.map(inv => { return ( diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx index 6d735baffdc..779819b5665 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx @@ -51,7 +51,6 @@ export const SetupMfaStartScreen = withCardStateProvider((props: SetupMfaStartSc )} ({ borderTopWidth: t.borderWidths.$normal, diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SmsCodeFlowScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SmsCodeFlowScreen.tsx index f37c899cde5..a62f99e644f 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SmsCodeFlowScreen.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SmsCodeFlowScreen.tsx @@ -299,7 +299,6 @@ const SmsCodeScreen = withCardStateProvider((props: SmsCodeScreenProps) => { )} ({ borderTopWidth: t.borderWidths.$normal, diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx index 1323ae0e5d5..21cb1714c82 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx @@ -466,7 +466,7 @@ describe('TaskSetupMFA', () => { await findByText(/add sms code verification/i); await findByText(/choose phone number you want to use/i); await findByText(/\+30 691 1111111/i); - expect(getByRole('menuitem', { name: /add phone number/i })).toBeInTheDocument(); + expect(getByRole('button', { name: /add phone number/i })).toBeInTheDocument(); }); it('should show add phone screen when no existing phone numbers', async () => { @@ -511,7 +511,7 @@ describe('TaskSetupMFA', () => { await findByText(/add sms code verification/i); await act(async () => { - await userEvent.click(getByRole('menuitem', { name: /add phone number/i })); + await userEvent.click(getByRole('button', { name: /add phone number/i })); }); await findByText(/add phone number/i); diff --git a/packages/ui/src/components/SignIn/SignInAccountSwitcher.tsx b/packages/ui/src/components/SignIn/SignInAccountSwitcher.tsx index 355cf79c9b0..f05028467d5 100644 --- a/packages/ui/src/components/SignIn/SignInAccountSwitcher.tsx +++ b/packages/ui/src/components/SignIn/SignInAccountSwitcher.tsx @@ -45,7 +45,7 @@ const SignInAccountSwitcherInternal = () => { borderTopColor: t.colors.$borderAlpha100, })} > - + {signedInSessions.map(s => ( { const { handleManageAccountClicked, handleSignOutSessionClicked, handleUserProfileActionClicked, session } = props; const { menutItems } = useUserButtonContext(); + const { t } = useLocalizations(); const commonActionSx: ThemableCssProp = t => ({ borderTopWidth: t.borderWidths.$normal, @@ -54,7 +55,8 @@ export const SingleSessionActions = (props: SingleSessionActionsProps) => { return ( ({ @@ -138,6 +140,7 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => { } = props; const { menutItems } = useUserButtonContext(); + const { t } = useLocalizations(); const handleActionClick = async (route: MenuItem) => { if (route?.path) { @@ -164,7 +167,8 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => { <> {hasOnlyDefaultItems ? ( @@ -200,7 +204,8 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => { ) : ( ({ @@ -267,7 +272,8 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => { )} ({ borderTopStyle: t.borderStyles.$solid, borderTopWidth: t.borderWidths.$normal, @@ -279,7 +285,6 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => { key={session.id} icon={SwitchArrowRight} onClick={handleSessionClicked(session)} - role='menuitem' > @@ -294,6 +299,7 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => { icon={Add} label={localizationKeys('userButton.action__addAccount')} onClick={handleAddAccountClicked} + role={undefined} iconSx={t => ({ width: t.sizes.$9, height: t.sizes.$6, @@ -336,9 +342,11 @@ export const SignOutAllActions = (props: SignOutAllActionsProps) => { sx, actionSx, } = props; + const { t } = useLocalizations(); return ( ({ padding: t.space.$2, @@ -358,6 +366,7 @@ export const SignOutAllActions = (props: SignOutAllActionsProps) => { onClick={handleSignOutAllClicked} variant='ghost' colorScheme='neutral' + role={undefined} sx={[ t => ({ backgroundColor: t.colors.$transparent, diff --git a/packages/ui/src/components/UserButton/UserButtonPopover.tsx b/packages/ui/src/components/UserButton/UserButtonPopover.tsx index 28aca51b525..6eab611bf1f 100644 --- a/packages/ui/src/components/UserButton/UserButtonPopover.tsx +++ b/packages/ui/src/components/UserButton/UserButtonPopover.tsx @@ -7,7 +7,7 @@ import { RootBox } from '@/ui/elements/RootBox'; import { UserPreview } from '@/ui/elements/UserPreview'; import { useEnvironment, useUserButtonContext } from '../../contexts'; -import { descriptors } from '../../customizables'; +import { descriptors, localizationKeys, useLocalizations } from '../../customizables'; import type { PropsOfComponent } from '../../styledSystem'; import { MultiSessionActions, SignOutAllActions, SingleSessionActions } from './SessionActions'; import { useMultisessionActions } from './useMultisessionActions'; @@ -22,6 +22,7 @@ export const UserButtonPopover = React.forwardRef diff --git a/packages/ui/src/components/UserButton/UserButtonTrigger.tsx b/packages/ui/src/components/UserButton/UserButtonTrigger.tsx index 64463e0b7a5..5a6b50b4e88 100644 --- a/packages/ui/src/components/UserButton/UserButtonTrigger.tsx +++ b/packages/ui/src/components/UserButton/UserButtonTrigger.tsx @@ -1,3 +1,4 @@ +import { getFullName, getIdentifier } from '@clerk/shared/internal/clerk-js/user'; import { useUser } from '@clerk/shared/react'; import { forwardRef } from 'react'; @@ -19,6 +20,7 @@ export const UserButtonTrigger = withAvatarShimmer( const { user } = useUser(); const { showName } = useUserButtonContext(); const { t } = useLocalizations(); + const userName = (user && (getFullName(user) || getIdentifier(user))) || ''; return (