or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

component-management.mddata-display-components.mdfeedback-components.mdform-components.mdindex.mdlayout-components.mdnavigation-components.mdvisual-effects.md

navigation-components.mddocs/

0

# Navigation Components

1

2

Menu systems and navigation components including dropdown menus and contextual navigation. These components provide user interface patterns for organizing and accessing application functionality.

3

4

## Capabilities

5

6

### Material Menu

7

8

Dropdown menu component with positioning, keyboard navigation, and smooth animations.

9

10

```javascript { .api }

11

/**

12

* Material Design menu component

13

* CSS Class: mdl-js-menu

14

* Widget: true

15

*/

16

interface MaterialMenu {

17

/**

18

* Display the menu at calculated position

19

* @param evt - Optional event object for positioning context

20

*/

21

show(evt?: Event): void;

22

23

/** Hide the menu with animation */

24

hide(): void;

25

26

/**

27

* Toggle menu visibility

28

* @param evt - Optional event object for positioning context

29

*/

30

toggle(evt?: Event): void;

31

}

32

```

33

34

**HTML Structure:**

35

36

```html

37

<!-- Basic menu -->

38

<button id="demo-menu-lower-left"

39

class="mdl-button mdl-js-button mdl-button--icon">

40

<i class="material-icons">more_vert</i>

41

</button>

42

43

<ul class="mdl-menu mdl-menu--bottom-left mdl-js-menu mdl-js-ripple-effect"

44

for="demo-menu-lower-left">

45

<li class="mdl-menu__item">Some Action</li>

46

<li class="mdl-menu__item">Another Action</li>

47

<li class="mdl-menu__item" disabled>Disabled Action</li>

48

<li class="mdl-menu__item">Yet Another Action</li>

49

</ul>

50

51

<!-- Menu with icons -->

52

<button id="demo-menu-with-icons"

53

class="mdl-button mdl-js-button mdl-button--icon">

54

<i class="material-icons">settings</i>

55

</button>

56

57

<ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu"

58

for="demo-menu-with-icons">

59

<li class="mdl-menu__item">

60

<i class="material-icons">edit</i>Edit

61

</li>

62

<li class="mdl-menu__item">

63

<i class="material-icons">delete</i>Delete

64

</li>

65

<li class="mdl-menu__item">

66

<i class="material-icons">share</i>Share

67

</li>

68

</ul>

69

```

70

71

**Menu Positioning:**

72

73

```html

74

<!-- Top positioning -->

75

<ul class="mdl-menu mdl-menu--top-left mdl-js-menu">

76

<ul class="mdl-menu mdl-menu--top-right mdl-js-menu">

77

78

<!-- Bottom positioning -->

79

<ul class="mdl-menu mdl-menu--bottom-left mdl-js-menu">

80

<ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu">

81

82

<!-- Unaligned (centered) -->

83

<ul class="mdl-menu mdl-menu--unaligned mdl-js-menu">

84

```

85

86

**Usage Examples:**

87

88

```javascript

89

// Access menu instance

90

const menuButton = document.querySelector('#demo-menu-lower-left');

91

const menu = document.querySelector('[for="demo-menu-lower-left"]').MaterialMenu;

92

93

// Show menu programmatically

94

menu.show();

95

96

// Hide menu programmatically

97

menu.hide();

98

99

// Toggle menu visibility

100

menuButton.addEventListener('click', (event) => {

101

menu.toggle(event);

102

});

103

104

// Handle menu item clicks

105

document.addEventListener('click', (event) => {

106

if (event.target.matches('.mdl-menu__item')) {

107

const menuItem = event.target;

108

const menuContainer = menuItem.closest('.mdl-js-menu');

109

110

console.log('Menu item clicked:', menuItem.textContent);

111

112

// Hide menu after selection

113

menuContainer.MaterialMenu.hide();

114

115

// Perform action based on menu item

116

handleMenuItemAction(menuItem);

117

}

118

});

119

120

function handleMenuItemAction(menuItem) {

121

const action = menuItem.textContent.trim();

122

123

switch (action) {

124

case 'Edit':

125

editItem();

126

break;

127

case 'Delete':

128

deleteItem();

129

break;

130

case 'Share':

131

shareItem();

132

break;

133

}

134

}

135

```

136

137

### Dynamic Menu Management

138

139

```javascript

140

// Create menu dynamically

141

function createMenu(buttonId, items) {

142

const button = document.getElementById(buttonId);

143

144

// Create menu element

145

const menu = document.createElement('ul');

146

menu.className = 'mdl-menu mdl-menu--bottom-left mdl-js-menu mdl-js-ripple-effect';

147

menu.setAttribute('for', buttonId);

148

149

// Add menu items

150

items.forEach(item => {

151

const li = document.createElement('li');

152

li.className = 'mdl-menu__item';

153

li.textContent = item.text;

154

155

if (item.disabled) {

156

li.setAttribute('disabled', '');

157

}

158

159

if (item.icon) {

160

const icon = document.createElement('i');

161

icon.className = 'material-icons';

162

icon.textContent = item.icon;

163

li.insertBefore(icon, li.firstChild);

164

}

165

166

menu.appendChild(li);

167

});

168

169

// Insert menu after button

170

button.parentNode.insertBefore(menu, button.nextSibling);

171

172

// Upgrade the menu

173

componentHandler.upgradeElement(menu);

174

175

return menu.MaterialMenu;

176

}

177

178

// Usage

179

const dynamicMenu = createMenu('my-button', [

180

{ text: 'Edit', icon: 'edit' },

181

{ text: 'Delete', icon: 'delete', disabled: true },

182

{ text: 'Share', icon: 'share' }

183

]);

184

```

185

186

### Keyboard Navigation

187

188

Menus support full keyboard navigation:

189

190

```javascript

191

// Keyboard navigation is automatic, but you can listen for events

192

document.addEventListener('keydown', (event) => {

193

const activeMenu = document.querySelector('.mdl-menu.is-visible');

194

195

if (activeMenu) {

196

const menu = activeMenu.MaterialMenu;

197

198

switch (event.key) {

199

case 'Escape':

200

menu.hide();

201

event.preventDefault();

202

break;

203

204

case 'ArrowUp':

205

navigateMenuUp(activeMenu);

206

event.preventDefault();

207

break;

208

209

case 'ArrowDown':

210

navigateMenuDown(activeMenu);

211

event.preventDefault();

212

break;

213

214

case 'Enter':

215

case ' ':

216

const focusedItem = activeMenu.querySelector('.mdl-menu__item:focus');

217

if (focusedItem && !focusedItem.hasAttribute('disabled')) {

218

focusedItem.click();

219

}

220

event.preventDefault();

221

break;

222

}

223

}

224

});

225

226

function navigateMenuUp(menu) {

227

const items = Array.from(menu.querySelectorAll('.mdl-menu__item:not([disabled])'));

228

const currentIndex = items.indexOf(document.activeElement);

229

const nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;

230

items[nextIndex].focus();

231

}

232

233

function navigateMenuDown(menu) {

234

const items = Array.from(menu.querySelectorAll('.mdl-menu__item:not([disabled])'));

235

const currentIndex = items.indexOf(document.activeElement);

236

const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;

237

items[nextIndex].focus();

238

}

239

```

240

241

## Menu Constants

242

243

```javascript { .api }

244

/**

245

* Material Menu constants and configuration

246

*/

247

interface MenuConstants {

248

/** Total transition duration in seconds */

249

TRANSITION_DURATION_SECONDS: 0.3;

250

251

/** Fraction of transition used for main animation */

252

TRANSITION_DURATION_FRACTION: 0.8;

253

254

/** Timeout before closing menu after selection */

255

CLOSE_TIMEOUT: 150;

256

}

257

258

/**

259

* Keyboard codes used by menu navigation

260

*/

261

interface MenuKeyCodes {

262

ENTER: 13;

263

ESCAPE: 27;

264

SPACE: 32;

265

UP_ARROW: 38;

266

DOWN_ARROW: 40;

267

}

268

269

/**

270

* Menu positioning classes

271

*/

272

interface MenuPositions {

273

BOTTOM_LEFT: 'mdl-menu--bottom-left';

274

BOTTOM_RIGHT: 'mdl-menu--bottom-right';

275

TOP_LEFT: 'mdl-menu--top-left';

276

TOP_RIGHT: 'mdl-menu--top-right';

277

UNALIGNED: 'mdl-menu--unaligned';

278

}

279

```

280

281

## Context Menus

282

283

Create context menus that appear on right-click:

284

285

```javascript

286

// Context menu implementation

287

function createContextMenu(targetSelector, menuItems) {

288

let contextMenu = null;

289

290

document.addEventListener('contextmenu', (event) => {

291

if (event.target.matches(targetSelector)) {

292

event.preventDefault();

293

294

// Remove existing context menu

295

if (contextMenu) {

296

contextMenu.remove();

297

}

298

299

// Create new context menu

300

contextMenu = document.createElement('ul');

301

contextMenu.className = 'mdl-menu mdl-js-menu mdl-menu--unaligned';

302

contextMenu.style.position = 'fixed';

303

contextMenu.style.left = event.clientX + 'px';

304

contextMenu.style.top = event.clientY + 'px';

305

306

// Add menu items

307

menuItems.forEach(item => {

308

const li = document.createElement('li');

309

li.className = 'mdl-menu__item';

310

li.textContent = item.text;

311

li.addEventListener('click', () => {

312

item.action(event.target);

313

contextMenu.MaterialMenu.hide();

314

});

315

contextMenu.appendChild(li);

316

});

317

318

document.body.appendChild(contextMenu);

319

componentHandler.upgradeElement(contextMenu);

320

321

// Show menu

322

contextMenu.MaterialMenu.show();

323

}

324

});

325

326

// Hide context menu on regular click

327

document.addEventListener('click', () => {

328

if (contextMenu) {

329

contextMenu.MaterialMenu.hide();

330

}

331

});

332

}

333

334

// Usage

335

createContextMenu('.data-item', [

336

{

337

text: 'Edit',

338

action: (target) => editDataItem(target)

339

},

340

{

341

text: 'Delete',

342

action: (target) => deleteDataItem(target)

343

},

344

{

345

text: 'Duplicate',

346

action: (target) => duplicateDataItem(target)

347

}

348

]);

349

```

