@xsolla/xui-button 0.79.0 → 0.81.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/web/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/Button.tsx
2
- import { useState } from "react";
2
+ import React3, { useState } from "react";
3
3
 
4
4
  // ../primitives-web/src/Box.tsx
5
5
  import React from "react";
@@ -458,6 +458,17 @@ TextAreaPrimitive.displayName = "TextAreaPrimitive";
458
458
  // src/Button.tsx
459
459
  import { useDesignSystem } from "@xsolla/xui-core";
460
460
  import { jsx as jsx8, jsxs } from "react/jsx-runtime";
461
+ var cloneIconWithDefaults = (icon, defaultSize, defaultColor) => {
462
+ if (!React3.isValidElement(icon)) return icon;
463
+ const iconElement = icon;
464
+ const existingProps = iconElement.props || {};
465
+ return React3.cloneElement(iconElement, {
466
+ ...existingProps,
467
+ // Preserve existing props (including accessibility attributes)
468
+ size: existingProps.size ?? defaultSize,
469
+ color: existingProps.color ?? defaultColor
470
+ });
471
+ };
461
472
  var Button = ({
462
473
  variant = "primary",
463
474
  tone = "brand",
@@ -468,6 +479,11 @@ var Button = ({
468
479
  onPress,
469
480
  iconLeft,
470
481
  iconRight,
482
+ divider,
483
+ sublabel,
484
+ labelAlignment = "center",
485
+ labelIcon,
486
+ customContent,
471
487
  "aria-label": ariaLabel,
472
488
  "aria-describedby": ariaDescribedBy,
473
489
  "aria-expanded": ariaExpanded,
@@ -483,9 +499,17 @@ var Button = ({
483
499
  const [isKeyboardPressed, setIsKeyboardPressed] = useState(false);
484
500
  const isDisabled = disabled || loading;
485
501
  const sizeStyles = theme.sizing.button(size);
486
- const variantStyles = theme?.colors?.control?.[tone]?.[variant] || theme?.colors?.control?.brand?.primary || {
502
+ const controlTone = theme?.colors?.control?.[tone];
503
+ const variantStyles = controlTone?.[variant] || theme?.colors?.control?.brand?.primary || {
487
504
  bg: "transparent",
488
- text: { primary: "#000" }
505
+ bgHover: "transparent",
506
+ bgPress: "transparent",
507
+ bgDisable: "transparent",
508
+ border: "transparent",
509
+ borderHover: "transparent",
510
+ borderPress: "transparent",
511
+ borderDisable: "transparent",
512
+ text: { primary: "#000", secondary: "#000", disable: "#666" }
489
513
  };
490
514
  const handlePress = () => {
491
515
  if (!isDisabled && onPress) {
@@ -509,17 +533,19 @@ var Button = ({
509
533
  }
510
534
  }
511
535
  };
512
- const styles = variantStyles;
513
- let backgroundColor = styles.bg;
536
+ let backgroundColor = variantStyles.bg;
514
537
  if (disabled) {
515
- backgroundColor = styles.bgDisable || styles.bg;
538
+ backgroundColor = variantStyles.bgDisable || variantStyles.bg;
516
539
  } else if (isKeyboardPressed) {
517
- backgroundColor = styles.bgPress || styles.bg;
540
+ backgroundColor = variantStyles.bgPress || variantStyles.bg;
518
541
  }
519
- const borderColor = disabled ? styles.borderDisable || styles.border : styles.border;
520
- const textColor = disabled ? styles.text?.disable || styles.text?.primary : styles.text?.primary;
521
- const isDarkText = textColor === "#000000" || textColor === "black" || textColor.startsWith("rgba(0, 0, 0");
522
- const dividerColor = isDarkText ? "rgba(0, 0, 0, 0.2)" : "rgba(255, 255, 255, 0.2)";
542
+ const borderColor = disabled ? variantStyles.borderDisable || variantStyles.border : variantStyles.border;
543
+ const textColor = disabled ? variantStyles.text?.disable || variantStyles.text?.primary : variantStyles.text?.primary;
544
+ const textColorStr = typeof textColor === "string" ? textColor : "";
545
+ const isDarkText = textColorStr === "#000000" || textColorStr === "black" || textColorStr.startsWith("rgba(0, 0, 0");
546
+ const dividerLineColor = isDarkText ? "rgba(0, 0, 0, 0.2)" : "rgba(255, 255, 255, 0.2)";
547
+ const hasIcon = Boolean(iconLeft || iconRight);
548
+ const showDivider = divider !== void 0 ? divider : hasIcon;
523
549
  const computedAriaLabel = ariaLabel;
524
550
  return /* @__PURE__ */ jsxs(
525
551
  Box,
@@ -543,7 +569,7 @@ var Button = ({
543
569
  backgroundColor,
544
570
  borderColor,
545
571
  borderWidth: borderColor !== "transparent" && borderColor !== "rgba(255, 255, 255, 0)" ? 1 : 0,
546
- borderRadius: theme.radius.button,
572
+ borderRadius: sizeStyles.borderRadius,
547
573
  height: sizeStyles.height,
548
574
  width: fullWidth ? "100%" : void 0,
549
575
  padding: 0,
@@ -566,72 +592,110 @@ var Button = ({
566
592
  outlineStyle: "solid"
567
593
  },
568
594
  children: [
569
- !loading && iconLeft && /* @__PURE__ */ jsxs(
595
+ loading && /* @__PURE__ */ jsx8(
596
+ Box,
597
+ {
598
+ position: "absolute",
599
+ top: 0,
600
+ left: 0,
601
+ right: 0,
602
+ bottom: 0,
603
+ alignItems: "center",
604
+ justifyContent: "center",
605
+ zIndex: 1,
606
+ children: /* @__PURE__ */ jsx8(
607
+ Spinner,
608
+ {
609
+ color: textColor,
610
+ size: sizeStyles.spinnerSize,
611
+ "aria-hidden": true
612
+ }
613
+ )
614
+ }
615
+ ),
616
+ iconLeft && /* @__PURE__ */ jsxs(
570
617
  Box,
571
618
  {
572
619
  height: "100%",
573
620
  flexDirection: "row",
574
621
  alignItems: "center",
575
- justifyContent: "center",
576
622
  "aria-hidden": true,
623
+ style: {
624
+ opacity: loading ? 0 : 1,
625
+ pointerEvents: loading ? "none" : "auto"
626
+ },
577
627
  children: [
578
628
  /* @__PURE__ */ jsx8(
579
629
  Box,
580
630
  {
631
+ width: sizeStyles.iconContainerSize,
632
+ height: sizeStyles.iconContainerSize,
581
633
  alignItems: "center",
582
634
  justifyContent: "center",
583
- paddingHorizontal: sizeStyles.iconPadding,
584
- children: /* @__PURE__ */ jsx8(Icon, { size: sizeStyles.iconSize, color: textColor, children: iconLeft })
635
+ children: cloneIconWithDefaults(iconLeft, sizeStyles.iconSize, textColor)
585
636
  }
586
637
  ),
587
- /* @__PURE__ */ jsx8(Divider, { vertical: true, color: dividerColor, height: "100%" })
638
+ showDivider && /* @__PURE__ */ jsx8(Divider, { vertical: true, color: dividerLineColor, height: "100%" })
588
639
  ]
589
640
  }
590
641
  ),
591
- /* @__PURE__ */ jsx8(
642
+ /* @__PURE__ */ jsxs(
592
643
  Box,
593
644
  {
594
645
  flex: fullWidth ? 1 : void 0,
595
646
  flexDirection: "row",
596
647
  alignItems: "center",
597
- justifyContent: "center",
598
- paddingHorizontal: loading ? sizeStyles.loadingPadding : sizeStyles.padding,
648
+ justifyContent: labelAlignment === "left" ? "flex-start" : "center",
649
+ paddingHorizontal: sizeStyles.padding,
599
650
  height: "100%",
600
- children: loading ? /* @__PURE__ */ jsx8(
601
- Spinner,
602
- {
603
- color: textColor,
604
- size: sizeStyles.spinnerSize,
605
- "aria-hidden": true
606
- }
607
- ) : /* @__PURE__ */ jsx8(
608
- Text,
609
- {
610
- color: textColor,
611
- fontSize: sizeStyles.fontSize,
612
- fontWeight: "500",
613
- children
614
- }
615
- )
651
+ gap: sizeStyles.labelIconGap,
652
+ style: {
653
+ opacity: loading ? 0 : 1,
654
+ pointerEvents: loading ? "none" : "auto"
655
+ },
656
+ "aria-hidden": loading ? true : void 0,
657
+ children: [
658
+ labelIcon && /* @__PURE__ */ jsx8(Box, { "aria-hidden": true, children: cloneIconWithDefaults(
659
+ labelIcon,
660
+ sizeStyles.labelIconSize,
661
+ textColor
662
+ ) }),
663
+ /* @__PURE__ */ jsx8(Text, { color: textColor, fontSize: sizeStyles.fontSize, fontWeight: "500", children }),
664
+ sublabel && /* @__PURE__ */ jsx8(
665
+ Text,
666
+ {
667
+ color: textColor,
668
+ fontSize: sizeStyles.fontSize,
669
+ fontWeight: "500",
670
+ style: { opacity: 0.4 },
671
+ children: sublabel
672
+ }
673
+ ),
674
+ customContent && /* @__PURE__ */ jsx8(Box, { "aria-hidden": true, children: customContent })
675
+ ]
616
676
  }
617
677
  ),
618
- !loading && iconRight && /* @__PURE__ */ jsxs(
678
+ iconRight && /* @__PURE__ */ jsxs(
619
679
  Box,
620
680
  {
621
681
  height: "100%",
622
682
  flexDirection: "row",
623
683
  alignItems: "center",
624
- justifyContent: "center",
625
684
  "aria-hidden": true,
685
+ style: {
686
+ opacity: loading ? 0 : 1,
687
+ pointerEvents: loading ? "none" : "auto"
688
+ },
626
689
  children: [
627
- /* @__PURE__ */ jsx8(Divider, { vertical: true, color: dividerColor, height: "100%" }),
690
+ showDivider && /* @__PURE__ */ jsx8(Divider, { vertical: true, color: dividerLineColor, height: "100%" }),
628
691
  /* @__PURE__ */ jsx8(
629
692
  Box,
630
693
  {
694
+ width: sizeStyles.iconContainerSize,
695
+ height: sizeStyles.iconContainerSize,
631
696
  alignItems: "center",
632
697
  justifyContent: "center",
633
- paddingHorizontal: sizeStyles.iconPadding,
634
- children: /* @__PURE__ */ jsx8(Icon, { size: sizeStyles.iconSize, color: textColor, children: iconRight })
698
+ children: cloneIconWithDefaults(iconRight, sizeStyles.iconSize, textColor)
635
699
  }
636
700
  )
637
701
  ]
@@ -644,9 +708,20 @@ var Button = ({
644
708
  Button.displayName = "Button";
645
709
 
646
710
  // src/IconButton.tsx
647
- import { useState as useState2 } from "react";
711
+ import React4, { useState as useState2 } from "react";
648
712
  import { useDesignSystem as useDesignSystem2 } from "@xsolla/xui-core";
649
- import { jsx as jsx9 } from "react/jsx-runtime";
713
+ import { jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
714
+ var cloneIconWithDefaults2 = (icon, defaultSize, defaultColor) => {
715
+ if (!React4.isValidElement(icon)) return icon;
716
+ const iconElement = icon;
717
+ const existingProps = iconElement.props || {};
718
+ return React4.cloneElement(iconElement, {
719
+ ...existingProps,
720
+ // Preserve existing props (including accessibility attributes)
721
+ size: existingProps.size ?? defaultSize,
722
+ color: existingProps.color ?? defaultColor
723
+ });
724
+ };
650
725
  var IconButton = ({
651
726
  variant = "primary",
652
727
  tone = "brand",
@@ -669,9 +744,17 @@ var IconButton = ({
669
744
  const [isKeyboardPressed, setIsKeyboardPressed] = useState2(false);
670
745
  const isDisabled = disabled || loading;
671
746
  const sizeStyles = theme.sizing.button(size);
672
- const variantStyles = theme?.colors?.control?.[tone]?.[variant] || theme?.colors?.control?.brand?.primary || {
747
+ const controlTone = theme?.colors?.control?.[tone];
748
+ const variantStyles = controlTone?.[variant] || theme?.colors?.control?.brand?.primary || {
673
749
  bg: "transparent",
674
- text: { primary: "#000" }
750
+ bgHover: "transparent",
751
+ bgPress: "transparent",
752
+ bgDisable: "transparent",
753
+ border: "transparent",
754
+ borderHover: "transparent",
755
+ borderPress: "transparent",
756
+ borderDisable: "transparent",
757
+ text: { primary: "#000", secondary: "#000", disable: "#666" }
675
758
  };
676
759
  const handlePress = () => {
677
760
  if (!isDisabled && onPress) {
@@ -695,16 +778,15 @@ var IconButton = ({
695
778
  }
696
779
  }
697
780
  };
698
- const styles = variantStyles;
699
- let backgroundColor = styles.bg;
781
+ let backgroundColor = variantStyles.bg;
700
782
  if (disabled) {
701
- backgroundColor = styles.bgDisable || styles.bg;
783
+ backgroundColor = variantStyles.bgDisable || variantStyles.bg;
702
784
  } else if (isKeyboardPressed) {
703
- backgroundColor = styles.bgPress || styles.bg;
785
+ backgroundColor = variantStyles.bgPress || variantStyles.bg;
704
786
  }
705
- const borderColor = disabled ? styles.borderDisable || styles.border : styles.border;
706
- const textColor = disabled ? styles.text?.disable || styles.text?.primary : styles.text?.primary;
707
- return /* @__PURE__ */ jsx9(
787
+ const borderColor = disabled ? variantStyles.borderDisable || variantStyles.border : variantStyles.border;
788
+ const textColor = disabled ? variantStyles.text?.disable || variantStyles.text?.primary : variantStyles.text?.primary;
789
+ return /* @__PURE__ */ jsxs2(
708
790
  Box,
709
791
  {
710
792
  as: "button",
@@ -726,7 +808,7 @@ var IconButton = ({
726
808
  backgroundColor,
727
809
  borderColor,
728
810
  borderWidth: borderColor !== "transparent" && borderColor !== "rgba(255, 255, 255, 0)" ? 1 : 0,
729
- borderRadius: theme.radius.button,
811
+ borderRadius: sizeStyles.borderRadius,
730
812
  height: sizeStyles.height,
731
813
  width: sizeStyles.height,
732
814
  padding: 0,
@@ -737,10 +819,10 @@ var IconButton = ({
737
819
  cursor: disabled ? "not-allowed" : loading ? "wait" : "pointer",
738
820
  opacity: disabled ? 0.6 : 1,
739
821
  hoverStyle: !isDisabled ? {
740
- backgroundColor: styles.bgHover
822
+ backgroundColor: variantStyles.bgHover
741
823
  } : void 0,
742
824
  pressStyle: !isDisabled ? {
743
- backgroundColor: styles.bgPress
825
+ backgroundColor: variantStyles.bgPress
744
826
  } : void 0,
745
827
  focusStyle: {
746
828
  outlineColor: theme.colors.border.brand,
@@ -748,14 +830,40 @@ var IconButton = ({
748
830
  outlineOffset: 2,
749
831
  outlineStyle: "solid"
750
832
  },
751
- children: loading ? /* @__PURE__ */ jsx9(
752
- Spinner,
753
- {
754
- color: textColor,
755
- size: sizeStyles.spinnerSize,
756
- "aria-hidden": true
757
- }
758
- ) : /* @__PURE__ */ jsx9(Icon, { size: sizeStyles.iconSize, color: textColor, "aria-hidden": true, children: icon })
833
+ children: [
834
+ loading && /* @__PURE__ */ jsx9(
835
+ Box,
836
+ {
837
+ position: "absolute",
838
+ top: 0,
839
+ left: 0,
840
+ right: 0,
841
+ bottom: 0,
842
+ alignItems: "center",
843
+ justifyContent: "center",
844
+ zIndex: 1,
845
+ children: /* @__PURE__ */ jsx9(
846
+ Spinner,
847
+ {
848
+ color: textColor,
849
+ size: sizeStyles.spinnerSize,
850
+ "aria-hidden": true
851
+ }
852
+ )
853
+ }
854
+ ),
855
+ /* @__PURE__ */ jsx9(
856
+ Box,
857
+ {
858
+ "aria-hidden": true,
859
+ style: {
860
+ opacity: loading ? 0 : 1,
861
+ pointerEvents: loading ? "none" : "auto"
862
+ },
863
+ children: cloneIconWithDefaults2(icon, sizeStyles.iconSize, textColor)
864
+ }
865
+ )
866
+ ]
759
867
  }
760
868
  );
761
869
  };
@@ -764,7 +872,7 @@ IconButton.displayName = "IconButton";
764
872
  // src/FlexButton.tsx
765
873
  import { useRef, useState as useState3 } from "react";
766
874
  import { useDesignSystem as useDesignSystem3 } from "@xsolla/xui-core";
767
- import { Fragment, jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
875
+ import { Fragment, jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
768
876
  var ICON_SIZES = {
769
877
  xs: 12,
770
878
  sm: 14,
@@ -1086,7 +1194,7 @@ var FlexButton = ({
1086
1194
  tabIndex,
1087
1195
  style: buttonStyle,
1088
1196
  "data-testid": testID || "flex-button",
1089
- children: /* @__PURE__ */ jsx10("span", { style: contentStyle, children: loading ? /* @__PURE__ */ jsx10("span", { style: spinnerStyle, children: /* @__PURE__ */ jsx10(Spinner, { size: spinnerSize, color: spinnerColor }) }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
1197
+ children: /* @__PURE__ */ jsx10("span", { style: contentStyle, children: loading ? /* @__PURE__ */ jsx10("span", { style: spinnerStyle, children: /* @__PURE__ */ jsx10(Spinner, { size: spinnerSize, color: spinnerColor }) }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
1090
1198
  iconLeft && /* @__PURE__ */ jsx10(Icon, { size: iconSize, color: colors.text, children: iconLeft }),
1091
1199
  /* @__PURE__ */ jsx10("span", { children }),
1092
1200
  iconRight && /* @__PURE__ */ jsx10(Icon, { size: iconSize, color: colors.text, children: iconRight })
@@ -1099,7 +1207,7 @@ FlexButton.displayName = "FlexButton";
1099
1207
  // src/ButtonGroup.tsx
1100
1208
  import React5 from "react";
1101
1209
  import { useDesignSystem as useDesignSystem4 } from "@xsolla/xui-core";
1102
- import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
1210
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
1103
1211
  var ButtonGroup = ({
1104
1212
  orientation = "horizontal",
1105
1213
  size = "md",
@@ -1148,7 +1256,7 @@ var ButtonGroup = ({
1148
1256
  const computedAriaDescribedBy = [
1149
1257
  ariaDescribedBy,
1150
1258
  error && errorId ? errorId : void 0,
1151
- description && !error && descriptionId ? descriptionId : void 0
1259
+ description && descriptionId ? descriptionId : void 0
1152
1260
  ].filter(Boolean).join(" ") || void 0;
1153
1261
  const processChildren = (childrenToProcess) => {
1154
1262
  if (orientation === "vertical") {
@@ -1170,7 +1278,7 @@ var ButtonGroup = ({
1170
1278
  if (useSpaceBetween) {
1171
1279
  const firstChild = processedChildren[0];
1172
1280
  const restChildren = processedChildren.slice(1);
1173
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1281
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1174
1282
  firstChild,
1175
1283
  /* @__PURE__ */ jsx11(Box, { flexDirection: "row", gap: computedGap, children: restChildren })
1176
1284
  ] });
@@ -1180,7 +1288,7 @@ var ButtonGroup = ({
1180
1288
  }
1181
1289
  return children;
1182
1290
  };
1183
- return /* @__PURE__ */ jsxs3(Box, { flexDirection: "column", width: "100%", gap: 8, children: [
1291
+ return /* @__PURE__ */ jsxs4(Box, { flexDirection: "column", width: "100%", gap: 8, children: [
1184
1292
  /* @__PURE__ */ jsx11(
1185
1293
  Box,
1186
1294
  {
@@ -1211,7 +1319,7 @@ var ButtonGroup = ({
1211
1319
  children: error
1212
1320
  }
1213
1321
  ) }),
1214
- description && !error && /* @__PURE__ */ jsx11(Box, { marginTop: 4, children: /* @__PURE__ */ jsx11(
1322
+ description && /* @__PURE__ */ jsx11(Box, { marginTop: 4, children: /* @__PURE__ */ jsx11(
1215
1323
  Text,
1216
1324
  {
1217
1325
  id: descriptionId,