@xsolla/xui-tabs 0.141.0 → 0.141.1
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/native/index.js +4 -4
- package/native/index.js.map +1 -1
- package/native/index.mjs +4 -4
- package/native/index.mjs.map +1 -1
- package/package.json +4 -4
- package/web/index.js +9 -9
- package/web/index.js.map +1 -1
- package/web/index.mjs +9 -9
- package/web/index.mjs.map +1 -1
- package/README.md +0 -447
package/README.md
DELETED
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
# Tabs
|
|
2
|
-
|
|
3
|
-
A cross-platform React tabs component for organizing content into multiple panels that users can switch between. Implements WAI-ARIA tablist pattern for accessibility.
|
|
4
|
-
|
|
5
|
-
**Variants:**
|
|
6
|
-
- **Line** (default): Traditional underlined tabs with bottom border indicator
|
|
7
|
-
- **Segmented**: Button-group style segmented control with sliding active indicator
|
|
8
|
-
|
|
9
|
-
## Installation
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install @xsolla/xui-tabs
|
|
13
|
-
# or
|
|
14
|
-
yarn add @xsolla/xui-tabs
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Demo
|
|
18
|
-
|
|
19
|
-
### Basic Tabs
|
|
20
|
-
|
|
21
|
-
```tsx
|
|
22
|
-
import * as React from 'react';
|
|
23
|
-
import { Tabs, TabPanel } from '@xsolla/xui-tabs';
|
|
24
|
-
|
|
25
|
-
export default function BasicTabs() {
|
|
26
|
-
const [activeTab, setActiveTab] = React.useState('tab1');
|
|
27
|
-
|
|
28
|
-
const tabs = [
|
|
29
|
-
{ id: 'tab1', label: 'Overview' },
|
|
30
|
-
{ id: 'tab2', label: 'Features' },
|
|
31
|
-
{ id: 'tab3', label: 'Pricing' },
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div>
|
|
36
|
-
<Tabs
|
|
37
|
-
id="basic-tabs"
|
|
38
|
-
tabs={tabs}
|
|
39
|
-
activeTabId={activeTab}
|
|
40
|
-
onChange={setActiveTab}
|
|
41
|
-
/>
|
|
42
|
-
<TabPanel id="tab1" tabsId="basic-tabs" hidden={activeTab !== 'tab1'}>
|
|
43
|
-
<p>Overview content goes here.</p>
|
|
44
|
-
</TabPanel>
|
|
45
|
-
<TabPanel id="tab2" tabsId="basic-tabs" hidden={activeTab !== 'tab2'}>
|
|
46
|
-
<p>Features content goes here.</p>
|
|
47
|
-
</TabPanel>
|
|
48
|
-
<TabPanel id="tab3" tabsId="basic-tabs" hidden={activeTab !== 'tab3'}>
|
|
49
|
-
<p>Pricing content goes here.</p>
|
|
50
|
-
</TabPanel>
|
|
51
|
-
</div>
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Tabs with Icons
|
|
57
|
-
|
|
58
|
-
```tsx
|
|
59
|
-
import * as React from 'react';
|
|
60
|
-
import { Tabs } from '@xsolla/xui-tabs';
|
|
61
|
-
import { Home, User, Settings } from '@xsolla/xui-icons';
|
|
62
|
-
|
|
63
|
-
export default function TabsWithIcons() {
|
|
64
|
-
const [activeTab, setActiveTab] = React.useState('home');
|
|
65
|
-
|
|
66
|
-
const tabs = [
|
|
67
|
-
{ id: 'home', label: 'Home', icon: <Home size={16} /> },
|
|
68
|
-
{ id: 'profile', label: 'Profile', icon: <User size={16} /> },
|
|
69
|
-
{ id: 'settings', label: 'Settings', icon: <Settings size={16} /> },
|
|
70
|
-
];
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<Tabs
|
|
74
|
-
tabs={tabs}
|
|
75
|
-
activeTabId={activeTab}
|
|
76
|
-
onChange={setActiveTab}
|
|
77
|
-
/>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### Tabs with Counter
|
|
83
|
-
|
|
84
|
-
```tsx
|
|
85
|
-
import * as React from 'react';
|
|
86
|
-
import { Tabs } from '@xsolla/xui-tabs';
|
|
87
|
-
|
|
88
|
-
export default function TabsWithCounter() {
|
|
89
|
-
const [activeTab, setActiveTab] = React.useState('inbox');
|
|
90
|
-
|
|
91
|
-
const tabs = [
|
|
92
|
-
{ id: 'inbox', label: 'Inbox', counter: 12 },
|
|
93
|
-
{ id: 'sent', label: 'Sent', counter: 5 },
|
|
94
|
-
{ id: 'drafts', label: 'Drafts', counter: 3 },
|
|
95
|
-
{ id: 'spam', label: 'Spam', badge: true },
|
|
96
|
-
];
|
|
97
|
-
|
|
98
|
-
return (
|
|
99
|
-
<Tabs
|
|
100
|
-
tabs={tabs}
|
|
101
|
-
activeTabId={activeTab}
|
|
102
|
-
onChange={setActiveTab}
|
|
103
|
-
/>
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Tabs Sizes
|
|
109
|
-
|
|
110
|
-
```tsx
|
|
111
|
-
import * as React from 'react';
|
|
112
|
-
import { Tabs } from '@xsolla/xui-tabs';
|
|
113
|
-
|
|
114
|
-
export default function TabsSizes() {
|
|
115
|
-
const tabs = [
|
|
116
|
-
{ id: 'a', label: 'Tab A' },
|
|
117
|
-
{ id: 'b', label: 'Tab B' },
|
|
118
|
-
{ id: 'c', label: 'Tab C' },
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
|
|
123
|
-
<Tabs tabs={tabs} activeTabId="a" size="sm" />
|
|
124
|
-
<Tabs tabs={tabs} activeTabId="a" size="md" />
|
|
125
|
-
<Tabs tabs={tabs} activeTabId="a" size="lg" />
|
|
126
|
-
<Tabs tabs={tabs} activeTabId="a" size="xl" />
|
|
127
|
-
</div>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Segmented Variant
|
|
133
|
-
|
|
134
|
-
A button-group style segmented control with a sliding active indicator animation.
|
|
135
|
-
|
|
136
|
-
```tsx
|
|
137
|
-
import * as React from 'react';
|
|
138
|
-
import { Tabs } from '@xsolla/xui-tabs';
|
|
139
|
-
|
|
140
|
-
export default function SegmentedTabs() {
|
|
141
|
-
const [activeTab, setActiveTab] = React.useState('daily');
|
|
142
|
-
|
|
143
|
-
const tabs = [
|
|
144
|
-
{ id: 'daily', label: 'Daily' },
|
|
145
|
-
{ id: 'weekly', label: 'Weekly' },
|
|
146
|
-
{ id: 'monthly', label: 'Monthly' },
|
|
147
|
-
];
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<Tabs
|
|
151
|
-
tabs={tabs}
|
|
152
|
-
activeTabId={activeTab}
|
|
153
|
-
onChange={setActiveTab}
|
|
154
|
-
variant="segmented"
|
|
155
|
-
/>
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Stretched Tabs
|
|
161
|
-
|
|
162
|
-
Use the `stretched` prop to make tabs fill the entire container width.
|
|
163
|
-
|
|
164
|
-
```tsx
|
|
165
|
-
import * as React from 'react';
|
|
166
|
-
import { Tabs } from '@xsolla/xui-tabs';
|
|
167
|
-
|
|
168
|
-
export default function StretchedTabs() {
|
|
169
|
-
const [activeTab, setActiveTab] = React.useState('tab1');
|
|
170
|
-
|
|
171
|
-
const tabs = [
|
|
172
|
-
{ id: 'tab1', label: 'First' },
|
|
173
|
-
{ id: 'tab2', label: 'Second' },
|
|
174
|
-
{ id: 'tab3', label: 'Third' },
|
|
175
|
-
];
|
|
176
|
-
|
|
177
|
-
return (
|
|
178
|
-
<div style={{ width: '100%' }}>
|
|
179
|
-
{/* Line variant stretched */}
|
|
180
|
-
<Tabs
|
|
181
|
-
tabs={tabs}
|
|
182
|
-
activeTabId={activeTab}
|
|
183
|
-
onChange={setActiveTab}
|
|
184
|
-
stretched
|
|
185
|
-
/>
|
|
186
|
-
|
|
187
|
-
{/* Segmented variant stretched */}
|
|
188
|
-
<Tabs
|
|
189
|
-
tabs={tabs}
|
|
190
|
-
activeTabId={activeTab}
|
|
191
|
-
onChange={setActiveTab}
|
|
192
|
-
variant="segmented"
|
|
193
|
-
stretched
|
|
194
|
-
/>
|
|
195
|
-
</div>
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## Anatomy
|
|
201
|
-
|
|
202
|
-
Import the components and compose them:
|
|
203
|
-
|
|
204
|
-
```jsx
|
|
205
|
-
import { Tabs, TabPanel } from '@xsolla/xui-tabs';
|
|
206
|
-
|
|
207
|
-
// Tab list
|
|
208
|
-
<Tabs
|
|
209
|
-
id="my-tabs" // Unique ID for ARIA
|
|
210
|
-
tabs={tabItems} // Array of tab definitions
|
|
211
|
-
activeTabId={activeId} // Currently active tab
|
|
212
|
-
onChange={handleChange} // Tab selection handler
|
|
213
|
-
size="md" // Size variant
|
|
214
|
-
variant="line" // Visual variant: "line" | "segmented"
|
|
215
|
-
stretched={false} // Fill container width
|
|
216
|
-
alignLeft={true} // Alignment (line variant only)
|
|
217
|
-
activateOnFocus={true} // Auto-activate on focus
|
|
218
|
-
/>
|
|
219
|
-
|
|
220
|
-
// Tab panels
|
|
221
|
-
<TabPanel
|
|
222
|
-
id="tab-id" // Matches tab.id
|
|
223
|
-
tabsId="my-tabs" // Parent Tabs id
|
|
224
|
-
hidden={!isActive} // Visibility control
|
|
225
|
-
>
|
|
226
|
-
Panel content
|
|
227
|
-
</TabPanel>
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## Examples
|
|
231
|
-
|
|
232
|
-
### Disabled Tab
|
|
233
|
-
|
|
234
|
-
```tsx
|
|
235
|
-
import * as React from 'react';
|
|
236
|
-
import { Tabs } from '@xsolla/xui-tabs';
|
|
237
|
-
|
|
238
|
-
export default function DisabledTab() {
|
|
239
|
-
const [activeTab, setActiveTab] = React.useState('active');
|
|
240
|
-
|
|
241
|
-
const tabs = [
|
|
242
|
-
{ id: 'active', label: 'Active Tab' },
|
|
243
|
-
{ id: 'disabled', label: 'Disabled Tab', disabled: true },
|
|
244
|
-
{ id: 'another', label: 'Another Tab' },
|
|
245
|
-
];
|
|
246
|
-
|
|
247
|
-
return (
|
|
248
|
-
<Tabs
|
|
249
|
-
tabs={tabs}
|
|
250
|
-
activeTabId={activeTab}
|
|
251
|
-
onChange={setActiveTab}
|
|
252
|
-
/>
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### Manual Activation
|
|
258
|
-
|
|
259
|
-
```tsx
|
|
260
|
-
import * as React from 'react';
|
|
261
|
-
import { Tabs } from '@xsolla/xui-tabs';
|
|
262
|
-
|
|
263
|
-
export default function ManualActivation() {
|
|
264
|
-
const [activeTab, setActiveTab] = React.useState('tab1');
|
|
265
|
-
|
|
266
|
-
const tabs = [
|
|
267
|
-
{ id: 'tab1', label: 'First' },
|
|
268
|
-
{ id: 'tab2', label: 'Second' },
|
|
269
|
-
{ id: 'tab3', label: 'Third' },
|
|
270
|
-
];
|
|
271
|
-
|
|
272
|
-
return (
|
|
273
|
-
<div>
|
|
274
|
-
<p>Use Arrow keys to navigate, Enter/Space to activate</p>
|
|
275
|
-
<Tabs
|
|
276
|
-
tabs={tabs}
|
|
277
|
-
activeTabId={activeTab}
|
|
278
|
-
onChange={setActiveTab}
|
|
279
|
-
activateOnFocus={false}
|
|
280
|
-
/>
|
|
281
|
-
</div>
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### Full Example with Content
|
|
287
|
-
|
|
288
|
-
```tsx
|
|
289
|
-
import * as React from 'react';
|
|
290
|
-
import { Tabs, TabPanel } from '@xsolla/xui-tabs';
|
|
291
|
-
import { Settings } from '@xsolla/xui-icons';
|
|
292
|
-
import { LayoutDashboard, BarChart3, FileText } from '@xsolla/xui-icons-base';
|
|
293
|
-
|
|
294
|
-
export default function FullTabsExample() {
|
|
295
|
-
const [activeTab, setActiveTab] = React.useState('dashboard');
|
|
296
|
-
|
|
297
|
-
const tabs = [
|
|
298
|
-
{ id: 'dashboard', label: 'Dashboard', icon: <LayoutDashboard size={16} /> },
|
|
299
|
-
{ id: 'analytics', label: 'Analytics', icon: <BarChart3 size={16} />, counter: 5 },
|
|
300
|
-
{ id: 'reports', label: 'Reports', icon: <FileText size={16} /> },
|
|
301
|
-
{ id: 'settings', label: 'Settings', icon: <Settings size={16} /> },
|
|
302
|
-
];
|
|
303
|
-
|
|
304
|
-
return (
|
|
305
|
-
<div style={{ width: '100%' }}>
|
|
306
|
-
<Tabs
|
|
307
|
-
id="main-tabs"
|
|
308
|
-
tabs={tabs}
|
|
309
|
-
activeTabId={activeTab}
|
|
310
|
-
onChange={setActiveTab}
|
|
311
|
-
size="md"
|
|
312
|
-
/>
|
|
313
|
-
|
|
314
|
-
<div style={{ padding: 24, border: '1px solid #eee', borderTop: 'none' }}>
|
|
315
|
-
<TabPanel id="dashboard" tabsId="main-tabs" hidden={activeTab !== 'dashboard'}>
|
|
316
|
-
<h3>Dashboard</h3>
|
|
317
|
-
<p>Welcome to your dashboard overview.</p>
|
|
318
|
-
</TabPanel>
|
|
319
|
-
|
|
320
|
-
<TabPanel id="analytics" tabsId="main-tabs" hidden={activeTab !== 'analytics'}>
|
|
321
|
-
<h3>Analytics</h3>
|
|
322
|
-
<p>View your analytics and metrics here.</p>
|
|
323
|
-
</TabPanel>
|
|
324
|
-
|
|
325
|
-
<TabPanel id="reports" tabsId="main-tabs" hidden={activeTab !== 'reports'}>
|
|
326
|
-
<h3>Reports</h3>
|
|
327
|
-
<p>Generate and view reports.</p>
|
|
328
|
-
</TabPanel>
|
|
329
|
-
|
|
330
|
-
<TabPanel id="settings" tabsId="main-tabs" hidden={activeTab !== 'settings'}>
|
|
331
|
-
<h3>Settings</h3>
|
|
332
|
-
<p>Configure your preferences.</p>
|
|
333
|
-
</TabPanel>
|
|
334
|
-
</div>
|
|
335
|
-
</div>
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
## API Reference
|
|
341
|
-
|
|
342
|
-
### Tabs
|
|
343
|
-
|
|
344
|
-
The tab list component.
|
|
345
|
-
|
|
346
|
-
**Tabs Props:**
|
|
347
|
-
|
|
348
|
-
| Prop | Type | Default | Description |
|
|
349
|
-
| :--- | :--- | :------ | :---------- |
|
|
350
|
-
| tabs | `TabItemType[]` | - | **Required.** Array of tab definitions. |
|
|
351
|
-
| activeTabId | `string` | - | Currently active tab ID. |
|
|
352
|
-
| onChange | `(id: string) => void` | - | Callback when tab selection changes. |
|
|
353
|
-
| size | `"xl" \| "lg" \| "md" \| "sm"` | `"md"` | Size of the tabs. |
|
|
354
|
-
| variant | `"line" \| "segmented"` | `"line"` | Visual variant. Line shows underlined tabs; segmented shows button-group style. |
|
|
355
|
-
| stretched | `boolean` | `false` | Whether to stretch tabs to fill container width. |
|
|
356
|
-
| alignLeft | `boolean` | `true` | Left vs center alignment (line variant only). |
|
|
357
|
-
| activateOnFocus | `boolean` | `true` | Auto-activate tab on focus. |
|
|
358
|
-
| id | `string` | - | Unique ID for ARIA association. |
|
|
359
|
-
| aria-label | `string` | - | Accessible label for tab list. |
|
|
360
|
-
| aria-labelledby | `string` | - | ID of labeling element. |
|
|
361
|
-
| testID | `string` | - | Test identifier. |
|
|
362
|
-
|
|
363
|
-
**TabItemType:**
|
|
364
|
-
|
|
365
|
-
```typescript
|
|
366
|
-
interface TabItemType {
|
|
367
|
-
id: string; // Unique tab identifier
|
|
368
|
-
label: string; // Display label
|
|
369
|
-
icon?: ReactNode; // Optional icon
|
|
370
|
-
counter?: string | number; // Optional counter badge
|
|
371
|
-
badge?: boolean | string | number; // Optional badge
|
|
372
|
-
disabled?: boolean; // Whether tab is disabled
|
|
373
|
-
"aria-label"?: string; // Custom accessible label
|
|
374
|
-
}
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
---
|
|
378
|
-
|
|
379
|
-
### TabPanel
|
|
380
|
-
|
|
381
|
-
Container for tab content.
|
|
382
|
-
|
|
383
|
-
**TabPanel Props:**
|
|
384
|
-
|
|
385
|
-
| Prop | Type | Default | Description |
|
|
386
|
-
| :--- | :--- | :------ | :---------- |
|
|
387
|
-
| id | `string` | - | **Required.** Matches corresponding tab.id. |
|
|
388
|
-
| tabsId | `string` | - | **Required.** Parent Tabs component id. |
|
|
389
|
-
| hidden | `boolean` | - | Whether panel is hidden. |
|
|
390
|
-
| children | `ReactNode` | - | Panel content. |
|
|
391
|
-
| aria-label | `string` | - | Accessible label. |
|
|
392
|
-
| testID | `string` | - | Test identifier. |
|
|
393
|
-
|
|
394
|
-
## Keyboard Navigation
|
|
395
|
-
|
|
396
|
-
| Key | Action |
|
|
397
|
-
| :-- | :----- |
|
|
398
|
-
| Arrow Right/Down | Move to next enabled tab |
|
|
399
|
-
| Arrow Left/Up | Move to previous enabled tab |
|
|
400
|
-
| Home | Jump to first enabled tab |
|
|
401
|
-
| End | Jump to last enabled tab |
|
|
402
|
-
| Enter/Space | Activate tab (when activateOnFocus is false) |
|
|
403
|
-
|
|
404
|
-
## Theming
|
|
405
|
-
|
|
406
|
-
Tabs uses the design system theme for colors:
|
|
407
|
-
|
|
408
|
-
### Line Variant
|
|
409
|
-
|
|
410
|
-
```typescript
|
|
411
|
-
// Colors accessed via theme
|
|
412
|
-
theme.colors.content.primary // Active tab text
|
|
413
|
-
theme.colors.content.tertiary // Inactive tab text
|
|
414
|
-
theme.colors.border.primary // Active tab indicator (bottom border)
|
|
415
|
-
theme.colors.border.secondary // Container bottom border
|
|
416
|
-
theme.colors.overlay.mono // Hover background
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
### Segmented Variant
|
|
420
|
-
|
|
421
|
-
```typescript
|
|
422
|
-
// Colors accessed via theme
|
|
423
|
-
theme.colors.control.segmented.bg // Container background
|
|
424
|
-
theme.colors.control.segmented.bgHover // Tab hover background
|
|
425
|
-
theme.colors.control.segmented.bgActive // Active tab indicator background
|
|
426
|
-
theme.colors.control.segmented.textActive // Active tab text color
|
|
427
|
-
theme.colors.control.text.primary // Inactive tab text
|
|
428
|
-
theme.colors.control.text.disable // Disabled tab text
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
## Accessibility
|
|
432
|
-
|
|
433
|
-
- Implements WAI-ARIA tablist pattern
|
|
434
|
-
- Uses `role="tablist"`, `role="tab"`, `role="tabpanel"`
|
|
435
|
-
- `aria-selected` indicates active tab
|
|
436
|
-
- `aria-controls` links tabs to panels
|
|
437
|
-
- `aria-labelledby` links panels to tabs
|
|
438
|
-
- Full keyboard navigation support
|
|
439
|
-
- Focus management follows ARIA best practices
|
|
440
|
-
- Segmented variant sliding indicator is marked with `aria-hidden`
|
|
441
|
-
|
|
442
|
-
## Platform Support
|
|
443
|
-
|
|
444
|
-
Both variants support web and React Native platforms:
|
|
445
|
-
|
|
446
|
-
- **Web**: Segmented variant includes smooth sliding animation for the active indicator
|
|
447
|
-
- **React Native**: Falls back to static background without animation
|