@xsolla/xui-button 0.78.0 → 0.79.0-pr122.1769779574
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/native/index.d.mts +32 -6
- package/native/index.d.ts +32 -6
- package/native/index.js +172 -70
- package/native/index.js.flow +39 -6
- package/native/index.js.map +1 -1
- package/native/index.mjs +178 -76
- package/native/index.mjs.map +1 -1
- package/package.json +4 -4
- package/web/index.d.mts +32 -6
- package/web/index.d.ts +32 -6
- package/web/index.js +172 -64
- package/web/index.js.flow +39 -6
- package/web/index.js.map +1 -1
- package/web/index.mjs +183 -75
- package/web/index.mjs.map +1 -1
package/native/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import React, { ReactNode } from 'react';
|
|
|
2
2
|
|
|
3
3
|
interface ButtonProps {
|
|
4
4
|
/** Visual variant of the button */
|
|
5
|
-
variant?: "primary" | "secondary";
|
|
5
|
+
variant?: "primary" | "secondary" | "tertiary";
|
|
6
6
|
/** Color tone of the button */
|
|
7
7
|
tone?: "brand" | "brandExtra" | "alert" | "mono";
|
|
8
8
|
/** Size of the button */
|
|
@@ -15,10 +15,32 @@ interface ButtonProps {
|
|
|
15
15
|
children: React.ReactNode;
|
|
16
16
|
/** Click handler */
|
|
17
17
|
onPress?: () => void;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Icon to display on the left side.
|
|
20
|
+
* Size and color are automatically set based on button size/state.
|
|
21
|
+
* To override, specify size/color on the icon: `iconLeft={<ArrowLeft size={16} />}`
|
|
22
|
+
*/
|
|
19
23
|
iconLeft?: React.ReactNode;
|
|
20
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Icon to display on the right side.
|
|
26
|
+
* Size and color are automatically set based on button size/state.
|
|
27
|
+
* To override, specify size/color on the icon: `iconRight={<ArrowRight size={16} />}`
|
|
28
|
+
*/
|
|
21
29
|
iconRight?: React.ReactNode;
|
|
30
|
+
/** Show/hide vertical divider between icon and content (default: true when icon present) */
|
|
31
|
+
divider?: boolean;
|
|
32
|
+
/** Secondary text displayed inline with the main label (e.g., price), shown with 40% opacity */
|
|
33
|
+
sublabel?: string;
|
|
34
|
+
/** Alignment of the label text */
|
|
35
|
+
labelAlignment?: "left" | "center";
|
|
36
|
+
/**
|
|
37
|
+
* Small icon displayed directly next to the label text.
|
|
38
|
+
* Size and color are automatically set based on button size/state.
|
|
39
|
+
* To override, specify size/color on the icon: `labelIcon={<InfoIcon size={12} />}`
|
|
40
|
+
*/
|
|
41
|
+
labelIcon?: React.ReactNode;
|
|
42
|
+
/** Custom content slot for badges, tags, or other elements */
|
|
43
|
+
customContent?: React.ReactNode;
|
|
22
44
|
/** Accessible label for screen readers (use for icon-only buttons) */
|
|
23
45
|
"aria-label"?: string;
|
|
24
46
|
/** ID of element that describes this button */
|
|
@@ -37,7 +59,7 @@ interface ButtonProps {
|
|
|
37
59
|
id?: string;
|
|
38
60
|
/** HTML type attribute for the button */
|
|
39
61
|
type?: "button" | "submit" | "reset";
|
|
40
|
-
/** Whether the button should
|
|
62
|
+
/** Whether the button should stretch to fill the full width of its container */
|
|
41
63
|
fullWidth?: boolean;
|
|
42
64
|
}
|
|
43
65
|
/**
|
|
@@ -59,7 +81,7 @@ declare const Button: React.FC<ButtonProps>;
|
|
|
59
81
|
|
|
60
82
|
interface IconButtonProps {
|
|
61
83
|
/** Visual variant of the button */
|
|
62
|
-
variant?: "primary" | "secondary";
|
|
84
|
+
variant?: "primary" | "secondary" | "tertiary";
|
|
63
85
|
/** Color tone of the button */
|
|
64
86
|
tone?: "brand" | "brandExtra" | "alert" | "mono";
|
|
65
87
|
/** Size of the button */
|
|
@@ -68,7 +90,11 @@ interface IconButtonProps {
|
|
|
68
90
|
disabled?: boolean;
|
|
69
91
|
/** Whether the button is in a loading state */
|
|
70
92
|
loading?: boolean;
|
|
71
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Icon to display in the button (required).
|
|
95
|
+
* Size and color are automatically set based on button size/state.
|
|
96
|
+
* To override, specify size/color on the icon: `icon={<CloseIcon size={16} />}`
|
|
97
|
+
*/
|
|
72
98
|
icon: React.ReactNode;
|
|
73
99
|
/** Click handler */
|
|
74
100
|
onPress?: () => void;
|
package/native/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import React, { ReactNode } from 'react';
|
|
|
2
2
|
|
|
3
3
|
interface ButtonProps {
|
|
4
4
|
/** Visual variant of the button */
|
|
5
|
-
variant?: "primary" | "secondary";
|
|
5
|
+
variant?: "primary" | "secondary" | "tertiary";
|
|
6
6
|
/** Color tone of the button */
|
|
7
7
|
tone?: "brand" | "brandExtra" | "alert" | "mono";
|
|
8
8
|
/** Size of the button */
|
|
@@ -15,10 +15,32 @@ interface ButtonProps {
|
|
|
15
15
|
children: React.ReactNode;
|
|
16
16
|
/** Click handler */
|
|
17
17
|
onPress?: () => void;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Icon to display on the left side.
|
|
20
|
+
* Size and color are automatically set based on button size/state.
|
|
21
|
+
* To override, specify size/color on the icon: `iconLeft={<ArrowLeft size={16} />}`
|
|
22
|
+
*/
|
|
19
23
|
iconLeft?: React.ReactNode;
|
|
20
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Icon to display on the right side.
|
|
26
|
+
* Size and color are automatically set based on button size/state.
|
|
27
|
+
* To override, specify size/color on the icon: `iconRight={<ArrowRight size={16} />}`
|
|
28
|
+
*/
|
|
21
29
|
iconRight?: React.ReactNode;
|
|
30
|
+
/** Show/hide vertical divider between icon and content (default: true when icon present) */
|
|
31
|
+
divider?: boolean;
|
|
32
|
+
/** Secondary text displayed inline with the main label (e.g., price), shown with 40% opacity */
|
|
33
|
+
sublabel?: string;
|
|
34
|
+
/** Alignment of the label text */
|
|
35
|
+
labelAlignment?: "left" | "center";
|
|
36
|
+
/**
|
|
37
|
+
* Small icon displayed directly next to the label text.
|
|
38
|
+
* Size and color are automatically set based on button size/state.
|
|
39
|
+
* To override, specify size/color on the icon: `labelIcon={<InfoIcon size={12} />}`
|
|
40
|
+
*/
|
|
41
|
+
labelIcon?: React.ReactNode;
|
|
42
|
+
/** Custom content slot for badges, tags, or other elements */
|
|
43
|
+
customContent?: React.ReactNode;
|
|
22
44
|
/** Accessible label for screen readers (use for icon-only buttons) */
|
|
23
45
|
"aria-label"?: string;
|
|
24
46
|
/** ID of element that describes this button */
|
|
@@ -37,7 +59,7 @@ interface ButtonProps {
|
|
|
37
59
|
id?: string;
|
|
38
60
|
/** HTML type attribute for the button */
|
|
39
61
|
type?: "button" | "submit" | "reset";
|
|
40
|
-
/** Whether the button should
|
|
62
|
+
/** Whether the button should stretch to fill the full width of its container */
|
|
41
63
|
fullWidth?: boolean;
|
|
42
64
|
}
|
|
43
65
|
/**
|
|
@@ -59,7 +81,7 @@ declare const Button: React.FC<ButtonProps>;
|
|
|
59
81
|
|
|
60
82
|
interface IconButtonProps {
|
|
61
83
|
/** Visual variant of the button */
|
|
62
|
-
variant?: "primary" | "secondary";
|
|
84
|
+
variant?: "primary" | "secondary" | "tertiary";
|
|
63
85
|
/** Color tone of the button */
|
|
64
86
|
tone?: "brand" | "brandExtra" | "alert" | "mono";
|
|
65
87
|
/** Size of the button */
|
|
@@ -68,7 +90,11 @@ interface IconButtonProps {
|
|
|
68
90
|
disabled?: boolean;
|
|
69
91
|
/** Whether the button is in a loading state */
|
|
70
92
|
loading?: boolean;
|
|
71
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Icon to display in the button (required).
|
|
95
|
+
* Size and color are automatically set based on button size/state.
|
|
96
|
+
* To override, specify size/color on the icon: `icon={<CloseIcon size={16} />}`
|
|
97
|
+
*/
|
|
72
98
|
icon: React.ReactNode;
|
|
73
99
|
/** Click handler */
|
|
74
100
|
onPress?: () => void;
|
package/native/index.js
CHANGED
|
@@ -38,7 +38,7 @@ __export(index_exports, {
|
|
|
38
38
|
module.exports = __toCommonJS(index_exports);
|
|
39
39
|
|
|
40
40
|
// src/Button.tsx
|
|
41
|
-
var import_react4 = require("react");
|
|
41
|
+
var import_react4 = __toESM(require("react"));
|
|
42
42
|
|
|
43
43
|
// ../primitives-native/src/Box.tsx
|
|
44
44
|
var import_react_native = require("react-native");
|
|
@@ -250,7 +250,6 @@ var Spinner = ({
|
|
|
250
250
|
role,
|
|
251
251
|
"aria-label": ariaLabel,
|
|
252
252
|
"aria-live": ariaLive,
|
|
253
|
-
"aria-describedby": ariaDescribedBy,
|
|
254
253
|
testID
|
|
255
254
|
}) => {
|
|
256
255
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -369,14 +368,11 @@ var InputPrimitive = (0, import_react2.forwardRef)(
|
|
|
369
368
|
fontSize,
|
|
370
369
|
placeholderTextColor,
|
|
371
370
|
maxLength,
|
|
372
|
-
name,
|
|
373
371
|
type,
|
|
374
372
|
inputMode,
|
|
375
373
|
autoComplete,
|
|
376
374
|
id,
|
|
377
|
-
"aria-invalid": ariaInvalid,
|
|
378
375
|
"aria-describedby": ariaDescribedBy,
|
|
379
|
-
"aria-labelledby": ariaLabelledBy,
|
|
380
376
|
"aria-label": ariaLabel,
|
|
381
377
|
"aria-disabled": ariaDisabled,
|
|
382
378
|
"data-testid": dataTestId
|
|
@@ -468,9 +464,7 @@ var TextAreaPrimitive = (0, import_react3.forwardRef)(
|
|
|
468
464
|
maxLength,
|
|
469
465
|
rows,
|
|
470
466
|
id,
|
|
471
|
-
"aria-invalid": ariaInvalid,
|
|
472
467
|
"aria-describedby": ariaDescribedBy,
|
|
473
|
-
"aria-labelledby": ariaLabelledBy,
|
|
474
468
|
"aria-label": ariaLabel,
|
|
475
469
|
"aria-disabled": ariaDisabled,
|
|
476
470
|
"data-testid": dataTestId
|
|
@@ -542,6 +536,17 @@ TextAreaPrimitive.displayName = "TextAreaPrimitive";
|
|
|
542
536
|
// src/Button.tsx
|
|
543
537
|
var import_xui_core = require("@xsolla/xui-core");
|
|
544
538
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
539
|
+
var cloneIconWithDefaults = (icon, defaultSize, defaultColor) => {
|
|
540
|
+
if (!import_react4.default.isValidElement(icon)) return icon;
|
|
541
|
+
const iconElement = icon;
|
|
542
|
+
const existingProps = iconElement.props || {};
|
|
543
|
+
return import_react4.default.cloneElement(iconElement, {
|
|
544
|
+
...existingProps,
|
|
545
|
+
// Preserve existing props (including accessibility attributes)
|
|
546
|
+
size: existingProps.size ?? defaultSize,
|
|
547
|
+
color: existingProps.color ?? defaultColor
|
|
548
|
+
});
|
|
549
|
+
};
|
|
545
550
|
var Button = ({
|
|
546
551
|
variant = "primary",
|
|
547
552
|
tone = "brand",
|
|
@@ -552,6 +557,11 @@ var Button = ({
|
|
|
552
557
|
onPress,
|
|
553
558
|
iconLeft,
|
|
554
559
|
iconRight,
|
|
560
|
+
divider,
|
|
561
|
+
sublabel,
|
|
562
|
+
labelAlignment = "center",
|
|
563
|
+
labelIcon,
|
|
564
|
+
customContent,
|
|
555
565
|
"aria-label": ariaLabel,
|
|
556
566
|
"aria-describedby": ariaDescribedBy,
|
|
557
567
|
"aria-expanded": ariaExpanded,
|
|
@@ -567,9 +577,17 @@ var Button = ({
|
|
|
567
577
|
const [isKeyboardPressed, setIsKeyboardPressed] = (0, import_react4.useState)(false);
|
|
568
578
|
const isDisabled = disabled || loading;
|
|
569
579
|
const sizeStyles = theme.sizing.button(size);
|
|
570
|
-
const
|
|
580
|
+
const controlTone = theme?.colors?.control?.[tone];
|
|
581
|
+
const variantStyles = controlTone?.[variant] || theme?.colors?.control?.brand?.primary || {
|
|
571
582
|
bg: "transparent",
|
|
572
|
-
|
|
583
|
+
bgHover: "transparent",
|
|
584
|
+
bgPress: "transparent",
|
|
585
|
+
bgDisable: "transparent",
|
|
586
|
+
border: "transparent",
|
|
587
|
+
borderHover: "transparent",
|
|
588
|
+
borderPress: "transparent",
|
|
589
|
+
borderDisable: "transparent",
|
|
590
|
+
text: { primary: "#000", secondary: "#000", disable: "#666" }
|
|
573
591
|
};
|
|
574
592
|
const handlePress = () => {
|
|
575
593
|
if (!isDisabled && onPress) {
|
|
@@ -593,17 +611,19 @@ var Button = ({
|
|
|
593
611
|
}
|
|
594
612
|
}
|
|
595
613
|
};
|
|
596
|
-
|
|
597
|
-
let backgroundColor = styles.bg;
|
|
614
|
+
let backgroundColor = variantStyles.bg;
|
|
598
615
|
if (disabled) {
|
|
599
|
-
backgroundColor =
|
|
616
|
+
backgroundColor = variantStyles.bgDisable || variantStyles.bg;
|
|
600
617
|
} else if (isKeyboardPressed) {
|
|
601
|
-
backgroundColor =
|
|
618
|
+
backgroundColor = variantStyles.bgPress || variantStyles.bg;
|
|
602
619
|
}
|
|
603
|
-
const borderColor = disabled ?
|
|
604
|
-
const textColor = disabled ?
|
|
605
|
-
const
|
|
606
|
-
const
|
|
620
|
+
const borderColor = disabled ? variantStyles.borderDisable || variantStyles.border : variantStyles.border;
|
|
621
|
+
const textColor = disabled ? variantStyles.text?.disable || variantStyles.text?.primary : variantStyles.text?.primary;
|
|
622
|
+
const textColorStr = typeof textColor === "string" ? textColor : "";
|
|
623
|
+
const isDarkText = textColorStr === "#000000" || textColorStr === "black" || textColorStr.startsWith("rgba(0, 0, 0");
|
|
624
|
+
const dividerLineColor = isDarkText ? "rgba(0, 0, 0, 0.2)" : "rgba(255, 255, 255, 0.2)";
|
|
625
|
+
const hasIcon = Boolean(iconLeft || iconRight);
|
|
626
|
+
const showDivider = divider !== void 0 ? divider : hasIcon;
|
|
607
627
|
const computedAriaLabel = ariaLabel;
|
|
608
628
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
609
629
|
Box,
|
|
@@ -627,7 +647,7 @@ var Button = ({
|
|
|
627
647
|
backgroundColor,
|
|
628
648
|
borderColor,
|
|
629
649
|
borderWidth: borderColor !== "transparent" && borderColor !== "rgba(255, 255, 255, 0)" ? 1 : 0,
|
|
630
|
-
borderRadius:
|
|
650
|
+
borderRadius: sizeStyles.borderRadius,
|
|
631
651
|
height: sizeStyles.height,
|
|
632
652
|
width: fullWidth ? "100%" : void 0,
|
|
633
653
|
padding: 0,
|
|
@@ -650,72 +670,110 @@ var Button = ({
|
|
|
650
670
|
outlineStyle: "solid"
|
|
651
671
|
},
|
|
652
672
|
children: [
|
|
653
|
-
|
|
673
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
674
|
+
Box,
|
|
675
|
+
{
|
|
676
|
+
position: "absolute",
|
|
677
|
+
top: 0,
|
|
678
|
+
left: 0,
|
|
679
|
+
right: 0,
|
|
680
|
+
bottom: 0,
|
|
681
|
+
alignItems: "center",
|
|
682
|
+
justifyContent: "center",
|
|
683
|
+
zIndex: 1,
|
|
684
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
685
|
+
Spinner,
|
|
686
|
+
{
|
|
687
|
+
color: textColor,
|
|
688
|
+
size: sizeStyles.spinnerSize,
|
|
689
|
+
"aria-hidden": true
|
|
690
|
+
}
|
|
691
|
+
)
|
|
692
|
+
}
|
|
693
|
+
),
|
|
694
|
+
iconLeft && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
654
695
|
Box,
|
|
655
696
|
{
|
|
656
697
|
height: "100%",
|
|
657
698
|
flexDirection: "row",
|
|
658
699
|
alignItems: "center",
|
|
659
|
-
justifyContent: "center",
|
|
660
700
|
"aria-hidden": true,
|
|
701
|
+
style: {
|
|
702
|
+
opacity: loading ? 0 : 1,
|
|
703
|
+
pointerEvents: loading ? "none" : "auto"
|
|
704
|
+
},
|
|
661
705
|
children: [
|
|
662
706
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
663
707
|
Box,
|
|
664
708
|
{
|
|
709
|
+
width: sizeStyles.iconContainerSize,
|
|
710
|
+
height: sizeStyles.iconContainerSize,
|
|
665
711
|
alignItems: "center",
|
|
666
712
|
justifyContent: "center",
|
|
667
|
-
|
|
668
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, { size: sizeStyles.iconSize, color: textColor, children: iconLeft })
|
|
713
|
+
children: cloneIconWithDefaults(iconLeft, sizeStyles.iconSize, textColor)
|
|
669
714
|
}
|
|
670
715
|
),
|
|
671
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Divider, { vertical: true, color:
|
|
716
|
+
showDivider && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Divider, { vertical: true, color: dividerLineColor, height: "100%" })
|
|
672
717
|
]
|
|
673
718
|
}
|
|
674
719
|
),
|
|
675
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.
|
|
720
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
676
721
|
Box,
|
|
677
722
|
{
|
|
678
723
|
flex: fullWidth ? 1 : void 0,
|
|
679
724
|
flexDirection: "row",
|
|
680
725
|
alignItems: "center",
|
|
681
|
-
justifyContent: "center",
|
|
682
|
-
paddingHorizontal:
|
|
726
|
+
justifyContent: labelAlignment === "left" ? "flex-start" : "center",
|
|
727
|
+
paddingHorizontal: sizeStyles.padding,
|
|
683
728
|
height: "100%",
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
729
|
+
gap: sizeStyles.labelIconGap,
|
|
730
|
+
style: {
|
|
731
|
+
opacity: loading ? 0 : 1,
|
|
732
|
+
pointerEvents: loading ? "none" : "auto"
|
|
733
|
+
},
|
|
734
|
+
"aria-hidden": loading ? true : void 0,
|
|
735
|
+
children: [
|
|
736
|
+
labelIcon && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { "aria-hidden": true, children: cloneIconWithDefaults(
|
|
737
|
+
labelIcon,
|
|
738
|
+
sizeStyles.labelIconSize,
|
|
739
|
+
textColor
|
|
740
|
+
) }),
|
|
741
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: textColor, fontSize: sizeStyles.fontSize, fontWeight: "500", children }),
|
|
742
|
+
sublabel && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
743
|
+
Text,
|
|
744
|
+
{
|
|
745
|
+
color: textColor,
|
|
746
|
+
fontSize: sizeStyles.fontSize,
|
|
747
|
+
fontWeight: "500",
|
|
748
|
+
style: { opacity: 0.4 },
|
|
749
|
+
children: sublabel
|
|
750
|
+
}
|
|
751
|
+
),
|
|
752
|
+
customContent && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { "aria-hidden": true, children: customContent })
|
|
753
|
+
]
|
|
700
754
|
}
|
|
701
755
|
),
|
|
702
|
-
|
|
756
|
+
iconRight && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
703
757
|
Box,
|
|
704
758
|
{
|
|
705
759
|
height: "100%",
|
|
706
760
|
flexDirection: "row",
|
|
707
761
|
alignItems: "center",
|
|
708
|
-
justifyContent: "center",
|
|
709
762
|
"aria-hidden": true,
|
|
763
|
+
style: {
|
|
764
|
+
opacity: loading ? 0 : 1,
|
|
765
|
+
pointerEvents: loading ? "none" : "auto"
|
|
766
|
+
},
|
|
710
767
|
children: [
|
|
711
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Divider, { vertical: true, color:
|
|
768
|
+
showDivider && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Divider, { vertical: true, color: dividerLineColor, height: "100%" }),
|
|
712
769
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
713
770
|
Box,
|
|
714
771
|
{
|
|
772
|
+
width: sizeStyles.iconContainerSize,
|
|
773
|
+
height: sizeStyles.iconContainerSize,
|
|
715
774
|
alignItems: "center",
|
|
716
775
|
justifyContent: "center",
|
|
717
|
-
|
|
718
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, { size: sizeStyles.iconSize, color: textColor, children: iconRight })
|
|
776
|
+
children: cloneIconWithDefaults(iconRight, sizeStyles.iconSize, textColor)
|
|
719
777
|
}
|
|
720
778
|
)
|
|
721
779
|
]
|
|
@@ -728,9 +786,20 @@ var Button = ({
|
|
|
728
786
|
Button.displayName = "Button";
|
|
729
787
|
|
|
730
788
|
// src/IconButton.tsx
|
|
731
|
-
var import_react5 = require("react");
|
|
789
|
+
var import_react5 = __toESM(require("react"));
|
|
732
790
|
var import_xui_core2 = require("@xsolla/xui-core");
|
|
733
791
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
792
|
+
var cloneIconWithDefaults2 = (icon, defaultSize, defaultColor) => {
|
|
793
|
+
if (!import_react5.default.isValidElement(icon)) return icon;
|
|
794
|
+
const iconElement = icon;
|
|
795
|
+
const existingProps = iconElement.props || {};
|
|
796
|
+
return import_react5.default.cloneElement(iconElement, {
|
|
797
|
+
...existingProps,
|
|
798
|
+
// Preserve existing props (including accessibility attributes)
|
|
799
|
+
size: existingProps.size ?? defaultSize,
|
|
800
|
+
color: existingProps.color ?? defaultColor
|
|
801
|
+
});
|
|
802
|
+
};
|
|
734
803
|
var IconButton = ({
|
|
735
804
|
variant = "primary",
|
|
736
805
|
tone = "brand",
|
|
@@ -753,9 +822,17 @@ var IconButton = ({
|
|
|
753
822
|
const [isKeyboardPressed, setIsKeyboardPressed] = (0, import_react5.useState)(false);
|
|
754
823
|
const isDisabled = disabled || loading;
|
|
755
824
|
const sizeStyles = theme.sizing.button(size);
|
|
756
|
-
const
|
|
825
|
+
const controlTone = theme?.colors?.control?.[tone];
|
|
826
|
+
const variantStyles = controlTone?.[variant] || theme?.colors?.control?.brand?.primary || {
|
|
757
827
|
bg: "transparent",
|
|
758
|
-
|
|
828
|
+
bgHover: "transparent",
|
|
829
|
+
bgPress: "transparent",
|
|
830
|
+
bgDisable: "transparent",
|
|
831
|
+
border: "transparent",
|
|
832
|
+
borderHover: "transparent",
|
|
833
|
+
borderPress: "transparent",
|
|
834
|
+
borderDisable: "transparent",
|
|
835
|
+
text: { primary: "#000", secondary: "#000", disable: "#666" }
|
|
759
836
|
};
|
|
760
837
|
const handlePress = () => {
|
|
761
838
|
if (!isDisabled && onPress) {
|
|
@@ -779,16 +856,15 @@ var IconButton = ({
|
|
|
779
856
|
}
|
|
780
857
|
}
|
|
781
858
|
};
|
|
782
|
-
|
|
783
|
-
let backgroundColor = styles.bg;
|
|
859
|
+
let backgroundColor = variantStyles.bg;
|
|
784
860
|
if (disabled) {
|
|
785
|
-
backgroundColor =
|
|
861
|
+
backgroundColor = variantStyles.bgDisable || variantStyles.bg;
|
|
786
862
|
} else if (isKeyboardPressed) {
|
|
787
|
-
backgroundColor =
|
|
863
|
+
backgroundColor = variantStyles.bgPress || variantStyles.bg;
|
|
788
864
|
}
|
|
789
|
-
const borderColor = disabled ?
|
|
790
|
-
const textColor = disabled ?
|
|
791
|
-
return /* @__PURE__ */ (0, import_jsx_runtime9.
|
|
865
|
+
const borderColor = disabled ? variantStyles.borderDisable || variantStyles.border : variantStyles.border;
|
|
866
|
+
const textColor = disabled ? variantStyles.text?.disable || variantStyles.text?.primary : variantStyles.text?.primary;
|
|
867
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
792
868
|
Box,
|
|
793
869
|
{
|
|
794
870
|
as: "button",
|
|
@@ -810,7 +886,7 @@ var IconButton = ({
|
|
|
810
886
|
backgroundColor,
|
|
811
887
|
borderColor,
|
|
812
888
|
borderWidth: borderColor !== "transparent" && borderColor !== "rgba(255, 255, 255, 0)" ? 1 : 0,
|
|
813
|
-
borderRadius:
|
|
889
|
+
borderRadius: sizeStyles.borderRadius,
|
|
814
890
|
height: sizeStyles.height,
|
|
815
891
|
width: sizeStyles.height,
|
|
816
892
|
padding: 0,
|
|
@@ -821,10 +897,10 @@ var IconButton = ({
|
|
|
821
897
|
cursor: disabled ? "not-allowed" : loading ? "wait" : "pointer",
|
|
822
898
|
opacity: disabled ? 0.6 : 1,
|
|
823
899
|
hoverStyle: !isDisabled ? {
|
|
824
|
-
backgroundColor:
|
|
900
|
+
backgroundColor: variantStyles.bgHover
|
|
825
901
|
} : void 0,
|
|
826
902
|
pressStyle: !isDisabled ? {
|
|
827
|
-
backgroundColor:
|
|
903
|
+
backgroundColor: variantStyles.bgPress
|
|
828
904
|
} : void 0,
|
|
829
905
|
focusStyle: {
|
|
830
906
|
outlineColor: theme.colors.border.brand,
|
|
@@ -832,14 +908,40 @@ var IconButton = ({
|
|
|
832
908
|
outlineOffset: 2,
|
|
833
909
|
outlineStyle: "solid"
|
|
834
910
|
},
|
|
835
|
-
children:
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
911
|
+
children: [
|
|
912
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
913
|
+
Box,
|
|
914
|
+
{
|
|
915
|
+
position: "absolute",
|
|
916
|
+
top: 0,
|
|
917
|
+
left: 0,
|
|
918
|
+
right: 0,
|
|
919
|
+
bottom: 0,
|
|
920
|
+
alignItems: "center",
|
|
921
|
+
justifyContent: "center",
|
|
922
|
+
zIndex: 1,
|
|
923
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
924
|
+
Spinner,
|
|
925
|
+
{
|
|
926
|
+
color: textColor,
|
|
927
|
+
size: sizeStyles.spinnerSize,
|
|
928
|
+
"aria-hidden": true
|
|
929
|
+
}
|
|
930
|
+
)
|
|
931
|
+
}
|
|
932
|
+
),
|
|
933
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
934
|
+
Box,
|
|
935
|
+
{
|
|
936
|
+
"aria-hidden": true,
|
|
937
|
+
style: {
|
|
938
|
+
opacity: loading ? 0 : 1,
|
|
939
|
+
pointerEvents: loading ? "none" : "auto"
|
|
940
|
+
},
|
|
941
|
+
children: cloneIconWithDefaults2(icon, sizeStyles.iconSize, textColor)
|
|
942
|
+
}
|
|
943
|
+
)
|
|
944
|
+
]
|
|
843
945
|
}
|
|
844
946
|
);
|
|
845
947
|
};
|
|
@@ -1232,7 +1334,7 @@ var ButtonGroup = ({
|
|
|
1232
1334
|
const computedAriaDescribedBy = [
|
|
1233
1335
|
ariaDescribedBy,
|
|
1234
1336
|
error && errorId ? errorId : void 0,
|
|
1235
|
-
description &&
|
|
1337
|
+
description && descriptionId ? descriptionId : void 0
|
|
1236
1338
|
].filter(Boolean).join(" ") || void 0;
|
|
1237
1339
|
const processChildren = (childrenToProcess) => {
|
|
1238
1340
|
if (orientation === "vertical") {
|
|
@@ -1295,7 +1397,7 @@ var ButtonGroup = ({
|
|
|
1295
1397
|
children: error
|
|
1296
1398
|
}
|
|
1297
1399
|
) }),
|
|
1298
|
-
description &&
|
|
1400
|
+
description && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box, { marginTop: 4, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1299
1401
|
Text,
|
|
1300
1402
|
{
|
|
1301
1403
|
id: descriptionId,
|
package/native/index.js.flow
CHANGED
|
@@ -10,7 +10,7 @@ declare interface ButtonProps {
|
|
|
10
10
|
/**
|
|
11
11
|
* Visual variant of the button
|
|
12
12
|
*/
|
|
13
|
-
variant?: "primary" | "secondary";
|
|
13
|
+
variant?: "primary" | "secondary" | "tertiary";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Color tone of the button
|
|
@@ -43,15 +43,46 @@ declare interface ButtonProps {
|
|
|
43
43
|
onPress?: () => void;
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Icon to display on the left side
|
|
46
|
+
* Icon to display on the left side.
|
|
47
|
+
* Size and color are automatically set based on button size/state.
|
|
48
|
+
* To override, specify size/color on the icon: `iconLeft={<ArrowLeft size={16} />}`
|
|
47
49
|
*/
|
|
48
50
|
iconLeft?: React.ReactNode;
|
|
49
51
|
|
|
50
52
|
/**
|
|
51
|
-
* Icon to display on the right side
|
|
53
|
+
* Icon to display on the right side.
|
|
54
|
+
* Size and color are automatically set based on button size/state.
|
|
55
|
+
* To override, specify size/color on the icon: `iconRight={<ArrowRight size={16} />}`
|
|
52
56
|
*/
|
|
53
57
|
iconRight?: React.ReactNode;
|
|
54
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Show/hide vertical divider between icon and content (default: true when icon present)
|
|
61
|
+
*/
|
|
62
|
+
divider?: boolean;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Secondary text displayed inline with the main label (e.g., price), shown with 40% opacity
|
|
66
|
+
*/
|
|
67
|
+
sublabel?: string;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Alignment of the label text
|
|
71
|
+
*/
|
|
72
|
+
labelAlignment?: "left" | "center";
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Small icon displayed directly next to the label text.
|
|
76
|
+
* Size and color are automatically set based on button size/state.
|
|
77
|
+
* To override, specify size/color on the icon: `labelIcon={<InfoIcon size={12} />}`
|
|
78
|
+
*/
|
|
79
|
+
labelIcon?: React.ReactNode;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Custom content slot for badges, tags, or other elements
|
|
83
|
+
*/
|
|
84
|
+
customContent?: React.ReactNode;
|
|
85
|
+
|
|
55
86
|
/**
|
|
56
87
|
* Accessible label for screen readers (use for icon-only buttons)
|
|
57
88
|
*/
|
|
@@ -98,7 +129,7 @@ declare interface ButtonProps {
|
|
|
98
129
|
type?: "button" | "submit" | "reset";
|
|
99
130
|
|
|
100
131
|
/**
|
|
101
|
-
* Whether the button should
|
|
132
|
+
* Whether the button should stretch to fill the full width of its container
|
|
102
133
|
*/
|
|
103
134
|
fullWidth?: boolean;
|
|
104
135
|
}
|
|
@@ -121,7 +152,7 @@ declare interface IconButtonProps {
|
|
|
121
152
|
/**
|
|
122
153
|
* Visual variant of the button
|
|
123
154
|
*/
|
|
124
|
-
variant?: "primary" | "secondary";
|
|
155
|
+
variant?: "primary" | "secondary" | "tertiary";
|
|
125
156
|
|
|
126
157
|
/**
|
|
127
158
|
* Color tone of the button
|
|
@@ -144,7 +175,9 @@ declare interface IconButtonProps {
|
|
|
144
175
|
loading?: boolean;
|
|
145
176
|
|
|
146
177
|
/**
|
|
147
|
-
* Icon to display in the button (required)
|
|
178
|
+
* Icon to display in the button (required).
|
|
179
|
+
* Size and color are automatically set based on button size/state.
|
|
180
|
+
* To override, specify size/color on the icon: `icon={<CloseIcon size={16} />}`
|
|
148
181
|
*/
|
|
149
182
|
icon: React.ReactNode;
|
|
150
183
|
|