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

NumberControl: Add custom spin buttons #45333

Merged
merged 15 commits into from
Nov 2, 2022

Conversation

noisysocks
Copy link
Member

@noisysocks noisysocks commented Oct 27, 2022

What?

Adds custom spin buttons (+ / - buttons) to NumberControl.

Why?

So that controls such as Line height in Global Styles match the design, see #34345 (comment).

How?

  • The buttons are rendered within the existing suffix slot that InputControl has.
    • I considered adding a new prop to InputControl or using the context system, but using suffix works really well and requires minimal changes.
  • Pressing the + and - buttons increases/decreases value by step. Values are clamped to be within min and max.
  • The buttons are a progressive enhancement hidden from screen readers.
  • We continue to use the browser's step buttons for the 'default' and 'small' sizes as these sizes don't have enough height to fit custom buttons.

Testing Instructions

Check out the story book for NumberControl or go to Appearance → Editor → Global Styles → Typography → Text.

Screenshots or screencast

Kapture.2022-10-28.at.16.35.52.mp4

@noisysocks noisysocks added [Type] Enhancement A suggestion for improvement. [Package] Components /packages/components labels Oct 27, 2022
@noisysocks noisysocks self-assigned this Oct 27, 2022
@noisysocks noisysocks added the Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json label Oct 27, 2022
@github-actions
Copy link

github-actions bot commented Oct 27, 2022

Size Change: +348 B (0%)

Total Size: 1.28 MB

