@xsolla/xui-button 0.110.0 → 0.112.0-pr178.1773103324

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/README.md CHANGED
@@ -348,16 +348,22 @@ export default function ButtonWithCustomContent() {
348
348
 
349
349
  ### Tertiary Variant
350
350
 
351
- The tertiary variant provides a minimal, text-like button style.
351
+ The tertiary variant is a subtle filled button with a muted background and border. It is commonly used for secondary actions inside content cards (e.g. "Activate" in game cards).
352
352
 
353
353
  ```tsx
354
354
  import * as React from 'react';
355
355
  import { Button, IconButton } from '@xsolla/xui-button';
356
- import { Settings } from '@xsolla/xui-icons-base';
356
+ import { Settings, ArrowRight } from '@xsolla/xui-icons-base';
357
357
 
358
358
  export default function TertiaryButtons() {
359
359
  return (
360
360
  <div style={{ display: 'flex', gap: 16 }}>
361
+ <Button variant="tertiary" tone="brand" size="xs">
362
+ Activate
363
+ </Button>
364
+ <Button variant="tertiary" tone="brand" size="xs" iconRight={<ArrowRight />}>
365
+ Details
366
+ </Button>
361
367
  <Button variant="tertiary">Learn More</Button>
362
368
  <IconButton
363
369
  variant="tertiary"
@@ -207,6 +207,58 @@ interface FlexButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElem
207
207
  */
208
208
  declare const FlexButton: React.FC<FlexButtonProps>;
209
209
 
210
+ interface AppButtonProps {
211
+ /** Size of the button */
212
+ size?: "xl" | "lg" | "md" | "sm" | "xs";
213
+ /** Whether the button is disabled */
214
+ disabled?: boolean;
215
+ /** Whether the button is in a loading state */
216
+ loading?: boolean;
217
+ /** Button content */
218
+ children: React.ReactNode;
219
+ /** Click handler */
220
+ onPress?: () => void;
221
+ /** Icon to display on the left side */
222
+ iconLeft?: React.ReactNode;
223
+ /** Icon to display on the right side */
224
+ iconRight?: React.ReactNode;
225
+ /** Secondary text displayed inline with the main label */
226
+ sublabel?: string;
227
+ /** Alignment of the label text */
228
+ labelAlignment?: "left" | "center";
229
+ /** Small icon displayed directly next to the label text */
230
+ labelIcon?: React.ReactNode;
231
+ /** Custom content slot for badges, tags, or other elements */
232
+ customContent?: React.ReactNode;
233
+ /** Accessible label for screen readers */
234
+ "aria-label"?: string;
235
+ /** ID of element that describes this button */
236
+ "aria-describedby"?: string;
237
+ /** Indicates the button controls an expandable element */
238
+ "aria-expanded"?: boolean;
239
+ /** Indicates the type of popup triggered by the button */
240
+ "aria-haspopup"?: boolean | "menu" | "listbox" | "tree" | "grid" | "dialog";
241
+ /** Indicates the button is pressed (for toggle buttons) */
242
+ "aria-pressed"?: boolean | "mixed";
243
+ /** ID of the element this button controls */
244
+ "aria-controls"?: string;
245
+ /** Test ID for testing frameworks */
246
+ testID?: string;
247
+ /** HTML id attribute */
248
+ id?: string;
249
+ /** HTML type attribute for the button */
250
+ type?: "button" | "submit" | "reset";
251
+ /** Whether the button should stretch to fill the full width of its container */
252
+ fullWidth?: boolean;
253
+ }
254
+ /**
255
+ * AppButton - A prominent filled button for app-level actions.
256
+ *
257
+ * Uses the `control.appButton` theme tokens for styling.
258
+ * Supports all the same layout features as Button (icons, sublabels, etc.).
259
+ */
260
+ declare const AppButton: React.FC<AppButtonProps>;
261
+
210
262
  interface ButtonGroupProps {
211
263
  /**
212
264
  * Layout orientation of the buttons
@@ -270,4 +322,4 @@ interface ButtonGroupProps {
270
322
  */
271
323
  declare const ButtonGroup: React.FC<ButtonGroupProps>;
272
324
 
273
- export { Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, FlexButton, type FlexButtonProps, IconButton, type IconButtonProps };
325
+ export { AppButton, type AppButtonProps, Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, FlexButton, type FlexButtonProps, IconButton, type IconButtonProps };
package/native/index.d.ts CHANGED
@@ -207,6 +207,58 @@ interface FlexButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElem
207
207
  */
208
208
  declare const FlexButton: React.FC<FlexButtonProps>;
209
209
 
210
+ interface AppButtonProps {
211
+ /** Size of the button */
212
+ size?: "xl" | "lg" | "md" | "sm" | "xs";
213
+ /** Whether the button is disabled */
214
+ disabled?: boolean;
215
+ /** Whether the button is in a loading state */
216
+ loading?: boolean;
217
+ /** Button content */
218
+ children: React.ReactNode;
219
+ /** Click handler */
220
+ onPress?: () => void;
221
+ /** Icon to display on the left side */
222
+ iconLeft?: React.ReactNode;
223
+ /** Icon to display on the right side */
224
+ iconRight?: React.ReactNode;
225
+ /** Secondary text displayed inline with the main label */
226
+ sublabel?: string;
227
+ /** Alignment of the label text */
228
+ labelAlignment?: "left" | "center";
229
+ /** Small icon displayed directly next to the label text */
230
+ labelIcon?: React.ReactNode;
231
+ /** Custom content slot for badges, tags, or other elements */
232
+ customContent?: React.ReactNode;
233
+ /** Accessible label for screen readers */
234
+ "aria-label"?: string;
235
+ /** ID of element that describes this button */
236
+ "aria-describedby"?: string;
237
+ /** Indicates the button controls an expandable element */
238
+ "aria-expanded"?: boolean;
239
+ /** Indicates the type of popup triggered by the button */
240
+ "aria-haspopup"?: boolean | "menu" | "listbox" | "tree" | "grid" | "dialog";
241
+ /** Indicates the button is pressed (for toggle buttons) */
242
+ "aria-pressed"?: boolean | "mixed";
243
+ /** ID of the element this button controls */
244
+ "aria-controls"?: string;
245
+ /** Test ID for testing frameworks */
246
+ testID?: string;
247
+ /** HTML id attribute */
248
+ id?: string;
249
+ /** HTML type attribute for the button */
250
+ type?: "button" | "submit" | "reset";
251
+ /** Whether the button should stretch to fill the full width of its container */
252
+ fullWidth?: boolean;
253
+ }
254
+ /**
255
+ * AppButton - A prominent filled button for app-level actions.
256
+ *
257
+ * Uses the `control.appButton` theme tokens for styling.
258
+ * Supports all the same layout features as Button (icons, sublabels, etc.).
259
+ */
260
+ declare const AppButton: React.FC<AppButtonProps>;
261
+
210
262
  interface ButtonGroupProps {
211
263
  /**
212
264
  * Layout orientation of the buttons
@@ -270,4 +322,4 @@ interface ButtonGroupProps {
270
322
  */
271
323
  declare const ButtonGroup: React.FC<ButtonGroupProps>;
272
324
 
273
- export { Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, FlexButton, type FlexButtonProps, IconButton, type IconButtonProps };
325
+ export { AppButton, type AppButtonProps, Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, FlexButton, type FlexButtonProps, IconButton, type IconButtonProps };
package/native/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,
@@ -216,29 +217,57 @@ var roleMap = {
216
217
  link: "link",
217
218
  text: "text"
218
219
  };
220
+ var parseNumericValue = (value) => {
221
+ if (value === void 0) return void 0;
222
+ if (typeof value === "number") return value;
223
+ const parsed = parseFloat(value);
224
+ return isNaN(parsed) ? void 0 : parsed;
225
+ };
219
226
  var Text = ({
220
227
  children,
221
228
  color,
222
229
  fontSize,
223
230
  fontWeight,
224
231
  fontFamily,
232
+ textAlign,
233
+ lineHeight,
234
+ numberOfLines,
225
235
  id,
226
236
  role,
237
+ style: styleProp,
227
238
  ...props
228
239
  }) => {
229
240
  let resolvedFontFamily = fontFamily ? fontFamily.split(",")[0].replace(/['"]/g, "").trim() : void 0;
230
241
  if (resolvedFontFamily === "Pilat Wide" || resolvedFontFamily === "Pilat Wide Bold" || resolvedFontFamily === "Aktiv Grotesk") {
231
242
  resolvedFontFamily = void 0;
232
243
  }
233
- const style = {
244
+ const incomingStyle = import_react_native2.StyleSheet.flatten(styleProp);
245
+ const baseStyle = {
234
246
  color,
235
247
  fontSize: typeof fontSize === "number" ? fontSize : void 0,
236
248
  fontWeight,
237
249
  fontFamily: resolvedFontFamily,
238
- textDecorationLine: props.textDecoration
250
+ textDecorationLine: props.textDecoration,
251
+ textAlign: textAlign ?? incomingStyle?.textAlign,
252
+ lineHeight: parseNumericValue(lineHeight ?? incomingStyle?.lineHeight),
253
+ marginTop: parseNumericValue(
254
+ incomingStyle?.marginTop
255
+ ),
256
+ marginBottom: parseNumericValue(
257
+ incomingStyle?.marginBottom
258
+ )
239
259
  };
240
260
  const accessibilityRole = role ? roleMap[role] : void 0;
241
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style, testID: id, accessibilityRole, children });
261
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
262
+ import_react_native2.Text,
263
+ {
264
+ style: baseStyle,
265
+ numberOfLines,
266
+ testID: id,
267
+ accessibilityRole,
268
+ children
269
+ }
270
+ );
242
271
  };
243
272
 
244
273
  // ../primitives-native/src/Spinner.tsx
@@ -1029,10 +1058,231 @@ var FlexButton = ({
1029
1058
  };
1030
1059
  FlexButton.displayName = "FlexButton";
1031
1060
 
1032
- // src/ButtonGroup.tsx
1061
+ // src/AppButton.tsx
1033
1062
  var import_react5 = __toESM(require("react"));
1034
1063
  var import_xui_core4 = require("@xsolla/xui-core");
1035
1064
  var import_jsx_runtime8 = require("react/jsx-runtime");
1065
+ var cloneIconWithDefaults3 = (icon, defaultSize, defaultColor) => {
1066
+ if (!import_react5.default.isValidElement(icon)) return icon;
1067
+ const iconElement = icon;
1068
+ const existingProps = iconElement.props || {};
1069
+ return import_react5.default.cloneElement(iconElement, {
1070
+ ...existingProps,
1071
+ size: existingProps.size ?? defaultSize,
1072
+ color: existingProps.color ?? defaultColor
1073
+ });
1074
+ };
1075
+ var AppButton = ({
1076
+ size = "md",
1077
+ disabled = false,
1078
+ loading = false,
1079
+ children,
1080
+ onPress,
1081
+ iconLeft,
1082
+ iconRight,
1083
+ sublabel,
1084
+ labelAlignment = "center",
1085
+ labelIcon,
1086
+ customContent,
1087
+ "aria-label": ariaLabel,
1088
+ "aria-describedby": ariaDescribedBy,
1089
+ "aria-expanded": ariaExpanded,
1090
+ "aria-haspopup": ariaHasPopup,
1091
+ "aria-pressed": ariaPressed,
1092
+ "aria-controls": ariaControls,
1093
+ testID,
1094
+ id,
1095
+ type = "button",
1096
+ fullWidth = false
1097
+ }) => {
1098
+ const { theme } = (0, import_xui_core4.useDesignSystem)();
1099
+ const [isKeyboardPressed, setIsKeyboardPressed] = (0, import_react5.useState)(false);
1100
+ const isDisabled = disabled || loading;
1101
+ const sizeStyles = theme.sizing.button(size);
1102
+ const tokens = theme?.colors?.control?.appButton || {
1103
+ bg: "#34474b",
1104
+ bgHover: "#3d5256",
1105
+ bgPress: "#2b3b3e",
1106
+ border: "rgba(255, 255, 255, 0.12)",
1107
+ borderHover: "rgba(255, 255, 255, 0.18)",
1108
+ borderPress: "rgba(255, 255, 255, 0.12)",
1109
+ text: "#b7c5c8",
1110
+ textDisable: "#b3b3b3"
1111
+ };
1112
+ const handlePress = () => {
1113
+ if (!isDisabled && onPress) {
1114
+ onPress();
1115
+ }
1116
+ };
1117
+ const handleKeyDown = (e) => {
1118
+ if (isDisabled) return;
1119
+ if (e.key === "Enter" || e.key === " ") {
1120
+ e.preventDefault();
1121
+ setIsKeyboardPressed(true);
1122
+ }
1123
+ };
1124
+ const handleKeyUp = (e) => {
1125
+ if (isDisabled) return;
1126
+ if (e.key === "Enter" || e.key === " ") {
1127
+ e.preventDefault();
1128
+ setIsKeyboardPressed(false);
1129
+ if (onPress) {
1130
+ onPress();
1131
+ }
1132
+ }
1133
+ };
1134
+ let backgroundColor = tokens.bg;
1135
+ if (disabled) {
1136
+ backgroundColor = tokens.bg;
1137
+ } else if (isKeyboardPressed) {
1138
+ backgroundColor = tokens.bgPress;
1139
+ }
1140
+ const borderColor = tokens.border;
1141
+ const textColor = disabled ? tokens.textDisable : tokens.text;
1142
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1143
+ Box,
1144
+ {
1145
+ as: "button",
1146
+ type,
1147
+ id,
1148
+ onPress: handlePress,
1149
+ onKeyDown: handleKeyDown,
1150
+ onKeyUp: handleKeyUp,
1151
+ disabled: isDisabled,
1152
+ "aria-label": ariaLabel,
1153
+ "aria-disabled": isDisabled || void 0,
1154
+ "aria-busy": loading || void 0,
1155
+ "aria-describedby": ariaDescribedBy,
1156
+ "aria-expanded": ariaExpanded,
1157
+ "aria-haspopup": ariaHasPopup,
1158
+ "aria-pressed": ariaPressed,
1159
+ "aria-controls": ariaControls,
1160
+ testID,
1161
+ backgroundColor,
1162
+ borderColor,
1163
+ borderWidth: borderColor !== "transparent" && borderColor !== "rgba(255, 255, 255, 0)" && borderColor !== "rgba(0, 0, 0, 0)" && !borderColor.endsWith(", 0)") ? 1 : 0,
1164
+ borderRadius: sizeStyles.borderRadius,
1165
+ height: sizeStyles.height,
1166
+ width: fullWidth ? "100%" : void 0,
1167
+ padding: 0,
1168
+ flexDirection: "row",
1169
+ alignItems: "center",
1170
+ justifyContent: "center",
1171
+ position: "relative",
1172
+ cursor: disabled ? "not-allowed" : loading ? "wait" : "pointer",
1173
+ opacity: disabled ? 0.6 : 1,
1174
+ hoverStyle: !isDisabled ? {
1175
+ backgroundColor: tokens.bgHover,
1176
+ borderColor: tokens.borderHover
1177
+ } : void 0,
1178
+ pressStyle: !isDisabled ? {
1179
+ backgroundColor: tokens.bgPress,
1180
+ borderColor: tokens.borderPress
1181
+ } : void 0,
1182
+ focusStyle: {
1183
+ outlineColor: theme.colors.border.brand,
1184
+ outlineWidth: 2,
1185
+ outlineOffset: 2,
1186
+ outlineStyle: "solid"
1187
+ },
1188
+ children: [
1189
+ loading && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1190
+ Box,
1191
+ {
1192
+ position: "absolute",
1193
+ top: 0,
1194
+ left: 0,
1195
+ right: 0,
1196
+ bottom: 0,
1197
+ alignItems: "center",
1198
+ justifyContent: "center",
1199
+ zIndex: 1,
1200
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1201
+ Spinner,
1202
+ {
1203
+ color: textColor,
1204
+ size: sizeStyles.spinnerSize,
1205
+ "aria-hidden": true
1206
+ }
1207
+ )
1208
+ }
1209
+ ),
1210
+ iconLeft && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1211
+ Box,
1212
+ {
1213
+ width: sizeStyles.iconContainerSize,
1214
+ height: sizeStyles.iconContainerSize,
1215
+ alignItems: "center",
1216
+ justifyContent: "center",
1217
+ "aria-hidden": true,
1218
+ style: {
1219
+ opacity: loading ? 0 : 1,
1220
+ pointerEvents: loading ? "none" : "auto"
1221
+ },
1222
+ children: cloneIconWithDefaults3(iconLeft, sizeStyles.iconSize, textColor)
1223
+ }
1224
+ ),
1225
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1226
+ Box,
1227
+ {
1228
+ flex: fullWidth ? 1 : void 0,
1229
+ flexDirection: "row",
1230
+ alignItems: "center",
1231
+ justifyContent: labelAlignment === "left" ? "flex-start" : "center",
1232
+ paddingHorizontal: sizeStyles.padding,
1233
+ height: "100%",
1234
+ gap: sizeStyles.labelIconGap,
1235
+ style: {
1236
+ opacity: loading ? 0 : 1,
1237
+ pointerEvents: loading ? "none" : "auto"
1238
+ },
1239
+ "aria-hidden": loading ? true : void 0,
1240
+ children: [
1241
+ labelIcon && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { "aria-hidden": true, children: cloneIconWithDefaults3(
1242
+ labelIcon,
1243
+ sizeStyles.labelIconSize,
1244
+ textColor
1245
+ ) }),
1246
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: textColor, fontSize: sizeStyles.fontSize, fontWeight: "500", children }),
1247
+ sublabel && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1248
+ Text,
1249
+ {
1250
+ color: textColor,
1251
+ fontSize: sizeStyles.fontSize,
1252
+ fontWeight: "500",
1253
+ style: { opacity: 0.4 },
1254
+ children: sublabel
1255
+ }
1256
+ ),
1257
+ customContent && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { "aria-hidden": true, children: customContent })
1258
+ ]
1259
+ }
1260
+ ),
1261
+ iconRight && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1262
+ Box,
1263
+ {
1264
+ width: sizeStyles.iconContainerSize,
1265
+ height: sizeStyles.iconContainerSize,
1266
+ alignItems: "center",
1267
+ justifyContent: "center",
1268
+ "aria-hidden": true,
1269
+ style: {
1270
+ opacity: loading ? 0 : 1,
1271
+ pointerEvents: loading ? "none" : "auto"
1272
+ },
1273
+ children: cloneIconWithDefaults3(iconRight, sizeStyles.iconSize, textColor)
1274
+ }
1275
+ )
1276
+ ]
1277
+ }
1278
+ );
1279
+ };
1280
+ AppButton.displayName = "AppButton";
1281
+
1282
+ // src/ButtonGroup.tsx
1283
+ var import_react6 = __toESM(require("react"));
1284
+ var import_xui_core5 = require("@xsolla/xui-core");
1285
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1036
1286
  var ButtonGroup = ({
1037
1287
  orientation = "horizontal",
1038
1288
  size = "md",
@@ -1046,11 +1296,11 @@ var ButtonGroup = ({
1046
1296
  id,
1047
1297
  testID
1048
1298
  }) => {
1049
- const { theme } = (0, import_xui_core4.useDesignSystem)();
1299
+ const { theme } = (0, import_xui_core5.useDesignSystem)();
1050
1300
  const flattenChildren = (children2) => {
1051
1301
  const result = [];
1052
- import_react5.default.Children.forEach(children2, (child) => {
1053
- if (import_react5.default.isValidElement(child) && child.type === import_react5.default.Fragment) {
1302
+ import_react6.default.Children.forEach(children2, (child) => {
1303
+ if (import_react6.default.isValidElement(child) && child.type === import_react6.default.Fragment) {
1054
1304
  result.push(...flattenChildren(child.props.children));
1055
1305
  } else if (child !== null && child !== void 0) {
1056
1306
  result.push(child);
@@ -1086,8 +1336,8 @@ var ButtonGroup = ({
1086
1336
  const processChildren = (childrenToProcess) => {
1087
1337
  if (orientation === "vertical") {
1088
1338
  return childrenToProcess.map((child, index) => {
1089
- if (import_react5.default.isValidElement(child)) {
1090
- return import_react5.default.cloneElement(child, {
1339
+ if (import_react6.default.isValidElement(child)) {
1340
+ return import_react6.default.cloneElement(child, {
1091
1341
  ...child.props,
1092
1342
  fullWidth: true,
1093
1343
  key: child.key ?? index
@@ -1103,9 +1353,9 @@ var ButtonGroup = ({
1103
1353
  if (useSpaceBetween) {
1104
1354
  const firstChild = processedChildren[0];
1105
1355
  const restChildren = processedChildren.slice(1);
1106
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1356
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1107
1357
  firstChild,
1108
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { flexDirection: "row", gap: computedGap, children: restChildren })
1358
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box, { flexDirection: "row", gap: computedGap, children: restChildren })
1109
1359
  ] });
1110
1360
  }
1111
1361
  if (orientation === "vertical") {
@@ -1113,8 +1363,8 @@ var ButtonGroup = ({
1113
1363
  }
1114
1364
  return children;
1115
1365
  };
1116
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box, { flexDirection: "column", width: "100%", gap: 8, children: [
1117
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1366
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box, { flexDirection: "column", width: "100%", gap: 8, children: [
1367
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1118
1368
  Box,
1119
1369
  {
1120
1370
  role: "group",
@@ -1131,7 +1381,7 @@ var ButtonGroup = ({
1131
1381
  children: renderChildren()
1132
1382
  }
1133
1383
  ),
1134
- error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { marginTop: 4, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1384
+ error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box, { marginTop: 4, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1135
1385
  Text,
1136
1386
  {
1137
1387
  id: errorId,
@@ -1144,7 +1394,7 @@ var ButtonGroup = ({
1144
1394
  children: error
1145
1395
  }
1146
1396
  ) }),
1147
- description && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { marginTop: 4, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1397
+ description && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box, { marginTop: 4, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1148
1398
  Text,
1149
1399
  {
1150
1400
  id: descriptionId,
@@ -1160,6 +1410,7 @@ var ButtonGroup = ({
1160
1410
  ButtonGroup.displayName = "ButtonGroup";
1161
1411
  // Annotate the CommonJS export names for ESM import in node:
1162
1412
  0 && (module.exports = {
1413
+ AppButton,
1163
1414
  Button,
1164
1415
  ButtonGroup,
1165
1416
  FlexButton,
@@ -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 };