From 56fa5a0183c10de304941210a5c201f04174dca5 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 8 Jan 2026 14:42:06 +0000 Subject: [PATCH 01/14] feat(auth): Add YahooSignInButton support --- .../screens/sign-in-auth-screen-w-oauth.tsx | 2 + .../angular/src/lib/components/logos/yahoo.ts | 44 ++++++++++++++ packages/core/brands/yahoo/logo.svg | 14 +++++ packages/react/src/auth/index.ts | 1 + .../src/auth/oauth/yahoo-sign-in-button.tsx | 59 +++++++++++++++++++ .../react/src/components/logos/yahoo/Logo.tsx | 16 +++++ packages/styles/src/base.css | 8 +++ packages/translations/src/locales/en-us.ts | 1 + packages/translations/src/types.ts | 2 + 9 files changed, 147 insertions(+) create mode 100644 packages/angular/src/lib/components/logos/yahoo.ts create mode 100644 packages/core/brands/yahoo/logo.svg create mode 100644 packages/react/src/auth/oauth/yahoo-sign-in-button.tsx create mode 100644 packages/react/src/components/logos/yahoo/Logo.tsx diff --git a/examples/react/src/screens/sign-in-auth-screen-w-oauth.tsx b/examples/react/src/screens/sign-in-auth-screen-w-oauth.tsx index c84d86cd..a4b4bc74 100644 --- a/examples/react/src/screens/sign-in-auth-screen-w-oauth.tsx +++ b/examples/react/src/screens/sign-in-auth-screen-w-oauth.tsx @@ -22,6 +22,7 @@ import { GitHubSignInButton, MicrosoftSignInButton, TwitterSignInButton, + YahooSignInButton, } from "@firebase-oss/ui-react"; import { useNavigate } from "react-router"; @@ -41,6 +42,7 @@ export default function SignInAuthScreenWithOAuthPage() { + ); diff --git a/packages/angular/src/lib/components/logos/yahoo.ts b/packages/angular/src/lib/components/logos/yahoo.ts new file mode 100644 index 00000000..35a8fbf7 --- /dev/null +++ b/packages/angular/src/lib/components/logos/yahoo.ts @@ -0,0 +1,44 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// GENERATED BY generate-logos.ts + +import { Component, input } from "@angular/core"; + +@Component({ + selector: "fui-yahoo-logo", + standalone: true, + template: ` + + + + + `, +}) +/** + * The Yahoo logo SVG component. + */ +export class YahooLogoComponent { + /** The width of the logo. */ + width = input("1em"); + /** The height of the logo. */ + height = input("1em"); + /** Optional additional CSS class names. */ + className = input(""); +} diff --git a/packages/core/brands/yahoo/logo.svg b/packages/core/brands/yahoo/logo.svg new file mode 100644 index 00000000..e211f679 --- /dev/null +++ b/packages/core/brands/yahoo/logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/react/src/auth/index.ts b/packages/react/src/auth/index.ts index 846f012c..e4df78db 100644 --- a/packages/react/src/auth/index.ts +++ b/packages/react/src/auth/index.ts @@ -128,4 +128,5 @@ export { type MicrosoftSignInButtonProps, } from "./oauth/microsoft-sign-in-button"; export { TwitterSignInButton, TwitterLogo, type TwitterSignInButtonProps } from "./oauth/twitter-sign-in-button"; +export { YahooSignInButton, YahooLogo, type YahooSignInButtonProps } from "./oauth/yahoo-sign-in-button"; export { OAuthButton, useSignInWithProvider, type OAuthButtonProps } from "./oauth/oauth-button"; diff --git a/packages/react/src/auth/oauth/yahoo-sign-in-button.tsx b/packages/react/src/auth/oauth/yahoo-sign-in-button.tsx new file mode 100644 index 00000000..a7b5691a --- /dev/null +++ b/packages/react/src/auth/oauth/yahoo-sign-in-button.tsx @@ -0,0 +1,59 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +"use client"; + +import { getTranslation } from "@firebase-oss/ui-core"; +import { OAuthProvider, type UserCredential } from "firebase/auth"; +import { useUI } from "~/hooks"; +import { OAuthButton } from "./oauth-button"; +import YahooSvgLogo from "~/components/logos/yahoo/Logo"; +import { cn } from "~/utils/cn"; + +/** Props for the YahooSignInButton component. */ +export type YahooSignInButtonProps = { + /** Optional OAuth provider instance. Defaults to Yahoo provider. */ + provider?: OAuthProvider; + /** Whether to apply themed styling. */ + themed?: boolean; + /** Callback function called when sign-in is successful. */ + onSignIn?: (credential: UserCredential) => void; +}; + +/** + * A button component for signing in with Yahoo. + * + * @returns The Yahoo sign-in button component. + */ +export function YahooSignInButton({ provider, ...props }: YahooSignInButtonProps) { + const ui = useUI(); + + return ( + + + {getTranslation(ui, "labels", "signInWithYahoo")} + + ); +} + +/** + * The Yahoo logo SVG component. + * + * @returns The Yahoo logo component. + */ +export function YahooLogo({ className, ...props }: React.SVGProps) { + return ; +} diff --git a/packages/react/src/components/logos/yahoo/Logo.tsx b/packages/react/src/components/logos/yahoo/Logo.tsx new file mode 100644 index 00000000..6ebabbfa --- /dev/null +++ b/packages/react/src/components/logos/yahoo/Logo.tsx @@ -0,0 +1,16 @@ +import type { SVGProps } from "react"; +const SvgLogo = (props: SVGProps) => ( + + + + +); +export default SvgLogo; diff --git a/packages/styles/src/base.css b/packages/styles/src/base.css index c6a4d48b..7faf70ca 100644 --- a/packages/styles/src/base.css +++ b/packages/styles/src/base.css @@ -322,4 +322,12 @@ --color-primary-surface: #FFFFFF; --color-border: var(--twitter-primary); } + + .fui-provider__button[data-provider="yahoo.com"][data-themed="true"] { + --yahoo-primary: #5F01D1; + --color-primary: var(--yahoo-primary); + --color-primary-hover: --alpha(var(--yahoo-primary) / 85%); + --color-primary-surface: #FFFFFF; + --color-border: var(--yahoo-primary); + } } diff --git a/packages/translations/src/locales/en-us.ts b/packages/translations/src/locales/en-us.ts index 2cb38358..dabd398d 100644 --- a/packages/translations/src/locales/en-us.ts +++ b/packages/translations/src/locales/en-us.ts @@ -80,6 +80,7 @@ export const enUS = { signInWithMicrosoft: "Sign in with Microsoft", signInWithGitHub: "Sign in with GitHub", signInWithTwitter: "Sign in with X", + signInWithYahoo: "Sign in with Yahoo", signInWithEmailLink: "Sign in with Email Link", sendSignInLink: "Send Sign-in Link", termsOfService: "Terms of Service", diff --git a/packages/translations/src/types.ts b/packages/translations/src/types.ts index 26e4ce82..7cd41537 100644 --- a/packages/translations/src/types.ts +++ b/packages/translations/src/types.ts @@ -160,6 +160,8 @@ export type Translations = { signInWithMicrosoft?: string; /** Translation for sign in with GitHub button. */ signInWithGitHub?: string; + /** Translation for sign in with Yahoo button. */ + signInWithYahoo?: string; /** Translation for sign in with email link button. */ signInWithEmailLink?: string; /** Translation for send sign-in link button. */ From 17beca06d0afb3e251e332a941b693e6619e6f97 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 22 Jan 2026 13:18:14 +0000 Subject: [PATCH 02/14] fix: Check errors on field change rather than focus --- packages/core/src/schemas.ts | 16 ++++++++-------- .../src/auth/forms/email-link-auth-form.tsx | 2 +- .../src/auth/forms/forgot-password-auth-form.tsx | 2 +- .../mfa/sms-multi-factor-assertion-form.tsx | 2 +- .../mfa/sms-multi-factor-enrollment-form.tsx | 4 ++-- .../mfa/totp-multi-factor-assertion-form.tsx | 2 +- .../mfa/totp-multi-factor-enrollment-form.tsx | 4 ++-- .../react/src/auth/forms/phone-auth-form.tsx | 4 ++-- .../react/src/auth/forms/sign-in-auth-form.tsx | 2 +- .../react/src/auth/forms/sign-up-auth-form.tsx | 2 +- packages/react/src/components/form.tsx | 12 ++++++++---- 11 files changed, 28 insertions(+), 24 deletions(-) diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts index 2e03b42c..804f7d2e 100644 --- a/packages/core/src/schemas.ts +++ b/packages/core/src/schemas.ts @@ -22,22 +22,22 @@ import { hasBehavior } from "./behaviors"; /** * Creates a Zod schema for sign-in form validation. * - * Validates email format and password minimum length (6 characters). + * Validates email format and password minimum length (8 characters). * * @param ui - The FirebaseUI instance. * @returns A Zod schema for sign-in form validation. */ export function createSignInAuthFormSchema(ui: FirebaseUI) { return z.object({ - email: z.email(getTranslation(ui, "errors", "invalidEmail")), - password: z.string().min(6, getTranslation(ui, "errors", "weakPassword")), + email: z.string().email(getTranslation(ui, "errors", "invalidEmail")), + password: z.string().min(8, getTranslation(ui, "errors", "weakPassword")), }); } /** * Creates a Zod schema for sign-up form validation. * - * Validates email format, password minimum length (6 characters), and optionally requires a display name + * Validates email format, password minimum length (8 characters), and optionally requires a display name * if the `requireDisplayName` behavior is enabled. * * @param ui - The FirebaseUI instance. @@ -48,8 +48,8 @@ export function createSignUpAuthFormSchema(ui: FirebaseUI) { const displayNameRequiredMessage = getTranslation(ui, "errors", "displayNameRequired"); return z.object({ - email: z.email(getTranslation(ui, "errors", "invalidEmail")), - password: z.string().min(6, getTranslation(ui, "errors", "weakPassword")), + email: z.string().email(getTranslation(ui, "errors", "invalidEmail")), + password: z.string().min(8, getTranslation(ui, "errors", "weakPassword")), displayName: requireDisplayName ? z.string().min(1, displayNameRequiredMessage) : z.string().min(1, displayNameRequiredMessage).optional(), @@ -66,7 +66,7 @@ export function createSignUpAuthFormSchema(ui: FirebaseUI) { */ export function createForgotPasswordAuthFormSchema(ui: FirebaseUI) { return z.object({ - email: z.email(getTranslation(ui, "errors", "invalidEmail")), + email: z.string().email(getTranslation(ui, "errors", "invalidEmail")), }); } @@ -80,7 +80,7 @@ export function createForgotPasswordAuthFormSchema(ui: FirebaseUI) { */ export function createEmailLinkAuthFormSchema(ui: FirebaseUI) { return z.object({ - email: z.email(getTranslation(ui, "errors", "invalidEmail")), + email: z.string().email(getTranslation(ui, "errors", "invalidEmail")), }); } diff --git a/packages/react/src/auth/forms/email-link-auth-form.tsx b/packages/react/src/auth/forms/email-link-auth-form.tsx index 028f25df..064a2e97 100644 --- a/packages/react/src/auth/forms/email-link-auth-form.tsx +++ b/packages/react/src/auth/forms/email-link-auth-form.tsx @@ -71,7 +71,7 @@ export function useEmailLinkAuthForm(onSuccess?: EmailLinkAuthFormProps["onEmail email: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { await action(value); diff --git a/packages/react/src/auth/forms/forgot-password-auth-form.tsx b/packages/react/src/auth/forms/forgot-password-auth-form.tsx index ff6937ed..f0e7ce0e 100644 --- a/packages/react/src/auth/forms/forgot-password-auth-form.tsx +++ b/packages/react/src/auth/forms/forgot-password-auth-form.tsx @@ -70,7 +70,7 @@ export function useForgotPasswordAuthForm(onSuccess?: ForgotPasswordAuthFormProp email: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { await action(value); diff --git a/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx b/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx index 761e9514..055c0ca4 100644 --- a/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx +++ b/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx @@ -180,7 +180,7 @@ export function useSmsMultiFactorAssertionVerifyForm({ verificationCode: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const credential = await action(value); diff --git a/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx b/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx index 3b383955..5612663a 100644 --- a/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx +++ b/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx @@ -79,7 +79,7 @@ export function useSmsMultiFactorEnrollmentPhoneNumberForm({ phoneNumber: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const formatted = formatPhoneNumber ? formatPhoneNumber(value.phoneNumber) : value.phoneNumber; @@ -201,7 +201,7 @@ export function useMultiFactorEnrollmentVerifyPhoneNumberForm({ verificationCode: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { await action({ ...value, displayName }); diff --git a/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx b/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx index acdb607b..efc9aabd 100644 --- a/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx +++ b/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx @@ -60,7 +60,7 @@ export function useTotpMultiFactorAssertionForm({ hint, onSuccess }: UseTotpMult verificationCode: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const credential = await action({ verificationCode: value.verificationCode, hint }); diff --git a/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx b/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx index 6581875a..a1ddf6aa 100644 --- a/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx +++ b/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx @@ -60,7 +60,7 @@ export function useTotpMultiFactorSecretGenerationForm({ onSuccess }: UseTotpMul displayName: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const secret = await action(); @@ -160,7 +160,7 @@ export function useMultiFactorEnrollmentVerifyTotpForm({ verificationCode: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { await action({ secret, verificationCode: value.verificationCode, displayName }); diff --git a/packages/react/src/auth/forms/phone-auth-form.tsx b/packages/react/src/auth/forms/phone-auth-form.tsx index 0553c457..09262197 100644 --- a/packages/react/src/auth/forms/phone-auth-form.tsx +++ b/packages/react/src/auth/forms/phone-auth-form.tsx @@ -71,7 +71,7 @@ export function usePhoneNumberForm({ recaptchaVerifier, onSuccess, formatPhoneNu phoneNumber: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const formatted = formatPhoneNumber ? formatPhoneNumber(value.phoneNumber) : value.phoneNumber; @@ -183,7 +183,7 @@ export function useVerifyPhoneNumberForm({ verificationId, onSuccess }: UseVerif verificationCode: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const credential = await action(value); diff --git a/packages/react/src/auth/forms/sign-in-auth-form.tsx b/packages/react/src/auth/forms/sign-in-auth-form.tsx index 488a0462..97cef95d 100644 --- a/packages/react/src/auth/forms/sign-in-auth-form.tsx +++ b/packages/react/src/auth/forms/sign-in-auth-form.tsx @@ -74,7 +74,7 @@ export function useSignInAuthForm(onSuccess?: SignInAuthFormProps["onSignIn"]) { password: "", }, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const credential = await action(value); diff --git a/packages/react/src/auth/forms/sign-up-auth-form.tsx b/packages/react/src/auth/forms/sign-up-auth-form.tsx index b2eab420..74f5e240 100644 --- a/packages/react/src/auth/forms/sign-up-auth-form.tsx +++ b/packages/react/src/auth/forms/sign-up-auth-form.tsx @@ -85,7 +85,7 @@ export function useSignUpAuthForm(onSuccess?: SignUpAuthFormProps["onSignUp"]) { displayName: requireDisplayName ? "" : undefined, } as z.infer, validators: { - onBlur: schema, + onChange: schema, onSubmitAsync: async ({ value }) => { try { const credential = await action(value); diff --git a/packages/react/src/components/form.tsx b/packages/react/src/components/form.tsx index 897cf01e..d812b34f 100644 --- a/packages/react/src/components/form.tsx +++ b/packages/react/src/components/form.tsx @@ -27,10 +27,8 @@ function FieldMetadata({ className, ...props }: ComponentProps<"div"> & { field: } return ( -
-
- {props.field.state.meta.errors.map((error) => error.message).join(", ")} -
+
+ {props.field.state.meta.errors.map((error) => error.message).join(", ")}
); } @@ -46,6 +44,7 @@ function Input({ ComponentProps<"input"> & { label: string; before?: ReactNode; action?: ReactNode; description?: ReactNode } >) { const field = useFieldContext(); + const form = useFormContext(); return (
From 5c85c6a4c9f948d96376aee16d05be37d3818dc8 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 22 Jan 2026 13:21:03 +0000 Subject: [PATCH 03/14] chore: remove yahoo references from other PR --- .../screens/sign-in-auth-screen-w-oauth.tsx | 2 - .../angular/src/lib/components/logos/yahoo.ts | 44 ------------------- packages/core/brands/yahoo/logo.svg | 14 ------ 3 files changed, 60 deletions(-) delete mode 100644 packages/angular/src/lib/components/logos/yahoo.ts delete mode 100644 packages/core/brands/yahoo/logo.svg diff --git a/examples/react/src/screens/sign-in-auth-screen-w-oauth.tsx b/examples/react/src/screens/sign-in-auth-screen-w-oauth.tsx index a4b4bc74..c84d86cd 100644 --- a/examples/react/src/screens/sign-in-auth-screen-w-oauth.tsx +++ b/examples/react/src/screens/sign-in-auth-screen-w-oauth.tsx @@ -22,7 +22,6 @@ import { GitHubSignInButton, MicrosoftSignInButton, TwitterSignInButton, - YahooSignInButton, } from "@firebase-oss/ui-react"; import { useNavigate } from "react-router"; @@ -42,7 +41,6 @@ export default function SignInAuthScreenWithOAuthPage() { - ); diff --git a/packages/angular/src/lib/components/logos/yahoo.ts b/packages/angular/src/lib/components/logos/yahoo.ts deleted file mode 100644 index 35a8fbf7..00000000 --- a/packages/angular/src/lib/components/logos/yahoo.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// GENERATED BY generate-logos.ts - -import { Component, input } from "@angular/core"; - -@Component({ - selector: "fui-yahoo-logo", - standalone: true, - template: ` - - - - - `, -}) -/** - * The Yahoo logo SVG component. - */ -export class YahooLogoComponent { - /** The width of the logo. */ - width = input("1em"); - /** The height of the logo. */ - height = input("1em"); - /** Optional additional CSS class names. */ - className = input(""); -} diff --git a/packages/core/brands/yahoo/logo.svg b/packages/core/brands/yahoo/logo.svg deleted file mode 100644 index e211f679..00000000 --- a/packages/core/brands/yahoo/logo.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - \ No newline at end of file From 21300d5cbd5dd8486b54b6248e6c91f396ba91ec Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 22 Jan 2026 13:23:35 +0000 Subject: [PATCH 04/14] chore: last random yahoo references --- packages/react/src/auth/index.ts | 1 - .../src/auth/oauth/yahoo-sign-in-button.tsx | 59 ------------------- packages/styles/src/base.css | 8 --- packages/translations/src/locales/en-us.ts | 1 - packages/translations/src/types.ts | 2 - 5 files changed, 71 deletions(-) delete mode 100644 packages/react/src/auth/oauth/yahoo-sign-in-button.tsx diff --git a/packages/react/src/auth/index.ts b/packages/react/src/auth/index.ts index e4df78db..846f012c 100644 --- a/packages/react/src/auth/index.ts +++ b/packages/react/src/auth/index.ts @@ -128,5 +128,4 @@ export { type MicrosoftSignInButtonProps, } from "./oauth/microsoft-sign-in-button"; export { TwitterSignInButton, TwitterLogo, type TwitterSignInButtonProps } from "./oauth/twitter-sign-in-button"; -export { YahooSignInButton, YahooLogo, type YahooSignInButtonProps } from "./oauth/yahoo-sign-in-button"; export { OAuthButton, useSignInWithProvider, type OAuthButtonProps } from "./oauth/oauth-button"; diff --git a/packages/react/src/auth/oauth/yahoo-sign-in-button.tsx b/packages/react/src/auth/oauth/yahoo-sign-in-button.tsx deleted file mode 100644 index a7b5691a..00000000 --- a/packages/react/src/auth/oauth/yahoo-sign-in-button.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -"use client"; - -import { getTranslation } from "@firebase-oss/ui-core"; -import { OAuthProvider, type UserCredential } from "firebase/auth"; -import { useUI } from "~/hooks"; -import { OAuthButton } from "./oauth-button"; -import YahooSvgLogo from "~/components/logos/yahoo/Logo"; -import { cn } from "~/utils/cn"; - -/** Props for the YahooSignInButton component. */ -export type YahooSignInButtonProps = { - /** Optional OAuth provider instance. Defaults to Yahoo provider. */ - provider?: OAuthProvider; - /** Whether to apply themed styling. */ - themed?: boolean; - /** Callback function called when sign-in is successful. */ - onSignIn?: (credential: UserCredential) => void; -}; - -/** - * A button component for signing in with Yahoo. - * - * @returns The Yahoo sign-in button component. - */ -export function YahooSignInButton({ provider, ...props }: YahooSignInButtonProps) { - const ui = useUI(); - - return ( - - - {getTranslation(ui, "labels", "signInWithYahoo")} - - ); -} - -/** - * The Yahoo logo SVG component. - * - * @returns The Yahoo logo component. - */ -export function YahooLogo({ className, ...props }: React.SVGProps) { - return ; -} diff --git a/packages/styles/src/base.css b/packages/styles/src/base.css index 7faf70ca..c6a4d48b 100644 --- a/packages/styles/src/base.css +++ b/packages/styles/src/base.css @@ -322,12 +322,4 @@ --color-primary-surface: #FFFFFF; --color-border: var(--twitter-primary); } - - .fui-provider__button[data-provider="yahoo.com"][data-themed="true"] { - --yahoo-primary: #5F01D1; - --color-primary: var(--yahoo-primary); - --color-primary-hover: --alpha(var(--yahoo-primary) / 85%); - --color-primary-surface: #FFFFFF; - --color-border: var(--yahoo-primary); - } } diff --git a/packages/translations/src/locales/en-us.ts b/packages/translations/src/locales/en-us.ts index dabd398d..2cb38358 100644 --- a/packages/translations/src/locales/en-us.ts +++ b/packages/translations/src/locales/en-us.ts @@ -80,7 +80,6 @@ export const enUS = { signInWithMicrosoft: "Sign in with Microsoft", signInWithGitHub: "Sign in with GitHub", signInWithTwitter: "Sign in with X", - signInWithYahoo: "Sign in with Yahoo", signInWithEmailLink: "Sign in with Email Link", sendSignInLink: "Send Sign-in Link", termsOfService: "Terms of Service", diff --git a/packages/translations/src/types.ts b/packages/translations/src/types.ts index 7cd41537..26e4ce82 100644 --- a/packages/translations/src/types.ts +++ b/packages/translations/src/types.ts @@ -160,8 +160,6 @@ export type Translations = { signInWithMicrosoft?: string; /** Translation for sign in with GitHub button. */ signInWithGitHub?: string; - /** Translation for sign in with Yahoo button. */ - signInWithYahoo?: string; /** Translation for sign in with email link button. */ signInWithEmailLink?: string; /** Translation for send sign-in link button. */ From 8e6b50d73cc0814899d75fcf1205400afe129743 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 22 Jan 2026 13:50:43 +0000 Subject: [PATCH 05/14] fix: put schema password length to 6 --- packages/core/src/schemas.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts index 804f7d2e..255b8ab6 100644 --- a/packages/core/src/schemas.ts +++ b/packages/core/src/schemas.ts @@ -22,7 +22,7 @@ import { hasBehavior } from "./behaviors"; /** * Creates a Zod schema for sign-in form validation. * - * Validates email format and password minimum length (8 characters). + * Validates email format and password minimum length (6 characters). * * @param ui - The FirebaseUI instance. * @returns A Zod schema for sign-in form validation. @@ -30,14 +30,14 @@ import { hasBehavior } from "./behaviors"; export function createSignInAuthFormSchema(ui: FirebaseUI) { return z.object({ email: z.string().email(getTranslation(ui, "errors", "invalidEmail")), - password: z.string().min(8, getTranslation(ui, "errors", "weakPassword")), + password: z.string().min(6, getTranslation(ui, "errors", "weakPassword")), }); } /** * Creates a Zod schema for sign-up form validation. * - * Validates email format, password minimum length (8 characters), and optionally requires a display name + * Validates email format, password minimum length (6 characters), and optionally requires a display name * if the `requireDisplayName` behavior is enabled. * * @param ui - The FirebaseUI instance. @@ -49,7 +49,7 @@ export function createSignUpAuthFormSchema(ui: FirebaseUI) { return z.object({ email: z.string().email(getTranslation(ui, "errors", "invalidEmail")), - password: z.string().min(8, getTranslation(ui, "errors", "weakPassword")), + password: z.string().min(6, getTranslation(ui, "errors", "weakPassword")), displayName: requireDisplayName ? z.string().min(1, displayNameRequiredMessage) : z.string().min(1, displayNameRequiredMessage).optional(), From 92992dcb818351b78133c3d410ecba1831d0e147 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Fri, 23 Jan 2026 13:53:08 +0000 Subject: [PATCH 06/14] chore: change to onChange modes for validation --- .../lib/auth/forms/email-link-auth-form.ts | 1 + .../auth/forms/forgot-password-auth-form.ts | 1 + .../mfa/sms-multi-factor-assertion-form.ts | 1 + .../mfa/sms-multi-factor-enrollment-form.ts | 4 +-- .../mfa/totp-multi-factor-assertion-form.ts | 1 + .../mfa/totp-multi-factor-enrollment-form.ts | 4 +-- .../src/lib/auth/forms/phone-auth-form.ts | 2 ++ .../src/lib/auth/forms/sign-in-auth-form.ts | 2 +- .../src/lib/auth/forms/sign-up-auth-form.ts | 1 + packages/angular/src/lib/components/form.ts | 26 ++++++++++++++++--- .../src/auth/forms/email-link-auth-form.tsx | 1 + .../auth/forms/forgot-password-auth-form.tsx | 1 + .../mfa/sms-multi-factor-assertion-form.tsx | 1 + .../mfa/sms-multi-factor-enrollment-form.tsx | 2 ++ .../mfa/totp-multi-factor-assertion-form.tsx | 1 + .../mfa/totp-multi-factor-enrollment-form.tsx | 2 ++ .../react/src/auth/forms/phone-auth-form.tsx | 2 ++ .../src/auth/forms/sign-in-auth-form.tsx | 1 + .../src/auth/forms/sign-up-auth-form.tsx | 1 + .../react/tests/register.integration.test.tsx | 2 +- .../src/components/email-link-auth-form.tsx | 2 ++ .../components/forgot-password-auth-form.tsx | 1 + .../shadcn/src/components/phone-auth-form.tsx | 2 ++ .../src/components/sign-in-auth-form.tsx | 1 + .../src/components/sign-up-auth-form.tsx | 1 + packages/translations/src/locales/en-us.ts | 2 +- 26 files changed, 55 insertions(+), 11 deletions(-) diff --git a/packages/angular/src/lib/auth/forms/email-link-auth-form.ts b/packages/angular/src/lib/auth/forms/email-link-auth-form.ts index 0e7f0709..64b2a339 100644 --- a/packages/angular/src/lib/auth/forms/email-link-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/email-link-auth-form.ts @@ -107,6 +107,7 @@ export class EmailLinkAuthFormComponent { this.form.update({ validators: { onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { await sendSignInLinkToEmail(this.ui(), value.email); diff --git a/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts b/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts index e45e6509..ec6002f0 100644 --- a/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts @@ -115,6 +115,7 @@ export class ForgotPasswordAuthFormComponent { this.form.update({ validators: { onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { await sendPasswordResetEmail(this.ui(), value.email); diff --git a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts index 143ef207..21f27f25 100644 --- a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts +++ b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts @@ -204,6 +204,7 @@ export class SmsMultiFactorAssertionVerifyFormComponent { this.form.update({ validators: { onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmit: this.formSchema(), onSubmitAsync: async ({ value }) => { try { diff --git a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts index 680244e5..af8d72f5 100644 --- a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts +++ b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts @@ -164,7 +164,7 @@ export class SmsMultiFactorEnrollmentFormComponent { effect(() => { this.phoneForm.update({ validators: { - onBlur: this.phoneFormSchema(), + onChange: this.phoneFormSchema(), onSubmit: this.phoneFormSchema(), onSubmitAsync: async ({ value }) => { try { @@ -192,7 +192,7 @@ export class SmsMultiFactorEnrollmentFormComponent { effect(() => { this.verificationForm.update({ validators: { - onBlur: this.verificationFormSchema(), + onChange: this.verificationFormSchema(), onSubmit: this.verificationFormSchema(), onSubmitAsync: async ({ value }) => { try { diff --git a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts index d9b08e92..1a05a20b 100644 --- a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts +++ b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts @@ -89,6 +89,7 @@ export class TotpMultiFactorAssertionFormComponent { this.form.update({ validators: { onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { const assertion = TotpMultiFactorGenerator.assertionForSignIn(this.hint().uid, value.verificationCode); diff --git a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts index 936690eb..a9b2caa6 100644 --- a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts +++ b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts @@ -91,7 +91,7 @@ export class TotpMultiFactorSecretGenerationFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmit: this.formSchema(), onSubmitAsync: async ({ value }) => { try { @@ -191,7 +191,7 @@ export class TotpMultiFactorVerificationFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmit: this.formSchema(), onSubmitAsync: async ({ value }) => { try { diff --git a/packages/angular/src/lib/auth/forms/phone-auth-form.ts b/packages/angular/src/lib/auth/forms/phone-auth-form.ts index b87d10fe..b0c6ac6b 100644 --- a/packages/angular/src/lib/auth/forms/phone-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/phone-auth-form.ts @@ -111,6 +111,7 @@ export class PhoneNumberFormComponent { this.form.update({ validators: { onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { const selectedCountry = countryData.find((c) => c.code === this.country()); const formattedNumber = formatPhoneNumber(value.phoneNumber, selectedCountry!); @@ -227,6 +228,7 @@ export class VerificationFormComponent { this.form.update({ validators: { onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { const credential = await confirmPhoneNumber(this.ui(), this.verificationId(), value.verificationCode); diff --git a/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts b/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts index a1fa5c12..b6547923 100644 --- a/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts @@ -129,7 +129,7 @@ export class SignInAuthFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { const credential = await signInWithEmailAndPassword(this.ui(), value.email, value.password); diff --git a/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts b/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts index 498d985c..0de40548 100644 --- a/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts @@ -126,6 +126,7 @@ export class SignUpAuthFormComponent { this.form.update({ validators: { onBlur: this.formSchema(), + onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { const credential = await createUserWithEmailAndPassword( diff --git a/packages/angular/src/lib/components/form.ts b/packages/angular/src/lib/components/form.ts index 856a96fa..027751cc 100644 --- a/packages/angular/src/lib/components/form.ts +++ b/packages/angular/src/lib/components/form.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Component, computed, input } from "@angular/core"; +import { ChangeDetectorRef, Component, computed, inject, input, OnChanges, SimpleChanges } from "@angular/core"; import { AnyFieldApi, AnyFormState, injectField } from "@tanstack/angular-form"; import { ButtonComponent } from "./button"; @@ -37,7 +37,8 @@ import { ButtonComponent } from "./button"; /** * A component that displays form field metadata, such as validation errors. */ -export class FormMetadataComponent { +export class FormMetadataComponent implements OnChanges { + private cdr = inject(ChangeDetectorRef); /** The form field API instance. */ field = input.required(); errors = computed(() => @@ -45,6 +46,12 @@ export class FormMetadataComponent { .state.meta.errors.map((error) => error.message) .join(", ") ); + + ngOnChanges(changes: SimpleChanges): void { + if (changes['field']) { + this.cdr.markForCheck(); + } + } } @Component({ @@ -70,7 +77,7 @@ export class FormMetadataComponent { [id]="field.api.name" [name]="field.api.name" [value]="field.api.state.value" - (blur)="field.api.handleBlur()" + (blur)="handleBlur()" (input)="field.api.handleChange($any($event).target.value)" [type]="type()" /> @@ -83,14 +90,25 @@ export class FormMetadataComponent { /** * A form input component with label, description, and validation support. */ -export class FormInputComponent { +export class FormInputComponent implements OnChanges { field = injectField(); + private cdr = inject(ChangeDetectorRef); /** The label text for the input field. */ label = input.required(); /** The input type (e.g., "text", "email", "password"). */ type = input("text"); /** Optional description text displayed below the label. */ description = input(); + + ngOnChanges(_changes: SimpleChanges): void { + // Trigger change detection when any input changes + this.cdr.markForCheck(); + } + + handleBlur() { + this.field.api.handleBlur(); + this.cdr.markForCheck(); + } } @Component({ diff --git a/packages/react/src/auth/forms/email-link-auth-form.tsx b/packages/react/src/auth/forms/email-link-auth-form.tsx index 064a2e97..0778c749 100644 --- a/packages/react/src/auth/forms/email-link-auth-form.tsx +++ b/packages/react/src/auth/forms/email-link-auth-form.tsx @@ -71,6 +71,7 @@ export function useEmailLinkAuthForm(onSuccess?: EmailLinkAuthFormProps["onEmail email: "", }, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/forgot-password-auth-form.tsx b/packages/react/src/auth/forms/forgot-password-auth-form.tsx index f0e7ce0e..f17e443c 100644 --- a/packages/react/src/auth/forms/forgot-password-auth-form.tsx +++ b/packages/react/src/auth/forms/forgot-password-auth-form.tsx @@ -70,6 +70,7 @@ export function useForgotPasswordAuthForm(onSuccess?: ForgotPasswordAuthFormProp email: "", }, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx b/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx index 055c0ca4..21b9c927 100644 --- a/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx +++ b/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx @@ -180,6 +180,7 @@ export function useSmsMultiFactorAssertionVerifyForm({ verificationCode: "", }, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx b/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx index 5612663a..24b20e49 100644 --- a/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx +++ b/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx @@ -79,6 +79,7 @@ export function useSmsMultiFactorEnrollmentPhoneNumberForm({ phoneNumber: "", }, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { @@ -201,6 +202,7 @@ export function useMultiFactorEnrollmentVerifyPhoneNumberForm({ verificationCode: "", }, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx b/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx index efc9aabd..f12dd212 100644 --- a/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx +++ b/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx @@ -60,6 +60,7 @@ export function useTotpMultiFactorAssertionForm({ hint, onSuccess }: UseTotpMult verificationCode: "", }, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx b/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx index a1ddf6aa..23adfde4 100644 --- a/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx +++ b/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx @@ -60,6 +60,7 @@ export function useTotpMultiFactorSecretGenerationForm({ onSuccess }: UseTotpMul displayName: "", }, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { @@ -160,6 +161,7 @@ export function useMultiFactorEnrollmentVerifyTotpForm({ verificationCode: "", }, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/phone-auth-form.tsx b/packages/react/src/auth/forms/phone-auth-form.tsx index 09262197..8e44380e 100644 --- a/packages/react/src/auth/forms/phone-auth-form.tsx +++ b/packages/react/src/auth/forms/phone-auth-form.tsx @@ -71,6 +71,7 @@ export function usePhoneNumberForm({ recaptchaVerifier, onSuccess, formatPhoneNu phoneNumber: "", }, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { @@ -183,6 +184,7 @@ export function useVerifyPhoneNumberForm({ verificationId, onSuccess }: UseVerif verificationCode: "", }, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/sign-in-auth-form.tsx b/packages/react/src/auth/forms/sign-in-auth-form.tsx index 97cef95d..1e912736 100644 --- a/packages/react/src/auth/forms/sign-in-auth-form.tsx +++ b/packages/react/src/auth/forms/sign-in-auth-form.tsx @@ -74,6 +74,7 @@ export function useSignInAuthForm(onSuccess?: SignInAuthFormProps["onSignIn"]) { password: "", }, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/sign-up-auth-form.tsx b/packages/react/src/auth/forms/sign-up-auth-form.tsx index 74f5e240..358a7c25 100644 --- a/packages/react/src/auth/forms/sign-up-auth-form.tsx +++ b/packages/react/src/auth/forms/sign-up-auth-form.tsx @@ -85,6 +85,7 @@ export function useSignUpAuthForm(onSuccess?: SignUpAuthFormProps["onSignUp"]) { displayName: requireDisplayName ? "" : undefined, } as z.infer, validators: { + onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/tests/register.integration.test.tsx b/packages/react/tests/register.integration.test.tsx index f0cd04ad..60f9a9db 100644 --- a/packages/react/tests/register.integration.test.tsx +++ b/packages/react/tests/register.integration.test.tsx @@ -43,7 +43,7 @@ import { describe } from "vitest"; describe.skip("TODO"); // describe("Register Integration", () => { -// // Ensure password is at least 8 characters to pass validation +// // Ensure password is at least 6 characters to pass validation // const testPassword = "Test123456!"; // let testEmail: string; diff --git a/packages/shadcn/src/components/email-link-auth-form.tsx b/packages/shadcn/src/components/email-link-auth-form.tsx index 748f93eb..7c1d4195 100644 --- a/packages/shadcn/src/components/email-link-auth-form.tsx +++ b/packages/shadcn/src/components/email-link-auth-form.tsx @@ -46,6 +46,8 @@ export function EmailLinkAuthForm(props: EmailLinkAuthFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + mode: "onBlur", + reValidateMode: "onChange", defaultValues: { email: "", }, diff --git a/packages/shadcn/src/components/forgot-password-auth-form.tsx b/packages/shadcn/src/components/forgot-password-auth-form.tsx index 60546ce1..88219e79 100644 --- a/packages/shadcn/src/components/forgot-password-auth-form.tsx +++ b/packages/shadcn/src/components/forgot-password-auth-form.tsx @@ -43,6 +43,7 @@ export function ForgotPasswordAuthForm(props: ForgotPasswordAuthFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + reValidateMode: "onChange", defaultValues: { email: "", }, diff --git a/packages/shadcn/src/components/phone-auth-form.tsx b/packages/shadcn/src/components/phone-auth-form.tsx index 0708e7be..6f2bb29d 100644 --- a/packages/shadcn/src/components/phone-auth-form.tsx +++ b/packages/shadcn/src/components/phone-auth-form.tsx @@ -57,6 +57,7 @@ function VerifyPhoneNumberForm(props: VerifyPhoneNumberFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + reValidateMode: "onChange", defaultValues: { verificationId: props.verificationId, verificationCode: "", @@ -122,6 +123,7 @@ function PhoneNumberForm(props: PhoneNumberFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + reValidateMode: "onChange", defaultValues: { phoneNumber: "", }, diff --git a/packages/shadcn/src/components/sign-in-auth-form.tsx b/packages/shadcn/src/components/sign-in-auth-form.tsx index 76c510eb..34438ae6 100644 --- a/packages/shadcn/src/components/sign-in-auth-form.tsx +++ b/packages/shadcn/src/components/sign-in-auth-form.tsx @@ -41,6 +41,7 @@ export function SignInAuthForm(props: SignInAuthFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + reValidateMode: "onChange", defaultValues: { email: "", password: "", diff --git a/packages/shadcn/src/components/sign-up-auth-form.tsx b/packages/shadcn/src/components/sign-up-auth-form.tsx index 43107394..25d8ee56 100644 --- a/packages/shadcn/src/components/sign-up-auth-form.tsx +++ b/packages/shadcn/src/components/sign-up-auth-form.tsx @@ -43,6 +43,7 @@ export function SignUpAuthForm(props: SignUpAuthFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), + reValidateMode: "onChange", defaultValues: { email: "", password: "", diff --git a/packages/translations/src/locales/en-us.ts b/packages/translations/src/locales/en-us.ts index 2cb38358..4b49b5d9 100644 --- a/packages/translations/src/locales/en-us.ts +++ b/packages/translations/src/locales/en-us.ts @@ -28,7 +28,7 @@ export const enUS = { missingVerificationCode: "Please enter the verification code", emailAlreadyInUse: "An account already exists with this email", invalidCredential: "The provided credentials are invalid.", - weakPassword: "Password should be at least 8 characters", + weakPassword: "Password should be at least 6 characters", unverifiedEmail: "Please verify your email address to continue.", operationNotAllowed: "This operation is not allowed. Please contact support.", invalidPhoneNumber: "The phone number is invalid", From 1bac75dbbd66c2eb85c62b4bac5d83bbd1181976 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Fri, 23 Jan 2026 13:55:07 +0000 Subject: [PATCH 07/14] chore: run lint --- packages/angular/src/lib/auth/forms/email-link-auth-form.ts | 1 - .../angular/src/lib/auth/forms/forgot-password-auth-form.ts | 1 - .../src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts | 1 - .../src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts | 1 - packages/angular/src/lib/auth/forms/phone-auth-form.ts | 2 -- packages/angular/src/lib/auth/forms/sign-up-auth-form.ts | 1 - packages/shadcn/src/components/email-link-auth-form.tsx | 1 - 7 files changed, 8 deletions(-) diff --git a/packages/angular/src/lib/auth/forms/email-link-auth-form.ts b/packages/angular/src/lib/auth/forms/email-link-auth-form.ts index 64b2a339..2d1d984f 100644 --- a/packages/angular/src/lib/auth/forms/email-link-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/email-link-auth-form.ts @@ -106,7 +106,6 @@ export class EmailLinkAuthFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { diff --git a/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts b/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts index ec6002f0..da0d44ec 100644 --- a/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts @@ -114,7 +114,6 @@ export class ForgotPasswordAuthFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { diff --git a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts index 21f27f25..49b32bea 100644 --- a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts +++ b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts @@ -203,7 +203,6 @@ export class SmsMultiFactorAssertionVerifyFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), onChange: this.formSchema(), onSubmit: this.formSchema(), onSubmitAsync: async ({ value }) => { diff --git a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts index 1a05a20b..c6d6c479 100644 --- a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts +++ b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts @@ -88,7 +88,6 @@ export class TotpMultiFactorAssertionFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { diff --git a/packages/angular/src/lib/auth/forms/phone-auth-form.ts b/packages/angular/src/lib/auth/forms/phone-auth-form.ts index b0c6ac6b..88e57422 100644 --- a/packages/angular/src/lib/auth/forms/phone-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/phone-auth-form.ts @@ -110,7 +110,6 @@ export class PhoneNumberFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { const selectedCountry = countryData.find((c) => c.code === this.country()); @@ -227,7 +226,6 @@ export class VerificationFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { diff --git a/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts b/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts index 0de40548..5d455dce 100644 --- a/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts +++ b/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts @@ -125,7 +125,6 @@ export class SignUpAuthFormComponent { effect(() => { this.form.update({ validators: { - onBlur: this.formSchema(), onChange: this.formSchema(), onSubmitAsync: async ({ value }) => { try { diff --git a/packages/shadcn/src/components/email-link-auth-form.tsx b/packages/shadcn/src/components/email-link-auth-form.tsx index 7c1d4195..dfb5525d 100644 --- a/packages/shadcn/src/components/email-link-auth-form.tsx +++ b/packages/shadcn/src/components/email-link-auth-form.tsx @@ -46,7 +46,6 @@ export function EmailLinkAuthForm(props: EmailLinkAuthFormProps) { const form = useForm({ resolver: standardSchemaResolver(schema), - mode: "onBlur", reValidateMode: "onChange", defaultValues: { email: "", From a8cd454f13daeadafcae169097cb4716aaa6a505 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 29 Jan 2026 12:41:48 +0000 Subject: [PATCH 08/14] fix: onChange for angular and tests --- .../angular/src/lib/components/form.spec.ts | 30 ++++--------------- packages/angular/src/lib/components/form.ts | 29 ++++++++---------- 2 files changed, 17 insertions(+), 42 deletions(-) diff --git a/packages/angular/src/lib/components/form.spec.ts b/packages/angular/src/lib/components/form.spec.ts index 34372808..b0d50bdf 100644 --- a/packages/angular/src/lib/components/form.spec.ts +++ b/packages/angular/src/lib/components/form.spec.ts @@ -28,19 +28,13 @@ import { import { ButtonComponent } from "./button"; @Component({ - template: ``, + template: ``, standalone: true, imports: [FormMetadataComponent], }) class TestFormMetadataHostComponent { - field = signal({ - state: { - meta: { - isTouched: true, - errors: [{ message: "Test error" }], - }, - }, - } as any); + isTouched = signal(true); + errors = signal([{ message: "Test error" }]); } @Component({ @@ -90,14 +84,7 @@ describe("Form Components", () => { it("does not render error message when field has no errors", async () => { const component = await render(TestFormMetadataHostComponent); - component.fixture.componentInstance.field.set({ - state: { - meta: { - isTouched: true, - errors: [], - }, - }, - } as any); + component.fixture.componentInstance.errors.set([]); component.fixture.detectChanges(); const errorElement = screen.queryByRole("alert"); @@ -107,14 +94,7 @@ describe("Form Components", () => { it("does not render error message when field is not touched", async () => { const component = await render(TestFormMetadataHostComponent); - component.fixture.componentInstance.field.set({ - state: { - meta: { - isTouched: false, - errors: [{ message: "Test error" }], - }, - }, - } as any); + component.fixture.componentInstance.isTouched.set(false); component.fixture.detectChanges(); const errorElement = screen.queryByRole("alert"); diff --git a/packages/angular/src/lib/components/form.ts b/packages/angular/src/lib/components/form.ts index 027751cc..a0d47e1d 100644 --- a/packages/angular/src/lib/components/form.ts +++ b/packages/angular/src/lib/components/form.ts @@ -15,7 +15,7 @@ */ import { ChangeDetectorRef, Component, computed, inject, input, OnChanges, SimpleChanges } from "@angular/core"; -import { AnyFieldApi, AnyFormState, injectField } from "@tanstack/angular-form"; +import { AnyFormState, injectField } from "@tanstack/angular-form"; import { ButtonComponent } from "./button"; @Component({ @@ -25,10 +25,10 @@ import { ButtonComponent } from "./button"; style: "display: block;", }, template: ` - @if (field().state.meta.isTouched && errors().length > 0) { + @if (isTouched() && errors().length > 0) {
} @@ -37,20 +37,12 @@ import { ButtonComponent } from "./button"; /** * A component that displays form field metadata, such as validation errors. */ -export class FormMetadataComponent implements OnChanges { - private cdr = inject(ChangeDetectorRef); - /** The form field API instance. */ - field = input.required(); - errors = computed(() => - this.field() - .state.meta.errors.map((error) => error.message) - .join(", ") - ); +export class FormMetadataComponent { + isTouched = input.required(); + errors = input.required>(); - ngOnChanges(changes: SimpleChanges): void { - if (changes['field']) { - this.cdr.markForCheck(); - } + errorMessage(): string { + return this.errors().map((error) => error.message).join(", "); } } @@ -83,7 +75,10 @@ export class FormMetadataComponent implements OnChanges { /> - + `, }) From 54cb5fd0cbad8ec7f71ba402facd56767422ee52 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 29 Jan 2026 12:49:58 +0000 Subject: [PATCH 09/14] format: run format --- packages/angular/src/lib/components/form.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/angular/src/lib/components/form.ts b/packages/angular/src/lib/components/form.ts index a0d47e1d..46a264c1 100644 --- a/packages/angular/src/lib/components/form.ts +++ b/packages/angular/src/lib/components/form.ts @@ -42,7 +42,9 @@ export class FormMetadataComponent { errors = input.required>(); errorMessage(): string { - return this.errors().map((error) => error.message).join(", "); + return this.errors() + .map((error) => error.message) + .join(", "); } } From d3aa4588575295b15ada1d3c3455ec084b9affba Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 29 Jan 2026 14:04:48 +0000 Subject: [PATCH 10/14] feat: onChange on React rather than onBlur --- packages/react/src/auth/forms/email-link-auth-form.tsx | 1 - packages/react/src/auth/forms/forgot-password-auth-form.tsx | 1 - .../src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx | 1 - .../src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx | 2 -- .../src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx | 1 - .../src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx | 2 -- packages/react/src/auth/forms/phone-auth-form.tsx | 2 -- packages/react/src/auth/forms/sign-in-auth-form.tsx | 1 - packages/react/src/auth/forms/sign-up-auth-form.tsx | 1 - packages/react/src/components/form.tsx | 3 --- 10 files changed, 15 deletions(-) diff --git a/packages/react/src/auth/forms/email-link-auth-form.tsx b/packages/react/src/auth/forms/email-link-auth-form.tsx index 0778c749..064a2e97 100644 --- a/packages/react/src/auth/forms/email-link-auth-form.tsx +++ b/packages/react/src/auth/forms/email-link-auth-form.tsx @@ -71,7 +71,6 @@ export function useEmailLinkAuthForm(onSuccess?: EmailLinkAuthFormProps["onEmail email: "", }, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/forgot-password-auth-form.tsx b/packages/react/src/auth/forms/forgot-password-auth-form.tsx index f17e443c..f0e7ce0e 100644 --- a/packages/react/src/auth/forms/forgot-password-auth-form.tsx +++ b/packages/react/src/auth/forms/forgot-password-auth-form.tsx @@ -70,7 +70,6 @@ export function useForgotPasswordAuthForm(onSuccess?: ForgotPasswordAuthFormProp email: "", }, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx b/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx index 21b9c927..055c0ca4 100644 --- a/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx +++ b/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx @@ -180,7 +180,6 @@ export function useSmsMultiFactorAssertionVerifyForm({ verificationCode: "", }, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx b/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx index 24b20e49..5612663a 100644 --- a/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx +++ b/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx @@ -79,7 +79,6 @@ export function useSmsMultiFactorEnrollmentPhoneNumberForm({ phoneNumber: "", }, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { @@ -202,7 +201,6 @@ export function useMultiFactorEnrollmentVerifyPhoneNumberForm({ verificationCode: "", }, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx b/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx index f12dd212..efc9aabd 100644 --- a/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx +++ b/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx @@ -60,7 +60,6 @@ export function useTotpMultiFactorAssertionForm({ hint, onSuccess }: UseTotpMult verificationCode: "", }, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx b/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx index 23adfde4..a1ddf6aa 100644 --- a/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx +++ b/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx @@ -60,7 +60,6 @@ export function useTotpMultiFactorSecretGenerationForm({ onSuccess }: UseTotpMul displayName: "", }, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { @@ -161,7 +160,6 @@ export function useMultiFactorEnrollmentVerifyTotpForm({ verificationCode: "", }, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/phone-auth-form.tsx b/packages/react/src/auth/forms/phone-auth-form.tsx index 8e44380e..09262197 100644 --- a/packages/react/src/auth/forms/phone-auth-form.tsx +++ b/packages/react/src/auth/forms/phone-auth-form.tsx @@ -71,7 +71,6 @@ export function usePhoneNumberForm({ recaptchaVerifier, onSuccess, formatPhoneNu phoneNumber: "", }, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { @@ -184,7 +183,6 @@ export function useVerifyPhoneNumberForm({ verificationId, onSuccess }: UseVerif verificationCode: "", }, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/sign-in-auth-form.tsx b/packages/react/src/auth/forms/sign-in-auth-form.tsx index 1e912736..97cef95d 100644 --- a/packages/react/src/auth/forms/sign-in-auth-form.tsx +++ b/packages/react/src/auth/forms/sign-in-auth-form.tsx @@ -74,7 +74,6 @@ export function useSignInAuthForm(onSuccess?: SignInAuthFormProps["onSignIn"]) { password: "", }, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/auth/forms/sign-up-auth-form.tsx b/packages/react/src/auth/forms/sign-up-auth-form.tsx index 358a7c25..74f5e240 100644 --- a/packages/react/src/auth/forms/sign-up-auth-form.tsx +++ b/packages/react/src/auth/forms/sign-up-auth-form.tsx @@ -85,7 +85,6 @@ export function useSignUpAuthForm(onSuccess?: SignUpAuthFormProps["onSignUp"]) { displayName: requireDisplayName ? "" : undefined, } as z.infer, validators: { - onBlur: schema, onChange: schema, onSubmitAsync: async ({ value }) => { try { diff --git a/packages/react/src/components/form.tsx b/packages/react/src/components/form.tsx index d812b34f..b17ac050 100644 --- a/packages/react/src/components/form.tsx +++ b/packages/react/src/components/form.tsx @@ -61,9 +61,6 @@ function Input({ id={field.name} name={field.name} value={field.state.value} - onBlur={() => { - field.handleBlur(); - }} onChange={(e) => { field.handleChange(e.target.value); // Clear form-level submission errors when user starts typing From 8ddb5d31c2ee8e47477c68963acb4b56e94b6881 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 29 Jan 2026 14:37:56 +0000 Subject: [PATCH 11/14] chore: remove onBlur as not needed --- packages/angular/src/lib/components/form.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/angular/src/lib/components/form.ts b/packages/angular/src/lib/components/form.ts index 46a264c1..df389559 100644 --- a/packages/angular/src/lib/components/form.ts +++ b/packages/angular/src/lib/components/form.ts @@ -71,7 +71,6 @@ export class FormMetadataComponent { [id]="field.api.name" [name]="field.api.name" [value]="field.api.state.value" - (blur)="handleBlur()" (input)="field.api.handleChange($any($event).target.value)" [type]="type()" /> @@ -102,10 +101,6 @@ export class FormInputComponent implements OnChanges { this.cdr.markForCheck(); } - handleBlur() { - this.field.api.handleBlur(); - this.cdr.markForCheck(); - } } @Component({ From e4435be0b035dafae34b74857a55c4949b63d382 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 29 Jan 2026 14:41:11 +0000 Subject: [PATCH 12/14] format: run prettier --- packages/angular/src/lib/components/form.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/angular/src/lib/components/form.ts b/packages/angular/src/lib/components/form.ts index df389559..d9db7406 100644 --- a/packages/angular/src/lib/components/form.ts +++ b/packages/angular/src/lib/components/form.ts @@ -100,7 +100,6 @@ export class FormInputComponent implements OnChanges { // Trigger change detection when any input changes this.cdr.markForCheck(); } - } @Component({ From f0712e9fb56a163421d02bd498dfe776bf00c567 Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Thu, 29 Jan 2026 14:55:46 +0000 Subject: [PATCH 13/14] fix: tests --- .../react/src/auth/forms/email-link-auth-form.test.tsx | 6 +++--- .../src/auth/forms/forgot-password-auth-form.test.tsx | 4 ++-- packages/react/src/auth/forms/phone-auth-form.test.tsx | 5 +++-- packages/react/src/auth/forms/sign-in-auth-form.test.tsx | 4 ++-- packages/react/src/auth/forms/sign-up-auth-form.test.tsx | 9 +++++---- packages/react/src/components/form.tsx | 4 ++-- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/react/src/auth/forms/email-link-auth-form.test.tsx b/packages/react/src/auth/forms/email-link-auth-form.test.tsx index 4e358cc6..6181be94 100644 --- a/packages/react/src/auth/forms/email-link-auth-form.test.tsx +++ b/packages/react/src/auth/forms/email-link-auth-form.test.tsx @@ -292,7 +292,7 @@ describe("", () => { expect(onSignInMock).not.toHaveBeenCalled(); }); - it("should trigger validation errors when the form is blurred", () => { + it("should trigger validation errors when the form changes", async () => { const mockUI = createMockUI(); const { container } = render( @@ -307,9 +307,9 @@ describe("", () => { const input = screen.getByRole("textbox", { name: /email/i }); act(() => { - fireEvent.blur(input); + fireEvent.change(input, { target: { value: "invalid" } }); }); - expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument(); + expect(await screen.findByText("Please enter a valid email address")).toBeInTheDocument(); }); }); diff --git a/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx b/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx index 3fe18a8f..ca49549e 100644 --- a/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx +++ b/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx @@ -198,7 +198,7 @@ describe("", () => { expect(onBackToSignInClickMock).toHaveBeenCalled(); }); - it("should trigger validation errors when the form is blurred", () => { + it("should trigger validation errors when the form changes", () => { const mockUI = createMockUI(); const { container } = render( @@ -213,7 +213,7 @@ describe("", () => { const input = screen.getByRole("textbox", { name: /email/i }); act(() => { - fireEvent.blur(input); + fireEvent.change(input, { target: { value: "invalid" } }); }); expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument(); diff --git a/packages/react/src/auth/forms/phone-auth-form.test.tsx b/packages/react/src/auth/forms/phone-auth-form.test.tsx index 7bd9d6fb..ea8580bf 100644 --- a/packages/react/src/auth/forms/phone-auth-form.test.tsx +++ b/packages/react/src/auth/forms/phone-auth-form.test.tsx @@ -479,7 +479,7 @@ describe("", () => { expect(sendCodeButton).toHaveAttribute("type", "submit"); }); - it("should trigger validation errors when the form is blurred", () => { + it("should trigger validation errors when the form changes", () => { const mockUI = createMockUI(); const { container } = render( @@ -494,7 +494,8 @@ describe("", () => { const input = screen.getByRole("textbox", { name: /phone number/i }); act(() => { - fireEvent.blur(input); + fireEvent.change(input, { target: { value: "1" } }); + fireEvent.change(input, { target: { value: "" } }); }); expect(screen.getByText("Please provide a phone number")).toBeInTheDocument(); diff --git a/packages/react/src/auth/forms/sign-in-auth-form.test.tsx b/packages/react/src/auth/forms/sign-in-auth-form.test.tsx index fff23639..57fe6bd4 100644 --- a/packages/react/src/auth/forms/sign-in-auth-form.test.tsx +++ b/packages/react/src/auth/forms/sign-in-auth-form.test.tsx @@ -262,7 +262,7 @@ describe("", () => { expect(onSignUpClick).toHaveBeenCalled(); }); - it("should trigger validation errors when the form is blurred", () => { + it("should trigger validation errors when the form changes", () => { const mockUI = createMockUI(); const { container } = render( @@ -277,7 +277,7 @@ describe("", () => { const input = screen.getByRole("textbox", { name: /email/i }); act(() => { - fireEvent.blur(input); + fireEvent.change(input, { target: { value: "invalid" } }); }); expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument(); diff --git a/packages/react/src/auth/forms/sign-up-auth-form.test.tsx b/packages/react/src/auth/forms/sign-up-auth-form.test.tsx index 0d3cdf53..049a6dcd 100644 --- a/packages/react/src/auth/forms/sign-up-auth-form.test.tsx +++ b/packages/react/src/auth/forms/sign-up-auth-form.test.tsx @@ -315,7 +315,7 @@ describe("", () => { expect(onSignInClickMock).toHaveBeenCalled(); }); - it("should trigger validation errors when the form is blurred", () => { + it("should trigger validation errors when the form changes", () => { const mockUI = createMockUI(); const { container } = render( @@ -330,7 +330,7 @@ describe("", () => { const input = screen.getByRole("textbox", { name: /email/i }); act(() => { - fireEvent.blur(input); + fireEvent.change(input, { target: { value: "invalid" } }); }); expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument(); @@ -401,7 +401,7 @@ describe("", () => { expect(screen.queryByRole("textbox", { name: /displayName/ })).not.toBeInTheDocument(); }); - it("should trigger displayName validation errors when the form is blurred and requireDisplayName is enabled", () => { + it("should trigger displayName validation errors when the form changes and requireDisplayName is enabled", () => { const mockUI = createMockUI({ locale: registerLocale("test", { errors: { @@ -431,7 +431,8 @@ describe("", () => { expect(displayNameInput).toBeInTheDocument(); act(() => { - fireEvent.blur(displayNameInput); + fireEvent.change(displayNameInput, { target: { value: "a" } }); + fireEvent.change(displayNameInput, { target: { value: "" } }); }); expect(screen.getByText("Please provide a display name")).toBeInTheDocument(); diff --git a/packages/react/src/components/form.tsx b/packages/react/src/components/form.tsx index b17ac050..603c8371 100644 --- a/packages/react/src/components/form.tsx +++ b/packages/react/src/components/form.tsx @@ -22,7 +22,7 @@ import { cn } from "~/utils/cn"; const { fieldContext, useFieldContext, formContext, useFormContext } = createFormHookContexts(); function FieldMetadata({ className, ...props }: ComponentProps<"div"> & { field: AnyFieldApi }) { - if (!props.field.state.meta.isTouched || !props.field.state.meta.errors.length) { + if (!props.field.state.meta.errors.length) { return null; } @@ -57,7 +57,7 @@ function Input({ {before} 0} + aria-invalid={field.state.meta.errors.length > 0} id={field.name} name={field.name} value={field.state.value} From 4ccb5301eeb2801c2fa29a7f8c5a35d7a0d97fcc Mon Sep 17 00:00:00 2001 From: MichaelVerdon Date: Wed, 4 Feb 2026 15:36:15 +0000 Subject: [PATCH 14/14] chore: remove --- .../react/src/components/logos/yahoo/Logo.tsx | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 packages/react/src/components/logos/yahoo/Logo.tsx diff --git a/packages/react/src/components/logos/yahoo/Logo.tsx b/packages/react/src/components/logos/yahoo/Logo.tsx deleted file mode 100644 index 6ebabbfa..00000000 --- a/packages/react/src/components/logos/yahoo/Logo.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { SVGProps } from "react"; -const SvgLogo = (props: SVGProps) => ( - - - - -); -export default SvgLogo;