Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gutenboarding: Domain Picker Modal #41212

Merged
merged 19 commits into from Apr 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 2 additions & 9 deletions client/landing/gutenboarding/components/close-button/index.tsx
Expand Up @@ -5,17 +5,10 @@ import React from 'react';
import { Button, Path, SVG } from '@wordpress/components';
import { useI18n } from '@automattic/react-i18n';

interface CloseButtonProps extends Button.ButtonProps {
onClose: () => void;
}

const CloseButton: React.FunctionComponent< CloseButtonProps > = ( {
onClose,
...buttonProps
} ) => {
const CloseButton: React.FunctionComponent< Button.ButtonProps > = ( { ...buttonProps } ) => {
const { __ } = useI18n();
return (
<Button onClick={ onClose } label={ __( 'Close dialog' ) } { ...buttonProps }>
<Button label={ __( 'Close dialog' ) } { ...buttonProps }>
<SVG
width="12"
height="12"
Expand Down
Expand Up @@ -2,18 +2,16 @@
* External dependencies
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be time to organize all these under a domain-picker directory:

domain-picker/
  - index - re-exports whatever makes sense (modal, popover, the contents?)
  - modal
  - popover
  - …

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created an issue for this. #41221

*/
import React, { createRef, FunctionComponent, useState } from 'react';
import { Button, Popover, Dashicon } from '@wordpress/components';
import classnames from 'classnames';
import { Button, Dashicon } from '@wordpress/components';

// Core package needs to add this to the type definitions.
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
import { useViewportMatch } from '@wordpress/compose';
import classnames from 'classnames';

/**
* Internal dependencies
*/
import DomainPicker, { Props as DomainPickerProps } from '../domain-picker';
import { Props as DomainPickerProps } from '../domain-picker';
import DomainPickerPopover from '../domain-picker-popover';
import DomainPickerModal from '../domain-picker-modal';

/**
* Style dependencies
Expand All @@ -37,18 +35,25 @@ const DomainPickerButton: FunctionComponent< Props > = ( {
const buttonRef = createRef< HTMLButtonElement >();

const [ isDomainPopoverVisible, setDomainPopoverVisibility ] = useState( false );
const [ isDomainModalVisible, setDomainModalVisibility ] = useState( false );

// Popover expands at medium viewport width
const isMobile = useViewportMatch( 'medium', '<' );

const handleClose = ( e?: React.FocusEvent ) => {
const handlePopoverClose = ( e?: React.FocusEvent ) => {
// Don't collide with button toggling
if ( e?.relatedTarget === buttonRef.current ) {
return;
}
setDomainPopoverVisibility( false );
};

const handleModalClose = () => {
setDomainModalVisibility( false );
};

const handleMoreOptions = () => {
setDomainPopoverVisibility( false );
setDomainModalVisibility( true );
};

return (
<>
<Button
Expand All @@ -58,32 +63,31 @@ const DomainPickerButton: FunctionComponent< Props > = ( {
aria-pressed={ isDomainPopoverVisible }
className={ classnames( 'domain-picker-button', className, {
'is-open': isDomainPopoverVisible,
'is-modal-open': isDomainModalVisible,
} ) }
onClick={ () => setDomainPopoverVisibility( s => ! s ) }
ref={ buttonRef }
>
<span className="domain-picker-button__label">{ children }</span>
<Dashicon icon="arrow-down-alt2" size={ 16 } />
</Button>
{ isDomainPopoverVisible && (
<div className="domain-picker-button__popover-container">
<Popover
className="domain-picker-button__popover"
focusOnMount={ isMobile ? 'container' : 'firstElement' }
noArrow
onClose={ handleClose }
onFocusOutside={ handleClose }
position={ 'bottom center' }
expandOnMobile={ true }
>
<DomainPicker
currentDomain={ currentDomain }
onClose={ handleClose }
onDomainSelect={ onDomainSelect }
/>
</Popover>
</div>
) }
<DomainPickerPopover
isOpen={ isDomainPopoverVisible }
showDomainConnectButton={ false }
showDomainCategories={ false }
currentDomain={ currentDomain }
onDomainSelect={ onDomainSelect }
onMoreOptions={ handleMoreOptions }
onClose={ handlePopoverClose }
/>
<DomainPickerModal
isOpen={ isDomainModalVisible }
showDomainConnectButton
showDomainCategories
currentDomain={ currentDomain }
onDomainSelect={ onDomainSelect }
onClose={ handleModalClose }
/>
</>
);
};
Expand Down
Expand Up @@ -9,39 +9,22 @@
height: auto; // prevent clipping when there are 2 lines
text-align: left;
}

&__label {
word-break: break-word;
}

.dashicon {
margin-left: 0.5em;
margin-top: 2px;
transition: transform 100ms ease-in-out;
}

&.is-open .dashicon {
transform: rotate( 180deg );
}
}

.domain-picker-button__popover {
.components-popover__content {
border: 1px solid var( --studio-gray-5 );
box-shadow: 0 4px 10px rgba( 0, 0, 0, 0.12 );
border-radius: 4px;
}
}

.domain-picker-button__popover-container {
.components-popover__header {
display: none;
}

.components-popover.is-expanded .components-popover__content {
height: 100%;
}

@include break-small {
position: fixed;
left: 72px;
top: 54px;
&.is-modal-open {
display: none; // Hide domain picker button when modal is open
}
}
@@ -0,0 +1,40 @@
/**
* External dependencies
*/
import * as React from 'react';
import Modal from 'react-modal';

/**
* Internal dependencies
*/
import DomainPicker, { Props as DomainPickerProps } from '../domain-picker';

/**
* Style dependencies
*/
import './style.scss';

interface Props extends DomainPickerProps {
isOpen: boolean;
onMoreOptions?: () => void;
}

const DomainPickerModal: React.FunctionComponent< Props > = ( { isOpen, ...props } ) => {
// This is needed otherwise it throws a warning.
Modal.setAppElement( '#wpcom' );

if ( ! isOpen ) return null;

return (
<Modal
isOpen
className="domain-picker-modal"
overlayClassName="domain-picker-modal-overlay"
bodyOpenClassName="has-domain-picker-modal"
>
<DomainPicker showDomainConnectButton showDomainCategories { ...props } />
</Modal>
);
};

export default DomainPickerModal;
@@ -0,0 +1,68 @@
@import 'assets/stylesheets/gutenberg-base-styles';
@import '../../mixins.scss';

.domain-picker-modal-overlay {
// Absolute positioning allows the modal
// to reuse the <body> element's scrollbar.
position: absolute;

// This positions the domain picker modal
// right below the gutenboarding header,
// keeping the header clickable.
top: $gutenboarding-header-height;
left: 0;

// Using min-height lets the overlay cover
// the entire viewport ensuring nothing behind
// it can be seen.
//
// When the domain picker's content is taller
// than the viewport height, it will expand taller
// than the provided min-height, triggering
// the appearance of the <body> element's scrollbar.
min-height: calc( 100vh - $gutenboarding-header-height );
width: 100%;

background: var( --studio-white );
}

.domain-picker-modal {


.domain-picker__panel-row-main {

// Replace domain picker's padding with onboarding block margins
padding: 0;

// Increase specificity to override margin: 0 from .components-panel__row
&.components-panel__row {
@include onboarding-block-margin;
}
}

.domain-picker__header {
@include onboarding-heading-padding;
margin-bottom: 27px; // Maintain the same 27px bottom margin as in the original domain picker

@include break-mobile {
margin-bottom: 27px;
}
}

.domain-picker__header-title {
@include onboarding-heading-text;
}

// Do not display domain picker footer which contains
// the confirm button when showing inside a modal.
.domain-picker__panel-row-footer {
display: none;
}
}

// Hide onboarding block when domain picker modal is open
body.has-domain-picker-modal {
.onboarding-block {
display: none;
}
}
@@ -0,0 +1,75 @@
/**
* External dependencies
*/
import * as React from 'react';
import { Button, Popover } from '@wordpress/components';
import { useI18n } from '@automattic/react-i18n';
import config from 'config';

// Core package needs to add this to the type definitions.
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
import { useViewportMatch } from '@wordpress/compose';

/**
* Internal dependencies
*/
import DomainPicker, { Props as DomainPickerProps } from '../domain-picker';
import CloseButton from '../close-button';

/**
* Style dependencies
*/
import './style.scss';

interface Props extends DomainPickerProps {
isOpen: boolean;
onMoreOptions?: () => void;
}

const DomainPickerPopover: React.FunctionComponent< Props > = ( {
isOpen,
onMoreOptions,
...props
} ) => {
const { __ } = useI18n();
const onClose = props.onClose;

// Popover expands at medium viewport width
const isMobile = useViewportMatch( 'medium', '<' );

// Don't render popover when isOpen is false.
// We need this component to be hot because useViewportMatch
// returns false on initial mount before returning true,
// causing search input to be automatically focused.
if ( ! isOpen ) return null;

return (
<div className="domain-picker-popover">
<Popover
focusOnMount={ isMobile ? 'container' : 'firstElement' }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I seem to get the input focused on mobile now. Is that a regression?

Copy link
Contributor Author

@yansern yansern Apr 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. useViewportMatch returns false on initial mount, causing the popover to focus on the search input, then after that, it updates itself to true, which at this point, the search input was already in focused. The solution was to keep the DomainPickerPopover component warm, mounted but renders nothing until isOpen flag is set to true. f81559a#diff-3acfaab9099bccb32e73498b4be055a4R41-R46

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created an issue WordPress/gutenberg#21676.

noArrow
onClose={ onClose }
onFocusOutside={ onClose }
position={ 'bottom center' }
expandOnMobile={ true }
>
<DomainPicker { ...props } />
<div className="domain-picker-popover__addons">
{ config.isEnabled( 'gutenboarding/domain-picker-modal' ) && (
<Button
className="domain-picker-popover__more-button"
isTertiary
onClick={ onMoreOptions }
>
{ __( 'More Options' ) }
</Button>
) }
<CloseButton className="domain-picker-popover__close-button" onClick={ onClose } />
</div>
</Popover>
</div>
);
};

export default DomainPickerPopover;