Skip to content

Commit 99e30f7

Browse files
committed
feat(mosaic): add showSearch to UserProfile and OrganizationProfile
Search is now off by default. Pass showSearch to show it.
1 parent af72325 commit 99e30f7

10 files changed

Lines changed: 104 additions & 9 deletions

File tree

‎packages/mosaic/src/features/organization-profile/__tests__/organization-profile.feature.test.tsx‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ describe('OrganizationProfile', () => {
106106
expect(tabNames()).toEqual(tabs);
107107
});
108108

109+
it.each([
110+
{ showSearch: undefined, shown: false },
111+
{ showSearch: true, shown: true },
112+
])('shows the search only when asked: $showSearch', async ({ showSearch, shown }) => {
113+
serve();
114+
await renderWithClerk(<OrganizationProfile showSearch={showSearch} />);
115+
expect(screen.queryByRole('combobox', { name: 'Search' }) !== null).toBe(shown);
116+
});
117+
109118
it('passes the API keys options to the API keys page', async () => {
110119
serve({ orgAPIKeysEnabled: true, permissions: ['org:sys_api_keys:read', 'org:sys_api_keys:manage'] });
111120
await renderWithClerk(
@@ -141,6 +150,7 @@ describe('OrganizationProfile', () => {
141150
const user = userEvent.setup();
142151
await renderWithClerk(
143152
<OrganizationProfile
153+
showSearch
144154
customPages={[
145155
{
146156
path: 'usage',

‎packages/mosaic/src/features/organization-profile/__tests__/organization-profile.view.test.tsx‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,24 @@ it('renders the content given for the active page', () => {
2424
expect(screen.getByText('Members content')).toBeVisible();
2525
});
2626

27+
it.each([
28+
{ showSearch: undefined, shown: false },
29+
{ showSearch: true, shown: true },
30+
])('shows the search only when asked: $showSearch', ({ showSearch, shown }) => {
31+
render(
32+
<MosaicProvider>
33+
<OrganizationProfileView
34+
activePage='general'
35+
onPageChange={vi.fn()}
36+
showSearch={showSearch}
37+
pages={{ general: <OrganizationProfileGeneralPanelView /> }}
38+
/>
39+
</MosaicProvider>,
40+
);
41+
42+
expect(screen.queryByRole('combobox', { name: 'Search' }) !== null).toBe(shown);
43+
});
44+
2745
it('warns when there are no pages to show', () => {
2846
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
2947

‎packages/mosaic/src/features/organization-profile/organization-profile.tsx‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { OrganizationProfileMembersPanel } from './organization-profile-members-
1717
/** Everything `<OrganizationProfile />` takes: your own pages and their order, the page it opens on, and where leaving the organization lands. */
1818
export interface OrganizationProfileProps
1919
extends
20-
Pick<OrganizationProfileViewProps, 'customPages' | 'pageOrder'>,
20+
Pick<OrganizationProfileViewProps, 'customPages' | 'pageOrder' | 'showSearch'>,
2121
Pick<OrganizationProfileOptions, 'afterLeaveOrganizationUrl'> {
2222
/**
2323
* The page the profile opens on: a built-in page's id, or a custom page's `path`. Unlike
@@ -64,6 +64,12 @@ export interface OrganizationProfileProps
6464
* ```
6565
*
6666
* @example
67+
* `showSearch` adds a search field above the navigation that finds pages, and the sections on them.
68+
* ```tsx
69+
* <OrganizationProfile showSearch />
70+
* ```
71+
*
72+
* @example
6773
* `customPages` adds your own pages; `pageOrder` places them among the built-in ones.
6874
* ```tsx
6975
* <OrganizationProfile
@@ -84,6 +90,7 @@ export function OrganizationProfile({
8490
defaultPage,
8591
afterLeaveOrganizationUrl,
8692
fallback,
93+
showSearch,
8794
apiKeysProps: { hide: hideAPIKeys = false, ...apiKeysProps } = {},
8895
}: OrganizationProfileProps) {
8996
const model = useOrganizationProfileModel();
@@ -106,6 +113,7 @@ export function OrganizationProfile({
106113
onPageChange={setActivePage}
107114
customPages={customPages}
108115
pageOrder={pageOrder}
116+
showSearch={showSearch}
109117
pages={{
110118
general: <OrganizationProfileGeneralPanel />,
111119
members: model.pages.includes('members') ? <OrganizationProfileMembersPanel /> : undefined,

‎packages/mosaic/src/features/organization-profile/organization-profile.view.tsx‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,20 @@ export interface OrganizationProfileViewProps extends Omit<ProfileRootProps, 'ch
2323
customPages?: readonly CustomOrganizationProfilePage[];
2424
pageOrder?: readonly (OrganizationProfilePageId | (string & {}))[];
2525
onPageChange: (page: OrganizationProfilePageId | (string & {})) => void;
26+
/**
27+
* Shows a search field above the navigation that finds pages, and the sections on them. Only the
28+
* column layout shows it.
29+
*
30+
* @default false
31+
*/
32+
showSearch?: boolean;
2633
}
2734

2835
export const OrganizationProfileView = React.forwardRef<HTMLDivElement, OrganizationProfileViewProps>(
29-
function OrganizationProfileView({ activePage, pages, customPages, pageOrder, onPageChange, label, ...rest }, ref) {
36+
function OrganizationProfileView(
37+
{ activePage, pages, customPages, pageOrder, onPageChange, label, showSearch = false, ...rest },
38+
ref,
39+
) {
3040
const m = useMessages('organizationProfile');
3141
const entries = resolveOrganizationProfilePages(
3242
getAvailableOrganizationProfilePages(pages),
@@ -62,7 +72,7 @@ export const OrganizationProfileView = React.forwardRef<HTMLDivElement, Organiza
6272
{...rest}
6373
>
6474
<Profile.Title>{label ?? m.label}</Profile.Title>
65-
<Profile.Nav search={navPages}>
75+
<Profile.Nav search={showSearch ? navPages : undefined}>
6676
{navPages.map(page => (
6777
<Profile.NavItem
6878
key={page.value}

‎packages/mosaic/src/features/user-profile/__tests__/user-profile.feature.test.tsx‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ describe('UserProfile', () => {
101101
const user = userEvent.setup();
102102
await renderWithClerk(
103103
<UserProfile
104+
showSearch
104105
customPages={[
105106
{
106107
path: 'usage',
@@ -136,6 +137,7 @@ describe('UserProfile', () => {
136137
const user = userEvent.setup();
137138
await renderWithClerk(
138139
<UserProfile
140+
showSearch
139141
customPages={[
140142
{
141143
path: 'usage',
@@ -170,6 +172,15 @@ describe('UserProfile', () => {
170172
expect(drawn.height).toBeCloseTo(around.height + 24);
171173
});
172174

175+
it.each([
176+
{ showSearch: undefined, shown: false },
177+
{ showSearch: true, shown: true },
178+
])('shows the search only when asked: $showSearch', async ({ showSearch, shown }) => {
179+
serve();
180+
await renderWithClerk(<UserProfile showSearch={showSearch} />);
181+
expect(screen.queryByRole('combobox', { name: 'Search' }) !== null).toBe(shown);
182+
});
183+
173184
it('leaves the page where it is when the found target is already in view', async () => {
174185
serve();
175186
const user = userEvent.setup();
@@ -178,7 +189,7 @@ describe('UserProfile', () => {
178189
data-testid='scroller'
179190
style={{ height: 400, overflowY: 'auto' }}
180191
>
181-
<UserProfile />
192+
<UserProfile showSearch />
182193
</div>,
183194
);
184195
const scroller = screen.getByTestId('scroller');

‎packages/mosaic/src/features/user-profile/__tests__/user-profile.view.test.tsx‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,26 @@ describe('UserProfileView', () => {
195195
expect(warn).toHaveBeenCalledWith('[Clerk] UserProfile has no pages to show.');
196196
});
197197

198+
it('shows the search only when asked', () => {
199+
const { rerender, props } = renderView();
200+
expect(screen.queryByRole('combobox', { name: 'Search' })).toBeNull();
201+
202+
rerender(
203+
<MosaicProvider>
204+
<UserProfileView
205+
{...props}
206+
showSearch
207+
/>
208+
</MosaicProvider>,
209+
);
210+
211+
expect(screen.getByRole('combobox', { name: 'Search' })).toBeVisible();
212+
});
213+
198214
it('finds a section on a page that is not open, and opens that page', async () => {
199215
const onPageChange = vi.fn();
200216
const user = userEvent.setup();
201-
renderView({ onPageChange });
217+
renderView({ onPageChange, showSearch: true });
202218

203219
await user.type(screen.getByRole('combobox', { name: 'Search' }), 'subscr');
204220
const billing = screen.getByRole('group', { name: 'Billing' });
@@ -212,6 +228,7 @@ describe('UserProfileView', () => {
212228
it('finds a page of your own by its keywords', async () => {
213229
const user = userEvent.setup();
214230
renderView({
231+
showSearch: true,
215232
customPages: [{ label: 'Usage', path: 'usage', keywords: ['quota'], content: <p>Usage content</p> }],
216233
});
217234

‎packages/mosaic/src/features/user-profile/user-profile.tsx‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import { useUserProfileUserModel } from './user-profile-user.model';
1717

1818
/** Everything `<UserProfile />` takes: your own pages and their order, the page it opens on, and the OAuth scopes it asks for. */
1919
export interface UserProfileProps
20-
extends Pick<UserProfileViewProps, 'customPages' | 'pageOrder'>, Pick<UserProfileOptions, 'additionalOAuthScopes'> {
20+
extends
21+
Pick<UserProfileViewProps, 'customPages' | 'pageOrder' | 'showSearch'>,
22+
Pick<UserProfileOptions, 'additionalOAuthScopes'> {
2123
/**
2224
* The page the profile opens on: a built-in page's id, or a custom page's `path`. Unlike
2325
* `pageOrder`, it leaves the navigation order alone. A page the navigation does not list opens
@@ -61,6 +63,12 @@ export interface UserProfileProps
6163
* ```
6264
*
6365
* @example
66+
* `showSearch` adds a search field above the navigation that finds pages, and the sections on them.
67+
* ```tsx
68+
* <UserProfile showSearch />
69+
* ```
70+
*
71+
* @example
6472
* `customPages` adds your own pages; `pageOrder` places them among the built-in ones.
6573
* ```tsx
6674
* <UserProfile
@@ -81,6 +89,7 @@ export function UserProfile({
8189
pageOrder,
8290
defaultPage,
8391
additionalOAuthScopes,
92+
showSearch,
8493
fallback,
8594
apiKeysProps: { hide: hideAPIKeys = false, ...apiKeysProps } = {},
8695
}: UserProfileProps) {
@@ -108,6 +117,7 @@ export function UserProfile({
108117
onPageChange={setActivePage}
109118
customPages={customPages}
110119
pageOrder={pageOrder}
120+
showSearch={showSearch}
111121
pages={{
112122
account: <UserProfileProfilePanel />,
113123
security: <UserProfileSecurityPanel />,

‎packages/mosaic/src/features/user-profile/user-profile.view.tsx‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ export interface UserProfileViewProps extends Omit<ProfileRootProps, 'children'
2222
*/
2323
pageOrder?: readonly (UserProfilePageId | (string & {}))[];
2424
onPageChange: (page: UserProfilePageId | (string & {})) => void;
25+
/**
26+
* Shows a search field above the navigation that finds pages, and the sections on them. Only the
27+
* column layout shows it.
28+
*
29+
* @default false
30+
*/
31+
showSearch?: boolean;
2532
}
2633

2734
/**
@@ -31,7 +38,7 @@ export interface UserProfileViewProps extends Omit<ProfileRootProps, 'children'
3138
* surface blank.
3239
*/
3340
export const UserProfileView = React.forwardRef<HTMLDivElement, UserProfileViewProps>(function UserProfileView(
34-
{ activePage, pages, customPages, pageOrder, onPageChange, label, ...rest },
41+
{ activePage, pages, customPages, pageOrder, onPageChange, label, showSearch = false, ...rest },
3542
ref,
3643
) {
3744
const m = useMessages('userProfile');
@@ -65,7 +72,7 @@ export const UserProfileView = React.forwardRef<HTMLDivElement, UserProfileViewP
6572
{...rest}
6673
>
6774
<Profile.Title>{label ?? m.label}</Profile.Title>
68-
<Profile.Nav search={navPages}>
75+
<Profile.Nav search={showSearch ? navPages : undefined}>
6976
{navPages.map(page => (
7077
<Profile.NavItem
7178
key={page.value}

‎packages/swingset/src/app/(clerk)/live/organization-profile/organization-profile/page.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ export default function OrganizationProfileLivePage() {
3333
{isLoaded && isSignedIn && !organization ? (
3434
<p className='text-muted-foreground text-sm'>Pick an active organization to see its profile.</p>
3535
) : null}
36-
<OrganizationProfile fallback={<p className='text-muted-foreground text-sm'>Loading…</p>} />
36+
<OrganizationProfile
37+
showSearch
38+
fallback={<p className='text-muted-foreground text-sm'>Loading…</p>}
39+
/>
3740
</div>
3841
</MosaicProvider>
3942
);

‎packages/swingset/src/app/(clerk)/live/user-profile/user-profile/page.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default function UserProfileLivePage() {
3131
</p>
3232
) : null}
3333
<UserProfile
34+
showSearch
3435
fallback={<p className='text-muted-foreground text-sm'>Loading…</p>}
3536
customPages={[
3637
{

0 commit comments

Comments
 (0)