Wire production domain, CORS for tenant subdomains, celery volume mounts, and nginx reverse proxy configs for apex, API, identity, auth, and wildcard tenants. Co-authored-by: Cursor <cursoragent@cursor.com>
60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
import type { ButtonHTMLAttributes, ReactNode } from "react";
|
|
|
|
type ButtonVariant = "primary" | "secondary" | "ghost" | "outline";
|
|
type ButtonSize = "sm" | "md" | "lg";
|
|
|
|
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
variant?: ButtonVariant;
|
|
size?: ButtonSize;
|
|
children: ReactNode;
|
|
loading?: boolean;
|
|
loadingText?: string;
|
|
}
|
|
|
|
const variantClasses: Record<ButtonVariant, string> = {
|
|
primary: "bg-primary text-white hover:opacity-90 shadow-sm",
|
|
secondary: "bg-secondary text-white hover:opacity-90",
|
|
ghost: "bg-transparent text-secondary hover:bg-[var(--color-secondary-muted)]",
|
|
outline:
|
|
"border border-gray-200 bg-white text-secondary hover:border-primary hover:text-primary",
|
|
};
|
|
|
|
const sizeClasses: Record<ButtonSize, string> = {
|
|
sm: "px-3 py-1.5 text-sm",
|
|
md: "px-5 py-2.5 text-sm",
|
|
lg: "px-6 py-3 text-base",
|
|
};
|
|
|
|
export function Button({
|
|
variant = "primary",
|
|
size = "md",
|
|
className = "",
|
|
children,
|
|
loading = false,
|
|
loadingText,
|
|
disabled,
|
|
...props
|
|
}: ButtonProps) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
disabled={disabled || loading}
|
|
aria-busy={loading || undefined}
|
|
className={`inline-flex items-center justify-center gap-2 rounded-xl font-semibold transition-all disabled:cursor-not-allowed disabled:opacity-50 ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}
|
|
{...props}
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<span
|
|
className="inline-block h-4 w-4 shrink-0 animate-spin rounded-full border-2 border-current border-r-transparent"
|
|
aria-hidden
|
|
/>
|
|
<span>{loadingText ?? "در حال انجام..."}</span>
|
|
</>
|
|
) : (
|
|
children
|
|
)}
|
|
</button>
|
|
);
|
|
}
|