Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lazy-hawk-old-fish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/ui': minor
---

Add `autoFocus` appearance option to disable automatic input focusing
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import { FormContainer } from '@/ui/elements/FormContainer';
import { FullHeightLoader } from '@/ui/elements/FullHeightLoader';
import { handleError } from '@/ui/utils/errorHandler';

import { Button, descriptors, localizationKeys, Text } from '../../customizables';
import { Button, descriptors, localizationKeys, Text, useAppearance } from '../../customizables';
import { MfaBackupCodeList } from './MfaBackupCodeList';

type MfaBackupCodeCreateFormProps = FormProps;
export const MfaBackupCodeCreateForm = withCardStateProvider((props: MfaBackupCodeCreateFormProps) => {
const { onSuccess, onReset } = props;
const { user } = useUser();
const card = useCardState();
const { autoFocus: optionAutoFocus } = useAppearance().parsedOptions;
const createBackupCode = useReverification(() => user?.createBackupCode());
const [backupCode, setBackupCode] = React.useState<BackupCodeResource | undefined>(undefined);

Expand Down Expand Up @@ -56,7 +57,7 @@ export const MfaBackupCodeCreateForm = withCardStateProvider((props: MfaBackupCo

<FormButtonContainer>
<Button
autoFocus
autoFocus={optionAutoFocus}
onClick={onSuccess}
localizationKey={localizationKeys('userProfile.formButtonPrimary__finish')}
elementDescriptor={descriptors.formButtonPrimary}
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/customizables/parseAppearance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const defaultOptions: ParsedOptions = {
shimmer: true,
animations: true,
unsafe_disableDevelopmentModeWarnings: false,
autoFocus: true,
};

const defaultCaptchaOptions: ParsedCaptcha = {
Expand Down
5 changes: 3 additions & 2 deletions packages/ui/src/elements/CodeControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { PropsWithChildren } from 'react';
import React, { useCallback } from 'react';

import type { LocalizationKey } from '../customizables';
import { Box, descriptors, Flex, OTPInputSegment } from '../customizables';
import { Box, descriptors, Flex, OTPInputSegment, useAppearance } from '../customizables';
import { useCardState } from '../elements/contexts';
import { useLoadingStatus } from '../hooks';
import type { PropsOfComponent } from '../styledSystem';
Expand Down Expand Up @@ -166,6 +166,7 @@ export const OTPResendButton = () => {
export const OTPCodeControl = () => {
const [disabled, setDisabled] = React.useState(false);
const { otpControl, isLoading, isDisabled } = useOTPInputContext();
const { autoFocus: optionAutoFocus } = useAppearance().parsedOptions;
const { feedback, values, setValues, feedbackType, length } = otpControl.otpInputProps;

React.useEffect(() => {
Expand All @@ -180,7 +181,7 @@ export const OTPCodeControl = () => {
sx={{ position: 'relative' }}
>
<OTPInput
autoFocus // eslint-disable-line jsx-a11y/no-autofocus
autoFocus={optionAutoFocus} // eslint-disable-line jsx-a11y/no-autofocus
aria-label='Enter verification code'
aria-required
maxLength={length}
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/src/elements/FieldControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Link,
localizationKeys,
Text,
useAppearance,
useLocalizations,
} from '../customizables';
import type { ElementDescriptor, ElementId } from '../customizables/elementDescriptors';
Expand All @@ -33,13 +34,15 @@ type FormControlProps = Omit<PropsOfComponent<typeof Input>, 'label' | 'placehol

const Root = (props: PropsWithChildren<FormControlProps>) => {
const card = useCardState();
const { autoFocus: optionAutoFocus } = useAppearance().parsedOptions;
const { children, isDisabled: isDisabledProp, ...restProps } = props;

const isDisabled = isDisabledProp || card.isLoading;

const ctxProps = {
...restProps,
isDisabled,
autoFocus: optionAutoFocus && restProps.autoFocus,
};

return <FormFieldContextProvider {...ctxProps}>{children}</FormFieldContextProvider>;
Expand Down
5 changes: 3 additions & 2 deletions packages/ui/src/elements/SuccessPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import type { Flex } from '../customizables';
import { Button, Col, descriptors, Text } from '../customizables';
import { Button, Col, descriptors, Text, useAppearance } from '../customizables';
import { useNavigateToFlowStart } from '../hooks';
import type { LocalizationKey } from '../localization';
import { localizationKeys } from '../localization';
Expand All @@ -23,6 +23,7 @@ type SuccessPageProps = Omit<PropsOfComponent<typeof Flex>, 'headerTitle' | 'tit
export const SuccessPage = (props: SuccessPageProps) => {
const { text, title, subtitle, finishLabel, onFinish, contents, finishButtonProps, headerBadgeText, ...rest } = props;
const { navigateToFlowStart } = useNavigateToFlowStart();
const { autoFocus: optionAutoFocus } = useAppearance().parsedOptions;

return (
<Col
Expand Down Expand Up @@ -63,7 +64,7 @@ export const SuccessPage = (props: SuccessPageProps) => {
{contents}
<FormButtonContainer>
<Button
autoFocus
autoFocus={optionAutoFocus}
//Do we need a separate key here?
localizationKey={finishLabel || localizationKeys('userProfile.formButtonPrimary__finish')}
elementDescriptor={descriptors.formButtonPrimary}
Expand Down
6 changes: 4 additions & 2 deletions packages/ui/src/elements/TagInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import type { LocalizationKey } from '../customizables';
import { descriptors, Flex, Icon, Input, Text, useLocalizations } from '../customizables';
import { descriptors, Flex, Icon, Input, Text, useAppearance, useLocalizations } from '../customizables';
import { Plus } from '../icons';
import type { PropsOfComponent } from '../styledSystem';
import { common } from '../styledSystem';
Expand All @@ -21,16 +21,18 @@ type TagInputProps = Pick<PropsOfComponent<typeof Flex>, 'sx'> & {

export const TagInput = (props: TagInputProps) => {
const { t } = useLocalizations();
const { autoFocus: optionAutoFocus } = useAppearance().parsedOptions;
const {
sx,
placeholder,
validate = () => true,
value: valueProp,
onChange: onChangeProp,
autoFocus,
autoFocus: autoFocusProp,
validateUnsubmittedEmail = () => null,
...rest
} = props;
const autoFocus = optionAutoFocus && autoFocusProp;
const tags = valueProp.split(',').map(sanitize).filter(Boolean);
const tagsSet = new Set(tags);
const keyReleasedRef = React.useRef(true);
Expand Down
7 changes: 7 additions & 0 deletions packages/ui/src/internal/appearance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,13 @@ export type Options = {
* @default false
*/
unsafe_disableDevelopmentModeWarnings?: boolean;
/**
* Controls whether inputs will automatically receive focus when a component is mounted.
* Set to false to prevent the components from auto-focusing any input fields.
*
* @default true
*/
autoFocus?: boolean;
};

export type CaptchaAppearanceOptions = {
Expand Down
Loading