diff --git a/.changeset/mosaic-profile-search.md b/.changeset/mosaic-profile-search.md
new file mode 100644
index 00000000000..a845151cc84
--- /dev/null
+++ b/.changeset/mosaic-profile-search.md
@@ -0,0 +1,2 @@
+---
+---
diff --git a/packages/mosaic/src/components/profile/index.ts b/packages/mosaic/src/components/profile/index.ts
index 9f9d3b6f073..a06a025562b 100644
--- a/packages/mosaic/src/components/profile/index.ts
+++ b/packages/mosaic/src/components/profile/index.ts
@@ -9,3 +9,6 @@ export type {
ProfileRootProps,
ProfileTitleProps,
} from './profile';
+export type { ProfileSearchPage } from './profile-search';
+export type { SearchTargetOptions, SearchTargetProps } from './profile-search-index';
+export { SearchTarget, useIsSearchIndexing } from './profile-search-index';
diff --git a/packages/mosaic/src/components/profile/profile-search-index.tsx b/packages/mosaic/src/components/profile/profile-search-index.tsx
new file mode 100644
index 00000000000..6a30b4cb2ac
--- /dev/null
+++ b/packages/mosaic/src/components/profile/profile-search-index.tsx
@@ -0,0 +1,286 @@
+import * as stylex from '@stylexjs/stylex';
+import React from 'react';
+import { createPortal } from 'react-dom';
+
+import { useRender } from '../../primitives/utils';
+import type { MosaicComponentProps } from '../../props';
+import { mergeStyleProps, themeProps } from '../../props';
+import { reset } from '../../styles/reset.styles';
+import { ProfileContext } from './profile.context';
+import { styles } from './profile-search.styles';
+
+/** What the profile's search finds a target by. */
+export interface SearchTargetOptions {
+ /** Names the target in the search results. */
+ label: string;
+ /** Other words that find the target. */
+ keywords?: readonly string[];
+ /** Tells apart targets on one page that share a `label`. Defaults to the `label`. */
+ id?: string;
+}
+
+export interface SearchTargetProps extends MosaicComponentProps<'div'>, SearchTargetOptions {}
+
+/**
+ * A part of a profile page that the profile's search can find, scroll to, and focus. Pass your own
+ * element as `render` to make it the target, with no wrapper of its own. While the search builds
+ * its index, the page renders a second, hidden time, and a target renders none of its children.
+ *
+ * ```tsx
+ *
Devices list
; +} + +function IndexOnly() { + return useIsSearchIndexing() ?Broken page content
; + } + render( +Usage content
}], + }); + + await user.type(screen.getByRole('combobox', { name: 'Search' }), 'quota'); + + expect(screen.getByRole('option', { name: 'Usage' })).toBeInTheDocument(); + }); }); diff --git a/packages/mosaic/src/features/user-profile/user-profile-active-devices-section/user-profile-active-devices-section.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-active-devices-section/user-profile-active-devices-section.view.tsx index f95b6830764..00f5963c1bc 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-active-devices-section/user-profile-active-devices-section.view.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-active-devices-section/user-profile-active-devices-section.view.tsx @@ -40,7 +40,7 @@ export function UserProfileActiveDevicesSectionView({ return (Pick an active organization to see its profile.
) : null} -1,000 requests per minute.
+ +4 of 10 seats used.
+ +