or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdanimation-transitions.mdcore-interface.mdeditable-tabs.mdindex.mdtab-configuration.md

editable-tabs.mddocs/

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

```