Skip to content

Commit 5acc1ad

Browse files
committed
feat(shadcn): Add basic example + build script
1 parent 2c8241e commit 5acc1ad

File tree

13 files changed

+338
-173
lines changed

13 files changed

+338
-173
lines changed

packages/react/src/components/policies.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export interface PolicyProps {
2626
onNavigate?: (url: PolicyURL) => void;
2727
}
2828

29-
const PolicyContext = createContext<PolicyProps | undefined>(undefined);
29+
export const PolicyContext = createContext<PolicyProps | undefined>(undefined);
3030

3131
export function PolicyProvider({ children, policies }: { children: React.ReactNode; policies?: PolicyProps }) {
3232
return <PolicyContext.Provider value={policies}>{children}</PolicyContext.Provider>;

packages/react/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import { registerFramework } from "@firebase-ui/core";
1919
import pkgJson from "../package.json";
2020

21+
export { PolicyContext } from "./components/policies";
2122
export * from "./auth";
2223
export * from "./hooks";
2324
export * from "./components";

packages/shadcn/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Shadcn registry is generated by the build script
2+
registry.json
3+
14
# Logs
25
logs
36
*.log

packages/shadcn/build.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import fs from "fs";
2+
import path from "path";
3+
import { execSync } from "child_process";
4+
5+
// Get the domain from CLI args
6+
const [, , domain] = process.argv;
7+
8+
if (!domain) {
9+
console.error("Missing domain argument");
10+
process.exit(1);
11+
}
12+
13+
const registryPath = path.resolve("registry-spec.json");
14+
const registryRaw = fs.readFileSync(registryPath, "utf8");
15+
16+
const replaced = registryRaw.replace(/{{\s*DOMAIN\s*}}/g, domain);
17+
fs.writeFileSync("registry.json", replaced, "utf8");
18+
19+
const publicRDir = path.resolve("public", "r");
20+
if (fs.existsSync(publicRDir)) {
21+
execSync("rm -rf " + publicRDir, { stdio: "inherit" });
22+
}
23+
24+
try {
25+
execSync("shadcn build", { stdio: "inherit" });
26+
} finally {
27+
execSync("rm registry.json");
28+
}

packages/shadcn/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
8-
"build": "tsc -b && vite build",
8+
"build": "tsx build.ts https://ui.firebase.com",
99
"lint": "eslint .",
1010
"preview": "vite preview"
1111
},
@@ -21,6 +21,7 @@
2121
"tailwindcss": "catalog:",
2222
"tw-animate-css": "^1.4.0",
2323
"typescript": "catalog:",
24+
"tsx": "^4.20.6",
2425
"vite": "catalog:"
2526
},
2627
"dependencies": {

packages/shadcn/public/r/hello-world.json

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "policies",
4+
"type": "registry:block",
5+
"title": "Policies",
6+
"description": "A component allowing users to navigate to the terms of service and privacy policy.",
7+
"dependencies": [
8+
"@firebase-ui/react"
9+
],
10+
"files": [
11+
{
12+
"path": "src/registry/policies.tsx",
13+
"content": "import { cn } from \"@/lib/utils\";\nimport { getTranslation } from \"@firebase-ui/core\";\nimport { useUI, PolicyContext } from \"@firebase-ui/react\";\nimport { cloneElement, useContext } from \"react\";\n\nexport function Policies() {\n const ui = useUI();\n const policies = useContext(PolicyContext);\n\n if (!policies) {\n return null;\n }\n\n const { termsOfServiceUrl, privacyPolicyUrl, onNavigate } = policies;\n const termsAndPrivacyText = getTranslation(ui, \"messages\", \"termsAndPrivacy\");\n const parts = termsAndPrivacyText.split(/(\\{tos\\}|\\{privacy\\})/);\n\n const className = cn(\"hover:underline font-semibold\");\n const Handler = onNavigate ? (\n <button className={className} />\n ) : (\n <a target=\"_blank\" rel=\"noopener noreferrer\" className={className} />\n );\n\n return (\n <div className=\"text-text-muted text-center text-xs\">\n {parts.map((part: string, index: number) => {\n if (part === \"{tos}\") {\n return cloneElement(Handler, {\n key: index,\n onClick: onNavigate ? () => onNavigate(termsOfServiceUrl) : undefined,\n href: onNavigate ? undefined : termsOfServiceUrl,\n children: getTranslation(ui, \"labels\", \"termsOfService\"),\n });\n }\n\n if (part === \"{privacy}\") {\n return cloneElement(Handler, {\n key: index,\n onClick: onNavigate ? () => onNavigate(privacyPolicyUrl) : undefined,\n href: onNavigate ? undefined : privacyPolicyUrl,\n children: getTranslation(ui, \"labels\", \"privacyPolicy\"),\n });\n }\n\n return <span key={index}>{part}</span>;\n })}\n </div>\n );\n}\n",
14+
"type": "registry:component"
15+
}
16+
]
17+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "sign-in-auth-form",
4+
"type": "registry:block",
5+
"title": "Sign In Auth Form",
6+
"description": "A form allowing users to sign in with email and password.",
7+
"dependencies": [
8+
"@firebase-ui/react"
9+
],
10+
"registryDependencies": [
11+
"input",
12+
"button",
13+
"form",
14+
"https://ui.firebase.com/r/policies"
15+
],
16+
"files": [
17+
{
18+
"path": "src/registry/sign-in-auth-form.tsx",
19+
"content": "\"use client\";\n\nimport type { SignInAuthFormSchema } from \"@firebase-ui/core\";\nimport { useSignInAuthFormAction, useSignInAuthFormSchema, useUI, SignInAuthFormProps } from \"@firebase-ui/react\";\nimport { useForm } from \"react-hook-form\";\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\nimport { FirebaseUIError, getTranslation } from \"@firebase-ui/core\";\n\nimport { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Button } from \"@/components/ui/button\";\nimport { Policies } from \"./policies\";\n\nexport type { SignInAuthFormProps };\n\nexport function SignInAuthForm(props: SignInAuthFormProps) {\n const ui = useUI();\n const schema = useSignInAuthFormSchema();\n const action = useSignInAuthFormAction();\n\n const form = useForm<SignInAuthFormSchema>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n email: \"\",\n password: \"\",\n },\n });\n\n async function onSubmit(values: SignInAuthFormSchema) {\n try {\n const credential = await action(values);\n props.onSignIn?.(credential);\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n }\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-2\">\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"emailAddress\")}</FormLabel>\n <FormControl>\n <Input {...field} type=\"email\" />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <FormField\n control={form.control}\n name=\"password\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{getTranslation(ui, \"labels\", \"password\")}</FormLabel>\n <FormControl>\n <div className=\"flex items-center gap-2\">\n <Input {...field} type=\"password\" className=\"flex-grow\" />\n {props.onForgotPasswordClick ? (\n <Button type=\"button\" variant=\"secondary\" onClick={props.onForgotPasswordClick}>\n {getTranslation(ui, \"labels\", \"forgotPassword\")}\n </Button>\n ) : null}\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Policies />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"signIn\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n {props.onRegisterClick ? (\n <>\n <Button type=\"button\" variant=\"secondary\" onClick={props.onRegisterClick}>\n {getTranslation(ui, \"prompts\", \"noAccount\")} {getTranslation(ui, \"labels\", \"register\")}\n </Button>\n </>\n ) : null}\n </form>\n </Form>\n );\n}\n",
20+
"type": "registry:component"
21+
}
22+
]
23+
}

packages/shadcn/registry-spec.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry.json",
3+
"name": "acme",
4+
"homepage": "{{ DOMAIN }}",
5+
"items": [
6+
{
7+
"name": "sign-in-auth-form",
8+
"type": "registry:block",
9+
"title": "Sign In Auth Form",
10+
"description": "A form allowing users to sign in with email and password.",
11+
"dependencies": ["@firebase-ui/react"],
12+
"registryDependencies": ["input", "button", "form", "{{ DOMAIN }}/r/policies"],
13+
"files": [
14+
{
15+
"path": "src/registry/sign-in-auth-form.tsx",
16+
"type": "registry:component"
17+
}
18+
]
19+
},
20+
{
21+
"name": "policies",
22+
"type": "registry:block",
23+
"title": "Policies",
24+
"description": "A component allowing users to navigate to the terms of service and privacy policy.",
25+
"dependencies": ["@firebase-ui/react"],
26+
"files": [
27+
{
28+
"path": "src/registry/policies.tsx",
29+
"type": "registry:component"
30+
}
31+
]
32+
}
33+
]
34+
}

packages/shadcn/registry.json

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)