@xsolla/xui-button 0.191.0 → 0.192.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.ts CHANGED
@@ -170,7 +170,14 @@ interface IconButtonProps extends ThemeOverrideProps {
170
170
  declare const IconButton: React.FC<IconButtonProps>;
171
171
 
172
172
  interface FlexButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "type"> {
173
- /** Button label. Omit for icon-only buttons (provide `aria-label`). */
173
+ /**
174
+ * Button label. Omit for icon-only buttons (provide `aria-label`).
175
+ *
176
+ * An icon may also be passed directly as `children` instead of via
177
+ * `iconLeft`/`iconRight`. When `children` carries no text, the button is
178
+ * treated as icon-only and renders as a square sized from the design-system
179
+ * height token for `size`.
180
+ */
174
181
  children?: ReactNode;
175
182
  /** Visual variant of the button */
176
183
  variant?: "brand" | "primary" | "secondary" | "tertiary" | "brandExtra" | "inverse";
@@ -190,6 +197,9 @@ interface FlexButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElem
190
197
  * The component applies padding via an inline style, which overrides any CSS
191
198
  * class or `style` prop a consumer passes, so this prop is the supported way
192
199
  * to render a zero-padding FlexButton (e.g. an inline text action).
200
+ *
201
+ * Icon-only buttons keep their square dimensions when `noPadding` is set —
202
+ * only the inner padding is removed.
193
203
  * @default false
194
204
  */
195
205
  noPadding?: boolean;
@@ -224,6 +234,19 @@ interface FlexButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElem
224
234
  *
225
235
  * Renders as a semantic `<button>` element with full ARIA support.
226
236
  *
237
+ * ## Icon-only buttons
238
+ *
239
+ * A FlexButton is icon-only when it has no text label and an icon is supplied
240
+ * either as `children` or via `iconLeft`/`iconRight`. Icon-only buttons render
241
+ * as a square whose side equals `theme.sizing.flexButton(size).height`, so they
242
+ * line up with the Modal header icon slots (36x36 at `size="xl"`).
243
+ *
244
+ * ```tsx
245
+ * <FlexButton variant="secondary" size="xl" aria-label="Go back">
246
+ * <BackwardAlt variant="line" />
247
+ * </FlexButton>
248
+ * ```
249
+ *
227
250
  * ## Accessibility Features
228
251
  *
229
252
  * - **Semantic HTML**: Renders as a native `<button>` element
package/web/index.js CHANGED
@@ -871,15 +871,25 @@ var IconButton = ({
871
871
  IconButton.displayName = "IconButton";
872
872
 
873
873
  // src/FlexButton.tsx
874
- var import_react5 = require("react");
874
+ var import_react5 = __toESM(require("react"));
875
875
  var import_xui_core3 = require("@xsolla/xui-core");
876
876
  var import_jsx_runtime7 = require("react/jsx-runtime");
877
877
  var FLEX_BUTTON_PADDING = 4;
878
878
  var hasVisibleLabel = (node) => {
879
- if (node == null || node === false) return false;
879
+ if (node == null || node === false || node === true) return false;
880
880
  if (typeof node === "string") return node.trim().length > 0;
881
881
  if (typeof node === "number") return true;
882
882
  if (Array.isArray(node)) return node.some(hasVisibleLabel);
883
+ if (import_react5.default.isValidElement(node)) {
884
+ const { children: elementChildren } = node.props ?? {};
885
+ return hasVisibleLabel(elementChildren);
886
+ }
887
+ return true;
888
+ };
889
+ var hasRenderableContent = (node) => {
890
+ if (node == null || node === false || node === true) return false;
891
+ if (typeof node === "string") return node.trim().length > 0;
892
+ if (Array.isArray(node)) return node.some(hasRenderableContent);
883
893
  return true;
884
894
  };
885
895
  var ICON_SIZES = {
@@ -1081,8 +1091,10 @@ var FlexButton = ({
1081
1091
  const fontSize = FONT_SIZES[size];
1082
1092
  const borderRadius = BORDER_RADIUS[size];
1083
1093
  const lineHeight = LINE_HEIGHTS[size];
1084
- const isIconOnly = !loading && !hasVisibleLabel(children) && Boolean(iconLeft || iconRight);
1085
- const iconOnlyHitArea = iconSize + FLEX_BUTTON_PADDING * 2;
1094
+ const showTextLabel = hasVisibleLabel(children);
1095
+ const hasIconChildren = !showTextLabel && hasRenderableContent(children);
1096
+ const isIconOnly = !loading && !showTextLabel && Boolean(iconLeft || iconRight || hasIconChildren);
1097
+ const iconOnlyHitArea = theme.sizing.flexButton(size).height;
1086
1098
  const handleMouseEnter = () => {
1087
1099
  if (!isDisabled) {
1088
1100
  isMouseOverRef.current = true;
@@ -1222,7 +1234,8 @@ var FlexButton = ({
1222
1234
  "data-testid": testID || "flex-button",
1223
1235
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: contentStyle, children: loading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: spinnerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Spinner, { size: spinnerSize, color: spinnerColor }) }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1224
1236
  iconLeft && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { size: iconSize, color: colors.text, children: iconLeft }),
1225
- hasVisibleLabel(children) && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children }),
1237
+ showTextLabel && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children }),
1238
+ hasIconChildren && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { size: iconSize, color: colors.text, children }),
1226
1239
  iconRight && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { size: iconSize, color: colors.text, children: iconRight })
1227
1240
  ] }) })
1228
1241
  }