@xsolla/xui-button 0.99.0 → 0.101.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.
Files changed (2) hide show
  1. package/README.md +501 -47
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -1,79 +1,533 @@
1
- # @xsolla/xui-button
1
+ # Button
2
2
 
3
- Accessible button components with multiple visual variants, sizes, and states.
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
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
+ npm install @xsolla/xui-button
9
+ # or
8
10
  yarn add @xsolla/xui-button
9
11
  ```
10
12
 
11
- ## Usage
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.
12
229
 
13
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';
14
311
  import { Button } from '@xsolla/xui-button';
312
+ import { Apple } from '@xsolla/xui-icons-brand';
15
313
 
16
- <Button variant="primary" tone="brand" onPress={() => console.log('clicked')}>
17
- Buy Now
18
- </Button>
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
+ }
19
325
  ```
20
326
 
21
- ## Components
327
+ ### Button with Divider
328
+
329
+ The `divider` prop controls whether a separator line appears between icons and content.
330
+
331
+ ```tsx
332
+ import * as React from 'react';
333
+ import { Button } from '@xsolla/xui-button';
334
+ import { ArrowRight } from '@xsolla/xui-icons-base';
335
+
336
+ export default function ButtonWithDivider() {
337
+ return (
338
+ <div style={{ display: 'flex', gap: 16 }}>
339
+ <Button iconRight={<ArrowRight />} divider>
340
+ With Divider
341
+ </Button>
342
+ <Button iconRight={<ArrowRight />} divider={false}>
343
+ No Divider
344
+ </Button>
345
+ </div>
346
+ );
347
+ }
348
+ ```
22
349
 
23
- - **Button** — Standard button with optional icons, sublabel, and loading state.
24
- - **IconButton** — Square icon-only button; requires `aria-label`.
25
- - **FlexButton** — Compact inline button designed for use inside modals and popups.
26
- - **ButtonGroup** — Container for grouping related buttons with optional description and error text.
350
+ ### Button with Custom Content
27
351
 
28
- ## Props
352
+ Use `customContent` to add badges, tags, or other elements inside the button.
353
+
354
+ ```tsx
355
+ import * as React from 'react';
356
+ import { Button } from '@xsolla/xui-button';
357
+ import { Tag } from '@xsolla/xui-tag';
358
+ import { ArrowRight } from '@xsolla/xui-icons-base';
359
+
360
+ export default function ButtonWithCustomContent() {
361
+ return (
362
+ <Button
363
+ iconRight={<ArrowRight />}
364
+ customContent={<Tag size="sm">5x</Tag>}
365
+ divider
366
+ >
367
+ Claim Reward
368
+ </Button>
369
+ );
370
+ }
371
+ ```
372
+
373
+ ### Tertiary Variant
374
+
375
+ The tertiary variant provides a minimal, text-like button style.
376
+
377
+ ```tsx
378
+ import * as React from 'react';
379
+ import { Button, IconButton } from '@xsolla/xui-button';
380
+ import { Settings } from '@xsolla/xui-icons-base';
381
+
382
+ export default function TertiaryButtons() {
383
+ return (
384
+ <div style={{ display: 'flex', gap: 16 }}>
385
+ <Button variant="tertiary">Learn More</Button>
386
+ <IconButton
387
+ variant="tertiary"
388
+ icon={<Settings />}
389
+ aria-label="Settings"
390
+ />
391
+ </div>
392
+ );
393
+ }
394
+ ```
395
+
396
+ ## API Reference
29
397
 
30
398
  ### Button
31
399
 
400
+ The main button component. Renders a semantic `<button>` element.
401
+
402
+ **Button Props:**
403
+
32
404
  | Prop | Type | Default | Description |