Filename Size Change
build/block-editor/index.min.js 169 kB +6 B (0%)
build/block-editor/style-rtl.css 15.8 kB -19 B (0%)
build/block-editor/style.css 15.8 kB -15 B (0%)
build/block-library/blocks/navigation/style-rtl.css 2.19 kB +22 B (+1%)
build/block-library/blocks/navigation/style.css 2.18 kB +21 B (+1%)
build/block-library/index.min.js 192 kB -6 B (0%)
build/block-library/style-rtl.css 12.3 kB +19 B (0%)
build/block-library/style.css 12.3 kB +22 B (0%)
build/blocks/index.min.js 49.9 kB +7 B (0%)
build/components/index.min.js 203 kB +483 B (0%)
build/components/style-rtl.css 11.3 kB -20 B (0%)
build/components/style.css 11.3 kB -20 B (0%)
build/edit-post/index.min.js 34.1 kB -29 B (0%)
build/edit-site/index.min.js 57.9 kB -94 B (0%)
build/editor/index.min.js 43.6 kB -23 B (0%)
build/format-library/index.min.js 6.95 kB -6 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 982 B
build/annotations/index.min.js 2.76 kB
build/api-fetch/index.min.js 2.26 kB
build/autop/index.min.js 2.14 kB
build/blob/index.min.js 475 B
build/block-directory/index.min.js 7.09 kB
build/block-directory/style-rtl.css 990 B
build/block-directory/style.css 991 B
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 90 B
build/block-library/blocks/archives/style.css 90 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 122 B
build/block-library/blocks/audio/style.css 122 B
build/block-library/blocks/audio/theme-rtl.css 126 B
build/block-library/blocks/audio/theme.css 126 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 84 B
build/block-library/blocks/avatar/style.css 84 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 482 B
build/block-library/blocks/button/editor.css 482 B
build/block-library/blocks/button/style-rtl.css 532 B
build/block-library/blocks/button/style.css 532 B
build/block-library/blocks/buttons/editor-rtl.css 337 B
build/block-library/blocks/buttons/editor.css 337 B
build/block-library/blocks/buttons/style-rtl.css 332 B
build/block-library/blocks/buttons/style.css 332 B
build/block-library/blocks/calendar/style-rtl.css 239 B
build/block-library/blocks/calendar/style.css 239 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 100 B
build/block-library/blocks/categories/style.css 100 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 121 B
build/block-library/blocks/code/style.css 121 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-content/style-rtl.css 92 B
build/block-library/blocks/comment-content/style.css 92 B
build/block-library/blocks/comment-template/style-rtl.css 199 B
build/block-library/blocks/comment-template/style.css 198 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/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 840 B
build/block-library/blocks/comments/editor.css 839 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 636 B
build/block-library/blocks/cover/editor-rtl.css 612 B
build/block-library/blocks/cover/editor.css 613 B
build/block-library/blocks/cover/style-rtl.css 1.57 kB
build/block-library/blocks/cover/style.css 1.55 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 410 B
build/block-library/blocks/embed/style.css 410 B
build/block-library/blocks/embed/theme-rtl.css 126 B
build/block-library/blocks/embed/theme.css 126 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 253 B
build/block-library/blocks/file/style.css 254 B
build/block-library/blocks/file/view.min.js 346 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 948 B
build/block-library/blocks/gallery/editor.css 950 B
build/block-library/blocks/gallery/style-rtl.css 1.53 kB
build/block-library/blocks/gallery/style.css 1.53 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 394 B
build/block-library/blocks/group/editor.css 394 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 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 327 B
build/block-library/blocks/html/editor.css 329 B
build/block-library/blocks/image/editor-rtl.css 880 B
build/block-library/blocks/image/editor.css 880 B
build/block-library/blocks/image/style-rtl.css 627 B
build/block-library/blocks/image/style.css 630 B
build/block-library/blocks/image/theme-rtl.css 126 B
build/block-library/blocks/image/theme.css 126 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 213 B
build/block-library/blocks/latest-posts/editor.css 212 B
build/block-library/blocks/latest-posts/style-rtl.css 463 B
build/block-library/blocks/latest-posts/style.css 462 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 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 507 B
build/block-library/blocks/media-text/style.css 505 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 705 B
build/block-library/blocks/navigation-link/editor.css 703 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 296 B
build/block-library/blocks/navigation-submenu/editor.css 295 B
build/block-library/blocks/navigation/editor-rtl.css 2.02 kB
build/block-library/blocks/navigation/editor.css 2.03 kB
build/block-library/blocks/navigation/view-modal.min.js 2.78 kB
build/block-library/blocks/navigation/view.min.js 443 B
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 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 174 B
build/block-library/blocks/paragraph/editor.css 174 B
build/block-library/blocks/paragraph/style-rtl.css 279 B
build/block-library/blocks/paragraph/style.css 281 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/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 501 B
build/block-library/blocks/post-comments-form/style.css 501 B
build/block-library/blocks/post-date/style-rtl.css 61 B
build/block-library/blocks/post-date/style.css 61 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 586 B
build/block-library/blocks/post-featured-image/editor.css 584 B
build/block-library/blocks/post-featured-image/style-rtl.css 315 B
build/block-library/blocks/post-featured-image/style.css 315 B
build/block-library/blocks/post-navigation-link/style-rtl.css 153 B
build/block-library/blocks/post-navigation-link/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 282 B
build/block-library/blocks/post-template/style.css 282 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-title/style-rtl.css 100 B
build/block-library/blocks/post-title/style.css 100 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 135 B
build/block-library/blocks/pullquote/editor.css 135 B
build/block-library/blocks/pullquote/style-rtl.css 326 B
build/block-library/blocks/pullquote/style.css 325 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-pagination/style-rtl.css 282 B
build/block-library/blocks/query-pagination/style.css 278 B
build/block-library/blocks/query-title/style-rtl.css 63 B
build/block-library/blocks/query-title/style.css 63 B
build/block-library/blocks/query/editor-rtl.css 439 B
build/block-library/blocks/query/editor.css 439 B
build/block-library/blocks/quote/style-rtl.css 213 B
build/block-library/blocks/quote/style.css 213 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 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 409 B
build/block-library/blocks/search/style.css 406 B
build/block-library/blocks/search/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/separator/editor-rtl.css 146 B
build/block-library/blocks/separator/editor.css 146 B
build/block-library/blocks/separator/style-rtl.css 234 B
build/block-library/blocks/separator/style.css 234 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/shortcode/editor-rtl.css 464 B
build/block-library/blocks/shortcode/editor.css 464 B
build/block-library/blocks/site-logo/editor-rtl.css 490 B
build/block-library/blocks/site-logo/editor.css 490 B
build/block-library/blocks/site-logo/style-rtl.css 203 B
build/block-library/blocks/site-logo/style.css 203 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 116 B
build/block-library/blocks/site-title/editor.css 116 B
build/block-library/blocks/site-title/style-rtl.css 57 B
build/block-library/blocks/site-title/style.css 57 B
build/block-library/blocks/social-link/editor-rtl.css 184 B
build/block-library/blocks/social-link/editor.css 184 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.4 kB
build/block-library/blocks/social-links/style.css 1.39 kB
build/block-library/blocks/spacer/editor-rtl.css 322 B
build/block-library/blocks/spacer/editor.css 322 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 494 B
build/block-library/blocks/table/editor.css 494 B
build/block-library/blocks/table/style-rtl.css 611 B
build/block-library/blocks/table/style.css 609 B
build/block-library/blocks/table/theme-rtl.css 190 B
build/block-library/blocks/table/theme.css 190 B
build/block-library/blocks/tag-cloud/style-rtl.css 251 B
build/block-library/blocks/tag-cloud/style.css 253 B
build/block-library/blocks/template-part/editor-rtl.css 235 B
build/block-library/blocks/template-part/editor.css 235 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/editor-rtl.css 691 B
build/block-library/blocks/video/editor.css 694 B
build/block-library/blocks/video/style-rtl.css 174 B
build/block-library/blocks/video/style.css 174 B
build/block-library/blocks/video/theme-rtl.css 126 B
build/block-library/blocks/video/theme.css 126 B
build/block-library/classic-rtl.css 162 B
build/block-library/classic.css 162 B
build/block-library/common-rtl.css 1.02 kB
build/block-library/common.css 1.02 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 11.2 kB
build/block-library/editor.css 11.2 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/theme-rtl.css 719 B
build/block-library/theme.css 722 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/compose/index.min.js 12.2 kB
build/core-data/index.min.js 15.5 kB
build/customize-widgets/index.min.js 11.3 kB
build/customize-widgets/style-rtl.css 1.38 kB
build/customize-widgets/style.css 1.38 kB
build/data-controls/index.min.js 653 B
build/data/index.min.js 8.08 kB
build/date/index.min.js 32.1 kB
build/deprecated/index.min.js 507 B
build/dom-ready/index.min.js 324 B
build/dom/index.min.js 4.7 kB
build/edit-navigation/index.min.js 16.1 kB
build/edit-navigation/style-rtl.css 3.99 kB
build/edit-navigation/style.css 4 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/style-rtl.css 7.33 kB
build/edit-post/style.css 7.32 kB
build/edit-site/style-rtl.css 8.37 kB
build/edit-site/style.css 8.35 kB
build/edit-widgets/index.min.js 16.7 kB
build/edit-widgets/style-rtl.css 4.34 kB
build/edit-widgets/style.css 4.34 kB
build/editor/style-rtl.css 3.6 kB
build/editor/style.css 3.59 kB
build/element/index.min.js 4.68 kB
build/escape-html/index.min.js 537 B
build/experiments/index.min.js 868 B
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.64 kB
build/html-entities/index.min.js 448 B
build/i18n/index.min.js 3.77 kB
build/is-shallow-equal/index.min.js 527 B
build/keyboard-shortcuts/index.min.js 1.78 kB
build/keycodes/index.min.js 1.83 kB
build/list-reusable-blocks/index.min.js 2.13 kB
build/list-reusable-blocks/style-rtl.css 835 B
build/list-reusable-blocks/style.css 835 B
build/media-utils/index.min.js 2.93 kB
build/notices/index.min.js 963 B
build/nux/index.min.js 2.06 kB
build/nux/style-rtl.css 732 B
build/nux/style.css 728 B
build/plugins/index.min.js 1.94 kB
build/preferences-persistence/index.min.js 2.22 kB
build/preferences/index.min.js 1.33 kB
build/primitives/index.min.js 944 B
build/priority-queue/index.min.js 1.58 kB
build/react-i18n/index.min.js 696 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.74 kB
build/reusable-blocks/index.min.js 2.21 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 10.6 kB
build/server-side-render/index.min.js 1.77 kB
build/shortcode/index.min.js 1.53 kB
build/style-engine/index.min.js 1.46 kB
build/token-list/index.min.js 644 B
build/url/index.min.js 3.61 kB
build/vendors/inert-polyfill.min.js 2.48 kB
build/vendors/react-dom.min.js 38.5 kB
build/vendors/react.min.js 4.34 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 268 B
build/widgets/index.min.js 7.21 kB
build/widgets/style-rtl.css 1.18 kB
build/widgets/style.css 1.19 kB
build/wordcount/index.min.js 1.06 kB

