Skip to content

Commit 3ab00d1

Browse files
authored
Merge pull request #75 from fleetbase/feature/menu-item-shortcuts-description
feat(menu-service): auto-register shortcuts as first-class header men…
2 parents de3157b + dc4455e commit 3ab00d1

File tree

3 files changed

+143
-12
lines changed

3 files changed

+143
-12
lines changed

addon/contracts/menu-item.js

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import BaseContract from './base-contract';
22
import ExtensionComponent from './extension-component';
33
import { dasherize } from '@ember/string';
4+
import { isArray } from '@ember/array';
45
import isObject from '../utils/is-object';
56

67
/**
@@ -123,6 +124,11 @@ export default class MenuItem extends BaseContract {
123124
// plain object with at minimum { title, route } and optionally
124125
// { icon, iconPrefix, id }. Optional – defaults to null.
125126
this.shortcuts = definition.shortcuts || null;
127+
128+
// An array of string tags used to improve search discoverability in
129+
// the overflow dropdown. e.g. ['logistics', 'tracking', 'fleet'].
130+
// Optional – defaults to null.
131+
this.tags = isArray(definition.tags) ? definition.tags : definition.tags ? [definition.tags] : null;
126132
} else {
127133
// Handle string title with optional route (chaining pattern)
128134
this.title = titleOrDefinition;
@@ -178,6 +184,7 @@ export default class MenuItem extends BaseContract {
178184
// ── Phase 2 additions ──────────────────────────────────────────
179185
this.description = null;
180186
this.shortcuts = null;
187+
this.tags = null;
181188
}
182189

183190
// Call setup() to trigger validation after properties are set
@@ -388,14 +395,40 @@ export default class MenuItem extends BaseContract {
388395
}
389396

390397
/**
391-
* Set an array of shortcut items displayed beneath the extension in the
392-
* multi-column overflow dropdown. Each shortcut is a plain object:
398+
* Set an array of shortcut items displayed as independent sibling cards in
399+
* the multi-column overflow dropdown (AWS Console style). Each shortcut
400+
* supports the full MenuItem property surface:
401+
*
402+
* Required:
403+
* title {String} Display label
404+
*
405+
* Routing:
406+
* route {String} Ember route name
407+
* queryParams {Object}
408+
* routeParams {Array}
409+
*
410+
* Identity:
411+
* id {String} Explicit id (auto-dasherized from title if omitted)
412+
* slug {String} URL slug (falls back to id)
413+
*
414+
* Icons:
415+
* icon {String} FontAwesome icon name
416+
* iconPrefix {String} FA prefix (e.g. 'far', 'fab')
417+
* iconSize {String} FA size string
418+
* iconClass {String} Extra CSS class on the icon element
419+
* iconComponent {String} Lazy-loaded engine component path
420+
* iconComponentOptions {Object}
393421
*
394-
* { title, route, icon?, iconPrefix?, id? }
422+
* Metadata:
423+
* description {String} Short description shown in the card
424+
* tags {String[]} Search tags
395425
*
396-
* Shortcuts are purely navigational – they do not support onClick handlers.
397-
* They are rendered as compact links inside the extension card in the
398-
* dropdown and can be individually pinned to the navigation bar.
426+
* Behaviour:
427+
* onClick {Function} Click handler (receives the shortcut item)
428+
* disabled {Boolean}
429+
*
430+
* Shortcuts are registered as first-class header menu items at boot time
431+
* and can be individually pinned to the navigation bar.
399432
*
400433
* @method withShortcuts
401434
* @param {Array<Object>} shortcuts Array of shortcut definition objects
@@ -404,16 +437,47 @@ export default class MenuItem extends BaseContract {
404437
* @example
405438
* new MenuItem('Fleet-Ops', 'console.fleet-ops')
406439
* .withShortcuts([
407-
* { title: 'Scheduler', route: 'console.fleet-ops.scheduler', icon: 'calendar' },
408-
* { title: 'Order Config', route: 'console.fleet-ops.order-configs', icon: 'gear' },
440+
* {
441+
* title: 'Scheduler',
442+
* route: 'console.fleet-ops.scheduler',
443+
* icon: 'calendar',
444+
* description: 'Plan and visualise driver schedules',
445+
* tags: ['schedule', 'calendar'],
446+
* },
447+
* {
448+
* title: 'Live Map',
449+
* route: 'console.fleet-ops',
450+
* iconComponent: 'fleet-ops@components/live-map-icon',
451+
* description: 'Real-time vehicle tracking',
452+
* },
409453
* ])
410454
*/
411455
withShortcuts(shortcuts) {
412-
this.shortcuts = Array.isArray(shortcuts) ? shortcuts : null;
456+
this.shortcuts = isArray(shortcuts) ? shortcuts : null;
413457
this._options.shortcuts = this.shortcuts;
414458
return this;
415459
}
416460

