@xsolla/xui-button 0.216.1 → 0.218.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.
@@ -183,7 +183,13 @@ interface FlexButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElem
183
183
  variant?: "brand" | "primary" | "secondary" | "tertiary" | "brandExtra" | "inverse";
184
184
  /** Size of the button */
185
185
  size?: "xl" | "lg" | "md" | "sm" | "xs";
186
- /** Whether to show background fill */
186
+ /**
187
+ * Whether to show background fill.
188
+ *
189
+ * This also drives the button's frame padding: a filled button is padded per
190
+ * size ({@link BACKGROUND_PADDING}), while an unfilled one renders flush so
191
+ * it sits inline with surrounding text (XUITSD-86).
192
+ */
187
193
  background?: boolean;
188
194
  /**
189
195
  * Whether to show the hover/press background color.
@@ -198,6 +204,9 @@ interface FlexButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElem
198
204
  * class or `style` prop a consumer passes, so this prop is the supported way
199
205
  * to render a zero-padding FlexButton (e.g. an inline text action).
200
206
  *
207
+ * Only meaningful together with `background` — an unfilled FlexButton already
208
+ * renders flush at every size, so `noPadding` is a no-op there (XUITSD-86).
209
+ *
201
210
  * Icon-only buttons keep their square dimensions when `noPadding` is set —
202
211
  * only the inner padding is removed.
203
212
  * @default false
@@ -239,12 +248,22 @@ declare const getFlexButtonBoxSize: (size?: NonNullable<FlexButtonProps["size"]>
239
248
  *
240
249
  * Renders as a semantic `<button>` element with full ARIA support.
241
250
  *
251
+ * ## Frame padding
252
+ *
253
+ * Padding follows the `background` prop. A filled button (`background`) is
254
+ * padded per size — 2px at `xs`/`sm`/`md`, 4px at `lg`/`xl` — while an unfilled
255
+ * one renders flush on all four sides so it flows inline with surrounding text.
256
+ * `noPadding` strips the padding from a filled button; on an unfilled one there
257
+ * is nothing left to strip (XUITSD-86).
258
+ *
242
259
  * ## Icon-only buttons
243
260
  *
244
261
  * A FlexButton is icon-only when it has no text label and an icon is supplied
245
262
  * either as `children` or via `iconLeft`/`iconRight`. Icon-only buttons render
246
263
  * as a square whose side equals `theme.sizing.flexButton(size).height`, so they
247
- * line up with the Modal header icon slots (36x36 at `size="xl"`).
264
+ * line up with the Modal header icon slots (36x36 at `size="xl"`). The square is
265
+ * independent of the padding above, so an unfilled icon-only button keeps its
266
+ * hit area and simply centres the icon inside it.
248
267
  *
249
268
  * ```tsx
250
269
  * <FlexButton variant="secondary" size="xl" aria-label="Go back">
@@ -257,7 +276,10 @@ declare const getFlexButtonBoxSize: (size?: NonNullable<FlexButtonProps["size"]>
257
276
  * - **Semantic HTML**: Renders as a native `<button>` element
258
277
  * - **Keyboard Navigation**: Focusable via Tab, activated with Enter or Space
259
278
  * - **ARIA States**: Properly announces disabled and loading states
260
- * - **Focus Indicator**: Visible focus ring for keyboard navigation
279
+ * - **Focus Indicator**: The browser's native `:focus-visible` ring. The design
280
+ * system specifies no focus treatment for FlexButton (XUITSD-61), so the
281
+ * component paints none of its own and does not suppress the UA outline —
282
+ * which keeps a keyboard-only indicator without drawing a ring on click.
261
283
  * - **Screen Reader Support**: Announces button label, state, and any associated descriptions
262
284
  */
263
285
  declare const FlexButton: React.ForwardRefExoticComponent<FlexButtonProps & React.RefAttributes<HTMLButtonElement>>;
package/native/index.d.ts CHANGED
@@ -183,7 +183,13 @@ interface FlexButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElem
183
183
  variant?: "brand" | "primary" | "secondary" | "tertiary" | "brandExtra" | "inverse";
184
184
  /** Size of the button */
185
185
  size?: "xl" | "lg" | "md" | "sm" | "xs";
186
- /** Whether to show background fill */
186
+ /**
187
+ * Whether to show background fill.
188
+ *
189
+ * This also drives the button's frame padding: a filled button is padded per
190
+ * size ({@link BACKGROUND_PADDING}), while an unfilled one renders flush so
191
+ * it sits inline with surrounding text (XUITSD-86).
192
+ */
187
193
  background?: boolean;
