11import { Icon , IconName } from "@/components" ;
2- import { styled } from "styled-components" ;
2+ import { styled , keyframes , css } from "styled-components" ;
33import { BaseButton } from "../commonElement" ;
44import React from "react" ;
55
@@ -21,12 +21,10 @@ export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
2121 align ?: Alignment ;
2222 /** Whether the button should fill the full width of its container */
2323 fillWidth ?: boolean ;
24- /** Whether to show a loading spinner */
24+ /** Whether to show a loading state */
2525 loading ?: boolean ;
2626 /** Whether the button should be focused on mount */
2727 autoFocus ?: boolean ;
28- /** Whether to show the label alongside the loading spinner */
29- showLabelWithLoading ?: boolean ;
3028}
3129
3230export const Button = ( {
@@ -39,69 +37,52 @@ export const Button = ({
3937 label,
4038 loading = false ,
4139 disabled,
42- showLabelWithLoading = false ,
4340 ...delegated
4441} : ButtonProps ) => (
4542 < StyledButton
4643 $styleType = { type }
4744 $align = { align }
4845 $fillWidth = { fillWidth }
46+ $loading = { loading }
4947 disabled = { disabled || loading }
5048 aria-disabled = { disabled || loading }
5149 role = "button"
5250 { ...delegated }
5351 >
54- { ! loading && (
55- < >
56- { iconLeft && (
57- < ButtonIcon
58- name = { iconLeft }
59- aria-hidden
60- size = "sm"
61- />
62- ) }
63-
64- { label ?? children }
65-
66- { iconRight && (
67- < ButtonIcon
68- name = { iconRight }
69- aria-hidden
70- size = "sm"
71- />
72- ) }
73- </ >
52+ { iconLeft && (
53+ < ButtonIcon
54+ name = { iconLeft }
55+ aria-hidden
56+ size = "sm"
57+ />
7458 ) }
75- { loading && (
76- < LoadingIconWrapper data-testid = "click-ui-loading-icon-wrapper" >
77- < Icon
78- name = "loading-animated"
79- data-testid = "click-ui-loading-icon"
80- aria-label = "loading"
81- > </ Icon >
82- { showLabelWithLoading ? ( label ?? children ) : "" }
83- </ LoadingIconWrapper >
59+
60+ { label ?? children }
61+
62+ { iconRight && (
63+ < ButtonIcon
64+ name = { iconRight }
65+ aria-hidden
66+ size = "sm"
67+ / >
8468 ) }
8569 </ StyledButton >
8670) ;
8771
88- const LoadingIconWrapper = styled . div `
89- background-color: inherit;
90- top: 0;
91- left: 0;
92- bottom: 0;
93- right: 0;
94- display: flex;
95- align-content: center;
96- justify-content: center;
97- align-items: center;
98- gap: 0.5rem;
72+ const shimmer = keyframes `
73+ 0% {
74+ background- position: -200px 0;
75+ }
76+ 100% {
77+ background- position: 200px 0;
78+ }
9979` ;
10080
10181const StyledButton = styled ( BaseButton ) < {
10282 $styleType : ButtonType ;
10383 $align ?: Alignment ;
10484 $fillWidth ?: boolean ;
85+ $loading ?: boolean ;
10586} > `
10687 width: ${ ( { $fillWidth } ) => ( $fillWidth ? "100%" : "revert" ) } ;
10788 color: ${ ( { $styleType = "primary" , theme } ) =>
@@ -117,6 +98,32 @@ const StyledButton = styled(BaseButton)<{
11798 align-items: center;
11899 justify-content: ${ ( { $align } ) => ( $align === "left" ? "flex-start" : "center" ) } ;
119100 white-space: nowrap;
101+ overflow: hidden;
102+
103+ &::before {
104+ content: "";
105+ position: absolute;
106+ top: 0;
107+ left: 0;
108+ right: 0;
109+ bottom: 0;
110+ pointer-events: none;
111+ background-size: 200px 100%;
112+ opacity: 0;
113+ }
114+
115+ ${ ( { $loading, $styleType, theme } ) => {
116+ if ( ! $loading ) return "" ;
117+
118+ return css `
119+ & ::before {
120+ background-image : ${ theme . click . button . basic . color [ $styleType ] . background
121+ . loading } ;
122+ animation : ${ shimmer } 1.5s ease-in-out infinite;
123+ opacity : 1 ;
124+ }
125+ ` ;
126+ } }
120127
121128 &:hover {
122129 background-color: ${ ( { $styleType = "primary" , theme } ) =>
@@ -138,18 +145,56 @@ const StyledButton = styled(BaseButton)<{
138145 font: ${ ( { theme } ) => theme . click . button . basic . typography . label . active } ;
139146 }
140147
141- &:disabled,
142- &:disabled:hover,
143- &:disabled:active {
144- background-color: ${ ( { $styleType = "primary" , theme } ) =>
145- theme . click . button . basic . color [ $styleType ] . background . disabled } ;
146- color: ${ ( { $styleType = "primary" , theme } ) =>
147- theme . click . button . basic . color [ $styleType ] . text . disabled } ;
148- border: ${ ( { theme } ) => theme . click . button . stroke } solid
149- ${ ( { $styleType = "primary" , theme } ) =>
150- theme . click . button . basic . color [ $styleType ] . stroke . disabled } ;
151- font: ${ ( { theme } ) => theme . click . button . basic . typography . label . disabled } ;
152- }
148+ ${ ( { $loading, $styleType, theme } ) => {
149+ if ( $loading ) return "" ;
150+
151+ const bgDisabled = theme . click . button . basic . color [ $styleType ] . background . disabled ;
152+ const textDisabled = theme . click . button . basic . color [ $styleType ] . text . disabled ;
153+ const strokeDisabled = theme . click . button . basic . color [ $styleType ] . stroke . disabled ;
154+ const stroke = theme . click . button . stroke ;
155+ const fontDisabled = theme . click . button . basic . typography . label . disabled ;
156+ const isPrimary = $styleType === "primary" ;
157+
158+ return css `
159+ & : disabled ,
160+ & : disabled : hover ,
161+ & : disabled : active {
162+ background-color : ${ bgDisabled } ;
163+ color : ${ textDisabled } ;
164+ border : ${ stroke } solid ${ strokeDisabled } ;
165+ font : ${ fontDisabled } ;
166+ cursor : not-allowed;
167+ ${ isPrimary ? "opacity: 0.6;" : "" }
168+ }
169+ ` ;
170+ } }
171+
172+ /* Loading state styling */
173+ ${ ( { $loading, $styleType } ) => {
174+ if ( ! $loading ) return "" ;
175+
176+ if ( $styleType === "primary" ) {
177+ // Primary: 60% opacity + shimmer animation
178+ return css `
179+ opacity : 0.6 ;
180+ cursor : not-allowed;
181+ ` ;
182+ } else if ( $styleType === "secondary" || $styleType === "empty" ) {
183+ // Secondary & Empty: Full opacity during loading, shimmer only, text dimmed (70%)
184+ return css `
185+ opacity : 0.7 ;
186+ cursor : not-allowed;
187+ ` ;
188+ } else if ( $styleType === "danger" ) {
189+ // Destructive: Full opacity during loading, shimmer only, text dimmed (70%)
190+ return css `
191+ opacity : 0.7 ;
192+ cursor : not-allowed;
193+ ` ;
194+ }
195+
196+ return "" ;
197+ } }
153198` ;
154199
155200const ButtonIcon = styled ( Icon ) `
0 commit comments