461+
/**
462+
* Set an array of string tags for this menu item.
463+
* Tags are matched against the search query in the overflow dropdown,
464+
* making items discoverable even when the query doesn't match the title
465+
* or description.
466+
*
467+
* @method withTags
468+
* @param {String|String[]} tags One tag string or an array of tag strings
469+
* @returns {MenuItem} This instance for chaining
470+
*
471+
* @example
472+
* new MenuItem('Fleet-Ops', 'console.fleet-ops')
473+
* .withTags(['logistics', 'tracking', 'fleet', 'drivers'])
474+
*/
475+
withTags(tags) {
476+
this.tags = isArray(tags) ? tags : tags ? [tags] : null;
477+
this._options.tags = this.tags;
478+
return this;
479+
}
480+
417481
/**
418482
* Add a single shortcut to the existing shortcuts array.
419483
* Creates the array if it does not yet exist.
@@ -428,7 +492,7 @@ export default class MenuItem extends BaseContract {
428492
* .addShortcut({ title: 'Order Config', route: 'console.fleet-ops.order-configs' })
429493
*/
430494
addShortcut(shortcut) {
431-
if (!Array.isArray(this.shortcuts)) {
495+
if (!isArray(this.shortcuts)) {
432496
this.shortcuts = [];
433497
}
434498
this.shortcuts = [...this.shortcuts, shortcut];
@@ -502,6 +566,9 @@ export default class MenuItem extends BaseContract {
502566
// Optional array of shortcut sub-links shown inside the extension card
503567
shortcuts: this.shortcuts,
504568

569+
// Optional array of string tags for search discoverability
570+
tags: this.tags,
571+
505572
// Indicator flag
506573
_isMenuItem: true,
507574

addon/services/universe/menu-service.js

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Evented from '@ember/object/evented';
33
import { tracked } from '@glimmer/tracking';
44
import { inject as service } from '@ember/service';
55
import { dasherize } from '@ember/string';
6-
import { A } from '@ember/array';
6+
import { A, isArray } from '@ember/array';
77
import MenuItem from '../../contracts/menu-item';
88
import MenuPanel from '../../contracts/menu-panel';
99

@@ -153,6 +153,70 @@ export default class MenuService extends Service.extend(Evented) {
153153
const menuItem = this.#normalizeMenuItem(itemOrTitle, route, options);
154154
this.registry.register('header', 'menu-item', menuItem.slug, menuItem);
155155

156+
// Auto-register each shortcut as a first-class header menu item so that
157+
// they appear in the customiser's "All Extensions" list and can be found
158+
// by id in allItems when pinned to the bar.
159+
if (isArray(menuItem.shortcuts)) {
160+
for (const sc of menuItem.shortcuts) {
161+
const scId = sc.id ?? dasherize(menuItem.id + '-sc-' + sc.title);
162+
const scSlug = sc.slug ?? scId;
163+
164+
// Build a first-class item that supports the full MenuItem
165+
// property surface. Each property falls back to the parent's
166+
// value so shortcuts inherit sensible defaults without the
167+
// consumer having to repeat them.
168+
const scItem = {
169+
// ── Identity ──────────────────────────────────────────────
170+
id: scId,
171+
slug: scSlug,
172+
title: sc.title,
173+
text: sc.text ?? sc.title,
174+
label: sc.label ?? sc.title,
175+
view: sc.view ?? scId,
176+
177+
// ── Routing ───────────────────────────────────────────────
178+
route: sc.route ?? menuItem.route,
179+
section: sc.section ?? null,
180+
queryParams: sc.queryParams ?? {},
181+
routeParams: sc.routeParams ?? [],
182+
183+
// ── Icons (full surface) ──────────────────────────────────
184+
icon: sc.icon ?? menuItem.icon,
185+
iconPrefix: sc.iconPrefix ?? menuItem.iconPrefix,
186+
iconSize: sc.iconSize ?? menuItem.iconSize ?? null,
187+
iconClass: sc.iconClass ?? menuItem.iconClass ?? null,
188+
iconComponent: sc.iconComponent ?? null,
189+
iconComponentOptions: sc.iconComponentOptions ?? {},
190+
191+
// ── Metadata ──────────────────────────────────────────────
192+
description: sc.description ?? null,
193+
// Shortcuts inherit parent tags so they surface under the
194+
// same search terms; shortcut-specific tags take precedence.
195+
tags: isArray(sc.tags) ? sc.tags : isArray(menuItem.tags) ? menuItem.tags : null,
196+
197+
// ── Behaviour ─────────────────────────────────────────────
198+
onClick: sc.onClick ?? null,
199+
disabled: sc.disabled ?? false,
200+
type: sc.type ?? 'default',
201+
buttonType: sc.buttonType ?? null,
202+
203+
// ── Styling ───────────────────────────────────────────────
204+
class: sc.class ?? null,
205+
inlineClass: sc.inlineClass ?? null,
206+
wrapperClass: sc.wrapperClass ?? null,
207+
208+
// ── Internal flags ────────────────────────────────────────
209+
_isShortcut: true,
210+
_parentTitle: menuItem.title,
211+
_parentId: menuItem.id,
212+
priority: (menuItem.priority ?? 0) + 1,
213+
_isMenuItem: true,
214+
};
215+
this.registry.register('header', 'menu-item', scSlug, scItem);
216+
this.trigger('menuItem.registered', scItem, 'header');
217+
}
218+
}
219+
156220
// Trigger event for backward compatibility
157221
this.trigger('menuItem.registered', menuItem, 'header');
158222
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fleetbase/ember-core",
3-
"version": "0.3.13",
3+
"version": "0.3.14",
44
"description": "Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.",
55
"keywords": [
66
"fleetbase-core",

0 commit comments

Comments
 (0)