diff --git a/.changeset/mosaic-switcher-group-labels.md b/.changeset/mosaic-switcher-group-labels.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/mosaic-switcher-group-labels.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/mosaic/src/features/organization-switcher/__tests__/organization-switcher.feature.test.tsx b/packages/mosaic/src/features/organization-switcher/__tests__/organization-switcher.feature.test.tsx index 4c617211702..2a424929365 100644 --- a/packages/mosaic/src/features/organization-switcher/__tests__/organization-switcher.feature.test.tsx +++ b/packages/mosaic/src/features/organization-switcher/__tests__/organization-switcher.feature.test.tsx @@ -107,6 +107,11 @@ const reading = (...names: string[]) => .filter(node => !node.closest('.cl-organization-switcher-header')) .map(node => node.textContent); +const groupNames = () => + within(requiredPopup()) + .getAllByRole('group') + .map(group => group.getAttribute('aria-label')); + describe('OrganizationSwitcher', () => { it('renders nothing while Clerk is still loading', async () => { serveFapi(signedIn()); @@ -153,6 +158,20 @@ describe('OrganizationSwitcher', () => { expect(openOrganizationProfile).toHaveBeenCalled(); }); + it('groups the organizations apart from the actions on the active one, naming each', async () => { + await renderSwitcher({}, noOffers()); + await openWithList(); + + expect(groupNames()).toEqual(['Organizations', 'Organization actions']); + const organizations = screen.getByRole('group', { name: 'Organizations' }); + expect(within(organizations).getByText('Acme').closest('[aria-current="true"]')).not.toBeNull(); + expect(within(organizations).getByRole('button', { name: 'Other' })).toBeInTheDocument(); + expect(within(organizations).getByRole('button', { name: 'Add organization' })).toBeInTheDocument(); + const actions = screen.getByRole('group', { name: 'Organization actions' }); + expect(within(actions).getByRole('button', { name: 'Settings' })).toBeInTheDocument(); + expect(within(actions).getByRole('button', { name: 'Invite members' })).toBeInTheDocument(); + }); + it('falls back to the account, with nothing to manage, where no organization is active', async () => { await renderSwitcher({}, signedIn({ client: fapiClient([personalSession]) })); await open(); @@ -172,6 +191,7 @@ describe('OrganizationSwitcher', () => { expect(within(requiredPopup()).queryByText('Alice Smith')).toBeNull(); expect(screen.queryByRole('button', { name: 'Settings' })).toBeNull(); expect(screen.queryByRole('button', { name: 'Invite members' })).toBeNull(); + expect(screen.queryByRole('group', { name: 'Organization actions' })).toBeNull(); }); it('carries no account rows, and trails the organizations with create-organization', async () => { diff --git a/packages/mosaic/src/features/organization-switcher/organization-switcher.messages.ts b/packages/mosaic/src/features/organization-switcher/organization-switcher.messages.ts index 42dfee1fba7..39a7313b6d2 100644 --- a/packages/mosaic/src/features/organization-switcher/organization-switcher.messages.ts +++ b/packages/mosaic/src/features/organization-switcher/organization-switcher.messages.ts @@ -15,6 +15,10 @@ export const organizationSwitcherMessages = { requested: 'Requested', pending: 'pending', }, + groups: { + organizations: 'Organizations', + organization: 'Organization actions', + }, manage: { invite: 'Invite members', settings: 'Settings', diff --git a/packages/mosaic/src/features/switcher/switcher-item.view.tsx b/packages/mosaic/src/features/switcher/switcher-item.view.tsx index b06e3fd3342..efda49b9162 100644 --- a/packages/mosaic/src/features/switcher/switcher-item.view.tsx +++ b/packages/mosaic/src/features/switcher/switcher-item.view.tsx @@ -96,9 +96,17 @@ export function SwitcherItemTrailing({ xstyle, ...rest }: MosaicElementProps<'di ); } -export function SwitcherGroup({ xstyle, ...rest }: MosaicElementProps<'div'>) { +export type SwitcherGroupProps = MosaicElementProps<'div'> & { label: string }; + +export function SwitcherGroup({ label, xstyle, ...rest }: SwitcherGroupProps) { const slot = useSlot(); - return
; + return ( + + ); } export function SwitcherSeparator({ xstyle, ...rest }: MosaicElementProps<'hr'>) { diff --git a/packages/mosaic/src/features/switcher/switcher.view.tsx b/packages/mosaic/src/features/switcher/switcher.view.tsx index 5ae81dcbfba..79ec1069fb3 100644 --- a/packages/mosaic/src/features/switcher/switcher.view.tsx +++ b/packages/mosaic/src/features/switcher/switcher.view.tsx @@ -761,7 +761,10 @@ function OrganizationSection() { {/* `auto` rather than `stable`: a reserved gutter insets the rows whether or not the list overflows, so short lists would sit their avatars and icons off the edge the header and footer align to. */} -