@xsolla/xui-tabs 0.147.1 → 0.148.0-pr266.1777533632
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 +437 -0
- package/native/index.d.mts +3 -1
- package/native/index.d.ts +3 -1
- package/native/index.js +10 -5
- package/native/index.js.map +1 -1
- package/native/index.mjs +10 -5
- package/native/index.mjs.map +1 -1
- package/package.json +4 -4
- package/web/index.d.mts +3 -1
- package/web/index.d.ts +3 -1
- package/web/index.js +10 -5
- package/web/index.js.map +1 -1
- package/web/index.mjs +10 -5
- package/web/index.mjs.map +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
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 Segmented Tabs
|
|
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.
|
|
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: 'Log in' },
|
|
173
|
+
{ id: 'tab2', label: 'Sign up' },
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<Tabs
|
|
178
|
+
tabs={tabs}
|
|
179
|
+
activeTabId={activeTab}
|
|
180
|
+
onChange={setActiveTab}
|
|
181
|
+
variant="segmented"
|
|
182
|
+
stretched
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Anatomy
|
|
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
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import * as React from 'react';
|
|
224
|
+
import { Tabs } from '@xsolla/xui-tabs';
|
|
225
|
+
|
|
226
|
+
export default function DisabledTab() {
|
|
227
|
+
const [activeTab, setActiveTab] = React.useState('active');
|
|
228
|
+
|
|
229
|
+
const tabs = [
|
|
230
|
+
{ id: 'active', label: 'Active Tab' },
|
|
231
|
+
{ id: 'disabled', label: 'Disabled Tab', disabled: true },
|
|
232
|
+
{ id: 'another', label: 'Another Tab' },
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<Tabs
|
|
237
|
+
tabs={tabs}
|
|
238
|
+
activeTabId={activeTab}
|
|
239
|
+
onChange={setActiveTab}
|
|
240
|
+
/>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Manual Activation
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
import * as React from 'react';
|
|
249
|
+
import { Tabs } from '@xsolla/xui-tabs';
|
|
250
|
+
|
|
251
|
+
export default function ManualActivation() {
|
|
252
|
+
const [activeTab, setActiveTab] = React.useState('tab1');
|
|
253
|
+
|
|
254
|
+
const tabs = [
|
|
255
|
+
{ id: 'tab1', label: 'First' },
|
|
256
|
+
{ id: 'tab2', label: 'Second' },
|
|
257
|
+
{ id: 'tab3', label: 'Third' },
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<div>
|
|
262
|
+
<p>Use Arrow keys to navigate, Enter/Space to activate</p>
|
|
263
|
+
<Tabs
|
|
264
|
+
tabs={tabs}
|
|
265
|
+
activeTabId={activeTab}
|
|
266
|
+
onChange={setActiveTab}
|
|
267
|
+
activateOnFocus={false}
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Full Example with Content
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
import * as React from 'react';
|
|
278
|
+
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
|
+
|
|
285
|
+
const tabs = [
|
|
286
|
+
{ id: 'dashboard', label: 'Dashboard', icon: <LayoutDashboard size={16} /> },
|
|
287
|
+
{ id: 'analytics', label: 'Analytics', icon: <BarChart3 size={16} />, counter: 5 },
|
|
288
|
+
{ id: 'reports', label: 'Reports', icon: <FileText size={16} /> },
|
|
289
|
+
{ id: 'settings', label: 'Settings', icon: <Settings size={16} /> },
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div style={{ width: '100%' }}>
|
|
294
|
+
<Tabs
|
|
295
|
+
id="main-tabs"
|
|
296
|
+
tabs={tabs}
|
|
297
|
+
activeTabId={activeTab}
|
|
298
|
+
onChange={setActiveTab}
|
|
299
|
+
size="md"
|
|
300
|
+
/>
|
|
301
|
+
|
|
302
|
+
<div style={{ padding: 24, border: '1px solid #eee', borderTop: 'none' }}>
|
|
303
|
+
<TabPanel id="dashboard" tabsId="main-tabs" hidden={activeTab !== 'dashboard'}>
|
|
304
|
+
<h3>Dashboard</h3>
|
|
305
|
+
<p>Welcome to your dashboard overview.</p>
|
|
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>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
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
|
+
## Accessibility
|
|
422
|
+
|
|
423
|
+
- Implements WAI-ARIA tablist pattern
|
|
424
|
+
- Uses `role="tablist"`, `role="tab"`, `role="tabpanel"`
|
|
425
|
+
- `aria-selected` indicates active tab
|
|
426
|
+
- `aria-controls` links tabs to panels
|
|
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`
|
|
431
|
+
|
|
432
|
+
## Platform Support
|
|
433
|
+
|
|
434
|
+
Both variants support web and React Native platforms:
|
|
435
|
+
|
|
436
|
+
- **Web**: Segmented variant includes smooth sliding animation for the active indicator
|
|
437
|
+
- **React Native**: Falls back to static background without animation
|
package/native/index.d.mts
CHANGED
|
@@ -10,6 +10,8 @@ interface TabItemType {
|
|
|
10
10
|
icon?: React.ReactNode;
|
|
11
11
|
/** Optional counter to display after the label */
|
|
12
12
|
counter?: string | number;
|
|
13
|
+
/** Counter colour palette (line variant only). Defaults to "brand". */
|
|
14
|
+
counterPalette?: "brand" | "tertiary" | "default";
|
|
13
15
|
/** Optional badge to display */
|
|
14
16
|
badge?: boolean | string | number;
|
|
15
17
|
/** Whether the tab is disabled */
|
|
@@ -30,7 +32,7 @@ interface TabsProps extends ThemeOverrideProps {
|
|
|
30
32
|
variant?: "line" | "segmented";
|
|
31
33
|
/** Whether to align tabs to the left (only for line variant) */
|
|
32
34
|
alignLeft?: boolean;
|
|
33
|
-
/**
|
|
35
|
+
/** Segmented variant only: distribute items at equal width across the container */
|
|
34
36
|
stretched?: boolean;
|
|
35
37
|
/** Accessible label for the tab list */
|
|
36
38
|
"aria-label"?: string;
|
package/native/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ interface TabItemType {
|
|
|
10
10
|
icon?: React.ReactNode;
|
|
11
11
|
/** Optional counter to display after the label */
|
|
12
12
|
counter?: string | number;
|
|
13
|
+
/** Counter colour palette (line variant only). Defaults to "brand". */
|
|
14
|
+
counterPalette?: "brand" | "tertiary" | "default";
|
|
13
15
|
/** Optional badge to display */
|
|
14
16
|
badge?: boolean | string | number;
|
|
15
17
|
/** Whether the tab is disabled */
|
|
@@ -30,7 +32,7 @@ interface TabsProps extends ThemeOverrideProps {
|
|
|
30
32
|
variant?: "line" | "segmented";
|
|
31
33
|
/** Whether to align tabs to the left (only for line variant) */
|
|
32
34
|
alignLeft?: boolean;
|
|
33
|
-
/**
|
|
35
|
+
/** Segmented variant only: distribute items at equal width across the container */
|
|
34
36
|
stretched?: boolean;
|
|
35
37
|
/** Accessible label for the tab list */
|
|
36
38
|
"aria-label"?: string;
|
package/native/index.js
CHANGED
|
@@ -573,8 +573,7 @@ var Tabs = ({
|
|
|
573
573
|
flexDirection: "row",
|
|
574
574
|
alignItems: "flex-end",
|
|
575
575
|
justifyContent: alignLeft ? "flex-start" : "center",
|
|
576
|
-
width:
|
|
577
|
-
alignSelf: !stretched && !isWeb ? "flex-start" : void 0,
|
|
576
|
+
width: "100%",
|
|
578
577
|
height: lineStyles.height,
|
|
579
578
|
borderBottomWidth: 1,
|
|
580
579
|
borderBottomColor: theme.colors.border.secondary,
|
|
@@ -592,7 +591,9 @@ var Tabs = ({
|
|
|
592
591
|
const handleFocus = () => {
|
|
593
592
|
setFocusedIndex(index);
|
|
594
593
|
};
|
|
595
|
-
const textColor = isDisabled ? theme.colors.
|
|
594
|
+
const textColor = isDisabled ? theme.colors.control.text.disable : theme.colors.content.primary;
|
|
595
|
+
const counterPaletteColor = tab.counterPalette === "default" ? theme.colors.content.primary : tab.counterPalette === "tertiary" ? theme.colors.content.tertiary : theme.colors.content.brand.primary;
|
|
596
|
+
const counterColor = isDisabled ? theme.colors.control.text.disable : counterPaletteColor;
|
|
596
597
|
const borderBottomColor = isActive ? theme.colors.border.primary : "transparent";
|
|
597
598
|
const borderBottomWidth = isActive ? 2 : 0;
|
|
598
599
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
@@ -620,6 +621,8 @@ var Tabs = ({
|
|
|
620
621
|
justifyContent: "center",
|
|
621
622
|
gap: lineStyles.gap,
|
|
622
623
|
position: "relative",
|
|
624
|
+
borderTopLeftRadius: 2,
|
|
625
|
+
borderTopRightRadius: 2,
|
|
623
626
|
borderBottomWidth,
|
|
624
627
|
borderBottomColor,
|
|
625
628
|
borderStyle: isActive ? "solid" : "none",
|
|
@@ -640,15 +643,17 @@ var Tabs = ({
|
|
|
640
643
|
{
|
|
641
644
|
color: textColor,
|
|
642
645
|
fontSize: lineStyles.fontSize,
|
|
643
|
-
|
|
646
|
+
lineHeight: lineStyles.lineHeight,
|
|
647
|
+
fontWeight: "500",
|
|
644
648
|
children: tab.label
|
|
645
649
|
}
|
|
646
650
|
),
|
|
647
651
|
tab.counter !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Box, { marginLeft: 2, "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
648
652
|
Text,
|
|
649
653
|
{
|
|
650
|
-
color:
|
|
654
|
+
color: counterColor,
|
|
651
655
|
fontSize: lineStyles.fontSize,
|
|
656
|
+
lineHeight: lineStyles.lineHeight,
|
|
652
657
|
fontWeight: "500",
|
|
653
658
|
"aria-label": `${tab.counter} items`,
|
|
654
659
|
children: tab.counter
|