compressed-size-action

@ramonjd
Copy link
Member

ramonjd commented Oct 28, 2022

This is looking and working well for me. 🍠

✅ Mouse interactions with plus/minus buttons work as expected
✅ Keyboard navigation works: I can tab to the control and use arrow keys to change the value.
✅ Text to speech treats it like a regular stepper and announces changes
✅ Checked in Chrome, Firefox and Safari

@noisysocks noisysocks marked this pull request as ready for review October 28, 2022 05:27
@noisysocks
Copy link
Member Author

Thanks @ramonjd! I added unit tests and updated the changelog. Probably best to page @ciampo and @jasmussen for extra eyes.

@ciampo
Copy link
Contributor

ciampo commented Oct 28, 2022

I know that @mirka was involved in some conversations around this, so probably better to wait for her feedback here

@mirka mirka added this to In progress (owned) ⏳ in WordPress Components via automation Oct 28, 2022
Comment on lines 189 to 200
const buildSpinHandler =
( direction: 'up' | 'down' ) =>
( event: ChangeEvent< HTMLInputElement > ) => {
let nextValue = isValueEmpty( valueProp ) ? baseValue : valueProp;
if ( direction === 'up' ) {
nextValue = add( nextValue, baseStep );
} else if ( direction === 'down' ) {
nextValue = subtract( nextValue, baseStep );
}
nextValue = constrainValue( nextValue );
onChange( String( nextValue ), { event } );
};
Copy link
Member