188
194
  /**
189
195
  * Whether to show the hover/press background color.
@@ -198,6 +204,9 @@ interface FlexButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElem
198
204
  * class or `style` prop a consumer passes, so this prop is the supported way
199
205
  * to render a zero-padding FlexButton (e.g. an inline text action).
200
206
  *
207
+ * Only meaningful together with `background` — an unfilled FlexButton already
208
+ * renders flush at every size, so `noPadding` is a no-op there (XUITSD-86).
209
+ *
201
210
  * Icon-only buttons keep their square dimensions when `noPadding` is set —
202
211
  * only the inner padding is removed.
203
212
  * @default false
@@ -239,12 +248,22 @@ declare const getFlexButtonBoxSize: (size?: NonNullable<FlexButtonProps["size"]>
239
248
  *
240
249
  * Renders as a semantic `<button>` element with full ARIA support.
241
250
  *
251
+ * ## Frame padding
252
+ *
253
+ * Padding follows the `background` prop. A filled button (`background`) is
254
+ * padded per size — 2px at `xs`/`sm`/`md`, 4px at `lg`/`xl` — while an unfilled
255
+ * one renders flush on all four sides so it flows inline with surrounding text.
256
+ * `noPadding` strips the padding from a filled button; on an unfilled one there
257
+ * is nothing left to strip (XUITSD-86).
258
+ *
242
259
  * ## Icon-only buttons
243
260
  *
244
261
  * A FlexButton is icon-only when it has no text label and an icon is supplied
245
262
  * either as `children` or via `iconLeft`/`iconRight`. Icon-only buttons render
246
263
  * as a square whose side equals `theme.sizing.flexButton(size).height`, so they
247
- * line up with the Modal header icon slots (36x36 at `size="xl"`).
264
+ * line up with the Modal header icon slots (36x36 at `size="xl"`). The square is
265
+ * independent of the padding above, so an unfilled icon-only button keeps its
266
+ * hit area and simply centres the icon inside it.
248
267
  *
249
268
  * ```tsx
250
269
  * <FlexButton variant="secondary" size="xl" aria-label="Go back">
@@ -257,7 +276,10 @@ declare const getFlexButtonBoxSize: (size?: NonNullable<FlexButtonProps["size"]>
257
276
  * - **Semantic HTML**: Renders as a native `<button>` element
258
277
  * - **Keyboard Navigation**: Focusable via Tab, activated with Enter or Space
259
278
  * - **ARIA States**: Properly announces disabled and loading states
260
- * - **Focus Indicator**: Visible focus ring for keyboard navigation
279
+ * - **Focus Indicator**: The browser's native `:focus-visible` ring. The design
280
+ * system specifies no focus treatment for FlexButton (XUITSD-61), so the
281
+ * component paints none of its own and does not suppress the UA outline —
282
+ * which keeps a keyboard-only indicator without drawing a ring on click.
261
283
  * - **Screen Reader Support**: Announces button label, state, and any associated descriptions
262
284
  */
263
285
  declare const FlexButton: React.ForwardRefExoticComponent<FlexButtonProps & React.RefAttributes<HTMLButtonElement>>;
package/native/index.js CHANGED
@@ -889,7 +889,13 @@ var SPINNER_SIZES = {
889
889
  lg: 18,
890
890
  xl: 20
891
891
  };
892
- var FLEX_BUTTON_PADDING = 4;
892
+ var BACKGROUND_PADDING = {
893
+ xs: 2,
894
+ sm: 2,
895
+ md: 2,
896
+ lg: 4,
897
+ xl: 4
898
+ };
893
899
  var hasVisibleLabel = (node) => {
894
900
  if (node == null || node === false || node === true) return false;
895
901
  if (typeof node === "string") return node.trim().length > 0;
@@ -966,7 +972,6 @@ var FlexButton = (0, import_react4.forwardRef)(
966
972
  }, ref) => {
967
973
  const { theme } = (0, import_xui_core3.useResolvedTheme)({ themeMode, themeProductContext });
968
974
  const [state, setState] = (0, import_react4.useState)("default");
969
- const [isFocused, setIsFocused] = (0, import_react4.useState)(false);
970
975
  const isMouseOverRef = (0, import_react4.useRef)(false);
971
976
  const isDisabled = disabled || loading;
972
977
  const getVariantColors = (currentState) => {
@@ -1065,18 +1070,6 @@ var FlexButton = (0, import_react4.forwardRef)(
1065
1070
  };
1066
1071
  }
1067
1072
  };
