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

Rework combobox.jsx to functional component #3854

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from

Conversation

Gazook89
Copy link
Collaborator

@Gazook89 Gazook89 commented Oct 24, 2024

Description

This is a rework of the Combobox component. It is now a Functional component. It makes a few small changes to structure (the options are all buttons now). It also adds better keyboard navigation (which is pretty much all of the "new lines of code"). Beyond that, it is mostly the same at this point.

Right now, only the Language picker uses this component. I started to rework it because I want to use it in the TagInput component (aka StringArrayEditor).

Beyond whatever is suggested in review, some additional things I want to do:

  • add a "expand"/"open" button to the right side of the input as a button to open it (in truth, the whole input toggles the menu so this will just be a little visual indicator).
  • a quick pass on accessibility issues (roles, hidden navigation)
  • get the validators working with it again.

Then, as a subsequent PR, I want to work out a good way to get submenus to work.

Related Issues or Discussions

QA Instructions, Screenshots, Recordings

Try out the Language picker, using your mouse and navigating it with the keyboard. You should be able to:

  • Tab into it and focus the input
  • Space to open the menu with all options, Space again to close it
  • Type any characters to open menu to filtered options
  • Typing Space should still work to add whitespace, without opening/closing the menu, as long as the input isn't otherwise empty
  • Esc to close the drop down
  • Enter to select an option
  • ArrowDown from input to open and navigate through menu, cycling to first option if reached the bottom
  • ArrowUp to move through menu, focusing the input when you move past the first option
  • when focus is on menu option, be able to use Backspace to move focus to input and delete characters.
  • Tab out to next input closes the dropdown and moves focus.
  • entering a value that filters to zero results displays a 'no matches' item.

Possibly some of these behaviors will change as i look at WCAG for comboboxes and I'll try to update this list.

Reviewer Checklist

*Reviewers, refer to this list when testing features, or suggest new items *

Copy this list
- [ ] Verify new features are functional
	- [ ] Tab into it and focus the input
	- [ ] Space to open the menu with all options, Space again to close it
	- [ ] Type any characters to open menu to filtered options
	- [ ] Typing Space should still work to add whitespace, without opening/closing the menu, as long as the input isn't otherwise empty
	- [ ] Esc to close the drop down
	- [ ] Enter to select an option
	- [ ] ArrowDown from input to open and navigate through menu, cycling to first option if reached the bottom
	- [ ] ArrowUp to move through menu, focusing the input when you move past the first option
	- [ ] when focus is on menu option, be able to use Backspace to move focus to input and delete characters.
	- [ ] Tab out to next input closes the dropdown and moves focus.
	- [ ] entering a value that filters to zero results displays a 'no matches' item.
- [ ] Verify old features have not broken
  - [ ] Still correctly sets the `lang` attribute and is preserved with the brew metadata
- [ ] Test for edge cases / try to break things
- [ ] Identify opportunities for simplification and refactoring
- [ ] Check for code legibility and appropriate comments

This is barebones features of the combobox, but written as a functional component.  It features a text input and a div containing a list of buttons with the different options included.  It functionally updates the brew metadata props and loads them when the component initializes.

Many features of the original combobox are still missing.

Light re-styling.

Small HTML changes to metadataEditor.  Change the list of options to buttons rather than divs with onClick attributes.

Change how options are created and passed into Combobox component.
Closes the dropdown menu when clicked outside the component.
This allows the dropdown to contain all available children when it is first opened when otherwise it would be empty.
small lintining
Creates a simple span that indicates no matches for a given input value.
Copy link
Member

@5e-Cleric 5e-Cleric left a comment

Choose a reason for hiding this comment

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

Overall looks fine, i made more suggestions than i thought, sorry for making them separate.

This makes it easier to open the menu with Space when you tab into the input, since the text gets highlighted (and would otherwise get deleted/replaced by ' ').
lightened up the grays on focus and hover to leave the writing slightly more legible.
@Gazook89 Gazook89 requested review from 5e-Cleric and ericscheid and removed request for ericscheid October 25, 2024 01:16
if(this.refs.dropdown && !this.refs.dropdown.contains(e.target)) {
this.handleDropdown(false);

document.addEventListener('pointerdown', handleClickOutside);
Copy link
Member

Choose a reason for hiding this comment

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

This is also adding a new event listener every time showDropdown changes.

If you want to add a listener once when the component first mounts, change the [showDropdown] to an empty array []

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If just change this to onMount [], it doesn't work. But it works if i remove the showDropdown condition in the handler:

const handleClickOutside = (evt)=>{
		console.log(componentRef.current, showDropdown)
		if(componentRef.current && !componentRef.current.contains(evt.target)) {
			setShowDropdown(false);
		}
	};

So that's what i'll do.

Comment on lines 26 to 29
useEffect(()=>{
onSelect(inputValue);
// handleInputChange({ target: { value: inputValue } });
}, [inputValue]);
Copy link
Member

@calculuschild calculuschild Oct 25, 2024

Choose a reason for hiding this comment

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

useEffect is not usually not needed for this type of case. Here, we trigger two renders, first when inputValue state changes, and then again when onSelect() changes some state of the parent metaDataEditor.

Instead of setting inputValue and then handling side effects later in useEffect, both setInputValue and calling onSelect() should happen together in a handler function, probably in your handleInputChange() function.

// Handle keyboard navigation
const handleKeyDown = (evt)=>{
const modifiers = ['Meta', 'Shift', 'Alt', 'Control', 'Tab'];
if(inputFocused || (currentOption >= 0)) {
Copy link
Member

Choose a reason for hiding this comment

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

If we are already focused, why do we need to keep setting focus for the different keys below?

// handleInputChange({ target: { value: inputValue } });
}, [inputValue]);

useEffect(()=>{
Copy link
Member

Choose a reason for hiding this comment

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

Similar comment here. Changing currentOption and the side effect of focusing the element should happen inside of a handler function, not inside useEffect.

See https://react.dev/learn/you-might-not-need-an-effect

And removes the dependence on showDropdown of that effect.
@Gazook89
Copy link
Collaborator Author

Thanks for reviewing. I'm going to leave some comments unaddressed because I'm in the middle of refiguring out how focus is managed and a few small behavior changes as part of the "align with WCAG" portion of this. I didn't think I'd get two code reviews so quick :) but they were still valuable and I changed a few things as suggested.

@5e-Cleric 5e-Cleric added cleanup Cleaning up code for legibility, style, ease-of-use, etc. 🔍 R_ - Not ready for review 🕑 labels Oct 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cleanup Cleaning up code for legibility, style, ease-of-use, etc. 🔍 R_ - Not ready for review 🕑
Projects
Status: Backlog
Development

Successfully merging this pull request may close these issues.

3 participants