Skip to content

Commit

Permalink
feat(ui): Add <AutoFillPasswordField /> sign in functionality (#4275)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcarpenter authored Oct 3, 2024
1 parent 57e8fb4 commit 5aeff83
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .changeset/quiet-pumpkins-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
45 changes: 28 additions & 17 deletions packages/ui/src/common/password-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,28 @@ import EyeSlashSm from '~/primitives/icons/eye-slash-sm';
import EyeSm from '~/primitives/icons/eye-sm';
import { translatePasswordError } from '~/utils/make-localizable';

export function PasswordField({
alternativeFieldTrigger,
className,
label,
name = 'password',
...props
}: {
alternativeFieldTrigger?: React.ReactNode;
validatePassword?: boolean;
name?: 'password' | 'confirmPassword';
/**
* **Note:** this prop is required as the `label` differs depending on the context (e.g. new password)
*/
label: React.ReactNode;
} & Omit<React.ComponentProps<typeof Common.Input>, 'autoCapitalize' | 'autoComplete' | 'spellCheck' | 'type'>) {
export const PasswordField = React.forwardRef(function PasswordField(
{
alternativeFieldTrigger,
className,
fieldClassName,
fieldRef,
label,
name = 'password',
...props
}: {
alternativeFieldTrigger?: React.ReactNode;
validatePassword?: boolean;
name?: 'password' | 'confirmPassword';
/**
* **Note:** this prop is required as the `label` differs depending on the context (e.g. new password)
*/
label: React.ReactNode;
fieldRef?: React.Ref<HTMLDivElement>;
fieldClassName?: string;
} & Omit<React.ComponentProps<typeof Common.Input>, 'autoCapitalize' | 'autoComplete' | 'spellCheck' | 'type'>,
forwardedRef: React.ForwardedRef<HTMLInputElement>,
) {
const [type, setType] = React.useState('password');
const id = React.useId();
const { t, locale } = useLocalizations();
Expand All @@ -33,7 +40,10 @@ export function PasswordField({
name={name}
asChild
>
<Field.Root>
<Field.Root
className={fieldClassName}
ref={fieldRef}
>
<Common.Label asChild>
<Field.Label>
{label}
Expand All @@ -48,6 +58,7 @@ export function PasswordField({
type={type}
className={cx('pe-7', className)}
{...props}
ref={forwardedRef}
aria-describedby={props.validatePassword && state !== 'idle' ? id : undefined}
asChild
>
Expand Down Expand Up @@ -121,4 +132,4 @@ export function PasswordField({
</Field.Root>
</Common.Field>
);
}
});
38 changes: 38 additions & 0 deletions packages/ui/src/components/sign-in/steps/start.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import * as Common from '@clerk/elements/common';
import * as SignIn from '@clerk/elements/sign-in';
import { cx } from 'cva';
import * as React from 'react';

import { Connections } from '~/common/connections';
import { EmailField } from '~/common/email-field';
import { EmailOrPhoneNumberField } from '~/common/email-or-phone-number-field';
import { EmailOrUsernameField } from '~/common/email-or-username-field';
import { EmailOrUsernameOrPhoneNumberField } from '~/common/email-or-username-or-phone-number-field';
import { GlobalError } from '~/common/global-error';
import { PasswordField } from '~/common/password-field';
import { PhoneNumberField } from '~/common/phone-number-field';
import { PhoneNumberOrUsernameField } from '~/common/phone-number-or-username-field';
import { UsernameField } from '~/common/username-field';
Expand Down Expand Up @@ -136,6 +139,8 @@ export function SignInStart() {
required
/>
) : null}

<AutoFillPasswordField />
</div>
) : null}
{options.socialButtonsPlacement === 'bottom' ? connectionsWithSeperator.reverse() : null}
Expand Down Expand Up @@ -203,3 +208,36 @@ export function SignInStart() {
</Common.Loading>
);
}

function AutoFillPasswordField() {
const { t } = useLocalizations();
const [isAutoFilled, setIsAutoFilled] = React.useState(false);
const fieldRef = React.useRef<HTMLDivElement>(null);

const handleAutofill = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.value && !isAutoFilled) {
setIsAutoFilled(true);
}
};

React.useEffect(() => {
if (fieldRef.current) {
fieldRef.current.setAttribute('inert', '');
}
}, []);

React.useEffect(() => {
if (fieldRef.current && isAutoFilled) {
fieldRef.current.removeAttribute('inert');
}
}, [isAutoFilled]);

return (
<PasswordField
label={t('formFieldLabel__password')}
fieldRef={fieldRef}
fieldClassName={cx(!isAutoFilled && 'absolute opacity-0 [clip-path:polygon(0px_0px,_0px_0px,_0px_0px,_0px_0px)]')}
onChange={handleAutofill}
/>
);
}

0 comments on commit 5aeff83

Please sign in to comment.