diff --git a/src/components/PinInput/PinInput.tsx b/src/components/PinInput/PinInput.tsx index 3fe0fee872..a73ca16578 100644 --- a/src/components/PinInput/PinInput.tsx +++ b/src/components/PinInput/PinInput.tsx @@ -8,7 +8,7 @@ import type {TextInputProps, TextInputSize} from '../controls'; import {TextInput} from '../controls'; import {OuterAdditionalContent} from '../controls/common/OuterAdditionalContent/OuterAdditionalContent'; import {useDirection} from '../theme'; -import type {DOMProps, QAProps} from '../types'; +import type {AriaLabelingProps, DOMProps, QAProps} from '../types'; import {block} from '../utils/cn'; import './PinInput.scss'; @@ -16,7 +16,11 @@ import './PinInput.scss'; export type PinInputSize = TextInputSize; export type PinInputType = 'numeric' | 'alphanumeric'; -export interface PinInputProps extends DOMProps, QAProps { +export interface PinInputApi { + focus: () => void; +} + +export interface PinInputProps extends DOMProps, AriaLabelingProps, QAProps { value?: string[]; defaultValue?: string[]; onUpdate?: (value: string[]) => void; @@ -34,9 +38,7 @@ export interface PinInputProps extends DOMProps, QAProps { note?: TextInputProps['note']; validationState?: TextInputProps['validationState']; errorMessage?: TextInputProps['errorMessage']; - 'aria-label'?: string; - 'aria-labelledby'?: string; - 'aria-describedby'?: string; + apiRef?: React.RefObject; } const b = block('pin-input'); @@ -70,6 +72,7 @@ export const PinInput = React.forwardRef((props, note, validationState, errorMessage, + apiRef, className, style, qa, @@ -215,6 +218,7 @@ export const PinInput = React.forwardRef((props, const handleFocus = (index: number) => { setFocusedIndex(index); + setActiveIndex(index); }; const handleBlur = () => { @@ -229,6 +233,16 @@ export const PinInput = React.forwardRef((props, // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + React.useImperativeHandle( + apiRef, + () => ({ + focus: () => { + refs.current[activeIndex]?.focus(); + }, + }), + [activeIndex], + ); + return (
@@ -253,6 +267,7 @@ export const PinInput = React.forwardRef((props, 'aria-label': props['aria-label'], 'aria-labelledby': props['aria-labelledby'], 'aria-describedby': ariaDescribedBy, + 'aria-details': props['aria-details'], 'aria-invalid': validationState === 'invalid' ? true : undefined, }} controlRef={handleRef.bind(null, i)} diff --git a/src/components/PinInput/README.md b/src/components/PinInput/README.md index 01e68242f6..5f4876929b 100644 --- a/src/components/PinInput/README.md +++ b/src/components/PinInput/README.md @@ -157,10 +157,15 @@ LANDING_BLOCK--> If you want the browser to suggest "one time codes" from the outer context (e.g. SMS) set the `otp` prop. +## API + +- `focus(): void` - Set focus to the current active input. + ## Properties | Name | Description | Type | Default | | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :---------: | +| apiRef | Ref to the [API](#api) | `React.RefObject` | | | aria-describedby | HTML `aria-describedby` attribute | `string` | | | aria-label | HTML `aria-label` attribute | `string` | | | aria-labelledby | HTML `aria-labelledby` attribute | `string` | | diff --git a/src/components/PinInput/__tests__/PinInput.test.tsx b/src/components/PinInput/__tests__/PinInput.test.tsx index c0cc5ebcb7..6e92c2a56a 100644 --- a/src/components/PinInput/__tests__/PinInput.test.tsx +++ b/src/components/PinInput/__tests__/PinInput.test.tsx @@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event'; import {act, fireEvent, render, screen} from '../../../../test-utils/utils'; import {PinInput} from '../PinInput'; +import type {PinInputApi} from '../PinInput'; describe('PinInput', () => { let inputs: HTMLElement[]; @@ -158,8 +159,6 @@ describe('PinInput', () => { expect(inputs[0]).toHaveValue('x'); }); - xtest('replace current input value on change', () => {}); - test('typing via keyboard', async () => { const user = userEvent.setup(); const onUpdate = jest.fn(); @@ -271,4 +270,21 @@ describe('PinInput', () => { await user.keyboard('{ArrowUp}'); expect(inputs[0]).toHaveFocus(); }); + + describe('API', () => { + test('focus', async () => { + const user = userEvent.setup(); + const apiRef: React.RefObject = {current: null}; + renderComponent(); + + await user.click(inputs[1]); + expect(inputs[1]).toHaveFocus(); + await user.tab(); + expect(inputs[1]).not.toHaveFocus(); + act(() => { + apiRef.current?.focus(); + }); + expect(inputs[1]).toHaveFocus(); + }); + }); });