@4alldigital/foundation-ui--core 3.6.4 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/components/Accordion/Accordion.tsx +2 -2
- package/src/components/Button/Button.stories.tsx +247 -18
- package/src/components/Button/Button.tsx +252 -105
- package/src/components/Button/Button.types.ts +44 -12
- package/src/components/Button/index.ts +8 -1
- package/src/components/ButtonDeprecated/Button.stories.tsx +51 -0
- package/src/components/ButtonDeprecated/Button.tsx +138 -0
- package/src/components/ButtonDeprecated/Button.types.ts +43 -0
- package/src/components/ButtonDeprecated/index.ts +1 -0
- package/src/components/ProductCard/ProductCard.tsx +5 -5
- package/src/components/ProductDetail/ProductDetail.tsx +7 -7
- package/src/components/ShadcnCarousel/ShadcnCarousel.tsx +7 -7
- package/src/components/index.ts +4 -4
- package/src/templates/NotFoundScreen/NotFoundScreen.tsx +0 -1
- package/src/templates/ProductListScreen/ProductListScreen.stories.tsx +3 -0
- package/src/templates/ProductListScreen/ProductListScreen.tsx +8 -3
- package/src/templates/ProductListScreen/ProductListScreen.types.ts +1 -0
- package/src/templates/PurchaseConfirmationScreen/PurchaseConfirmationScreen.tsx +7 -7
- package/src/components/ShadcnButton/ShadcnButton.stories.tsx +0 -152
- package/src/components/ShadcnButton/ShadcnButton.tsx +0 -57
- package/src/components/ShadcnButton/index.ts +0 -2
|
@@ -1,117 +1,264 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { cn } from '../../utils';
|
|
4
5
|
import Icon from '../Icon';
|
|
5
6
|
import Loader from '../Loader';
|
|
7
|
+
import { BTN_VARIANTS, BTN_SIZES, type ButtonProps } from './Button.types';
|
|
6
8
|
|
|
7
|
-
const
|
|
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
|
-
|
|
9
|
+
const buttonVariants = cva(
|
|
10
|
+
'inline-flex items-center cursor-pointer justify-center whitespace-nowrap rounded-sm text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
|
11
|
+
{
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
// New variants matching old Button theme colors
|
|
15
|
+
primary:
|
|
16
|
+
'text-white border border-primary bg-primary hover:bg-primary-darker',
|
|
17
|
+
secondary:
|
|
18
|
+
'text-white border border-secondary bg-secondary hover:bg-secondary-darker',
|
|
19
|
+
tertiary:
|
|
20
|
+
'text-black border border-tertiary bg-tertiary hover:bg-tertiary-darker',
|
|
21
|
+
link: 'px-0 text-body-text dark:text-body-text-dark border-transparent bg-transparent hover:bg-transparent shadow-none group-hover:invert',
|
|
22
|
+
|
|
23
|
+
// Shadcn variants
|
|
24
|
+
default:
|
|
25
|
+
'bg-white/20 text-primary-foreground shadow hover:bg-primary/90 border-2 border-red',
|
|
26
|
+
destructive:
|
|
27
|
+
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
|
28
|
+
ghost: 'hover:bg-transparent hover:bg-transparent',
|
|
29
|
+
|
|
30
|
+
// Outline is handled as compound variant
|
|
31
|
+
outline:
|
|
32
|
+
'border-primary bg-transparent text-body-text dark:text-body-text-dark hover:bg-opacity-30 border-primary-300 dark:border-primary-400 hover:border-primary-400 dark:hover:border-primary-500',
|
|
33
|
+
},
|
|
34
|
+
size: {
|
|
35
|
+
sm: 'h-8 px-3 py-1 text-xs',
|
|
36
|
+
default: 'h-9 px-6 py-2 text-base',
|
|
37
|
+
lg: 'h-10 px-5 py-3 text-lg',
|
|
38
|
+
icon: 'h-9 w-9',
|
|
39
|
+
},
|
|
40
|
+
wide: {
|
|
41
|
+
true: '',
|
|
42
|
+
false: '',
|
|
43
|
+
},
|
|
44
|
+
rounded: {
|
|
45
|
+
true: 'rounded-full leading-none',
|
|
46
|
+
false: '',
|
|
47
|
+
},
|
|
48
|
+
raised: {
|
|
49
|
+
true: 'shadow',
|
|
50
|
+
false: '',
|
|
51
|
+
},
|
|
52
|
+
uppercase: {
|
|
53
|
+
true: 'uppercase',
|
|
54
|
+
false: '',
|
|
55
|
+
},
|
|
54
56
|
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const classes = twMerge(
|
|
58
|
-
cx(
|
|
59
|
-
'button flex rounded-sm items-center cursor-pointer',
|
|
60
|
-
{ shadow: raised },
|
|
61
|
-
{ uppercase: uppercase },
|
|
62
|
-
{ 'auto-cols-auto grid-cols-2 gap-4': isLoading || icon || external },
|
|
63
|
-
{ 'flex-row-reverse': iconFirst },
|
|
64
|
-
{ 'opacity-50 cursor-not-allowed': disabled },
|
|
65
|
-
`${sizes?.[size]?.y} ${sizes?.[size]?.x} ${sizes?.[size]?.text}`,
|
|
66
|
-
{ 'text-white border-primary bg-primary hover:bg-primary-darker': variant === BTN_VARIANTS.PRIMARY },
|
|
67
|
-
{ 'text-white border-secondary bg-secondary hover:bg-secondary-darker': variant === BTN_VARIANTS.SECONDARY },
|
|
68
|
-
{ 'text-black border-tertiary bg-tertiary hover:bg-tertiary-darker': variant === BTN_VARIANTS.TERTIARY },
|
|
69
|
-
{ 'border-primary bg-transparent text-body-text dark:text-body-text-dark hover:bg-opacity-30': outline },
|
|
70
|
-
{ 'border-primary-300 dark:border-primary-400 hover:border-primary-400 dark:hover:border-primary-500': outline },
|
|
57
|
+
compoundVariants: [
|
|
58
|
+
// Wide variants adjust padding
|
|
71
59
|
{
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
size: 'sm',
|
|
61
|
+
wide: true,
|
|
62
|
+
className: 'px-5',
|
|
74
63
|
},
|
|
75
|
-
{
|
|
64
|
+
{
|
|
65
|
+
size: 'default',
|
|
66
|
+
wide: true,
|
|
67
|
+
className: 'px-8',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
size: 'lg',
|
|
71
|
+
wide: true,
|
|
72
|
+
className: 'px-11',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
defaultVariants: {
|
|
76
|
+
variant: 'primary',
|
|
77
|
+
size: 'default',
|
|
78
|
+
wide: false,
|
|
79
|
+
rounded: false,
|
|
80
|
+
raised: false,
|
|
81
|
+
uppercase: false,
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
87
|
+
(
|
|
88
|
+
{
|
|
76
89
|
className,
|
|
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
|
-
|
|
90
|
+
variant: variantProp,
|
|
91
|
+
size: sizeProp,
|
|
92
|
+
wide = false,
|
|
93
|
+
rounded = false,
|
|
94
|
+
raised = false,
|
|
95
|
+
uppercase = false,
|
|
96
|
+
outline = false,
|
|
97
|
+
asChild = false,
|
|
98
|
+
children,
|
|
99
|
+
icon,
|
|
100
|
+
external,
|
|
101
|
+
iconFirst,
|
|
102
|
+
isLoading,
|
|
103
|
+
offsetFont = false,
|
|
104
|
+
ariaLabel,
|
|
105
|
+
testID,
|
|
106
|
+
id,
|
|
107
|
+
disabled,
|
|
108
|
+
type = 'button',
|
|
109
|
+
...props
|
|
110
|
+
},
|
|
111
|
+
ref
|
|
112
|
+
) => {
|
|
113
|
+
// Map legacy enum values to new string values
|
|
114
|
+
const mapVariant = (v: ButtonProps['variant']) => {
|
|
115
|
+
if (typeof v === 'object' && 'PRIMARY' in (v as typeof BTN_VARIANTS)) {
|
|
116
|
+
// Handle enum values
|
|
117
|
+
const enumValue = v as BTN_VARIANTS;
|
|
118
|
+
switch (enumValue) {
|
|
119
|
+
case BTN_VARIANTS.PRIMARY:
|
|
120
|
+
return 'primary';
|
|
121
|
+
case BTN_VARIANTS.SECONDARY:
|
|
122
|
+
return 'secondary';
|
|
123
|
+
case BTN_VARIANTS.TERTIARY:
|
|
124
|
+
return 'tertiary';
|
|
125
|
+
case BTN_VARIANTS.LINK:
|
|
126
|
+
return 'link';
|
|
127
|
+
default:
|
|
128
|
+
return 'primary';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Handle string enum values
|
|
132
|
+
if (v === 'primary' || v === 'secondary' || v === 'tertiary' || v === 'link') {
|
|
133
|
+
return v;
|
|
134
|
+
}
|
|
135
|
+
// If outline prop is set, use outline variant
|
|
136
|
+
if (outline) {
|
|
137
|
+
return 'outline';
|
|
138
|
+
}
|
|
139
|
+
return v || 'primary';
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const mapSize = (s: ButtonProps['size']) => {
|
|
143
|
+
if (typeof s === 'object' && 'SMALL' in (s as typeof BTN_SIZES)) {
|
|
144
|
+
// Handle enum values
|
|
145
|
+
const enumValue = s as BTN_SIZES;
|
|
146
|
+
switch (enumValue) {
|
|
147
|
+
case BTN_SIZES.SMALL:
|
|
148
|
+
return 'sm';
|
|
149
|
+
case BTN_SIZES.MEDIUM:
|
|
150
|
+
return 'default';
|
|
151
|
+
case BTN_SIZES.LARGE:
|
|
152
|
+
return 'lg';
|
|
153
|
+
default:
|
|
154
|
+
return 'default';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Handle string size mapping
|
|
158
|
+
if (s === 'small') return 'sm';
|
|
159
|
+
if (s === 'medium') return 'default';
|
|
160
|
+
if (s === 'large') return 'lg';
|
|
161
|
+
return s || 'default';
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const variant = mapVariant(variantProp);
|
|
165
|
+
const size = mapSize(sizeProp);
|
|
166
|
+
|
|
167
|
+
// Determine icon size based on button size
|
|
168
|
+
const getIconSize = () => {
|
|
169
|
+
switch (size) {
|
|
170
|
+
case 'sm':
|
|
171
|
+
return 'w-3 h-3';
|
|
172
|
+
case 'lg':
|
|
173
|
+
return 'w-6 h-6';
|
|
174
|
+
default:
|
|
175
|
+
return 'w-5 h-5';
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const iconClasses = getIconSize();
|
|
180
|
+
|
|
181
|
+
const Comp = asChild ? Slot : 'button';
|
|
182
|
+
|
|
183
|
+
// Build content with proper ordering for icons
|
|
184
|
+
const renderContent = () => {
|
|
185
|
+
const iconElement = icon ? (
|
|
186
|
+
<span className="flex items-center">
|
|
187
|
+
<Icon name={icon} className={iconClasses} />
|
|
188
|
+
</span>
|
|
189
|
+
) : null;
|
|
190
|
+
|
|
191
|
+
const externalIcon = external ? (
|
|
192
|
+
<span className="flex items-center">
|
|
193
|
+
<Icon name="carbon:launch" className={iconClasses} />
|
|
194
|
+
</span>
|
|
195
|
+
) : null;
|
|
196
|
+
|
|
197
|
+
const loader = isLoading ? (
|
|
198
|
+
<div className="flex items-center">
|
|
199
|
+
<Loader size={4} />
|
|
200
|
+
</div>
|
|
201
|
+
) : null;
|
|
202
|
+
|
|
203
|
+
const childContent = offsetFont && children ? (
|
|
204
|
+
<span className="brand-font-offset">{children}</span>
|
|
205
|
+
) : (
|
|
206
|
+
children
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// If iconFirst is true, render icon before children
|
|
210
|
+
if (iconFirst) {
|
|
211
|
+
return (
|
|
212
|
+
<>
|
|
213
|
+
{iconElement}
|
|
214
|
+
{externalIcon}
|
|
215
|
+
{childContent}
|
|
216
|
+
{loader}
|
|
217
|
+
</>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Default: children first, then icon
|
|
222
|
+
return (
|
|
223
|
+
<>
|
|
224
|
+
{childContent}
|
|
225
|
+
{iconElement}
|
|
226
|
+
{externalIcon}
|
|
227
|
+
{loader}
|
|
228
|
+
</>
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<Comp
|
|
234
|
+
ref={ref}
|
|
235
|
+
id={id}
|
|
236
|
+
data-testid={testID || id || 'Button'}
|
|
237
|
+
className={cn(
|
|
238
|
+
buttonVariants({
|
|
239
|
+
variant,
|
|
240
|
+
size,
|
|
241
|
+
wide,
|
|
242
|
+
rounded,
|
|
243
|
+
raised,
|
|
244
|
+
uppercase,
|
|
245
|
+
className,
|
|
246
|
+
})
|
|
108
247
|
)}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
248
|
+
type={type as 'button' | 'submit' | 'reset'}
|
|
249
|
+
disabled={disabled}
|
|
250
|
+
aria-label={ariaLabel}
|
|
251
|
+
role="button"
|
|
252
|
+
{...props}
|
|
253
|
+
>
|
|
254
|
+
{renderContent()}
|
|
255
|
+
</Comp>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
);
|
|
113
259
|
|
|
114
260
|
Button.displayName = 'Button';
|
|
115
261
|
|
|
116
262
|
export default Button;
|
|
117
|
-
export
|
|
263
|
+
export { Button, buttonVariants };
|
|
264
|
+
export type { ButtonProps };
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
2
|
+
import { buttonVariants } from './Button';
|
|
1
3
|
import { MouseEventHandler, ReactNode } from 'react';
|
|
2
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Use string literals instead: 'submit' | 'reset' | 'button'
|
|
7
|
+
*/
|
|
3
8
|
export enum BTN_TYPES {
|
|
4
9
|
SUBMIT = 'submit',
|
|
5
10
|
RESET = 'reset',
|
|
6
11
|
BUTTON = 'button',
|
|
7
12
|
}
|
|
8
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @deprecated Use ButtonProps['variant'] string literals instead: 'primary' | 'secondary' | 'tertiary' | 'link'
|
|
16
|
+
*/
|
|
9
17
|
export enum BTN_VARIANTS {
|
|
10
18
|
PRIMARY = 'primary',
|
|
11
19
|
SECONDARY = 'secondary',
|
|
@@ -13,20 +21,33 @@ export enum BTN_VARIANTS {
|
|
|
13
21
|
LINK = 'link',
|
|
14
22
|
}
|
|
15
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @deprecated Use ButtonProps['size'] string literals instead: 'sm' | 'default' | 'lg'
|
|
26
|
+
*/
|
|
16
27
|
export enum BTN_SIZES {
|
|
17
28
|
SMALL = 'small',
|
|
18
29
|
MEDIUM = 'medium',
|
|
19
30
|
LARGE = 'large',
|
|
20
31
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
|
|
33
|
+
// New CVA-based button props
|
|
34
|
+
export interface ButtonProps
|
|
35
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
36
|
+
VariantProps<typeof buttonVariants> {
|
|
37
|
+
// Radix UI Slot support
|
|
38
|
+
asChild?: boolean;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Button variant. Accepts string literals or legacy BTN_VARIANTS enum values for backward compatibility.
|
|
42
|
+
*/
|
|
43
|
+
variant?: 'primary' | 'secondary' | 'tertiary' | 'link' | 'outline' | 'ghost' | 'destructive' | 'default' | BTN_VARIANTS;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Size of the button. Accepts both new ('sm', 'default', 'lg') and legacy ('small', 'medium', 'large') values
|
|
47
|
+
*/
|
|
48
|
+
size?: 'sm' | 'default' | 'lg' | 'icon' | 'small' | 'medium' | 'large' | BTN_SIZES;
|
|
49
|
+
|
|
24
50
|
uppercase?: boolean;
|
|
25
|
-
children?: ReactNode;
|
|
26
|
-
type?: BTN_TYPES;
|
|
27
|
-
disabled?: boolean;
|
|
28
|
-
ariaLabel?: string;
|
|
29
|
-
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
30
51
|
icon?: string;
|
|
31
52
|
external?: boolean;
|
|
32
53
|
iconFirst?: boolean;
|
|
@@ -34,10 +55,21 @@ export interface Props {
|
|
|
34
55
|
outline?: boolean;
|
|
35
56
|
wide?: boolean;
|
|
36
57
|
rounded?: boolean;
|
|
37
|
-
id?: string;
|
|
38
|
-
testID?: string;
|
|
39
|
-
className?: string;
|
|
40
58
|
isLoading?: boolean;
|
|
41
|
-
iconOnly?: boolean;
|
|
42
59
|
offsetFont?: boolean;
|
|
60
|
+
ariaLabel?: string;
|
|
61
|
+
testID?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated Use ButtonProps instead
|
|
66
|
+
*/
|
|
67
|
+
export interface Props extends Omit<ButtonProps, 'variant' | 'size'> {
|
|
68
|
+
variant?: BTN_VARIANTS;
|
|
69
|
+
size?: BTN_SIZES;
|
|
70
|
+
type?: BTN_TYPES;
|
|
71
|
+
children?: ReactNode;
|
|
72
|
+
disabled?: boolean;
|
|
73
|
+
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
74
|
+
id?: string;
|
|
43
75
|
}
|
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import ButtonComponent from './Button';
|
|
2
|
+
export { Button, buttonVariants } from './Button';
|
|
3
|
+
export type { ButtonProps } from './Button.types';
|
|
4
|
+
export { BTN_VARIANTS, BTN_SIZES, BTN_TYPES } from './Button.types';
|
|
5
|
+
export type { Props } from './Button.types';
|
|
6
|
+
|
|
7
|
+
// Default export for backward compatibility
|
|
8
|
+
export default ButtonComponent;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/nextjs';
|
|
2
|
+
import Button from '.';
|
|
3
|
+
import { BTN_VARIANTS } from './Button.types';
|
|
4
|
+
|
|
5
|
+
type Story = StoryObj<typeof Button>;
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Button> = {
|
|
8
|
+
title: 'DEPRECATED/ButtonDeprecated',
|
|
9
|
+
component: Button,
|
|
10
|
+
tags: ['deprecated'],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
/* STORIES */
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
args: {
|
|
18
|
+
children: <>Default Btn</>,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const WithIcon: Story = {
|
|
23
|
+
args: {
|
|
24
|
+
variant: BTN_VARIANTS.PRIMARY,
|
|
25
|
+
children: <>Example Btn</>,
|
|
26
|
+
icon: 'carbon:arrow-right',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const IconOnly: Story = {
|
|
31
|
+
args: {
|
|
32
|
+
variant: BTN_VARIANTS.PRIMARY,
|
|
33
|
+
icon: 'carbon:arrow-right',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const AsLink: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
variant: BTN_VARIANTS.LINK,
|
|
40
|
+
children: <>Example Link</>,
|
|
41
|
+
icon: 'carbon:arrow-right',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const LoadingButton: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
variant: BTN_VARIANTS.PRIMARY,
|
|
48
|
+
children: <>Example Btn</>,
|
|
49
|
+
isLoading: true,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import cx from 'classnames';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
import { BTN_SIZES, BTN_TYPES, BTN_VARIANTS, Props } from './Button.types';
|
|
4
|
+
import Icon from '../Icon';
|
|
5
|
+
import Loader from '../Loader';
|
|
6
|
+
import { useEffect } from 'react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated ButtonDeprecated is deprecated and will be removed in a future version.
|
|
10
|
+
* Please migrate to the new Button component from '@foundation-ui/core'.
|
|
11
|
+
*
|
|
12
|
+
* Migration guide:
|
|
13
|
+
* - The new Button component is backward compatible with all existing props
|
|
14
|
+
* - No immediate changes required, but consider updating to use the new component
|
|
15
|
+
* - See BUTTON_MIGRATION.md for detailed migration instructions
|
|
16
|
+
*/
|
|
17
|
+
const Button = ({
|
|
18
|
+
variant = BTN_VARIANTS.PRIMARY,
|
|
19
|
+
size = BTN_SIZES.MEDIUM,
|
|
20
|
+
type = BTN_TYPES.BUTTON,
|
|
21
|
+
wide = false,
|
|
22
|
+
rounded = false,
|
|
23
|
+
raised = false,
|
|
24
|
+
uppercase,
|
|
25
|
+
children,
|
|
26
|
+
id,
|
|
27
|
+
disabled,
|
|
28
|
+
ariaLabel,
|
|
29
|
+
onClick,
|
|
30
|
+
icon,
|
|
31
|
+
external,
|
|
32
|
+
iconFirst,
|
|
33
|
+
outline,
|
|
34
|
+
testID,
|
|
35
|
+
className,
|
|
36
|
+
isLoading,
|
|
37
|
+
offsetFont = false,
|
|
38
|
+
}: Props) => {
|
|
39
|
+
// Deprecation warning
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
42
|
+
console.warn(
|
|
43
|
+
'ButtonDeprecated is deprecated and will be removed in a future version. ' +
|
|
44
|
+
'Please migrate to the new Button component. The new Button is fully backward compatible. ' +
|
|
45
|
+
'See BUTTON_MIGRATION.md for migration instructions.'
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
const smallX = wide ? 'px-5' : 'px-3';
|
|
51
|
+
const mediumX = wide ? 'px-8' : 'px-4';
|
|
52
|
+
const largeX = wide ? 'px-11' : 'px-5';
|
|
53
|
+
|
|
54
|
+
const sizes = {
|
|
55
|
+
small: {
|
|
56
|
+
x: children && !rounded ? smallX : children ? 'px-3' : 'px-1',
|
|
57
|
+
y: 'py-1',
|
|
58
|
+
space: 'space-x-2',
|
|
59
|
+
text: 'text-sm',
|
|
60
|
+
icon: 'w-3 h-3',
|
|
61
|
+
},
|
|
62
|
+
medium: {
|
|
63
|
+
x: children && !rounded ? mediumX : children ? 'px-4' : 'px-2',
|
|
64
|
+
y: 'py-2',
|
|
65
|
+
space: 'space-x-3',
|
|
66
|
+
text: 'text-base',
|
|
67
|
+
icon: 'w-5 h-5',
|
|
68
|
+
},
|
|
69
|
+
large: {
|
|
70
|
+
x: children && !rounded ? largeX : children ? 'px-5' : 'px-3',
|
|
71
|
+
y: 'py-3',
|
|
72
|
+
space: 'space-x-4',
|
|
73
|
+
text: 'text-lg',
|
|
74
|
+
icon: 'w-6 h-6',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const classes = twMerge(
|
|
79
|
+
cx(
|
|
80
|
+
'button flex rounded-sm items-center cursor-pointer',
|
|
81
|
+
{ shadow: raised },
|
|
82
|
+
{ uppercase: uppercase },
|
|
83
|
+
{ 'auto-cols-auto grid-cols-2 gap-4': isLoading || icon || external },
|
|
84
|
+
{ 'flex-row-reverse': iconFirst },
|
|
85
|
+
{ 'opacity-50 cursor-not-allowed': disabled },
|
|
86
|
+
`${sizes?.[size]?.y} ${sizes?.[size]?.x} ${sizes?.[size]?.text}`,
|
|
87
|
+
{ 'text-white border-primary bg-primary hover:bg-primary-darker': variant === BTN_VARIANTS.PRIMARY },
|
|
88
|
+
{ 'text-white border-secondary bg-secondary hover:bg-secondary-darker': variant === BTN_VARIANTS.SECONDARY },
|
|
89
|
+
{ 'text-black border-tertiary bg-tertiary hover:bg-tertiary-darker': variant === BTN_VARIANTS.TERTIARY },
|
|
90
|
+
{ 'border-primary bg-transparent text-body-text dark:text-body-text-dark hover:bg-opacity-30': outline },
|
|
91
|
+
{ 'border-primary-300 dark:border-primary-400 hover:border-primary-400 dark:hover:border-primary-500': outline },
|
|
92
|
+
{
|
|
93
|
+
'px-0 text-body-text dark:text-body-text-dark border-transparent bg-transparent hover:bg-transparent shadow-none group-hover:invert':
|
|
94
|
+
variant === BTN_VARIANTS.LINK,
|
|
95
|
+
},
|
|
96
|
+
{ 'rounded-full leading-none': rounded },
|
|
97
|
+
className,
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const iconClasses = cx(sizes?.[size]?.icon);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<button
|
|
105
|
+
id={id}
|
|
106
|
+
data-testid={testID || id || 'Button'}
|
|
107
|
+
onClick={onClick}
|
|
108
|
+
className={classes}
|
|
109
|
+
type={type}
|
|
110
|
+
disabled={disabled}
|
|
111
|
+
aria-label={ariaLabel}
|
|
112
|
+
role="button">
|
|
113
|
+
<>
|
|
114
|
+
{offsetFont ? <span className="brand-font-offset">{children}</span>: children}
|
|
115
|
+
{icon && !external && (
|
|
116
|
+
<span className="flex items-center">
|
|
117
|
+
<Icon name={icon} className={iconClasses} />
|
|
118
|
+
</span>
|
|
119
|
+
)}
|
|
120
|
+
{external && (
|
|
121
|
+
<span className="flex items-center">
|
|
122
|
+
<Icon name="carbon:launch" className={iconClasses} />
|
|
123
|
+
</span>
|
|
124
|
+
)}
|
|
125
|
+
{isLoading && (
|
|
126
|
+
<div className="flex items-center">
|
|
127
|
+
<Loader size={4} />
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
</>
|
|
131
|
+
</button>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
Button.displayName = 'Button';
|
|
136
|
+
|
|
137
|
+
export default Button;
|
|
138
|
+
export type { Props };
|