diff --git a/.gitignore b/.gitignore index 72f09694..cb1a1417 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ dist-ssr # Coverage coverage +# Firebase +.firebase + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/eslint.config.ts b/eslint.config.ts index 7b9b527a..eddd6d89 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -20,6 +20,7 @@ const config: any[] = [ "**/shadcn/public-dev/**", "packages/styles/dist.css", "packages/angular/**", + "packages/shadcn/public", ]), ...tseslint.configs.recommended, { @@ -48,7 +49,7 @@ const config: any[] = [ }, { // React package specific rules - files: ["packages/react/src/**/*.{ts,tsx}"], + files: ["packages/react/src/**/*.{ts,tsx}", "packages/shadcn/src/**/*.{ts,tsx}"], plugins: { react: pluginReact, "react-hooks": pluginReactHooks }, languageOptions: { parserOptions: { diff --git a/examples/shadcn/.gitignore b/examples/shadcn/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/examples/shadcn/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/shadcn/README.md b/examples/shadcn/README.md new file mode 100644 index 00000000..30404ce4 --- /dev/null +++ b/examples/shadcn/README.md @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/examples/shadcn/add-all.ts b/examples/shadcn/add-all.ts new file mode 100644 index 00000000..b295913c --- /dev/null +++ b/examples/shadcn/add-all.ts @@ -0,0 +1,39 @@ +import parser from "yargs-parser"; +import readline from "node:readline"; +import registryJson from "../../packages/shadcn/registry-spec.json"; +import { execSync } from "node:child_process"; + +const components = registryJson.items.map((item) => item.name); +const args = parser(process.argv.slice(2)); +const prefix = String(args.prefix) || "@dev"; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +const items = components + .map((component) => { + return `${prefix}/${component}`; + }) + .join(" "); + +rl.question( + `Add ${components.length} components. This will overrwrite all existing files. Continue? (y/N) `, + (answer: unknown) => { + const answerString = String(answer || "n").toLowerCase(); + + if (answerString === "y") { + try { + execSync(`pnpm dlx shadcn@latest add -y -o -a ${items}`, { stdio: "inherit" }); + process.exit(0); + } catch (error) { + console.error(error); + process.exit(1); + } + } + + console.log("Aborting..."); + process.exit(0); + } +); diff --git a/examples/shadcn/components.json b/examples/shadcn/components.json new file mode 100644 index 00000000..ea1fd790 --- /dev/null +++ b/examples/shadcn/components.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": { + "@dev": "http://localhost:5178/{name}.json", + "@firebase-ui": "https://ui.firebase.com/{name}.json" + } +} diff --git a/examples/shadcn/index.html b/examples/shadcn/index.html new file mode 100644 index 00000000..1d79ade8 --- /dev/null +++ b/examples/shadcn/index.html @@ -0,0 +1,14 @@ + + + + + + + shadcn + + + +
+ + + diff --git a/examples/shadcn/package.json b/examples/shadcn/package.json new file mode 100644 index 00000000..654e7976 --- /dev/null +++ b/examples/shadcn/package.json @@ -0,0 +1,77 @@ +{ + "name": "shadcn", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "shadcn:add-all": "tsx add-all.ts" + }, + "dependencies": { + "@firebase-ui/core": "workspace:*", + "@firebase-ui/react": "workspace:*", + "@firebase-ui/styles": "workspace:*", + "@hookform/resolvers": "^5.2.2", + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-aspect-ratio": "^1.1.7", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-context-menu": "^2.2.16", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-menubar": "^1.1.16", + "@radix-ui/react-navigation-menu": "^1.2.14", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", + "@radix-ui/react-tooltip": "^1.2.8", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "embla-carousel-react": "^8.6.0", + "firebase": "catalog:", + "input-otp": "^1.4.2", + "lucide-react": "^0.544.0", + "next-themes": "^0.4.6", + "react": "catalog:", + "react-day-picker": "^9.11.1", + "react-dom": "catalog:", + "react-hook-form": "^7.64.0", + "react-resizable-panels": "^3.0.6", + "react-router": "^7.9.3", + "react-router-dom": "^6.28.0", + "recharts": "2.15.4", + "sonner": "^2.0.7", + "tailwind-merge": "^3.3.1", + "vaul": "^1.1.2", + "zod": "catalog:" + }, + "devDependencies": { + "@tailwindcss/vite": "catalog:", + "@types/node": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "@types/yargs-parser": "^21.0.3", + "@vitejs/plugin-react": "catalog:", + "tailwindcss": "catalog:", + "tsx": "^4.20.6", + "tw-animate-css": "^1.4.0", + "typescript": "catalog:", + "vite": "catalog:", + "yargs-parser": "^22.0.0" + } +} diff --git a/examples/shadcn/public/vite.svg b/examples/shadcn/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/examples/shadcn/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/shadcn/src/App.tsx b/examples/shadcn/src/App.tsx new file mode 100644 index 00000000..3ef015d3 --- /dev/null +++ b/examples/shadcn/src/App.tsx @@ -0,0 +1,91 @@ +/** + * 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 { NavLink } from "react-router"; +import { useUser } from "./firebase/hooks"; + +function App() { + const user = useUser(); + + return ( +
+

Firebase UI Demo

+
{user &&
Welcome: {user.email || user.phoneNumber}
}
+
+

Auth Screens

+ +
+
+ ); +} + +export default App; diff --git a/examples/shadcn/src/assets/react.svg b/examples/shadcn/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/examples/shadcn/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/shadcn/src/components/email-link-auth-form.tsx b/examples/shadcn/src/components/email-link-auth-form.tsx new file mode 100644 index 00000000..b51c077c --- /dev/null +++ b/examples/shadcn/src/components/email-link-auth-form.tsx @@ -0,0 +1,83 @@ +"use client"; + +import type { EmailLinkAuthFormSchema } from "@firebase-ui/core"; +import { + useEmailLinkAuthFormAction, + useEmailLinkAuthFormCompleteSignIn, + useEmailLinkAuthFormSchema, + useUI, + type EmailLinkAuthFormProps, +} from "@firebase-ui/react"; + +import { FirebaseUIError, getTranslation } from "@firebase-ui/core"; +import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; + +import { Button } from "@/components/ui/button"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Policies } from "./policies"; + +export type { EmailLinkAuthFormProps }; + +export function EmailLinkAuthForm(props: EmailLinkAuthFormProps) { + const { onEmailSent, onSignIn } = props; + const ui = useUI(); + const schema = useEmailLinkAuthFormSchema(); + const action = useEmailLinkAuthFormAction(); + const [emailSent, setEmailSent] = useState(false); + + const form = useForm({ + resolver: standardSchemaResolver(schema), + defaultValues: { + email: "", + }, + }); + + useEmailLinkAuthFormCompleteSignIn(onSignIn); + + async function onSubmit(values: EmailLinkAuthFormSchema) { + try { + await action(values); + setEmailSent(true); + onEmailSent?.(); + } catch (error) { + const message = error instanceof FirebaseUIError ? error.message : String(error); + form.setError("root", { message }); + } + } + + if (emailSent) { + return ( +
+
{getTranslation(ui, "messages", "signInLinkSent")}
+
+ ); + } + + return ( +
+ + ( + + {getTranslation(ui, "labels", "emailAddress")} + + + + + + )} + /> + + + {form.formState.errors.root && {form.formState.errors.root.message}} + + + ); +} diff --git a/examples/shadcn/src/components/email-link-auth-screen.tsx b/examples/shadcn/src/components/email-link-auth-screen.tsx new file mode 100644 index 00000000..b636fe9d --- /dev/null +++ b/examples/shadcn/src/components/email-link-auth-screen.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { getTranslation } from "@firebase-ui/core"; +import { useUI, type EmailLinkAuthScreenProps } from "@firebase-ui/react"; + +import { EmailLinkAuthForm } from "@/components/email-link-auth-form"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; + +export type { EmailLinkAuthScreenProps }; + +export function EmailLinkAuthScreen({ children, ...props }: EmailLinkAuthScreenProps) { + const ui = useUI(); + + const titleText = getTranslation(ui, "labels", "signIn"); + const subtitleText = getTranslation(ui, "prompts", "signInToAccount"); + + return ( +
+ + + {titleText} + {subtitleText} + + + + {children ? ( + <> + {getTranslation(ui, "messages", "dividerOr")} +
{children}
+ + ) : null} +
+
+
+ ); +} diff --git a/examples/shadcn/src/components/forgot-password-auth-form.tsx b/examples/shadcn/src/components/forgot-password-auth-form.tsx new file mode 100644 index 00000000..e050d262 --- /dev/null +++ b/examples/shadcn/src/components/forgot-password-auth-form.tsx @@ -0,0 +1,83 @@ +"use client"; + +import type { ForgotPasswordAuthFormSchema } from "@firebase-ui/core"; +import { + useForgotPasswordAuthFormAction, + useForgotPasswordAuthFormSchema, + useUI, + type ForgotPasswordAuthFormProps, +} from "@firebase-ui/react"; +import { useForm } from "react-hook-form"; +import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; +import { FirebaseUIError, getTranslation } from "@firebase-ui/core"; +import { useState } from "react"; + +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Policies } from "./policies"; + +export type { ForgotPasswordAuthFormProps }; + +export function ForgotPasswordAuthForm(props: ForgotPasswordAuthFormProps) { + const ui = useUI(); + const schema = useForgotPasswordAuthFormSchema(); + const action = useForgotPasswordAuthFormAction(); + const [emailSent, setEmailSent] = useState(false); + + const form = useForm({ + resolver: standardSchemaResolver(schema), + defaultValues: { + email: "", + }, + }); + + async function onSubmit(values: ForgotPasswordAuthFormSchema) { + try { + await action(values); + setEmailSent(true); + props.onPasswordSent?.(); + } catch (error) { + const message = error instanceof FirebaseUIError ? error.message : String(error); + form.setError("root", { message }); + } + } + + if (emailSent) { + return ( +
+
{getTranslation(ui, "messages", "checkEmailForReset")}
+
+ ); + } + + return ( +
+ + ( + + {getTranslation(ui, "labels", "emailAddress")} + + + + + + )} + /> + + + {form.formState.errors.root && {form.formState.errors.root.message}} + {props.onBackToSignInClick ? ( + + ) : null} + + + ); +} diff --git a/examples/shadcn/src/components/forgot-password-auth-screen.tsx b/examples/shadcn/src/components/forgot-password-auth-screen.tsx new file mode 100644 index 00000000..cb074b2f --- /dev/null +++ b/examples/shadcn/src/components/forgot-password-auth-screen.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { getTranslation } from "@firebase-ui/core"; +import { useUI, type ForgotPasswordAuthScreenProps } from "@firebase-ui/react"; + +import { ForgotPasswordAuthForm } from "@/components/forgot-password-auth-form"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; + +export type { ForgotPasswordAuthScreenProps }; + +export function ForgotPasswordAuthScreen(props: ForgotPasswordAuthScreenProps) { + const ui = useUI(); + + const titleText = getTranslation(ui, "labels", "resetPassword"); + const subtitleText = getTranslation(ui, "prompts", "enterEmailToReset"); + + return ( +
+ + + {titleText} + {subtitleText} + + + + + +
+ ); +} diff --git a/examples/shadcn/src/components/google-sign-in-button.tsx b/examples/shadcn/src/components/google-sign-in-button.tsx new file mode 100644 index 00000000..775234ac --- /dev/null +++ b/examples/shadcn/src/components/google-sign-in-button.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { GoogleAuthProvider } from "firebase/auth"; +import { getTranslation } from "@firebase-ui/core"; +import { useUI, type GoogleSignInButtonProps, GoogleLogo } from "@firebase-ui/react"; + +import { OAuthButton } from "@/components"; + +export type { GoogleSignInButtonProps }; + +export function GoogleSignInButton({ provider, themed }: GoogleSignInButtonProps) { + const ui = useUI(); + + return ( + + + {getTranslation(ui, "labels", "signInWithGoogle")} + + ); +} diff --git a/examples/shadcn/src/components/header.tsx b/examples/shadcn/src/components/header.tsx new file mode 100644 index 00000000..5f231c30 --- /dev/null +++ b/examples/shadcn/src/components/header.tsx @@ -0,0 +1,55 @@ +/** + * 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 { NavLink } from "react-router"; +import { useUser } from "../firebase/hooks"; +import { signOut } from "firebase/auth"; +import { auth } from "../firebase/firebase"; + +export function Header() { + const user = useUser(); + + async function onSignOut() { + await signOut(auth); + // TODO: Use the router instead of window.location.href + window.location.href = "/"; + } + + return ( +
+
+
+ FirebaseUI +
+
+
    + {user ? ( +
  • + +
  • + ) : ( +
  • + Sign In +
  • + )} +
+
+
+
+ ); +} diff --git a/examples/shadcn/src/components/oauth-button.tsx b/examples/shadcn/src/components/oauth-button.tsx new file mode 100644 index 00000000..dd5d3740 --- /dev/null +++ b/examples/shadcn/src/components/oauth-button.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { useUI, type OAuthButtonProps, useSignInWithProvider } from "@firebase-ui/react"; +import { Button } from "@/components/ui/button"; + +export type { OAuthButtonProps }; + +export function OAuthButton({ provider, children, themed }: OAuthButtonProps) { + const ui = useUI(); + + const { error, callback } = useSignInWithProvider(provider); + + return ( +
+ + {error &&
{error}
} +
+ ); +} diff --git a/examples/shadcn/src/components/phone-auth-form.tsx b/examples/shadcn/src/components/phone-auth-form.tsx new file mode 100644 index 00000000..05dc2959 --- /dev/null +++ b/examples/shadcn/src/components/phone-auth-form.tsx @@ -0,0 +1,67 @@ +"use client"; + +import type { PhoneAuthNumberFormSchema } from "@firebase-ui/core"; +import { FirebaseUIError, getTranslation } from "@firebase-ui/core"; +import { + type PhoneAuthFormProps, + usePhoneAuthNumberFormSchema, + usePhoneNumberFormAction, + useUI, +} from "@firebase-ui/react"; +import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; +import { useForm } from "react-hook-form"; + +import { Button } from "@/components/ui/button"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Policies } from "./policies"; + +export type { PhoneAuthFormProps }; + +export function PhoneAuthForm(props: PhoneAuthFormProps) { + const ui = useUI(); + const schema = usePhoneAuthNumberFormSchema(); + const action = usePhoneNumberFormAction(); + + const form = useForm({ + resolver: standardSchemaResolver(schema), + defaultValues: { + phoneNumber: "", + }, + }); + + async function onSubmit(values: PhoneAuthNumberFormSchema) { + try { + const credential = await action(values); + props.onSignIn?.(credential); + } catch (error) { + const message = error instanceof FirebaseUIError ? error.message : String(error); + form.setError("root", { message }); + } + } + + return ( +
+ + ( + + {getTranslation(ui, "labels", "phoneNumber")} + + + + + + )} + /> + + + {form.formState.errors.root && {form.formState.errors.root.message}} + + + ); +} diff --git a/examples/shadcn/src/components/policies.tsx b/examples/shadcn/src/components/policies.tsx new file mode 100644 index 00000000..970cf37f --- /dev/null +++ b/examples/shadcn/src/components/policies.tsx @@ -0,0 +1,50 @@ +import { cn } from "@/lib/utils"; +import { getTranslation } from "@firebase-ui/core"; +import { useUI, PolicyContext } from "@firebase-ui/react"; +import { cloneElement, useContext } from "react"; + +export function Policies() { + const ui = useUI(); + const policies = useContext(PolicyContext); + + if (!policies) { + return null; + } + + const { termsOfServiceUrl, privacyPolicyUrl, onNavigate } = policies; + const termsAndPrivacyText = getTranslation(ui, "messages", "termsAndPrivacy"); + const parts = termsAndPrivacyText.split(/(\{tos\}|\{privacy\})/); + + const className = cn("hover:underline font-semibold"); + const Handler = onNavigate ? ( + + ) : null} + + + + + )} + /> + + + {form.formState.errors.root && {form.formState.errors.root.message}} + {props.onRegisterClick ? ( + <> + + + ) : null} + + + ); +} diff --git a/examples/shadcn/src/components/sign-in-auth-screen.tsx b/examples/shadcn/src/components/sign-in-auth-screen.tsx new file mode 100644 index 00000000..0b6cf4ea --- /dev/null +++ b/examples/shadcn/src/components/sign-in-auth-screen.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { getTranslation } from "@firebase-ui/core"; +import { useUI, type SignInAuthScreenProps } from "@firebase-ui/react"; + +import { SignInAuthForm } from "@/components/sign-in-auth-form"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; + +export type { SignInAuthScreenProps }; + +export function SignInAuthScreen({ children, ...props }: SignInAuthScreenProps) { + const ui = useUI(); + + const titleText = getTranslation(ui, "labels", "signIn"); + const subtitleText = getTranslation(ui, "prompts", "signInToAccount"); + + return ( +
+ + + {titleText} + {subtitleText} + + + + {children ? ( + <> + {getTranslation(ui, "messages", "dividerOr")} +
{children}
+ + ) : null} +
+
+
+ ); +} diff --git a/examples/shadcn/src/components/sign-up-auth-form.tsx b/examples/shadcn/src/components/sign-up-auth-form.tsx new file mode 100644 index 00000000..dc8ea78c --- /dev/null +++ b/examples/shadcn/src/components/sign-up-auth-form.tsx @@ -0,0 +1,104 @@ +"use client"; + +import type { SignUpAuthFormSchema } from "@firebase-ui/core"; +import { + useSignUpAuthFormAction, + useSignUpAuthFormSchema, + useUI, + type SignUpAuthFormProps, + useRequireDisplayName, +} from "@firebase-ui/react"; +import { useForm } from "react-hook-form"; +import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; +import { FirebaseUIError, getTranslation } from "@firebase-ui/core"; + +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Policies } from "./policies"; + +export type { SignUpAuthFormProps }; + +export function SignUpAuthForm(props: SignUpAuthFormProps) { + const ui = useUI(); + const schema = useSignUpAuthFormSchema(); + const action = useSignUpAuthFormAction(); + const requireDisplayName = useRequireDisplayName(); + + const form = useForm({ + resolver: standardSchemaResolver(schema), + defaultValues: { + email: "", + password: "", + displayName: requireDisplayName ? "" : undefined, + }, + }); + + async function onSubmit(values: SignUpAuthFormSchema) { + try { + const credential = await action(values); + props.onSignUp?.(credential); + } catch (error) { + const message = error instanceof FirebaseUIError ? error.message : String(error); + form.setError("root", { message }); + } + } + + return ( +
+ + ( + + {getTranslation(ui, "labels", "emailAddress")} + + + + + + )} + /> + ( + + {getTranslation(ui, "labels", "password")} + + + + + + )} + /> + {requireDisplayName ? ( + ( + + {getTranslation(ui, "labels", "displayName")} + + + + + + )} + /> + ) : null} + + + {form.formState.errors.root && {form.formState.errors.root.message}} + {props.onBackToSignInClick ? ( + + ) : null} + + + ); +} diff --git a/examples/shadcn/src/components/sign-up-auth-screen.tsx b/examples/shadcn/src/components/sign-up-auth-screen.tsx new file mode 100644 index 00000000..e12daf3a --- /dev/null +++ b/examples/shadcn/src/components/sign-up-auth-screen.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { getTranslation } from "@firebase-ui/core"; +import { useUI, type SignUpAuthScreenProps } from "@firebase-ui/react"; + +import { SignUpAuthForm } from "@/components/sign-up-auth-form"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; + +export type { SignUpAuthScreenProps }; + +export function SignUpAuthScreen({ children, ...props }: SignUpAuthScreenProps) { + const ui = useUI(); + + const titleText = getTranslation(ui, "labels", "register"); + const subtitleText = getTranslation(ui, "prompts", "enterDetailsToCreate"); + + return ( +
+ + + {titleText} + {subtitleText} + + + + {children ? ( + <> + {getTranslation(ui, "messages", "dividerOr")} +
{children}
+ + ) : null} +
+
+
+ ); +} diff --git a/examples/shadcn/src/components/ui/accordion.tsx b/examples/shadcn/src/components/ui/accordion.tsx new file mode 100644 index 00000000..14d143e0 --- /dev/null +++ b/examples/shadcn/src/components/ui/accordion.tsx @@ -0,0 +1,51 @@ +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDownIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +function Accordion({ ...props }: React.ComponentProps) { + return ; +} + +function AccordionItem({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +function AccordionTrigger({ className, children, ...props }: React.ComponentProps) { + return ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + + ); +} + +function AccordionContent({ className, children, ...props }: React.ComponentProps) { + return ( + +
{children}
+
+ ); +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/examples/shadcn/src/components/ui/alert-dialog.tsx b/examples/shadcn/src/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..271c9cff --- /dev/null +++ b/examples/shadcn/src/components/ui/alert-dialog.tsx @@ -0,0 +1,111 @@ +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; + +function AlertDialog({ ...props }: React.ComponentProps) { + return ; +} + +function AlertDialogTrigger({ ...props }: React.ComponentProps) { + return ; +} + +function AlertDialogPortal({ ...props }: React.ComponentProps) { + return ; +} + +function AlertDialogOverlay({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogContent({ className, ...props }: React.ComponentProps) { + return ( + + + + + ); +} + +function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogTitle({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogAction({ className, ...props }: React.ComponentProps) { + return ; +} + +function AlertDialogCancel({ className, ...props }: React.ComponentProps) { + return ; +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/examples/shadcn/src/components/ui/alert.tsx b/examples/shadcn/src/components/ui/alert.tsx new file mode 100644 index 00000000..c6f7846f --- /dev/null +++ b/examples/shadcn/src/components/ui/alert.tsx @@ -0,0 +1,49 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +function Alert({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps) { + return
; +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { Alert, AlertTitle, AlertDescription }; diff --git a/examples/shadcn/src/components/ui/aspect-ratio.tsx b/examples/shadcn/src/components/ui/aspect-ratio.tsx new file mode 100644 index 00000000..59d6b357 --- /dev/null +++ b/examples/shadcn/src/components/ui/aspect-ratio.tsx @@ -0,0 +1,9 @@ +"use client"; + +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; + +function AspectRatio({ ...props }: React.ComponentProps) { + return ; +} + +export { AspectRatio }; diff --git a/examples/shadcn/src/components/ui/avatar.tsx b/examples/shadcn/src/components/ui/avatar.tsx new file mode 100644 index 00000000..83b8ec6b --- /dev/null +++ b/examples/shadcn/src/components/ui/avatar.tsx @@ -0,0 +1,32 @@ +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "@/lib/utils"; + +function Avatar({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +function AvatarImage({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +function AvatarFallback({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/examples/shadcn/src/components/ui/badge.tsx b/examples/shadcn/src/components/ui/badge.tsx new file mode 100644 index 00000000..a6b124f7 --- /dev/null +++ b/examples/shadcn/src/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span"; + + return ; +} + +export { Badge, badgeVariants }; diff --git a/examples/shadcn/src/components/ui/breadcrumb.tsx b/examples/shadcn/src/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..ce08888f --- /dev/null +++ b/examples/shadcn/src/components/ui/breadcrumb.tsx @@ -0,0 +1,92 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { + return