diff --git a/client/landing/gutenboarding/components/domain-picker-button/index.tsx b/client/landing/gutenboarding/components/domain-picker-button/index.tsx index 2b5c01aca65191..e621278a995d96 100644 --- a/client/landing/gutenboarding/components/domain-picker-button/index.tsx +++ b/client/landing/gutenboarding/components/domain-picker-button/index.tsx @@ -2,12 +2,13 @@ * External dependencies */ import * as React from 'react'; +import classnames from 'classnames'; import { Button } from '@wordpress/components'; import { Icon, chevronDown } from '@wordpress/icons'; -import { useDispatch, useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { useI18n } from '@automattic/react-i18n'; -import classnames from 'classnames'; import type { DomainSuggestions } from '@automattic/data-stores'; +import DomainPicker from '@automattic/domain-picker'; /** * Internal dependencies @@ -16,10 +17,7 @@ import DomainPickerPopover from '../domain-picker-popover'; import DomainPickerModal from '../domain-picker-modal'; import { FLOW_ID } from '../../constants'; import { STORE_KEY } from '../../stores/onboard'; -import { useDomainSuggestions } from '../../hooks/use-domain-suggestions'; -import { DOMAIN_SUGGESTIONS_STORE } from '../../stores/domain-suggestions'; import { getSuggestionsVendor } from 'lib/domains/suggestions'; - const DOMAIN_SUGGESTION_VENDOR = getSuggestionsVendor( true ); /** @@ -42,91 +40,71 @@ const DomainPickerButton: React.FunctionComponent< Props > = ( { currentDomain, ...buttonProps } ) => { - const buttonRef = React.createRef< HTMLButtonElement >(); - - const domainSuggestions = useDomainSuggestions( { locale: useI18n().i18nLocale, quantity: 10 } ); - - const domainCategories = useSelect( ( select ) => - select( DOMAIN_SUGGESTIONS_STORE ).getCategories() - ); + const { __ } = useI18n(); - const { domainSearch, domainCategory } = useSelect( ( select ) => - select( STORE_KEY ).getState() - ); - const { setDomainSearch, setDomainCategory } = useDispatch( STORE_KEY ); + const buttonRef = React.createRef< HTMLButtonElement >(); - const [ isDomainPopoverVisible, setDomainPopoverVisibility ] = React.useState( false ); - const [ isDomainModalVisible, setDomainModalVisibility ] = React.useState( false ); + const [ domainPickerMode, setDomainPickerMode ] = React.useState< + 'popover' | 'modal' | undefined + >(); - const handlePopoverClose = ( e?: React.FocusEvent ) => { - // Don't collide with button toggling - if ( e?.relatedTarget === buttonRef.current ) { - return; - } - setDomainPopoverVisibility( false ); + const handlePopoverToggle = () => { + setDomainPickerMode( ( mode ) => ( mode ? undefined : 'popover' ) ); }; - const handleModalClose = () => { - setDomainModalVisibility( false ); + const handleClose = () => { + setDomainPickerMode( undefined ); }; - const handleMoreOptions = () => { - setDomainPopoverVisibility( false ); - setDomainModalVisibility( true ); + const onClickMoreOptions = () => { + setDomainPickerMode( 'modal' ); }; + const domainSearch = useSelect( ( select ) => select( STORE_KEY ).getDomainSearch() ); + const { setDomainSearch } = useDispatch( STORE_KEY ); + + const domainPicker = ( + + ); + return ( <> - + isOpen={ domainPickerMode === 'popover' } + onClose={ handlePopoverToggle } + > + { domainPicker } + + + { domainPicker } + ); }; diff --git a/client/landing/gutenboarding/components/domain-picker-modal/index.tsx b/client/landing/gutenboarding/components/domain-picker-modal/index.tsx index b7709bed68ee21..606e7cc4d037c4 100644 --- a/client/landing/gutenboarding/components/domain-picker-modal/index.tsx +++ b/client/landing/gutenboarding/components/domain-picker-modal/index.tsx @@ -4,7 +4,6 @@ import * as React from 'react'; import Modal from 'react-modal'; import { select } from '@wordpress/data'; -import DomainPicker, { Props as DomainPickerProps } from '@automattic/domain-picker'; /** * Internal dependencies @@ -17,11 +16,13 @@ import { STORE_KEY } from '../../stores/onboard'; */ import './style.scss'; -interface Props extends DomainPickerProps { +interface Props { + onClose: Function; isOpen: boolean; + children: any; } -const DomainPickerModal: React.FunctionComponent< Props > = ( { isOpen, onClose, ...props } ) => { +const DomainPickerModal: React.FunctionComponent< Props > = ( { isOpen, onClose, children } ) => { if ( ! isOpen ) { return null; } @@ -54,13 +55,7 @@ const DomainPickerModal: React.FunctionComponent< Props > = ( { isOpen, onClose, overlayClassName="domain-picker-modal-overlay" bodyOpenClassName="has-domain-picker-modal" > - + { children } ); }; diff --git a/client/landing/gutenboarding/components/domain-picker-modal/style.scss b/client/landing/gutenboarding/components/domain-picker-modal/style.scss index b05e15fad79a47..40ab7ba353c749 100644 --- a/client/landing/gutenboarding/components/domain-picker-modal/style.scss +++ b/client/landing/gutenboarding/components/domain-picker-modal/style.scss @@ -5,7 +5,7 @@ @include onboarding-modal-overlay; } -.domain-picker-modal { +.is-section-gutenboarding .domain-picker-modal { .domain-picker__panel-row-main { diff --git a/client/landing/gutenboarding/components/domain-picker-popover/index.tsx b/client/landing/gutenboarding/components/domain-picker-popover/index.tsx index 7994d10e2e21de..c1f20deab483e0 100644 --- a/client/landing/gutenboarding/components/domain-picker-popover/index.tsx +++ b/client/landing/gutenboarding/components/domain-picker-popover/index.tsx @@ -5,7 +5,6 @@ import * as React from 'react'; import { Popover } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { select } from '@wordpress/data'; -import DomainPicker, { Props as DomainPickerProps } from '@automattic/domain-picker'; /** * Internal dependencies @@ -18,11 +17,13 @@ import { STORE_KEY } from '../../stores/onboard'; */ import './style.scss'; -interface Props extends DomainPickerProps { +interface Props { + onClose: Function; isOpen: boolean; + children: any; } -const DomainPickerPopover: React.FunctionComponent< Props > = ( { isOpen, onClose, ...props } ) => { +const DomainPickerPopover: React.FunctionComponent< Props > = ( { isOpen, onClose, children } ) => { // Popover expands at medium viewport width const isMobile = useViewportMatch( 'medium', '<' ); @@ -59,7 +60,7 @@ const DomainPickerPopover: React.FunctionComponent< Props > = ( { isOpen, onClos position={ 'bottom center' } expandOnMobile={ true } > - + { children } ); diff --git a/client/landing/gutenboarding/stores/onboard/selectors.ts b/client/landing/gutenboarding/stores/onboard/selectors.ts index bece8ae98efe82..61f588dbffc71d 100644 --- a/client/landing/gutenboarding/stores/onboard/selectors.ts +++ b/client/landing/gutenboarding/stores/onboard/selectors.ts @@ -1,7 +1,13 @@ +/** + * External dependencies + */ +import { select } from '@wordpress/data'; + /** * Internal dependencies */ import type { State } from './reducer'; +import { USER_STORE } from '../user'; export const getIsRedirecting = ( state: State ) => state.isRedirecting; export const getState = ( state: State ) => state; @@ -23,3 +29,8 @@ export const getSelectedFonts = ( state: State ) => state.selectedFonts; export const getSelectedVertical = ( state: State ) => state.siteVertical; export const getSelectedDomain = ( state: State ) => state.domain; export const getSelectedSiteTitle = ( state: State ) => state.siteTitle; +export const getDomainSearch = ( state: State ) => + state.domainSearch || + getSelectedSiteTitle( state ) || + getSelectedVertical( state )?.label.trim() || + select( USER_STORE ).getCurrentUser()?.username; diff --git a/packages/domain-picker/package.json b/packages/domain-picker/package.json index 47fc92bc85e0e8..5784400e28b464 100644 --- a/packages/domain-picker/package.json +++ b/packages/domain-picker/package.json @@ -39,7 +39,8 @@ "lodash": "^4.17.15", "tslib": "^1.10.0", "use-debounce": "^3.1.0", - "uuid": "^7.0.2" + "uuid": "^7.0.2", + "@wordpress/base-styles": "^1.8.0" }, "peerDependencies": { "@wordpress/data": "^4.18.0", diff --git a/packages/domain-picker/src/constants.ts b/packages/domain-picker/src/constants.ts new file mode 100644 index 00000000000000..8024fb11712fe2 --- /dev/null +++ b/packages/domain-picker/src/constants.ts @@ -0,0 +1,5 @@ +export const PAID_DOMAINS_TO_SHOW_WITH_CATEGORIES_MODE = 10; +export const PAID_DOMAINS_TO_SHOW_WITHOUT_CATEGORIES_MODE = 5; +export const DOMAIN_SEARCH_DEBOUNCE_INTERVAL = 300; +export const DOMAIN_SUGGESTION_VENDOR = 'variation4_front'; +export const DOMAIN_SUGGESTIONS_STORE = 'automattic/domains/suggestions'; diff --git a/packages/domain-picker/src/domain-categories/index.tsx b/packages/domain-picker/src/domain-categories/index.tsx index 13148af0ddfc36..3da37fbbbc867e 100644 --- a/packages/domain-picker/src/domain-categories/index.tsx +++ b/packages/domain-picker/src/domain-categories/index.tsx @@ -7,25 +7,24 @@ import { Icon, chevronDown } from '@wordpress/icons'; import classNames from 'classnames'; import { useI18n } from '@automattic/react-i18n'; import { useState } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { DOMAIN_SUGGESTIONS_STORE } from '../constants'; /** * Style dependencies */ import './style.scss'; -type DomainCategory = import('@automattic/data-stores').DomainSuggestions.DomainCategory; - export interface Props { onSelect: ( domainCategorySlug?: string ) => void; selected?: string; - domainCategories: DomainCategory[]; } -const DomainPickerCategories: React.FunctionComponent< Props > = ( { - onSelect, - selected, - domainCategories, -} ) => { +const DomainPickerCategories: React.FunctionComponent< Props > = ( { onSelect, selected } ) => { const { __ } = useI18n(); const [ isOpen, setIsOpen ] = useState( false ); @@ -35,6 +34,10 @@ const DomainPickerCategories: React.FunctionComponent< Props > = ( { onSelect( slug ); }; + const domainCategories = useSelect( ( select ) => + select( DOMAIN_SUGGESTIONS_STORE ).getCategories() + ); + return (
{ showDomainCategories && (
- +
) }
@@ -260,7 +256,7 @@ const DomainPicker: FunctionComponent< Props > = ( { ) ) } { ! paidSuggestions && - times( quantity - 1, ( i ) => ) } + times( quantity, ( i ) => ) } { paidSuggestions && ( paidSuggestions?.length ? ( paidSuggestions.map( ( suggestion, i ) => ( diff --git a/packages/domain-picker/src/domain-picker/style.scss b/packages/domain-picker/src/domain-picker/style.scss index bbadfbd2143495..b46ebca878a328 100644 --- a/packages/domain-picker/src/domain-picker/style.scss +++ b/packages/domain-picker/src/domain-picker/style.scss @@ -1,7 +1,5 @@ -@import 'assets/stylesheets/gutenberg-base-styles'; -@import 'assets/stylesheets/shared/mixins/placeholder'; // Contains the placeholder mixin -@import 'assets/stylesheets/shared/animation'; // Needed for the placeholder -@import '../mixins'; +@import '../styles/placeholder.scss'; // Contains the placeholder mixin +@import '../styles/mixins'; $domain-picker__suggestion-item-height: 24px; @@ -14,6 +12,8 @@ $domain-picker__suggestion-item-height: 24px; padding: 0; width: 100%; height: 100%; + border-top: unset; + border-bottom: unset; } .components-panel__row { diff --git a/packages/domain-picker/src/hooks/use-domain-suggestions.ts b/packages/domain-picker/src/hooks/use-domain-suggestions.ts new file mode 100644 index 00000000000000..666f123750867b --- /dev/null +++ b/packages/domain-picker/src/hooks/use-domain-suggestions.ts @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +import { useSelect } from '@wordpress/data'; +import { useDebounce } from 'use-debounce'; + +/** + * Internal dependencies + */ +import { + DOMAIN_SUGGESTION_VENDOR, + PAID_DOMAINS_TO_SHOW_WITHOUT_CATEGORIES_MODE, + DOMAIN_SUGGESTIONS_STORE, + DOMAIN_SEARCH_DEBOUNCE_INTERVAL, +} from '../constants'; + +export function useDomainSuggestions( + searchTerm = '', + domainCategory?: string, + locale = 'en', + quantity = PAID_DOMAINS_TO_SHOW_WITHOUT_CATEGORIES_MODE +) { + const [ domainSearch ] = useDebounce( searchTerm, DOMAIN_SEARCH_DEBOUNCE_INTERVAL ); + + return useSelect( + ( select ) => { + if ( ! domainSearch ) { + return; + } + return select( DOMAIN_SUGGESTIONS_STORE ).getDomainSuggestions( domainSearch, { + // Avoid `only_wordpressdotcom` — it seems to fail to find results sometimes + include_wordpressdotcom: true, + include_dotblogsubdomain: false, + quantity: quantity + 1, // increment the count to add the free domain + locale, + vendor: DOMAIN_SUGGESTION_VENDOR, + category_slug: domainCategory, + } ); + }, + [ domainSearch, domainCategory, quantity ] + ); +} diff --git a/packages/domain-picker/src/styles/base-styles.scss b/packages/domain-picker/src/styles/base-styles.scss new file mode 100644 index 00000000000000..efd1f7932c73a3 --- /dev/null +++ b/packages/domain-picker/src/styles/base-styles.scss @@ -0,0 +1,4 @@ +@import '~@wordpress/base-styles/colors'; +@import '~@wordpress/base-styles/breakpoints'; +@import '~@wordpress/base-styles/mixins'; +@import '~@automattic/calypso-color-schemes' diff --git a/packages/domain-picker/src/mixins.scss b/packages/domain-picker/src/styles/mixins.scss similarity index 75% rename from packages/domain-picker/src/mixins.scss rename to packages/domain-picker/src/styles/mixins.scss index da8e5615ca0464..4bea1199a35677 100644 --- a/packages/domain-picker/src/mixins.scss +++ b/packages/domain-picker/src/styles/mixins.scss @@ -1,5 +1,4 @@ -@import './variables.scss'; -@import 'assets/stylesheets/gutenberg-base-styles'; +@import './base-styles.scss'; @mixin domain-picker-package--font-recoleta { font-family: Recoleta, Georgia, 'Times New Roman', Times, serif; diff --git a/packages/domain-picker/src/styles/placeholder.scss b/packages/domain-picker/src/styles/placeholder.scss new file mode 100644 index 00000000000000..15ed42c0eb18e8 --- /dev/null +++ b/packages/domain-picker/src/styles/placeholder.scss @@ -0,0 +1,29 @@ +@import './base-styles.scss'; + +// ========================================================================== +// Placeholder mixin +// Adds animation to placeholder section +// ========================================================================== + +@mixin placeholder( $color-property: $light-gray-200 ) { + animation: domain-picker-loading-fade 1.6s ease-in-out infinite; + background: $color-property; + color: transparent; + + &::after { + content: '\00a0'; + } +} + +// a pulsing animation for placeholders +@keyframes domain-picker-loading-fade { + 0% { + opacity: 0.5; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.5; + } +} diff --git a/packages/domain-picker/src/variables.scss b/packages/domain-picker/src/variables.scss deleted file mode 100644 index e28701f63f0b44..00000000000000 --- a/packages/domain-picker/src/variables.scss +++ /dev/null @@ -1,7 +0,0 @@ -// Reusable style variables -$domain-picker-package-header-height: 64px; -$domain-picker-package-heading-padding-desktop: 64px 0 80px; -$domain-picker-package-heading-padding-mobile: 44px 0 50px; -$domain-picker-package-block-margin-mobile: 20px; -$domain-picker-package-block-margin-small: 44px; -$domain-picker-package-block-margin-medium: 88px;