From ab7e0bce1eca1d48451790d8bdf329c888045a98 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Tue, 6 Oct 2026 13:51:25 -0400 Subject: [PATCH] feat(mosaic): name the active organization in the UserButton header --- .../mosaic-user-button-header-organization.md | 5 ++++ .../__tests__/user-button.feature.test.tsx | 3 ++- .../features/user-button/user-button.view.tsx | 24 ++++++++++++------- 3 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 .changeset/mosaic-user-button-header-organization.md diff --git a/.changeset/mosaic-user-button-header-organization.md b/.changeset/mosaic-user-button-header-organization.md new file mode 100644 index 00000000000..451f4c3dace --- /dev/null +++ b/.changeset/mosaic-user-button-header-organization.md @@ -0,0 +1,5 @@ +--- +'@clerk/mosaic': patch +--- + +Show the active organization's name, instead of the account's email or username, under the user's name in the `UserButton` popover header when an organization is active in combined mode. diff --git a/packages/mosaic/src/features/user-button/__tests__/user-button.feature.test.tsx b/packages/mosaic/src/features/user-button/__tests__/user-button.feature.test.tsx index 8d8909398a0..b6e04685760 100644 --- a/packages/mosaic/src/features/user-button/__tests__/user-button.feature.test.tsx +++ b/packages/mosaic/src/features/user-button/__tests__/user-button.feature.test.tsx @@ -110,6 +110,7 @@ function requiredPopup() { const reading = (...names: string[]) => within(requiredPopup()) .queryAllByText(new RegExp(`^(${names.join('|')})$`)) + .filter(node => !node.closest('.cl-user-button-header')) .map(node => node.textContent); const current = () => @@ -788,7 +789,6 @@ describe('UserButton', () => { await openWithList(); expect(reading('Personal account', 'Acme', 'Other', 'Create organization')).toEqual([ - 'Acme', 'Acme', 'Personal account', 'Other', @@ -809,6 +809,7 @@ describe('UserButton', () => { const user = await open(); expect(within(requiredPopup()).getByText('Alice Smith')).toBeInTheDocument(); + expect(requiredPopup().querySelector('.cl-user-button-header-description')).toHaveTextContent('Acme'); expect(within(requiredPopup()).getByRole('button', { name: 'Invite' })).toBeInTheDocument(); await openSettings(user, 'Organization settings'); expect(openOrganizationProfile).toHaveBeenCalled(); diff --git a/packages/mosaic/src/features/user-button/user-button.view.tsx b/packages/mosaic/src/features/user-button/user-button.view.tsx index 51791d865c7..f69b7e83848 100644 --- a/packages/mosaic/src/features/user-button/user-button.view.tsx +++ b/packages/mosaic/src/features/user-button/user-button.view.tsx @@ -160,6 +160,21 @@ function membershipSubtitle(membership: UserButtonMembership, m: Messages, local return joinDetails(membership.planLabel, members); } +function headerSubtitle(lead: LeadSelection, identifier: string, m: Messages, locale: string): string { + if (lead.kind === 'organization') { + return membershipSubtitle(lead.organization, m, locale); + } + if (lead.kind === 'none') { + // No selection is not the account, so it carries no identifier line. + return ''; + } + if (lead.badge) { + return lead.badge.name; + } + // An account with no name is titled by its identifier, and repeating it underneath says nothing. + return identifier === lead.name ? '' : identifier; +} + // ─── Presentational leaves ────────────────────────────────────────────────── /** @@ -437,14 +452,7 @@ function Header() { const lead = leadSelection(data, m); const { name } = lead; const organization = leadOrganization(lead); - // An account with no name is titled by its identifier, and repeating it underneath says nothing. - // No selection is not the account, so it carries no identifier line either. - const subtitle = - lead.kind === 'organization' - ? membershipSubtitle(lead.organization, m, locale) - : lead.kind === 'user' && identifier !== name - ? identifier - : ''; + const subtitle = headerSubtitle(lead, identifier, m, locale); const actions: HeaderAction[] = []; for (const action of data.layout.actions.header) {