childcafe/src/components/ui/Button.tsx
2025-10-11 17:31:24 +09:00

61 lines
2.0 KiB
TypeScript

import { Slot } from "@radix-ui/react-slot";
import { cn } from "@/lib/utils";
import { ReactNode } from "react";
type ButtonProps = {
variant?: "primary" | "secondary" | "success" | "warning" | "danger" | "muted" | "info";
size?: "sm" | "md" | "lg";
asChild?: boolean;
children: ReactNode;
isLoading?: boolean;
icon?: ReactNode; // ← ★ アイコン追加
iconPosition?: "left" | "right"; // ← ★ 位置も制御可能
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
export function Button({
variant = "primary",
size = "md",
asChild,
children,
isLoading,
icon,
iconPosition = "left",
className,
...props
}: ButtonProps) {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(
"inline-flex items-center justify-center gap-2 font-medium rounded-lg transition whitespace-nowrap disabled:opacity-50 disabled:pointer-events-none",
{
"bg-sky-500 hover:bg-sky-600 text-white shadow": variant === "primary",
"bg-gray-300 hover:bg-gray-400 text-gray-700": variant === "secondary",
"bg-green-500 hover:bg-green-600 text-white shadow": variant === "success",
"bg-orange-500 hover:bg-orange-600 text-white shadow": variant === "warning",
"bg-red-600 hover:bg-red-700 text-white shadow": variant === "danger",
"bg-purple-200 hover:bg-purple-300 text-gray-400": variant === "muted",
"bg-sky-400 hover:bg-sky-500 text-gray-50 shadow": variant === "info",
},
{
"w-[110px] h-[36px] px-3 text-sm": size === "sm",
"w-[140px] h-[42px] px-4 text-base": size === "md",
"w-[180px] h-[48px] px-6 text-base font-semibold": size === "lg",
},
className
)}
{...props}
>
<span className="flex items-center justify-center gap-2">
{isLoading && (
<span className="inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
)}
{!isLoading && icon && iconPosition === "left" && <span>{icon}</span>}
<span>{children}</span>
{!isLoading && icon && iconPosition === "right" && <span>{icon}</span>}
</span>
</Comp>
);
}