1068
- const getFocusRingColor = () => {
1069
- switch (variant) {
1070
- case "brand":
1071
- return theme.colors.overlay.brand;
1072
- case "brandExtra":
1073
- return theme.colors.overlay.brandExtra;
1074
- case "inverse":
1075
- return "rgba(255, 255, 255, 0.3)";
1076
- default:
1077
- return theme.colors.overlay.mono;
1078
- }
1079
- };
1080
1073
  const getSpinnerColor = () => {
1081
1074
  switch (variant) {
1082
1075
  case "brand":
@@ -1096,13 +1089,13 @@ var FlexButton = (0, import_react4.forwardRef)(
1096
1089
  }
1097
1090
  };
1098
1091
  const colors = getVariantColors(state);
1099
- const focusRingColor = getFocusRingColor();
1100
1092
  const spinnerColor = getSpinnerColor();
1101
1093
  const iconSize = ICON_SIZES[size];
1102
1094
  const spinnerSize = SPINNER_SIZES[size];
1103
1095
  const fontSize = FONT_SIZES[size];
1104
1096
  const borderRadius = BORDER_RADIUS[size];
1105
1097
  const lineHeight = LINE_HEIGHTS[size];
1098
+ const framePadding = background ? BACKGROUND_PADDING[size] : 0;
1106
1099
  const showTextLabel = hasVisibleLabel(children);
1107
1100
  const hasIconChildren = !showTextLabel && hasRenderableContent(children);
1108
1101
  const isIconOnly = !loading && !showTextLabel && Boolean(iconLeft || iconRight || hasIconChildren);
@@ -1129,14 +1122,6 @@ var FlexButton = (0, import_react4.forwardRef)(
1129
1122
  setState(isMouseOverRef.current ? "hover" : "default");
1130
1123
  }
1131
1124
  };
1132
- const handleFocus = () => {
1133
- if (!isDisabled) {
1134
- setIsFocused(true);
1135
- }
1136
- };
1137
- const handleBlur = () => {
1138
- setIsFocused(false);
1139
- };
1140
1125
  const handleClick = (event) => {
1141
1126
  if (isDisabled) return;
1142
1127
  if (onPress) {
@@ -1164,18 +1149,13 @@ var FlexButton = (0, import_react4.forwardRef)(
1164
1149
  }
1165
1150
  };
1166
1151
  const borderShadow = colors.border ? `inset 0 0 0 1px ${colors.border}` : void 0;
1167
- const focusShadow = isFocused && !isDisabled ? `0 0 0 2px ${focusRingColor}` : void 0;
1168
- const boxShadows = [];
1169
- if (borderShadow) boxShadows.push(borderShadow);
1170
- if (focusShadow) boxShadows.push(focusShadow);
1171
- const combinedBoxShadow = boxShadows.length > 0 ? boxShadows.join(", ") : "none";
1172
1152
  const buttonStyle = {
1173
1153
  display: "inline-flex",
1174
1154
  alignItems: "center",
1175
1155
  justifyContent: "center",
1176
1156
  gap: isIconOnly ? 0 : "2px",
1177
1157
  boxSizing: "border-box",
1178
- padding: noPadding ? "0px" : `${FLEX_BUTTON_PADDING}px`,
1158
+ padding: noPadding ? "0px" : `${framePadding}px`,
1179
1159
  backgroundColor: loading ? "transparent" : colors.bg,
1180
1160
  color: colors.text,
1181
1161
  border: "none",
@@ -1187,8 +1167,7 @@ var FlexButton = (0, import_react4.forwardRef)(
1187
1167
  lineHeight: isIconOnly ? 0 : lineHeight,
1188
1168
  fontFamily: "inherit",
1189
1169
  transition: "background-color 100ms ease-in-out, color 100ms ease-in-out, box-shadow 100ms ease-in-out",
1190
- outline: "none",
1191
- boxShadow: combinedBoxShadow,
1170
+ boxShadow: borderShadow ?? "none",
1192
1171
  ...isIconOnly && {
1193
1172
  width: iconOnlyHitArea,
1194
1173
  height: iconOnlyHitArea,
@@ -1231,8 +1210,6 @@ var FlexButton = (0, import_react4.forwardRef)(
1231
1210
  onMouseUp: handleMouseUp,
1232
1211
  onKeyDown: handleKeyDown,
1233
1212
  onKeyUp: handleKeyUp,
1234
- onFocus: handleFocus,
1235
- onBlur: handleBlur,
1236
1213
  "aria-label": computedAriaLabel,
1237
1214
  "aria-busy": loading || void 0,
1238
1215
  "aria-disabled": isDisabled || void 0,