33
- |------|------|---------|-------------|
34
- | `variant` | `'primary' \| 'secondary' \| 'tertiary'` | `'primary'` | Visual style variant |
35
- | `tone` | `'brand' \| 'brandExtra' \| 'alert' \| 'mono'` | `'brand'` | Colour tone |
36
- | `size` | `'xl' \| 'lg' \| 'md' \| 'sm' \| 'xs'` | `'md'` | Button size |
37
- | `disabled` | `boolean` | `false` | Disables the button |
38
- | `loading` | `boolean` | `false` | Shows a spinner and blocks interaction |
39
- | `onPress` | `() => void` | | Click handler |
40
- | `iconLeft` | `React.ReactNode` | | Icon displayed on the left |
41
- | `iconRight` | `React.ReactNode` | | Icon displayed on the right |
42
- | `sublabel` | `string` | | Secondary inline text shown at reduced opacity |
43
- | `fullWidth` | `boolean` | `false` | Stretches button to fill its container |
405
+ | :--- | :--- | :------ | :---------- |
406
+ | children | `ReactNode` | - | The button label content. |
407
+ | variant | `"primary" \| "secondary" \| "tertiary"` | `"primary"` | The visual style variant. |
408
+ | tone | `"brand" \| "brandExtra" \| "alert" \| "mono"` | `"brand"` | The color tone of the button. |
409
+ | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | The size of the button. |
410
+ | disabled | `boolean` | `false` | Whether the button is disabled. |
411
+ | loading | `boolean` | `false` | Whether to show loading spinner. Disables interaction when true. |
412
+ | onPress | `() => void` | - | Callback fired when button is pressed. |
413
+ | iconLeft | `ReactNode` | - | Icon to display on the left side. |
414
+ | iconRight | `ReactNode` | - | Icon to display on the right side. |
415
+ | divider | `boolean` | `true` when icon present | Whether to show divider between icon and label. |
416
+ | sublabel | `string` | - | Secondary label text displayed inline with main label at 40% opacity. |
417
+ | labelIcon | `ReactNode` | - | Small icon displayed directly next to the label text. |
418
+ | labelAlignment | `"left" \| "center"` | `"center"` | Alignment of the label content within the button. |
419
+ | customContent | `ReactNode` | - | Custom content slot for badges, tags, or other elements. |
420
+ | fullWidth | `boolean` | `false` | Whether button should span full container width. |
421
+ | type | `"button" \| "submit" \| "reset"` | `"button"` | The HTML button type attribute. |
422
+ | aria-label | `string` | - | Accessible label for the button. |
423
+ | aria-describedby | `string` | - | ID of element that describes the button. |
424
+ | aria-expanded | `boolean` | - | Indicates if controlled element is expanded. |
425
+ | aria-haspopup | `boolean \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` | - | Indicates popup type triggered by button. |
426
+ | aria-pressed | `boolean \| "mixed"` | - | Indicates pressed state for toggle buttons. |
427
+ | aria-controls | `string` | - | ID of element controlled by this button. |
428
+ | testID | `string` | - | Test identifier for testing frameworks. |
429
+ | id | `string` | - | HTML id attribute. |
430
+
431
+ ---
44
432
 
45
433
  ### IconButton
46
434
 
435
+ A button variant that displays only an icon. Requires `aria-label` for accessibility.
436
+
437
+ **IconButton Props:**
438
+
47
439
  | Prop | Type | Default | Description |
48
- |------|------|---------|-------------|
49
- | `icon` | `React.ReactNode` | | Icon to display (required) |
50
- | `aria-label` | `string` | | Accessible label (required) |
51
- | `variant` | `'primary' \| 'secondary' \| 'tertiary'` | `'primary'` | Visual style variant |
52
- | `tone` | `'brand' \| 'brandExtra' \| 'alert' \| 'mono'` | `'brand'` | Colour tone |
53
- | `size` | `'xl' \| 'lg' \| 'md' \| 'sm' \| 'xs'` | `'md'` | Button size |
54
- | `disabled` | `boolean` | `false` | Disables the button |
55
- | `loading` | `boolean` | `false` | Shows a spinner and blocks interaction |
56
- | `onPress` | `() => void` | | Click handler |
440
+ | :--- | :--- | :------ | :---------- |
441
+ | icon | `ReactNode` | - | **Required.** The icon to display. |
442
+ | aria-label | `string` | - | **Required.** Accessible label for the button. |
443
+ | variant | `"primary" \| "secondary" \| "tertiary"` | `"primary"` | The visual style variant. |
444
+ | tone | `"brand" \| "brandExtra" \| "alert" \| "mono"` | `"brand"` | The color tone of the button. |
445
+ | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | The size of the button. |
446
+ | disabled | `boolean` | `false` | Whether the button is disabled. |
447
+ | loading | `boolean` | `false` | Whether to show loading spinner. |
448
+ | onPress | `() => void` | - | Callback fired when button is pressed. |
449
+ | type | `"button" \| "submit" \| "reset"` | `"button"` | The HTML button type attribute. |
450
+
451
+ ---
57
452
 
58
453
  ### FlexButton
59
454
 
455
+ A flexible button with more granular control over background and styling.
456
+
457
+ **FlexButton Props:**
458
+
60
459
  | Prop | Type | Default | Description |
