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

CustomSelectControl: Update to use a Popover component for rendering the internals #37272

Closed
wants to merge 13 commits into from

Conversation

andrewserong
Copy link
Contributor

@andrewserong andrewserong commented Dec 10, 2021

Description

Raised in #36545 (comment), the CustomSelectControl component gets cut off when used in certain areas, such as in the global styles sidebar.

This PR explores using a Popover component for rendering the drop down items. The goal is to ensure that the dropdown items do not get cut off, and that we preserve accessibility and backwards compatibility of the component.

Kudos @aaronrobertshaw for the approach in this PR.

How has this been tested?

Open up the site editor and go to the global styles tab. Select block styles, and go to a block that opts in to Typography controls (e.g. the Heading block). Select from the font size dropdown and before this PR, you should see that the dropdown gets cut off visually. After this PR, the Popover component should try to ensure that the dropdown options are always visible, or at least can be scrolled to.

Check to ensure that keyboard navigation is unaffected.

You can also test in Storybook by running npm run storybook:dev and then navigate to: http://localhost:50240/?path=/story/components-customselectcontrol--long-labels

To-do

  • Update styling to ensure the correct width is set for the dropdown
  • Update the position of where the Popover opens from, so that it's correctly aligned
  • Update the border of the Popover as it looks like it adds an extra grey border
  • Try using the DropdownMenu component instead of the Popover component (Reverted attempt to use DropdownMenu component due to accessibility issues)
  • Render popover position relative to the Container position and size
  • Place the Popover behaviour before an experimental prop (e.g. __experimentalPopover) so that we don't break backwards compatibility? Update: since we've managed to pretty well preserve the styling of the menu, I'm not sure this is worth it. Let me know if anyone thinks it's necessary, though!

Screenshots

Before After
image Kapture 2021-12-10 at 15 27 28

Types of changes

Bug fix (non-breaking change which fixes an issue)

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • I've tested my changes with keyboard and screen readers.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR (please manually search all *.native.js files for terms that need renaming or removal).

@andrewserong andrewserong added [Type] Bug An existing feature does not function as intended [Package] Components /packages/components [Feature] Design Tools Tools that impact the appearance of blocks both to expand the number of tools and improve the experi labels Dec 10, 2021
@andrewserong andrewserong added the [Status] In Progress Tracking issues with work in progress label Dec 10, 2021
@andrewserong andrewserong changed the title CustomSelectControl: Update to use a Popover component for rendering the internals [WIP] CustomSelectControl: Update to use a Popover component for rendering the internals Dec 10, 2021
@github-actions
Copy link

github-actions bot commented Dec 10, 2021

Size Change: +1.37 kB (0%)

Total Size: 1.13 MB

