0
# Editable Tabs
1
2
Interactive tab management with add/remove functionality and customizable controls. Editable tabs allow users to dynamically create and destroy tabs at runtime, providing a flexible interface for document editing, multi-view applications, and dynamic content management.
3
4
## Capabilities
5
6
### Editable Configuration
7
8
Comprehensive configuration for interactive tab management functionality.
9
10
```typescript { .api }
11
/**
12
* Configuration for editable tabs functionality
13
* Enables dynamic tab creation and removal with customizable UI
14
*/
15
interface EditableConfig {
16
/**
17
* Callback function handling both add and remove operations
18
* @param type - Type of edit operation ('add' or 'remove')
19
* @param info - Context information about the operation
20
*/
21
onEdit: (
22
type: 'add' | 'remove',
23
info: {
24
key?: string;
25
event: React.MouseEvent | React.KeyboardEvent
26
}
27
) => void;
28
29
/** Whether to show the add button in the tab bar */
30
showAdd?: boolean;
31
32
/** Custom icon for remove buttons on closable tabs */
33
removeIcon?: React.ReactNode;
34
35
/** Custom icon for the add button */
36
addIcon?: React.ReactNode;
37
}
38
39
/**
40
* Edit operation context information
41
*/
42
interface EditInfo {
43
/** Tab key for remove operations (undefined for add operations) */
44
key?: string;
45
/** The mouse or keyboard event that triggered the operation */
46
event: React.MouseEvent | React.KeyboardEvent;
47
}
48
```
49
50
**Usage Examples:**
51
52
```typescript
53
import Tabs from "rc-tabs";
54
import { PlusOutlined, CloseOutlined } from "@ant-design/icons";
55
56
function EditableTabs() {
57
const [items, setItems] = useState([
58
{ key: '1', label: 'Tab 1', children: <div>Content 1</div>, closable: true },
59
{ key: '2', label: 'Tab 2', children: <div>Content 2</div>, closable: true },
60
]);
61
const [activeKey, setActiveKey] = useState('1');
62
63
const handleEdit = (type: 'add' | 'remove', info: { key?: string }) => {
64
if (type === 'add') {
65
const newKey = `tab-${Date.now()}`;
66
const newTab = {
67
key: newKey,
68
label: `New Tab ${items.length + 1}`,
69
children: <div>New tab content</div>,
70
closable: true,
71
};
72
setItems([...items, newTab]);
73
setActiveKey(newKey);
74
} else if (type === 'remove' && info.key) {
75
const newItems = items.filter(item => item.key !== info.key);
76
setItems(newItems);
77
78
// If removing active tab, switch to another tab
79
if (activeKey === info.key && newItems.length > 0) {
80
setActiveKey(newItems[0].key);
81
}
82
}
83
};
84
85
return (
86
<Tabs
87
activeKey={activeKey}
88
onChange={setActiveKey}
89
items={items}
90
editable={{
91
onEdit: handleEdit,
92
showAdd: true,
93
addIcon: <PlusOutlined />,
94
removeIcon: <CloseOutlined />,
95
}}
96
/>
97
);
98
}
99
```
100
101
### Add Tab Functionality
102
103
Dynamic tab creation with customizable add button and behavior.
104
105
```typescript { .api }
106
/**
107
* Add tab configuration and behavior
108
*/
109
interface AddTabConfig {
110
/** Whether to display the add button in tab bar */
111
showAdd?: boolean;
112
/** Custom icon for the add button */
113
addIcon?: React.ReactNode;
114
/** Callback for add operations */
115
onAdd?: (event: React.MouseEvent | React.KeyboardEvent) => void;
116
}
117
```
118
119
**Add Tab Examples:**
120
121
```typescript
122
// Basic add functionality
123
function BasicAddTabs() {
124
const [tabs, setTabs] = useState(initialTabs);
125
126
const addTab = (type: 'add' | 'remove', info: { event: Event }) => {
127
if (type === 'add') {
128
const newTab = {
129
key: `new-${Date.now()}`,
130
label: 'New Tab',
131
children: <div>Fresh content</div>,
132
closable: true,
133
};
134
setTabs(prev => [...prev, newTab]);
135
}
136
};
137
138
return (
139
<Tabs
140
items={tabs}
141
editable={{
142
onEdit: addTab,
143
showAdd: true, // Shows + button in tab bar
144
}}
145
/>
146
);
147
}
148
149
// Custom add button with confirmation
150
function ConfirmAddTabs() {
151
const [tabs, setTabs] = useState(initialTabs);
152
153
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
154
if (type === 'add') {
155
const name = prompt('Enter tab name:');
156
if (name) {
157
const newTab = {
158
key: `tab-${Date.now()}`,
159
label: name,
160
children: <div>{name} content</div>,
161
closable: true,
162
};
163
setTabs(prev => [...prev, newTab]);
164
}
165
}
166
};
167
168
return (
169
<Tabs
170
items={tabs}
171
editable={{
172
onEdit: handleEdit,
173
showAdd: true,
174
addIcon: <span>+ Add</span>, // Custom add button text
175
}}
176
/>
177
);
178
}
179
```
180
181
### Remove Tab Functionality
182
183
Dynamic tab removal with confirmation and cleanup options.
184
185
```typescript { .api }
186
/**
187
* Remove tab configuration and behavior
188
*/
189
interface RemoveTabConfig {
190
/** Custom icon for remove buttons */
191
removeIcon?: React.ReactNode;
192
/** Callback for remove operations */
193
onRemove?: (key: string, event: React.MouseEvent | React.KeyboardEvent) => void;
194
}
195
```
196
197
**Remove Tab Examples:**
198
199
```typescript
200
// Basic remove functionality
201
function BasicRemoveTabs() {
202
const [tabs, setTabs] = useState(initialTabs);
203
const [activeKey, setActiveKey] = useState('1');
204
205
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
206
if (type === 'remove' && info.key) {
207
setTabs(prev => prev.filter(tab => tab.key !== info.key));
208
209
// Handle active tab removal
210
if (activeKey === info.key) {
211
const remainingTabs = tabs.filter(tab => tab.key !== info.key);
212
setActiveKey(remainingTabs.length > 0 ? remainingTabs[0].key : '');
213
}
214
}
215
};
216
217
return (
218
<Tabs
219
activeKey={activeKey}
220
onChange={setActiveKey}
221
items={tabs.map(tab => ({ ...tab, closable: true }))}
222
editable={{
223
onEdit: handleEdit,
224
removeIcon: <CloseOutlined style={{ fontSize: 12 }} />,
225
}}
226
/>
227
);
228
}
229
230
// Remove with confirmation
231
function ConfirmRemoveTabs() {
232
const [tabs, setTabs] = useState(initialTabs);
233
234
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
235
if (type === 'remove' && info.key) {
236
const tabToRemove = tabs.find(tab => tab.key === info.key);
237
const confirmed = window.confirm(
238
`Are you sure you want to close "${tabToRemove?.label}"?`
239
);
240
241
if (confirmed) {
242
setTabs(prev => prev.filter(tab => tab.key !== info.key));
243
}
244
}
245
};
246
247
return (
248
<Tabs
249
items={tabs}
250
editable={{
251
onEdit: handleEdit,
252
removeIcon: <span>×</span>,
253
}}
254
/>
255
);
256
}
257
```
258
259
### Advanced Editable Patterns
260
261
Complex scenarios with validation, limits, and custom behaviors.
262
263
```typescript { .api }
264
/**
265
* Advanced editable tab patterns and constraints
266
*/
267
interface AdvancedEditableConfig {
268
/** Maximum number of tabs allowed */
269
maxTabs?: number;
270
/** Minimum number of tabs required */
271
minTabs?: number;
272
/** Validation function for tab operations */
273
validateOperation?: (type: 'add' | 'remove', context: any) => boolean;
274
/** Custom behavior for different tab types */
275
tabTypeHandlers?: Record<string, EditHandler>;
276
}
277
```
278
279
**Advanced Examples:**
280
281
```typescript
282
// Limited tabs with validation
283
function LimitedEditableTabs() {
284
const [tabs, setTabs] = useState(initialTabs);
285
const maxTabs = 5;
286
const minTabs = 1;
287
288
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
289
if (type === 'add') {
290
if (tabs.length >= maxTabs) {
291
alert(`Maximum ${maxTabs} tabs allowed`);
292
return;
293
}
294
295
const newTab = {
296
key: `tab-${Date.now()}`,
297
label: `Tab ${tabs.length + 1}`,
298
children: <div>Content {tabs.length + 1}</div>,
299
closable: tabs.length + 1 > minTabs, // Don't allow closing if at minimum
300
};
301
setTabs(prev => [...prev, newTab]);
302
303
} else if (type === 'remove' && info.key) {
304
if (tabs.length <= minTabs) {
305
alert(`Minimum ${minTabs} tab required`);
306
return;
307
}
308
309
setTabs(prev => prev.filter(tab => tab.key !== info.key));
310
}
311
};
312
313
return (
314
<Tabs
315
items={tabs}
316
editable={{
317
onEdit: handleEdit,
318
showAdd: tabs.length < maxTabs,
319
}}
320
/>
321
);
322
}
323
324
// Type-specific tab handling
325
function TypedEditableTabs() {
326
const [tabs, setTabs] = useState([
327
{ key: '1', type: 'document', label: 'Document 1', closable: true },
328
{ key: '2', type: 'settings', label: 'Settings', closable: false },
329
]);
330
331
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
332
if (type === 'add') {
333
const tabType = 'document'; // Could be dynamic
334
const newTab = {
335
key: `${tabType}-${Date.now()}`,
336
type: tabType,
337
label: `New ${tabType}`,
338
children: createContentByType(tabType),
339
closable: tabType !== 'settings', // Settings tabs can't be closed
340
};
341
setTabs(prev => [...prev, newTab]);
342
343
} else if (type === 'remove' && info.key) {
344
const tab = tabs.find(t => t.key === info.key);
345
346
if (tab?.type === 'settings') {
347
alert('Settings tabs cannot be closed');
348
return;
349
}
350
351
// Save document before closing
352
if (tab?.type === 'document') {
353
const shouldSave = window.confirm('Save document before closing?');
354
if (shouldSave) {
355
saveDocument(tab.key);
356
}
357
}
358
359
setTabs(prev => prev.filter(tab => tab.key !== info.key));
360
}
361
};
362
363
return (
364
<Tabs
365
items={tabs}
366
editable={{
367
onEdit: handleEdit,
368
showAdd: true,
369
addIcon: <span>+ New Document</span>,
370
}}
371
/>
372
);
373
}
374
```
375
376
### Keyboard Support
377
378
Keyboard shortcuts and accessibility for editable tabs.
379
380
```typescript { .api }
381
/**
382
* Keyboard interactions for editable tabs
383
* - Ctrl/Cmd + T: Add new tab (if showAdd is true)
384
* - Ctrl/Cmd + W: Close current tab (if closable)
385
* - Alt + Click: Alternative close action
386
*/
387
interface EditableKeyboardSupport {
388
addShortcut: 'Ctrl+T' | 'Cmd+T';
389
closeShortcut: 'Ctrl+W' | 'Cmd+W';
390
altCloseClick: 'Alt+Click';
391
}
392
```
393
394
**Keyboard Integration Example:**
395
396
```typescript
397
function KeyboardEditableTabs() {
398
const [tabs, setTabs] = useState(initialTabs);
399
const [activeKey, setActiveKey] = useState('1');
400
401
useEffect(() => {
402
const handleKeyboard = (e: KeyboardEvent) => {
403
if ((e.ctrlKey || e.metaKey) && e.key === 't') {
404
e.preventDefault();
405
addNewTab();
406
} else if ((e.ctrlKey || e.metaKey) && e.key === 'w') {
407
e.preventDefault();
408
closeCurrentTab();
409
}
410
};
411
412
document.addEventListener('keydown', handleKeyboard);
413
return () => document.removeEventListener('keydown', handleKeyboard);
414
}, [activeKey, tabs]);
415
416
const addNewTab = () => {
417
const newTab = {
418
key: `tab-${Date.now()}`,
419
label: 'New Tab',
420
children: <div>New content</div>,
421
closable: true,
422
};
423
setTabs(prev => [...prev, newTab]);
424
setActiveKey(newTab.key);
425
};
426
427
const closeCurrentTab = () => {
428
const currentTab = tabs.find(tab => tab.key === activeKey);
429
if (currentTab?.closable) {
430
handleEdit('remove', { key: activeKey, event: new KeyboardEvent('keydown') });
431
}
432
};
433
434
const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
435
// Standard edit handling...
436
};
437
438
return (
439
<Tabs
440
activeKey={activeKey}
441
onChange={setActiveKey}
442
items={tabs}
443
editable={{
444
onEdit: handleEdit,
445
showAdd: true,
446
}}
447
/>
448
);
449
}
450
```