diff --git a/client/a8c-for-agencies/sections/partner-directory/agency-details/index.tsx b/client/a8c-for-agencies/sections/partner-directory/agency-details/index.tsx
index 67745830ae072..5d55e37384dcf 100644
--- a/client/a8c-for-agencies/sections/partner-directory/agency-details/index.tsx
+++ b/client/a8c-for-agencies/sections/partner-directory/agency-details/index.tsx
@@ -1,5 +1,5 @@
import page from '@automattic/calypso-router';
-import { Button } from '@automattic/components';
+import { Button, SearchableDropdown } from '@automattic/components';
import { TextareaControl, TextControl, ToggleControl } from '@wordpress/components';
import { useTranslate } from 'i18n-calypso';
import { useCallback } from 'react';
@@ -9,7 +9,6 @@ import validateEmail from 'calypso/a8c-for-agencies/components/form/hoc/with-err
import validateNonEmpty from 'calypso/a8c-for-agencies/components/form/hoc/with-error-handling/validators/non-empty';
import validateUrl from 'calypso/a8c-for-agencies/components/form/hoc/with-error-handling/validators/url';
import FormSection from 'calypso/a8c-for-agencies/components/form/section';
-import SearchableDropdown from 'calypso/a8c-for-agencies/components/searchable-dropdown';
import { A4A_PARTNER_DIRECTORY_DASHBOARD_LINK } from 'calypso/a8c-for-agencies/components/sidebar-menu/lib/constants';
import BudgetSelector from 'calypso/a8c-for-agencies/sections/partner-directory/components/budget-selector';
import { AgencyDetails } from 'calypso/a8c-for-agencies/sections/partner-directory/types';
diff --git a/client/a8c-for-agencies/sections/signup/agency-details-form/index.tsx b/client/a8c-for-agencies/sections/signup/agency-details-form/index.tsx
index 30de3d4b4852e..92a85cae55ed8 100644
--- a/client/a8c-for-agencies/sections/signup/agency-details-form/index.tsx
+++ b/client/a8c-for-agencies/sections/signup/agency-details-form/index.tsx
@@ -1,9 +1,8 @@
-import { Button, Gridicon, FormLabel } from '@automattic/components';
+import { Button, Gridicon, FormLabel, SearchableDropdown } from '@automattic/components';
import { useLocalizeUrl } from '@automattic/i18n-utils';
import clsx from 'clsx';
import { useTranslate } from 'i18n-calypso';
import { useCallback, useState, useMemo, ChangeEvent, useEffect } from 'react';
-import SearchableDropdown from 'calypso/a8c-for-agencies/components/searchable-dropdown';
import TextPlaceholder from 'calypso/a8c-for-agencies/components/text-placeholder';
import FormFieldset from 'calypso/components/forms/form-fieldset';
import FormSelect from 'calypso/components/forms/form-select';
diff --git a/client/jetpack-cloud/sections/partner-portal/company-details-form/index.tsx b/client/jetpack-cloud/sections/partner-portal/company-details-form/index.tsx
index 58b614851239a..7e9afe3312f51 100644
--- a/client/jetpack-cloud/sections/partner-portal/company-details-form/index.tsx
+++ b/client/jetpack-cloud/sections/partner-portal/company-details-form/index.tsx
@@ -1,4 +1,4 @@
-import { Button, Gridicon, FormLabel } from '@automattic/components';
+import { Button, Gridicon, FormLabel, SearchableDropdown } from '@automattic/components';
import { useTranslate } from 'i18n-calypso';
import { useCallback, useState, useMemo, ChangeEvent, useEffect } from 'react';
import FormFieldset from 'calypso/components/forms/form-fieldset';
@@ -8,7 +8,6 @@ import FormTextInput from 'calypso/components/forms/form-text-input';
import TextPlaceholder from 'calypso/jetpack-cloud/sections/partner-portal/text-placeholder';
import { PartnerDetailsPayload } from 'calypso/state/partner-portal/types';
import PartnerProgramOptInFieldSet from '../partner-program-opt-in-fieldset/partner-program-opt-in-fieldset';
-import SearchableDropdown from '../searchable-dropdown';
import { Option as CountryOption, useCountriesAndStates } from './hooks/use-countries-and-states';
import type { FormEventHandler } from 'react';
diff --git a/client/jetpack-cloud/sections/partner-portal/searchable-dropdown/index.tsx b/client/jetpack-cloud/sections/partner-portal/searchable-dropdown/index.tsx
deleted file mode 100644
index 7cb94b5a61c1d..0000000000000
--- a/client/jetpack-cloud/sections/partner-portal/searchable-dropdown/index.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { ComboboxControl, Disabled } from '@wordpress/components';
-import clsx from 'clsx';
-
-import './style.scss';
-
-type Props = React.ComponentProps< typeof ComboboxControl > & {
- disabled?: boolean;
-};
-
-export default function SearchableDropdown( props: Props ) {
- const { disabled } = props;
-
- return (
-
-
-
-
-
- );
-}
diff --git a/client/jetpack-cloud/sections/partner-portal/searchable-dropdown/style.scss b/client/jetpack-cloud/sections/partner-portal/searchable-dropdown/style.scss
deleted file mode 100644
index a2475d02b04df..0000000000000
--- a/client/jetpack-cloud/sections/partner-portal/searchable-dropdown/style.scss
+++ /dev/null
@@ -1,61 +0,0 @@
-.searchable-dropdown {
- // TODO: We will have to figure out and adopt standard Calypso styling for the combobox control.
- // So we do not have to override so many styles here or in the future.
- .components-base-control__field.components-base-control__field {
- margin: 0;
- }
-
- input.components-combobox-control__input[type="text"] {
- font-size: 1rem;
- line-height: 1.5;
- padding: 7px 14px;
- }
-
- .components-combobox-control__suggestions-container .components-flex {
- border: 1px solid var(--color-neutral-10);
- border-radius: 2px;
- background-color: var(--color-surface);
- transition: all 0.15s ease-in-out;
- height: 40px;
- }
-
- .components-combobox-control__suggestions-container:focus-within {
- border-color: var(--color-primary);
- box-shadow: 0 0 0 2px var(--color-primary-10);
- outline: none;
- }
-
- .components-combobox-control__suggestions-container {
- border: none;
- }
-
- .components-form-token-field__suggestion.is-selected {
- background-color: var(--color-primary);
- }
-
- .components-form-token-field__suggestions-list li {
- padding: 10px;
- font-size: 1rem;
- }
-}
-
-.searchable-dropdown.is-disabled {
- .components-base-control__field.components-base-control__field {
- background: var(--color-neutral-0);
- }
-
- input.components-combobox-control__input[type="text"] {
- border-color: var(--color-neutral-0);
- color: var(--color-neutral-20);
- opacity: 1;
- -webkit-text-fill-color: var(--color-neutral-20);
-
- &:hover {
- cursor: default;
- }
-
- &::placeholder {
- color: var(--color-neutral-20);
- }
- }
-}
diff --git a/client/site-performance/components/PageSelector.tsx b/client/site-performance/components/PageSelector.tsx
new file mode 100644
index 0000000000000..e237b91479902
--- /dev/null
+++ b/client/site-performance/components/PageSelector.tsx
@@ -0,0 +1,52 @@
+import page from '@automattic/calypso-router';
+import { SearchableDropdown } from '@automattic/components';
+import { useDebouncedInput } from '@wordpress/compose';
+import { useSelector } from 'calypso/state';
+import getCurrentQueryArguments from 'calypso/state/selectors/get-current-query-arguments';
+import { useSitePages } from '../hooks/useSitePages';
+
+export const PageSelector = () => {
+ const queryParams = useSelector( getCurrentQueryArguments );
+ const [ , setQuery, query ] = useDebouncedInput();
+ const pages = useSitePages( { query } );
+
+ return (
+ <>
+ {
+ const url = new URL( window.location.href );
+
+ if ( page_id ) {
+ url.searchParams.set( 'page_id', page_id );
+ } else {
+ url.searchParams.delete( 'page_id' );
+ }
+
+ page.replace( url.pathname + url.search );
+ } }
+ css={ {
+ maxWidth: '240px',
+ '.components-form-token-field__suggestions-list': { maxHeight: 'initial !important' },
+ '.components-form-token-field__suggestions-list li': { padding: '0 !important' },
+ } }
+ __experimentalRenderItem={ ( { item } ) => (
+
+ { item.label }
+ { item.url }
+
+ ) }
+ />
+ >
+ );
+};
diff --git a/client/site-performance/controller.tsx b/client/site-performance/controller.tsx
index 1bf07421534f7..efae44d32487f 100644
--- a/client/site-performance/controller.tsx
+++ b/client/site-performance/controller.tsx
@@ -1,6 +1,7 @@
import config from '@automattic/calypso-config';
import page from '@automattic/calypso-router';
import PageViewTracker from 'calypso/lib/analytics/page-view-tracker';
+import { PageSelector } from './components/PageSelector';
import type { Context as PageJSContext } from '@automattic/calypso-router';
export function sitePerformance( context: PageJSContext, next: () => void ) {
@@ -12,7 +13,7 @@ export function sitePerformance( context: PageJSContext, next: () => void ) {
context.primary = (
<>
- Site Performance
+
>
);
diff --git a/client/site-performance/hooks/useSitePages.ts b/client/site-performance/hooks/useSitePages.ts
new file mode 100644
index 0000000000000..128e86e65bdf6
--- /dev/null
+++ b/client/site-performance/hooks/useSitePages.ts
@@ -0,0 +1,81 @@
+import { useQuery, keepPreviousData } from '@tanstack/react-query';
+import { useI18n } from '@wordpress/react-i18n';
+import { addQueryArgs } from '@wordpress/url';
+import { useMemo } from 'react';
+import wpcomRequest from 'wpcom-proxy-request';
+import { useSiteSettings } from 'calypso/blocks/plugins-scheduled-updates/hooks/use-site-settings';
+import { useSelector } from 'calypso/state';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+interface SitePage {
+ id: number;
+ link: string;
+ title: { rendered: string };
+ wpcom_performance_url?: string;
+}
+
+const getPages = ( siteId: number, query = '' ) => {
+ return wpcomRequest< SitePage[] >( {
+ path: addQueryArgs( `/sites/${ siteId }/pages`, {
+ per_page: 10,
+ search: query,
+ page: 1,
+ status: 'publish',
+ _fields: [ 'id', 'link', 'title', 'wpcom_performance_url' ],
+ } ),
+ method: 'GET',
+ apiNamespace: 'wp/v2',
+ } );
+};
+
+export const useSitePages = ( { query = '' } ) => {
+ const { __ } = useI18n();
+
+ const site = useSelector( getSelectedSite );
+ const siteId = site?.ID;
+
+ const { data } = useQuery( {
+ queryKey: [ 'useSitePages', siteId, query ],
+ queryFn: () => getPages( siteId!, query ),
+ refetchOnWindowFocus: false,
+ enabled: !! siteId,
+ placeholderData: keepPreviousData,
+ select: ( data ) => {
+ return data.map( ( page ) => {
+ let url = page.link.replace( site?.URL ?? '', '' );
+ url = url.length > 1 ? url.replace( /\/$/, '' ) : url;
+
+ return {
+ url,
+ label: page.title.rendered,
+ value: page.id.toString(),
+ wpcom_performance_url: page.wpcom_performance_url,
+ };
+ } );
+ },
+ meta: {
+ persist: false,
+ },
+ } );
+
+ const { getSiteSetting } = useSiteSettings( site?.slug );
+ const homePagePerformanceUrl = getSiteSetting( 'wpcom_performance_url' );
+
+ const pages = useMemo( () => {
+ if ( ! query ) {
+ return [
+ {
+ url: '/',
+ label: __( 'Home' ),
+ value: 'home',
+ wpcom_performance_url: homePagePerformanceUrl || undefined,
+ },
+ ...( data ?? [] ),
+ ];
+ }
+
+ return data ?? [];
+ }, [ query, data, __, homePagePerformanceUrl ] );
+
+ return pages;
+};
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index 45a03c7f8177a..f359b60e00430 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -21,6 +21,7 @@ export { default as RootChild } from './root-child';
export { default as ScreenReaderText } from './screen-reader-text';
export { useScrollToTop } from './scroll-to-top/use-scroll-to-top';
export { default as SelectDropdown } from './select-dropdown';
+export { default as SearchableDropdown } from './searchable-dropdown';
export { SiteThumbnail, DEFAULT_THUMBNAIL_SIZE } from './site-thumbnail';
export { default as Suggestions } from './suggestions';
export { default as PaginationControl } from './pagination-control';
diff --git a/packages/components/src/searchable-dropdown/index.stories.tsx b/packages/components/src/searchable-dropdown/index.stories.tsx
new file mode 100644
index 0000000000000..c4beb10df604e
--- /dev/null
+++ b/packages/components/src/searchable-dropdown/index.stories.tsx
@@ -0,0 +1,27 @@
+import { useState } from 'react';
+import SearchableDropdown from './index';
+import type { Meta, StoryObj } from '@storybook/react';
+
+const meta: Meta< typeof SearchableDropdown > = {
+ title: 'packages/components/SearchableDropdown',
+ component: ( props ) => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const [ value, onChange ] = useState( 'home' );
+
+ return onChange( e! ) } { ...props } />;
+ },
+};
+
+export default meta;
+type Story = StoryObj< typeof SearchableDropdown >;
+
+export const Default: Story = {
+ args: {
+ options: [
+ {
+ label: 'Home',
+ value: 'home',
+ },
+ ],
+ },
+};
diff --git a/client/a8c-for-agencies/components/searchable-dropdown/index.tsx b/packages/components/src/searchable-dropdown/index.tsx
similarity index 92%
rename from client/a8c-for-agencies/components/searchable-dropdown/index.tsx
rename to packages/components/src/searchable-dropdown/index.tsx
index 7cb94b5a61c1d..e6d414d0cd55c 100644
--- a/client/a8c-for-agencies/components/searchable-dropdown/index.tsx
+++ b/packages/components/src/searchable-dropdown/index.tsx
@@ -8,7 +8,7 @@ type Props = React.ComponentProps< typeof ComboboxControl > & {
};
export default function SearchableDropdown( props: Props ) {
- const { disabled } = props;
+ const { disabled = false } = props;
return (