@xsolla/xui-tabs 0.141.1 → 0.147.0-pr265.1777518947
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
|
@@ -565,8 +565,7 @@ var Tabs = ({
|
|
|
565
565
|
flexDirection: "row",
|
|
566
566
|
alignItems: "flex-end",
|
|
567
567
|
justifyContent: alignLeft ? "flex-start" : "center",
|
|
568
|
-
width:
|
|
569
|
-
alignSelf: !stretched && !isWeb ? "flex-start" : void 0,
|
|
568
|
+
width: "100%",
|
|
570
569
|
height: lineStyles.height,
|
|
571
570
|
borderBottomWidth: 1,
|
|
572
571
|
borderBottomColor: theme.colors.border.secondary,
|
|
@@ -584,7 +583,9 @@ var Tabs = ({
|
|
|
584
583
|
const handleFocus = () => {
|
|
585
584
|
setFocusedIndex(index);
|
|
586
585
|
};
|
|
587
|
-
const textColor = isDisabled ? theme.colors.
|
|
586
|
+
const textColor = isDisabled ? theme.colors.control.text.disable : theme.colors.content.primary;
|
|
587
|
+
const counterPaletteColor = tab.counterPalette === "default" ? theme.colors.content.primary : tab.counterPalette === "tertiary" ? theme.colors.content.tertiary : theme.colors.content.brand.primary;
|
|
588
|
+
const counterColor = isDisabled ? theme.colors.control.text.disable : counterPaletteColor;
|
|
588
589
|
const borderBottomColor = isActive ? theme.colors.border.primary : "transparent";
|
|
589
590
|
const borderBottomWidth = isActive ? 2 : 0;
|
|
590
591
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
@@ -612,6 +613,8 @@ var Tabs = ({
|
|
|
612
613
|
justifyContent: "center",
|
|
613
614
|
gap: lineStyles.gap,
|
|
614
615
|
position: "relative",
|
|
616
|
+
borderTopLeftRadius: 2,
|
|
617
|
+
borderTopRightRadius: 2,
|
|
615
618
|
borderBottomWidth,
|
|
616
619
|
borderBottomColor,
|
|
617
620
|
borderStyle: isActive ? "solid" : "none",
|
|
@@ -632,15 +635,17 @@ var Tabs = ({
|
|
|
632
635
|
{
|
|
633
636
|
color: textColor,
|
|
634
637
|
fontSize: lineStyles.fontSize,
|
|
635
|
-
|
|
638
|
+
lineHeight: lineStyles.lineHeight,
|
|
639
|
+
fontWeight: "500",
|
|
636
640
|
children: tab.label
|
|
637
641
|
}
|
|
638
642
|
),
|
|
639
643
|
tab.counter !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Box, { marginLeft: 2, "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
640
644
|
Text,
|
|
641
645
|
{
|
|
642
|
-
color:
|
|
646
|
+
color: counterColor,
|
|
643
647
|
fontSize: lineStyles.fontSize,
|
|
648
|
+
lineHeight: lineStyles.lineHeight,
|
|
644
649
|
fontWeight: "500",
|
|
645
650
|
"aria-label": `${tab.counter} items`,
|
|
646
651
|
children: tab.counter
|