(function Profile
styles.layout,
inline && styles.layoutInline,
dialog !== null && !inline && styles.layoutInDialog,
+ fullscreen && styles.layoutFullscreen,
),
)}
>
@@ -209,6 +230,32 @@ type NavItemMode = 'tab' | 'option';
// `Profile.Nav` children render again as the select's options.
const NavItemModeContext = React.createContext
('tab');
+function BackButton() {
+ const m = useMessages('profile');
+ return (
+
+
+
+
+
+ {m.backToApp}
+
+
+ );
+}
+
function NavBranding() {
return (
@@ -225,8 +272,18 @@ const Nav = React.forwardRef(function ProfileNav(
{ children, render, xstyle, ...rest },
ref,
) {
- const { titleId, renderBranding, compact, navLayout, navOpen, closeNav, setNavItems, navTriggerRef, inline } =
- useProfileContext('Profile.Nav');
+ const {
+ titleId,
+ renderBranding,
+ compact,
+ navLayout,
+ navOpen,
+ closeNav,
+ setNavItems,
+ navTriggerRef,
+ inline,
+ dismiss,
+ } = useProfileContext('Profile.Nav');
useSafeLayoutEffect(() => {
setNavItems(children);
}, [children, setNavItems]);
@@ -244,6 +301,7 @@ const Nav = React.forwardRef(function ProfileNav(
),
children: (
<>
+ {dismiss === 'back' ? : null}
{children}
diff --git a/packages/mosaic/src/features/organization-profile/__tests__/organization-profile.view.test.tsx b/packages/mosaic/src/features/organization-profile/__tests__/organization-profile.view.test.tsx
index d26c79c8c8d..52dcbb5d6f2 100644
--- a/packages/mosaic/src/features/organization-profile/__tests__/organization-profile.view.test.tsx
+++ b/packages/mosaic/src/features/organization-profile/__tests__/organization-profile.view.test.tsx
@@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react';
import { expect, it, vi } from 'vitest';
+import { Dialog } from '../../../components/dialog';
import { MosaicProvider } from '../../../mosaic-provider';
import { OrganizationProfileView } from '../organization-profile.view';
@@ -37,3 +38,22 @@ it('warns when there are no pages to show', () => {
expect(warn).toHaveBeenCalledWith('[Clerk] OrganizationProfile has no pages to show.');
warn.mockRestore();
});
+
+it('replaces the corner dismiss with a back button in a fullscreen dialog', () => {
+ render(
+
+
+
+
+
+
+ ,
+ );
+
+ expect(screen.getByRole('dialog')).toContainElement(screen.getByRole('button', { name: 'Back to app' }));
+ expect(screen.queryByRole('button', { name: 'Close' })).not.toBeInTheDocument();
+});
diff --git a/packages/mosaic/src/features/user-profile/__tests__/user-profile.view.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile.view.test.tsx
index 429f4444d76..fb957f6646b 100644
--- a/packages/mosaic/src/features/user-profile/__tests__/user-profile.view.test.tsx
+++ b/packages/mosaic/src/features/user-profile/__tests__/user-profile.view.test.tsx
@@ -153,6 +153,35 @@ describe('UserProfileView', () => {
expect(popup).toContainElement(screen.getByRole('tab', { name: 'Security' }));
});
+ it('replaces the close button with a back button in a fullscreen dialog', async () => {
+ const onOpenChange = vi.fn();
+ render(
+
+
+
+
+
+
+ ,
+ );
+
+ const popup = screen.getByRole('dialog', { name: 'User profile' });
+ expect(screen.queryByRole('button', { name: 'Close' })).not.toBeInTheDocument();
+ const back = screen.getByRole('button', { name: 'Back to app' });
+ expect(popup).toContainElement(back);
+
+ await userEvent.setup().click(back);
+
+ expect(onOpenChange).toHaveBeenCalledWith(false, expect.anything());
+ });
+
it('warns when there are no pages to show', () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
diff --git a/packages/mosaic/src/localization/registry.ts b/packages/mosaic/src/localization/registry.ts
index 236c54566fc..772317ed242 100644
--- a/packages/mosaic/src/localization/registry.ts
+++ b/packages/mosaic/src/localization/registry.ts
@@ -1,4 +1,5 @@
import { formMessages } from '../components/form/form.messages';
+import { profileMessages } from '../components/profile/profile.messages';
import { apiKeysTableMessages } from '../features/api-keys/api-keys-table.messages';
import { invitationsTableTabMessages } from '../features/organization-profile/invitations-table-tab.messages';
import { membersTableTabMessages } from '../features/organization-profile/members-table-tab.messages';
@@ -40,6 +41,7 @@ export const mosaicMessages = {
organizationProfileDangerSection: organizationProfileDangerSectionMessages,
organizationProfileProfileSection: organizationProfileProfileSectionMessages,
organizationProfileInviteMembers: organizationProfileInviteMembersMessages,
+ profile: profileMessages,
reverification: reverificationMessages,
userButton: userButtonMessages,
userProfile: userProfileMessages,
diff --git a/packages/swingset/src/stories/organization-profile.mdx b/packages/swingset/src/stories/organization-profile.mdx
index 19d70d381d3..194739d8749 100644
--- a/packages/swingset/src/stories/organization-profile.mdx
+++ b/packages/swingset/src/stories/organization-profile.mdx
@@ -26,3 +26,13 @@ and animates the popup; the profile paints it, names it, and carries the dismiss
name='Overlay'
storyModule={Stories}
/>
+
+## As a full screen overlay
+
+The same view inside a `fullscreen` dialog, which fills the viewport. A back button above the
+navigation dismisses it in place of the corner close button.
+
+
diff --git a/packages/swingset/src/stories/organization-profile.stories.tsx b/packages/swingset/src/stories/organization-profile.stories.tsx
index c62fdbfe65a..5b6ebcbb853 100644
--- a/packages/swingset/src/stories/organization-profile.stories.tsx
+++ b/packages/swingset/src/stories/organization-profile.stories.tsx
@@ -51,3 +51,14 @@ export function Overlay() {
);
}
+
+export function Fullscreen() {
+ return (
+
+ }>Manage organization
+
+
+
+
+ );
+}
diff --git a/packages/swingset/src/stories/user-profile.mdx b/packages/swingset/src/stories/user-profile.mdx
index 5fd35d80134..c9277186ba3 100644
--- a/packages/swingset/src/stories/user-profile.mdx
+++ b/packages/swingset/src/stories/user-profile.mdx
@@ -28,3 +28,13 @@ and animates the popup; the profile paints it, names it, and carries the dismiss
name='Overlay'
storyModule={Stories}
/>
+
+## As a full screen overlay
+
+The same view inside a `fullscreen` dialog, which fills the viewport. A back button above the
+navigation dismisses it in place of the corner close button.
+
+
diff --git a/packages/swingset/src/stories/user-profile.stories.tsx b/packages/swingset/src/stories/user-profile.stories.tsx
index 04a8034c036..0ef8a86a063 100644
--- a/packages/swingset/src/stories/user-profile.stories.tsx
+++ b/packages/swingset/src/stories/user-profile.stories.tsx
@@ -53,3 +53,19 @@ export function Overlay() {
);
}
+
+export function Fullscreen() {
+ const { activePage, setActivePage, pages } = useUserProfileFixture();
+ return (
+
+ }>Manage account
+
+
+
+
+ );
+}