1- import React from "react" ;
1+ import { forwardRef } from "react" ;
22import type { IconProps } from "@/common/Icon" ;
33
44export interface ButtonProps
@@ -13,93 +13,110 @@ export interface ButtonProps
1313 fullWidth ?: boolean ;
1414 iconLeft ?: React . ReactElement < IconProps > ;
1515 iconRight ?: React . ReactElement < IconProps > ;
16+ /**
17+ * Loading text to display when isLoading is true
18+ */
19+ loadingText ?: string ;
1620}
1721
18- const Button : React . FC < ButtonProps > = ( {
19- children,
20- variant = "primary" ,
21- size = "md" ,
22- isLoading = false ,
23- fullWidth = false ,
24- iconLeft,
25- iconRight,
26- className,
27- disabled,
28- type = "button" ,
29- ...rest
30- } ) => {
31- const baseStyles =
32- "inline-flex items-center justify-center font-medium focus:outline-none transition-colors duration-150 ease-in-out" ;
22+ const Button = forwardRef < HTMLButtonElement , ButtonProps > (
23+ (
24+ {
25+ children,
26+ variant = "primary" ,
27+ size = "md" ,
28+ isLoading = false ,
29+ fullWidth = false ,
30+ iconLeft,
31+ iconRight,
32+ className,
33+ disabled,
34+ type = "button" ,
35+ loadingText = "Processing..." ,
36+ "aria-label" : ariaLabel ,
37+ ...rest
38+ } ,
39+ ref ,
40+ ) => {
41+ const baseStyles =
42+ "inline-flex items-center justify-center font-medium focus:outline-none transition-colors duration-150 ease-in-out" ;
43+
44+ const cursorStyles =
45+ disabled || isLoading ? "cursor-not-allowed" : "cursor-pointer" ;
46+
47+ let variantStyles = "" ;
48+ switch ( variant ) {
49+ case "primary" :
50+ variantStyles =
51+ "bg-primary text-white hover:bg-primary/90 focus:ring-4 focus:ring-primary/15 disabled:opacity-70 disabled:bg-primary/70 px-4 py-4 text-md rounded" ;
52+ break ;
53+ case "secondary" :
54+ variantStyles =
55+ "bg-background-widget text-text-default border border-gray-mid hover:bg-gray-100 focus:ring-4 focus:ring-primary/15 disabled:opacity-70" ;
56+ break ;
57+ case "icon" :
58+ variantStyles =
59+ "h-full flex items-center justify-center px-3 focus:outline-none hover:text-text-default disabled:opacity-50" ;
60+ break ;
61+ default :
62+ variantStyles = "" ;
63+ break ;
64+ }
3365
34- let cursorStyles =
35- disabled || isLoading ? "cursor-not-allowed" : "cursor-pointer" ;
66+ let sizeStyles = "" ;
67+ switch ( size ) {
68+ case "sm" :
69+ sizeStyles = variant === "icon" ? "" : "py-1 text-sm rounded-sm" ;
70+ break ;
71+ case "md" :
72+ sizeStyles = variant === "icon" ? "" : "py-1 text-md rounded" ;
73+ break ;
74+ case "lg" :
75+ sizeStyles = variant === "icon" ? "" : "py-1 text-lg rounded-lg" ;
76+ break ;
77+ }
3678
37- let variantStyles = "" ;
38- switch ( variant ) {
39- case "primary" :
40- variantStyles =
41- "bg-primary text-white hover:bg-primary/90 focus:ring-4 focus:ring-primary/15 disabled:opacity-70 disabled:bg-primary/70 px-4 py-4 text-md rounded" ;
42- break ;
43- case "secondary" :
44- variantStyles =
45- "bg-background-widget text-text-default border border-gray-mid hover:bg-gray-100 focus:ring-4 focus:ring-primary/15 disabled:opacity-70" ;
46- break ;
47- case "icon" :
48- variantStyles =
49- "h-full flex items-center justify-center px-3 focus:outline-none hover:text-text-default disabled:opacity-50" ;
50- break ;
51- default :
52- variantStyles = "" ;
53- break ;
54- }
79+ const widthStyles = fullWidth ? "w-full" : "" ;
80+ const loadingStyles = isLoading ? "opacity-75" : "" ;
5581
56- let sizeStyles = "" ;
57- switch ( size ) {
58- case "sm" :
59- sizeStyles = variant === "icon" ? "" : "py-1 text-sm rounded-sm" ;
60- break ;
61- case "md" :
62- sizeStyles = variant === "icon" ? "" : "py-1 text-md rounded" ;
63- break ;
64- case "lg" :
65- sizeStyles = variant === "icon" ? "" : "py-1 text-lg rounded-lg" ;
66- break ;
67- }
82+ const combinedClassName = [
83+ baseStyles ,
84+ variantStyles ,
85+ sizeStyles ,
86+ widthStyles ,
87+ loadingStyles ,
88+ cursorStyles ,
89+ className ,
90+ ]
91+ . filter ( Boolean )
92+ . join ( " " ) ;
6893
69- const widthStyles = fullWidth ? "w-full" : "" ;
70- const loadingStyles = isLoading ? "opacity-75" : "" ;
94+ const isDisabled = disabled || isLoading ;
7195
72- const combinedClassName = [
73- baseStyles ,
74- variantStyles ,
75- sizeStyles ,
76- widthStyles ,
77- loadingStyles ,
78- cursorStyles ,
79- className ,
80- ]
81- . filter ( Boolean )
82- . join ( " " ) ;
96+ return (
97+ < button
98+ ref = { ref }
99+ type = { type }
100+ disabled = { isDisabled }
101+ aria-disabled = { isDisabled }
102+ aria-label = { ariaLabel }
103+ className = { combinedClassName }
104+ { ...rest }
105+ >
106+ { isLoading && < span className = "mr-2" > { loadingText } </ span > }
107+ { ! isLoading && iconLeft && (
108+ < span className = "mr-2 flex items-center" > { iconLeft } </ span >
109+ ) }
110+ { ! isLoading && children }
111+ { ! isLoading && iconRight && (
112+ < span className = "ml-2 flex items-center" > { iconRight } </ span >
113+ ) }
114+ </ button >
115+ ) ;
116+ } ,
117+ ) ;
83118
84- return (
85- < button
86- type = { type }
87- disabled = { disabled || isLoading }
88- aria-disabled = { disabled || isLoading }
89- className = { combinedClassName }
90- { ...rest }
91- >
92- { isLoading && < span className = "mr-2" > Processing...</ span > }
93- { ! isLoading && iconLeft && (
94- < span className = "mr-2 flex items-center" > { iconLeft } </ span >
95- ) }
96- { ! isLoading && children }
97- { ! isLoading && iconRight && (
98- < span className = "ml-2 flex items-center" > { iconRight } </ span >
99- ) }
100- </ button >
101- ) ;
102- } ;
119+ Button . displayName = "Button" ;
103120
104121export type { IconProps as ButtonIconProps } from "@/common/Icon" ;
105122export default Button ;
0 commit comments