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 (