350

351

## Menu State Management

352

353

```javascript

354

// Track menu states across the application

355

class MenuManager {

356

constructor() {

357

this.openMenus = new Set();

358

this.setupGlobalListeners();

359

}

360

361

setupGlobalListeners() {

362

// Track menu opening/closing

363

document.addEventListener('click', (event) => {

364

if (event.target.matches('.mdl-menu__item')) {

365

const menu = event.target.closest('.mdl-js-menu');

366

this.closeMenu(menu);

367

}

368

});

369

370

// Close all menus on escape

371

document.addEventListener('keydown', (event) => {

372

if (event.key === 'Escape') {

373

this.closeAllMenus();

374

}

375

});

376

377

// Close menus when clicking outside

378

document.addEventListener('click', (event) => {

379

if (!event.target.closest('.mdl-menu') &&

380

!event.target.matches('[class*="mdl-button"]')) {

381

this.closeAllMenus();

382

}

383

});

384

}

385

386

openMenu(menu) {

387

this.openMenus.add(menu);

388

menu.MaterialMenu.show();

389

}

390

391

closeMenu(menu) {

392

this.openMenus.delete(menu);

393

menu.MaterialMenu.hide();

394

}

395

396

closeAllMenus() {

397

this.openMenus.forEach(menu => {

398

menu.MaterialMenu.hide();

399

});

400

this.openMenus.clear();

401

}

402

403

toggleMenu(menu, event) {

404

if (this.openMenus.has(menu)) {

405

this.closeMenu(menu);

406

} else {

407

this.closeAllMenus(); // Close other menus first

408

this.openMenu(menu);

409

}

410

}

411

}

412

413

// Global menu manager instance

414

const menuManager = new MenuManager();

415

416

// Use with buttons

417

document.addEventListener('click', (event) => {

418

if (event.target.matches('[data-menu-trigger]')) {

419

const menuId = event.target.getAttribute('data-menu-trigger');

420

const menu = document.getElementById(menuId);

421

if (menu) {

422

menuManager.toggleMenu(menu, event);

423

}

424

}

425

});

426

```

427

428

## Menu Customization

429

430

```javascript

431

// Custom menu themes and styles

432

function applyMenuTheme(menu, theme) {

433

const themes = {

434

dark: {

435

backgroundColor: '#333',

436

color: '#fff',

437

itemHover: '#555'

438

},

439

light: {

440

backgroundColor: '#fff',

441

color: '#333',

442

itemHover: '#f5f5f5'

443

},

444

colored: {

445

backgroundColor: '#3f51b5',

446

color: '#fff',

447

itemHover: '#5c6bc0'

448

}

449

};

450

451

const themeConfig = themes[theme];

452

if (!themeConfig) return;

453

454

menu.style.backgroundColor = themeConfig.backgroundColor;

455

menu.style.color = themeConfig.color;

456

457

const items = menu.querySelectorAll('.mdl-menu__item');

458

items.forEach(item => {

459

item.addEventListener('mouseenter', () => {

460

item.style.backgroundColor = themeConfig.itemHover;

461

});

462

463

item.addEventListener('mouseleave', () => {

464

item.style.backgroundColor = '';

465

});

466

});

467

}

468

469

// Animation customization

470

function customMenuAnimation(menu) {

471

// Override default animation

472

menu.addEventListener('mdl-componentupgraded', () => {

473

const menuInstance = menu.MaterialMenu;

474

475

// Custom show animation

476

const originalShow = menuInstance.show;

477

menuInstance.show = function(evt) {

478

originalShow.call(this, evt);

479

480

// Add custom animation class

481

menu.classList.add('custom-menu-animation');

482

483

setTimeout(() => {

484

menu.classList.add('custom-menu-visible');

485

}, 10);

486

};

487

488

// Custom hide animation

489

const originalHide = menuInstance.hide;

490

menuInstance.hide = function() {

491

menu.classList.remove('custom-menu-visible');

492

493

setTimeout(() => {

494

originalHide.call(this);

495

menu.classList.remove('custom-menu-animation');

496

}, 200);

497

};

498

});

499

}

500

```