@xsolla/xui-button 0.112.0 → 0.113.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.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.tsx
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AppButton: () => AppButton,
33
34
  Button: () => Button,
34
35
  ButtonGroup: () => ButtonGroup,
35
36
  FlexButton: () => FlexButton,
@@ -227,6 +228,7 @@ var Text = ({
227
228
  className,
228
229
  id,
229
230
  role,
231
+ numberOfLines: _numberOfLines,
230
232
  ...props
231
233
  }) => {
232
234
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -1040,10 +1042,231 @@ var FlexButton = ({
1040
1042
  };
1041
1043
  FlexButton.displayName = "FlexButton";
1042
1044
 
1043
- // src/ButtonGroup.tsx
1045
+ // src/AppButton.tsx
1044
1046
  var import_react5 = __toESM(require("react"));
1045
1047
  var import_xui_core4 = require("@xsolla/xui-core");
1046
1048
  var import_jsx_runtime8 = require("react/jsx-runtime");
1049
+ var cloneIconWithDefaults3 = (icon, defaultSize, defaultColor) => {
1050
+ if (!import_react5.default.isValidElement(icon)) return icon;
1051
+ const iconElement = icon;
1052
+ const existingProps = iconElement.props || {};
1053
+ return import_react5.default.cloneElement(iconElement, {
1054
+ ...existingProps,
1055
+ size: existingProps.size ?? defaultSize,
1056
+ color: existingProps.color ?? defaultColor
1057
+ });
1058
+ };
1059
+ var AppButton = ({
1060
+ size = "md",
1061
+ disabled = false,
1062
+ loading = false,
1063
+ children,
1064
+ onPress,
1065
+ iconLeft,
1066
+ iconRight,
1067
+ sublabel,
1068
+ labelAlignment = "center",
1069
+ labelIcon,
1070
+ customContent,
1071
+ "aria-label": ariaLabel,
1072
+ "aria-describedby": ariaDescribedBy,
1073
+ "aria-expanded": ariaExpanded,
1074
+ "aria-haspopup": ariaHasPopup,
1075
+ "aria-pressed": ariaPressed,
1076
+ "aria-controls": ariaControls,
1077
+ testID,
1078
+ id,
1079
+ type = "button",
1080
+ fullWidth = false
1081
+ }) => {
1082
+ const { theme } = (0, import_xui_core4.useDesignSystem)();
1083
+ const [isKeyboardPressed, setIsKeyboardPressed] = (0, import_react5.useState)(false);
1084
+ const isDisabled = disabled || loading;
1085
+ const sizeStyles = theme.sizing.button(size);
1086
+ const tokens = theme?.colors?.control?.appButton || {
1087
+ bg: "#34474b",
1088
+ bgHover: "#3d5256",
1089
+ bgPress: "#2b3b3e",
1090
+ border: "rgba(255, 255, 255, 0.12)",
1091
+ borderHover: "rgba(255, 255, 255, 0.18)",
1092
+ borderPress: "rgba(255, 255, 255, 0.12)",
1093
+ text: "#b7c5c8",
1094
+ textDisable: "#b3b3b3"
1095
+ };
1096
+ const handlePress = () => {
1097
+ if (!isDisabled && onPress) {
1098
+ onPress();
1099
+ }
1100
+ };
1101
+ const handleKeyDown = (e) => {
1102
+ if (isDisabled) return;
1103
+ if (e.key === "Enter" || e.key === " ") {
1104
+ e.preventDefault();
1105
+ setIsKeyboardPressed(true);
1106
+ }
1107
+ };
1108
+ const handleKeyUp = (e) => {
1109
+ if (isDisabled) return;
1110
+ if (e.key === "Enter" || e.key === " ") {
1111
+ e.preventDefault();
1112
+ setIsKeyboardPressed(false);
1113
+ if (onPress) {
1114
+ onPress();
1115
+ }
1116
+ }
1117
+ };
1118
+ let backgroundColor = tokens.bg;
1119
+ if (disabled) {
1120
+ backgroundColor = tokens.bg;
1121
+ } else if (isKeyboardPressed) {
1122
+ backgroundColor = tokens.bgPress;
1123
+ }
1124
+ const borderColor = tokens.border;
1125
+ const textColor = disabled ? tokens.textDisable : tokens.text;
1126
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1127
+ Box,
1128
+ {
1129
+ as: "button",
1130
+ type,
1131
+ id,
1132
+ onPress: handlePress,
1133
+ onKeyDown: handleKeyDown,
1134
+ onKeyUp: handleKeyUp,
1135
+ disabled: isDisabled,
1136
+ "aria-label": ariaLabel,
1137
+ "aria-disabled": isDisabled || void 0,
1138
+ "aria-busy": loading || void 0,
1139
+ "aria-describedby": ariaDescribedBy,
1140
+ "aria-expanded": ariaExpanded,
1141
+ "aria-haspopup": ariaHasPopup,
1142
+ "aria-pressed": ariaPressed,
1143
+ "aria-controls": ariaControls,
1144
+ testID,
1145
+ backgroundColor,
1146
+ borderColor,
1147
+ borderWidth: borderColor !== "transparent" && borderColor !== "rgba(255, 255, 255, 0)" && borderColor !== "rgba(0, 0, 0, 0)" && !borderColor.endsWith(", 0)") ? 1 : 0,
1148
+ borderRadius: sizeStyles.borderRadius,
1149
+ height: sizeStyles.height,
1150
+ width: fullWidth ? "100%" : void 0,
1151
+ padding: 0,
1152
+ flexDirection: "row",
1153
+ alignItems: "center",
1154
+ justifyContent: "center",
1155
+ position: "relative",
1156
+ cursor: disabled ? "not-allowed" : loading ? "wait" : "pointer",
1157
+ opacity: disabled ? 0.6 : 1,
1158
+ hoverStyle: !isDisabled ? {
1159
+ backgroundColor: tokens.bgHover,
1160
+ borderColor: tokens.borderHover
1161
+ } : void 0,
1162
+ pressStyle: !isDisabled ? {
1163
+ backgroundColor: tokens.bgPress,
1164
+ borderColor: tokens.borderPress
1165
+ } : void 0,
1166
+ focusStyle: {
1167
+ outlineColor: theme.colors.border.brand,
1168
+ outlineWidth: 2,
1169
+ outlineOffset: 2,
1170
+ outlineStyle: "solid"
1171
+ },
1172
+ children: [
1173
+ loading && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1174
+ Box,
1175
+ {
1176
+ position: "absolute",
1177
+ top: 0,
1178
+ left: 0,
1179
+ right: 0,
1180
+ bottom: 0,
1181
+ alignItems: "center",
1182
+ justifyContent: "center",
1183
+ zIndex: 1,
1184
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1185
+ Spinner,
1186
+ {
1187
+ color: textColor,
1188
+ size: sizeStyles.spinnerSize,
1189
+ "aria-hidden": true
1190
+ }
1191
+ )
1192
+ }
1193
+ ),
1194
+ iconLeft && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1195
+ Box,
1196
+ {
1197
+ width: sizeStyles.iconContainerSize,
1198
+ height: sizeStyles.iconContainerSize,
1199
+ alignItems: "center",
1200
+ justifyContent: "center",
1201
+ "aria-hidden": true,
1202
+ style: {
1203
+ opacity: loading ? 0 : 1,
1204
+ pointerEvents: loading ? "none" : "auto"
1205
+ },
1206
+ children: cloneIconWithDefaults3(iconLeft, sizeStyles.iconSize, textColor)
1207
+ }
1208
+ ),
1209
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1210
+ Box,
1211
+ {
1212
+ flex: fullWidth ? 1 : void 0,
1213
+ flexDirection: "row",
1214
+ alignItems: "center",
1215
+ justifyContent: labelAlignment === "left" ? "flex-start" : "center",
1216
+ paddingHorizontal: sizeStyles.padding,
1217
+ height: "100%",
1218
+ gap: sizeStyles.labelIconGap,
1219
+ style: {
1220
+ opacity: loading ? 0 : 1,
1221
+ pointerEvents: loading ? "none" : "auto"
1222
+ },
1223
+ "aria-hidden": loading ? true : void 0,
1224
+ children: [
1225
+ labelIcon && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { "aria-hidden": true, children: cloneIconWithDefaults3(
1226
+ labelIcon,
1227
+ sizeStyles.labelIconSize,
1228
+ textColor
1229
+ ) }),
1230
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: textColor, fontSize: sizeStyles.fontSize, fontWeight: "500", children }),
1231
+ sublabel && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1232
+ Text,
1233
+ {
1234
+ color: textColor,
1235
+ fontSize: sizeStyles.fontSize,
1236
+ fontWeight: "500",
1237
+ style: { opacity: 0.4 },
1238
+ children: sublabel
1239
+ }
1240
+ ),
1241
+ customContent && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { "aria-hidden": true, children: customContent })
1242
+ ]
1243
+ }
1244
+ ),
1245
+ iconRight && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1246
+ Box,
1247
+ {
1248
+ width: sizeStyles.iconContainerSize,
1249
+ height: sizeStyles.iconContainerSize,
1250
+ alignItems: "center",
1251
+ justifyContent: "center",
1252
+ "aria-hidden": true,
1253
+ style: {
1254
+ opacity: loading ? 0 : 1,
1255
+ pointerEvents: loading ? "none" : "auto"
1256
+ },
1257
+ children: cloneIconWithDefaults3(iconRight, sizeStyles.iconSize, textColor)
1258
+ }
1259
+ )
1260
+ ]
1261
+ }
1262
+ );
1263
+ };
1264
+ AppButton.displayName = "AppButton";
1265
+
1266
+ // src/ButtonGroup.tsx
1267
+ var import_react6 = __toESM(require("react"));
1268
+ var import_xui_core5 = require("@xsolla/xui-core");
1269
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1047
1270
  var ButtonGroup = ({
1048
1271
  orientation = "horizontal",
1049
1272
  size = "md",
@@ -1057,11 +1280,11 @@ var ButtonGroup = ({
1057
1280
  id,
1058
1281
  testID
1059
1282
  }) => {
1060
- const { theme } = (0, import_xui_core4.useDesignSystem)();
1283
+ const { theme } = (0, import_xui_core5.useDesignSystem)();
1061
1284
  const flattenChildren = (children2) => {
1062
1285
  const result = [];
1063
- import_react5.default.Children.forEach(children2, (child) => {
1064
- if (import_react5.default.isValidElement(child) && child.type === import_react5.default.Fragment) {
1286
+ import_react6.default.Children.forEach(children2, (child) => {
1287
+ if (import_react6.default.isValidElement(child) && child.type === import_react6.default.Fragment) {
1065
1288
  result.push(...flattenChildren(child.props.children));
1066
1289
  } else if (child !== null && child !== void 0) {
1067
1290
  result.push(child);
@@ -1097,8 +1320,8 @@ var ButtonGroup = ({
1097
1320
  const processChildren = (childrenToProcess) => {
1098
1321
  if (orientation === "vertical") {
1099
1322
  return childrenToProcess.map((child, index) => {
1100
- if (import_react5.default.isValidElement(child)) {
1101
- return import_react5.default.cloneElement(child, {
1323
+ if (import_react6.default.isValidElement(child)) {
1324
+ return import_react6.default.cloneElement(child, {
1102
1325
  ...child.props,
1103
1326
  fullWidth: true,
1104
1327
  key: child.key ?? index
@@ -1114,9 +1337,9 @@ var ButtonGroup = ({
1114
1337
  if (useSpaceBetween) {
1115
1338
  const firstChild = processedChildren[0];
1116
1339
  const restChildren = processedChildren.slice(1);
1117
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1340
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1118
1341
  firstChild,
1119
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { flexDirection: "row", gap: computedGap, children: restChildren })
1342
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box, { flexDirection: "row", gap: computedGap, children: restChildren })
1120
1343
  ] });
1121
1344
  }
1122
1345
  if (orientation === "vertical") {
@@ -1124,8 +1347,8 @@ var ButtonGroup = ({
1124
1347
  }
1125
1348
  return children;
1126
1349
  };
1127
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box, { flexDirection: "column", width: "100%", gap: 8, children: [
1128
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1350
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box, { flexDirection: "column", width: "100%", gap: 8, children: [
1351
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1129
1352
  Box,
1130
1353
  {
1131
1354
  role: "group",
@@ -1142,7 +1365,7 @@ var ButtonGroup = ({
1142
1365
  children: renderChildren()
1143
1366
  }
1144
1367
  ),
1145
- error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { marginTop: 4, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1368
+ error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box, { marginTop: 4, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1146
1369
  Text,
1147
1370
  {
1148
1371
  id: errorId,
@@ -1155,7 +1378,7 @@ var ButtonGroup = ({
1155
1378
  children: error
1156
1379
  }
1157
1380
  ) }),
1158
- description && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { marginTop: 4, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1381
+ description && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box, { marginTop: 4, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1159
1382
  Text,
1160
1383
  {
1161
1384
  id: descriptionId,
@@ -1171,6 +1394,7 @@ var ButtonGroup = ({
1171
1394
  ButtonGroup.displayName = "ButtonGroup";
1172
1395
  // Annotate the CommonJS export names for ESM import in node:
1173
1396
  0 && (module.exports = {
1397
+ AppButton,
1174
1398
  Button,
1175
1399
  ButtonGroup,
1176
1400
  FlexButton,
package/web/index.js.flow CHANGED
@@ -372,6 +372,119 @@ declare type FlexButtonProps = {
372
372
  * - **Screen Reader Support**: Announces button label, state, and any associated descriptions
373
373
  */
374
374
  declare var FlexButton: React.FC<FlexButtonProps>;
375
+ declare interface AppButtonProps {
376
+ /**
377
+ * Size of the button
378
+ */
379
+ size?: "xl" | "lg" | "md" | "sm" | "xs";
380
+
381
+ /**
382
+ * Whether the button is disabled
383
+ */
384
+ disabled?: boolean;
385
+
386
+ /**
387
+ * Whether the button is in a loading state
388
+ */
389
+ loading?: boolean;
390
+
391
+ /**
392
+ * Button content
393
+ */
394
+ children: React.ReactNode;
395
+
396
+ /**
397
+ * Click handler
398
+ */
399
+ onPress?: () => void;
400
+
401
+ /**
402
+ * Icon to display on the left side
403
+ */
404
+ iconLeft?: React.ReactNode;
405
+
406
+ /**
407
+ * Icon to display on the right side
408
+ */
409
+ iconRight?: React.ReactNode;
410
+
411
+ /**
412
+ * Secondary text displayed inline with the main label
413
+ */
414
+ sublabel?: string;
415
+
416
+ /**
417
+ * Alignment of the label text
418
+ */
419
+ labelAlignment?: "left" | "center";
420
+
421
+ /**
422
+ * Small icon displayed directly next to the label text
423
+ */
424
+ labelIcon?: React.ReactNode;
425
+
426
+ /**
427
+ * Custom content slot for badges, tags, or other elements
428
+ */
429
+ customContent?: React.ReactNode;
430
+
431
+ /**
432
+ * Accessible label for screen readers
433
+ */
434
+ "aria-label"?: string;
435
+
436
+ /**
437
+ * ID of element that describes this button
438
+ */
439
+ "aria-describedby"?: string;
440
+
441
+ /**
442
+ * Indicates the button controls an expandable element
443
+ */
444
+ "aria-expanded"?: boolean;
445
+
446
+ /**
447
+ * Indicates the type of popup triggered by the button
448
+ */
449
+ "aria-haspopup"?: boolean | "menu" | "listbox" | "tree" | "grid" | "dialog";
450
+
451
+ /**
452
+ * Indicates the button is pressed (for toggle buttons)
453
+ */
454
+ "aria-pressed"?: boolean | "mixed";
455
+
456
+ /**
457
+ * ID of the element this button controls
458
+ */
459
+ "aria-controls"?: string;
460
+
461
+ /**
462
+ * Test ID for testing frameworks
463
+ */
464
+ testID?: string;
465
+
466
+ /**
467
+ * HTML id attribute
468
+ */
469
+ id?: string;
470
+
471
+ /**
472
+ * HTML type attribute for the button
473
+ */
474
+ type?: "button" | "submit" | "reset";
475
+
476
+ /**
477
+ * Whether the button should stretch to fill the full width of its container
478
+ */
479
+ fullWidth?: boolean;
480
+ }
481
+ /**
482
+ * AppButton - A prominent filled button for app-level actions.
483
+ *
484
+ * Uses the `control.appButton` theme tokens for styling.
485
+ * Supports all the same layout features as Button (icons, sublabels, etc.).
486
+ */
487
+ declare var AppButton: React.FC<AppButtonProps>;
375
488
  declare interface ButtonGroupProps {
376
489
  /**
377
490
  * Layout orientation of the buttons
@@ -443,5 +556,11 @@ declare interface ButtonGroupProps {
443
556
  * - **Description Support**: Optional description text for additional context
444
557
  */
445
558
  declare var ButtonGroup: React.FC<ButtonGroupProps>;
446
- export type { ButtonGroupProps, ButtonProps, FlexButtonProps, IconButtonProps };
447
- declare export { Button, ButtonGroup, FlexButton, IconButton };
559
+ export type {
560
+ AppButtonProps,
561
+ ButtonGroupProps,
562
+ ButtonProps,
563
+ FlexButtonProps,
564
+ IconButtonProps,
565
+ };
566
+ declare export { AppButton, Button, ButtonGroup, FlexButton, IconButton };