Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3ef88da
chore(shadcn): Setup initial workspace package
Ehesp Oct 6, 2025
2c8241e
feat(shadcn): Add basic sign-in-auth-form example
Ehesp Oct 6, 2025
5acc1ad
feat(shadcn): Add basic example + build script
Ehesp Oct 6, 2025
7d9262f
chore: Update readme
Ehesp Oct 6, 2025
ce065f2
chore: Formatting
Ehesp Oct 6, 2025
b9cda45
test(shadcn): Add tests
Ehesp Oct 6, 2025
f0c752a
feat(shadcn): Add local dev setup & example flow
Ehesp Oct 6, 2025
b55a07c
chore: Formatting
Ehesp Oct 6, 2025
0a35684
chore: Remove .only test
Ehesp Oct 6, 2025
79b8ff9
test(shadcn): Add sign in auth screen test example
Ehesp Oct 7, 2025
a36672e
test(shadcn): Add registry spec tests
Ehesp Oct 7, 2025
98b1495
feat(shadcn): Sign up components
Ehesp Oct 7, 2025
05125f7
feat(shadcn): Forgot password components
Ehesp Oct 7, 2025
738e908
test(shadcn): Update component tests
Ehesp Oct 7, 2025
009b1da
feat(shadcn): Email link auth components
Ehesp Oct 7, 2025
7d2be39
Merge branch '@invertase/v7-development' of https://github.com/fireba…
Ehesp Oct 7, 2025
06fee7c
Merge branch '@invertase/shadcn-registry' of https://github.com/fireb…
Ehesp Oct 7, 2025
7aa8d58
feat(shadcn): OAuth & GoogleSignInButton
Ehesp Oct 7, 2025
bb4f91f
feat(shadcn): Apply themed button styles via registry
Ehesp Oct 7, 2025
710560a
feat(shadcn): Firebase sign in button
Ehesp Oct 8, 2025
954f4c3
feat(shadcn): GitHub sign in button
Ehesp Oct 8, 2025
7cad353
feat(shadcn): Apple sign in button
Ehesp Oct 8, 2025
edb48a9
feat(shadcn): Microsoft sign in button
Ehesp Oct 8, 2025
ea958f6
feat(shadcn): Twitter sign in button
Ehesp Oct 8, 2025
5a7e296
Merge branch '@invertase/v7-development' of https://github.com/fireba…
Ehesp Oct 8, 2025
43e1fe3
Merge branch '@invertase/shadcn-registry' of https://github.com/fireb…
Ehesp Oct 8, 2025
7428e09
feat(shadcn): CountrySelector component
Ehesp Oct 8, 2025
555ee6a
fix: Add CountrySelector to registry
Ehesp Oct 8, 2025
8dfd52a
chore: Formatting
Ehesp Oct 8, 2025
666be94
feat(shadcn): WIP Phone auth form
Ehesp Oct 8, 2025
d8abce0
Merge branch '@invertase/v7-development' of https://github.com/fireba…
Ehesp Oct 17, 2025
5f8e0a2
Merge branch '@invertase/shadcn-registry' of https://github.com/fireb…
Ehesp Oct 17, 2025
d097e07
feat(shadcn): Phone auth form tests
Ehesp Oct 17, 2025
2771504
chore(shadcn): Formatting / linting
Ehesp Oct 17, 2025
9b32d5a
feat(shadn):added example application
dackers86 Oct 22, 2025
d6b61c4
fix(shadn): updated screen component styling
dackers86 Oct 22, 2025
1cd1049
Merge branch '@invertase/shadcn-registry' into @invertase/shadcn-comp…
Ehesp Oct 27, 2025
14a5218
Merge pull request #1223 from firebase/@invertase/shadcn-components
Ehesp Oct 27, 2025
ba69050
chore(shadcn): Add OTP component
Ehesp Oct 27, 2025
604e015
Merge branch '@invertase/v7-development' of https://github.com/fireba…
Ehesp Oct 27, 2025
55b7ddb
feat(shadcn): MFA Enrollment
Ehesp Oct 27, 2025
f97c551
feat(shadcn): Assertion flows
Ehesp Oct 27, 2025
e6eed4d
fix(shadcn): Align remaining components
Ehesp Oct 27, 2025
bea5da3
test(shadcn): Update shadcn components to mock otp w/ window
Ehesp Oct 27, 2025
819f8fc
chore(shadcn): Add shadcn deployment flow (firebase hosting)
Ehesp Oct 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ dist-ssr
# Coverage
coverage

