Skip to content

Commit 0d3efb3

Browse files
committed
feat(mosaic): add showSearch to UserProfile and OrganizationProfile
Search is now off by default. Pass showSearch to show it.
1 parent 920a131 commit 0d3efb3

9 files changed

Lines changed: 92 additions & 7 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
@@ -19,7 +19,7 @@ import { OrganizationProfileMembersPanel } from './organization-profile-members-
1919
/** Everything `<OrganizationProfile />` takes: your own pages and their order, the page it opens on, and where leaving the organization lands. */
2020
export interface OrganizationProfileProps
2121
extends
22-
Pick<OrganizationProfileViewProps, 'customPages' | 'pageOrder'>,
22+
Pick<OrganizationProfileViewProps, 'customPages' | 'pageOrder' | 'showSearch'>,
2323
Pick<OrganizationProfileOptions, 'afterLeaveOrganizationUrl'> {
2424
/**
2525
* The page the profile opens on: a built-in page's id, or a custom page's `path`. Unlike
@@ -66,6 +66,12 @@ export interface OrganizationProfileProps
6666
* ```
6767
*
6868
* @example
69+
* `showSearch` adds a search field above the navigation that finds pages, and the sections on them.
70+
* ```tsx
71+
* <OrganizationProfile showSearch />
72+
* ```
73+
*
74+
* @example
6975
* `customPages` adds your own pages; `pageOrder` places them among the built-in ones.
7076
* ```tsx
7177
* <OrganizationProfile
@@ -86,6 +92,7 @@ export function OrganizationProfile({
8692
defaultPage,
8793
afterLeaveOrganizationUrl,
8894
fallback,
95+
showSearch,
8996
apiKeysProps,
9097
}: OrganizationProfileProps) {
9198
const model = useOrganizationProfileModel();
@@ -109,6 +116,7 @@ export function OrganizationProfile({
109116
{...navigation}
110117
customPages={customPages}
111118
pageOrder={pageOrder}
119+
showSearch={showSearch}
112120
pages={pages}
113121
/>
114122
</OrganizationProfileProvider>

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,18 @@ export interface ProfilePagesViewProps extends Omit<ProfileRootProps, 'children'
2828
activePage: string;
2929
/** Called with a page's id when the user picks it from the navigation. */
3030
onPageChange: (page: string) => void;
31+
/**
32+
* Shows a search field above the navigation that finds pages, and the sections on them. Only the
33+
* column layout shows it.
34+
*
35+
* @default false
36+
*/
37+
showSearch?: boolean;
3138
}
3239

