[Experimental] Refactor the interactivity dropdown to remove dependence on FormTokenField (#43183)

* Also fix https://github.com/woocommerce/woocommerce/issues/43154
This commit is contained in:
Sam Seay 2024-01-02 12:13:27 +08:00 committed by GitHub
parent e43eb1dcab
commit 5de0f4a274
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 238 additions and 449 deletions

View File

@ -1,36 +1,28 @@
/**
* External dependencies
*/
import styled from '@emotion/styled';
import FormTokenField from '@woocommerce/base-components/form-token-field';
import { __, sprintf } from '@wordpress/i18n';
import { Icon, chevronDown } from '@wordpress/icons';
/**
* Internal dependencies
*/
import { PreviewDropdown } from '../../components/preview-dropdown';
type Props = {
label: string;
textColor: string;
};
export const AttributeDropdown = ( { label, textColor }: Props ) => {
const StyledFormTokenField = textColor
? styled( FormTokenField )`
.components-form-token-field__input::placeholder {
color: ${ textColor } !important;
}
`
: FormTokenField;
export const AttributeDropdown = ( { label }: Props ) => {
return (
<div className="wc-block-attribute-filter style-dropdown">
<StyledFormTokenField
suggestions={ [] }
<PreviewDropdown
placeholder={ sprintf(
/* translators: %s attribute name. */
__( 'Select %s', 'woocommerce' ),
label
) }
onChange={ () => null }
value={ [] }
/>
<Icon icon={ chevronDown } size={ 30 } />
</div>

View File

@ -0,0 +1,30 @@
/**
* Preview dropdown component for collection filter editor blocks that mimics the markup of interactivity dropdown.
*/
export const PreviewDropdown = ( { placeholder }: { placeholder: string } ) => {
return (
<div className="wc-interactivity-dropdown">
<div className="wc-interactivity-dropdown__dropdown">
<div
className="wc-interactivity-dropdown__dropdown-selection"
tabIndex={ 0 }
>
<span className="wc-interactivity-dropdown__placeholder">
{ placeholder }
</span>
<span className="wc-interactivity-dropdown__svg-container">
<svg
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
width="30"
height="30"
>
<path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z"></path>
</svg>
</span>
</div>
</div>
</div>
);
};

View File

@ -2,13 +2,10 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Icon, chevronDown } from '@wordpress/icons';
import classnames from 'classnames';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
import type { BlockEditProps, Template } from '@wordpress/blocks';
import Rating, {
RatingValues,
} from '@woocommerce/base-components/product-rating';
import Rating from '@woocommerce/base-components/product-rating';
import {
useQueryStateByKey,
useQueryStateByContext,
@ -18,10 +15,7 @@ import { getSettingWithCoercion } from '@woocommerce/settings';
import { isBoolean, isObject, objectHasProp } from '@woocommerce/types';
import { useState, useMemo, useEffect } from '@wordpress/element';
import { CheckboxList } from '@woocommerce/blocks-components';
import FormTokenField from '@woocommerce/base-components/form-token-field';
import { Disabled, Notice, withSpokenMessages } from '@wordpress/components';
import { useStyleProps } from '@woocommerce/base-hooks';
import styled from '@emotion/styled';
/**
* Internal dependencies
@ -29,11 +23,11 @@ import styled from '@emotion/styled';
import { previewOptions } from './preview';
import './style.scss';
import { Attributes } from './types';
import { formatSlug, getActiveFilters, generateUniqueId } from './utils';
import { getActiveFilters } from './utils';
import { useSetWraperVisibility } from '../../../filter-wrapper/context';
import './editor.scss';
import { Inspector } from '../attribute-filter/components/inspector-controls';
import { extractBuiltInColor } from '../../utils';
import { PreviewDropdown } from '../components/preview-dropdown';
const NoRatings = () => (
<Notice status="warning" isDismissible={ false }>
@ -50,21 +44,6 @@ const Edit = ( props: BlockEditProps< Attributes > ) => {
const { className } = props.attributes;
const blockAttributes = props.attributes;
const { className: styleClass, style } = useStyleProps( props.attributes );
const builtInColor = extractBuiltInColor( styleClass );
const textColor = builtInColor
? `var(--wp--preset--color--${ builtInColor })`
: style.color;
const StyledFormTokenField = textColor
? styled( FormTokenField )`
.components-form-token-field__input::placeholder {
color: ${ textColor } !important;
}
`
: FormTokenField;
const blockProps = useBlockProps( {
className: classnames( 'wc-block-rating-filter', className ),
} );
@ -111,21 +90,9 @@ const Edit = ( props: BlockEditProps< Attributes > ) => {
initialFilters
);
/*
FormTokenField forces the dropdown to reopen on reset, so we create a unique ID to use as the components key.
This will force the component to remount on reset when we change this value.
More info: https://github.com/woocommerce/woocommerce-blocks/pull/6920#issuecomment-1222402482
*/
const [ remountKey, setRemountKey ] = useState( generateUniqueId() );
const [ displayNoProductRatingsNotice, setDisplayNoProductRatingsNotice ] =
useState( false );
const multiple = blockAttributes.selectType !== 'single';
const showChevron = multiple
? ! isLoading && checked.length < displayedOptions.length
: ! isLoading && checked.length === 0;
/**
* Compare intersection of all ratings and filtered counts to get a list of options to display.
*/
@ -173,7 +140,6 @@ const Edit = ( props: BlockEditProps< Attributes > ) => {
} );
setDisplayedOptions( newOptions );
setRemountKey( generateUniqueId() );
}, [
blockAttributes.showCounts,
blockAttributes.isPreview,
@ -221,107 +187,19 @@ const Edit = ( props: BlockEditProps< Attributes > ) => {
>
{ blockAttributes.displayStyle === 'dropdown' ? (
<>
<StyledFormTokenField
key={ remountKey }
className={ classnames( {
'single-selection': ! multiple,
'is-loading': isLoading,
} ) }
style={ {
borderStyle: 'none',
} }
suggestions={ displayedOptions
.filter(
( option ) =>
! checked.includes(
option.value
)
)
.map( ( option ) => option.value ) }
disabled={ isLoading }
placeholder={ __(
'Select Rating',
'woocommerce'
) }
onChange={ () => {
// noop
} }
value={ checked }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - FormTokenField doesn't accept custom components, forcing it here to display component
displayTransform={ ( value ) => {
const resultWithZeroCount = {
value,
label: (
<Rating
key={
Number(
value
) as RatingValues
}
rating={
Number(
value
) as RatingValues
}
ratedProductsCount={ 0 }
/>
),
};
const resultWithNonZeroCount =
displayedOptions.find(
( option ) =>
option.value === value
);
const displayedResult =
resultWithNonZeroCount ||
resultWithZeroCount;
const { label, value: rawValue } =
displayedResult;
// A label - JSX component - is extended with faked string methods to allow using JSX element as an option in FormTokenField
const extendedLabel = Object.assign(
{},
label,
{
toLocaleLowerCase: () =>
rawValue,
substring: (
start: number,
end: number
) =>
start === 0 && end === 1
? label
: '',
}
);
return extendedLabel;
} }
saveTransform={ formatSlug }
messages={ {
added: __(
'Rating filter added.',
'woocommerce'
),
removed: __(
'Rating filter removed.',
'woocommerce'
),
remove: __(
'Remove rating filter.',
'woocommerce'
),
__experimentalInvalid: __(
'Invalid rating filter.',
'woocommerce'
),
} }
<PreviewDropdown
placeholder={
blockAttributes.selectType === 'single'
? __(
'Select a rating',
'woocommerce'
)
: __(
'Select ratings',
'woocommerce'
)
}
/>
{ showChevron && (
<Icon icon={ chevronDown } size={ 30 } />
) }
</>
) : (
<CheckboxList

View File

@ -37,7 +37,7 @@ export const Inspector = ( { attributes, setAttributes }: EditProps ) => {
'Allow selecting multiple options?',
'woocommerce'
) }
value={ selectType || 'multiple' }
value={ selectType || 'single' }
onChange={ ( value: string ) =>
setAttributes( {
selectType: value,

View File

@ -6,27 +6,20 @@ import classnames from 'classnames';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
import { Disabled } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { Icon, chevronDown } from '@wordpress/icons';
import { CheckboxList } from '@woocommerce/blocks-components';
import Label from '@woocommerce/base-components/filter-element-label';
import FormTokenField from '@woocommerce/base-components/form-token-field';
import type { BlockEditProps, Template } from '@wordpress/blocks';
import { getSetting } from '@woocommerce/settings';
import { useCollectionData } from '@woocommerce/base-context/hooks';
import { useStyleProps } from '@woocommerce/base-hooks';
import styled from '@emotion/styled';
/**
* Internal dependencies
*/
import { BlockProps } from './types';
import { Inspector } from './components/inspector';
import { extractBuiltInColor } from '../../utils';
import { PreviewDropdown } from '../components/preview-dropdown';
type CollectionData = {
// attribute_counts: null | unknown;
// price_range: null | unknown;
// rating_counts: null | unknown;
stock_status_counts: StockStatusCount[];
};
@ -53,21 +46,6 @@ const Edit = ( props: BlockEditProps< BlockProps > ) => {
],
];
const { className, style } = useStyleProps( props.attributes );
const builtInColor = extractBuiltInColor( className );
const textColor = builtInColor
? `var(--wp--preset--color--${ builtInColor })`
: style.color;
const StyledFormTokenField = textColor
? styled( FormTokenField )`
.components-form-token-field__input::placeholder {
color: ${ textColor } !important;
}
`
: FormTokenField;
const { showCounts, displayStyle } = props.attributes;
const stockStatusOptions: Record< string, string > = getSetting(
'stockStatusOptions',
@ -121,27 +99,29 @@ const Edit = ( props: BlockEditProps< BlockProps > ) => {
>
{ displayStyle === 'dropdown' ? (
<>
<StyledFormTokenField
className={ classnames( {
'single-selection': true,
'is-loading': false,
} ) }
suggestions={ [] }
placeholder={ __(
'Select stock status',
'woocommerce'
) }
onChange={ () => null }
value={ [] }
<PreviewDropdown
placeholder={
props.attributes.selectType ===
'single'
? __(
'Select stock status',
'woocommerce'
)
: __(
'Select stock statuses',
'woocommerce'
)
}
/>
<Icon icon={ chevronDown } size={ 30 } />
</>
) : (
<CheckboxList
className={ 'wc-block-stock-filter-list' }
options={ listOptions }
checked={ [] }
onChange={ () => null }
onChange={ () => {
// noop
} }
isLoading={ false }
isDisabled={ true }
/>

View File

@ -26,7 +26,11 @@ store( 'woocommerce/collection-stock-filter', {
'woocommerce/interactivity-dropdown'
);
navigate( getUrl( context.selectedItem.value || '' ) );
const selectedItems = context.selectedItems;
const items = selectedItems || [];
const filters = items.map( ( i ) => i.value );
navigate( getUrl( filters.join( ',' ) ) );
},
updateProducts: ( event: HTMLElementEvent< HTMLInputElement > ) => {
// get the active filters from the url:

View File

@ -2,6 +2,7 @@
* External dependencies
*/
import { getContext, store } from '@woocommerce/interactivity';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
@ -47,12 +48,12 @@ store< DropdownStore >( 'woocommerce/interactivity-dropdown', {
if ( selectType === 'single' ) {
return selectedItems?.length && selectedItems[ 0 ].label
? selectedItems[ 0 ]?.label
: 'Select an option';
: __( 'Select an option', 'woocommerce' );
} else if (
selectType === 'multiple' &&
selectedItems.length === 0
) {
return 'Select options';
return __( 'Select options', 'woocommerce' );
}
return '';
@ -69,7 +70,6 @@ store< DropdownStore >( 'woocommerce/interactivity-dropdown', {
actions: {
toggleIsOpen: () => {
const context = getContext< DropdownContext >();
context.isOpen = ! context.isOpen;
},
unselectDropdownItem: ( event: MouseEvent ) => {

View File

@ -1,145 +1,91 @@
@import "../../../assets/js/blocks/shared/styles/style";
.wc-interactivity-dropdown {
@include includeFormTokenFieldFix();
position: relative;
display: flex;
gap: $gap;
align-items: flex-start;
width: 100%;
gap: 16px;
.wc-block-components-filter-submit-button {
height: 36px;
line-height: 1;
}
color: inherit;
font-family: inherit;
font-size: inherit;
font-style: inherit;
font-weight: inherit;
letter-spacing: inherit;
line-height: inherit;
text-decoration: inherit;
text-transform: inherit;
> svg {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
}
.wc-interactivity-dropdown__selected-badge {
@include font-size(smaller);
padding: 0.25em 0.25em 0.25em 0.75em;
margin: 2px 0;
border-radius: em(20px);
border: 1px solid $black;
display: inline-flex;
align-items: center;
gap: em(4px);
.wc-blocks-components-form-token-field-wrapper {
flex-grow: 1;
max-width: unset;
width: 0;
height: max-content;
&:not(.is-loading) {
border: 1px solid $gray-700 !important;
border-radius: 4px;
.wc-interactivity-dropdown__badge-text {
margin-right: 0.25em;
}
&.is-loading {
.wc-interactivity-dropdown__badge-remove {
background-color: $gray-200;
cursor: pointer;
border-radius: em(20px);
}
}
.wc-interactivity-dropdown__dropdown {
.wc-interactivity-dropdown__dropdown-selection {
display: flex;
padding: 4px 30px 4px 8px;
border: 1px solid $gray-700;
border-radius: em(4px);
}
cursor: pointer;
align-items: center;
.components-form-token-field {
border-radius: inherit;
}
}
.wc-blocks-components-form-token-field-wrapper .components-form-token-field__input-container {
@include reset-color();
@include reset-typography();
border: 0;
padding: $gap-smaller;
border-radius: inherit;
.components-form-token-field__input {
@include font-size(small);
&::placeholder {
color: $black;
.wc-interactivity-dropdown__placeholder {
@include font-size(smaller);
margin: 0.25em 0;
display: flex;
justify-content: center;
flex-direction: column;
height: 30px;
}
}
.components-form-token-field__suggestions-list {
border: 1px solid $gray-700;
border-radius: 4px;
margin-top: $gap-smaller;
max-height: 21em;
.wc-interactivity-dropdown__svg-container {
> svg {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
}
}
.components-form-token-field__suggestion {
color: $black;
border: 1px solid $gray-400;
border-radius: 4px;
.wc-interactivity-dropdown__dropdown-list {
position: absolute;
width: 100%;
background: $white;
border: 1px solid $gray-700;
border-radius: em(4px);
z-index: 1000;
margin-top: $gap-smaller;
box-sizing: border-box;
.wc-interactivity-dropdown__dropdown-option {
margin: $gap-small;
padding: $gap-small;
&.is-selected,
&:hover {
background: $gray-100;
color: $gray-800;
border-radius: em(4px);
border: 1px solid $gray-400;
@include font-size(small);
cursor: pointer;
&:hover,
&:focus,
&.is-selected {
background-color: $gray-100;
}
}
}
.components-form-token-field__token,
.components-form-token-field__suggestion {
@include font-size(small);
}
}
.wc-block-components-product-rating {
margin-bottom: 0;
}
.wc-blocks-components-form-token-field-wrapper:not(.single-selection) .components-form-token-field__input-container {
padding: $gap-smallest 30px $gap-smallest $gap-smaller;
.components-form-token-field__token-text {
background-color: $white;
border: 1px solid;
border-right: 0;
border-radius: 25px 0 0 25px;
padding: em($gap-smallest) em($gap-smaller) em($gap-smallest) em($gap-small);
line-height: 22px;
}
> .components-form-token-field__input {
margin: em($gap-smallest) 0;
}
.components-button.components-form-token-field__remove-token {
background-color: $white;
border: 1px solid;
border-left: 0;
border-radius: 0 25px 25px 0;
padding: 1px em($gap-smallest) 0 0;
&.has-icon svg {
background-color: $gray-200;
border-radius: 25px;
}
}
}
.wc-block-stock-filter__actions {
align-items: center;
display: flex;
gap: $gap;
justify-content: flex-end;
margin-top: $gap;
// The specificity here is needed to overwrite the margin-top that is inherited on WC block template pages such as Shop.
button[type="submit"]:not(.wp-block-search__button).wc-block-components-filter-submit-button {
margin-left: 0;
margin-top: 0;
@include font-size(small);
}
.wc-block-stock-filter__button {
margin-top: em($gap-smaller);
padding: em($gap-smaller) em($gap);
@include font-size(small);
}
}
.editor-styles-wrapper .wc-block-stock-filter .wc-block-stock-filter__button {
margin-top: em($gap-smaller);
padding: em($gap-smaller) em($gap);
@include font-size(small);
}
}

View File

@ -0,0 +1,4 @@
Significance: patch
Type: add
[Experimental] Refactor interactivity dropdown to remove FormTokenField. Also fix multi select for rating filter.

View File

@ -67,8 +67,8 @@ final class AssetsController {
// Register the interactivity components here for now.
$this->api->register_script( 'wc-interactivity-dropdown', 'assets/client/blocks/wc-interactivity-dropdown.js', array() );
$this->api->register_script( 'wc-interactivity-checkbox-list', 'assets/client/blocks/wc-interactivity-checkbox-list.js', array() );
$this->register_style( 'wc-interactivity-checkbox-list', plugins_url( $this->api->get_block_asset_build_path( 'wc-interactivity-checkbox-list', 'css' ), __DIR__ ), array(), 'all', true );
$this->register_style( 'wc-interactivity-dropdown', plugins_url( $this->api->get_block_asset_build_path( 'wc-interactivity-dropdown', 'css' ), __DIR__ ), array(), 'all', true );
$this->register_style( 'wc-interactivity-checkbox-list', plugins_url( $this->api->get_block_asset_build_path( 'wc-interactivity-checkbox-list', 'css' ), dirname( __DIR__ ) ), array(), 'all', true );
$this->register_style( 'wc-interactivity-dropdown', plugins_url( $this->api->get_block_asset_build_path( 'wc-interactivity-dropdown', 'css' ), dirname( __DIR__ ) ), array(), 'all', true );
wp_add_inline_script(
'wc-blocks-middleware',

View File

@ -211,9 +211,6 @@ final class CollectionAttributeFilter extends AbstractBlock {
return '';
}
$text_color_class_and_style = StyleAttributesUtils::get_text_color_class_and_style( $attributes );
$text_color = $text_color_class_and_style['value'] ?? '';
$list_items = array();
$selected_item = array();
@ -232,10 +229,9 @@ final class CollectionAttributeFilter extends AbstractBlock {
return Dropdown::render(
array(
'items' => $list_items,
'action' => 'woocommerce/collection-attribute-filter::actions.navigate',
'selected_item' => $selected_item,
'text_color' => $text_color,
'items' => $list_items,
'action' => 'woocommerce/collection-attribute-filter::actions.navigate',
'selected_items' => array( $selected_item ),
)
);
}

View File

@ -111,9 +111,6 @@ final class CollectionRatingFilter extends AbstractBlock {
return '';
}
$text_color_class_and_style = StyleAttributesUtils::get_text_color_class_and_style( $attributes );
$text_color = $text_color_class_and_style['value'] ?? '';
$rating_counts = $block->context['collectionData']['rating_counts'] ?? array();
$display_style = $attributes['displayStyle'] ?? 'list';
$show_counts = $attributes['showCounts'] ?? false;
@ -141,7 +138,7 @@ final class CollectionRatingFilter extends AbstractBlock {
'on_change' => 'woocommerce/collection-rating-filter::actions.onCheckboxChange',
)
) : Dropdown::render(
$this->get_dropdown_props( $rating_counts, $selected_ratings_query_param, $show_counts, $attributes['selectType'], $text_color )
$this->get_dropdown_props( $rating_counts, $selected_ratings_query_param, $show_counts, $attributes['selectType'] )
);
return sprintf(
@ -222,10 +219,9 @@ final class CollectionRatingFilter extends AbstractBlock {
* @param mixed $selected_ratings_query The url query param for selected ratings.
* @param bool $show_counts Whether to show the counts.
* @param string $select_type The select type. (single|multiple).
* @param string $text_color The text color.
* @return array<array-key, array>
*/
private function get_dropdown_props( $rating_counts, $selected_ratings_query, $show_counts, $select_type, $text_color ) {
private function get_dropdown_props( $rating_counts, $selected_ratings_query, $show_counts, $select_type ) {
$ratings_array = explode( ',', $selected_ratings_query );
$selected_items = array_reduce(
@ -263,7 +259,6 @@ final class CollectionRatingFilter extends AbstractBlock {
'select_type' => $select_type,
'selected_items' => $selected_items,
'action' => 'woocommerce/collection-rating-filter::actions.onDropdownChange',
'text_color' => $text_color,
);
}
}

View File

@ -148,14 +148,13 @@ final class CollectionStockFilter extends AbstractBlock {
private function get_stock_filter_html( $stock_counts, $attributes ) {
$display_style = $attributes['displayStyle'] ?? 'list';
$show_counts = $attributes['showCounts'] ?? false;
$select_type = $attributes['selectType'] ?? 'single';
$stock_statuses = wc_get_product_stock_status_options();
$text_color_class_and_style = StyleAttributesUtils::get_text_color_class_and_style( $attributes );
$text_color = $text_color_class_and_style['value'] ?? '';
// check the url params to select initial item on page load.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here.
$selected_stock_status = isset( $_GET[ self::STOCK_STATUS_QUERY_VAR ] ) ? sanitize_text_field( wp_unslash( $_GET[ self::STOCK_STATUS_QUERY_VAR ] ) ) : '';
$query = isset( $_GET[ self::STOCK_STATUS_QUERY_VAR ] ) ? sanitize_text_field( wp_unslash( $_GET[ self::STOCK_STATUS_QUERY_VAR ] ) ) : '';
$selected_stock_statuses = explode( ',', $query );
$list_items = array_map(
function( $item ) use ( $stock_statuses, $show_counts ) {
@ -171,18 +170,12 @@ final class CollectionStockFilter extends AbstractBlock {
$selected_items = array_values(
array_filter(
$list_items,
function( $item ) use ( $selected_stock_status ) {
return $item['value'] === $selected_stock_status;
function( $item ) use ( $selected_stock_statuses ) {
return in_array( $item['value'], $selected_stock_statuses, true );
}
)
);
// Just for the dropdown, we can only select 1 item.
$selected_item = $selected_items[0] ?? array(
'label' => null,
'value' => null,
);
$data_directive = wp_json_encode( array( 'namespace' => 'woocommerce/collection-stock-filter' ) );
ob_start();
@ -203,7 +196,7 @@ final class CollectionStockFilter extends AbstractBlock {
aria-invalid="false"
data-wc-on--change="actions.updateProducts"
value="<?php echo esc_attr( $stock_count['status'] ); ?>"
<?php checked( strpos( $selected_stock_status, $stock_count['status'] ) !== false, 1 ); ?>
<?php checked( strpos( $query, $stock_count['status'] ) !== false, 1 ); ?>
>
<svg class="wc-block-components-checkbox__mark" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20">
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"></path>
@ -239,10 +232,10 @@ final class CollectionStockFilter extends AbstractBlock {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Dropdown::render() escapes output.
echo Dropdown::render(
array(
'items' => $list_items,
'action' => 'woocommerce/collection-stock-filter::actions.navigate',
'selected_item' => $selected_item,
'text_color' => $text_color,
'items' => $list_items,
'action' => 'woocommerce/collection-stock-filter::actions.navigate',
'selected_items' => $selected_items,
'select_type' => $select_type,
)
);
?>

View File

@ -18,10 +18,8 @@ class Dropdown {
wp_enqueue_script( 'wc-interactivity-dropdown' );
wp_enqueue_style( 'wc-interactivity-dropdown' );
$select_type = $props['select_type'] ?? 'single';
$selected_items = $props['selected_items'] ?? array();
$text_color = $props['text_color'] ?? 'inherit';
$text_color_style = "color: {$text_color};";
$select_type = $props['select_type'] ?? 'single';
$selected_items = $props['selected_items'] ?? array();
// Items should be an array of objects with a label and value property.
$items = $props['items'] ?? array();
@ -32,117 +30,90 @@ class Dropdown {
'selectType' => $select_type,
);
$action = $props['action'] ?? '';
$namespace = wp_json_encode( array( 'namespace' => 'woocommerce/interactivity-dropdown' ) );
$wrapper_class = 'multiple' === $select_type ? '' : 'single-selection';
$input_id = wp_unique_id( 'wc-interactivity-dropdown-input-' );
wp_add_inline_style(
'wc-interactivity-dropdown',
"#$input_id::placeholder {
$text_color_style
}"
);
$wrapper_class = 'multiple' === $select_type ? '' : 'single-selection';
$action = $props['action'] ?? '';
$namespace = wp_json_encode( array( 'namespace' => 'woocommerce/interactivity-dropdown' ) );
$default_placeholder = 'single' === $select_type ? __( 'Select an option', 'woocommerce' ) : __( 'Select options', 'woocommerce' );
ob_start();
?>
<div data-wc-interactive='<?php echo esc_attr( $namespace ); ?>'>
<div class="wc-interactivity-dropdown" data-wc-context='<?php echo esc_attr( wp_json_encode( $dropdown_context ) ); ?>' >
<div class="wc-blocks-components-form-token-field-wrapper <?php echo esc_attr( $wrapper_class ); ?>" >
<div class="components-form-token-field" tabindex="-1">
<div class="components-form-token-field__input-container"
data-wc-class--is-active="context.isOpen"
tabindex="-1"
data-wc-on--click="actions.toggleIsOpen"
>
<?php if ( 'multiple' === $select_type ) { ?>
<div class="wc-interactivity-dropdown" data-wc-on--click="actions.toggleIsOpen" data-wc-context='<?php echo esc_attr( wp_json_encode( $dropdown_context ) ); ?>' >
<div class="wc-interactivity-dropdown__dropdown" tabindex="-1" >
<div class="wc-interactivity-dropdown__dropdown-selection" id="options-dropdown" tabindex="0" aria-haspopup="listbox">
<span class="wc-interactivity-dropdown__placeholder" data-wc-text="state.placeholderText">
<?php echo esc_html( $default_placeholder ); ?>
</span>
<?php if ( 'multiple' === $select_type ) { ?>
<div class="selected-options">
<template
data-wc-each="context.selectedItems"
data-wc-each-key="context.item.value"
>
<span class="components-form-token-field__token">
<span
class="components-form-token-field__token-text"
data-wc-text="context.item.label"
style="<?php echo esc_attr( $text_color_style ); ?>"
></span>
<button
type="button"
data-wc-on--click="actions.unselectDropdownItem"
data-wc-on--click--parent-action="<?php echo esc_attr( $action ); ?>"
class="components-button components-form-token-field__remove-token has-icon"
>
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
data-wc-each="context.selectedItems"
data-wc-each-key="context.item.value"
>
<div class="wc-interactivity-dropdown__selected-badge">
<span class="wc-interactivity-dropdown__badge-text" data-wc-text="context.item.label"></span>
<svg
data-wc-on--click="actions.unselectDropdownItem"
class="wc-interactivity-dropdown__badge-remove"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path d="M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z"></path>
</svg>
</button>
</span>
</div>
</template>
<?php foreach ( $selected_items as $selected ) { ?>
<span
class="components-form-token-field__token"
<div
class="wc-interactivity-dropdown__selected-badge"
data-wc-key="<?php echo esc_attr( $selected['label'] ); ?>"
data-wc-each-child
>
<span
class="components-form-token-field__token-text"
style="<?php echo esc_attr( $text_color_style ); ?>"
>
<?php echo esc_html( $selected['label'] ); ?>
</span>
<button
type="button"
class="components-button components-form-token-field__remove-token has-icon"
>
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<span class="wc-interactivity-dropdown__badge-text"><?php echo esc_html( $selected['label'] ); ?></span>
<svg
data-wc-on--click="actions.unselectDropdownItem"
class="wc-interactivity-dropdown__badge-remove"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path d="M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z"></path>
</svg>
</button>
</span>
</div>
<?php } ?>
<?php } ?>
<input
id="<?php echo esc_attr( $input_id ); ?>"
readonly
type="text"
autocomplete="off"
data-wc-bind--placeholder="state.placeholderText"
class="components-form-token-field__input"
role="combobox"
aria-expanded="false"
aria-autocomplete="list"
value=""
data-wc-key="input"
</div>
<?php } ?>
<span class="wc-interactivity-dropdown__svg-container">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="30" height="30" >
<path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z" ></path>
</svg>
</span>
</div>
<div data-wc-bind--hidden="!context.isOpen" class="wc-interactivity-dropdown__dropdown-list" aria-labelledby="options-dropdown" role="listbox">
<?php
foreach ( $items as $item ) :
$context = array( 'item' => $item );
?>
<div
class="wc-interactivity-dropdown__dropdown-option"
role="option"
tabindex="0"
data-wc-on--click--select-item="actions.selectDropdownItem"
data-wc-on--click--parent-action="<?php echo esc_attr( $action ); ?>"
data-wc-class--is-selected="state.isSelected"
class="components-form-token-field__suggestion"
data-wc-bind--aria-selected="state.isSelected"
data-wc-context='<?php echo wp_json_encode( $context ); ?>'
>
<ul hidden data-wc-bind--hidden="!context.isOpen" class="components-form-token-field__suggestions-list" id="components-form-token-suggestions-1" role="listbox" data-wc-key="ul">
<?php
foreach ( $items as $item ) :
$context = array( 'item' => $item );
?>
<li
role="option"
data-wc-on--click--select-item="actions.selectDropdownItem"
data-wc-on--click--parent-action="<?php echo esc_attr( $action ); ?>"
data-wc-class--is-selected="state.isSelected"
class="components-form-token-field__suggestion"
data-wc-bind--aria-selected="state.isSelected"
data-wc-context='<?php echo wp_json_encode( $context ); ?>'
style="<?php echo esc_attr( $text_color_style ); ?>"
>
<?php // This attribute supports HTML so should be sanitized by caller. ?>
<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php echo $item['label']; ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php echo $item['label']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<?php endforeach; ?>
</div>
</div>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="30" height="30" >
<path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z" ></path>
</svg>
</div>
</div>
<?php