diff --git a/examples/react/src/firebase/config.ts b/examples/react/src/firebase/config.ts index 2d95ca07..90abb862 100644 --- a/examples/react/src/firebase/config.ts +++ b/examples/react/src/firebase/config.ts @@ -15,5 +15,10 @@ */ export const firebaseConfig = { - // your Firebase config here + apiKey: "AIzaSyCvMftIUCD9lUQ3BzIrimfSfBbCUQYZf-I", + authDomain: "fir-ui-rework.firebaseapp.com", + projectId: "fir-ui-rework", + storageBucket: "fir-ui-rework.firebasestorage.app", + messagingSenderId: "200312857118", + appId: "1:200312857118:web:94e3f69b0e0a4a863f040f", }; diff --git a/examples/react/src/index.css b/examples/react/src/index.css index d2a6e9fa..f265e7fc 100644 --- a/examples/react/src/index.css +++ b/examples/react/src/index.css @@ -18,4 +18,4 @@ @import "@firebase-ui/styles/tailwind"; /* @import "@firebase-ui/styles/src/themes/dark.css"; */ -/* @import "@firebase-ui/styles/src/themes/brutalist.css"; */ +/* @import "@firebase-ui/styles/src/themes/brutalist.css"; */ \ No newline at end of file diff --git a/examples/react/src/screens/oauth-screen.tsx b/examples/react/src/screens/oauth-screen.tsx index 662ccecd..dc57aeb6 100644 --- a/examples/react/src/screens/oauth-screen.tsx +++ b/examples/react/src/screens/oauth-screen.tsx @@ -16,12 +16,25 @@ "use client"; -import { GoogleSignInButton, OAuthScreen } from "@firebase-ui/react"; +import { + FacebookSignInButton, + AppleSignInButton, + GitHubSignInButton, + GoogleSignInButton, + MicrosoftSignInButton, + OAuthScreen, + TwitterSignInButton, +} from "@firebase-ui/react"; export default function OAuthScreenPage() { return ( - + + + + + + ); } 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 d557867a..42e5c213 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 @@ -16,7 +16,15 @@ "use client"; -import { GoogleSignInButton, SignInAuthScreen } from "@firebase-ui/react"; +import { + AppleSignInButton, + GoogleSignInButton, + SignInAuthScreen, + FacebookSignInButton, + GitHubSignInButton, + MicrosoftSignInButton, + TwitterSignInButton, +} from "@firebase-ui/react"; import { useNavigate } from "react-router"; export default function SignInAuthScreenWithOAuthPage() { @@ -27,7 +35,14 @@ export default function SignInAuthScreenWithOAuthPage() { onForgotPasswordClick={() => navigate("/password-reset-screen")} onRegisterClick={() => navigate("/sign-up-auth-screen")} > - +
+ + + + + + +
); } diff --git a/packages/core/brands/apple/logo.svg b/packages/core/brands/apple/logo.svg new file mode 100644 index 00000000..f08dbc70 --- /dev/null +++ b/packages/core/brands/apple/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/brands/facebook/logo.svg b/packages/core/brands/facebook/logo.svg new file mode 100644 index 00000000..6dbfa09e --- /dev/null +++ b/packages/core/brands/facebook/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/brands/github/logo.svg b/packages/core/brands/github/logo.svg new file mode 100644 index 00000000..6d487e64 --- /dev/null +++ b/packages/core/brands/github/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/brands/google/logo.svg b/packages/core/brands/google/logo.svg new file mode 100644 index 00000000..c0669b38 --- /dev/null +++ b/packages/core/brands/google/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/brands/line/logo.svg b/packages/core/brands/line/logo.svg new file mode 100644 index 00000000..cc69bb5f --- /dev/null +++ b/packages/core/brands/line/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/brands/microsoft/logo.svg b/packages/core/brands/microsoft/logo.svg new file mode 100644 index 00000000..23a77fb5 --- /dev/null +++ b/packages/core/brands/microsoft/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/brands/snapchat/logo.svg b/packages/core/brands/snapchat/logo.svg new file mode 100644 index 00000000..04cd82e2 --- /dev/null +++ b/packages/core/brands/snapchat/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/brands/twitter/logo.svg b/packages/core/brands/twitter/logo.svg new file mode 100644 index 00000000..a21afdb4 --- /dev/null +++ b/packages/core/brands/twitter/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index 3312a1c7..77be4369 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -11,10 +11,12 @@ "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" - } + }, + "./brands/*": "./brands/*" }, "files": [ - "dist" + "dist", + "brands" ], "scripts": { "prepare": "pnpm run build", diff --git a/packages/react/package.json b/packages/react/package.json index 1955caa5..8908f2d3 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -17,8 +17,9 @@ ], "scripts": { "prepare": "pnpm run build", - "build": "tsup", + "build": "tsup && pnpm run build:logos", "build:local": "pnpm run build && pnpm pack", + "build:logos": "pnpm dlx @svgr/cli --icon --typescript --no-index --jsx-runtime automatic --out-dir src/components/logos ../core/brands", "dev": "tsup --watch", "lint": "eslint . --ext .ts,.tsx", "lint:fix": "eslint . --ext .ts,.tsx --fix", @@ -58,13 +59,14 @@ "@types/react-dom": "catalog:", "@vitejs/plugin-react": "catalog:", "firebase": "catalog:", - "nanostores": "catalog:", "jsdom": "catalog:", + "nanostores": "catalog:", "react": "catalog:", "react-dom": "catalog:", "tsup": "catalog:", "typescript": "catalog:", "vite": "catalog:", + "vite-plugin-svgr": "^4.5.0", "vitest": "catalog:" } } diff --git a/packages/react/src/auth/index.ts b/packages/react/src/auth/index.ts index 19df3de6..d3b32cbe 100644 --- a/packages/react/src/auth/index.ts +++ b/packages/react/src/auth/index.ts @@ -53,5 +53,14 @@ export { PhoneAuthScreen, type PhoneAuthScreenProps } from "./screens/phone-auth export { SignInAuthScreen, type SignInAuthScreenProps } from "./screens/sign-in-auth-screen"; export { SignUpAuthScreen, type SignUpAuthScreenProps } from "./screens/sign-up-auth-screen"; -export { GoogleSignInButton, GoogleIcon, type GoogleSignInButtonProps } from "./oauth/google-sign-in-button"; +export { AppleSignInButton, AppleLogo, type AppleSignInButtonProps } from "./oauth/apple-sign-in-button"; +export { FacebookSignInButton, FacebookLogo, type FacebookSignInButtonProps } from "./oauth/facebook-sign-in-button"; +export { GitHubSignInButton, GitHubLogo, type GitHubSignInButtonProps } from "./oauth/github-sign-in-button"; +export { GoogleSignInButton, GoogleLogo, type GoogleSignInButtonProps } from "./oauth/google-sign-in-button"; +export { + MicrosoftSignInButton, + MicrosoftLogo, + type MicrosoftSignInButtonProps, +} from "./oauth/microsoft-sign-in-button"; +export { TwitterSignInButton, TwitterLogo, type TwitterSignInButtonProps } from "./oauth/twitter-sign-in-button"; export { OAuthButton, type OAuthButtonProps } from "./oauth/oauth-button"; diff --git a/packages/react/src/auth/oauth/apple-sign-in-button.test.tsx b/packages/react/src/auth/oauth/apple-sign-in-button.test.tsx new file mode 100644 index 00000000..8119cf82 --- /dev/null +++ b/packages/react/src/auth/oauth/apple-sign-in-button.test.tsx @@ -0,0 +1,186 @@ +/** + * 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. + */ + +import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; +import { render, screen, cleanup } from "@testing-library/react"; +import { AppleLogo, AppleSignInButton } from "./apple-sign-in-button"; +import { CreateFirebaseUIProvider, createMockUI } from "~/tests/utils"; +import { registerLocale } from "@firebase-ui/translations"; +import { OAuthProvider } from "firebase/auth"; + +vi.mock("firebase/auth", () => ({ + OAuthProvider: class OAuthProvider { + constructor(providerId: string) { + this.providerId = providerId; + } + providerId: string; + }, +})); + +afterEach(() => { + cleanup(); +}); + +describe("", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders with the correct provider", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithApple: "Sign in with Apple", + }, + }), + }); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("apple.com"); + }); + + it("renders with custom provider when provided", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithApple: "Sign in with Apple", + }, + }), + }); + + const customProvider = new OAuthProvider("custom.apple.com"); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("custom.apple.com"); + }); + + it("renders with the Apple icon", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithApple: "Sign in with Apple", + }, + }), + }); + + render( + + + + ); + + const svg = document.querySelector(".fui-provider__icon"); + expect(svg).toBeDefined(); + expect(svg).toHaveClass("fui-provider__icon"); + expect(svg?.tagName.toLowerCase()).toBe("svg"); + }); + + it("renders with the correct translated text", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithApple: "Sign in with Apple", + }, + }), + }); + + render( + + + + ); + + expect(screen.getByText("Sign in with Apple")).toBeDefined(); + }); + + it("renders with different translated text for different locales", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithApple: "Iniciar sesión con Apple", + }, + }), + }); + + render( + + + + ); + + expect(screen.getByText("Iniciar sesión con Apple")).toBeDefined(); + }); + + it("renders as a button with correct classes", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithApple: "Sign in with Apple", + }, + }), + }); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toHaveClass("fui-provider__button"); + expect(button.getAttribute("type")).toBe("button"); + }); +}); + +describe("", () => { + it("renders as an SVG element", () => { + const { container } = render(); + const svg = container.querySelector("svg"); + + expect(svg).toBeDefined(); + expect(svg?.tagName.toLowerCase()).toBe("svg"); + }); + + it("has the correct CSS class", () => { + const { container } = render(); + const svg = container.querySelector("svg"); + + expect(svg).toHaveClass("fui-provider__icon"); + }); + + it("forwards custom SVG props", () => { + const { container } = render(); + const svg = container.querySelector('svg[data-testid="custom-svg"]'); + + expect(svg).toBeDefined(); + expect(svg!.getAttribute("width")).toBe("32"); + expect(svg).toHaveClass("fui-provider__icon"); + expect(svg).toHaveClass("foo"); + }); +}); diff --git a/packages/react/src/auth/oauth/apple-sign-in-button.tsx b/packages/react/src/auth/oauth/apple-sign-in-button.tsx new file mode 100644 index 00000000..1320b04c --- /dev/null +++ b/packages/react/src/auth/oauth/apple-sign-in-button.tsx @@ -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. + */ + +"use client"; + +import { getTranslation } from "@firebase-ui/core"; +import { OAuthProvider } from "firebase/auth"; +import { useUI } from "~/hooks"; +import { OAuthButton } from "./oauth-button"; +import AppleSvgLogo from "~/components/logos/apple/Logo"; +import { cn } from "~/utils/cn"; + +export type AppleSignInButtonProps = { + provider?: OAuthProvider; + themed?: boolean; +}; + +export function AppleSignInButton({ provider, themed }: AppleSignInButtonProps) { + const ui = useUI(); + + return ( + + + {getTranslation(ui, "labels", "signInWithApple")} + + ); +} + +export function AppleLogo({ className, ...props }: React.SVGProps) { + return ; +} diff --git a/packages/react/src/auth/oauth/facebook-sign-in-button.test.tsx b/packages/react/src/auth/oauth/facebook-sign-in-button.test.tsx new file mode 100644 index 00000000..d3c94fbe --- /dev/null +++ b/packages/react/src/auth/oauth/facebook-sign-in-button.test.tsx @@ -0,0 +1,187 @@ +/** + * 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. + */ + +import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; +import { render, screen, cleanup } from "@testing-library/react"; +import { FacebookLogo, FacebookSignInButton } from "./facebook-sign-in-button"; +import { CreateFirebaseUIProvider, createMockUI } from "~/tests/utils"; +import { registerLocale } from "@firebase-ui/translations"; + +vi.mock("firebase/auth", () => ({ + FacebookAuthProvider: class FacebookAuthProvider { + constructor() { + this.providerId = "facebook.com"; + } + providerId: string; + }, +})); + +afterEach(() => { + cleanup(); +}); + +describe("", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders with the correct provider", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithFacebook: "Sign in with Facebook", + }, + }), + }); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("facebook.com"); + }); + + it("renders with custom provider when provided", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithFacebook: "Sign in with Facebook", + }, + }), + }); + + const customProvider = new (class CustomFacebookProvider { + providerId = "custom.facebook.com"; + })() as any; + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("custom.facebook.com"); + }); + + it("renders with the Facebook icon", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithFacebook: "Sign in with Facebook", + }, + }), + }); + + render( + + + + ); + + const svg = document.querySelector(".fui-provider__icon"); + expect(svg).toBeDefined(); + expect(svg).toHaveClass("fui-provider__icon"); + expect(svg?.tagName.toLowerCase()).toBe("svg"); + }); + + it("renders with the correct translated text", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithFacebook: "Sign in with Facebook", + }, + }), + }); + + render( + + + + ); + + expect(screen.getByText("Sign in with Facebook")).toBeDefined(); + }); + + it("renders with different translated text for different locales", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithFacebook: "Iniciar sesión con Facebook", + }, + }), + }); + + render( + + + + ); + + expect(screen.getByText("Iniciar sesión con Facebook")).toBeDefined(); + }); + + it("renders as a button with correct classes", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithFacebook: "Sign in with Facebook", + }, + }), + }); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toHaveClass("fui-provider__button"); + expect(button.getAttribute("type")).toBe("button"); + }); +}); + +describe("", () => { + it("renders as an SVG element", () => { + const { container } = render(); + const svg = container.querySelector("svg"); + + expect(svg).toBeDefined(); + expect(svg?.tagName.toLowerCase()).toBe("svg"); + }); + + it("has the correct CSS class", () => { + const { container } = render(); + const svg = container.querySelector("svg"); + + expect(svg).toHaveClass("fui-provider__icon"); + }); + + it("forwards custom SVG props", () => { + const { container } = render(); + const svg = container.querySelector('svg[data-testid="custom-svg"]'); + + expect(svg).toBeDefined(); + expect(svg!.getAttribute("width")).toBe("32"); + expect(svg).toHaveClass("fui-provider__icon"); + expect(svg).toHaveClass("foo"); + }); +}); diff --git a/packages/react/src/auth/oauth/facebook-sign-in-button.tsx b/packages/react/src/auth/oauth/facebook-sign-in-button.tsx new file mode 100644 index 00000000..3065e33e --- /dev/null +++ b/packages/react/src/auth/oauth/facebook-sign-in-button.tsx @@ -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. + */ + +"use client"; + +import { getTranslation } from "@firebase-ui/core"; +import { FacebookAuthProvider } from "firebase/auth"; +import { useUI } from "~/hooks"; +import { OAuthButton } from "./oauth-button"; +import FacebookSvgLogo from "~/components/logos/facebook/Logo"; +import { cn } from "~/utils/cn"; + +export type FacebookSignInButtonProps = { + provider?: FacebookAuthProvider; + themed?: boolean; +}; + +export function FacebookSignInButton({ provider, themed }: FacebookSignInButtonProps) { + const ui = useUI(); + + return ( + + + {getTranslation(ui, "labels", "signInWithFacebook")} + + ); +} + +export function FacebookLogo({ className, ...props }: React.SVGProps) { + return ; +} diff --git a/packages/react/src/auth/oauth/github-sign-in-button.test.tsx b/packages/react/src/auth/oauth/github-sign-in-button.test.tsx new file mode 100644 index 00000000..11352246 --- /dev/null +++ b/packages/react/src/auth/oauth/github-sign-in-button.test.tsx @@ -0,0 +1,187 @@ +/** + * 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. + */ + +import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; +import { render, screen, cleanup } from "@testing-library/react"; +import { GitHubLogo, GitHubSignInButton } from "./github-sign-in-button"; +import { CreateFirebaseUIProvider, createMockUI } from "~/tests/utils"; +import { registerLocale } from "@firebase-ui/translations"; + +vi.mock("firebase/auth", () => ({ + GithubAuthProvider: class GithubAuthProvider { + constructor() { + this.providerId = "github.com"; + } + providerId: string; + }, +})); + +afterEach(() => { + cleanup(); +}); + +describe("", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders with the correct provider", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithGitHub: "Sign in with GitHub", + }, + }), + }); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("github.com"); + }); + + it("renders with custom provider when provided", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithGitHub: "Sign in with GitHub", + }, + }), + }); + + const customProvider = new (class CustomGitHubProvider { + providerId = "custom.github.com"; + })() as any; + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("custom.github.com"); + }); + + it("renders with the GitHub icon", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithGitHub: "Sign in with GitHub", + }, + }), + }); + + render( + + + + ); + + const svg = document.querySelector(".fui-provider__icon"); + expect(svg).toBeDefined(); + expect(svg).toHaveClass("fui-provider__icon"); + expect(svg?.tagName.toLowerCase()).toBe("svg"); + }); + + it("renders with the correct translated text", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithGitHub: "Sign in with GitHub", + }, + }), + }); + + render( + + + + ); + + expect(screen.getByText("Sign in with GitHub")).toBeDefined(); + }); + + it("renders with different translated text for different locales", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithGitHub: "Iniciar sesión con GitHub", + }, + }), + }); + + render( + + + + ); + + expect(screen.getByText("Iniciar sesión con GitHub")).toBeDefined(); + }); + + it("renders as a button with correct classes", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithGitHub: "Sign in with GitHub", + }, + }), + }); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toHaveClass("fui-provider__button"); + expect(button.getAttribute("type")).toBe("button"); + }); +}); + +describe("", () => { + it("renders as an SVG element", () => { + const { container } = render(); + const svg = container.querySelector("svg"); + + expect(svg).toBeDefined(); + expect(svg?.tagName.toLowerCase()).toBe("svg"); + }); + + it("has the correct CSS class", () => { + const { container } = render(); + const svg = container.querySelector("svg"); + + expect(svg).toHaveClass("fui-provider__icon"); + }); + + it("forwards custom SVG props", () => { + const { container } = render(); + const svg = container.querySelector('svg[data-testid="custom-svg"]'); + + expect(svg).toBeDefined(); + expect(svg!.getAttribute("width")).toBe("32"); + expect(svg).toHaveClass("fui-provider__icon"); + expect(svg).toHaveClass("foo"); + }); +}); diff --git a/packages/react/src/auth/oauth/github-sign-in-button.tsx b/packages/react/src/auth/oauth/github-sign-in-button.tsx new file mode 100644 index 00000000..0603f1d0 --- /dev/null +++ b/packages/react/src/auth/oauth/github-sign-in-button.tsx @@ -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. + */ + +"use client"; + +import { getTranslation } from "@firebase-ui/core"; +import { GithubAuthProvider } from "firebase/auth"; +import { useUI } from "~/hooks"; +import { OAuthButton } from "./oauth-button"; +import GitHubSvgLogo from "~/components/logos/github/Logo"; +import { cn } from "~/utils/cn"; + +export type GitHubSignInButtonProps = { + provider?: GithubAuthProvider; + themed?: boolean; +}; + +export function GitHubSignInButton({ provider, themed }: GitHubSignInButtonProps) { + const ui = useUI(); + + return ( + + + {getTranslation(ui, "labels", "signInWithGitHub")} + + ); +} + +export function GitHubLogo({ className, ...props }: React.SVGProps) { + return ; +} diff --git a/packages/react/src/auth/oauth/google-sign-in-button.test.tsx b/packages/react/src/auth/oauth/google-sign-in-button.test.tsx index bf90018d..12adcf70 100644 --- a/packages/react/src/auth/oauth/google-sign-in-button.test.tsx +++ b/packages/react/src/auth/oauth/google-sign-in-button.test.tsx @@ -15,26 +15,16 @@ import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; import { render, screen, cleanup } from "@testing-library/react"; -import { GoogleIcon, GoogleSignInButton } from "./google-sign-in-button"; +import { GoogleLogo, GoogleSignInButton } from "./google-sign-in-button"; import { CreateFirebaseUIProvider, createMockUI } from "~/tests/utils"; import { registerLocale } from "@firebase-ui/translations"; -import { ComponentProps } from "react"; - -// Mock the OAuthButton component -vi.mock("./oauth-button", () => ({ - OAuthButton: ({ children, provider }: ComponentProps<"div"> & { provider: any }) => ( -
- {children} -
- ), -})); -// Mock the GoogleAuthProvider vi.mock("firebase/auth", () => ({ GoogleAuthProvider: class GoogleAuthProvider { constructor() { - // Empty constructor + this.providerId = "google.com"; } + providerId: string; }, })); @@ -62,9 +52,9 @@ describe("", () => { ); - const oauthButton = screen.getByTestId("oauth-button"); - expect(oauthButton).toBeDefined(); - expect(oauthButton.getAttribute("data-provider")).toBe("GoogleAuthProvider"); + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("google.com"); }); it("renders with custom provider when provided", () => { @@ -77,20 +67,18 @@ describe("", () => { }); const customProvider = new (class CustomGoogleProvider { - constructor() { - // Empty constructor - } - })(); + providerId = "custom.google.com"; + })() as any; render( - + ); - const oauthButton = screen.getByTestId("oauth-button"); - expect(oauthButton).toBeDefined(); - expect(oauthButton.getAttribute("data-provider")).toBe("CustomGoogleProvider"); + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("custom.google.com"); }); it("renders with the Google icon", () => { @@ -150,7 +138,7 @@ describe("", () => { expect(screen.getByText("Iniciar sesión con Google")).toBeDefined(); }); - it("passes children to OAuthButton", () => { + it("renders as a button with correct classes", () => { const ui = createMockUI({ locale: registerLocale("test", { labels: { @@ -165,21 +153,15 @@ describe("", () => { ); - const oauthButton = screen.getByTestId("oauth-button"); - expect(oauthButton).toBeDefined(); - - const svg = oauthButton.querySelector(".fui-provider__icon"); - const text = oauthButton.querySelector("span"); - - expect(svg).toBeDefined(); - expect(text).toBeDefined(); - expect(text?.textContent).toBe("Sign in with Google"); + const button = screen.getByRole("button"); + expect(button).toHaveClass("fui-provider__button"); + expect(button.getAttribute("type")).toBe("button"); }); }); -describe("", () => { +describe("", () => { it("renders as an SVG element", () => { - const { container } = render(); + const { container } = render(); const svg = container.querySelector("svg"); expect(svg).toBeDefined(); @@ -187,16 +169,26 @@ describe("", () => { }); it("has the correct CSS class", () => { - const { container } = render(); + const { container } = render(); const svg = container.querySelector("svg"); expect(svg).toHaveClass("fui-provider__icon"); }); it("has the correct viewBox attribute", () => { - const { container } = render(); + const { container } = render(); const svg = container.querySelector("svg"); expect(svg?.getAttribute("viewBox")).toBe("0 0 48 48"); }); + + it("forwards custom SVG props", () => { + const { container } = render(); + const svg = container.querySelector('svg[data-testid="custom-svg"]'); + + expect(svg).toBeDefined(); + expect(svg!.getAttribute("width")).toBe("32"); + expect(svg).toHaveClass("fui-provider__icon"); + expect(svg).toHaveClass("foo"); + }); }); diff --git a/packages/react/src/auth/oauth/google-sign-in-button.tsx b/packages/react/src/auth/oauth/google-sign-in-button.tsx index 551a416b..45c03f21 100644 --- a/packages/react/src/auth/oauth/google-sign-in-button.tsx +++ b/packages/react/src/auth/oauth/google-sign-in-button.tsx @@ -20,41 +20,25 @@ import { getTranslation } from "@firebase-ui/core"; import { GoogleAuthProvider } from "firebase/auth"; import { useUI } from "~/hooks"; import { OAuthButton } from "./oauth-button"; +import GoogleSvgLogo from "~/components/logos/google/Logo"; +import { cn } from "~/utils/cn"; export type GoogleSignInButtonProps = { provider?: GoogleAuthProvider; + themed?: boolean | "neutral"; }; -export function GoogleSignInButton({ provider }: GoogleSignInButtonProps) { +export function GoogleSignInButton({ provider, themed }: GoogleSignInButtonProps) { const ui = useUI(); return ( - - + + {getTranslation(ui, "labels", "signInWithGoogle")} ); } -export function GoogleIcon() { - return ( - - - - - - - ); +export function GoogleLogo({ className, ...props }: React.SVGProps) { + return ; } diff --git a/packages/react/src/auth/oauth/microsoft-sign-in-button.test.tsx b/packages/react/src/auth/oauth/microsoft-sign-in-button.test.tsx new file mode 100644 index 00000000..78239903 --- /dev/null +++ b/packages/react/src/auth/oauth/microsoft-sign-in-button.test.tsx @@ -0,0 +1,186 @@ +/** + * 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. + */ + +import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; +import { render, screen, cleanup } from "@testing-library/react"; +import { MicrosoftLogo, MicrosoftSignInButton } from "./microsoft-sign-in-button"; +import { CreateFirebaseUIProvider, createMockUI } from "~/tests/utils"; +import { registerLocale } from "@firebase-ui/translations"; +import { OAuthProvider } from "firebase/auth"; + +vi.mock("firebase/auth", () => ({ + OAuthProvider: class OAuthProvider { + constructor(providerId: string) { + this.providerId = providerId; + } + providerId: string; + }, +})); + +afterEach(() => { + cleanup(); +}); + +describe("", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders with the correct provider", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithMicrosoft: "Sign in with Microsoft", + }, + }), + }); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("microsoft.com"); + }); + + it("renders with custom provider when provided", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithMicrosoft: "Sign in with Microsoft", + }, + }), + }); + + const customProvider = new OAuthProvider("custom.microsoft.com"); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("custom.microsoft.com"); + }); + + it("renders with the Microsoft icon", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithMicrosoft: "Sign in with Microsoft", + }, + }), + }); + + render( + + + + ); + + const svg = document.querySelector(".fui-provider__icon"); + expect(svg).toBeDefined(); + expect(svg).toHaveClass("fui-provider__icon"); + expect(svg?.tagName.toLowerCase()).toBe("svg"); + }); + + it("renders with the correct translated text", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithMicrosoft: "Sign in with Microsoft", + }, + }), + }); + + render( + + + + ); + + expect(screen.getByText("Sign in with Microsoft")).toBeDefined(); + }); + + it("renders with different translated text for different locales", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithMicrosoft: "Iniciar sesión con Microsoft", + }, + }), + }); + + render( + + + + ); + + expect(screen.getByText("Iniciar sesión con Microsoft")).toBeDefined(); + }); + + it("renders as a button with correct classes", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithMicrosoft: "Sign in with Microsoft", + }, + }), + }); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toHaveClass("fui-provider__button"); + expect(button.getAttribute("type")).toBe("button"); + }); +}); + +describe("", () => { + it("renders as an SVG element", () => { + const { container } = render(); + const svg = container.querySelector("svg"); + + expect(svg).toBeDefined(); + expect(svg?.tagName.toLowerCase()).toBe("svg"); + }); + + it("has the correct CSS class", () => { + const { container } = render(); + const svg = container.querySelector("svg"); + + expect(svg).toHaveClass("fui-provider__icon"); + }); + + it("forwards custom SVG props", () => { + const { container } = render(); + const svg = container.querySelector('svg[data-testid="custom-svg"]'); + + expect(svg).toBeDefined(); + expect(svg!.getAttribute("width")).toBe("32"); + expect(svg).toHaveClass("fui-provider__icon"); + expect(svg).toHaveClass("foo"); + }); +}); diff --git a/packages/react/src/auth/oauth/microsoft-sign-in-button.tsx b/packages/react/src/auth/oauth/microsoft-sign-in-button.tsx new file mode 100644 index 00000000..7bae44d7 --- /dev/null +++ b/packages/react/src/auth/oauth/microsoft-sign-in-button.tsx @@ -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. + */ + +"use client"; + +import { getTranslation } from "@firebase-ui/core"; +import { OAuthProvider } from "firebase/auth"; +import { useUI } from "~/hooks"; +import { OAuthButton } from "./oauth-button"; +import MicrosoftSvgLogo from "~/components/logos/microsoft/Logo"; +import { cn } from "~/utils/cn"; + +export type MicrosoftSignInButtonProps = { + provider?: OAuthProvider; + themed?: boolean; +}; + +export function MicrosoftSignInButton({ provider, themed }: MicrosoftSignInButtonProps) { + const ui = useUI(); + + return ( + + + {getTranslation(ui, "labels", "signInWithMicrosoft")} + + ); +} + +export function MicrosoftLogo({ className, ...props }: React.SVGProps) { + return ; +} diff --git a/packages/react/src/auth/oauth/oauth-button.test.tsx b/packages/react/src/auth/oauth/oauth-button.test.tsx index 4e697623..ad698797 100644 --- a/packages/react/src/auth/oauth/oauth-button.test.tsx +++ b/packages/react/src/auth/oauth/oauth-button.test.tsx @@ -17,33 +17,18 @@ import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; import { render, screen, fireEvent, cleanup } from "@testing-library/react"; import { OAuthButton } from "./oauth-button"; import { CreateFirebaseUIProvider, createMockUI } from "~/tests/utils"; -import { registerLocale } from "@firebase-ui/translations"; -import type { AuthProvider } from "firebase/auth"; +import { enUs, registerLocale } from "@firebase-ui/translations"; +import type { AuthProvider, UserCredential } from "firebase/auth"; import { ComponentProps } from "react"; import { signInWithProvider } from "@firebase-ui/core"; +import { FirebaseError } from "firebase/app"; vi.mock("@firebase-ui/core", async (importOriginal) => { const mod = await importOriginal(); return { ...(mod as object), signInWithProvider: vi.fn(), - // TODO: This will need updating when core lands - FirebaseUIError: class FirebaseUIError extends Error { - code: string; - constructor(error: any, _ui: any) { - const errorCode = error?.code || "unknown"; - const message = - errorCode === "auth/user-not-found" - ? "No account found with this email address" - : errorCode === "auth/wrong-password" - ? "The password is invalid or the user does not have a password" - : "An unexpected error occurred"; - super(message); - this.name = "FirebaseUIError"; - this.code = errorCode; - } - }, }; }); @@ -123,7 +108,6 @@ describe("", () => { it("calls signInWithProvider when clicked", async () => { const mockSignInWithProvider = vi.mocked(signInWithProvider); - mockSignInWithProvider.mockResolvedValue(undefined); const ui = createMockUI(); @@ -144,7 +128,10 @@ describe("", () => { const { FirebaseUIError } = await import("@firebase-ui/core"); const mockSignInWithProvider = vi.mocked(signInWithProvider); const ui = createMockUI(); - const mockError = new FirebaseUIError({ code: "auth/user-not-found" }, ui.get()); + const mockError = new FirebaseUIError( + ui.get(), + new FirebaseError("auth/user-not-found", "No account found with this email address") + ); mockSignInWithProvider.mockRejectedValue(mockError); render( @@ -156,10 +143,9 @@ describe("", () => { const button = screen.getByTestId("oauth-button"); fireEvent.click(button); - // Wait for error to appear + // Next tick - wait for the mock to resolve await new Promise((resolve) => setTimeout(resolve, 0)); - // The error message will be the translated message for auth/user-not-found const errorMessage = screen.getByText("No account found with this email address"); expect(errorMessage).toBeDefined(); expect(errorMessage.className).toContain("fui-form__error"); @@ -210,8 +196,8 @@ describe("", () => { // First call fails, second call succeeds mockSignInWithProvider - .mockRejectedValueOnce(new FirebaseUIError({ code: "auth/wrong-password" }, ui.get())) - .mockResolvedValueOnce(undefined); + .mockRejectedValueOnce(new FirebaseUIError(ui.get(), new FirebaseError("auth/wrong-password", "..."))) + .mockResolvedValueOnce({} as UserCredential); render( @@ -225,14 +211,16 @@ describe("", () => { fireEvent.click(button); await new Promise((resolve) => setTimeout(resolve, 0)); + const expectedError = enUs.translations.errors!.wrongPassword!; + // The error message will be the translated message for auth/wrong-password - const errorMessage = screen.getByText("The password is invalid or the user does not have a password"); + const errorMessage = screen.getByText(expectedError); expect(errorMessage).toBeDefined(); // Second click - should clear error fireEvent.click(button); await new Promise((resolve) => setTimeout(resolve, 0)); - expect(screen.queryByText("The password is invalid or the user does not have a password")).toBeNull(); + expect(screen.queryByText(expectedError)).toBeNull(); }); }); diff --git a/packages/react/src/auth/oauth/oauth-button.tsx b/packages/react/src/auth/oauth/oauth-button.tsx index 39dfd4f8..c4c5df72 100644 --- a/packages/react/src/auth/oauth/oauth-button.tsx +++ b/packages/react/src/auth/oauth/oauth-button.tsx @@ -25,9 +25,10 @@ import { useUI } from "~/hooks"; export type OAuthButtonProps = PropsWithChildren<{ provider: AuthProvider; + themed?: boolean | string; }>; -export function OAuthButton({ provider, children }: OAuthButtonProps) { +export function OAuthButton({ provider, children, themed }: OAuthButtonProps) { const ui = useUI(); const [error, setError] = useState(null); @@ -48,7 +49,14 @@ export function OAuthButton({ provider, children }: OAuthButtonProps) { return (
- {error &&
{error}
} diff --git a/packages/react/src/auth/oauth/twitter-sign-in-button.test.tsx b/packages/react/src/auth/oauth/twitter-sign-in-button.test.tsx new file mode 100644 index 00000000..648d11ef --- /dev/null +++ b/packages/react/src/auth/oauth/twitter-sign-in-button.test.tsx @@ -0,0 +1,187 @@ +/** + * 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. + */ + +import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; +import { render, screen, cleanup } from "@testing-library/react"; +import { TwitterLogo, TwitterSignInButton } from "./twitter-sign-in-button"; +import { CreateFirebaseUIProvider, createMockUI } from "~/tests/utils"; +import { registerLocale } from "@firebase-ui/translations"; + +vi.mock("firebase/auth", () => ({ + TwitterAuthProvider: class TwitterAuthProvider { + constructor() { + this.providerId = "twitter.com"; + } + providerId: string; + }, +})); + +afterEach(() => { + cleanup(); +}); + +describe("", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders with the correct provider", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithTwitter: "Sign in with Twitter", + }, + }), + }); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("twitter.com"); + }); + + it("renders with custom provider when provided", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithTwitter: "Sign in with Twitter", + }, + }), + }); + + const customProvider = new (class CustomTwitterProvider { + providerId = "custom.twitter.com"; + })() as any; + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toBeDefined(); + expect(button.getAttribute("data-provider")).toBe("custom.twitter.com"); + }); + + it("renders with the Twitter icon", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithTwitter: "Sign in with Twitter", + }, + }), + }); + + render( + + + + ); + + const svg = document.querySelector(".fui-provider__icon"); + expect(svg).toBeDefined(); + expect(svg).toHaveClass("fui-provider__icon"); + expect(svg?.tagName.toLowerCase()).toBe("svg"); + }); + + it("renders with the correct translated text", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithTwitter: "Sign in with Twitter", + }, + }), + }); + + render( + + + + ); + + expect(screen.getByText("Sign in with Twitter")).toBeDefined(); + }); + + it("renders with different translated text for different locales", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithTwitter: "Iniciar sesión con Twitter", + }, + }), + }); + + render( + + + + ); + + expect(screen.getByText("Iniciar sesión con Twitter")).toBeDefined(); + }); + + it("renders as a button with correct classes", () => { + const ui = createMockUI({ + locale: registerLocale("test", { + labels: { + signInWithTwitter: "Sign in with Twitter", + }, + }), + }); + + render( + + + + ); + + const button = screen.getByRole("button"); + expect(button).toHaveClass("fui-provider__button"); + expect(button.getAttribute("type")).toBe("button"); + }); +}); + +describe("", () => { + it("renders as an SVG element", () => { + const { container } = render(); + const svg = container.querySelector("svg"); + + expect(svg).toBeDefined(); + expect(svg?.tagName.toLowerCase()).toBe("svg"); + }); + + it("has the correct CSS class", () => { + const { container } = render(); + const svg = container.querySelector("svg"); + + expect(svg).toHaveClass("fui-provider__icon"); + }); + + it("forwards custom SVG props", () => { + const { container } = render(); + const svg = container.querySelector('svg[data-testid="custom-svg"]'); + + expect(svg).toBeDefined(); + expect(svg!.getAttribute("width")).toBe("32"); + expect(svg).toHaveClass("fui-provider__icon"); + expect(svg).toHaveClass("foo"); + }); +}); diff --git a/packages/react/src/auth/oauth/twitter-sign-in-button.tsx b/packages/react/src/auth/oauth/twitter-sign-in-button.tsx new file mode 100644 index 00000000..e040785e --- /dev/null +++ b/packages/react/src/auth/oauth/twitter-sign-in-button.tsx @@ -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. + */ + +"use client"; + +import { getTranslation } from "@firebase-ui/core"; +import { TwitterAuthProvider } from "firebase/auth"; +import { useUI } from "~/hooks"; +import { OAuthButton } from "./oauth-button"; +import TwitterSvgLogo from "~/components/logos/twitter/Logo"; +import { cn } from "~/utils/cn"; + +export type TwitterSignInButtonProps = { + provider?: TwitterAuthProvider; + themed?: boolean; +}; + +export function TwitterSignInButton({ provider, themed }: TwitterSignInButtonProps) { + const ui = useUI(); + + return ( + + + {getTranslation(ui, "labels", "signInWithTwitter")} + + ); +} + +export function TwitterLogo({ className, ...props }: React.SVGProps) { + return ; +} diff --git a/packages/react/src/auth/screens/oauth-screen.tsx b/packages/react/src/auth/screens/oauth-screen.tsx index 1e6617d9..74fa36cb 100644 --- a/packages/react/src/auth/screens/oauth-screen.tsx +++ b/packages/react/src/auth/screens/oauth-screen.tsx @@ -25,7 +25,6 @@ export type OAuthScreenProps = PropsWithChildren; export function OAuthScreen({ children }: OAuthScreenProps) { const ui = useUI(); - // TODO: Translations for oauth providers const titleText = getTranslation(ui, "labels", "signIn"); const subtitleText = getTranslation(ui, "prompts", "signInToAccount"); diff --git a/packages/react/src/components/logos/README.md b/packages/react/src/components/logos/README.md new file mode 100644 index 00000000..d379b14d --- /dev/null +++ b/packages/react/src/components/logos/README.md @@ -0,0 +1,3 @@ +This directory is generated, please do not edit. + +Run `pnpm run build:logos` to regenerate any files. \ No newline at end of file diff --git a/packages/react/src/components/logos/apple/Logo.tsx b/packages/react/src/components/logos/apple/Logo.tsx new file mode 100644 index 00000000..844d6c48 --- /dev/null +++ b/packages/react/src/components/logos/apple/Logo.tsx @@ -0,0 +1,10 @@ +import type { SVGProps } from "react"; +const SvgLogo = (props: SVGProps) => ( + + + +); +export default SvgLogo; diff --git a/packages/react/src/components/logos/facebook/Logo.tsx b/packages/react/src/components/logos/facebook/Logo.tsx new file mode 100644 index 00000000..19c6abd4 --- /dev/null +++ b/packages/react/src/components/logos/facebook/Logo.tsx @@ -0,0 +1,10 @@ +import type { SVGProps } from "react"; +const SvgLogo = (props: SVGProps) => ( + + + +); +export default SvgLogo; diff --git a/packages/react/src/components/logos/github/Logo.tsx b/packages/react/src/components/logos/github/Logo.tsx new file mode 100644 index 00000000..20738296 --- /dev/null +++ b/packages/react/src/components/logos/github/Logo.tsx @@ -0,0 +1,7 @@ +import type { SVGProps } from "react"; +const SvgLogo = (props: SVGProps) => ( + + + +); +export default SvgLogo; diff --git a/packages/react/src/components/logos/google/Logo.tsx b/packages/react/src/components/logos/google/Logo.tsx new file mode 100644 index 00000000..f8d84c7e --- /dev/null +++ b/packages/react/src/components/logos/google/Logo.tsx @@ -0,0 +1,22 @@ +import type { SVGProps } from "react"; +const SvgLogo = (props: SVGProps) => ( + + + + + + +); +export default SvgLogo; diff --git a/packages/react/src/components/logos/line/Logo.tsx b/packages/react/src/components/logos/line/Logo.tsx new file mode 100644 index 00000000..ec6db6eb --- /dev/null +++ b/packages/react/src/components/logos/line/Logo.tsx @@ -0,0 +1,14 @@ +import type { SVGProps } from "react"; +const SvgLogo = (props: SVGProps) => ( + + + + +); +export default SvgLogo; diff --git a/packages/react/src/components/logos/microsoft/Logo.tsx b/packages/react/src/components/logos/microsoft/Logo.tsx new file mode 100644 index 00000000..1427b32e --- /dev/null +++ b/packages/react/src/components/logos/microsoft/Logo.tsx @@ -0,0 +1,10 @@ +import type { SVGProps } from "react"; +const SvgLogo = (props: SVGProps) => ( + + + + + + +); +export default SvgLogo; diff --git a/packages/react/src/components/logos/snapchat/Logo.tsx b/packages/react/src/components/logos/snapchat/Logo.tsx new file mode 100644 index 00000000..ad751c49 --- /dev/null +++ b/packages/react/src/components/logos/snapchat/Logo.tsx @@ -0,0 +1,10 @@ +import type { SVGProps } from "react"; +const SvgLogo = (props: SVGProps) => ( + + + +); +export default SvgLogo; diff --git a/packages/react/src/components/logos/twitter/Logo.tsx b/packages/react/src/components/logos/twitter/Logo.tsx new file mode 100644 index 00000000..dc360cae --- /dev/null +++ b/packages/react/src/components/logos/twitter/Logo.tsx @@ -0,0 +1,7 @@ +import type { SVGProps } from "react"; +const SvgLogo = (props: SVGProps) => ( + + + +); +export default SvgLogo; diff --git a/packages/react/src/vite-env.d.ts b/packages/react/src/vite-env.d.ts index e0065e00..54a689ee 100644 --- a/packages/react/src/vite-env.d.ts +++ b/packages/react/src/vite-env.d.ts @@ -15,3 +15,4 @@ */ /// +/// diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index 6bd22fd3..9c22dfe5 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -7,6 +7,7 @@ "~/*": ["./src/*"], "~/tests/*": ["./tests/*"], "@firebase-ui/core": ["../core/src/index.ts"], + "@firebase-ui/translations": ["../translations/src/index.ts"], "@firebase-ui/styles": ["../styles/src/index.ts"] } }, diff --git a/packages/react/vite.config.ts b/packages/react/vite.config.ts index b414d784..083c8402 100644 --- a/packages/react/vite.config.ts +++ b/packages/react/vite.config.ts @@ -18,10 +18,11 @@ import { defineConfig } from "vite"; import path from "node:path"; import { fileURLToPath } from "node:url"; import react from "@vitejs/plugin-react"; +import svgr from "vite-plugin-svgr"; // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), svgr()], resolve: { alias: { "~/tests": path.resolve(path.dirname(fileURLToPath(import.meta.url)), "./tests"), diff --git a/packages/styles/dist.css b/packages/styles/dist.css index a562aa14..c40ab4ce 100644 --- a/packages/styles/dist.css +++ b/packages/styles/dist.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.1.14 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--color-red-500:oklch(63.7% .237 25.331);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-800:oklch(27.8% .033 256.848);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-bold:700;--radius-sm:.25rem;--radius-xl:.75rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--radius:var(--fui-radius);--color-primary:var(--fui-primary);--color-primary-hover:var(--fui-primary-hover);--color-primary-surface:var(--fui-primary-surface);--color-text:var(--fui-text);--color-text-muted:var(--fui-text-muted);--color-background:var(--fui-background);--color-border:var(--fui-border);--color-input:var(--fui-input);--color-error:var(--fui-error);--radius-card:var(--fui-radius-card)}:root{--fui-primary:var(--color-black);--fui-primary-hover:var(--fui-primary)}@supports (color:color-mix(in lab, red, red)){:root{--fui-primary-hover:color-mix(in oklab,var(--fui-primary)85%,transparent)}}:root{--fui-primary-surface:var(--color-white);--fui-text:var(--color-black);--fui-text-muted:var(--color-gray-800);--fui-background:var(--color-white);--fui-border:var(--color-gray-200);--fui-input:var(--color-gray-300);--fui-error:var(--color-red-500);--fui-radius:var(--radius-sm);--fui-radius-card:var(--radius-xl)}@media (prefers-color-scheme:dark){:root{--fui-primary:var(--color-white);--fui-primary-hover:var(--fui-primary)}@supports (color:color-mix(in lab, red, red)){:root{--fui-primary-hover:color-mix(in oklab,var(--fui-primary)85%,transparent)}}:root{--fui-primary-surface:var(--color-black);--fui-text:var(--color-white);--fui-text-muted:var(--color-gray-200);--fui-background:var(--color-black);--fui-border:var(--color-gray-200);--fui-input:var(--color-gray-300);--fui-error:var(--color-red-500);--fui-radius:var(--radius-sm);--fui-radius-card:var(--radius-xl)}}}@layer components{:where(.fui-screen){max-width:var(--container-md);padding-top:calc(var(--spacing)*24);margin-inline:auto}:where(:where(.fui-card)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.fui-card){border-radius:var(--radius-card);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-border);background-color:var(--color-background);padding:calc(var(--spacing)*10)}:where(:where(.fui-card__header)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.fui-card__header){text-align:center}:where(.fui-card__title){font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-text)}:where(.fui-card__subtitle){font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-text-muted)}:where(:where(.fui-form)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.fui-form fieldset),:where(.fui-form fieldset>label){gap:calc(var(--spacing)*2);color:var(--color-text);flex-direction:column;display:flex}:where(.fui-form fieldset>label>span){gap:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);display:inline-flex}:where(.fui-form .fui-form__action){padding-inline:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-text-muted)}@media (hover:hover){:where(.fui-form .fui-form__action):hover{text-decoration-line:underline}}:where(.fui-form fieldset>label>input){border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000}:where(.fui-form fieldset>label>input):focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}:where(.fui-form fieldset>label>input[aria-invalid=true]){outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-error)}:where(.fui-form .fui-form__error){text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-error)}:where(.fui-success){text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}:where(.fui-button){justify-content:center;align-items:center;gap:calc(var(--spacing)*3);border-radius:var(--radius);background-color:var(--color-primary);width:100%;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-primary-surface);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}@media (hover:hover){:where(.fui-button):hover{cursor:pointer;background-color:var(--color-primary-hover)}}:where(.fui-button):disabled{cursor:not-allowed;opacity:.5}:where(.fui-button--secondary){border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);color:var(--color-text);background-color:#0000}@media (hover:hover){:where(.fui-button--secondary):hover{border-color:var(--color-primary);background-color:var(--color-background)}}:where(.fui-provider__button>svg){height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}:where(.fui-divider){align-items:center;gap:calc(var(--spacing)*3);display:flex}:where(.fui-divider__line){background-color:var(--color-border);flex:1;height:1px}:where(.fui-divider__text){font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-text-muted)}:where(.fui-phone-input){align-items:center;gap:calc(var(--spacing)*2);display:flex}:where(.fui-phone-input__number-input){border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000;flex:1}:where(.fui-phone-input__number-input):focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}:where(.fui-phone-input__number-input[aria-invalid=true]){outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-error)}:where(.fui-country-selector){width:80px;display:inline-block;position:relative}:where(.fui-country-selector__wrapper){border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);background-color:#0000;align-items:center;display:flex;position:relative;overflow:hidden}:where(.fui-country-selector__flag){pointer-events:none;left:calc(var(--spacing)*2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));position:absolute}:where(.fui-country-selector select){cursor:pointer;appearance:none;width:100%;padding-block:calc(var(--spacing)*2);padding-right:calc(var(--spacing)*2);padding-left:calc(var(--spacing)*8);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:#0000;--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000}:where(.fui-country-selector select):focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}:where(.fui-country-selector__dial-code){pointer-events:none;top:50%;left:calc(var(--spacing)*8);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-text);position:absolute}}@layer utilities;@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--color-red-500:oklch(63.7% .237 25.331);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-800:oklch(27.8% .033 256.848);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-bold:700;--radius-sm:.25rem;--radius-xl:.75rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--radius:var(--fui-radius);--color-primary:var(--fui-primary);--color-primary-hover:var(--fui-primary-hover);--color-primary-surface:var(--fui-primary-surface);--color-text:var(--fui-text);--color-text-muted:var(--fui-text-muted);--color-background:var(--fui-background);--color-border:var(--fui-border);--color-input:var(--fui-input);--color-error:var(--fui-error);--radius-card:var(--fui-radius-card)}:root{--fui-primary:var(--color-black);--fui-primary-hover:var(--fui-primary)}@supports (color:color-mix(in lab, red, red)){:root{--fui-primary-hover:color-mix(in oklab,var(--fui-primary)85%,transparent)}}:root{--fui-primary-surface:var(--color-white);--fui-text:var(--color-black);--fui-text-muted:var(--color-gray-800);--fui-background:var(--color-white);--fui-border:var(--color-gray-200);--fui-input:var(--color-gray-300);--fui-error:var(--color-red-500);--fui-radius:var(--radius-sm);--fui-radius-card:var(--radius-xl)}@media (prefers-color-scheme:dark){:root{--fui-primary:var(--color-white);--fui-primary-hover:var(--fui-primary)}@supports (color:color-mix(in lab, red, red)){:root{--fui-primary-hover:color-mix(in oklab,var(--fui-primary)85%,transparent)}}:root{--fui-primary-surface:var(--color-black);--fui-text:var(--color-white);--fui-text-muted:var(--color-gray-200);--fui-background:var(--color-black);--fui-border:var(--color-gray-200);--fui-input:var(--color-gray-300);--fui-error:var(--color-red-500);--fui-radius:var(--radius-sm);--fui-radius-card:var(--radius-xl)}}}@layer components{:where(.fui-screen){max-width:var(--container-md);padding-top:calc(var(--spacing)*24);margin-inline:auto}:where(:where(.fui-card)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.fui-card){border-radius:var(--radius-card);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-border);background-color:var(--color-background);padding:calc(var(--spacing)*10)}:where(:where(.fui-card__header)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.fui-card__header){text-align:center}:where(.fui-card__title){font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-text)}:where(.fui-card__subtitle){font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-text-muted)}:where(:where(.fui-card__content)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(:where(.fui-form)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.fui-form fieldset),:where(.fui-form fieldset>label){gap:calc(var(--spacing)*2);color:var(--color-text);flex-direction:column;display:flex}:where(.fui-form fieldset>label>span){gap:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);display:inline-flex}:where(.fui-form .fui-form__action){padding-inline:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-text-muted)}@media (hover:hover){:where(.fui-form .fui-form__action):hover{text-decoration-line:underline}}:where(.fui-form fieldset>label>input){border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000}:where(.fui-form fieldset>label>input):focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}:where(.fui-form fieldset>label>input[aria-invalid=true]){outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-error)}:where(.fui-form .fui-form__error){text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-error)}:where(.fui-success){text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}:where(.fui-button){justify-content:center;align-items:center;gap:calc(var(--spacing)*3);border-radius:var(--radius);background-color:var(--color-primary);width:100%;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-primary-surface);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}@media (hover:hover){:where(.fui-button):hover{cursor:pointer;background-color:var(--color-primary-hover)}}:where(.fui-button):disabled{cursor:not-allowed;opacity:.5}:where(.fui-button--secondary){border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);color:var(--color-text);background-color:#0000}@media (hover:hover){:where(.fui-button--secondary):hover{border-color:var(--color-primary);background-color:var(--color-background)}}:where(.fui-button.fui-provider__button){border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-border)}:where(.fui-provider__button>svg,.fui-provider__button>img){height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}:where(.fui-divider){align-items:center;gap:calc(var(--spacing)*3);display:flex}:where(.fui-divider__line){background-color:var(--color-border);flex:1;height:1px}:where(.fui-divider__text){margin-block:calc(var(--spacing)*2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-text-muted)}:where(.fui-phone-input){align-items:center;gap:calc(var(--spacing)*2);display:flex}:where(.fui-phone-input__number-input){border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000;flex:1}:where(.fui-phone-input__number-input):focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}:where(.fui-phone-input__number-input[aria-invalid=true]){outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-error)}:where(.fui-country-selector){width:80px;display:inline-block;position:relative}:where(.fui-country-selector__wrapper){border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);background-color:#0000;align-items:center;display:flex;position:relative;overflow:hidden}:where(.fui-country-selector__flag){pointer-events:none;left:calc(var(--spacing)*2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));position:absolute}:where(.fui-country-selector select){cursor:pointer;appearance:none;width:100%;padding-block:calc(var(--spacing)*2);padding-right:calc(var(--spacing)*2);padding-left:calc(var(--spacing)*8);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:#0000;--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000}:where(.fui-country-selector select):focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}:where(.fui-country-selector__dial-code){pointer-events:none;top:50%;left:calc(var(--spacing)*8);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-text);position:absolute}.fui-provider__button[data-provider=google\.com][data-themed=true]{--google-primary:#4285f4;--color-primary:var(--google-primary);--color-primary-hover:var(--google-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=google\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--google-primary)85%,transparent)}}.fui-provider__button[data-provider=google\.com][data-themed=true]{--color-primary-surface:#fff;--color-border:var(--google-primary)}.fui-provider__button[data-provider=google\.com][data-themed=neutral]{--google-primary:#f2f2f2;--color-primary:var(--google-primary);--color-primary-hover:var(--google-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=google\.com][data-themed=neutral]{--color-primary-hover:color-mix(in oklab,var(--google-primary)85%,transparent)}}.fui-provider__button[data-provider=google\.com][data-themed=neutral]{--color-primary-surface:#1f1f1f;--color-border:transparent}@media (prefers-color-scheme:dark){.fui-provider__button[data-provider=google\.com][data-themed=true]{--google-primary:#fff;--color-primary:var(--google-primary);--color-primary-hover:var(--google-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=google\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--google-primary)85%,transparent)}}.fui-provider__button[data-provider=google\.com][data-themed=true]{--color-primary-surface:#1f1f1f;--color-border:#747775}}.fui-provider__button[data-provider=facebook\.com][data-themed=true]{--facebook-primary:#1877f2;--color-primary:var(--facebook-primary);--color-primary-hover:var(--facebook-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=facebook\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--facebook-primary)85%,transparent)}}.fui-provider__button[data-provider=facebook\.com][data-themed=true]{--color-primary-surface:var(--color-white);--color-border:var(--facebook-primary)}.fui-provider__button[data-provider=apple\.com][data-themed=true]{--apple-primary:#000;--color-primary:var(--apple-primary);--color-primary-hover:var(--apple-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=apple\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--apple-primary)85%,transparent)}}.fui-provider__button[data-provider=apple\.com][data-themed=true]{--color-primary-surface:#fff;--color-border:var(--apple-primary)}@media (prefers-color-scheme:dark){.fui-provider__button[data-provider=apple\.com][data-themed=true]{--apple-primary:var(--color-white);--color-primary:var(--apple-primary);--color-primary-hover:var(--apple-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=apple\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--apple-primary)85%,transparent)}}.fui-provider__button[data-provider=apple\.com][data-themed=true]{--color-primary-surface:var(--color-black);--color-border:var(--color-white)}}.fui-provider__button[data-provider=github\.com][data-themed=true]{--github-primary:#000;--color-primary:var(--github-primary);--color-primary-hover:var(--github-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=github\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--github-primary)85%,transparent)}}.fui-provider__button[data-provider=github\.com][data-themed=true]{--color-primary-surface:#fff;--color-border:var(--github-primary)}@media (prefers-color-scheme:dark){.fui-provider__button[data-provider=github\.com][data-themed=true]{--github-primary:var(--color-white);--color-primary:var(--github-primary);--color-primary-hover:var(--github-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=github\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--github-primary)85%,transparent)}}.fui-provider__button[data-provider=github\.com][data-themed=true]{--color-primary-surface:var(--color-black);--color-border:var(--color-white)}}.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--microsoft-primary:var(--color-white);--color-primary:var(--microsoft-primary);--color-primary-hover:var(--microsoft-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--microsoft-primary)85%,transparent)}}.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--color-primary-surface:#5e5e5e;--color-border:var(--color-black)}@media (prefers-color-scheme:dark){.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--microsoft-primary:var(--color-white);--color-primary:var(--microsoft-primary);--color-primary-hover:var(--microsoft-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--microsoft-primary)85%,transparent)}}.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--color-primary-surface:#5e5e5e;--color-border:var(--color-white)}}.fui-provider__button[data-provider=twitter\.com][data-themed=true]{--twitter-primary:var(--color-white);--color-primary:var(--twitter-primary);--color-primary-hover:var(--twitter-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=twitter\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--twitter-primary)85%,transparent)}}.fui-provider__button[data-provider=twitter\.com][data-themed=true]{--color-primary-surface:var(--color-black);--color-border:var(--color-black)}@media (prefers-color-scheme:dark){.fui-provider__button[data-provider=twitter\.com][data-themed=true]{--twitter-primary:var(--color-white);--color-primary:var(--twitter-primary);--color-primary-hover:var(--twitter-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=twitter\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--twitter-primary)85%,transparent)}}.fui-provider__button[data-provider=twitter\.com][data-themed=true]{--color-primary-surface:var(--color-black);--color-border:var(--color-white)}}}@layer utilities;@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0} \ No newline at end of file diff --git a/packages/styles/src/base.css b/packages/styles/src/base.css index 9ba45a48..e1554590 100644 --- a/packages/styles/src/base.css +++ b/packages/styles/src/base.css @@ -136,7 +136,11 @@ @apply bg-transparent text-text border border-input hover:bg-background hover:border-primary; } - :where(.fui-provider__button > svg) { + :where(.fui-button.fui-provider__button) { + @apply border border-border; + } + + :where(.fui-provider__button > svg, .fui-provider__button > img) { @apply w-5 h-5; } @@ -149,7 +153,7 @@ } :where(.fui-divider__text) { - @apply text-text-muted text-xs; + @apply text-text-muted text-xs my-2; } :where(.fui-phone-input) { @@ -183,4 +187,100 @@ :where(.fui-country-selector__dial-code) { @apply absolute left-8 top-1/2 -translate-y-1/2 text-sm pointer-events-none text-text; } + + .fui-provider__button[data-provider="google.com"][data-themed="true"] { + --google-primary: #131314; + --color-primary: var(--google-primary); + --color-primary-hover: --alpha(var(--google-primary) / 85%); + --color-primary-surface: #FFFFFF; + --color-border: var(--google-primary); + } + + .fui-provider__button[data-provider="google.com"][data-themed="neutral"] { + --google-primary: #F2F2F2; + --color-primary: var(--google-primary); + --color-primary-hover: --alpha(var(--google-primary) / 85%); + --color-primary-surface: #1F1F1F; + --color-border: transparent; + } + + @variant dark { + .fui-provider__button[data-provider="google.com"][data-themed="true"] { + --google-primary: #FFFFFF; + --color-primary: var(--google-primary); + --color-primary-hover: --alpha(var(--google-primary) / 85%); + --color-primary-surface: #1F1F1F; + --color-border: #747775; + } + } + + .fui-provider__button[data-provider="facebook.com"][data-themed="true"] { + --facebook-primary: #1877F2; + --color-primary: var(--facebook-primary); + --color-primary-hover: --alpha(var(--facebook-primary) / 85%); + --color-primary-surface: var(--color-white); + --color-border: var(--facebook-primary); + } + + .fui-provider__button[data-provider="apple.com"][data-themed="true"] { + --apple-primary: #000000; + --color-primary: var(--apple-primary); + --color-primary-hover: --alpha(var(--apple-primary) / 85%); + --color-primary-surface: #FFFFFF; + --color-border: var(--apple-primary); + } + + @variant dark { + .fui-provider__button[data-provider="apple.com"][data-themed="true"] { + --apple-primary: var(--color-white); + --color-primary: var(--apple-primary); + --color-primary-hover: --alpha(var(--apple-primary) / 85%); + --color-primary-surface: var(--color-black); + --color-border: var(--color-white); + } + } + + .fui-provider__button[data-provider="github.com"][data-themed="true"] { + --github-primary: #000000; + --color-primary: var(--github-primary); + --color-primary-hover: --alpha(var(--github-primary) / 85%); + --color-primary-surface: #FFFFFF; + --color-border: var(--github-primary); + } + + @variant dark { + .fui-provider__button[data-provider="github.com"][data-themed="true"] { + --github-primary: var(--color-white); + --color-primary: var(--github-primary); + --color-primary-hover: --alpha(var(--github-primary) / 85%); + --color-primary-surface: var(--color-black); + --color-border: var(--color-white); + } + } + + .fui-provider__button[data-provider="microsoft.com"][data-themed="true"] { + --microsoft-primary: #2F2F2F; + --color-primary: var(--microsoft-primary); + --color-primary-hover: --alpha(var(--microsoft-primary) / 85%); + --color-primary-surface: #FFFFFF; + --color-border: var(--microsoft-primary); + } + + @variant dark { + .fui-provider__button[data-provider="microsoft.com"][data-themed="true"] { + --microsoft-primary: var(--color-white); + --color-primary: var(--microsoft-primary); + --color-primary-hover: --alpha(var(--microsoft-primary) / 85%); + --color-primary-surface: #5E5E5E; + --color-border: var(--color-white); + } + } + + .fui-provider__button[data-provider="twitter.com"][data-themed="true"] { + --twitter-primary: #1DA1F2; + --color-primary: var(--twitter-primary); + --color-primary-hover: --alpha(var(--twitter-primary) / 85%); + --color-primary-surface: #FFFFFF; + --color-border: var(--twitter-primary); + } } diff --git a/packages/translations/src/locales/en-us.ts b/packages/translations/src/locales/en-us.ts index 015edabb..fee39ba1 100644 --- a/packages/translations/src/locales/en-us.ts +++ b/packages/translations/src/locales/en-us.ts @@ -68,6 +68,11 @@ export const enUS = { sendCode: "Send Code", verifyCode: "Verify Code", signInWithGoogle: "Sign in with Google", + signInWithFacebook: "Sign in with Facebook", + signInWithApple: "Sign in with Apple", + signInWithMicrosoft: "Sign in with Microsoft", + signInWithGitHub: "Sign in with GitHub", + signInWithTwitter: "Sign in with Twitter", 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 8ed7820d..1b1791fc 100644 --- a/packages/translations/src/types.ts +++ b/packages/translations/src/types.ts @@ -73,6 +73,11 @@ export type Translations = { sendCode?: string; verifyCode?: string; signInWithGoogle?: string; + signInWithFacebook?: string; + signInWithApple?: string; + signInWithTwitter?: string; + signInWithMicrosoft?: string; + signInWithGitHub?: string; signInWithEmailLink?: string; sendSignInLink?: string; termsOfService?: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d8d11a9..4c5ae7b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -605,6 +605,9 @@ importers: vite: specifier: 'catalog:' version: 7.1.5(@types/node@24.3.1)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) + vite-plugin-svgr: + specifier: ^4.5.0 + version: 4.5.0(rollup@4.50.1)(typescript@5.9.2)(vite@7.1.5(@types/node@24.3.1)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1)) vitest: specifier: 'catalog:' version: 3.2.4(@types/node@24.3.1)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) @@ -3166,6 +3169,74 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/core@8.1.0': + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} + engines: {node: '>=14'} + + '@svgr/plugin-jsx@8.1.0': + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -4175,6 +4246,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + caniuse-lite@1.0.30001741: resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==} @@ -4372,6 +4447,15 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + cosmiconfig@9.0.0: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} @@ -4592,6 +4676,9 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -5886,6 +5973,9 @@ packages: loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -6183,6 +6273,9 @@ packages: tailwindcss: optional: true + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-addon-api@6.1.0: resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} @@ -6410,6 +6503,10 @@ packages: path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -7000,6 +7097,9 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + socket.io-adapter@2.5.5: resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} @@ -7209,6 +7309,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svg-parser@2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -7545,6 +7648,11 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true + vite-plugin-svgr@4.5.0: + resolution: {integrity: sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==} + peerDependencies: + vite: '>=2.6.0' + vite@5.4.20: resolution: {integrity: sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==} engines: {node: ^18.0.0 || >=20.0.0} @@ -10947,6 +11055,76 @@ snapshots: '@socket.io/component-emitter@3.1.2': optional: true + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + + '@svgr/babel-preset@8.1.0(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.4) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.4) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.4) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.4) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.4) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.4) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.4) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.4) + + '@svgr/core@8.1.0(typescript@5.9.2)': + dependencies: + '@babel/core': 7.28.4 + '@svgr/babel-preset': 8.1.0(@babel/core@7.28.4) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.9.2) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.28.4 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.2))': + dependencies: + '@babel/core': 7.28.4 + '@svgr/babel-preset': 8.1.0(@babel/core@7.28.4) + '@svgr/core': 8.1.0(typescript@5.9.2) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15': @@ -12212,6 +12390,8 @@ snapshots: callsites@3.1.0: {} + camelcase@6.3.0: {} + caniuse-lite@1.0.30001741: {} chai@5.3.3: @@ -12411,6 +12591,15 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cosmiconfig@8.3.6(typescript@5.9.2): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.9.2 + cosmiconfig@9.0.0(typescript@5.9.2): dependencies: env-paths: 2.2.1 @@ -12605,6 +12794,11 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -12861,7 +13055,7 @@ snapshots: eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.35.0(jiti@2.5.1)) @@ -12895,7 +13089,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -12910,7 +13104,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -14248,6 +14442,10 @@ snapshots: loupe@3.2.1: {} + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + lru-cache@10.4.3: {} lru-cache@11.2.1: {} @@ -14551,6 +14749,11 @@ snapshots: rollup: 4.50.1 tailwindcss: 4.1.13 + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + node-addon-api@6.1.0: optional: true @@ -14839,6 +15042,8 @@ snapshots: path-to-regexp@8.3.0: {} + path-type@4.0.0: {} + pathe@1.1.2: {} pathe@2.0.3: {} @@ -15544,6 +15749,11 @@ snapshots: smart-buffer@4.2.0: {} + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + socket.io-adapter@2.5.5: dependencies: debug: 4.3.7 @@ -15813,6 +16023,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svg-parser@2.0.4: {} + symbol-tree@3.2.4: {} synckit@0.11.11: @@ -16216,6 +16428,17 @@ snapshots: - tsx - yaml + vite-plugin-svgr@4.5.0(rollup@4.50.1)(typescript@5.9.2)(vite@7.1.5(@types/node@24.3.1)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1)): + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.50.1) + '@svgr/core': 8.1.0(typescript@5.9.2) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.2)) + vite: 7.1.5(@types/node@24.3.1)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1) + transitivePeerDependencies: + - rollup + - supports-color + - typescript + vite@5.4.20(@types/node@24.3.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.43.1): dependencies: esbuild: 0.21.5