Add popover support to select control dropdown (#34967)

* Add dropdownPlacement option to SelectControl

* Make use of parent element of menu for width

* Add changelog

* Add useEffect for boundingRect so it gets updated accordingly

* Fix styling for popover dropdown

* Move popup markup down to render return

* Move getMenuProps up

* Move getMenuProps down to list again and always render the popover menu

* Add MenuSlot that adds popover slot to mody with aria-live=off so it is skipped by Modal

* Add comment in relation to the eslint-disable comment

* Fix flashing of popover and addressed some minor PR suggestions
This commit is contained in:
louwie17 2022-10-18 16:55:37 -03:00 committed by GitHub
parent 0769625b72
commit 1f7deb139f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 131 additions and 18 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Add support in SelectControl for using the popover slot for the popover.

View File

@ -5,13 +5,25 @@
left: 0;
margin-top: $gap-smaller;
box-sizing: border-box;
display: none;
}
.components-popover.woocommerce-experimental-select-control__popover-menu {
background: $studio-white;
border: 1px solid $studio-gray-5;
border-radius: 3px;
z-index: 10;
display: none;
&.is-open.has-results {
display: block;
}
}
.woocommerce-experimental-select-control__popover-menu-container {
margin: 0;
max-height: 300px;
overflow-y: scroll;
> .category-field-dropdown__item:not( :first-child ) {
.category-field-dropdown__item-content {
border-top: 1px solid $gray-200;
}
}
}

View File

@ -1,8 +1,15 @@
/**
* External dependencies
*/
import { Popover } from '@wordpress/components';
import classnames from 'classnames';
import { createElement } from '@wordpress/element';
import {
createElement,
useEffect,
useRef,
useState,
createPortal,
} from '@wordpress/element';
/**
* Internal dependencies
@ -22,21 +29,63 @@ export const Menu = ( {
isOpen,
className,
}: MenuProps ) => {
const [ boundingRect, setBoundingRect ] = useState< DOMRect >();
const selectControlMenuRef = useRef< HTMLDivElement >( null );
useEffect( () => {
if ( selectControlMenuRef.current?.parentElement ) {
setBoundingRect(
selectControlMenuRef.current.parentElement.getBoundingClientRect()
);
}
}, [ selectControlMenuRef.current ] );
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */
/* Disabled because of the onmouseup on the ul element below. */
return (
<ul
{ ...getMenuProps() }
<div
ref={ selectControlMenuRef }
className={ classnames(
'woocommerce-experimental-select-control__menu',
className,
{
'is-open': isOpen,
'has-results': Array.isArray( children )
? children.length
: Boolean( children ),
}
className
) }
>
{ isOpen && children }
</ul>
<Popover
focusOnMount={ false }
className={ classnames(
'woocommerce-experimental-select-control__popover-menu',
{
'is-open': isOpen,
'has-results':
Array.isArray( children ) && children.length > 0,
}
) }
position="bottom center"
animate={ false }
>
<ul
{ ...getMenuProps() }
className="woocommerce-experimental-select-control__popover-menu-container"
style={ {
width: boundingRect?.width,
} }
onMouseUp={ ( e ) =>
// Fix to prevent select control dropdown from closing when selecting within the Popover.
e.stopPropagation()
}
>
{ isOpen && children }
</ul>
</Popover>
</div>
);
/* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */
};
export const MenuSlot: React.FC = () =>
createPortal(
<div aria-live="off">
<Popover.Slot />
</div>,
document.body
);

View File

@ -1,7 +1,13 @@
/**
* External dependencies
*/
import { CheckboxControl, Spinner } from '@wordpress/components';
import {
Button,
CheckboxControl,
Modal,
SlotFillProvider,
Spinner,
} from '@wordpress/components';
import React from 'react';
import { createElement, useState } from '@wordpress/element';
@ -11,7 +17,7 @@ import { createElement, useState } from '@wordpress/element';
import { SelectedType, DefaultItemType, getItemLabelType } from '../types';
import { MenuItem } from '../menu-item';
import { SelectControl, selectControlStateChangeTypes } from '../';
import { Menu } from '../menu';
import { Menu, MenuSlot } from '../menu';
const sampleItems = [
{ value: 'apple', label: 'Apple' },
@ -365,6 +371,45 @@ export const CustomItemType: React.FC = () => {
);
};
export const SingleWithinModalUsingBodyDropdownPlacement: React.FC = () => {
const [ isOpen, setOpen ] = useState( true );
const [ selected, setSelected ] =
useState< SelectedType< DefaultItemType > >();
const [ selectedTwo, setSelectedTwo ] =
useState< SelectedType< DefaultItemType > >();
return (
<SlotFillProvider>
Selected: { JSON.stringify( selected ) }
<Button onClick={ () => setOpen( true ) }>
Show Dropdown in Modal
</Button>
{ isOpen && (
<Modal
title="Dropdown Modal"
onRequestClose={ () => setOpen( false ) }
>
<SelectControl
items={ sampleItems }
label="Single value"
selected={ selected }
onSelect={ ( item ) => item && setSelected( item ) }
onRemove={ () => setSelected( null ) }
/>
<SelectControl
items={ sampleItems }
label="Single value"
selected={ selectedTwo }
onSelect={ ( item ) => item && setSelectedTwo( item ) }
onRemove={ () => setSelectedTwo( null ) }
/>
</Modal>
) }
<MenuSlot />
</SlotFillProvider>
);
};
export default {
title: 'WooCommerce Admin/experimental/SelectControl',
component: SelectControl,

View File

@ -49,7 +49,10 @@ export {
MenuItem as __experimentalSelectControlMenuItem,
MenuItemProps as __experimentalSelectControlMenuItemProps,
} from './experimental-select-control/menu-item';
export { Menu as __experimentalSelectControlMenu } from './experimental-select-control/menu';
export {
Menu as __experimentalSelectControlMenu,
MenuSlot as __experimentalSelectControlMenuSlot,
} from './experimental-select-control/menu';
export { default as ScrollTo } from './scroll-to';
export { Sortable } from './sortable';
export { ListItem } from './list-item';