Skip to content

Commit

Permalink
Merge pull request #20496 from Yoast/4009-improve-site-policies-selec…
Browse files Browse the repository at this point in the history
…t-menu

4009 improve site policies select menu
  • Loading branch information
enricobattocchi committed Jul 12, 2023
2 parents 9f4f2b4 + 011138c commit b95a51d
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 17 deletions.
13 changes: 3 additions & 10 deletions packages/js/src/settings/components/formik-page-select-field.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,6 @@ const FormikPageSelectField = ( { name, id, className = "", ...props } ) => {
}, [ value, pages ] );

const debouncedFetchPages = useCallback( debounce( async search => {
if ( search === "" ) {
// No need to fetch pages if there is no search term.
setQueriedPageIds( map( pages, "id" ) );
setValue( 0 );
setStatus( ASYNC_ACTION_STATUS.success );
return;
}

try {
setStatus( ASYNC_ACTION_STATUS.loading );
// eslint-disable-next-line camelcase
Expand Down Expand Up @@ -90,11 +82,12 @@ const FormikPageSelectField = ( { name, id, className = "", ...props } ) => {
// Hack to force re-render of Headless UI Combobox.Input component when selectedPage changes.
value={ selectedPage ? value : 0 }
onChange={ handleChange }
placeholder={ __( "Select a page...", "wordpress-seo" ) }
placeholder={ __( "None", "wordpress-seo" ) }
selectedLabel={ selectedPage?.name }
onQueryChange={ handleQueryChange }
className={ className && props.disabled && "yst-autocomplete--disabled" }
className={ classNames( className, props.disabled && "yst-autocomplete--disabled" ) }
nullable={ true }
clearButtonScreenReaderText={ __( "Clear selection", "wordpress-seo" ) }
{ ...props }
>
<>
Expand Down
9 changes: 4 additions & 5 deletions packages/js/src/settings/store/pages.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable camelcase, complexity */
import { createEntityAdapter, createSelector, createSlice } from "@reduxjs/toolkit";
import apiFetch from "@wordpress/api-fetch";
import { __ } from "@wordpress/i18n";
import { decodeEntities } from "@wordpress/html-entities";
import { buildQueryString } from "@wordpress/url";
import { map, trim, pickBy } from "lodash";
import { ASYNC_ACTION_NAMES, ASYNC_ACTION_STATUS } from "../constants";
Expand Down Expand Up @@ -33,13 +33,13 @@ export function* fetchPages( queryData ) {

/**
* @param {Object} page The page.
* @returns {Object} The prepared and predictable user.
* @returns {Object} The prepared and predictable page.
*/
const preparePage = page => (
{
id: page?.id,
// Fallbacks for page title, because we always need something to show.
name: trim( page?.title.rendered ) || page?.slug || page.id,
name: decodeEntities( trim( page?.title.rendered ) ) || page?.slug || page.id,
slug: page?.slug,
"protected": page?.content?.protected,
} );
Expand Down Expand Up @@ -90,8 +90,7 @@ pageSelectors.selectPagesWith = createSelector(
( state, additionalPage = {} ) => additionalPage,
],
( pages, additionalPage ) => {
const none = { id: 0, name: __( "None", "wordpress-seo" ), slug: null, "protected": null };
const additionalPages = { 0: none };
const additionalPages = {};
additionalPage.forEach( page => {
if ( page?.id && ! pages[ page.id ] ) {
// Add the additional page.
Expand Down
35 changes: 34 additions & 1 deletion packages/ui-library/src/elements/autocomplete/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import PropTypes from "prop-types";
import { forwardRef, Fragment, useCallback } from "@wordpress/element";
import { Combobox, Transition } from "@headlessui/react";
import { SelectorIcon, CheckIcon } from "@heroicons/react/solid";
import { XIcon } from "@heroicons/react/outline";
import classNames from "classnames";
import { constant } from "lodash";
import { useSvgAria } from "../../hooks";
Expand Down Expand Up @@ -49,6 +50,31 @@ const optionPropType = {

Option.propTypes = optionPropType;

/**
*
* @param {Function} onChange Change callback.
* @param {Object} svgAriaProps SVG aria props.
* @param {string} screenReaderText Screen reader text.
* @returns {JSX.Element} Select component.
*/
const ClearSelection = ( { onChange, svgAriaProps, screenReaderText } ) => {
const clear = useCallback( ( e )=> {
e.preventDefault();
onChange( null );
}, [ onChange ] );

return <button className="yst-mr-4 yst-flex yst-items-center" onClick={ clear }>
<span className="yst-sr-only">{ screenReaderText }</span>
<XIcon className="yst-text-slate-400 yst-w-5 yst-h-5" { ...svgAriaProps } />
<div className="yst-w-2 yst-mr-2 yst-border-r-slate-200 yst-border-r yst-h-7" />
</button>;
};

ClearSelection.propTypes = {
onChange: PropTypes.func.isRequired,
svgAriaProps: PropTypes.object.isRequired,
screenReaderText: PropTypes.string.isRequired,
};

/**
* @param {string} id Identifier.
Expand Down Expand Up @@ -81,6 +107,7 @@ const Autocomplete = forwardRef( ( {
placeholder,
className,
buttonProps,
clearButtonScreenReaderText,
...props
}, ref ) => {
const getDisplayValue = useCallback( constant( selectedLabel ), [ selectedLabel ] );
Expand Down Expand Up @@ -109,10 +136,13 @@ const Autocomplete = forwardRef( ( {
>
<Combobox.Input
className="yst-autocomplete__input"
autoComplete="off"
placeholder={ placeholder }
displayValue={ getDisplayValue }
onChange={ onQueryChange }
/>
{ props.nullable && selectedLabel &&
<ClearSelection onChange={ onChange } svgAriaProps={ svgAriaProps } screenReaderText={ clearButtonScreenReaderText } /> }
{ ! validation?.message && (
<SelectorIcon className="yst-autocomplete__button-icon" { ...svgAriaProps } />
) }
Expand Down Expand Up @@ -141,7 +171,7 @@ Autocomplete.Option.displayName = "Autocomplete.Option";

const propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, PropTypes.bool ] ).isRequired,
value: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, PropTypes.bool ] ),
children: PropTypes.node,
selectedLabel: PropTypes.string,
label: PropTypes.string,
Expand All @@ -156,11 +186,13 @@ const propTypes = {
placeholder: PropTypes.string,
className: PropTypes.string,
buttonProps: PropTypes.object,
clearButtonScreenReaderText: PropTypes.string,
};
Autocomplete.propTypes = propTypes;

Autocomplete.defaultProps = {
children: null,
value: null,
selectedLabel: "",
label: "",
labelProps: {},
Expand All @@ -169,6 +201,7 @@ Autocomplete.defaultProps = {
placeholder: "",
className: "",
buttonProps: {},
clearButtonScreenReaderText: "Clear",
};

export default Autocomplete;
Expand Down
18 changes: 18 additions & 0 deletions packages/ui-library/src/elements/autocomplete/stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default {
argTypes: {
children: { control: "text" },
labelSuffix: { control: "text" },
nullable: { control: "boolean" },
},
parameters: {
docs: {
Expand Down Expand Up @@ -88,6 +89,23 @@ WithLabel.args = {
label: "Example label",
};

export const Nullable = Template.bind( {} );

Nullable.storyName = "Nullable";

Nullable.parameters = {
controls: { disable: false },
docs: { description: { story: "Allow empty values with reset button `X` or deleting the option and clicking outside the field." } },
};

Nullable.args = {
id: "with-label",
value: "",
label: "Example label",
nullable: true,
placeholder: "None...",
};

export const WithPlaceholder = Template.bind( {} );

WithPlaceholder.storyName = "With placeholder";
Expand Down
3 changes: 2 additions & 1 deletion packages/ui-library/src/elements/autocomplete/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
yst-pr-10
yst-shadow-none
yst-text-sm
focus:yst-ring-0;
focus:yst-ring-0
yst-text-slate-800;
}

.yst-autocomplete__options {
Expand Down

0 comments on commit b95a51d

Please sign in to comment.