@xsolla/xui-button 0.79.0 → 0.80.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.
@@ -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
- /** Icon to display on the left side */
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
- /** Icon to display on the right side */
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 take up the full width of its container */
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
- /** Icon to display in the button (required) */
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
- /** Icon to display on the left side */
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
- /** Icon to display on the right side */
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 take up the full width of its container */
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
- /** Icon to display in the button (required) */
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");
@@ -536,6 +536,17 @@ TextAreaPrimitive.displayName = "TextAreaPrimitive";
536
536
  // src/Button.tsx
537
537
  var import_xui_core = require("@xsolla/xui-core");
538
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
+ };
539
550
  var Button = ({
540
551
  variant = "primary",
541
552
  tone = "brand",
@@ -546,6 +557,11 @@ var Button = ({
546
557
  onPress,
547
558
  iconLeft,
548
559
  iconRight,
560
+ divider,
561
+ sublabel,
562
+ labelAlignment = "center",
563
+ labelIcon,
564
+ customContent,
549
565
  "aria-label": ariaLabel,
550
566
  "aria-describedby": ariaDescribedBy,
551
567
  "aria-expanded": ariaExpanded,
@@ -561,9 +577,17 @@ var Button = ({
561
577
  const [isKeyboardPressed, setIsKeyboardPressed] = (0, import_react4.useState)(false);
562
578
  const isDisabled = disabled || loading;
563
579
  const sizeStyles = theme.sizing.button(size);
564
- const variantStyles = theme?.colors?.control?.[tone]?.[variant] || theme?.colors?.control?.brand?.primary || {
580
+ const controlTone = theme?.colors?.control?.[tone];
581
+ const variantStyles = controlTone?.[variant] || theme?.colors?.control?.brand?.primary || {
565
582
  bg: "transparent",
566
- text: { primary: "#000" }
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" }
567
591
  };
568
592
  const handlePress = () => {
569
593
  if (!isDisabled && onPress) {
@@ -587,17 +611,19 @@ var Button = ({
587
611
  }
588
612
  }
589
613
  };
590
- const styles = variantStyles;
591
- let backgroundColor = styles.bg;
614
+ let backgroundColor = variantStyles.bg;
592
615
  if (disabled) {
593
- backgroundColor = styles.bgDisable || styles.bg;
616
+ backgroundColor = variantStyles.bgDisable || variantStyles.bg;
594
617
  } else if (isKeyboardPressed) {
595
- backgroundColor = styles.bgPress || styles.bg;
618
+ backgroundColor = variantStyles.bgPress || variantStyles.bg;
596
619
  }
597
- const borderColor = disabled ? styles.borderDisable || styles.border : styles.border;
598
- const textColor = disabled ? styles.text?.disable || styles.text?.primary : styles.text?.primary;
599
- const isDarkText = textColor === "#000000" || textColor === "black" || textColor.startsWith("rgba(0, 0, 0");
600
- const dividerColor = isDarkText ? "rgba(0, 0, 0, 0.2)" : "rgba(255, 255, 255, 0.2)";
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;
601
627
  const computedAriaLabel = ariaLabel;
602
628
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
603
629
  Box,
@@ -621,7 +647,7 @@ var Button = ({
621
647
  backgroundColor,
622
648
  borderColor,
623
649
  borderWidth: borderColor !== "transparent" && borderColor !== "rgba(255, 255, 255, 0)" ? 1 : 0,
624
- borderRadius: theme.radius.button,
650
+ borderRadius: sizeStyles.borderRadius,
625
651
  height: sizeStyles.height,
626
652
  width: fullWidth ? "100%" : void 0,
627
653
  padding: 0,
@@ -644,72 +670,110 @@ var Button = ({
644
670
  outlineStyle: "solid"
645
671
  },
646
672
  children: [
647
- !loading && iconLeft && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
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)(
648
695
  Box,
649
696
  {
650
697
  height: "100%",
651
698
  flexDirection: "row",
652
699
  alignItems: "center",
653
- justifyContent: "center",
654
700
  "aria-hidden": true,
701
+ style: {
702
+ opacity: loading ? 0 : 1,
703
+ pointerEvents: loading ? "none" : "auto"
704
+ },
655
705
  children: [
656
706
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
657
707
  Box,
658
708
  {
709
+ width: sizeStyles.iconContainerSize,
710
+ height: sizeStyles.iconContainerSize,
659
711
  alignItems: "center",
660
712
  justifyContent: "center",
661
- paddingHorizontal: sizeStyles.iconPadding,
662
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, { size: sizeStyles.iconSize, color: textColor, children: iconLeft })
713
+ children: cloneIconWithDefaults(iconLeft, sizeStyles.iconSize, textColor)
663
714
  }
664
715
  ),
665
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Divider, { vertical: true, color: dividerColor, height: "100%" })
716
+ showDivider && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Divider, { vertical: true, color: dividerLineColor, height: "100%" })
666
717
  ]
667
718
  }
668
719
  ),
669
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
720
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
670
721
  Box,
671
722
  {
672
723
  flex: fullWidth ? 1 : void 0,
673
724
  flexDirection: "row",
674
725
  alignItems: "center",
675
- justifyContent: "center",
676
- paddingHorizontal: loading ? sizeStyles.loadingPadding : sizeStyles.padding,
726
+ justifyContent: labelAlignment === "left" ? "flex-start" : "center",
727
+ paddingHorizontal: sizeStyles.padding,
677
728
  height: "100%",
678
- children: loading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
679
- Spinner,
680
- {
681
- color: textColor,
682
- size: sizeStyles.spinnerSize,
683
- "aria-hidden": true
684
- }
685
- ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
686
- Text,
687
- {
688
- color: textColor,
689
- fontSize: sizeStyles.fontSize,
690
- fontWeight: "500",
691
- children
692
- }
693
- )
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
+ ]
694
754
  }
695
755
  ),
696
- !loading && iconRight && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
756
+ iconRight && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
697
757
  Box,
698
758
  {
699
759
  height: "100%",
700
760
  flexDirection: "row",
701
761
  alignItems: "center",
702
- justifyContent: "center",
703
762
  "aria-hidden": true,
763
+ style: {
764
+ opacity: loading ? 0 : 1,
765
+ pointerEvents: loading ? "none" : "auto"
766
+ },
704
767
  children: [
705
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Divider, { vertical: true, color: dividerColor, height: "100%" }),
768
+ showDivider && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Divider, { vertical: true, color: dividerLineColor, height: "100%" }),
706
769
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
707
770
  Box,
708
771
  {
772
+ width: sizeStyles.iconContainerSize,
773
+ height: sizeStyles.iconContainerSize,
709
774
  alignItems: "center",
710
775
  justifyContent: "center",
711
- paddingHorizontal: sizeStyles.iconPadding,
712
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, { size: sizeStyles.iconSize, color: textColor, children: iconRight })
776
+ children: cloneIconWithDefaults(iconRight, sizeStyles.iconSize, textColor)
713
777
  }
714
778
  )
715
779
  ]
@@ -722,9 +786,20 @@ var Button = ({
722
786
  Button.displayName = "Button";
723
787
 
724
788
  // src/IconButton.tsx
725
- var import_react5 = require("react");
789
+ var import_react5 = __toESM(require("react"));
726
790
  var import_xui_core2 = require("@xsolla/xui-core");
727
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
+ };
728
803
  var IconButton = ({
729
804
  variant = "primary",
730
805
  tone = "brand",
@@ -747,9 +822,17 @@ var IconButton = ({
747
822
  const [isKeyboardPressed, setIsKeyboardPressed] = (0, import_react5.useState)(false);
748
823
  const isDisabled = disabled || loading;
749
824
  const sizeStyles = theme.sizing.button(size);
750
- const variantStyles = theme?.colors?.control?.[tone]?.[variant] || theme?.colors?.control?.brand?.primary || {
825
+ const controlTone = theme?.colors?.control?.[tone];
826
+ const variantStyles = controlTone?.[variant] || theme?.colors?.control?.brand?.primary || {
751
827
  bg: "transparent",
752
- text: { primary: "#000" }
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" }
753
836
  };
754
837
  const handlePress = () => {
755
838
  if (!isDisabled && onPress) {
@@ -773,16 +856,15 @@ var IconButton = ({
773
856
  }
774
857
  }
775
858
  };
776
- const styles = variantStyles;
777
- let backgroundColor = styles.bg;
859
+ let backgroundColor = variantStyles.bg;
778
860
  if (disabled) {
779
- backgroundColor = styles.bgDisable || styles.bg;
861
+ backgroundColor = variantStyles.bgDisable || variantStyles.bg;
780
862
  } else if (isKeyboardPressed) {
781
- backgroundColor = styles.bgPress || styles.bg;
863
+ backgroundColor = variantStyles.bgPress || variantStyles.bg;
782
864
  }
783
- const borderColor = disabled ? styles.borderDisable || styles.border : styles.border;
784
- const textColor = disabled ? styles.text?.disable || styles.text?.primary : styles.text?.primary;
785
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
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)(
786
868
  Box,
787
869
  {
788
870
  as: "button",
@@ -804,7 +886,7 @@ var IconButton = ({
804
886
  backgroundColor,
805
887
  borderColor,
806
888
  borderWidth: borderColor !== "transparent" && borderColor !== "rgba(255, 255, 255, 0)" ? 1 : 0,
807
- borderRadius: theme.radius.button,
889
+ borderRadius: sizeStyles.borderRadius,
808
890
  height: sizeStyles.height,
809
891
  width: sizeStyles.height,
810
892
  padding: 0,
@@ -815,10 +897,10 @@ var IconButton = ({
815
897
  cursor: disabled ? "not-allowed" : loading ? "wait" : "pointer",
816
898
  opacity: disabled ? 0.6 : 1,
817
899
  hoverStyle: !isDisabled ? {
818
- backgroundColor: styles.bgHover
900
+ backgroundColor: variantStyles.bgHover
819
901
  } : void 0,
820
902
  pressStyle: !isDisabled ? {
821
- backgroundColor: styles.bgPress
903
+ backgroundColor: variantStyles.bgPress
822
904
  } : void 0,
823
905
  focusStyle: {
824
906
  outlineColor: theme.colors.border.brand,
@@ -826,14 +908,40 @@ var IconButton = ({
826
908
  outlineOffset: 2,
827
909
  outlineStyle: "solid"
828
910
  },
829
- children: loading ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
830
- Spinner,
831
- {
832
- color: textColor,
833
- size: sizeStyles.spinnerSize,
834
- "aria-hidden": true
835
- }
836
- ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { size: sizeStyles.iconSize, color: textColor, "aria-hidden": true, children: icon })
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
+ ]
837
945
  }
838
946
  );
839
947
  };
@@ -1226,7 +1334,7 @@ var ButtonGroup = ({
1226
1334
  const computedAriaDescribedBy = [
1227
1335
  ariaDescribedBy,
1228
1336
  error && errorId ? errorId : void 0,
1229
- description && !error && descriptionId ? descriptionId : void 0
1337
+ description && descriptionId ? descriptionId : void 0
1230
1338
  ].filter(Boolean).join(" ") || void 0;
1231
1339
  const processChildren = (childrenToProcess) => {
1232
1340
  if (orientation === "vertical") {
@@ -1289,7 +1397,7 @@ var ButtonGroup = ({
1289
1397
  children: error
1290
1398
  }
1291
1399
  ) }),
1292
- description && !error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box, { marginTop: 4, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1400
+ description && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box, { marginTop: 4, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1293
1401
  Text,
1294
1402
  {
1295
1403
  id: descriptionId,
@@ -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 take up the full width of its container
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