Skip to content

Commit 819f8fc

Browse files
committed
chore(shadcn): Add shadcn deployment flow (firebase hosting)
1 parent bea5da3 commit 819f8fc

31 files changed

+684
-4
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ dist-ssr
2121
# Coverage
2222
coverage
2323

24+
# Firebase
25+
.firebase
26+
2427
# Editor directories and files
2528
.vscode/*
2629
!.vscode/extensions.json

firebase.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,11 @@
77
"enabled": true
88
},
99
"singleProjectMode": true
10-
}
10+
},
11+
"hosting": [
12+
{
13+
"site": "fir-ui-shadcn",
14+
"public": "packages/shadcn/public"
15+
}
16+
]
1117
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"build:react": "pnpm --filter=@firebase-ui/react run build",
1111
"build:angular": "pnpm --filter=@firebase-ui/angular run build",
1212
"build:shadcn": "pnpm --filter=@firebase-ui/shadcn run build",
13+
"shadcn:deploy": "pnpm run build:shadcn && firebase deploy --only hosting:fir-ui-shadcn",
1314
"lint:check": "eslint",
1415
"lint:fix": "eslint --fix",
1516
"format:check": "prettier --check **/{src,tests}/**/*.{ts,tsx}",

packages/shadcn/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
8-
"build": "tsx build.ts https://ui.firebase.com",
8+
"build": "tsx build.ts --domain https://fir-ui-shadcn.web.app",
99
"preview": "vite preview",
1010
"test": "vitest run"
1111
},
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "apple-sign-in-button",
4+
"type": "registry:block",
5+
"title": "Apple Sign In Button",
6+
"description": "A button component for Apple OAuth authentication.",
7+
"dependencies": [
8+
"@firebase-ui/react"
9+
],
10+
"registryDependencies": [
11+
"https://fir-ui-shadcn.web.app/oauth-button.json"
12+
],
13+
"files": [
14+
{
15+
"path": "src/registry/apple-sign-in-button.tsx",
16+
"content": "\"use client\";\n\nimport { OAuthProvider } from \"firebase/auth\";\nimport { getTranslation } from \"@firebase-ui/core\";\nimport { useUI, type AppleSignInButtonProps, AppleLogo } from \"@firebase-ui/react\";\n\nimport { OAuthButton } from \"@/registry/oauth-button\";\n\nexport type { AppleSignInButtonProps };\n\nexport function AppleSignInButton({ provider, themed }: AppleSignInButtonProps) {\n const ui = useUI();\n\n return (\n <OAuthButton provider={provider || new OAuthProvider(\"apple.com\")} themed={themed}>\n <AppleLogo />\n <span>{getTranslation(ui, \"labels\", \"signInWithApple\")}</span>\n </OAuthButton>\n );\n}\n",
17+
"type": "registry:component"
18+
}
19+
],
20+
"css": {
21+
"@layer components": {
22+
"button[data-provider='apple.com'][data-themed='true']": {
23+
"--apple-primary": "#000000",
24+
"--color-primary": "var(--apple-primary)",
25+
"--color-primary-hover": "--alpha(var(--apple-primary) / 85%)",
26+
"--color-primary-surface": "#FFFFFF",
27+
"--color-border": "var(--apple-primary)"
28+
}
29+
},
30+
"@variant dark": {
31+
"button[data-provider='apple.com'][data-themed='true']": {
32+
"--apple-primary": "var(--color-white)",
33+
"--color-primary": "var(--apple-primary)",
34+
"--color-primary-hover": "--alpha(var(--apple-primary) / 85%)",
35+
"--color-primary-surface": "var(--color-black)",
36+
"--color-border": "var(--color-white)"
37+
}
38+
}
39+
}
40+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "country-selector",
4+
"type": "registry:block",
5+
"title": "Country Selector",
6+
"description": "A country selector component for phone number input with country codes and flags.",
7+
"dependencies": [
8+
"@firebase-ui/react"
9+
],
10+
"registryDependencies": [
11+
"select"
12+
],
13+
"files": [
14+
{
15+
"path": "src/registry/country-selector.tsx",
16+
"content": "\"use client\";\n\nimport { forwardRef, useCallback, useImperativeHandle, useState } from \"react\";\nimport type { CountryCode, CountryData } from \"@firebase-ui/core\";\nimport {\n type CountrySelectorRef,\n type CountrySelectorProps,\n useCountries,\n useDefaultCountry,\n} from \"@firebase-ui/react\";\n\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\n\nexport type { CountrySelectorRef };\n\nexport const CountrySelector = forwardRef<CountrySelectorRef, CountrySelectorProps>((_props, ref) => {\n const countries = useCountries();\n const defaultCountry = useDefaultCountry();\n const [selected, setSelected] = useState<CountryData>(defaultCountry);\n\n const setCountry = useCallback(\n (code: CountryCode) => {\n const foundCountry = countries.find((country) => country.code === code);\n setSelected(foundCountry!);\n },\n [countries]\n );\n\n useImperativeHandle(\n ref,\n () => ({\n getCountry: () => selected,\n setCountry,\n }),\n [selected, setCountry]\n );\n\n return (\n <Select value={selected.code} onValueChange={setCountry}>\n <SelectTrigger className=\"w-[120px]\">\n <SelectValue>\n {selected.emoji} {selected.dialCode}\n </SelectValue>\n </SelectTrigger>\n <SelectContent>\n {countries.map((country) => (\n <SelectItem key={country.code} value={country.code}>\n {country.dialCode} ({country.name})\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n});\n\nCountrySelector.displayName = \"CountrySelector\";\n",
17+
"type": "registry:component"
18+
}
19+
]
20+
}
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": "email-link-auth-form",
4+
"type": "registry:block",
5+
"title": "Email Link Auth Form",
6+
"description": "A form allowing users to sign in via email link.",
7+
"dependencies": [
8+
"@firebase-ui/react"
9+
],
10+
"registryDependencies": [
11+
"input",
12+
"button",
13+
"form",
14+
"https://fir-ui-shadcn.web.app/policies.json"
15+
],
16+
"files": [
17+
{
18+
"path": "src/registry/email-link-auth-form.tsx",
19+
"content": "\"use client\";\n\nimport type { EmailLinkAuthFormSchema } from \"@firebase-ui/core\";\nimport {\n useUI,\n useEmailLinkAuthFormAction,\n useEmailLinkAuthFormSchema,\n useEmailLinkAuthFormCompleteSignIn,\n type EmailLinkAuthFormProps,\n} from \"@firebase-ui/react\";\nimport { useForm } from \"react-hook-form\";\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\nimport { FirebaseUIError, getTranslation } from \"@firebase-ui/core\";\nimport { useState } from \"react\";\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 { EmailLinkAuthFormProps };\n\nexport function EmailLinkAuthForm(props: EmailLinkAuthFormProps) {\n const { onEmailSent, onSignIn } = props;\n const ui = useUI();\n const schema = useEmailLinkAuthFormSchema();\n const action = useEmailLinkAuthFormAction();\n const [emailSent, setEmailSent] = useState(false);\n\n const form = useForm<EmailLinkAuthFormSchema>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n email: \"\",\n },\n });\n\n useEmailLinkAuthFormCompleteSignIn(onSignIn);\n\n async function onSubmit(values: EmailLinkAuthFormSchema) {\n try {\n await action(values);\n setEmailSent(true);\n onEmailSent?.();\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n }\n\n if (emailSent) {\n return (\n <div className=\"text-center space-y-4\">\n <div className=\"text-green-600 dark:text-green-400\">{getTranslation(ui, \"messages\", \"signInLinkSent\")}</div>\n </div>\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 <Policies />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"sendSignInLink\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n </form>\n </Form>\n );\n}\n",
20+
"type": "registry:component"
21+
}
22+
]
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "email-link-auth-screen",
4+
"type": "registry:block",
5+
"title": "Email Link Auth Screen",
6+
"description": "A screen allowing users to sign in via email link.",
7+
"dependencies": [
8+
"@firebase-ui/react"
9+
],
10+
"registryDependencies": [
11+
"separator",
12+
"card",
13+
"https://fir-ui-shadcn.web.app/email-link-auth-form.json"
14+
],
15+
"files": [
16+
{
17+
"path": "src/registry/email-link-auth-screen.tsx",
18+
"content": "\"use client\";\n\nimport { getTranslation } from \"@firebase-ui/core\";\nimport { useUI, type EmailLinkAuthScreenProps } from \"@firebase-ui/react\";\n\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { EmailLinkAuthForm } from \"@/registry/email-link-auth-form\";\n\nexport type { EmailLinkAuthScreenProps };\n\nexport function EmailLinkAuthScreen({ children, ...props }: EmailLinkAuthScreenProps) {\n const ui = useUI();\n\n const titleText = getTranslation(ui, \"labels\", \"signIn\");\n const subtitleText = getTranslation(ui, \"prompts\", \"signInToAccount\");\n\n return (\n <div className=\"max-w-md mx-auto\">\n <Card>\n <CardHeader>\n <CardTitle>{titleText}</CardTitle>\n <CardDescription>{subtitleText}</CardDescription>\n </CardHeader>\n <CardContent>\n <EmailLinkAuthForm {...props} />\n {children ? (\n <>\n <Separator>{getTranslation(ui, \"messages\", \"dividerOr\")}</Separator>\n <div className=\"space-y-2\">{children}</div>\n </>\n ) : null}\n </CardContent>\n </Card>\n </div>\n );\n}\n",
19+
"type": "registry:component"
20+
}
21+
]
22+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
3+
"name": "facebook-sign-in-button",
4+
"type": "registry:block",
5+
"title": "Facebook Sign In Button",
6+
"description": "A button component for Facebook OAuth authentication.",
7+
"dependencies": [
8+
"@firebase-ui/react"
9+
],
10+
"registryDependencies": [
11+
"https://fir-ui-shadcn.web.app/oauth-button.json"
12+
],
13+
"files": [
14+
{
15+
"path": "src/registry/facebook-sign-in-button.tsx",
16+
"content": "\"use client\";\n\nimport { FacebookAuthProvider } from \"firebase/auth\";\nimport { getTranslation } from \"@firebase-ui/core\";\nimport { useUI, type FacebookSignInButtonProps, FacebookLogo } from \"@firebase-ui/react\";\n\nimport { OAuthButton } from \"@/registry/oauth-button\";\n\nexport type { FacebookSignInButtonProps };\n\nexport function FacebookSignInButton({ provider, themed }: FacebookSignInButtonProps) {\n const ui = useUI();\n\n return (\n <OAuthButton provider={provider || new FacebookAuthProvider()} themed={themed}>\n <FacebookLogo />\n <span>{getTranslation(ui, \"labels\", \"signInWithFacebook\")}</span>\n </OAuthButton>\n );\n}\n",
17+
"type": "registry:component"
18+
}
19+
],
20+
"css": {
21+
"@layer components": {
22+
"button[data-provider='facebook.com'][data-themed='true']": {
23+
"--facebook-primary": "#1877F2",
24+
"--color-primary": "var(--facebook-primary)",
25+
"--color-primary-hover": "--alpha(var(--facebook-primary) / 85%)",
26+
"--color-primary-surface": "var(--color-white)",
27+
"--color-border": "var(--facebook-primary)"
28+
}
29+
}
30+
}
31+
}
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": "forgot-password-auth-form",
4+
"type": "registry:block",
5+
"title": "Forgot Password Auth Form",
6+
"description": "A form allowing users to reset their password via email.",
7+
"dependencies": [
8+
"@firebase-ui/react"
9+
],
10+
"registryDependencies": [
11+
"input",
12+
"button",
13+
"form",
14+
"https://fir-ui-shadcn.web.app/policies.json"
15+
],
16+
"files": [
17+
{
18+
"path": "src/registry/forgot-password-auth-form.tsx",
19+
"content": "\"use client\";\n\nimport type { ForgotPasswordAuthFormSchema } from \"@firebase-ui/core\";\nimport {\n useForgotPasswordAuthFormAction,\n useForgotPasswordAuthFormSchema,\n useUI,\n type ForgotPasswordAuthFormProps,\n} from \"@firebase-ui/react\";\nimport { useForm } from \"react-hook-form\";\nimport { standardSchemaResolver } from \"@hookform/resolvers/standard-schema\";\nimport { FirebaseUIError, getTranslation } from \"@firebase-ui/core\";\nimport { useState } from \"react\";\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 { ForgotPasswordAuthFormProps };\n\nexport function ForgotPasswordAuthForm(props: ForgotPasswordAuthFormProps) {\n const ui = useUI();\n const schema = useForgotPasswordAuthFormSchema();\n const action = useForgotPasswordAuthFormAction();\n const [emailSent, setEmailSent] = useState(false);\n\n const form = useForm<ForgotPasswordAuthFormSchema>({\n resolver: standardSchemaResolver(schema),\n defaultValues: {\n email: \"\",\n },\n });\n\n async function onSubmit(values: ForgotPasswordAuthFormSchema) {\n try {\n await action(values);\n setEmailSent(true);\n props.onPasswordSent?.();\n } catch (error) {\n const message = error instanceof FirebaseUIError ? error.message : String(error);\n form.setError(\"root\", { message });\n }\n }\n\n if (emailSent) {\n return (\n <div className=\"text-center space-y-4\">\n <div className=\"text-green-600 dark:text-green-400\">{getTranslation(ui, \"messages\", \"checkEmailForReset\")}</div>\n </div>\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 <Policies />\n <Button type=\"submit\" disabled={ui.state !== \"idle\"}>\n {getTranslation(ui, \"labels\", \"resetPassword\")}\n </Button>\n {form.formState.errors.root && <FormMessage>{form.formState.errors.root.message}</FormMessage>}\n {props.onBackToSignInClick ? (\n <Button type=\"button\" variant=\"secondary\" onClick={props.onBackToSignInClick}>\n {getTranslation(ui, \"labels\", \"backToSignIn\")}\n </Button>\n ) : null}\n </form>\n </Form>\n );\n}\n",
20+
"type": "registry:component"
21+
}
22+
]
23+
}

0 commit comments

Comments
 (0)