Choose a reason for hiding this comment

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

Not a blocker for this PR to land, but have you considered hooking these buttons up to the reducer system that is already in place? Having the buttons dispatch the pressUp/pressDown actions would dedupe the logic and would also add the Shift key enhancement.

I'm guessing that would involve moving the spin buttons down into the InputControl component, and some re-plumbing. Unclear whether it's worth the effort though. cc @stokesman and @ciampo who I'm sure have stronger ✨ feelings ✨ about the reducer construct.

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 did try a few different approaches here but couldn't figure out anything that felt right.

  • We can't dispatch reducer actions from NumberControl as the dispatch function in InputControl isn't available.
  • We can't call the reducer manually in NumberControl as the existing reducer state in InputControl isn't available.
  • I don't think it makes sense for InputControl to have the spin button functionality since it is supposed to be a generic input. (The docs say it's intended to be an eventual TextControl replacement.)

So I ended up settling on this boring repetitive approach. I can DRY it up a bit further by making the reducer and spin buttons use the same logic. I've done that in e63d7ed.

Let me know if you have any other suggestions.

Copy link
Member

Choose a reason for hiding this comment

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

  • I don't think it makes sense for InputControl to have the spin button functionality since it is supposed to be a generic input. (The docs say it's intended to be an eventual TextControl replacement.)

This is the thing — InputControl already contains a lot of the scaffolding that seems generic but basically only makes sense for number inputs (drag gestures, arrow key handlers). One can argue that spin buttons are just as generic as up/down arrow key handlers are. I guess in its current state, it's really a <input type="potentially anything, extend me!"> control rather than a strict <input type="text"> control.