Filename Size Change
build/block-editor/index.min.js 140 kB +11 B (0%)
build/block-editor/style-rtl.css 14.6 kB -1 B (0%)
build/block-editor/style.css 14.6 kB +1 B (0%)
build/block-library/blocks/buttons/editor-rtl.css 292 B +1 B (0%)
build/block-library/blocks/buttons/editor.css 292 B +1 B (0%)
build/block-library/blocks/cover/style-rtl.css 1.22 kB -1 B (0%)
build/block-library/blocks/cover/style.css 1.22 kB -2 B (0%)
build/block-library/blocks/embed/editor-rtl.css 293 B -195 B (-40%) 🎉
build/block-library/blocks/embed/editor.css 293 B -195 B (-40%) 🎉
build/block-library/blocks/gallery/style-rtl.css 1.6 kB -22 B (-1%)
build/block-library/blocks/gallery/style.css 1.6 kB -23 B (-1%)
build/block-library/blocks/navigation/editor-rtl.css 1.91 kB -2 B (0%)
build/block-library/blocks/navigation/editor.css 1.91 kB -8 B (0%)
build/block-library/blocks/navigation/style-rtl.css 1.8 kB +111 B (+7%) 🔍
build/block-library/blocks/navigation/style.css 1.79 kB +107 B (+6%) 🔍
build/block-library/blocks/post-comments/style-rtl.css 509 B +2 B (0%)
build/block-library/blocks/post-comments/style.css 509 B +2 B (0%)
build/block-library/blocks/query-pagination/style-rtl.css 234 B -39 B (-14%) 👏
build/block-library/blocks/query-pagination/style.css 231 B -38 B (-14%) 👏
build/block-library/blocks/video/editor-rtl.css 571 B +2 B (0%)
build/block-library/blocks/video/editor.css 572 B +2 B (0%)
build/block-library/editor-rtl.css 10 kB -22 B (0%)
build/block-library/editor.css 10 kB -20 B (0%)
build/block-library/index.min.js 165 kB +894 B (+1%)
build/block-library/style-rtl.css 10.8 kB +56 B (+1%)
build/block-library/style.css 10.9 kB +53 B (0%)
build/components/index.min.js 215 kB +177 B (0%)
build/components/style-rtl.css 15.5 kB +12 B (0%)
build/components/style.css 15.5 kB +12 B (0%)
build/edit-post/index.min.js 29.5 kB +155 B (+1%)
build/edit-post/style-rtl.css 7.16 kB +33 B (0%)
build/edit-post/style.css 7.16 kB +32 B (0%)
build/edit-site/index.min.js 35.8 kB +244 B (+1%)
build/edit-site/style-rtl.css 6.62 kB +11 B (0%)
build/edit-site/style.css 6.61 kB +10 B (0%)
build/edit-widgets/style-rtl.css 4.17 kB -2 B (0%)
build/editor/index.min.js 37.9 kB +9 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 960 B
build/admin-manifest/index.min.js 1.1 kB
build/annotations/index.min.js 2.75 kB
build/api-fetch/index.min.js 2.21 kB
build/autop/index.min.js 2.12 kB
build/blob/index.min.js 459 B
build/block-directory/index.min.js 6.28 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 58 B
build/block-library/blocks/audio/editor.css 58 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 470 B
build/block-library/blocks/button/editor.css 470 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 90 B
build/block-library/blocks/code/style.css 90 B
build/block-library/blocks/code/theme-rtl.css 134 B
build/block-library/blocks/code/theme.css 134 B
build/block-library/blocks/columns/editor-rtl.css 210 B
build/block-library/blocks/columns/editor.css 208 B
build/block-library/blocks/columns/style-rtl.css 502 B
build/block-library/blocks/columns/style.css 501 B
build/block-library/blocks/comment-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/embed/style-rtl.css 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 322 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 966 B
build/block-library/blocks/gallery/editor.css 970 B
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 159 B
build/block-library/blocks/group/editor.css 159 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 114 B
build/block-library/blocks/heading/style.css 114 B
build/block-library/blocks/html/editor-rtl.css 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 810 B
build/block-library/blocks/image/editor.css 809 B
build/block-library/blocks/image/style-rtl.css 507 B
build/block-library/blocks/image/style.css 511 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 137 B
build/block-library/blocks/latest-posts/editor.css 137 B
build/block-library/blocks/latest-posts/style-rtl.css 528 B
build/block-library/blocks/latest-posts/style.css 527 B
build/block-library/blocks/list/style-rtl.css 94 B
build/block-library/blocks/list/style.css 94 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 649 B
build/block-library/blocks/navigation-link/editor.css 650 B
build/block-library/blocks/navigation-link/style-rtl.css 94 B
build/block-library/blocks/navigation-link/style.css 94 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/view.min.js 343 B
build/block-library/blocks/navigation/view.min.js 2.82 kB
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 377 B
build/block-library/blocks/page-list/editor.css 377 B
build/block-library/blocks/page-list/style-rtl.css 172 B
build/block-library/blocks/page-list/style.css 172 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 273 B
build/block-library/blocks/paragraph/style.css 273 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/style-rtl.css 446 B
build/block-library/blocks/post-comments-form/style.css 446 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 721 B
build/block-library/blocks/post-featured-image/editor.css 721 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 391 B
build/block-library/blocks/post-template/style.css 392 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 389 B
build/block-library/blocks/pullquote/style.css 388 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query/editor-rtl.css 131 B
build/block-library/blocks/query/editor.css 132 B
build/block-library/blocks/quote/style-rtl.css 187 B
build/block-library/blocks/quote/style.css 187 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 397 B
build/block-library/blocks/search/style.css 398 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 99 B
build/block-library/blocks/separator/editor.css 99 B
build/block-library/blocks/separator/style-rtl.css 245 B
build/block-library/blocks/separator/style.css 245 B
build/block-library/blocks/separator/theme-rtl.css 172 B
build/block-library/blocks/separator/theme.css 172 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 744 B
build/block-library/blocks/site-logo/editor.css 744 B
build/block-library/blocks/site-logo/style-rtl.css 181 B
build/block-library/blocks/site-logo/style.css 181 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 B
build/block-library/blocks/social-links/editor-rtl.css 670 B
build/block-library/blocks/social-links/editor.css 669 B
build/block-library/blocks/social-links/style-rtl.css 1.32 kB
build/block-library/blocks/social-links/style.css 1.32 kB
build/block-library/blocks/spacer/editor-rtl.css 307 B
build/block-library/blocks/spacer/editor.css 307 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 471 B
build/block-library/blocks/table/editor.css 472 B
build/block-library/blocks/table/style-rtl.css 481 B
build/block-library/blocks/table/style.css 481 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 146 B
build/block-library/blocks/tag-cloud/style.css 146 B
build/block-library/blocks/template-part/editor-rtl.css 560 B
build/block-library/blocks/template-part/editor.css 559 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 910 B
build/block-library/common.css 908 B
build/block-library/reset-rtl.css 474 B
build/block-library/reset.css 474 B
build/block-library/theme-rtl.css 675 B
build/block-library/theme.css 679 B
build/block-serialization-default-parser/index.min.js 1.09 kB
build/block-serialization-spec-parser/index.min.js 2.79 kB
build/blocks/index.min.js 46.3 kB
build/compose/index.min.js 11.2 kB
build/core-data/index.min.js 13.2 kB
build/customize-widgets/index.min.js 11.4 kB
build/customize-widgets/style-rtl.css 1.5 kB
build/customize-widgets/style.css 1.49 kB
build/data-controls/index.min.js 631 B
build/data/index.min.js 7.49 kB
build/date/index.min.js 31.9 kB
build/deprecated/index.min.js 485 B
build/dom-ready/index.min.js 304 B
build/dom/index.min.js 4.5 kB
build/edit-navigation/index.min.js 16 kB
build/edit-navigation/style-rtl.css 3.76 kB
build/edit-navigation/style.css 3.76 kB
build/edit-post/classic-rtl.css 492 B
build/edit-post/classic.css 494 B
build/edit-widgets/index.min.js 16.5 kB
build/edit-widgets/style.css 4.18 kB
build/editor/style-rtl.css 3.75 kB
build/editor/style.css 3.74 kB
build/element/index.min.js 3.29 kB
build/escape-html/index.min.js 517 B
build/format-library/index.min.js 6.58 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.63 kB
build/html-entities/index.min.js 424 B
build/i18n/index.min.js 3.71 kB
build/is-shallow-equal/index.min.js 501 B
build/keyboard-shortcuts/index.min.js 1.8 kB
build/keycodes/index.min.js 1.39 kB
build/list-reusable-blocks/index.min.js 1.72 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.92 kB
build/notices/index.min.js 925 B
build/nux/index.min.js 2.08 kB
build/nux/style-rtl.css 747 B
build/nux/style.css 743 B
build/plugins/index.min.js 1.84 kB
build/primitives/index.min.js 924 B
build/priority-queue/index.min.js 582 B
build/react-i18n/index.min.js 671 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.65 kB
build/reusable-blocks/index.min.js 2.22 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11 kB
build/server-side-render/index.min.js 1.57 kB
build/shortcode/index.min.js 1.49 kB
build/token-list/index.min.js 639 B
build/url/index.min.js 1.9 kB
build/viewport/index.min.js 1.05 kB
build/warning/index.min.js 248 B
build/widgets/index.min.js 7.15 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.04 kB

