@nyaruka/temba-components 0.19.0 → 0.23.0

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.
Files changed (68) hide show
  1. package/.eslintrc.js +1 -0
  2. package/CHANGELOG.md +28 -0
  3. package/dist/28f45617.js +356 -0
  4. package/dist/index.js +356 -1
  5. package/dist/static/icons/symbol-defs.svg +56 -20
  6. package/dist/sw.js +1 -1
  7. package/dist/sw.js.map +1 -1
  8. package/dist/templates/components-body.html +1 -1
  9. package/dist/templates/components-head.html +1 -1
  10. package/out-tsc/src/anchor/Anchor.js +25 -0
  11. package/out-tsc/src/anchor/Anchor.js.map +1 -0
  12. package/out-tsc/src/contacts/ContactDetails.js +9 -5
  13. package/out-tsc/src/contacts/ContactDetails.js.map +1 -1
  14. package/out-tsc/src/contacts/ContactHistory.js +1 -5
  15. package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
  16. package/out-tsc/src/contacts/events.js +33 -7
  17. package/out-tsc/src/contacts/events.js.map +1 -1
  18. package/out-tsc/src/dialog/Modax.js +11 -2
  19. package/out-tsc/src/dialog/Modax.js.map +1 -1
  20. package/out-tsc/src/list/TembaMenu.js +384 -81
  21. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  22. package/out-tsc/src/utils/index.js +13 -14
  23. package/out-tsc/src/utils/index.js.map +1 -1
  24. package/out-tsc/src/vectoricon/VectorIcon.js +2 -1
  25. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  26. package/out-tsc/temba-modules.js +2 -0
  27. package/out-tsc/temba-modules.js.map +1 -1
  28. package/out-tsc/test/temba-menu.test.js +0 -13
  29. package/out-tsc/test/temba-menu.test.js.map +1 -1
  30. package/package.json +4 -4
  31. package/screenshots/truth/contacts/history-expanded.png +0 -0
  32. package/screenshots/truth/list/items-selected.png +0 -0
  33. package/screenshots/truth/list/items-updated.png +0 -0
  34. package/screenshots/truth/list/items.png +0 -0
  35. package/screenshots/truth/list/menu-submenu.png +0 -0
  36. package/src/anchor/Anchor.ts +26 -0
  37. package/src/contacts/ContactDetails.ts +9 -5
  38. package/src/contacts/ContactHistory.ts +0 -4
  39. package/src/contacts/events.ts +33 -7
  40. package/src/dialog/Modax.ts +11 -2
  41. package/src/list/TembaMenu.ts +424 -93
  42. package/src/utils/index.ts +17 -16
  43. package/src/vectoricon/VectorIcon.ts +2 -1
  44. package/static/icons/Read Me.txt +1 -1
  45. package/static/icons/SVG/channel.svg +5 -0
  46. package/static/icons/SVG/cloud1.svg +5 -0
  47. package/static/icons/SVG/codepen.svg +5 -0
  48. package/static/icons/SVG/codesandbox.svg +5 -0
  49. package/static/icons/SVG/git-pull-request.svg +5 -0
  50. package/static/icons/SVG/grid.svg +5 -0
  51. package/static/icons/SVG/hard-drive.svg +5 -0
  52. package/static/icons/SVG/layout.svg +5 -0
  53. package/static/icons/SVG/list.svg +5 -0
  54. package/static/icons/SVG/map-pin.svg +5 -0
  55. package/static/icons/SVG/menu.svg +5 -0
  56. package/static/icons/SVG/package.svg +5 -0
  57. package/static/icons/SVG/zapier.svg +5 -0
  58. package/static/icons/demo-external-svg.html +232 -172
  59. package/static/icons/demo-files/demo.css +4 -4
  60. package/static/icons/demo.html +288 -192
  61. package/static/icons/selection.json +646 -345
  62. package/static/icons/style.css +4 -4
  63. package/static/icons/symbol-defs.svg +56 -20
  64. package/temba-modules.ts +2 -0
  65. package/test/temba-menu.test.ts +0 -16
  66. package/test-assets/style.css +1 -1
  67. package/dist/b10b5805.js +0 -1
  68. package/static/icons/SVG/zendesk1.svg +0 -5