3340
/** A `Profile` built from groups of pages: their navigation, and the active page beside it. */
3441
export const ProfilePagesView = React.forwardRef<HTMLDivElement, ProfilePagesViewProps>(function ProfilePagesView(
35-
{ label, groups, activePage, onPageChange, ...rest },
42+
{ label, groups, activePage, onPageChange, showSearch = false, ...rest },
3643
ref,
3744
) {
3845
const pages = groups.flatMap(group => group.pages);
@@ -52,7 +59,7 @@ export const ProfilePagesView = React.forwardRef<HTMLDivElement, ProfilePagesVie
5259
{...rest}
5360
>
5461
<Profile.Title>{label}</Profile.Title>
55-
<Profile.Nav search={searchPages}>
62+
<Profile.Nav search={showSearch ? searchPages : undefined}>
5663
{groups.map((group, index) =>
5764
group.label === undefined ? (
5865
<NavItems

‎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
@@ -108,6 +108,7 @@ describe('UserProfile', () => {
108108
const user = userEvent.setup();
109109
await renderWithClerk(
110110
<UserProfile
111+
showSearch
111112
customPages={[
112113
{
113114
path: 'usage',
@@ -143,6 +144,7 @@ describe('UserProfile', () => {
143144
const user = userEvent.setup();
144145
await renderWithClerk(
145146
<UserProfile
147+
showSearch
146148
customPages={[
147149
{
148150
path: 'usage',
@@ -177,6 +179,15 @@ describe('UserProfile', () => {
177179
expect(drawn.height).toBeCloseTo(around.height + 24);
178180
});
179181

182+
it.each([
183+
{ showSearch: undefined, shown: false },
184+
{ showSearch: true, shown: true },
185+
])('shows the search only when asked: $showSearch', async ({ showSearch, shown }) => {
186+
serve();
187+
await renderWithClerk(<UserProfile showSearch={showSearch} />);
188+
expect(screen.queryByRole('combobox', { name: 'Search' }) !== null).toBe(shown);
189+
});
190+
180191
it('leaves the page where it is when the found target is already in view', async () => {
181192
serve();
182193
const user = userEvent.setup();
@@ -185,7 +196,7 @@ describe('UserProfile', () => {
185196
data-testid='scroller'
186197
style={{ height: 400, overflowY: 'auto' }}
187198
>
188-
<UserProfile />
199+
<UserProfile showSearch />
189200
</div>,
190201
);
191202
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
@@ -257,10 +257,26 @@ describe('UserProfileView', () => {
257257
});
258258
});
259259

260+
it('shows the search only when asked', () => {
261+
const { rerender, props } = renderView();
262+
expect(screen.queryByRole('combobox', { name: 'Search' })).toBeNull();
263+
264+
rerender(
265+
<MosaicProvider>
266+
<UserProfileView
267+
{...props}
268+
showSearch
269+
/>
270+
</MosaicProvider>,
271+
);
272+
273+
expect(screen.getByRole('combobox', { name: 'Search' })).toBeVisible();
274+
});
275+
260276
it('finds a section on a page that is not open, and opens that page', async () => {
261277
const onPageChange = vi.fn();
262278
const user = userEvent.setup();
263-
renderView({ onPageChange });
279+
renderView({ onPageChange, showSearch: true });
264280

265281
await user.type(screen.getByRole('combobox', { name: 'Search' }), 'subscr');
266282
const billing = screen.getByRole('group', { name: 'Billing' });
@@ -274,6 +290,7 @@ describe('UserProfileView', () => {
274290
it('finds a page of your own by its keywords', async () => {
275291
const user = userEvent.setup();
276292
renderView({
293+
showSearch: true,
277294
customPages: [{ label: 'Usage', path: 'usage', keywords: ['quota'], content: <p>Usage content</p> }],
278295
});
279296

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ export type UserProfileMode = Exclude<SwitcherMode, 'organization'>;
2929

3030
/** Everything `<UserProfile />` takes: your own pages and their order, the page it opens on, and the OAuth scopes it asks for. */
3131
export interface UserProfileProps
32-
extends Pick<UserProfileViewProps, 'customPages' | 'pageOrder'>, Pick<UserProfileOptions, 'additionalOAuthScopes'> {
32+
extends
33+
Pick<UserProfileViewProps, 'customPages' | 'pageOrder' | 'showSearch'>,
34+
Pick<UserProfileOptions, 'additionalOAuthScopes'> {
3335
/**
3436
* The page the profile opens on: a built-in page's id, or a custom page's `path`. Unlike
3537
* `pageOrder`, it leaves the navigation order alone. A page the navigation does not list opens
@@ -106,6 +108,12 @@ export interface UserProfileProps
106108
* ```
107109
*
108110
* @example
111+
* `showSearch` adds a search field above the navigation that finds pages, and the sections on them.
112+
* ```tsx
113+
* <UserProfile showSearch />
114+
* ```
115+
*
116+
* @example
109117
* `customPages` adds your own pages; `pageOrder` places them among the built-in ones.
110118
* ```tsx
111119
* <UserProfile
@@ -138,6 +146,7 @@ export function UserProfileSurface({
138146
pageOrder,
139147
defaultPage,
140148
additionalOAuthScopes,
149+
showSearch,
141150
fallback,
142151
apiKeysProps: { hide: hideAPIKeys = false, ...apiKeysProps } = {},
143152
}: UserProfileProps & { presentation: NonNullable<UserProfileOptions['mode']> }) {
@@ -156,6 +165,7 @@ export function UserProfileSurface({
156165
initialPage: defaultPage ?? pageOrder?.[0] ?? 'account',
157166
customPages,
158167
pageOrder,
168+
showSearch,
159169
pages: {
160170
account: <UserProfileProfilePanel />,
161171
security: <UserProfileSecurityPanel />,

‎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)