# Firebase
.firebase

# Editor directories and files
.vscode/*
!.vscode/extensions.json
Expand Down
3 changes: 2 additions & 1 deletion eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const config: any[] = [
"**/shadcn/public-dev/**",
"packages/styles/dist.css",
"packages/angular/**",
"packages/shadcn/public",
]),
...tseslint.configs.recommended,
{
Expand Down Expand Up @@ -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: {
Expand Down
24 changes: 24 additions & 0 deletions examples/shadcn/.gitignore
Original file line number Diff line number Diff line change
@@ -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?
1 change: 1 addition & 0 deletions examples/shadcn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TODO
39 changes: 39 additions & 0 deletions examples/shadcn/add-all.ts
Original file line number Diff line number Diff line change
@@ -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);
}
);
25 changes: 25 additions & 0 deletions examples/shadcn/components.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
14 changes: 14 additions & 0 deletions examples/shadcn/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>shadcn</title>
<link rel="stylesheet" href="src/index.css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
77 changes: 77 additions & 0 deletions examples/shadcn/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
1 change: 1 addition & 0 deletions examples/shadcn/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
91 changes: 91 additions & 0 deletions examples/shadcn/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="p-8 ">
<h1 className="text-3xl font-bold mb-6">Firebase UI Demo</h1>
<div className="mb-6">{user && <div>Welcome: {user.email || user.phoneNumber}</div>}</div>
<div>
<h2 className="text-2xl font-bold mb-4">Auth Screens</h2>
<ul className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<li>
<NavLink to="/screens/sign-in-auth-screen" className="text-blue-500 hover:underline">
Sign In Auth Screen
</NavLink>
</li>
<li>
<NavLink to="/screens/sign-in-auth-screen-w-handlers" className="text-blue-500 hover:underline">
Sign In Auth Screen with Handlers
</NavLink>
</li>
<li>
<NavLink to="/screens/sign-in-auth-screen-w-oauth" className="text-blue-500 hover:underline">
Sign In Auth Screen with OAuth
</NavLink>
</li>
<li>
<NavLink to="/screens/email-link-auth-screen" className="text-blue-500 hover:underline">
Email Link Auth Screen
</NavLink>
</li>
<li>
<NavLink to="/screens/email-link-auth-screen-w-oauth" className="text-blue-500 hover:underline">
Email Link Auth Screen with OAuth
</NavLink>
</li>
<li>
<NavLink to="/screens/phone-auth-screen" className="text-blue-500 hover:underline">
Phone Auth Screen
</NavLink>
</li>
<li>
<NavLink to="/screens/phone-auth-screen-w-oauth" className="text-blue-500 hover:underline">
Phone Auth Screen with OAuth
</NavLink>
</li>
<li>
<NavLink to="/screens/sign-up-auth-screen" className="text-blue-500 hover:underline">
Sign Up Auth Screen
</NavLink>
</li>
<li>
<NavLink to="/screens/sign-up-auth-screen-w-oauth" className="text-blue-500 hover:underline">
Sign Up Auth Screen with OAuth
</NavLink>
</li>
<li>
<NavLink to="/screens/oauth-screen" className="text-blue-500 hover:underline">
OAuth Screen
</NavLink>
</li>
<li>
<NavLink to="/screens/password-reset-screen" className="text-blue-500 hover:underline">
Password Reset Screen
</NavLink>
</li>
</ul>
</div>
</div>
);
}

export default App;
1 change: 1 addition & 0 deletions examples/shadcn/src/assets/react.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
83 changes: 83 additions & 0 deletions examples/shadcn/src/components/email-link-auth-form.tsx
Original file line number Diff line number Diff line change
@@ -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<EmailLinkAuthFormSchema>({
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 (
<div className="text-center space-y-4">
<div className="text-green-600 dark:text-green-400">{getTranslation(ui, "messages", "signInLinkSent")}</div>
</div>
);
}

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>{getTranslation(ui, "labels", "emailAddress")}</FormLabel>
<FormControl>
<Input {...field} type="email" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Policies />
<Button type="submit" disabled={ui.state !== "idle"}>
{getTranslation(ui, "labels", "sendSignInLink")}
</Button>
{form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}
</form>
</Form>
);
}
Loading