@@ -1,18 +1,15 @@
1
1
  import { css, html, property, TemplateResult } from 'lit-element';
2
2
  import { CustomEventType } from '../interfaces';
3
3
  import { RapidElement } from '../RapidElement';
4
- import {
5
- COOKIE_KEYS,
6
- fetchResults,
7
- getCookieBoolean,
8
- setCookie,
9
- } from '../utils';
4
+ import { fetchResults, getClasses } from '../utils';
10
5
 
11
6
  export interface MenuItem {
12
7
  id?: string;
8
+ vanity_id?: string;
13
9
  name?: string;
14
10
  count?: number;
15
11
  icon?: string;
12
+ collapsed_icon?: string;
16
13
  endpoint?: string;
17
14
  loading?: boolean;
18
15
  bottom?: boolean;
@@ -21,8 +18,18 @@ export interface MenuItem {
21
18
  href?: string;
22
19
  items?: MenuItem[];
23
20
  inline?: boolean;
21
+ type?: string;
24
22
  }
25
23
 
24
+ interface MenuItemState {
25
+ collapsed?: string;
26
+ }
27
+
28
+ const findItem = (items: MenuItem[], id: string) =>
29
+ (items || []).find((item: MenuItem) => {
30
+ return item.id == id || item.vanity_id == id;
31
+ });
32
+
26
33
  export class TembaMenu extends RapidElement {
27
34
  static get styles() {
28
35
  return css`
@@ -58,6 +65,7 @@ export class TembaMenu extends RapidElement {
58
65
  -webkit-user-select: none;
59
66
  display: flex;
60
67
  font-size: 1.15em;
68
+ --icon-color: var(--color-text-dark);
61
69
  }
62
70
 
63
71
  .item.selected {
@@ -106,8 +114,8 @@ export class TembaMenu extends RapidElement {
106
114
  background: var(--color-primary-dark);
107
115
  }
108
116
 
109
- .level-0 > .item > .name {
110
- display: none;
117
+ .level-0 > .item > .details {
118
+ display: none !important;
111
119
  }
112
120
 
113
121
  .level-0.expanding {
@@ -135,23 +143,33 @@ export class TembaMenu extends RapidElement {
135
143
  margin-top: 0.1em;
136
144
  border-radius: var(--curvature);
137
145
  display: flex;
146
+
147
+ min-width: 12em;
148
+ max-width: 12em;
138
149
  }
139
150
 
140
151
  .item > temba-icon {
141
152
  margin-right: 0.5em;
142
153
  }
143
154
 
144
- .item > .name {
155
+ .item.inline > temba-icon {
156
+ // margin-right: 0em;
157
+ }
158
+
159
+ .item > .details > .name {
145
160
  flex-grow: 1;
146
161
  white-space: nowrap;
147
162
  overflow: hidden;
148
163
  text-overflow: ellipsis;
164
+ width: 0;
149
165
  }
150
166
 
151
167
  .level-0 > .item {
152
168
  padding: 1em 1em;
153
169
  margin-top: 0em;
154
170
  border-radius: 0px;
171
+ min-width: inherit;
172
+ max-width: inherit;
155
173
  }
156
174
 
157
175
  .level-0 > .item > temba-icon {
@@ -199,19 +217,175 @@ export class TembaMenu extends RapidElement {
199
217
  }
200
218
 
201
219
  .inline-children {
202
- margin-left: 0.5em;
220
+ // background: #ffffff;
221
+ padding: 0.5em;
222
+ border-bottom-right-radius: var(--curvature);
223
+ border-bottom-left-radius: var(--curvature);
224
+ font-size: 1rem;
225
+ margin-bottom: 0.75em;
226
+ border: 1px solid #f3f3f3;
227
+ // box-shadow: var(--shadow);
228
+ // margin-top: -1px;
229
+ z-index: 1000;
230
+ // margin-left: 1em;
231
+ border-top: none;
203
232
  }
204
233
 
205
- .item.selected.inline.child-selected {
206
- background: inherit;
234
+ .inline-children .item {
235
+ max-width: 11em !important;
236
+ min-width: 11em !important;
237
+ // border: 1px solid #f1f1f1;
238
+ // margin-top: 0.75em;
239
+ // margin-right: -1em;
240
+ // padding-right: 0;
241
+ }
242
+
243
+ .item.inline {
244
+ border: 0px solid transparent;
245
+ }
246
+
247
+ .item.inline.child-selected,
248
+ .item.inline.selected {
249
+ background: #f3f3f3;
250
+ border: 0px solid #f1f1f1;
251
+ border-bottom-right-radius: 0px !important;
252
+ border-bottom-left-radius: 0px !important;
253
+ z-index: 1000;
254
+ color: #444;
255
+ --icon-color: #444;
256
+ // box-shadow: var(--shadow);
207
257
  }
208
258
 
209
- .item.selected.inline {
259
+ .level-1,
260
+ .level-2 {
261
+ border-right: 1px solid rgba(0 0 0 / 8%);
262
+ box-shadow: rgb(0 0 0 / 6%) 4px 0px 6px 1px;
210
263
  }
211
264
 
212
265
  .level-1 {
213
266
  overflow-y: auto;
214
- width: 16em;
267
+ z-index: 1500;
268
+ }
269
+
270
+ .level-2 {
271
+ background: #fbfbfb;
272
+ overflow-y: auto;
273
+ z-index: 1000;
274
+ }
275
+
276
+ .level-2 .item .details {
277
+ overflow: hidden;
278
+ }
279
+
280
+ .level-2 .item {
281
+ min-width: 12em;
282
+ max-width: 12em;
283
+ }
284
+
285
+ .level-1 .item {
286
+ overflow: hidden;
287
+ max-width: 12em;
288
+ min-width: 12em;
289
+ min-height: 1.5em;
290
+ max-height: 1.5em;
291
+ transition: min-width var(--transition-speed) !important;
292
+ }
293
+
294
+ .level-1 .item .details {
295
+ }
296
+
297
+ .collapsed .item {
298
+ overflow: hidden;
299
+ min-width: 0;
300
+ margin: 0;
301
+ }
302
+
303
+ .item .details {
304
+ opacity: 1;
305
+ min-height: 1.5em;
306
+ max-height: 1.5em;
307
+ align-items: center;
308
+ }
309
+
310
+ .item .details .name {
311
+ }
312
+
313
+ .item temba-icon {
314
+ }
315
+
316
+ .collapsed .item {
317
+ margin-bottom: 0.5em;
318
+ }
319
+
320
+ .collapsed .item .details {
321
+ overflow: hidden;
322
+ max-height: 0em;
323
+ max-width: 0em;
324
+ }
325
+
326
+ .collapsed .item .details {
327
+ max-height: 0em;
328
+ }
329
+
330
+ .collapsed .item temba-icon {
331
+ margin-right: 0;
332
+ }
333
+
334
+ .section {
335
+ max-width: 12em;
336
+ }
337
+
338
+ .collapsed .section {
339
+ opacity: 0;
340
+ max-width: 0em;
341
+ max-height: 0.6em;
342
+ }
343
+
344
+ .collapsed.level-1 {
345
+ overflow: hidden;
346
+ padding: 0.5em;
347
+ --icon-color: #999;
348
+ }
349
+
350
+ .collapsed .item .right {
351
+ flex-grow: 1;
352
+ }
353
+
354
+ .collapse-icon {
355
+ display: none;
356
+ }
357
+
358
+ .collapsed .collapse-icon {
359
+ --icon-color: #ccc;
360
+ display: block;
361
+ }
362
+
363
+ .collapsed .item.iconless {
364
+ max-height: 0em;
365
+ padding: 0em;
366
+ min-height: 0em;
367
+ margin-bottom: 0em;
368
+ }
369
+
370
+ .divider {
371
+ height: 1px;
372
+ background: #f3f3f3;
373
+ margin: 0.5em 0.75em;
374
+ min-height: 1px;
375
+ }
376
+
377
+ .collapsed .divider {
378
+ height: 0;
379
+ margin: 0;
380
+ padding: 0;
381
+ min-height: 0px;
382
+ }
383
+
384
+ .sub-section {
385
+ font-size: 1.1rem;
386
+ color: #888;
387
+ margin-top: 1rem;
388
+ margin-left: 0.3rem;
215
389
  }
216
390
  `;
217
391
  }
@@ -219,12 +393,6 @@ export class TembaMenu extends RapidElement {
219
393
  @property({ type: Boolean })
220
394
  wraps = false;
221
395
 
222
- @property({ type: Boolean })
223
- collapsible = false;
224
-
225
- @property({ type: Boolean })
226
- collapsed: boolean;
227
-
228
396
  @property({ type: Boolean })
229
397
  wait: boolean;
230
398
 
@@ -247,10 +415,22 @@ export class TembaMenu extends RapidElement {
247
415
  root: MenuItem;
248
416
  selection: string[] = [];
249
417
  pending: string[] = [];
418
+ state: { [id: string]: MenuItemState } = {};
250
419
 
251
420
  constructor() {
252
421
  super();
253
- this.collapsed = getCookieBoolean(COOKIE_KEYS.MENU_COLLAPSED);
422
+ }
423
+
424
+ private getMenuItemState(id: string): MenuItemState {
425
+ let itemState = {};
426
+ if (id) {
427
+ itemState = this.state[id];
428
+ if (!itemState) {
429
+ itemState = {};
430
+ this.state[id] = itemState;
431
+ }
432
+ }
433
+ return itemState;
254
434
  }
255
435
 
256
436
  public updated(changes: Map<string, any>) {
@@ -281,19 +461,34 @@ export class TembaMenu extends RapidElement {
281
461
  while (path.length > 0) {
282
462
  const step = path.splice(0, 1)[0];
283
463
  if (items) {
284
- item = items.find(mi => mi.id == step);
464
+ item = findItem(items, step);
285
465
  if (item) {
286
466
  if (item.endpoint) {
287
467
  item.loading = true;
288
468
  const itemToUpdate = item;
289
469
  fetchResults(itemToUpdate.endpoint).then((updated: MenuItem[]) => {
290
- // for now we only deal with updating counts
291
- (itemToUpdate.items || []).forEach((existing: MenuItem) => {
292
- const updatedItem = updated.find(
293
- updatedItem => updatedItem.id === existing.id
294
- );
295
- existing.count = updatedItem.count;
296
- });
470
+ // for now we only deal with updating counts and names
471
+ (itemToUpdate.items || []).forEach(
472
+ (existing: MenuItem, index: number, items: []) => {
473
+ const updatedItem = findItem(updated, existing.id);
474
+
475
+ // we were removed!
476
+ if (!updatedItem) {
477
+ items.splice(index, 1);
478
+
479
+ if (
480
+ this.selection.length > 1 &&
481
+ this.selection[this.selection.length - 1] == existing.id
482
+ ) {
483
+ this.selection.splice(this.selection.length - 1, 1);
484
+ this.clickItem(this.selection[this.selection.length - 1]);
485
+ }
486
+ } else {
487
+ existing.count = updatedItem.count;
488
+ existing.name = updatedItem.name;
489
+ }
490
+ }
491
+ );
297
492
 
298
493
  itemToUpdate.loading = false;
299
494
  this.requestUpdate('root');
@@ -308,40 +503,72 @@ export class TembaMenu extends RapidElement {
308
503
  }
309
504
  }
310
505
 
506
+ private fireNoPath(missingId: string) {
507
+ const item = this.getMenuItem();
508
+
509
+ const details = {
510
+ item: item.id,
511
+ selection: '/' + this.selection.join('/'),
512
+ endpoint: item.endpoint,
513
+ path: missingId + '/' + this.pending.join('/') + document.location.search,
514
+ };
515
+
516
+ // remove any excess from our selection
517
+ const selection = this.selection.join('/');
518
+ selection.replace(details.path, '');
519
+ this.selection = selection.split('/');
520
+
521
+ this.fireCustomEvent(CustomEventType.NoPath, details);
522
+ this.pending = [];
523
+ this.requestUpdate('root');
524
+ }
525
+
311
526
  // eslint-disable-next-line @typescript-eslint/no-empty-function
312
- private loadItems(item: MenuItem) {
527
+ private loadItems(item: MenuItem, selectFirst = true) {
313
528
  if (item && item.endpoint) {
314
529
  item.loading = true;
315
530
  this.httpComplete = fetchResults(item.endpoint).then(
316
531
  (items: MenuItem[]) => {
317
532
  // update our item level
318
- items.forEach(subItem => (subItem.level = item.level + 1));
533
+ items.forEach(subItem => {
534
+ subItem.level = item.level + 1;
535
+ // if we came with preset items, set the level for them accordingly
536
+ if (subItem.items) {
537
+ subItem.items.forEach(
538
+ inlineItem => (inlineItem.level = item.level + 2)
539
+ );
540
+ }
541
+ });
542
+
319
543
  item.items = items;
320
544
  item.loading = false;
321
545
  this.requestUpdate('root');
546
+ this.scrollSelectedIntoView();
322
547
  if (this.pending && this.pending.length > 0) {
323
548
  // auto select the next pending click
324
549
  const nextId = this.pending.splice(0, 1)[0];
325
550
  if (nextId && items.length > 0) {
326
- const nextItem = items.find(item => item.id === nextId);
551
+ const nextItem = findItem(items, nextId);
327
552
  if (nextItem) {
328
553
  this.handleItemClicked(null, nextItem);
329
554
  } else {
330
- this.fireCustomEvent(CustomEventType.NoPath, {
331
- item: item.id,
332
- endpoint: item.endpoint,
333
- path: nextId + '/' + this.pending.join('/'),
334
- });
555
+ this.fireNoPath(nextId);
335
556
  }
336
557
  }
337
558
  } else {
338
559
  // auto select the first item
339
560
  if (
561
+ selectFirst &&
340
562
  items.length > 0 &&
341
563
  this.selection.length >= 1 &&
342
564
  !item.inline
343
565
  ) {
344
- this.handleItemClicked(null, items[0]);
566
+ for (const item of items) {
567
+ if (!item.type) {
568
+ this.handleItemClicked(null, item);
569
+ break;
570
+ }
571
+ }
345
572
  }
346
573
  }
347
574
  }
@@ -368,17 +595,17 @@ export class TembaMenu extends RapidElement {
368
595
 
369
596
  // update our selection
370
597
  if (menuItem.level >= this.selection.length) {
371
- this.selection.push(menuItem.id);
598
+ this.selection.push(menuItem.vanity_id || menuItem.id);
372
599
  } else {
373
600
  this.selection.splice(
374
601
  menuItem.level,
375
602
  this.selection.length - menuItem.level,
376
- menuItem.id
603
+ menuItem.vanity_id || menuItem.id
377
604
  );
378
605
  }
379
606
 
380
607
  if (menuItem.endpoint) {
381
- this.loadItems(menuItem);
608
+ this.loadItems(menuItem, !menuItem.href);
382
609
  this.dispatchEvent(new Event('change'));
383
610
  } else {
384
611
  this.dispatchEvent(new Event('change'));
@@ -388,27 +615,43 @@ export class TembaMenu extends RapidElement {
388
615
  const nextId = this.pending.splice(0, 1)[0];
389
616
  const item = this.getMenuItem();
390
617
  if (nextId && item && item.items && item.items.length > 0) {
391
- const nextItem = item.items.find(item => item.id === nextId);
618
+ const nextItem = findItem(item.items, nextId);
392
619
  if (nextItem) {
393
620
  this.handleItemClicked(null, nextItem);
394
621
  }
622
+ } else {
623
+ this.fireNoPath(nextId);
395
624
  }
396
625
  }
397
-
398
- this.pending = [];
399
626
  this.requestUpdate('root');
400
627
  }
401
628
  }
402
629
  }
403
630
 
404
- public clickItem(id: string) {
631
+ public scrollSelectedIntoView() {
632
+ // makes sure we are scrolled into view
633
+ window.setTimeout(() => {
634
+ const eles = this.shadowRoot.querySelectorAll('.selected');
635
+ eles.forEach(ele => {
636
+ ele.scrollIntoView({ block: 'end', behavior: 'smooth' });
637
+ });
638
+ }, 0);
639
+ }
640
+
641
+ public clickItem(id: string): boolean {
405
642
  const path = [...this.selection];
406
643
  path.splice(path.length - 1, 1, id);
407
644
  const item = this.getMenuItemForSelection(path);
408
- this.handleItemClicked(null, item);
645
+
646
+ if (item) {
647
+ this.handleItemClicked(null, item);
648
+ this.scrollSelectedIntoView();
649
+ return true;
650
+ }
651
+ return false;
409
652
  }
410
653
 
411
- public getMenuItem() {
654
+ public getMenuItem(): MenuItem {
412
655
  return this.getMenuItemForSelection([...this.selection]);
413
656
  }
414
657
 
@@ -419,7 +662,7 @@ export class TembaMenu extends RapidElement {
419
662
  while (path.length > 0) {
420
663
  const step = path.splice(0, 1)[0];
421
664
  if (items) {
422
- item = items.find(mi => mi.id == step);
665
+ item = findItem(items, step);
423
666
  if (item) {
424
667
  items = item.items;
425
668
  } else {
@@ -447,19 +690,77 @@ export class TembaMenu extends RapidElement {
447
690
  }
448
691
  }
449
692
 
693
+ public setSelectionPath(path: string) {
694
+ const asPath = path.split('/').filter(step => !!step);
695
+
696
+ // first try to click in the current space
697
+ const clicked = this.clickItem(asPath[asPath.length - 1]);
698
+
699
+ if (!clicked) {
700
+ this.wait = true;
701
+ this.setSelection(asPath);
702
+ }
703
+ }
704
+
705
+ public async setFocusedItem(path: string) {
706
+ const focusedPath = path.split('/').filter(step => !!step);
707
+
708
+ // if we don't match at the first level, we are a noop
709
+ if (focusedPath.length > 0) {
710
+ const rootItem = findItem(this.root.items, focusedPath[0]);
711
+ if (!rootItem) {
712
+ return;
713
+ }
714
+ }
715
+
716
+ const newPath = [];
717
+ let level = this.root;
718
+ while (focusedPath.length > 0) {
719
+ const nextId = focusedPath.shift();
720
+ if (nextId) {
721
+ if (!level.items) {
722
+ this.loadItems(level, false);
723
+ await this.httpComplete;
724
+ }
725
+
726
+ level = findItem(level.items, nextId);
727
+ if (!level) {
728
+ focusedPath.splice(0, focusedPath.length);
729
+ } else {
730
+ newPath.push(nextId);
731
+ }
732
+ }
733
+ }
734
+
735
+ this.selection = newPath;
736
+ this.requestUpdate('root');
737
+ }
738
+
450
739
  private isSelected(menuItem: MenuItem) {
451
740
  if (menuItem.level < this.selection.length) {
452
- return this.selection[menuItem.level] == menuItem.id;
741
+ const selected =
742
+ this.selection[menuItem.level] == (menuItem.vanity_id || menuItem.id);
743
+ return selected;
453
744
  }
454
745
  return false;
455
746
  }
456
747
 
457
748
  private isExpanded(menuItem: MenuItem) {
458
- const expanded = !!this.selection.find(id => menuItem.id === id);
749
+ const expanded = !!this.selection.find(
750
+ id => id === menuItem.vanity_id || menuItem.id
751
+ );
459
752
  return expanded;
460
753
  }
461
754
 
462
755
  private renderMenuItem = (menuItem: MenuItem): TemplateResult => {
756
+ if (menuItem.type === 'divider') {
757
+ return html`<div class="divider"></div>`;
758
+ }
759
+
760
+ if (menuItem.type === 'section') {
761
+ return html`<div class="sub-section">${menuItem.name}</div>`;
762
+ }
763
+
463
764
  const isSelected = this.isSelected(menuItem);
464
765
  const isChildSelected =
465
766
  isSelected && this.selection.length > menuItem.level + 1;
@@ -471,44 +772,71 @@ export class TembaMenu extends RapidElement {
471
772
  ></temba-icon>`
472
773
  : null;
473
774
 
775
+ const collapsedIcon = menuItem.collapsed_icon
776
+ ? html`<temba-icon
777
+ size="${menuItem.level === 0 ? '1.5' : '1'}"
778
+ name="${menuItem.collapsed_icon}"
779
+ class="collapse-icon"
780
+ ></temba-icon>`
781
+ : null;
782
+
783
+ const itemClasses = getClasses({
784
+ ['menu-' + menuItem.id]: true,
785
+ 'child-selected': isChildSelected,
786
+ selected: isSelected,
787
+ item: true,
788
+ inline: menuItem.inline,
789
+ expanding: this.expanding && this.expanding === menuItem.id,
790
+ expanded: this.isExpanded(menuItem),
791
+ iconless: !icon && !collapsedIcon,
792
+ });
793
+
474
794
  const item = html` <div
475
- class="item-top ${isSelected ? 'selected' : null}"
795
+ class="item-top ${isSelected ? 'selected' : null} "
476
796
  ></div>
477
797
 
478
798
  <div
479
799
  id="menu-${menuItem.id}"
480
- class="item ${isSelected ? 'selected' : ''} ${isChildSelected
481
- ? 'child-selected'
482
- : ''} ${menuItem.inline ? 'inline' : ''} ${this.expanding &&
483
- this.expanding === menuItem.id
484
- ? 'expanding'
485
- : ''} ${this.isExpanded(menuItem) ? 'expanded' : ''}"
800
+ class="${itemClasses}"
486
801
  @click=${event => {
487
802
  this.handleItemClicked(event, menuItem);
488
803
  }}
489
804
  >
490
- ${this.collapsed || menuItem.level === 0
805
+ ${menuItem.level === 0
491
806
  ? html`<temba-tip
492
807
  style="display:flex;"
493
808
  text="${menuItem.name}"
494
809
  position="right"
495
810
  >${icon}</temba-tip
496
811
  >`
497
- : icon}
498
-
499
- <div
500
- class="name"
501
- style="flex-grow:1; white-space: ${this.wraps ? 'normal' : 'nowrap'};"
502
- >
503
- ${menuItem.name}
812
+ : html`${icon}${collapsedIcon}`}
813
+
814
+ <div class="details" style="flex-grow:1;display:flex">
815
+ <div
816
+ class="name"
817
+ style="flex-grow:1; flex-shrink:0; white-space: ${this.wraps
818
+ ? 'normal'
819
+ : 'nowrap'};"
820
+ >
821
+ ${menuItem.name}
822
+ </div>
823
+ ${menuItem.level > 0
824
+ ? menuItem.inline
825
+ ? html`<temba-icon
826
+ name="chevron-${isSelected || isChildSelected
827
+ ? 'up'
828
+ : 'down'}"
829
+ ></temba-icon>`
830
+ : html`${menuItem.count || menuItem.count == 0
831
+ ? html`
832
+ <div class="count">
833
+ ${menuItem.count.toLocaleString()}
834
+ </div>
835
+ `
836
+ : html`<div class="count"></div>`}`
837
+ : null}
504
838
  </div>
505
- ${menuItem.level > 0
506
- ? html`${menuItem.count || menuItem.count == 0
507
- ? html`
508
- <div class="count">${menuItem.count.toLocaleString()}</div>
509
- `
510
- : html`<div class="count"></div>`}`
511
- : null}
839
+ <div class="right"></div>
512
840
  </div>
513
841
 
514
842
  <div class="item-bottom ${isSelected ? 'selected' : null}"></div>`;
@@ -516,12 +844,6 @@ export class TembaMenu extends RapidElement {
516
844
  return item;
517
845
  };
518
846
 
519
- public toggleCollapsed() {
520
- this.collapsed = !this.collapsed;
521
- setCookie(COOKIE_KEYS.MENU_COLLAPSED, this.collapsed);
522
- this.requestUpdate('root');
523
- }
524
-
525
847
  public render(): TemplateResult {
526
848
  if (!this.root || !this.root.items) {
527
849
  return html`<temba-loading
@@ -532,7 +854,7 @@ export class TembaMenu extends RapidElement {
532
854
  />`;
533
855
  }
534
856
 
535
- let items = this.root.items;
857
+ let items = this.root.items || [];
536
858
  const levels = [];
537
859
 
538
860
  levels.push(
@@ -556,21 +878,40 @@ export class TembaMenu extends RapidElement {
556
878
  );
557
879
 
558
880
  this.selection.forEach((id, index) => {
559
- const selected = (items || []).find(item => item.id === id);
881
+ const selected = findItem(items, id);
882
+
883
+ let collapsed = false;
560
884
  if (selected) {
561
885
  items = selected.items;
886
+ const itemState = this.getMenuItemState(selected.id);
887
+ // users set an explicit collapse state
888
+ if (itemState.collapsed) {
889
+ collapsed = itemState.collapsed === 'collapsed';
890
+ }
891
+ // otherwise pick a default collapse state
892
+ else {
893
+ if (this.selection.length > selected.level + 2) {
894
+ collapsed = false;
895
+ }
896
+ }
562
897
  } else {
563
898
  items = null;
564
899
  }
565
900
 
566
901
  if (items && items.length > 0 && !selected.inline) {
567
902
  levels.push(
568
- html`<div class="level level-${index + 1}">
569
- ${index == 0 && !this.submenu
903
+ html`<div
904
+ class="${getClasses({
905
+ level: true,
906
+ ['level-' + (index + 1)]: true,
907
+ collapsed,
908
+ })}"
909
+ >
910
+ ${!this.submenu
570
911
  ? html`<div class="section">${selected.name}</div>`
571
912
  : null}
572
913
  ${items.map((item: MenuItem) => {
573
- if (item.inline && item.items && this.isSelected(item)) {
914
+ if (item.inline && this.isSelected(item)) {
574
915
  return html`${this.renderMenuItem(item)}
575
916
  <div class="inline-children">
576
917
  ${item.items.map((child: MenuItem) => {
@@ -586,16 +927,6 @@ export class TembaMenu extends RapidElement {
586
927
  });
587
928
 
588
929
  const menu = html`<div class="root">${levels}</div>`;
589
-
590
- if (this.collapsible) {
591
- return html`
592
- <div style="display:flex">
593
- ${menu}
594
- <div class="collapse-toggle" @click=${this.toggleCollapsed}></div>
595
- </div>
596
- `;
597
- }
598
-
599
930
  return html`${menu}`;
600
931
  }
601
932
  }