Some other things I'm thinking in relation to this:

  • This line in Storybook will error when using the big spin buttons. Is that a problem?

    setIsValidValue( extra.event.target.validity.valid );

  • The isPressEnterToChange prop currently does not behave as expected when drag gestures and up/down keys are involved. When we fix this logic at some point, will we also have to fix the big spin buttons separately?

So, on the surface it kind of seems like things will generally be simpler if make the big spin buttons "generic" (wink wink) and move them down into InputControl.

But in any case, I think this PR is DRY enough for the time being, and it wouldn't be hard to extract the buttons if we feel like it in the future. (We should look at that event.target.validity.valid thing before merging though)

Copy link
Contributor

Choose a reason for hiding this comment

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

InputControl already contains a lot of the scaffolding that seems generic but basically only makes sense for number inputs (drag gestures, arrow key handlers).

It does yet it seems wrong to me. Instead of putting any more effort into fitting that mold my feeling is InputControl ought to be broken into a hook + component combo. That would allow NumberControl to use the hook and get direct access to dispatch for more flexible extensibility. It would also allow making InputControl more generic. I have an old experimental branch doing that and I'd been thinking to make a fresh start on it after NumberControl was converted to TS. Now, I'm not sure when I'll find the time to do so.

Copy link
Contributor

@ciampo ciampo Oct 31, 2022

Choose a reason for hiding this comment

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

Not necessarily related to the immediate changes that this PR may need to make in order to be merged relatively soon — but my general feeling is also that InputControl could/should be simplified:

  • the reducer logic seems complicated and potentially over-engineered, I'd love to see if we can refactor it to be simpler
  • a lot of number-related functionality is written in InputControl, while it would make sense to me if they were part of NumberControl
  • I remember discussing whether the isPressEnterToChange prop is necessary at all

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 see 😅 thanks for the discussion.

It's easier to move the custom spinners from NumberControl to InputControl later in a BC way than it is to do the opposite, so I'm personally inclined to stick with putting them in NumberControl at least for now.

This line in Storybook will error when using the big spin buttons. Is that a problem?

Good catch. I'll fix this by overriding event.target to be the <input>. We do this already for the drag events.

The isPressEnterToChange prop currently does not behave as expected when drag gestures and up/down keys are involved. When we fix this logic at some point, will we also have to fix the big spin buttons separately?

I would expect that setting isPressEnterToChange should only make it so that onChange is not called while the user is typing a number and should not have any effect on the spin controls, no?

the reducer logic seems complicated and potentially over-engineered, I'd love to see if we can refactor it to be simpler

Yeah not a huge fan of this reducer pattern. I don't really see why InputControl couldn't be a standard managed component with callback props for each of the actions (onPressDown, onPressUp, etc.)

Comment on lines 36 to 38
&&& {
color: ${ COLORS.ui.theme };
}
Copy link
Member

Choose a reason for hiding this comment

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

I wonder how common this styling is going to be. A similar one is the unit dropdown in UnitControl. Do you happen to know any others? Hoping this will remain a one-off 😂

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'm not sure, sorry!

It's very possible I misunderstood the design and the + / - buttons are supposed to be tertiary (link) buttons and not regular buttons that have the theme's accent colour. Do you know @jasmussen?

