continue re-design
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import { ButtonHTMLAttributes, forwardRef } from "react";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
import "./button.css";
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: "primary" | "secondary" | "tertiary" | "playground" | "destructive";
|
||||
size?: "default" | "large";
|
||||
disabled?: boolean;
|
||||
loadingLabel?: string;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, Props>(
|
||||
(
|
||||
{
|
||||
variant = "primary",
|
||||
size = "default",
|
||||
disabled,
|
||||
isLoading = false,
|
||||
loadingLabel = "Loading…",
|
||||
...attrs
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const isNonInteractive = Boolean(disabled || isLoading);
|
||||
|
||||
// Focus ring adapts to light/dark variants
|
||||
const focusRing =
|
||||
variant === "primary" || variant === "destructive"
|
||||
? "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-white"
|
||||
: "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-black";
|
||||
|
||||
return (
|
||||
<button
|
||||
{...attrs}
|
||||
ref={ref}
|
||||
type={attrs.type ?? "button"}
|
||||
aria-disabled={isNonInteractive || undefined}
|
||||
aria-busy={isLoading || undefined}
|
||||
aria-live={isLoading ? "polite" : undefined}
|
||||
data-state={
|
||||
isLoading ? "loading" : isNonInteractive ? "disabled" : "idle"
|
||||
}
|
||||
className={cn(
|
||||
attrs.className,
|
||||
"flex items-center justify-center button relative [&>*]:relative",
|
||||
"text-label-medium lg-max:[&_svg]:size-24",
|
||||
`button-${variant} group/button`,
|
||||
focusRing,
|
||||
|
||||
// Shared non-interactive styles
|
||||
"disabled:cursor-not-allowed",
|
||||
isNonInteractive && "cursor-not-allowed",
|
||||
|
||||
// Size
|
||||
size === "default" && "rounded-8 px-10 py-6 gap-4",
|
||||
size === "large" && "rounded-10 px-12 py-8 gap-6",
|
||||
|
||||
// Variant + interactive nuances
|
||||
variant === "primary" && [
|
||||
"text-accent-white",
|
||||
// Hover/active only when interactive
|
||||
!isNonInteractive &&
|
||||
"hover:bg-[color:var(--heat-90)] active:[scale:0.995]",
|
||||
// Disabled: dim a bit, no hover, dim overlay bg layer if present
|
||||
"disabled:opacity-80",
|
||||
"disabled:[&_.button-background]:opacity-70",
|
||||
],
|
||||
|
||||
["secondary", "tertiary", "playground"].includes(variant) && [
|
||||
"text-accent-black",
|
||||
!isNonInteractive && "active:[scale:0.99] active:bg-black-alpha-7",
|
||||
],
|
||||
|
||||
variant === "secondary" && [
|
||||
"bg-black-alpha-4",
|
||||
!isNonInteractive && "hover:bg-black-alpha-6",
|
||||
// Disabled: lighter fill + muted text, no hover
|
||||
"disabled:bg-black-alpha-3",
|
||||
"disabled:text-black-alpha-48",
|
||||
"disabled:hover:bg-black-alpha-3",
|
||||
],
|
||||
|
||||
variant === "tertiary" && [
|
||||
!isNonInteractive && "hover:bg-black-alpha-4",
|
||||
// Disabled: no hover background, text muted
|
||||
"disabled:text-black-alpha-48",
|
||||
"disabled:hover:bg-transparent",
|
||||
],
|
||||
|
||||
variant === "destructive" && [
|
||||
"bg-red-600 text-accent-white",
|
||||
!isNonInteractive && "hover:bg-red-700 active:scale-[0.98]",
|
||||
// Disabled: keep red but softer; soften text slightly
|
||||
"disabled:bg-red-600/70",
|
||||
"disabled:text-white-alpha-72",
|
||||
"disabled:hover:bg-red-600/70",
|
||||
],
|
||||
|
||||
variant === "playground" && [
|
||||
"inside-border before:border-black-alpha-4",
|
||||
isNonInteractive
|
||||
? "before:opacity-0 bg-black-alpha-4 text-black-alpha-24"
|
||||
: "hover:bg-black-alpha-4 hover:before:opacity-0 active:before:opacity-0",
|
||||
],
|
||||
)}
|
||||
disabled={isNonInteractive}
|
||||
>
|
||||
{variant === "primary" && (
|
||||
<div className="overlay button-background !absolute" />
|
||||
)}
|
||||
|
||||
{/* loading state (spinner) */}
|
||||
{isLoading && (
|
||||
<div
|
||||
className={cn(
|
||||
"w-16 h-16 border-2 rounded-full animate-spin",
|
||||
variant === "primary" || variant === "destructive"
|
||||
? "border-white/30 border-t-white"
|
||||
: "border-black/30 border-t-black",
|
||||
)}
|
||||
aria-hidden
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Screen reader-only loading label */}
|
||||
{isLoading && <span className="sr-only">{loadingLabel}</span>}
|
||||
|
||||
{attrs.children}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Button.displayName = "Button";
|
||||
|
||||
export default Button;
|
||||
Reference in New Issue
Block a user