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

Add loading indicator to combo box control author selector #68927

10 changes: 9 additions & 1 deletion packages/components/src/combobox-control/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function MyComboboxControl() {
label="Font Size"
value={ fontSize }
onChange={ setFontSize }
isLoading={ isLoading }
options={ filteredOptions }
onFilterValueChange={ ( inputValue ) =>
setFilteredOptions(
Expand Down Expand Up @@ -112,13 +113,20 @@ If the control is clicked, the dropdown will expand regardless of this prop.
- Required: No
- Default: `true`

### placeholder
#### placeholder
Copy link
Contributor

Choose a reason for hiding this comment

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

🙏


If passed, the combobox input will show a placeholder string if no values are present.

- Type: `string`
- Required: No

#### isLoading

If true, the dropdown will show a loading indicator.
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's match the same description as the one from types.ts

Copy link
Member Author

Choose a reason for hiding this comment

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

matched


- Type: `Boolean`
- Required: No

#### __experimentalRenderItem

Custom renderer invoked for each option in the suggestion list. The render prop receives as its argument an object containing, under the `item` key, the single option's data (directly from the array of data passed to the `options` prop).
Expand Down
5 changes: 4 additions & 1 deletion packages/components/src/combobox-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type { TokenInputProps } from '../form-token-field/types';
import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props';
import { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events';
import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size';
import Spinner from '../spinner';

const noop = () => {};

Expand Down Expand Up @@ -126,6 +127,7 @@ function ComboboxControl( props: ComboboxControlProps ) {
help,
allowReset = true,
className,
isLoading = false,
Copy link
Contributor

Choose a reason for hiding this comment

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

Adding this prop won't affect existing usages, although I wonder if it's a good idea, API-wise.

The component doesn't have a canonical example of how to use it with asynchronously loaded options, and I wonder if adding this prop will improve the DevX (related to #55574) — @WordPress/gutenberg-components what do you think?

If we want to encourage this pattern, we should at least add a Storybook example about it?

Copy link
Member Author

@adamsilverstein adamsilverstein Jan 31, 2025

Choose a reason for hiding this comment

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

I wonder if adding this prop will improve the DevX

Not sure about DevX, this change is about improving User Experience. As is, the combobox selector provides a poor UX when the internet connection is slow or unreliable. The user has no way of knowing that a search is underway during the async search. This shortcoming was overlooked when we originally built the component and I feel this more of a bug than an enhancement.

messages = {
selected: __( 'Item selected.' ),
},
Expand Down Expand Up @@ -362,6 +364,7 @@ function ComboboxControl( props: ComboboxControlProps ) {
onChange={ onInputChange }
/>
</FlexBlock>
{ isLoading && <Spinner /> }
{ allowReset && (
<Button
size="small"
Expand All @@ -375,7 +378,7 @@ function ComboboxControl( props: ComboboxControlProps ) {
/>
) }
</InputWrapperFlex>
{ isExpanded && (
{ isExpanded && ! isLoading && (
<SuggestionsList
instanceId={ instanceId }
// The empty string for `value` here is not actually used, but is
Expand Down
6 changes: 6 additions & 0 deletions packages/components/src/combobox-control/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,10 @@ export type ComboboxControlProps = Pick<
* If passed, the combobox input will show a placeholder string if no values are present.
*/
placeholder?: string;

/**
* Show a spinner (and hide the suggestions dropdown) while data
* about the matching suggestions (ie the `options` prop) is loading
*/
isLoading?: boolean;
};
4 changes: 3 additions & 1 deletion packages/editor/src/components/post-author/combobox.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export default function PostAuthorCombobox() {
const [ fieldValue, setFieldValue ] = useState();

const { editPost } = useDispatch( editorStore );
const { authorId, authorOptions } = useAuthorsQuery( fieldValue );
const { authorId, authorOptions, isLoading } =
useAuthorsQuery( fieldValue );

/**
* Handle author selection.
Expand Down Expand Up @@ -51,6 +52,7 @@ export default function PostAuthorCombobox() {
onChange={ handleSelect }
allowReset={ false }
hideLabelFromVision
isLoading={ isLoading }
Copy link
Contributor

Choose a reason for hiding this comment

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

My preference would we to focus this PR solely on the ComboboxControl component.

We should add a Storybook example here to allow for easy testing of the component in isolation.

In a separate, follow-up PR, we could then tweak the post author combobox to use the new prop

Copy link
Member Author

Choose a reason for hiding this comment

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

The entire point of this PR is to improve the author selector - changing only the combobox won't actually change anything, shouldn't we aim for PRs that actually improve things?

I can look into adding to the storybook, would this just be a variation with the isLoading prop?

Copy link
Contributor

Choose a reason for hiding this comment

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

The @wordpress/component package is not only used in the Gutenberg repository, but it's also used by third-party developers. Therefore, we usually try to keep changes to the "library" separate from changes to the Gutenberg app (hence why I suggest testing the changes in isolation in Storybook).

Having smaller and more focused PRs is also easier to review, and understand for a future developer stumbling upon it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Having smaller and more focused PRs is also easier to review, and understand for a future developer stumbling upon it.

I understand the principal, however this entire PR is currently less than 35 lines modified so I think is already easy to review and understand! Given the testing instructions I provided, I feel having the PR do something makes it easier to test and understand.

That said, I sense that you feel strongly about this and I will defer to your more recent experience building here. I will split the combobox and author changes into separate PRs.

Copy link
Member Author

Choose a reason for hiding this comment

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

I created a follow up PR with just the Comobox changes here:

#68990

This is a follow up PR to introduce the post author changes:

#68991

Let's continue the conversation on those PRs.

/>
);
}
7 changes: 4 additions & 3 deletions packages/editor/src/components/post-author/hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import { store as editorStore } from '../../store';
import { AUTHORS_QUERY, BASE_QUERY } from './constants';

export function useAuthorsQuery( search ) {
const { authorId, authors, postAuthor } = useSelect(
const { authorId, authors, postAuthor, isLoading } = useSelect(
( select ) => {
const { getUser, getUsers } = select( coreStore );
const { getUser, getUsers, isResolving } = select( coreStore );
const { getEditedPostAttribute } = select( editorStore );
const _authorId = getEditedPostAttribute( 'author' );
const query = { ...AUTHORS_QUERY };
Expand All @@ -30,6 +30,7 @@ export function useAuthorsQuery( search ) {
authorId: _authorId,
authors: getUsers( query ),
postAuthor: getUser( _authorId, BASE_QUERY ),
isLoading: isResolving( 'getUsers', [ query ] ),
};
},
[ search ]
Expand Down Expand Up @@ -68,5 +69,5 @@ export function useAuthorsQuery( search ) {
return [ ...currentAuthor, ...fetchedAuthors ];
}, [ authors, postAuthor ] );

return { authorId, authorOptions, postAuthor };
return { authorId, authorOptions, postAuthor, isLoading };
}
Loading