File size: 4,439 Bytes
75fefa7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"use client";

import React from "react";
import { cn } from "@/utils/cn";
import { LucideIcon } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import AnimatedWidth from "@/components/shared/layout/animated-width";

interface CapsuleButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  icon?: LucideIcon | React.ComponentType<{ className?: string }>;
  iconPosition?: "left" | "right";
  children: React.ReactNode;
  size?: "sm" | "md" | "lg";
  fullWidth?: boolean;
  variant?: "primary" | "secondary" | "tertiary" | "ghost";
  loading?: boolean;
}

export function CapsuleButton({
  icon: Icon,
  iconPosition = "left",
  children,
  className,
  size = "md",
  fullWidth = false,
  variant = "primary",
  loading = false,
  disabled,
  ...props
}: CapsuleButtonProps) {
  const [isPressed, setIsPressed] = React.useState(false);

  const sizeClasses = {
    sm: "h-32 px-16 text-label-small gap-6",
    md: "h-40 px-20 text-label-medium gap-8",
    lg: "h-40 px-20 text-label-medium gap-8",
  };

  const iconSizes = {
    sm: "w-14 h-14",
    md: "w-16 h-16",
    lg: "w-16 h-16",
  };

  const variants = {
    primary: [
      "bg-heat-100 text-white",
      "hover:bg-heat-200",
      "active:scale-[0.98]",
      "shadow-[0_1px_2px_rgba(0,0,0,0.05)]",
      "hover:shadow-[0_4px_12px_rgba(250,93,25,0.25)]",
    ],
    secondary: [
      "bg-black text-white",
      "hover:bg-black/90",
      "active:scale-[0.98]",
      "shadow-[0_1px_2px_rgba(0,0,0,0.05)]",
      "hover:shadow-[0_4px_12px_rgba(0,0,0,0.15)]",
    ],
    tertiary: [
      "bg-white text-black border border-black-alpha-8",
      "hover:bg-black-alpha-4 hover:border-black-alpha-12",
      "active:scale-[0.98]",
    ],
    ghost: [
      "bg-transparent text-black-alpha-60",
      "hover:text-black hover:bg-black-alpha-4",
      "active:scale-[0.98]",
    ],
  };

  const isDisabled = disabled || loading;

  return (
    <button
      className={cn(
        // Base styles
        "inline-flex items-center justify-center rounded-full  transition-all duration-200",
        // Size
        sizeClasses[size],
        // Variant
        variants[variant],
        // Full width
        fullWidth && "w-full",
        // Disabled state
        isDisabled && [
          "opacity-50 cursor-not-allowed",
          "hover:shadow-none hover:bg-current",
        ],
        // Pressed state
        isPressed && "scale-[0.98]",
        className,
      )}
      disabled={isDisabled}
      onMouseDown={() => !isDisabled && setIsPressed(true)}
      onMouseUp={() => setIsPressed(false)}
      onMouseLeave={() => setIsPressed(false)}
      {...props}
    >
      <AnimatedWidth initial={{ width: "auto" }}>
        <AnimatePresence initial={false} mode="popLayout">
          {loading ? (
            <motion.div
              key="loading"
              animate={{ opacity: 1, filter: "blur(0px)", scale: 1 }}
              className="flex gap-8 items-center justify-center"
              exit={{ opacity: 0, filter: "blur(2px)", scale: 0.9 }}
              initial={{ opacity: 0, filter: "blur(2px)", scale: 0.95 }}
            >
              <span>Loading...</span>
            </motion.div>
          ) : (
            <motion.div
              key="content"
              animate={{ opacity: 1, filter: "blur(0px)", scale: 1 }}
              className="flex gap-8 items-center justify-center"
              exit={{ opacity: 0, filter: "blur(2px)", scale: 0.9 }}
              initial={{ opacity: 0, filter: "blur(2px)", scale: 0.95 }}
            >
              {Icon && iconPosition === "left" && (
                <span
                  className={cn(
                    iconSizes[size],
                    "flex-shrink-0 inline-flex items-center justify-center",
                  )}
                >
                  <Icon className="w-full h-full" />
                </span>
              )}
              <span>{children}</span>
              {Icon && iconPosition === "right" && (
                <span
                  className={cn(
                    iconSizes[size],
                    "flex-shrink-0 inline-flex items-center justify-center",
                  )}
                >
                  <Icon className="w-full h-full" />
                </span>
              )}
            </motion.div>
          )}
        </AnimatePresence>
      </AnimatedWidth>
    </button>
  );
}