61
- |------|------|---------|-------------|
62
- | `variant` | `'brand' \| 'primary' \| 'secondary' \| 'tertiary' \| 'brandExtra' \| 'inverse'` | `'brand'` | Visual style variant |
63
- | `size` | `'xl' \| 'lg' \| 'md' \| 'sm' \| 'xs'` | `'md'` | Button size |
64
- | `background` | `boolean` | `false` | Adds a filled background |
65
- | `disabled` | `boolean` | `false` | Disables the button |
66
- | `loading` | `boolean` | `false` | Shows a spinner and blocks interaction |
67
- | `iconLeft` | `React.ReactNode` | | Icon on the left |
68
- | `iconRight` | `React.ReactNode` | | Icon on the right |
69
- | `onPress` | `() => void` | | Click handler |
460
+ | :--- | :--- | :------ | :---------- |
461
+ | children | `ReactNode` | - | The button label content. |
462
+ | variant | `"brand" \| "primary" \| "secondary" \| "tertiary" \| "brandExtra" \| "inverse"` | `"brand"` | The visual style variant. |
463
+ | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | The size of the button. |
464
+ | background | `boolean` | `false` | Whether to show background fill. |
465
+ | disabled | `boolean` | `false` | Whether the button is disabled. |
466
+ | loading | `boolean` | `false` | Whether to show loading spinner. |
467
+ | iconLeft | `ReactNode` | - | Icon to display on the left side. |
468
+ | iconRight | `ReactNode` | - | Icon to display on the right side. |
469
+ | onPress | `() => void` | - | Callback fired when button is pressed. |
470
+ | type | `"button" \| "submit" \| "reset"` | `"button"` | The HTML button type attribute. |
471
+
472
+ ---
70
473
 
71
474
  ### ButtonGroup
72
475
 
476
+ A container for grouping related buttons together.
477
+
478
+ **ButtonGroup Props:**
479
+
73
480
  | Prop | Type | Default | Description |
74
- |------|------|---------|-------------|
75
- | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction |
76
- | `size` | `'xl' \| 'lg' \| 'md' \| 'sm' \| 'xs'` | `'md'` | Controls default gap between buttons |
77
- | `gap` | `number` | | Custom gap in pixels (overrides size default) |
78
- | `description` | `string` | | Helper text below the group |
79
- | `error` | `string` | | Error message below the group |
481
+ | :--- | :--- | :------ | :---------- |
482
+ | children | `ReactNode` | - | **Required.** Button children to group. |
483
+ | orientation | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction of the buttons. |
484
+ | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | Size applied to the group spacing. |
485
+ | gap | `number` | - | Custom gap between buttons (overrides size default). |
486
+ | description | `string` | - | Description text shown below the group. |
487
+ | error | `string` | - | Error message (replaces description when present). |
488
+ | aria-label | `string` | - | Accessible label for the button group. |
489
+ | aria-labelledby | `string` | - | ID of element labeling the group. |
490
+ | aria-describedby | `string` | - | ID of element describing the group. |
491
+ | id | `string` | - | HTML id attribute. |
492
+ | testID | `string` | - | Test identifier for testing frameworks. |
493
+
494
+ **ButtonGroup Gap Defaults:**
495
+
496
+ | Size | Vertical Gap | Horizontal Gap |
497
+ | :--- | :----------- | :------------- |
498
+ | xl | 16 | 16 |
499
+ | lg | 16 | 16 |
500
+ | md | 12 | 16 |
501
+ | sm | 8 | 12 |
502
+ | xs | 4 | 12 |
503
+
504
+ ## Theming
505
+
506
+ Button components use the design system theme for colors and sizing:
507
+
508
+ ```typescript
509
+ // Colors accessed via theme
510
+ theme.colors.control.[tone].[variant].bg // Background color
511
+ theme.colors.control.[tone].[variant].bgHover // Hover background
512
+ theme.colors.control.[tone].[variant].bgPress // Pressed background
513
+ theme.colors.control.[tone].[variant].text // Text color
514
+ theme.colors.control.[tone].[variant].border // Border color
515
+
516
+ // Sizing accessed via theme
517
+ theme.sizing.button(size).height
518
+ theme.sizing.button(size).paddingHorizontal
519
+ theme.sizing.button(size).fontSize
520
+ theme.sizing.button(size).iconSize
521
+
522
+ // Border radius
523
+ theme.radius.button
524
+ ```
525
+
526
+ ## Accessibility
527
+
528
+ - All buttons use semantic `<button>` elements
529
+ - `IconButton` requires `aria-label` for screen reader support
530
+ - `ButtonGroup` uses `role="group"` with proper ARIA attributes
531
+ - Focus indicators follow WCAG guidelines
532
+ - Disabled buttons are properly announced to assistive technology
533
+ - 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.99.0",
3
+ "version": "0.101.0",
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.99.0",
17
- "@xsolla/xui-icons": "0.99.0",
18
- "@xsolla/xui-primitives-core": "0.99.0"
16
+ "@xsolla/xui-core": "0.101.0",
17
+ "@xsolla/xui-icons": "0.101.0",
18
+ "@xsolla/xui-primitives-core": "0.101.0"
19
19
  },
20
20
  "peerDependencies": {
21
21
  "react": ">=16.8.0",