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