From f926f19a7b584cdc61552e94c3184c5729fd0a72 Mon Sep 17 00:00:00 2001 From: James Highfield Date: Wed, 30 Aug 2023 14:13:50 +0000 Subject: [PATCH] Updated TextField of type number to focus when a Spinner button is clicked --- .changeset/warm-points-punch.md | 5 +++ .../src/components/TextField/TextField.scss | 3 +- .../TextField/TextField.stories.tsx | 37 +++++++++++++++++++ .../src/components/TextField/TextField.tsx | 13 ++++--- .../TextField/components/Spinner/Spinner.tsx | 2 +- .../TextField/tests/TextField.test.tsx | 29 +++++++++++++-- 6 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 .changeset/warm-points-punch.md diff --git a/.changeset/warm-points-punch.md b/.changeset/warm-points-punch.md new file mode 100644 index 00000000000..c53b52d0061 --- /dev/null +++ b/.changeset/warm-points-punch.md @@ -0,0 +1,5 @@ +--- +'@shopify/polaris': patch +--- + +Updated `TextField` of `type` `number` to focus when a `Spinner` button is clicked diff --git a/polaris-react/src/components/TextField/TextField.scss b/polaris-react/src/components/TextField/TextField.scss index 69818ed8ce9..7bd770b94a8 100644 --- a/polaris-react/src/components/TextField/TextField.scss +++ b/polaris-react/src/components/TextField/TextField.scss @@ -70,10 +70,11 @@ $spinner-icon-size: 12px; .focus > .Input, .focus > .VerticalContent, +.TextField:focus-within > .Input, .Input:focus-visible { outline: none; - // stylelint-disable-next-line selector-max-class, selector-max-combinators -- generated by polaris-migrator DO NOT COPY + // stylelint-disable-next-line selector-max-class, selector-max-combinators, selector-max-specificity -- outline based on child focus requires complex specificity ~ .Backdrop { // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY @include focus-ring($style: 'focused'); diff --git a/polaris-react/src/components/TextField/TextField.stories.tsx b/polaris-react/src/components/TextField/TextField.stories.tsx index e32a5e2b30d..ce3972e5845 100644 --- a/polaris-react/src/components/TextField/TextField.stories.tsx +++ b/polaris-react/src/components/TextField/TextField.stories.tsx @@ -4,14 +4,17 @@ import { Button, LegacyCard, ChoiceList, + Form, FormLayout, InlineError, Select, LegacyStack, Tag, + Text, TextField, Icon, Tooltip, + VerticalStack, } from '@shopify/polaris'; import { DeleteMinor, @@ -829,3 +832,37 @@ export function All() { ); } + +export function WithFormSubmit() { + const [adjustment, setAdjustment] = useState('0'); + const [onHandTotal, setOnHandTotal] = useState(0); + + return ( + +
{ + event.preventDefault(); + setAdjustment('0'); + setOnHandTotal(onHandTotal + parseInt(adjustment, 10)); + }} + > + + + On hand quantity ({onHandTotal.toString()}) + + setAdjustment(value)} + autoComplete="off" + type="number" + selectTextOnFocus + /> + + +
+
+ ); +} diff --git a/polaris-react/src/components/TextField/TextField.tsx b/polaris-react/src/components/TextField/TextField.tsx index cfa4da3b22e..3de0a291139 100644 --- a/polaris-react/src/components/TextField/TextField.tsx +++ b/polaris-react/src/components/TextField/TextField.tsx @@ -401,11 +401,11 @@ export function TextField({ ], ); - const handleButtonRelease = useCallback(() => { + const handleSpinnerButtonRelease = useCallback(() => { clearTimeout(buttonPressTimer.current); }, []); - const handleButtonPress: SpinnerProps['onMouseDown'] = useCallback( + const handleSpinnerButtonPress: SpinnerProps['onMouseDown'] = useCallback( (onChange) => { const minInterval = 50; const decrementBy = 10; @@ -422,11 +422,11 @@ export function TextField({ buttonPressTimer.current = window.setTimeout(onChangeInterval, interval); - document.addEventListener('mouseup', handleButtonRelease, { + document.addEventListener('mouseup', handleSpinnerButtonRelease, { once: true, }); }, - [handleButtonRelease], + [handleSpinnerButtonRelease], ); const spinnerMarkup = @@ -434,8 +434,8 @@ export function TextField({ @@ -654,6 +654,7 @@ export function TextField({ } setFocus(true); + inputRef.current?.focus(); } function handleClearButtonPress() { diff --git a/polaris-react/src/components/TextField/components/Spinner/Spinner.tsx b/polaris-react/src/components/TextField/components/Spinner/Spinner.tsx index 9a4d8b36034..b3a108ced04 100644 --- a/polaris-react/src/components/TextField/components/Spinner/Spinner.tsx +++ b/polaris-react/src/components/TextField/components/Spinner/Spinner.tsx @@ -31,7 +31,7 @@ export const Spinner = React.forwardRef( function handleMouseDown(onChange: HandleStepFn) { return (event: React.MouseEvent) => { if (event.button !== 0) return; - onMouseDown(onChange); + onMouseDown?.(onChange); }; } diff --git a/polaris-react/src/components/TextField/tests/TextField.test.tsx b/polaris-react/src/components/TextField/tests/TextField.test.tsx index 6251ea884a8..668a0b39f0f 100644 --- a/polaris-react/src/components/TextField/tests/TextField.test.tsx +++ b/polaris-react/src/components/TextField/tests/TextField.test.tsx @@ -120,6 +120,24 @@ describe('', () => { expect(onClick).toHaveBeenCalled(); }); + it('focuses the text field when the spinner is clicked', () => { + const event = new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true, + }); + const textField = mountWithApp( + , + ); + + textField + .find(Spinner)! + .findAll('div', {role: 'button'})[0]! + .domNode?.dispatchEvent(event); + + expect(document.activeElement).toBe(textField.find('input')!.domNode); + }); + it('does not bubble up to the parent element when it occurs in an element other than the input', () => { const onClick = jest.fn(); const children = 'vertical-content-children'; @@ -221,7 +239,7 @@ describe('', () => { ); expect(textField).toContainReactComponent('input', { - id: ':ra:', + id: expect.any(String), }); }); @@ -330,11 +348,12 @@ describe('', () => { helpText="Some help" onChange={noop} autoComplete="off" + id="textField" />, ); expect(textField).toContainReactComponent('input', { - 'aria-describedby': ':ri:HelpText', + 'aria-describedby': 'textFieldHelpText', }); expect(textField.find('div')).toContainReactText('Some help'); }); @@ -369,11 +388,12 @@ describe('', () => { error="Some error" onChange={noop} autoComplete="off" + id="textField" />, ); expect(textField).toContainReactComponent('input', { - 'aria-describedby': ':rk:Error', + 'aria-describedby': 'textFieldError', }); }); @@ -407,11 +427,12 @@ describe('', () => { helpText="Some help" onChange={noop} autoComplete="off" + id="textField" />, ); expect(textField).toContainReactComponent('input', { - 'aria-describedby': ':rm:Error :rm:HelpText', + 'aria-describedby': 'textFieldError textFieldHelpText', }); expect(textField.find('div')).toContainReactText('Some error');