@@ -200,6 +221,37 @@ function UnforwardedNumberControl(
const baseState = numberControlStateReducer( state, action );
return stateReducerProp?.( baseState, action ) ?? baseState;
} }
size={ size }
suffix={
size === '__unstable-large' && ! hideHTMLArrows ? (
Copy link
Member

Choose a reason for hiding this comment

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

The big thing we need to decide before merging is this part!

While I do think the current logic makes sense, I also feel like the big spin buttons should be disabled by default 🤔 They take up a lot of space, so I'm afraid many use cases would prefer to disable them unless they were particularly useful for the specific case. On the other hand, this API could get super messy with the hideHTMLArrows and the size variants.

The middle ground I can think of is to update the API so hideHTMLArrows is true by default. I think this is a palatable change, given that NumberControl is still experimental and the visual change is non-disruptive. What do you all think?

Copy link
Member Author

Choose a reason for hiding this comment

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

Switching hideHTMLArrows to true by default works for me 👍

Another option is to remove hideHTMLArrows in favour of something like spinControls: 'hidden' | 'native' | 'custom'.

Let me know—happy to defer to you and @ciampo.

Copy link
Member

Choose a reason for hiding this comment

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

A spinControls prop would've been perfect if we actually had custom spin controls for the other size variants 😂

Switching hideHTMLArrows to true by default works for me 👍

Cool. @ciampo ^ We'll go with this if you don't have any objections.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good to me — just to make sure, we will still show these custom, larger spin buttons only for the __unstable-large size variant?

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 changed my mind 😅

I started working on this and in the process appreciated how many instances of NumberControl we have in the block editor (quite a few!) and how, in all instances, the arrow buttons / spin buttons are quite handy. I fear if we make hideHTMLArrows true by default that developers will forget to set hideHTMLArrows={ false } and that the UX will suffer. In other words I really think showing arrow buttons / spin buttons is a sensible default.

So I went ahead and implemented a spinControls prop as above. The default is spinControls = 'native'. I also added some styling so that if spinControls = 'custom' and size = 'small' things look squished but not totally broken.

I think this makes sense. Let me know if you disagree!

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, that makes sense 👍 I really appreciate the effort and care you put into working out the best solution!

Typing the event attribute as PointerEvent<T> | ChangeEvent<T> isn't
great as it means that future ways to input a value into InputControl,
NumberControl and UnitControl will result in BC breaking type changes.
Consumers of the component don't need to know the specifics of the event
beyond that it's a synthetic event and can use `is` to determine more if
necessary.
export type InputChangeCallback< P = {} > = (
nextValue: string | undefined,
extra: { event: SyntheticEvent } & P
) => void;
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 changed InputControl's onChange callback type to be less opinionated.

Typing the event attribute as PointerEvent<T> | ChangeEvent<T> isn't great for maintainability as it means every time a new way to input a value is added to InputControl, NumberControl or UnitControl (which is what this PR does) then there will be a BC-breaking type change.

Consumers of the component don't need to know the specifics of the event beyond that it's a synthetic event (not a native event). Consumers can use is to determine the event type if necessary.

Copy link
Member

@mirka mirka left a comment

Choose a reason for hiding this comment

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

This is good to go on my end. Great work! 🚀


function UnforwardedNumberControl(
{
__unstableStateReducer: stateReducerProp,
className,
dragDirection = 'n',
hideHTMLArrows = false,
Copy link
Member

Choose a reason for hiding this comment

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

We aren't bound to this by contract because NumberControl is marked as experimental, but it would be trivial to add a deprecated() and a line of back compat logic. I don't feel strongly about it either way, but just mentioning in case it wasn't considered.

Copy link
Member Author

@noisysocks noisysocks Nov 1, 2022

Choose a reason for hiding this comment

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

I was wondering this 🙂 I'll add a deprecated(), costs nothing!

@noisysocks noisysocks enabled auto-merge (squash) November 1, 2022 23:47
@noisysocks noisysocks merged commit 2961b34 into trunk Nov 2, 2022
WordPress Components automation moved this from In progress (owned) ⏳ to Done 🎉 Nov 2, 2022
@noisysocks noisysocks deleted the add/spin-buttons-to-number-control branch November 2, 2022 00:16
@github-actions github-actions bot added this to the Gutenberg 14.5 milestone Nov 2, 2022
@noisysocks noisysocks mentioned this pull request Nov 3, 2022
62 tasks
@femkreations femkreations added the Needs User Documentation Needs new user documentation label Jan 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json Needs User Documentation Needs new user documentation [Package] Components /packages/components [Type] Enhancement A suggestion for improvement.
Projects
Development

Successfully merging this pull request may close these issues.

None yet

6 participants