@xsolla/xui-button 0.148.0 → 0.148.2-pr235.1777605920

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 ADDED
@@ -0,0 +1,523 @@
1
+ # Button
2
+
3
+ A cross-platform React button component with primary/secondary variants, multiple color tones, sizes, loading states, and icon support. Works on both React (web) and React Native.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @xsolla/xui-button
9
+ # or
10
+ yarn add @xsolla/xui-button
11
+ ```
12
+
13
+ ## Demo
14
+
15
+ ### Basic Button
16
+
17
+ ```tsx
18
+ import * as React from 'react';
19
+ import { Button } from '@xsolla/xui-button';
20
+
21
+ export default function BasicButton() {
22
+ return (
23
+ <Button onPress={() => console.log('Pressed!')}>
24
+ Click me
25
+ </Button>
26
+ );
27
+ }
28
+ ```
29
+
30
+ ### Button Variants
31
+
32
+ ```tsx
33
+ import * as React from 'react';
34
+ import { Button } from '@xsolla/xui-button';
35
+
36
+ export default function ButtonVariants() {
37
+ return (
38
+ <div style={{ display: 'flex', gap: 16 }}>
39
+ <Button variant="primary">Primary</Button>
40
+ <Button variant="secondary">Secondary</Button>
41
+ <Button variant="tertiary">Tertiary</Button>
42
+ </div>
43
+ );
44
+ }
45
+ ```
46
+
47
+ ### Button Tones
48
+
49
+ ```tsx
50
+ import * as React from 'react';
51
+ import { Button } from '@xsolla/xui-button';
52
+
53
+ export default function ButtonTones() {
54
+ return (
55
+ <div style={{ display: 'flex', gap: 16 }}>
56
+ <Button tone="brand">Brand</Button>
57
+ <Button tone="brandExtra">Brand Extra</Button>
58
+ <Button tone="alert">Alert</Button>
59
+ <Button tone="mono">Mono</Button>
60
+ </div>
61
+ );
62
+ }
63
+ ```
64
+
65
+ ### Button Sizes
66
+
67
+ ```tsx
68
+ import * as React from 'react';
69
+ import { Button } from '@xsolla/xui-button';
70
+
71
+ export default function ButtonSizes() {
72
+ return (
73
+ <div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
74
+ <Button size="xs">Extra Small</Button>
75
+ <Button size="sm">Small</Button>
76
+ <Button size="md">Medium</Button>
77
+ <Button size="lg">Large</Button>
78
+ <Button size="xl">Extra Large</Button>
79
+ </div>
80
+ );
81
+ }
82
+ ```
83
+
84
+ ### Button with Icons
85
+
86
+ ```tsx
87
+ import * as React from 'react';
88
+ import { Button } from '@xsolla/xui-button';
89
+ import { Plus, ChevronDown, FileDownloadOut } from '@xsolla/xui-icons-base';
90
+ import { ArrowRight } from '@xsolla/xui-icons';
91
+
92
+ export default function ButtonWithIcons() {
93
+ return (
94
+ <div style={{ display: 'flex', gap: 16 }}>
95
+ <Button iconLeft={<Plus />}>
96
+ Add Item
97
+ </Button>
98
+ <Button iconRight={<ArrowRight />}>
99
+ Next Step
100
+ </Button>
101
+ <Button
102
+ iconLeft={<FileDownloadOut />}
103
+ iconRight={<ChevronDown />}
104
+ >
105
+ Download
106
+ </Button>
107
+ </div>
108
+ );
109
+ }
110
+ ```
111
+
112
+ ### Loading Button
113
+
114
+ ```tsx
115
+ import * as React from 'react';
116
+ import { Button } from '@xsolla/xui-button';
117
+
118
+ export default function LoadingButton() {
119
+ const [loading, setLoading] = React.useState(false);
120
+
121
+ const handlePress = () => {
122
+ setLoading(true);
123
+ setTimeout(() => setLoading(false), 2000);
124
+ };
125
+
126
+ return (
127
+ <Button loading={loading} onPress={handlePress}>
128
+ {loading ? 'Processing...' : 'Submit'}
129
+ </Button>
130
+ );
131
+ }
132
+ ```
133
+
134
+ ## Anatomy
135
+
136
+ Import the component and use it directly:
137
+
138
+ ```jsx
139
+ import { Button, IconButton, FlexButton, ButtonGroup } from '@xsolla/xui-button';
140
+ import { Plus } from '@xsolla/xui-icons-base';
141
+
142
+ // Basic button
143
+ <Button>Label</Button>
144
+
145
+ // Icon-only button
146
+ <IconButton icon={<Plus />} aria-label="Add item" />
147
+
148
+ // Flexible styling button
149
+ <FlexButton variant="brand">Label</FlexButton>
150
+
151
+ // Group of buttons
152
+ <ButtonGroup>
153
+ <Button>First</Button>
154
+ <Button>Second</Button>
155
+ </ButtonGroup>
156
+ ```
157
+
158
+ ## Examples
159
+
160
+ ### Full Width Button
161
+
162
+ Use the `fullWidth` prop to make the button span the entire container width.
163
+
164
+ ```tsx
165
+ import * as React from 'react';
166
+ import { Button } from '@xsolla/xui-button';
167
+
168
+ export default function FullWidthButton() {
169
+ return (
170
+ <div style={{ width: 300 }}>
171
+ <Button fullWidth>Full Width Button</Button>
172
+ </div>
173
+ );
174
+ }
175
+ ```
176
+
177
+ ### Disabled Button
178
+
179
+ ```tsx
180
+ import * as React from 'react';
181
+ import { Button } from '@xsolla/xui-button';
182
+
183
+ export default function DisabledButton() {
184
+ return (
185
+ <div style={{ display: 'flex', gap: 16 }}>
186
+ <Button disabled>Disabled Primary</Button>
187
+ <Button variant="secondary" disabled>Disabled Secondary</Button>
188
+ </div>
189
+ );
190
+ }
191
+ ```
192
+
193
+ ### Icon Button
194
+
195
+ Use `IconButton` for buttons that contain only an icon.
196
+
197
+ ```tsx
198
+ import * as React from 'react';
199
+ import { IconButton } from '@xsolla/xui-button';
200
+ import { Plus, TrashCan } from '@xsolla/xui-icons-base';
201
+ import { Settings } from '@xsolla/xui-icons';
202
+
203
+ export default function IconButtonExample() {
204
+ return (
205
+ <div style={{ display: 'flex', gap: 16 }}>
206
+ <IconButton
207
+ icon={<Plus />}
208
+ aria-label="Add"
209
+ size="md"
210
+ />
211
+ <IconButton
212
+ icon={<TrashCan />}
213
+ aria-label="Delete"
214
+ tone="alert"
215
+ />
216
+ <IconButton
217
+ icon={<Settings />}
218
+ aria-label="Settings"
219
+ variant="secondary"
220
+ />
221
+ </div>
222
+ );
223
+ }
224
+ ```
225
+
226
+ ### Button Group
227
+
228
+ Use `ButtonGroup` to group related buttons together.
229
+
230
+ ```tsx
231
+ import * as React from 'react';
232
+ import { Button, ButtonGroup } from '@xsolla/xui-button';
233
+
234
+ export default function ButtonGroupExample() {
235
+ return (
236
+ <ButtonGroup orientation="horizontal" size="md">
237
+ <Button variant="secondary">Cancel</Button>
238
+ <Button>Confirm</Button>
239
+ </ButtonGroup>
240
+ );
241
+ }
242
+ ```
243
+
244
+ ### Vertical Button Group
245
+
246
+ ```tsx
247
+ import * as React from 'react';
248
+ import { Button, ButtonGroup } from '@xsolla/xui-button';
249
+
250
+ export default function VerticalButtonGroup() {
251
+ return (
252
+ <ButtonGroup orientation="vertical" size="md">
253
+ <Button fullWidth>Option 1</Button>
254
+ <Button fullWidth>Option 2</Button>
255
+ <Button fullWidth>Option 3</Button>
256
+ </ButtonGroup>
257
+ );
258
+ }
259
+ ```
260
+
261
+ ### Flex Button
262
+
263
+ `FlexButton` provides more flexible styling options with different background modes.
264
+
265
+ ```tsx
266
+ import * as React from 'react';
267
+ import { FlexButton } from '@xsolla/xui-button';
268
+ import { Link } from '@xsolla/xui-icons-base';
269
+
270
+ export default function FlexButtonExample() {
271
+ return (
272
+ <div style={{ display: 'flex', gap: 16 }}>
273
+ <FlexButton variant="brand" background>
274
+ With Background
275
+ </FlexButton>
276
+ <FlexButton variant="brand">
277
+ Text Only
278
+ </FlexButton>
279
+ <FlexButton variant="tertiary" iconLeft={<Link />}>
280
+ Link Style
281
+ </FlexButton>
282
+ </div>
283
+ );
284
+ }
285
+ ```
286
+
287
+ ### Form Submit Button
288
+
289
+ ```tsx
290
+ import * as React from 'react';
291
+ import { Button } from '@xsolla/xui-button';
292
+
293
+ export default function FormSubmitButton() {
294
+ return (
295
+ <form onSubmit={(e) => {
296
+ e.preventDefault();
297
+ console.log('Form submitted');
298
+ }}>
299
+ <Button type="submit">Submit Form</Button>
300
+ </form>
301
+ );
302
+ }
303
+ ```
304
+
305
+ ### Button with Sublabel
306
+
307
+ Use the `sublabel` prop to add secondary text inline with the main label.
308
+
309
+ ```tsx
310
+ import * as React from 'react';
311
+ import { Button } from '@xsolla/xui-button';
312
+ import { Apple } from '@xsolla/xui-icons-brand';
313
+
314
+ export default function ButtonWithSublabel() {
315
+ return (
316
+ <Button
317
+ iconLeft={<Apple />}
318
+ sublabel="$39.99"
319
+ labelAlignment="left"
320
+ >
321
+ Buy Now
322
+ </Button>
323
+ );
324
+ }
325
+ ```
326
+
327
+ ### Button with Custom Content
328
+
329
+ Use `customContent` to add badges, tags, or other elements inside the button.
330
+
331
+ ```tsx
332
+ import * as React from 'react';
333
+ import { Button } from '@xsolla/xui-button';
334
+ import { Tag } from '@xsolla/xui-tag';
335
+ import { ArrowRight } from '@xsolla/xui-icons-base';
336
+
337
+ export default function ButtonWithCustomContent() {
338
+ return (
339
+ <Button
340
+ iconRight={<ArrowRight />}
341
+ customContent={<Tag size="sm">5x</Tag>}
342
+ >
343
+ Claim Reward
344
+ </Button>
345
+ );
346
+ }
347
+ ```
348
+
349
+ ### Tertiary Variant
350
+
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
+
353
+ ```tsx
354
+ import * as React from 'react';
355
+ import { Button, IconButton } from '@xsolla/xui-button';
356
+ import { Settings, ArrowRight } from '@xsolla/xui-icons-base';
357
+
358
+ export default function TertiaryButtons() {
359
+ return (
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>
367
+ <Button variant="tertiary">Learn More</Button>
368
+ <IconButton
369
+ variant="tertiary"
370
+ icon={<Settings />}
371
+ aria-label="Settings"
372
+ />
373
+ </div>
374
+ );
375
+ }
376
+ ```
377
+
378
+ ## API Reference
379
+
380
+ ### Button
381
+
382
+ The main button component. Renders a semantic `<button>` element.
383
+
384
+ **Button Props:**
385
+
386
+ | Prop | Type | Default | Description |
387
+ | :--- | :--- | :------ | :---------- |
388
+ | children | `ReactNode` | - | The button label content. |
389
+ | variant | `"primary" \| "secondary" \| "tertiary" \| "ghost"` | `"primary"` | The visual style variant. |
390
+ | tone | `"brand" \| "brandExtra" \| "alert" \| "mono"` | `"brand"` | The color tone of the button. |
391
+ | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | The size of the button. |
392
+ | disabled | `boolean` | `false` | Whether the button is disabled. |
393
+ | loading | `boolean` | `false` | Whether to show loading spinner. Disables interaction when true. |
394
+ | onPress | `() => void` | - | Callback fired when button is pressed. |
395
+ | iconLeft | `ReactNode` | - | Icon to display on the left side. |
396
+ | iconRight | `ReactNode` | - | Icon to display on the right side. |
397
+ | sublabel | `string` | - | Secondary label text displayed inline with main label at 40% opacity. |
398
+ | labelIcon | `ReactNode` | - | Small icon displayed directly next to the label text. |
399
+ | labelAlignment | `"left" \| "center"` | `"center"` | Alignment of the label content within the button. |
400
+ | customContent | `ReactNode` | - | Custom content slot for badges, tags, or other elements. |
401
+ | fullWidth | `boolean` | `false` | Whether button should span full container width. |
402
+ | type | `"button" \| "submit" \| "reset"` | `"button"` | The HTML button type attribute. |
403
+ | aria-label | `string` | - | Accessible label for the button. |
404
+ | aria-describedby | `string` | - | ID of element that describes the button. |
405
+ | aria-expanded | `boolean` | - | Indicates if controlled element is expanded. |
406
+ | aria-haspopup | `boolean \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` | - | Indicates popup type triggered by button. |
407
+ | aria-pressed | `boolean \| "mixed"` | - | Indicates pressed state for toggle buttons. |
408
+ | aria-controls | `string` | - | ID of element controlled by this button. |
409
+ | testID | `string` | - | Test identifier for testing frameworks. |
410
+ | id | `string` | - | HTML id attribute. |
411
+
412
+ ---
413
+
414
+ ### IconButton
415
+
416
+ A button variant that displays only an icon. Requires `aria-label` for accessibility.
417
+
418
+ **IconButton Props:**
419
+
420
+ | Prop | Type | Default | Description |
421
+ | :--- | :--- | :------ | :---------- |
422
+ | icon | `ReactNode` | - | **Required.** The icon to display. |
423
+ | aria-label | `string` | - | **Required.** Accessible label for the button. |
424
+ | variant | `"primary" \| "secondary" \| "tertiary" \| "ghost"` | `"primary"` | The visual style variant. |
425
+ | tone | `"brand" \| "brandExtra" \| "alert" \| "mono"` | `"brand"` | The color tone of the button. |
426
+ | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | The size of the button. |
427
+ | disabled | `boolean` | `false` | Whether the button is disabled. |
428
+ | loading | `boolean` | `false` | Whether to show loading spinner. |
429
+ | onPress | `() => void` | - | Callback fired when button is pressed. |
430
+ | type | `"button" \| "submit" \| "reset"` | `"button"` | The HTML button type attribute. |
431
+
432
+ ---
433
+
434
+ ### FlexButton
435
+
436
+ A flexible button with more granular control over background and styling.
437
+
438
+ **FlexButton Props:**
439
+
440
+ | Prop | Type | Default | Description |
441
+ | :--- | :--- | :------ | :---------- |
442
+ | children | `ReactNode` | - | The button label content. |
443
+ | variant | `"brand" \| "primary" \| "secondary" \| "tertiary" \| "brandExtra" \| "inverse"` | `"brand"` | The visual style variant. |
444
+ | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | The size of the button. |
445
+ | background | `boolean` | `false` | Whether to show background fill. |
446
+ | disabled | `boolean` | `false` | Whether the button is disabled. |
447
+ | loading | `boolean` | `false` | Whether to show loading spinner. |
448
+ | iconLeft | `ReactNode` | - | Icon to display on the left side. |
449
+ | iconRight | `ReactNode` | - | Icon to display on the right side. |
450
+ | onPress | `() => void` | - | Callback fired when button is pressed. |
451
+ | type | `"button" \| "submit" \| "reset"` | `"button"` | The HTML button type attribute. |
452
+
453
+ ---
454
+
455
+ ### ButtonGroup
456
+
457
+ A container for grouping related buttons together.
458
+
459
+ **ButtonGroup Props:**
460
+
461
+ | Prop | Type | Default | Description |
462
+ | :--- | :--- | :------ | :---------- |
463
+ | children | `ReactNode` | - | **Required.** Button children to group. |
464
+ | orientation | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction of the buttons. |
465
+ | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | Size applied to the group spacing. |
466
+ | gap | `number` | - | Custom gap between buttons (overrides size default). |
467
+ | description | `string` | - | Description text shown below the group. |
468
+ | error | `string` | - | Error message (replaces description when present). |
469
+ | aria-label | `string` | - | Accessible label for the button group. |
470
+ | aria-labelledby | `string` | - | ID of element labeling the group. |
471
+ | aria-describedby | `string` | - | ID of element describing the group. |
472
+ | id | `string` | - | HTML id attribute. |
473
+ | testID | `string` | - | Test identifier for testing frameworks. |
474
+
475
+ **ButtonGroup Gap Defaults:**
476
+
477
+ | Size | Vertical Gap | Horizontal Gap |
478
+ | :--- | :----------- | :------------- |
479
+ | xl | 16 | 16 |
480
+ | lg | 16 | 16 |
481
+ | md | 12 | 16 |
482
+ | sm | 8 | 12 |
483
+ | xs | 4 | 12 |
484
+
485
+ ## Theming
486
+
487
+ Button components use the design system theme for colors and sizing:
488
+
489
+ ```typescript
490
+ // Colors accessed via theme
491
+ theme.colors.control.[tone].[variant].bg // Background color
492
+ theme.colors.control.[tone].[variant].bgHover // Hover background
493
+ theme.colors.control.[tone].[variant].bgPress // Pressed background
494
+ theme.colors.control.[tone].[variant].bgDisable // Disabled background
495
+ theme.colors.control.[tone].[variant].border // Border color
496
+ theme.colors.control.[tone].[variant].borderHover // Hover border
497
+ theme.colors.control.[tone].[variant].borderPress // Pressed border
498
+ theme.colors.control.[tone].[variant].borderDisable // Disabled border
499
+
500
+ // Text colors are at the tone level, mapped by variant name
501
+ theme.colors.control.[tone].text.primary // Text color for primary variant
502
+ theme.colors.control.[tone].text.secondary // Text color for secondary variant
503
+ theme.colors.control.[tone].text.tertiary // Text color for tertiary variant
504
+ theme.colors.control.[tone].text.disable // Text color for disabled state
505
+
506
+ // Sizing accessed via theme
507
+ theme.sizing.button(size).height
508
+ theme.sizing.button(size).paddingHorizontal
509
+ theme.sizing.button(size).fontSize
510
+ theme.sizing.button(size).iconSize
511
+
512
+ // Border radius
513
+ theme.radius.button
514
+ ```
515
+
516
+ ## Accessibility
517
+
518
+ - All buttons use semantic `<button>` elements
519
+ - `IconButton` requires `aria-label` for screen reader support
520
+ - `ButtonGroup` uses `role="group"` with proper ARIA attributes
521
+ - Focus indicators follow WCAG guidelines
522
+ - Disabled buttons are properly announced to assistive technology
523
+ - Loading state is communicated via `aria-busy` attribute
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xsolla/xui-button",
3
- "version": "0.148.0",
3
+ "version": "0.148.2-pr235.1777605920",
4
4
  "main": "./web/index.js",
5
5
  "module": "./web/index.mjs",
6
6
  "types": "./web/index.d.ts",
@@ -13,9 +13,9 @@
13
13
  "test:coverage": "vitest run --coverage"
14
14
  },
15
15
  "dependencies": {
16
- "@xsolla/xui-core": "0.148.0",
17
- "@xsolla/xui-icons": "0.148.0",
18
- "@xsolla/xui-primitives-core": "0.148.0"
16
+ "@xsolla/xui-core": "0.148.2-pr235.1777605920",
17
+ "@xsolla/xui-icons": "0.148.2-pr235.1777605920",
18
+ "@xsolla/xui-primitives-core": "0.148.2-pr235.1777605920"
19
19
  },
20
20
  "peerDependencies": {
21
21
  "react": ">=16.8.0",
package/web/index.js CHANGED
@@ -226,6 +226,8 @@ var Box = import_react2.default.forwardRef(
226
226
  as,
227
227
  src,
228
228
  alt,
229
+ onError,
230
+ onLoad,
229
231
  type,
230
232
  disabled,
231
233
  id,
@@ -239,6 +241,8 @@ var Box = import_react2.default.forwardRef(
239
241
  {
240
242
  src,
241
243
  alt: alt || "",
244
+ onError,
245
+ onLoad,
242
246
  style: {
243
247
  display: "block",
244
248
  objectFit: "cover",