@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.
package/web/index.d.mts 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/web/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/web/index.js CHANGED
@@ -932,7 +932,13 @@ var SPINNER_SIZES = {
932
932
  lg: 18,
933
933
  xl: 20
934
934
  };
935
- var FLEX_BUTTON_PADDING = 4;
935
+ var BACKGROUND_PADDING = {
936
+ xs: 2,
937
+ sm: 2,
938
+ md: 2,
939
+ lg: 4,
940
+ xl: 4
941
+ };
936
942
  var hasVisibleLabel = (node) => {
937
943
  if (node == null || node === false || node === true) return false;
938
944
  if (typeof node === "string") return node.trim().length > 0;
@@ -1009,7 +1015,6 @@ var FlexButton = (0, import_react5.forwardRef)(
1009
1015
  }, ref) => {
1010
1016
  const { theme } = (0, import_xui_core3.useResolvedTheme)({ themeMode, themeProductContext });
1011
1017
  const [state, setState] = (0, import_react5.useState)("default");
1012
- const [isFocused, setIsFocused] = (0, import_react5.useState)(false);
1013
1018
  const isMouseOverRef = (0, import_react5.useRef)(false);
1014
1019
  const isDisabled = disabled || loading;
1015
1020
  const getVariantColors = (currentState) => {
@@ -1108,18 +1113,6 @@ var FlexButton = (0, import_react5.forwardRef)(
1108
1113
  };
1109
1114
  }
1110
1115
  };
1111
- const getFocusRingColor = () => {
1112
- switch (variant) {
1113
- case "brand":
1114
- return theme.colors.overlay.brand;
1115
- case "brandExtra":
1116
- return theme.colors.overlay.brandExtra;
1117
- case "inverse":
1118
- return "rgba(255, 255, 255, 0.3)";
1119
- default:
1120
- return theme.colors.overlay.mono;
1121
- }
1122
- };
1123
1116
  const getSpinnerColor = () => {
1124
1117
  switch (variant) {
1125
1118
  case "brand":
@@ -1139,13 +1132,13 @@ var FlexButton = (0, import_react5.forwardRef)(
1139
1132
  }
1140
1133
  };
1141
1134
  const colors = getVariantColors(state);
1142
- const focusRingColor = getFocusRingColor();
1143
1135
  const spinnerColor = getSpinnerColor();
1144
1136
  const iconSize = ICON_SIZES[size];
1145
1137
  const spinnerSize = SPINNER_SIZES[size];
1146
1138
  const fontSize = FONT_SIZES[size];
1147
1139
  const borderRadius = BORDER_RADIUS[size];
1148
1140
  const lineHeight = LINE_HEIGHTS[size];
1141
+ const framePadding = background ? BACKGROUND_PADDING[size] : 0;
1149
1142
  const showTextLabel = hasVisibleLabel(children);
1150
1143
  const hasIconChildren = !showTextLabel && hasRenderableContent(children);
1151
1144
  const isIconOnly = !loading && !showTextLabel && Boolean(iconLeft || iconRight || hasIconChildren);
@@ -1172,14 +1165,6 @@ var FlexButton = (0, import_react5.forwardRef)(
1172
1165
  setState(isMouseOverRef.current ? "hover" : "default");
1173
1166
  }
1174
1167
  };
1175
- const handleFocus = () => {
1176
- if (!isDisabled) {
1177
- setIsFocused(true);
1178
- }
1179
- };
1180
- const handleBlur = () => {
1181
- setIsFocused(false);
1182
- };
1183
1168
  const handleClick = (event) => {
1184
1169
  if (isDisabled) return;
1185
1170
  if (onPress) {
@@ -1207,18 +1192,13 @@ var FlexButton = (0, import_react5.forwardRef)(
1207
1192
  }
1208
1193
  };
1209
1194
  const borderShadow = colors.border ? `inset 0 0 0 1px ${colors.border}` : void 0;
1210
- const focusShadow = isFocused && !isDisabled ? `0 0 0 2px ${focusRingColor}` : void 0;
1211
- const boxShadows = [];
1212
- if (borderShadow) boxShadows.push(borderShadow);
1213
- if (focusShadow) boxShadows.push(focusShadow);
1214
- const combinedBoxShadow = boxShadows.length > 0 ? boxShadows.join(", ") : "none";
1215
1195
  const buttonStyle = {
1216
1196
  display: "inline-flex",
1217
1197
  alignItems: "center",
1218
1198
  justifyContent: "center",
1219
1199
  gap: isIconOnly ? 0 : "2px",
1220
1200
  boxSizing: "border-box",
1221
- padding: noPadding ? "0px" : `${FLEX_BUTTON_PADDING}px`,
1201
+ padding: noPadding ? "0px" : `${framePadding}px`,
1222
1202
  backgroundColor: loading ? "transparent" : colors.bg,
1223
1203
  color: colors.text,
1224
1204
  border: "none",
@@ -1230,8 +1210,7 @@ var FlexButton = (0, import_react5.forwardRef)(
1230
1210
  lineHeight: isIconOnly ? 0 : lineHeight,
1231
1211
  fontFamily: "inherit",
1232
1212
  transition: "background-color 100ms ease-in-out, color 100ms ease-in-out, box-shadow 100ms ease-in-out",
1233
- outline: "none",
1234
- boxShadow: combinedBoxShadow,
1213
+ boxShadow: borderShadow ?? "none",
1235
1214
  ...isIconOnly && {
1236
1215
  width: iconOnlyHitArea,
1237
1216
  height: iconOnlyHitArea,
@@ -1274,8 +1253,6 @@ var FlexButton = (0, import_react5.forwardRef)(
1274
1253
  onMouseUp: handleMouseUp,
1275
1254
  onKeyDown: handleKeyDown,
1276
1255
  onKeyUp: handleKeyUp,
1277
- onFocus: handleFocus,
1278
- onBlur: handleBlur,
1279
1256
  "aria-label": computedAriaLabel,
1280
1257
  "aria-busy": loading || void 0,
1281
1258
  "aria-disabled": isDisabled || void 0,