compressed-size-action

@aaronrobertshaw
Copy link
Contributor

@youknowriad and @ciampo, if you have time, could you please give a general 👍 or 👎 on the approach taken here before we continue to refine the results as per the PR description todo list?

import { Button, VisuallyHidden } from '../';
import { Button, Popover, VisuallyHidden } from '../';

const OptionList = ( { isOpen, children } ) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of a custom implementation like that, maybe DropdownMenu is a component suited for this use-case (with all the focus management...)

@andrewserong
Copy link
Contributor Author

Thanks for the suggestion @youknowriad — I think DropdownMenu might be a good approach. It looks like it covers most of what we need, except for managing moving focus to the selected item when you open the dropdown menu. So, I think we might need to add some custom focus handling, there. However, aside from that, I think it seems to work pretty well, and would mean that we might be able to remove the downshift dependency, since it looks like this is the only component that uses it?

Here's a screengrab of where I got up to by the end of the day:

Kapture 2021-12-13 at 17 31 42

I'll keep chipping away at this, next up, I'll look into how we can set focus to the currently selected item when we open the dropdown menu.

style: item.style,
} }
onClick={ () => {
onSelectedItemChange( {
Copy link
Contributor

Choose a reason for hiding this comment

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

The readme for this component states the onChange prop is optional so onSelectedItemChange here might not be a function. We hit this error when testing the recent changes in the Storybook as well.

@aaronrobertshaw aaronrobertshaw self-requested a review December 13, 2021 07:37
Copy link
Contributor

@aaronrobertshaw aaronrobertshaw left a comment

Choose a reason for hiding this comment

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

Thanks for continuing to refine this @andrewserong 👌

I took it for another premature test drive. It's working well so far for me in the block editor.

In addition to the focus management items you've already mentioned, there were a few small issues that I believe will be ironed out as this evolves. In case it helps they were:

  • The storybook example is currently broken - see earlier comment
  • Styling, size, and position of the dropdown menu is quite different to before this update
Before / After screenshots
Before After
Screen Shot 2021-12-13 at 5 38 07 pm Screen Shot 2021-12-13 at 5 36 28 pm
Screen Shot 2021-12-13 at 5 37 59 pm Screen Shot 2021-12-13 at 5 37 38 pm

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Hey @andrewserong , thank you for exploring a solution to avoid the CustomSelectControl getting cut off.

The main piece of feedback that I have is that, with the new approach in this PR, the semantics of the component are changing from being a listbox + option (like a native select) to being a menu + group + menuitem.

Given the fact that this component is a custom select component, I would argue that we should keep the same semantics — I'm not sure that DropdownMenu is the better choice in this regard, but I would like to hear also @diegohaz and @mirka 's opinion

@andrewserong
Copy link
Contributor Author

Thanks for the feedback @ciampo!

The main piece of feedback that I have is that, with the new approach in this PR, the semantics of the component are changing from being a listbox + option (like a native select) to being a menu + group + menuitem.

That's a great point. I've had a go at updating this in commits (3e7af65 and 48b92c1) to see if we can get closer to the listbox and option semantics while using this component. I've done the following:

  • Removed the MenuGroup nesting
  • Moved where role is set within the DropdownMenu component so that a passed in role will be rendered, and then I've passed it a role of listbox
  • Passed in a role of option to the MenuItem component

In terms of the rendered markup, it looks like this gets us the listbox + option semantics (at least superficially):

Listbox

image

Option

image

I haven't (yet) addressed @aaronrobertshaw's styling feedback or the other issues, as I wanted to confirm whether or not DropdownMenu is viable before polishing this PR too much. I think in terms of behaviour the DropdownMenu component gets us most of what we need aside from setting focus on the currently selected item (while allowing us to potentially drop the downshift dependency). But, I'm curious to hear feedback on whether or not this usage of that component seems viable, or if it feels a little too hacky. In this case, I suppose ideally we could re-use behaviour of components and treat that behaviour as an implementation detail, while ensuring it has the appropriate semantics. But I'm very happy to defer to others, particularly given that I'm pretty fresh to looking at this component 😁

If DropdownMenu doesn't appear to be viable, do you think a custom Popover like in my original commit is a better option? Happy to explore any ideas 🙂

@youknowriad
Copy link
Contributor

Note that we also have the more generic Dropdown if we want to get rid of the menu semantics and apply our own.

@ciampo
Copy link
Contributor

ciampo commented Dec 14, 2021

@diegohaz any thoughts on this?

@andrewserong
Copy link
Contributor Author

Thanks for the feedback @youknowriad @ciampo — I've explored a couple of options today and ran into a couple of issues with both the DropdownMenu and Dropdown components, and I think our best bet is to revert back to using downshift + a custom Popover. This is because:

  • Switching to Dropdown or DropdownMenu will involve a custom workaround for roles, which we could partially mitigate by passing a custom role attribute, but more importantly:
  • It turns out downshift handles a lot of detailed keyboard interaction that wasn't immediately obvious to me, for example like a native select element, being able to type a letter and automatically move the selected option to the first option starting with that letter as described in the listbox ARIA role.
  • Downshift also handles automatically focusing on the selected item within the popover, which we would have had to re-implement if using the Dropdown or DropdownMenu components.

The good news, is that it looks like we weren't too far off with the original addition of the Popover and downshift's keyboard handling appears to work nicely through that popover. To address the styling issues, I've added a useResizeObserver which grabs the width of the button and ensures that the options list matches the width of the button. This gets us much closer to preserving the original styling of the component. In the following screengrab, I'm using the keyboard to navigate through options, so it feels like things are working pretty well so far with the Popover:

Kapture 2021-12-15 at 17 10 46

Please let me know if there are any objections with this approach — the PR's still a work in progress (I only just managed to get the styling looking pretty good before wrapping up for the day). For example, the Storybook demo styling is still pretty broken due to the centered position, but I'll fix up the positioning tomorrow:

image

I'm fairly sure that the addition of the Popover and preserving the rest of the existing component's behaviour is our best bet forward, but as always, very happy to hear other ideas 🙂

@ciampo
Copy link
Contributor

ciampo commented Dec 15, 2021

I'm fairly sure that the addition of the Popover and preserving the rest of the existing component's behaviour is our best bet forward, but as always, very happy to hear other ideas 🙂

This also sounds like the most sensible (and least disruptive) approach. Feel free to ping again once you feel like the code is a good place for a review :)

@youknowriad
Copy link
Contributor

I'm fine with the plan but I do think it's a missed opportunity to consolidate our components and composability. The same behavior of CustomSelectControl might also be needed for ComboboxControl and potentially FormTokenField. why these components have different ways to handle popovers and keyboard navigation? They can all benefit from Dropdown and potentially hooks to handle the navigation.

@diegohaz
Copy link
Member

Semantically speaking, the select element is more similar to a combobox than to a dropdown menu. The dropdown menu trigger is a button. A button can only have a label. A select needs both a label and a value, and this is only possible with a few elements, like inputs, or elements with the combobox role. But, yeah, as @youknowriad mentioned, there's a lot of behavior that could be shared between these components.

A custom select is also one of the most complicated widgets to implement (correctly). This is the best article I know about this topic: https://sarahmhigley.com/writing/select-your-poison/

For now, I agree with the plan. if we aren't going to use a library for that, like downshift or Ariakit (soon), I'd also consider using the native select whenever possible.

@andrewserong
Copy link
Contributor Author

Thanks for confirming the approach, folks!

I'm fine with the plan but I do think it's a missed opportunity to consolidate our components and composability.

Agreed, initially I hoped we could consolidate at this stage. However, as Diego points out, there's a lot more complexity to implementing an accessible custom select component than I initially anticipated. After digging around in the keyboard navigation yesterday, I realised that downshift already handles all of the necessary listbox behaviour without us needing to do a larger refactor/rewrite, and it's good to contain scope creep on components when we can, it's very easy for the task to balloon!

I'm still keen for us to explore better composability / consolidation, but perhaps in the new year once the 5.9 work settles down (there's a good chunk of work for us to do on consolidating label spacing in components that we'd like to get to, too).

I'll ping again when this PR is ready for a review. Thanks everyone!

Copy link
Contributor Author

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

Alrighty, I believe this is now ready for a proper review (CC: @ciampo). I've updated it to calculate the width for the menu via the width of the container, which I believe gets us pretty close to maintaining the same positioning / width as on trunk. If the animation of the popover feels like too much, we can switch it off with an animate={ false }. Here's a GIF of the current state within the editor (testing with the typography on the paragraph block is the most prominent place to test this):

Kapture 2021-12-17 at 15 51 49

A quick link to the Storybook demo after running npm run storybook:dev is: http://localhost:50240/?path=/story/components-customselectcontrol--long-labels

Let me know if you'd like to see any changes!

@andrewserong andrewserong removed the [Status] In Progress Tracking issues with work in progress label Dec 17, 2021
@andrewserong andrewserong changed the title [WIP] CustomSelectControl: Update to use a Popover component for rendering the internals CustomSelectControl: Update to use a Popover component for rendering the internals Dec 17, 2021
@andrewserong andrewserong force-pushed the try/popover-in-custom-select-control branch from e734e6b to a556efe Compare December 17, 2021 05:24
@andrewserong
Copy link
Contributor Author

Rebased to fix conflicts with changelog.

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Hey @andrewserong , thank you for iterating!

I think we're definitely moving in the right direction! Unfortunately, I won't be able to review again before the first week of January — hope that's ok!

{ width: containerWidth },
] = useResizeObserver();
const widthMinusBorder =
containerWidth >= 2 ? containerWidth - 2 : containerWidth;
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if there's a way to get the border-width of the Popover instead of hardcoding it. Maybe we could get a ref of the Popover and get the computedStyles ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or maybe we could use a different value of box-sizing so that we don't need to take the border width into account at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one was slightly tricky because the border is set on .components-popover__content which is a child of the Popover container, so a ref doesn't quite get us there. Also, because .components-popover__content is a parent of the element we're attaching menuProps to, box-sizing doesn't help us either — the border is set on the parent so the width of the child cannot take it into account. Also, I wanted to avoid overriding Popover classes directly where we can.

I think the neatest thing to do here is to actually skip the JS calculation that added in 2px and instead set a negative horizontal margin that references $border-width so we're still matching a variable instead of using a magic number (I've seen this in a couple of other places in the repo, too). I've added that in this commit: 27c0cc2

Happy to continue to iterate there if you can think of a better way to handle it, but 🤞 this makes things a bit neater.

packages/components/src/custom-select-control/index.js Outdated Show resolved Hide resolved
Comment on lines +184 to +181
<ul { ...menuProps }>
{ isOpen &&
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if the ul should instead be wrapped around OptionList. That would also avoid the isOpen && check

i.e

<ul { ...menuProps }>
  <OptionList isOpen={ isOpen } anchorRef={ anchorRef }>
    { items.map( ... ) }
  </OptionList>
</ul>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The isOpen check is slightly odd, but unfortunately I don't think we can move OptionList to be a child of the ul, as then the Popover divs would be direct descendants of the ul element (where only li / script tags are allowed).

packages/components/src/custom-select-control/style.scss Outdated Show resolved Hide resolved
@andrewserong
Copy link
Contributor Author

Thanks for the review @ciampo!

Unfortunately, I won't be able to review again before the first week of January — hope that's ok!

No worries, I'll be AFK for a couple of weeks from the end of this week, so if this PR hasn't been merged by then, feel free to merge it if it looks okay, otherwise I can continue on with it when I get back.

@andrewserong andrewserong force-pushed the try/popover-in-custom-select-control branch from 27c0cc2 to d46ae30 Compare December 20, 2021 04:48
@aaronrobertshaw aaronrobertshaw self-requested a review December 22, 2021 05:09
Copy link
Contributor

@aaronrobertshaw aaronrobertshaw left a comment

Choose a reason for hiding this comment

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

Thanks for iterating on this one @andrewserong 🙇

I've tested again after the latest changes and I think it is getting close now.

✅ Storybook: Popover displays and functions as expected
✅ Site editor: Popover displays and is not cut-off in Global Styles panel
✅ Block editor: Popover renders
✅ Keyboard navigation: Works well in both Site and Block editors

❓ Styling: There are some issues with scrollbars showing when they shouldn't

Everywhere I tested the updated CustomSelectControl displayed extraneous scrollbars. I believe this is in part due to a small issue with the negation of the $border-width Sass variable. Quickly hacking around in dev tools, setting the popover menu's margin to 0 sorted out the scrollbars. I'm lacking some of the history behind the attempt to account for border-width so I'm probably missing something.

I've left an inline comment and suggestion regarding the Sass variable negation.

Screenshots Screen Shot 2021-12-22 at 3 17 10 pm Screen Shot 2021-12-22 at 3 06 42 pm Screen Shot 2021-12-22 at 3 08 53 pm Screen Shot 2021-12-22 at 3 08 33 pm Screen Shot 2021-12-22 at 3 08 27 pm

overflow: auto;
padding: 0;
position: absolute;
margin: 0 -$border-width; // Allow width value to include the Popover border width.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
margin: 0 -$border-width; // Allow width value to include the Popover border width.
margin: 0 (-$border-width); // Allow width value to include the Popover border width.

I believe the Sass compiler treats -$border-width like a subtraction operation.

So in the original code margin: 0 -$border-width; results in margin: -1px; not margin: 0 -1px. Given the comment only mentions the width I assume this is unintended.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great catch, thanks Aaron! Yes, the negative margin is only intended for the horizontal margin. It's to reduce the width of the menu by 2px to account for the border of the parent, so that the popover lines up perfectly with the toggle button. I'll see if I can squeeze in a bit of time today to tweak it.

@andrewserong
Copy link
Contributor Author

Thanks again for reviewing @aaronrobertshaw! I thought I'd come up with a simple fix for the scrollbar issue by switching off overflow for the Popover (pushed in f261d3b) that kind of works. An additional problem for us with the scrollbars is with how the Menu area behaves when the vertical room is limited and how the Popover behaves. At the moment, both expect to scroll the content, and I ran out of time to tease apart which should "really" scroll the content, as choosing either one introduced subtle bugs. The way I've left it at the moment, is that they're switched off on the Popover, which superficially looks better:

Screenshots
Before After
image image

However, if you shorten your browser window, and then use the keyboard to scroll up and down between items, the selected item will scroll off the edge of the screen. Not quite what we want!

I think what we want is for the actual height of the popover to be limited to the vertical edge of the screen, without scrolling at all, and for the Menu to be the true source of the scrollbar, but I didn't quite figure it out before wrapping up for the year.

If anyone would like to have a go at fixing it, feel free to! Otherwise, I'll take another look at it with fresh 👀 when I'm back in a couple of weeks 🙂

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Not sure if this solves the problem at the root, but while playing around with Storybook, I noticed that the scrollbar (at least in the Storybook example) is caused by the "huge" item — in particular because its font size is quite large (200%), while the line-height is fixed (28px). This causes the item to text to overflow its item's height, and the scrollbar to appear:

Screenshot 2022-01-08 at 11 34 26

Disabling the line-height rule allows the li item (and therefore the ul parent) to resize to the correct height, and thus avoids the scrollbar

Screenshot 2022-01-08 at 11 36 49

I wonder if we should rewrite some of the CSS so that:

  • the line-height is expressed in unitless values, becoming proportional to the font size
  • the li item has a min-height, and it otherwise grows as needed by its contents

@andrewserong
Copy link
Contributor Author

Thanks for all the earlier feedback on this now very old PR. Since this is stale, and it looks like it'll be superseded by #41726 which refactors the component, I'll close this one out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Design Tools Tools that impact the appearance of blocks both to expand the number of tools and improve the experi [Package] Components /packages/components [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants