refactor(auth): migrate remaining styles to tailwind

This commit is contained in:
2026-07-11 13:17:24 +08:00
parent b7c4ba9b4e
commit e1ad5705c2
11 changed files with 129 additions and 408 deletions
@@ -2,15 +2,26 @@ import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { AuthBackground } from "../auth-background"; import { AuthBackground } from "../auth-background";
import { AuthEmailPanel } from "../auth-email-panel";
import { AuthErrorMessage } from "../auth-error-message"; import { AuthErrorMessage } from "../auth-error-message";
import { AuthFacebookPanel } from "../auth-facebook-panel";
import { AuthLegalText } from "../auth-legal-text"; import { AuthLegalText } from "../auth-legal-text";
import { AuthPrimaryButton } from "../auth-primary-button";
import { AuthSocialButton } from "../auth-social-button"; import { AuthSocialButton } from "../auth-social-button";
import { AuthSocialButtons } from "../auth-social-buttons";
import { AuthTextField } from "../auth-text-field"; import { AuthTextField } from "../auth-text-field";
import { EmailLoginForm } from "../email-login-form"; import { EmailLoginForm } from "../email-login-form";
import { EmailRegisterForm } from "../email-register-form"; import { EmailRegisterForm } from "../email-register-form";
vi.mock("@/stores/auth/auth-context", () => ({ vi.mock("@/stores/auth/auth-context", () => ({
useAuthDispatch: () => vi.fn(), useAuthDispatch: () => vi.fn(),
useAuthState: () => ({
authPanelMode: "facebook",
isLoading: false,
errorMessage: null,
loginStatus: "notLoggedIn",
hasInitialized: true,
}),
})); }));
describe("auth Tailwind components", () => { describe("auth Tailwind components", () => {
@@ -23,6 +34,17 @@ describe("auth Tailwind components", () => {
expect(html).toContain("Invalid email"); expect(html).toContain("Invalid email");
}); });
it("renders AuthPrimaryButton with Tailwind gradient and spinner", () => {
const html = renderToStaticMarkup(
<AuthPrimaryButton isLoading>Continue</AuthPrimaryButton>,
);
expect(html).toContain("min-h-[var(--auth-field-height)]");
expect(html).toContain("bg-[linear-gradient");
expect(html).toContain("animate-spin");
expect(html).toContain("Continue");
});
it("renders EmailLoginForm with Tailwind form layout", () => { it("renders EmailLoginForm with Tailwind form layout", () => {
const html = renderToStaticMarkup(<EmailLoginForm />); const html = renderToStaticMarkup(<EmailLoginForm />);
@@ -64,6 +86,24 @@ describe("auth Tailwind components", () => {
expect(html).toContain("Continue"); expect(html).toContain("Continue");
}); });
it("renders AuthSocialButtons with provider-specific Tailwind styles", () => {
const html = renderToStaticMarkup(
<AuthSocialButtons
onFacebook={() => {}}
onGoogle={() => {}}
onApple={() => {}}
showApple
/>,
);
expect(html).toContain("bg-facebook-blue");
expect(html).toContain("bg-white");
expect(html).toContain("bg-black");
expect(html).toContain("Continue with Facebook");
expect(html).toContain("Continue with Google");
expect(html).toContain("Continue with Apple");
});
it("renders AuthLegalText with Tailwind checkbox and links", () => { it("renders AuthLegalText with Tailwind checkbox and links", () => {
const html = renderToStaticMarkup(<AuthLegalText />); const html = renderToStaticMarkup(<AuthLegalText />);
@@ -90,4 +130,25 @@ describe("auth Tailwind components", () => {
expect(html).toContain("text-auth-text-primary"); expect(html).toContain("text-auth-text-primary");
expect(html).toContain("Invalid email"); expect(html).toContain("Invalid email");
}); });
it("renders AuthEmailPanel with Tailwind panel layout", () => {
const html = renderToStaticMarkup(
<AuthEmailPanel onSwitchToFacebook={() => {}} />,
);
expect(html).toContain("flex min-h-0 w-full flex-auto flex-col");
expect(html).toContain("h-xxl");
expect(html).toContain("Other Sign In Options");
});
it("renders AuthFacebookPanel with Tailwind Facebook button", () => {
const html = renderToStaticMarkup(
<AuthFacebookPanel onSwitchToEmail={() => {}} />,
);
expect(html).toContain("Login with Facebook");
expect(html).toContain("bg-auth-surface");
expect(html).toContain("enabled:hover:brightness-[0.97]");
expect(html).toContain("Other Sign In Options");
});
}); });
@@ -1,75 +0,0 @@
/*
*
* 视觉规格(与 Dart 对齐):
* - 顶部:login 模式有 logo + 24px spacerregister 模式两个 24px spacer
* - 表单 + 12px gap + 切换链接 + 16px gap + 弹层链接
* - Spacer + 法律
* - 切换链接:14px 黑色下划线居中
* - "Other Sign In Options" 链接:14px 黑色下划线居中
*/
.panel {
display: flex;
flex-direction: column;
align-items: stretch;
flex: 1 1 auto;
width: 100%;
min-height: 0;
}
.topSection {
display: flex;
flex-direction: column;
align-items: stretch;
}
.spacer24 {
height: var(--spacing-xxl);
}
.logo {
display: block;
height: clamp(92px, 22.222vw, var(--auth-logo-height));
width: auto;
max-width: min(56vw, 220px);
margin: 0 auto;
object-fit: contain;
}
.spacer {
flex: 1 1 0;
min-height: var(--page-section-gap, var(--spacing-lg));
}
.toggleButton {
align-self: center;
background: none;
border: none;
padding: var(--spacing-md) 0 0;
font-size: var(--responsive-body, var(--font-size-md));
color: var(--color-auth-text-primary);
text-decoration: underline;
text-underline-offset: 3px;
cursor: pointer;
transition: opacity 0.15s ease;
}
.toggleButton:hover {
opacity: 0.8;
}
.linkButton {
align-self: center;
background: none;
border: none;
padding: var(--spacing-md) 0 var(--spacing-md);
font-size: var(--responsive-body, var(--font-size-md));
color: var(--color-auth-text-primary);
text-decoration: underline;
text-underline-offset: 3px;
cursor: pointer;
transition: opacity 0.15s ease;
}
.linkButton:hover {
opacity: 0.8;
}
+10 -11
View File
@@ -23,7 +23,6 @@ import { AuthLegalText } from "./auth-legal-text";
import { AuthOtherOptionsDialog } from "./auth-other-options-dialog"; import { AuthOtherOptionsDialog } from "./auth-other-options-dialog";
import { EmailLoginForm } from "./email-login-form"; import { EmailLoginForm } from "./email-login-form";
import { EmailRegisterForm } from "./email-register-form"; import { EmailRegisterForm } from "./email-register-form";
import styles from "./auth-email-panel.module.css";
export interface AuthEmailPanelProps { export interface AuthEmailPanelProps {
/** "Other Sign-in Options" 弹层中 Facebook 按钮回调(派发回 facebook 模式) */ /** "Other Sign-in Options" 弹层中 Facebook 按钮回调(派发回 facebook 模式) */
@@ -44,28 +43,28 @@ export function AuthEmailPanel({
const [showOptions, setShowOptions] = useState(false); const [showOptions, setShowOptions] = useState(false);
return ( return (
<div className={styles.panel}> <div className="flex min-h-0 w-full flex-auto flex-col items-stretch">
<div className={styles.topSection}> <div className="flex flex-col items-stretch">
{mode === "login" ? ( {mode === "login" ? (
<> <>
<div className={styles.spacer24} /> <div className="h-xxl" />
<Image <Image
src="/images/auth/ic-logo-login.png" src="/images/auth/ic-logo-login.png"
alt="Cozsweet" alt="Cozsweet"
width={120} width={120}
height={120} height={120}
className={styles.logo} className="mx-auto block h-[clamp(92px,22.222vw,var(--auth-logo-height))] max-w-[min(56vw,220px)] object-contain"
style={{ width: "auto", height: "auto" }} style={{ width: "auto", height: "auto" }}
priority priority
/> />
</> </>
) : ( ) : (
<> <>
<div className={styles.spacer24} /> <div className="h-xxl" />
<div className={styles.spacer24} /> <div className="h-xxl" />
</> </>
)} )}
<div className={styles.spacer24} /> <div className="h-xxl" />
</div> </div>
{mode === "login" ? ( {mode === "login" ? (
@@ -84,7 +83,7 @@ export function AuthEmailPanel({
<button <button
type="button" type="button"
className={styles.toggleButton} className="self-center border-0 bg-transparent pt-md text-[var(--responsive-body)] text-auth-text-primary underline underline-offset-[3px] transition-opacity duration-150 hover:opacity-80"
onClick={() => setMode((m) => (m === "login" ? "register" : "login"))} onClick={() => setMode((m) => (m === "login" ? "register" : "login"))}
> >
{mode === "login" {mode === "login"
@@ -94,13 +93,13 @@ export function AuthEmailPanel({
<button <button
type="button" type="button"
className={styles.linkButton} className="self-center border-0 bg-transparent py-md text-[var(--responsive-body)] text-auth-text-primary underline underline-offset-[3px] transition-opacity duration-150 hover:opacity-80"
onClick={() => setShowOptions(true)} onClick={() => setShowOptions(true)}
> >
Other Sign In Options Other Sign In Options
</button> </button>
<div className={styles.spacer} /> <div className="min-h-[var(--page-section-gap,var(--spacing-lg))] flex-[1_1_0]" />
<AuthLegalText /> <AuthLegalText />
<AuthOtherOptionsDialog <AuthOtherOptionsDialog
@@ -1,111 +0,0 @@
/*
*
* 视觉规格(与 Dart 对齐):
* - Spacer → logo120h)→ Spacer → Facebook 主按钮 → 链接 → Spacer → 法律
* - Facebook 主按钮:46px 白胶囊 + Facebook "f" 图标(#1877f2
* - 链接:14px 黑色下划线居中
*/
.panel {
display: flex;
flex-direction: column;
align-items: stretch;
flex: 1 1 auto;
width: 100%;
min-height: 0;
}
.spacer {
flex: 1 1 0;
min-height: var(--page-section-gap, var(--spacing-lg));
}
.logo {
display: block;
height: clamp(92px, 22.222vw, var(--auth-logo-height));
width: auto;
max-width: min(56vw, 220px);
margin: 0 auto;
object-fit: contain;
}
.facebookButton {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-md);
width: 100%;
min-height: var(--auth-field-height);
padding: 0 var(--spacing-lg);
border: none;
border-radius: var(--radius-full, 999px);
background: var(--color-auth-surface);
color: var(--color-auth-text-primary);
font-size: var(--responsive-body, var(--font-size-md));
font-weight: 600;
cursor: pointer;
margin-top: var(--spacing-md);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
transition: filter 0.15s ease, transform 0.05s ease, opacity 0.15s ease;
}
.facebookButton:hover:not(:disabled) {
filter: brightness(0.97);
}
.facebookButton:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.facebookButton:not(:disabled):active {
transform: scale(0.98);
}
.facebookIcon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
flex: 0 0 auto;
}
.facebookLabel {
flex: 0 0 auto;
text-align: center;
}
.spinner {
display: inline-block;
width: var(--responsive-spinner-size, 18px);
height: var(--responsive-spinner-size, 18px);
border-width: 2px;
border-style: solid;
border-color: var(--color-facebook-blue) transparent transparent transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
flex: 0 0 auto;
}
.linkButton {
align-self: center;
background: none;
border: none;
padding: var(--spacing-md) 0;
font-size: var(--responsive-body, var(--font-size-md));
color: var(--color-auth-text-primary);
text-decoration: underline;
text-underline-offset: 3px;
cursor: pointer;
transition: opacity 0.15s ease;
}
.linkButton:hover {
opacity: 0.8;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
+16 -11
View File
@@ -21,7 +21,6 @@ import { AuthErrorMessage } from "./auth-error-message";
import { AuthLegalText } from "./auth-legal-text"; import { AuthLegalText } from "./auth-legal-text";
import { AuthOtherOptionsDialog } from "./auth-other-options-dialog"; import { AuthOtherOptionsDialog } from "./auth-other-options-dialog";
import { AuthProviderIcon } from "./auth-provider-icon"; import { AuthProviderIcon } from "./auth-provider-icon";
import styles from "./auth-facebook-panel.module.css";
export interface AuthFacebookPanelProps { export interface AuthFacebookPanelProps {
onSwitchToEmail: () => void; onSwitchToEmail: () => void;
@@ -49,46 +48,52 @@ export function AuthFacebookPanel({
}; };
return ( return (
<div className={styles.panel}> <div className="flex min-h-0 w-full flex-auto flex-col items-stretch">
<div className={styles.spacer} /> <div className="min-h-[var(--page-section-gap,var(--spacing-lg))] flex-[1_1_0]" />
<Image <Image
src="/images/auth/ic-logo-login.png" src="/images/auth/ic-logo-login.png"
alt="Cozsweet" alt="Cozsweet"
width={120} width={120}
height={120} height={120}
className={styles.logo} className="mx-auto block h-[clamp(92px,22.222vw,var(--auth-logo-height))] max-w-[min(56vw,220px)] object-contain"
style={{ width: "auto", height: "auto" }} style={{ width: "auto", height: "auto" }}
priority priority
/> />
<div className={styles.spacer} /> <div className="min-h-[var(--page-section-gap,var(--spacing-lg))] flex-[1_1_0]" />
<button <button
type="button" type="button"
className={styles.facebookButton} className="mt-md inline-flex min-h-[var(--auth-field-height)] w-full cursor-pointer items-center justify-center gap-md rounded-full border-0 bg-auth-surface px-lg text-[var(--responsive-body)] font-semibold text-auth-text-primary shadow-[0_2px_6px_rgba(0,0,0,0.06)] transition-[filter,transform,opacity] duration-150 enabled:hover:brightness-[0.97] enabled:active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-60"
onClick={() => handleFacebook()} onClick={() => handleFacebook()}
disabled={isLoading} disabled={isLoading}
> >
{isLoading ? ( {isLoading ? (
<span className={styles.spinner} aria-hidden="true" /> <span
className="inline-block size-[var(--responsive-spinner-size,18px)] flex-none animate-spin rounded-full border-2 border-solid border-facebook-blue border-r-transparent border-b-transparent border-l-transparent"
aria-hidden="true"
/>
) : ( ) : (
<span className={styles.facebookIcon} aria-hidden="true"> <span
className="inline-flex size-5 flex-none items-center justify-center"
aria-hidden="true"
>
<AuthProviderIcon provider="facebook" /> <AuthProviderIcon provider="facebook" />
</span> </span>
)} )}
<span className={styles.facebookLabel}> <span className="flex-none text-center">
{isLoading ? "Logging in..." : "Login with Facebook"} {isLoading ? "Logging in..." : "Login with Facebook"}
</span> </span>
</button> </button>
<button <button
type="button" type="button"
className={styles.linkButton} className="self-center border-0 bg-transparent py-md text-[var(--responsive-body)] text-auth-text-primary underline underline-offset-[3px] transition-opacity duration-150 hover:opacity-80"
onClick={() => setShowOptions(true)} onClick={() => setShowOptions(true)}
> >
Other Sign In Options Other Sign In Options
</button> </button>
<div className={styles.spacer} /> <div className="min-h-[var(--page-section-gap,var(--spacing-lg))] flex-[1_1_0]" />
<AuthErrorMessage message={errorMessage} /> <AuthErrorMessage message={errorMessage} />
<AuthLegalText /> <AuthLegalText />
@@ -1,53 +0,0 @@
/*
*
* 视觉规格(与 Dart 对齐):
* - drag handle 40×4 灰胶囊
* - 标题 24px bold 深色居中
* - 选项按钮 40px 白胶囊
* - Cancel 14px 深色居中文本按钮
*/
.body {
display: flex;
flex-direction: column;
align-items: stretch;
gap: var(--spacing-md);
padding: var(--spacing-md) var(--spacing-xxl)
calc(var(--spacing-xxl) + var(--app-safe-bottom, 0px));
}
.handle {
align-self: center;
width: var(--auth-drag-handle-width);
height: var(--auth-drag-handle-height);
border-radius: 2px;
background: var(--color-auth-drag-handle);
margin-bottom: var(--spacing-sm);
}
.title {
margin: var(--spacing-sm) 0;
font-size: var(--responsive-page-title, var(--font-size-bottom-sheet-title));
font-weight: 700;
color: var(--color-auth-text-primary);
text-align: center;
}
.googleSlot {
width: 100%;
}
.cancel {
align-self: center;
background: none;
border: none;
padding: var(--spacing-md) 0;
color: var(--color-auth-text-primary);
font-size: var(--responsive-body, var(--font-size-md));
font-weight: 500;
cursor: pointer;
text-decoration: none;
}
.cancel:hover {
text-decoration: underline;
}
@@ -7,7 +7,6 @@ import { AppEnvUtil } from "@/utils";
import { AuthProviderIcon } from "./auth-provider-icon"; import { AuthProviderIcon } from "./auth-provider-icon";
import { AuthSocialButton } from "./auth-social-button"; import { AuthSocialButton } from "./auth-social-button";
import styles from "./auth-other-options-dialog.module.css";
export interface AuthOtherOptionsDialogProps { export interface AuthOtherOptionsDialogProps {
open: boolean; open: boolean;
@@ -39,9 +38,17 @@ export function AuthOtherOptionsDialog({
onClose={onClose} onClose={onClose}
ariaLabel="Other sign-in options" ariaLabel="Other sign-in options"
> >
<div className={styles.body}> <div
<div className={styles.handle} aria-hidden="true" /> className="flex flex-col items-stretch gap-md px-xxl pt-md"
<h2 className={styles.title}>Other Sign-in Options</h2> style={{ paddingBottom: "calc(var(--spacing-xxl) + var(--app-safe-bottom, 0px))" }}
>
<div
className="mb-sm h-[var(--auth-drag-handle-height)] w-[var(--auth-drag-handle-width)] self-center rounded-[2px] bg-auth-drag-handle"
aria-hidden="true"
/>
<h2 className="my-sm text-center text-[var(--responsive-page-title)] font-bold text-auth-text-primary">
Other Sign-in Options
</h2>
{mode === "email" ? ( {mode === "email" ? (
<AuthSocialButton <AuthSocialButton
@@ -68,7 +75,7 @@ export function AuthOtherOptionsDialog({
) : null} ) : null}
{googleSlot ? ( {googleSlot ? (
<div className={styles.googleSlot}>{googleSlot}</div> <div className="w-full">{googleSlot}</div>
) : ( ) : (
<AuthSocialButton <AuthSocialButton
icon={<AuthProviderIcon provider="google" />} icon={<AuthProviderIcon provider="google" />}
@@ -81,7 +88,11 @@ export function AuthOtherOptionsDialog({
</AuthSocialButton> </AuthSocialButton>
)} )}
<button type="button" className={styles.cancel} onClick={onClose}> <button
type="button"
className="self-center border-0 bg-transparent py-md text-[var(--responsive-body)] font-medium text-auth-text-primary no-underline hover:underline"
onClick={onClose}
>
Cancel Cancel
</button> </button>
</div> </div>
@@ -1,62 +0,0 @@
/*
*
* 视觉规格(与 Dart 对齐):
* - 46px 高
* - 粉渐变(#f96ADE → #f657A0,左→右)
* - 半径 24
* - 20% 红阴影(#d00c41 @ 20%
* - 文字 16px bold 白色
*/
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
width: 100%;
min-height: var(--auth-field-height);
padding: 0 var(--spacing-lg);
border: none;
border-radius: var(--radius-full, 999px);
background: linear-gradient(
90deg,
var(--color-auth-primary-gradient-start),
var(--color-auth-primary-gradient-end)
);
box-shadow: 0 3px 5px var(--color-auth-primary-button-shadow);
color: #ffffff;
font-size: var(--responsive-body, var(--font-size-lg));
font-weight: 700;
cursor: pointer;
transition: filter 0.15s ease, transform 0.05s ease, opacity 0.15s ease;
}
.button:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.button:not(:disabled):active {
transform: scale(0.98);
}
.label {
flex: 1 1 auto;
text-align: center;
}
.spinner {
display: inline-block;
width: var(--responsive-spinner-size, 18px);
height: var(--responsive-spinner-size, 18px);
border-width: 2px;
border-style: solid;
border-color: #ffffff transparent transparent transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@@ -13,8 +13,6 @@
*/ */
import { type ButtonHTMLAttributes, type ReactNode } from "react"; import { type ButtonHTMLAttributes, type ReactNode } from "react";
import styles from "./auth-primary-button.module.css";
export interface AuthPrimaryButtonProps export interface AuthPrimaryButtonProps
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> { extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
isLoading?: boolean; isLoading?: boolean;
@@ -33,13 +31,21 @@ export function AuthPrimaryButton({
<button <button
type={type} type={type}
disabled={disabled || isLoading} disabled={disabled || isLoading}
className={[styles.button, className].filter(Boolean).join(" ")} className={[
"inline-flex min-h-[var(--auth-field-height)] w-full cursor-pointer items-center justify-center gap-sm rounded-full border-0 bg-[linear-gradient(90deg,var(--color-auth-primary-gradient-start),var(--color-auth-primary-gradient-end))] px-lg text-[var(--responsive-body)] font-bold text-white shadow-[0_3px_5px_var(--color-auth-primary-button-shadow)] transition-[filter,transform,opacity] duration-150 active:enabled:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-70",
className,
]
.filter(Boolean)
.join(" ")}
{...rest} {...rest}
> >
{isLoading ? ( {isLoading ? (
<span className={styles.spinner} aria-hidden="true" /> <span
className="inline-block size-[var(--responsive-spinner-size,18px)] animate-spin rounded-full border-2 border-solid border-white border-r-transparent border-b-transparent border-l-transparent"
aria-hidden="true"
/>
) : null} ) : null}
<span className={styles.label}>{children}</span> <span className="flex-auto text-center">{children}</span>
</button> </button>
); );
} }
@@ -1,65 +0,0 @@
.list {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
width: 100%;
}
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
width: 100%;
min-height: var(--button-height);
padding: 0 var(--spacing-lg);
border: var(--border-light) solid var(--color-chat-input-border);
border-radius: var(--radius-full);
background: rgba(255, 255, 255, 0.04);
color: var(--color-text-primary);
font-size: var(--responsive-body, var(--font-size-md));
font-weight: 500;
cursor: pointer;
transition: background 0.15s ease;
}
.button:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.08);
}
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
}
.facebook {
background: var(--color-facebook-blue);
border-color: transparent;
}
.google {
background: #ffffff;
color: #1f1f1f;
border-color: transparent;
}
.apple {
background: #000000;
border-color: rgba(255, 255, 255, 0.2);
color: #ffffff;
}
.googleSlot {
display: flex;
justify-content: center;
width: 100%;
min-height: var(--button-height);
}
@@ -9,7 +9,6 @@
import { type ReactNode } from "react"; import { type ReactNode } from "react";
import { AuthProviderIcon } from "./auth-provider-icon"; import { AuthProviderIcon } from "./auth-provider-icon";
import styles from "./auth-social-buttons.module.css";
export interface AuthSocialButtonsProps { export interface AuthSocialButtonsProps {
onFacebook: () => void; onFacebook: () => void;
@@ -29,30 +28,36 @@ export function AuthSocialButtons({
showApple = false, showApple = false,
googleSlot, googleSlot,
}: AuthSocialButtonsProps) { }: AuthSocialButtonsProps) {
const buttonClassName =
"inline-flex min-h-[var(--button-height)] w-full cursor-pointer items-center justify-center gap-sm rounded-full border border-chat-input-border bg-[rgba(255,255,255,0.04)] px-lg text-[var(--responsive-body)] font-medium text-text-primary transition-colors duration-150 enabled:hover:bg-[rgba(255,255,255,0.08)] disabled:cursor-not-allowed disabled:opacity-50";
const iconClassName = "inline-flex size-5 items-center justify-center";
return ( return (
<div className={styles.list}> <div className="flex w-full flex-col gap-md">
<button <button
type="button" type="button"
className={[styles.button, styles.facebook].join(" ")} className={`${buttonClassName} border-transparent bg-facebook-blue`}
onClick={onFacebook} onClick={onFacebook}
disabled={loadingProvider !== null && loadingProvider !== "facebook"} disabled={loadingProvider !== null && loadingProvider !== "facebook"}
> >
<span className={styles.icon} aria-hidden="true"> <span className={iconClassName} aria-hidden="true">
<AuthProviderIcon provider="facebook" /> <AuthProviderIcon provider="facebook" />
</span> </span>
<span>Continue with Facebook</span> <span>Continue with Facebook</span>
</button> </button>
{googleSlot ? ( {googleSlot ? (
<div className={styles.googleSlot}>{googleSlot}</div> <div className="flex min-h-[var(--button-height)] w-full justify-center">
{googleSlot}
</div>
) : ( ) : (
<button <button
type="button" type="button"
className={[styles.button, styles.google].join(" ")} className={`${buttonClassName} border-transparent bg-white text-[#1f1f1f]`}
onClick={onGoogle} onClick={onGoogle}
disabled={loadingProvider !== null && loadingProvider !== "google"} disabled={loadingProvider !== null && loadingProvider !== "google"}
> >
<span className={styles.icon} aria-hidden="true"> <span className={iconClassName} aria-hidden="true">
<AuthProviderIcon provider="google" /> <AuthProviderIcon provider="google" />
</span> </span>
<span>Continue with Google</span> <span>Continue with Google</span>
@@ -62,11 +67,11 @@ export function AuthSocialButtons({
{showApple && onApple ? ( {showApple && onApple ? (
<button <button
type="button" type="button"
className={[styles.button, styles.apple].join(" ")} className={`${buttonClassName} border-[rgba(255,255,255,0.2)] bg-black text-white`}
onClick={onApple} onClick={onApple}
disabled={loadingProvider !== null && loadingProvider !== "apple"} disabled={loadingProvider !== null && loadingProvider !== "apple"}
> >
<span className={styles.icon} aria-hidden="true"> <span className={iconClassName} aria-hidden="true">
<AuthProviderIcon provider="apple" /> <AuthProviderIcon provider="apple" />
</span> </span>
<span>Continue with Apple</span> <span>Continue with Apple</span>