@fleetbase/ember-core 0.3.12 → 0.3.14

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.
@@ -1,6 +1,7 @@
1
1
  import BaseContract from './base-contract';
2
2
  import ExtensionComponent from './extension-component';
3
3
  import { dasherize } from '@ember/string';
4
+ import { isArray } from '@ember/array';
4
5
  import isObject from '../utils/is-object';
5
6
 
6
7
  /**
@@ -35,6 +36,16 @@ import isObject from '../utils/is-object';
35
36
  * .onClick((menuItem, router) => {
36
37
  * router.transitionTo('virtual', menuItem.slug);
37
38
  * })
39
+ *
40
+ * @example
41
+ * // Menu item with description and shortcuts (Phase 2)
42
+ * new MenuItem('Fleet-Ops', 'console.fleet-ops')
43
+ * .withIcon('route')
44
+ * .withDescription('Manage fleets, drivers, and live orders')
45
+ * .withShortcuts([
46
+ * { title: 'Scheduler', route: 'console.fleet-ops.scheduler', icon: 'calendar' },
47
+ * { title: 'Order Config', route: 'console.fleet-ops.order-configs', icon: 'gear' },
48
+ * ])
38
49
  */
39
50
  export default class MenuItem extends BaseContract {
40
51
  /**
@@ -102,6 +113,22 @@ export default class MenuItem extends BaseContract {
102
113
 
103
114
  // Nested items
104
115
  this.items = definition.items || null;
116
+
117
+ // ── Phase 2 additions ──────────────────────────────────────────
118
+ // A short human-readable description of the extension shown in the
119
+ // overflow dropdown. Optional – defaults to null.
120
+ this.description = definition.description || null;
121
+
122
+ // An array of shortcut items (sub-menu links) displayed beneath the
123
+ // extension name in the multi-column dropdown. Each shortcut is a
124
+ // plain object with at minimum { title, route } and optionally
125
+ // { icon, iconPrefix, id }. Optional – defaults to null.
126
+ 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;
105
132
  } else {
106
133
  // Handle string title with optional route (chaining pattern)
107
134
  this.title = titleOrDefinition;
@@ -153,6 +180,11 @@ export default class MenuItem extends BaseContract {
153
180
 
154
181
  // Nested items
155
182
  this.items = null;
183
+
184
+ // ── Phase 2 additions ──────────────────────────────────────────
185
+ this.description = null;
186
+ this.shortcuts = null;
187
+ this.tags = null;
156
188
  }
157
189
 
158
190
  // Call setup() to trigger validation after properties are set
@@ -342,6 +374,132 @@ export default class MenuItem extends BaseContract {
342
374
  return this;
343
375
  }
344
376
 
377
+ // ── Phase 2 builder methods ────────────────────────────────────────────
378
+
379
+ /**
380
+ * Set a short human-readable description for the extension.
381
+ * Displayed beneath the extension title in the overflow dropdown.
382
+ *
383
+ * @method withDescription
384
+ * @param {String} description Short description text
385
+ * @returns {MenuItem} This instance for chaining
386
+ *
387
+ * @example
388
+ * new MenuItem('Fleet-Ops', 'console.fleet-ops')
389
+ * .withDescription('Manage fleets, drivers, and live orders')
390
+ */
391
+ withDescription(description) {
392
+ this.description = description;
393
+ this._options.description = description;
394
+ return this;
395
+ }
396
+
397
+ /**
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}
421
+ *
422
+ * Metadata:
423
+ * description {String} Short description shown in the card
424
+ * tags {String[]} Search tags
425
+ *
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.
432
+ *
433
+ * @method withShortcuts
434
+ * @param {Array<Object>} shortcuts Array of shortcut definition objects
435
+ * @returns {MenuItem} This instance for chaining
436
+ *
437
+ * @example
438
+ * new MenuItem('Fleet-Ops', 'console.fleet-ops')
439
+ * .withShortcuts([
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
+ * },
453
+ * ])
454
+ */
455
+ withShortcuts(shortcuts) {
456
+ this.shortcuts = isArray(shortcuts) ? shortcuts : null;
457
+ this._options.shortcuts = this.shortcuts;
458
+ return this;
459
+ }
460
+
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
+
481
+ /**
482
+ * Add a single shortcut to the existing shortcuts array.
483
+ * Creates the array if it does not yet exist.
484
+ *
485
+ * @method addShortcut
486
+ * @param {Object} shortcut Shortcut definition object { title, route, icon?, iconPrefix?, id? }
487
+ * @returns {MenuItem} This instance for chaining
488
+ *
489
+ * @example
490
+ * new MenuItem('Fleet-Ops', 'console.fleet-ops')
491
+ * .addShortcut({ title: 'Scheduler', route: 'console.fleet-ops.scheduler' })
492
+ * .addShortcut({ title: 'Order Config', route: 'console.fleet-ops.order-configs' })
493
+ */
494
+ addShortcut(shortcut) {
495
+ if (!isArray(this.shortcuts)) {
496
+ this.shortcuts = [];
497
+ }
498
+ this.shortcuts = [...this.shortcuts, shortcut];
499
+ this._options.shortcuts = this.shortcuts;
500
+ return this;
501
+ }
502
+
345
503
  /**
346
504
  * Get the plain object representation
347
505
  *
@@ -401,6 +559,16 @@ export default class MenuItem extends BaseContract {
401
559
  // Nested items
402
560
  items: this.items,
403
561
 
562
+ // ── Phase 2 additions ──────────────────────────────────────────
563
+ // Optional short description shown in the overflow dropdown card
564
+ description: this.description,
565
+
566
+ // Optional array of shortcut sub-links shown inside the extension card
567
+ shortcuts: this.shortcuts,
568
+
569
+ // Optional array of string tags for search discoverability
570
+ tags: this.tags,
571
+
404
572
  // Indicator flag
405
573
  _isMenuItem: true,
406
574
 
@@ -3,7 +3,7 @@ import Evented from '@ember/object/evented';
3
3
  import { tracked } from '@glimmer/tracking';
4
4
  import { inject as service } from '@ember/service';
5
5
  import { dasherize } from '@ember/string';
6
- import { A } from '@ember/array';
6
+ import { A, isArray } from '@ember/array';
7
7
  import MenuItem from '../../contracts/menu-item';
8
8
  import MenuPanel from '../../contracts/menu-panel';
9
9
 
@@ -153,6 +153,70 @@ export default class MenuService extends Service.extend(Evented) {
153
153
  const menuItem = this.#normalizeMenuItem(itemOrTitle, route, options);
154
154
  this.registry.register('header', 'menu-item', menuItem.slug, menuItem);
155
155
 
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
+
156
220
  // Trigger event for backward compatibility
157
221
  this.trigger('menuItem.registered', menuItem, 'header');
158
222
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/ember-core",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.",
5
5
  "keywords": [
6
6
  "fleetbase-core",